Bug 1060529, send the enabled state of child process commands to the parent on update, without the test, r=smaug,ehsan
authorNeil Deakin <neil@mozilla.com>
Tue, 09 Dec 2014 10:48:27 -0500
changeset 218861 6940b3bf61ad3bec8d782ddbfa2197cd08298612
parent 218860 bc5ad20e8f27b76e4f54d434c40044bbcb394892
child 218862 36d257ead3da3227fe8e9c316d9ebbb302460849
push id52661
push userneil@mozilla.com
push dateTue, 09 Dec 2014 15:49:02 +0000
treeherdermozilla-inbound@6940b3bf61ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, ehsan
bugs1060529
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1060529, send the enabled state of child process commands to the parent on update, without the test, r=smaug,ehsan
dom/base/nsGlobalWindow.cpp
dom/base/nsPIWindowRoot.h
dom/base/nsWindowRoot.cpp
dom/base/nsWindowRoot.h
dom/interfaces/base/moz.build
dom/interfaces/base/nsIRemoteBrowser.idl
dom/interfaces/base/nsITabChild.idl
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/xul/nsIController.idl
embedding/components/commandhandler/nsBaseCommandController.cpp
embedding/components/commandhandler/nsControllerCommandTable.cpp
embedding/components/commandhandler/nsIControllerCommandTable.idl
toolkit/content/widgets/remote-browser.xml
toolkit/modules/RemoteController.jsm
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -9267,16 +9267,46 @@ nsGlobalWindow::ShowModalDialog(const ns
 
   ErrorResult rv;
   nsCOMPtr<nsIVariant> retVal = ShowModalDialog(aURI, aArgs, aOptions, rv);
   retVal.forget(aRetVal);
 
   return rv.ErrorCode();
 }
 
+class ChildCommandDispatcher : public nsRunnable
+{
+public:
+  ChildCommandDispatcher(nsGlobalWindow* aWindow,
+                         nsITabChild* aTabChild,
+                         const nsAString& aAction)
+  : mWindow(aWindow), mTabChild(aTabChild), mAction(aAction) {}
+
+  NS_IMETHOD Run()
+  {
+    nsCOMPtr<nsPIWindowRoot> root = mWindow->GetTopWindowRoot();
+    if (!root) {
+      return NS_OK;
+    }
+
+    nsTArray<nsCString> enabledCommands, disabledCommands;
+    root->GetEnabledDisabledCommands(enabledCommands, disabledCommands);
+    if (enabledCommands.Length() || disabledCommands.Length()) {
+      mTabChild->EnableDisableCommands(mAction, enabledCommands, disabledCommands);
+    }
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<nsGlobalWindow>             mWindow;
+  nsCOMPtr<nsITabChild>                mTabChild;
+  nsString                             mAction;
+};
+
 class CommandDispatcher : public nsRunnable
 {
 public:
   CommandDispatcher(nsIDOMXULCommandDispatcher* aDispatcher,
                     const nsAString& aAction)
   : mDispatcher(aDispatcher), mAction(aAction) {}
 
   NS_IMETHOD Run()
@@ -9286,16 +9316,22 @@ public:
 
   nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher;
   nsString                             mAction;
 };
 
 NS_IMETHODIMP
 nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason)
 {
+  // If this is a child process, redirect to the parent process.
+  if (nsCOMPtr<nsITabChild> child = do_GetInterface(GetDocShell())) {
+    nsContentUtils::AddScriptRunner(new ChildCommandDispatcher(this, child, anAction));
+    return NS_OK;
+  }
+
   nsPIDOMWindow *rootWindow = nsGlobalWindow::GetPrivateRoot();
   if (!rootWindow)
     return NS_OK;
 
   nsCOMPtr<nsIDOMXULDocument> xulDoc =
     do_QueryInterface(rootWindow->GetExtantDoc());
   // See if we contain a XUL document.
   // selectionchange action is only used for mozbrowser, not for XUL. So we bypass
--- a/dom/base/nsPIWindowRoot.h
+++ b/dom/base/nsPIWindowRoot.h
@@ -10,34 +10,37 @@
 #include "nsISupports.h"
 #include "mozilla/dom/EventTarget.h"
 
 class nsPIDOMWindow;
 class nsIControllers;
 class nsIController;
 
 #define NS_IWINDOWROOT_IID \
-{ 0x3f71f50c, 0xa7e0, 0x43bc, \
- { 0xac, 0x25, 0x4d, 0xbb, 0x88, 0x7b, 0x21, 0x09 } }
+{ 0x728a2682, 0x55c0, 0x4860, \
+ { 0x82, 0x6b, 0x0c, 0x30, 0x0a, 0xac, 0xaa, 0x60 } }
 
 class nsPIWindowRoot : public mozilla::dom::EventTarget
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IWINDOWROOT_IID)
 
   virtual nsPIDOMWindow* GetWindow()=0;
 
   // get and set the node that is the context of a popup menu
   virtual nsIDOMNode* GetPopupNode() = 0;
   virtual void SetPopupNode(nsIDOMNode* aNode) = 0;
 
   virtual nsresult GetControllerForCommand(const char *aCommand,
                                            nsIController** aResult) = 0;
   virtual nsresult GetControllers(nsIControllers** aResult) = 0;
 
+  virtual void GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
+                                          nsTArray<nsCString>& aDisabledCommands) = 0;
+
   virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) = 0;
   virtual mozilla::dom::EventTarget* GetParentTarget() = 0;
   virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIWindowRoot, NS_IWINDOWROOT_IID)
 
 #endif // nsPIWindowRoot_h__
--- a/dom/base/nsWindowRoot.cpp
+++ b/dom/base/nsWindowRoot.cpp
@@ -276,16 +276,85 @@ nsWindowRoot::GetControllerForCommand(co
     // XXXndeakin P3 is this casting safe?
     nsGlobalWindow *win = static_cast<nsGlobalWindow*>(focusedWindow.get());
     focusedWindow = win->GetPrivateParent();
   }
   
   return NS_OK;
 }
 
+void
+nsWindowRoot::GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers,
+                                                       nsTHashtable<nsCharPtrHashKey>& aCommandsHandled,
+                                                       nsTArray<nsCString>& aEnabledCommands,
+                                                       nsTArray<nsCString>& aDisabledCommands)
+{
+  uint32_t controllerCount;
+  aControllers->GetControllerCount(&controllerCount);
+  for (uint32_t c = 0; c < controllerCount; c++) {
+    nsCOMPtr<nsIController> controller;
+    aControllers->GetControllerAt(c, getter_AddRefs(controller));
+
+    nsCOMPtr<nsICommandController> commandController(do_QueryInterface(controller));
+    if (commandController) {
+      uint32_t commandsCount;
+      char** commands;
+      if (NS_SUCCEEDED(commandController->GetSupportedCommands(&commandsCount, &commands))) {
+        for (uint32_t e = 0; e < commandsCount; e++) {
+          // Use a hash to determine which commands have already been handled by
+          // earlier controllers, as the earlier controller's result should get
+          // priority.
+          if (!aCommandsHandled.Contains(commands[e])) {
+            aCommandsHandled.PutEntry(commands[e]);
+
+            bool enabled = false;
+            controller->IsCommandEnabled(commands[e], &enabled);
+
+            const nsDependentCSubstring commandStr(commands[e], strlen(commands[e]));
+            if (enabled) {
+              aEnabledCommands.AppendElement(commandStr);
+            } else {
+              aDisabledCommands.AppendElement(commandStr);
+            }
+          }
+        }
+
+        NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(commandsCount, commands);
+      }
+    }
+  }
+}
+
+void
+nsWindowRoot::GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
+                                         nsTArray<nsCString>& aDisabledCommands)
+{
+  nsTHashtable<nsCharPtrHashKey> commandsHandled;
+
+  nsCOMPtr<nsIControllers> controllers;
+  GetControllers(getter_AddRefs(controllers));
+  if (controllers) {
+    GetEnabledDisabledCommandsForControllers(controllers, commandsHandled,
+                                             aEnabledCommands, aDisabledCommands);
+  }
+
+  nsCOMPtr<nsPIDOMWindow> focusedWindow;
+  nsFocusManager::GetFocusedDescendant(mWindow, true, getter_AddRefs(focusedWindow));
+  while (focusedWindow) {
+    focusedWindow->GetControllers(getter_AddRefs(controllers));
+    if (controllers) {
+      GetEnabledDisabledCommandsForControllers(controllers, commandsHandled,
+                                               aEnabledCommands, aDisabledCommands);
+    }
+
+    nsGlobalWindow* win = static_cast<nsGlobalWindow*>(focusedWindow.get());
+    focusedWindow = win->GetPrivateParent();
+  }
+}
+
 nsIDOMNode*
 nsWindowRoot::GetPopupNode()
 {
   return mPopupNode;
 }
 
 void
 nsWindowRoot::SetPopupNode(nsIDOMNode* aNode)
--- a/dom/base/nsWindowRoot.h
+++ b/dom/base/nsWindowRoot.h
@@ -18,16 +18,18 @@ class EventChainPreVisitor;
 } // namespace mozilla
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsIDOMEventTarget.h"
 #include "nsPIWindowRoot.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
 
 class nsWindowRoot : public nsPIWindowRoot
 {
 public:
   explicit nsWindowRoot(nsPIDOMWindow* aWindow);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIDOMEVENTTARGET
@@ -47,16 +49,19 @@ public:
   // nsPIWindowRoot
 
   virtual nsPIDOMWindow* GetWindow() MOZ_OVERRIDE;
 
   virtual nsresult GetControllers(nsIControllers** aResult) MOZ_OVERRIDE;
   virtual nsresult GetControllerForCommand(const char * aCommand,
                                            nsIController** _retval) MOZ_OVERRIDE;
 
+  virtual void GetEnabledDisabledCommands(nsTArray<nsCString>& aEnabledCommands,
+                                          nsTArray<nsCString>& aDisabledCommands) MOZ_OVERRIDE;
+
   virtual nsIDOMNode* GetPopupNode() MOZ_OVERRIDE;
   virtual void SetPopupNode(nsIDOMNode* aNode) MOZ_OVERRIDE;
 
   virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) MOZ_OVERRIDE
   {
     mParent = aTarget;
   }
   virtual mozilla::dom::EventTarget* GetParentTarget() MOZ_OVERRIDE { return mParent; }
@@ -67,16 +72,21 @@ public:
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsWindowRoot,
                                                          nsIDOMEventTarget)
 
 protected:
   virtual ~nsWindowRoot();
 
+  void GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers,
+                                                nsTHashtable<nsCharPtrHashKey>& aCommandsHandled,
+                                                nsTArray<nsCString>& aEnabledCommands,
+                                                nsTArray<nsCString>& aDisabledCommands);
+
   // Members
   nsCOMPtr<nsPIDOMWindow> mWindow;
   // We own the manager, which owns event listeners attached to us.
   nsRefPtr<mozilla::EventListenerManager> mListenerManager; // [Strong]
   nsCOMPtr<nsIDOMNode> mPopupNode; // [OWNER]
 
   nsCOMPtr<mozilla::dom::EventTarget> mParent;
 };
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -26,16 +26,17 @@ XPIDL_SOURCES += [
     'nsIDOMScreen.idl',
     'nsIDOMWindow.idl',
     'nsIDOMWindowCollection.idl',
     'nsIDOMWindowUtils.idl',
     'nsIFocusManager.idl',
     'nsIFrameRequestCallback.idl',
     'nsIIdleObserver.idl',
     'nsIQueryContentEventResult.idl',
+    'nsIRemoteBrowser.idl',
     'nsIServiceWorkerManager.idl',
     'nsIStructuredCloneContainer.idl',
     'nsITabChild.idl',
     'nsITabParent.idl',
 ]
 
 XPIDL_MODULE = 'dom_base'
 
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsIRemoteBrowser.idl
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(C8379366-F79F-4D25-89A6-22BEC0A93D16)]
+interface nsIRemoteBrowser : nsISupports
+{
+  /*
+   * Called by the child to inform the parent that a command update has occurred
+   * and the supplied set of commands are now enabled and disabled.
+   *
+   * @param action command updater action
+   * @param enabledLength length of enabledCommands array
+   * @param enabledCommands commands to enable
+   * @param disabledLength length of disabledCommands array
+   * @param disabledCommand commands to disable
+   */
+  void enableDisableCommands(in AString action,
+                             in unsigned long enabledLength,
+                             [array, size_is(enabledLength)] in string enabledCommands,
+                             in unsigned long disabledLength,
+                             [array, size_is(disabledLength)] in string disabledCommands);
+};
+
--- a/dom/interfaces/base/nsITabChild.idl
+++ b/dom/interfaces/base/nsITabChild.idl
@@ -2,18 +2,25 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #include "domstubs.idl"
 interface nsIContentFrameMessageManager;
 interface nsIWebBrowserChrome3;
 
-[scriptable, uuid(2eb3bc54-78bf-40f2-b301-a5b5b70f7da0)]
+native CommandsArray(nsTArray<nsCString>);
+[ref] native CommandsArrayRef(nsTArray<nsCString>);
+
+[scriptable, uuid(7227bac4-b6fe-4090-aeb4-bc288b790925)]
 interface nsITabChild : nsISupports
 {
   readonly attribute nsIContentFrameMessageManager messageManager;
 
   attribute nsIWebBrowserChrome3 webBrowserChrome;
 
   [notxpcom] void sendRequestFocus(in boolean canFocus);
+
+  [noscript, notxpcom] void enableDisableCommands(in AString action,
+                                                  in CommandsArrayRef enabledCommands,
+                                                  in CommandsArrayRef disabledCommands);
 };
 
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -239,16 +239,24 @@ parent:
     prio(urgent) sync EndIMEComposition(bool cancel) returns (nsString composition);
 
     /**
      * Request that the parent process move focus to the browser's frame. If
      * canRaise is true, the window can be raised if it is inactive.
      */
     RequestFocus(bool canRaise);
 
+    /**
+     * Indicate, based on the current state, that some commands are enabled and
+     * some are disabled.
+     */
+    EnableDisableCommands(nsString action,
+                          nsCString[] enabledCommands,
+                          nsCString[] disabledCommands);
+
     prio(urgent) sync GetInputContext() returns (int32_t IMEEnabled,
                                                  int32_t IMEOpen,
                                                  intptr_t NativeIMEContext);
 
     prio(urgent) async SetInputContext(int32_t IMEEnabled,
                                        int32_t IMEOpen,
                                        nsString type,
                                        nsString inputmode,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3264,16 +3264,25 @@ TabChild::SetWebBrowserChrome(nsIWebBrow
 }
 
 void
 TabChild::SendRequestFocus(bool aCanFocus)
 {
   PBrowserChild::SendRequestFocus(aCanFocus);
 }
 
+void
+TabChild::EnableDisableCommands(const nsAString& aAction,
+                                nsTArray<nsCString>& aEnabledCommands,
+                                nsTArray<nsCString>& aDisabledCommands)
+{
+  PBrowserChild::SendEnableDisableCommands(PromiseFlatString(aAction),
+                                           aEnabledCommands, aDisabledCommands);
+}
+
 bool
 TabChild::DoSendBlockingMessage(JSContext* aCx,
                                 const nsAString& aMessage,
                                 const StructuredCloneData& aData,
                                 JS::Handle<JSObject *> aCpows,
                                 nsIPrincipal* aPrincipal,
                                 InfallibleTArray<nsString>* aJSONRetVal,
                                 bool aIsSync)
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -42,16 +42,17 @@
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsILoadInfo.h"
 #include "nsIPromptFactory.h"
 #include "nsIURI.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIWindowCreator2.h"
 #include "nsIXULBrowserWindow.h"
 #include "nsIXULWindow.h"
+#include "nsIRemoteBrowser.h"
 #include "nsViewManager.h"
 #include "nsIWidget.h"
 #include "nsIWindowWatcher.h"
 #include "nsOpenURIInFrameParams.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowWatcher.h"
 #include "nsPresShell.h"
 #include "nsPrintfCString.h"
@@ -1466,16 +1467,47 @@ TabParent::RecvRequestFocus(const bool& 
   if (aCanRaise)
     flags |= nsIFocusManager::FLAG_RAISE;
 
   nsCOMPtr<nsIDOMElement> node = do_QueryInterface(mFrameElement);
   fm->SetFocus(node, flags);
   return true;
 }
 
+bool
+TabParent::RecvEnableDisableCommands(const nsString& aAction,
+                                     const nsTArray<nsCString>& aEnabledCommands,
+                                     const nsTArray<nsCString>& aDisabledCommands)
+{
+  nsCOMPtr<nsIRemoteBrowser> remoteBrowser = do_QueryInterface(mFrameElement);
+  if (remoteBrowser) {
+    nsAutoArrayPtr<const char*> enabledCommands, disabledCommands;
+
+    if (aEnabledCommands.Length()) {
+      enabledCommands = new const char* [aEnabledCommands.Length()];
+      for (uint32_t c = 0; c < aEnabledCommands.Length(); c++) {
+        enabledCommands[c] = aEnabledCommands[c].get();
+      }
+    }
+
+    if (aDisabledCommands.Length()) {
+      disabledCommands = new const char* [aDisabledCommands.Length()];
+      for (uint32_t c = 0; c < aDisabledCommands.Length(); c++) {
+        disabledCommands[c] = aDisabledCommands[c].get();
+      }
+    }
+
+    remoteBrowser->EnableDisableCommands(aAction,
+                                         aEnabledCommands.Length(), enabledCommands,
+                                         aDisabledCommands.Length(), disabledCommands);
+  }
+
+  return true;
+}
+
 nsIntPoint
 TabParent::GetChildProcessOffset()
 {
   // The "toplevel widget" in child processes is always at position
   // 0,0.  Map the event coordinates to match that.
 
   nsIntPoint offset(0, 0);
   nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -186,16 +186,19 @@ public:
     virtual bool RecvSetInputContext(const int32_t& aIMEEnabled,
                                      const int32_t& aIMEOpen,
                                      const nsString& aType,
                                      const nsString& aInputmode,
                                      const nsString& aActionHint,
                                      const int32_t& aCause,
                                      const int32_t& aFocusChange) MOZ_OVERRIDE;
     virtual bool RecvRequestFocus(const bool& aCanRaise) MOZ_OVERRIDE;
+    virtual bool RecvEnableDisableCommands(const nsString& aAction,
+                                           const nsTArray<nsCString>& aEnabledCommands,
+                                           const nsTArray<nsCString>& aDisabledCommands) MOZ_OVERRIDE;
     virtual bool RecvSetCursor(const uint32_t& aValue, const bool& aForce) MOZ_OVERRIDE;
     virtual bool RecvSetBackgroundColor(const nscolor& aValue) MOZ_OVERRIDE;
     virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus) MOZ_OVERRIDE;
     virtual bool RecvIsParentWindowMainWidgetVisible(bool* aIsVisible);
     virtual bool RecvShowTooltip(const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip);
     virtual bool RecvHideTooltip();
     virtual bool RecvGetDPI(float* aValue) MOZ_OVERRIDE;
     virtual bool RecvGetDefaultScale(double* aValue) MOZ_OVERRIDE;
--- a/dom/xul/nsIController.idl
+++ b/dom/xul/nsIController.idl
@@ -2,42 +2,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 [scriptable, uuid(D5B61B82-1DA4-11d3-BF87-00105A1B0627)]
 interface nsIController : nsISupports {
-	boolean isCommandEnabled(in string command);
-	boolean supportsCommand(in string command);
+  boolean isCommandEnabled(in string command);
+  boolean supportsCommand(in string command);
 
-	void doCommand(in string command);
+  void doCommand(in string command);
 
-	void onEvent(in string eventName);
+  void onEvent(in string eventName);
 };
 
 
 /*
 
   Enhanced controller interface that allows for passing of parameters
   to commands.
   
 */
 
 interface nsICommandParams;
 
-[scriptable, uuid(EBE55080-C8A9-11D5-A73C-DD620D6E04BC)]
+[scriptable, uuid(EEC0B435-7F53-44FE-B00A-CF3EED65C01A)]
 interface nsICommandController : nsISupports
 {
   
   void        getCommandStateWithParams( in string command, in nsICommandParams aCommandParams);
     
   void        doCommandWithParams(in string command, in nsICommandParams aCommandParams);
 
+  void getSupportedCommands(out unsigned long count,
+                            [array, size_is(count), retval] out string commands);
 };
 
 
 /*
   An API for registering commands in groups, to allow for 
   updating via nsIDOMWindow::UpdateCommands.
 */
 interface nsISimpleEnumerator;
--- a/embedding/components/commandhandler/nsBaseCommandController.cpp
+++ b/embedding/components/commandhandler/nsBaseCommandController.cpp
@@ -169,8 +169,15 @@ nsBaseCommandController::GetCommandState
 }
 
 NS_IMETHODIMP
 nsBaseCommandController::OnEvent(const char * aEventName)
 {
   NS_ENSURE_ARG_POINTER(aEventName);
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsBaseCommandController::GetSupportedCommands(uint32_t* aCount, char*** aCommands)
+{
+  NS_ENSURE_STATE(mCommandTable);
+  return mCommandTable->GetSupportedCommands(aCount, aCommands);
+}
--- a/embedding/components/commandhandler/nsControllerCommandTable.cpp
+++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp
@@ -183,16 +183,40 @@ nsControllerCommandTable::GetCommandStat
 #if DEBUG
     NS_WARNING("Controller command table asked to do a command that it does not handle -- ");
 #endif
     return NS_OK;    // we don't handle this command
   }
   return commandHandler->GetCommandStateParams(aCommandName, aParams, aCommandRefCon);
 }
 
+static PLDHashOperator
+AddCommand(const nsACString& aKey, nsIControllerCommand* aData, void* aArg)
+{
+  // aArg is a pointer to a array of strings. It gets incremented after
+  // allocating each one so that it points to the next location for AddCommand
+  // to assign a string to.
+  char*** commands = static_cast<char***>(aArg);
+  (**commands) = ToNewCString(aKey);
+  (*commands)++;
+  return PL_DHASH_NEXT;
+}
+
+NS_IMETHODIMP
+nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount,
+                                               char*** aCommands)
+{
+  char** commands =
+    static_cast<char **>(NS_Alloc(sizeof(char *) * mCommandsTable.Count()));
+  *aCount = mCommandsTable.Count();
+  *aCommands = commands;
+
+  mCommandsTable.EnumerateRead(AddCommand, &commands);
+  return NS_OK;
+}
 
 nsresult
 NS_NewControllerCommandTable(nsIControllerCommandTable** aResult)
 {
   NS_PRECONDITION(aResult != nullptr, "null ptr");
   if (! aResult)
     return NS_ERROR_NULL_POINTER;
 
--- a/embedding/components/commandhandler/nsIControllerCommandTable.idl
+++ b/embedding/components/commandhandler/nsIControllerCommandTable.idl
@@ -13,17 +13,17 @@
  * and efficiently dispatch commands to their respective handlers.
  *
  * Controllers that use an nsIControllerCommandTable should support
  * nsIInterfaceRequestor, and be able to return an interface to their
  * controller command table via getInterface().
  * 
  */
 
-[scriptable, uuid(d1a47834-6ad4-11d7-bfad-000393636592)]
+[scriptable, uuid(c847f90e-b8f3-49db-a4df-8867831f2800)]
 interface nsIControllerCommandTable : nsISupports
 {
   /**
    * Make this command table immutable, so that commands cannot
    * be registered or unregistered. Some command tables are made
    * mutable after command registration so that they can be 
    * used as singletons.
    */
@@ -77,16 +77,19 @@ interface nsIControllerCommandTable : ns
    * @param aCommandName    the name of the command to execute
    * @param aCommandRefCon  the command context data
    */
 	void    doCommand(in string aCommandName, in nsISupports aCommandRefCon);
 
 	void    doCommandParams(in string aCommandName, in nsICommandParams aParam, in nsISupports aCommandRefCon);
 
 	void    getCommandState(in string aCommandName, in nsICommandParams aParam, in nsISupports aCommandRefCon);
+
+  void getSupportedCommands(out unsigned long count,
+                            [array, size_is(count), retval] out string commands);
 };
 
 
 
 %{C++
 // {670ee5da-6ad5-11d7-9950-000393636592}
 #define NS_CONTROLLERCOMMANDTABLE_CID \
   {0x670ee5da, 0x6ad5, 0x11d7, \
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -5,17 +5,18 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <bindings id="firefoxBrowserBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <binding id="remote-browser" extends="chrome://global/content/bindings/browser.xml#browser">
 
-    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener">
+    <implementation type="application/javascript"
+                    implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIRemoteBrowser">
 
       <field name="_securityUI">null</field>
 
       <property name="securityUI"
                 readonly="true">
         <getter><![CDATA[
           if (!this._securityUI) {
             let jsm = "resource://gre/modules/RemoteSecurityUI.jsm";
@@ -367,13 +368,28 @@
         <body>
         <![CDATA[
           return { x: aScreenX + this.boxObject.screenX,
                    y: aScreenY + this.boxObject.screenY };
         ]]>
         </body>
       </method>
 
+      <method name="enableDisableCommands">
+        <parameter name="aAction"/>
+        <parameter name="aEnabledLength"/>
+        <parameter name="aEnabledCommands"/>
+        <parameter name="aDisabledLength"/>
+        <parameter name="aDisabledCommands"/>
+        <body>
+          if (this._controller) {
+            this._controller.enableDisableCommands(aAction,
+                                                   aEnabledLength, aEnabledCommands,
+                                                   aDisabledLength, aDisabledCommands);
+          }
+        </body>
+      </method>
+
     </implementation>
 
   </binding>
 
 </bindings>
--- a/toolkit/modules/RemoteController.jsm
+++ b/toolkit/modules/RemoteController.jsm
@@ -9,50 +9,50 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 function RemoteController(browser)
 {
   this._browser = browser;
+
+  // A map of commands that have had their enabled/disabled state assigned. The
+  // value of each key will be true if enabled, and false if disabled.
+  this._supportedCommands = { };
 }
 
 RemoteController.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIController]),
 
   isCommandEnabled: function(aCommand) {
-    // We can't synchronously ask content if a command is enabled,
-    // so we always pretend is.
-    // The right way forward would be to never use nsIController
-    // to ask if something in content is enabled. Maybe even
-    // by replacing the nsIController architecture by something else.
-    // See bug 905768.
-    return true;
+    return this._supportedCommands[aCommand] || false;
   },
 
   supportsCommand: function(aCommand) {
-    // Optimize the lookup a bit.
-    if (!aCommand.startsWith("cmd_"))
-      return false;
-
-    // For now only support the commands used in "browser-context.inc"
-    let commands = [
-      "cmd_copyLink",
-      "cmd_copyImage",
-      "cmd_undo",
-      "cmd_cut",
-      "cmd_copy",
-      "cmd_paste",
-      "cmd_delete",
-      "cmd_selectAll",
-      "cmd_switchTextDirection"
-    ];
-
-    return commands.indexOf(aCommand) >= 0;
+    return aCommand in this._supportedCommands;
   },
 
   doCommand: function(aCommand) {
     this._browser.messageManager.sendAsyncMessage("ControllerCommands:Do", aCommand);
   },
 
-  onEvent: function () {}
+  onEvent: function () {},
+
+  // This is intended to be called from the remote-browser binding to update
+  // the enabled and disabled commands.
+  enableDisableCommands: function(aAction,
+                                  aEnabledLength, aEnabledCommands,
+                                  aDisabledLength, aDisabledCommands) {
+    // Clear the list first
+    this._supportedCommands = { };
+
+    for (let c = 0; c < aEnabledLength; c++) {
+      this._supportedCommands[aEnabledCommands[c]] = true;
+    }
+
+    for (let c = 0; c < aDisabledLength; c++) {
+      this._supportedCommands[aDisabledCommands[c]] = false;
+    }
+
+    this._browser.ownerDocument.defaultView.updateCommands(aAction);
+  }
 };