Bug 1060529, send the enabled state of child process commands to the parent on update, r=smaug,ehsan
☠☠ backed out by 36e3a60e3dc6 ☠ ☠
authorNeil Deakin <neil@mozilla.com>
Fri, 07 Nov 2014 15:47:39 -0500
changeset 226134 a84fe0cefd77c06af4801613195ac00b4373880a
parent 226133 41536afea9b39595727186826e89032292dec5b7
child 226135 c2369f6c3a3557b59bfa90e013bc67c25537fb80
push id36
push userdburns@mozilla.com
push dateMon, 10 Nov 2014 15:14:02 +0000
reviewerssmaug, ehsan
bugs1060529
milestone36.0a1
Bug 1060529, send the enabled state of child process commands to the parent on update, r=smaug,ehsan
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_updatecommands.js
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/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -493,8 +493,10 @@ skip-if = e10s
 [browser_addCertException.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
 [browser_bug1045809.js]
 [browser_e10s_switchbrowser.js]
 [browser_blockHPKP.js]
 skip-if = e10s # bug ?????? - test directly manipulates content (content.document.getElementById)
 [browser_mcb_redirect.js]
 skip-if = e10s # bug 1084504 - [e10s] Mixed content detection does not take redirection into account
+[browser_updatecommands.js]
+skip-if = os == "win" && e10s # times out on Windows 7 e10s intermittently
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_updatecommands.js
@@ -0,0 +1,89 @@
+let testPage = "data:text/html,<body><input id='input1' value='value'><select size=2><option val=1>One</select></body>";
+
+let browser;
+
+function test() {
+  waitForExplicitFinish();
+
+  var tab = gBrowser.addTab();
+  browser = gBrowser.getBrowserForTab(tab);
+  gBrowser.selectedTab = tab;
+
+  function runFirstTest(event) {
+    browser.removeEventListener("load", runFirstTest, true);
+    addEventListener("commandupdate", checkTest, false);
+    doTest();
+  }
+
+  browser.addEventListener("load", runFirstTest, true);
+  browser.contentWindow.location = testPage;
+
+  gURLBar.focus();
+}
+
+let currentTest;
+
+let tests = [
+  // Switch focus to the content. The body should be focused and select all should be enabled. In a
+  // multi-process browser, we won't see the blur update.
+  { name: "focus body", test: function() { browser.focus() }, skipUpdate: gMultiProcessBrowser ? 0 : 1,
+    commands: { "cmd_copy" : false, "cmd_paste": false, "cmd_selectAll" : true, "cmd_undo" : false, "cmd_redo": false } },
+
+  // Switch focus to 'input1'. Paste and select all should be enabled.
+  { name: "focus input", test: function() { EventUtils.synthesizeKey("VK_TAB", {}) }, skipUpdate: 2,
+    commands: { "cmd_copy" : true, "cmd_paste": true, "cmd_selectAll" : true, "cmd_undo" : false, "cmd_redo": false } },
+
+  // Move cursor to end which will deselect the text. Copy should be disabled but paste and select all should still be enabled.
+  { name: "cursor right", test: function() { EventUtils.synthesizeKey("VK_RIGHT", {}) },
+    commands: { "cmd_copy" : false, "cmd_paste": true, "cmd_selectAll" : true, "cmd_undo" : false, "cmd_redo": false } },
+
+  // Select all of the text. Copy should become enabled.
+  { name: "select all", test: function() { EventUtils.synthesizeKey("a", { accelKey: true }) },
+    commands: { "cmd_copy" : true, "cmd_paste": true, "cmd_selectAll" : true, "cmd_undo" : false, "cmd_redo": false } },
+
+  // Replace the text with 'c'. Copy should now be disabled and undo enabled.
+  { name: "change value", test: function() { EventUtils.synthesizeKey("c", {}) },
+    commands: { "cmd_copy" : false, "cmd_paste": true, "cmd_selectAll" : true, "cmd_undo" : true, "cmd_redo": false } },
+
+  // Undo. Undo should be disabled and redo enabled. The text is reselected so copy is enabled.
+  { name: "undo", test: function() { EventUtils.synthesizeKey("z", {accelKey: true }) },
+    commands: { "cmd_copy" : true, "cmd_paste": true, "cmd_selectAll" : true, "cmd_undo" : false, "cmd_redo": true  } },
+
+  // Switch focus to the select. Only select all should now be enabled.
+  { name: "focus select", test: function() { EventUtils.synthesizeKey("VK_TAB", {}) }, skipUpdate: 1,
+    commands: { "cmd_copy" : false, "cmd_paste": false, "cmd_selectAll" : true, "cmd_undo" : false, "cmd_redo": false } },
+];
+
+function doTest()
+{
+  if (!tests.length) {
+    removeEventListener("commandupdate", checkTest, false);
+    gBrowser.removeCurrentTab();
+    finish();
+    return;
+  }
+
+  currentTest = tests.shift();
+  currentTest.test();
+}
+
+function checkTest(event)
+{
+  // Skip events fired on command updaters other than the main edit menu one.
+  if (event.target != document.getElementById("editMenuCommandSetAll")) {
+    return;
+  }
+
+  // Updates also occur on blur. These should be ignored.
+  if ("skipUpdate" in currentTest && currentTest["skipUpdate"] > 0) {
+    currentTest["skipUpdate"]--;
+    return;
+  }
+
+  for (let command in currentTest.commands) {
+    is(document.getElementById(command).getAttribute("disabled") != "true", currentTest.commands[command],
+       currentTest["name"] + " " + command);
+  }
+
+  SimpleTest.executeSoon(doTest);
+}
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -9304,16 +9304,46 @@ public:
   {
     return mDispatcher->UpdateCommands(mAction);
   }
 
   nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher;
   nsString                             mAction;
 };
 
+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;
+};
+
 static bool
 CheckReason(int16_t aReason, SelectionChangeReason aReasonType)
 {
   switch (aReasonType) {
     case SelectionChangeReason::Drag:
       return aReason & nsISelectionListener::DRAG_REASON;
     case SelectionChangeReason::Mousedown:
       return aReason & nsISelectionListener::MOUSEDOWN_REASON;
@@ -9362,36 +9392,45 @@ GetSelectionBoundingRect(Selection* aSel
   }
 
   return res;
 }
 
 NS_IMETHODIMP
 nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason)
 {
-  nsPIDOMWindow *rootWindow = nsGlobalWindow::GetPrivateRoot();
-  if (!rootWindow)
+  if (!anAction.EqualsLiteral("selectionchange")) {
+    // 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));
+    } else {
+      nsPIDOMWindow* rootWindow = nsGlobalWindow::GetPrivateRoot();
+      if (!rootWindow) {
+        return NS_OK;
+      }
+
+      nsCOMPtr<nsIDOMXULDocument> xulDoc =
+        do_QueryInterface(rootWindow->GetExtantDoc());
+      // See if we contain a XUL document.
+      if (xulDoc) {
+        // Retrieve the command dispatcher and call updateCommands on it.
+        nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher;
+        xulDoc->GetCommandDispatcher(getter_AddRefs(xulCommandDispatcher));
+        if (xulCommandDispatcher) {
+          nsContentUtils::AddScriptRunner(new CommandDispatcher(xulCommandDispatcher,
+                                                                anAction));
+        }
+      }
+    }
+
     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
-  // XUL command dispatch if anAction is "selectionchange".
-  if (xulDoc && !anAction.EqualsLiteral("selectionchange")) {
-    // Retrieve the command dispatcher and call updateCommands on it.
-    nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher;
-    xulDoc->GetCommandDispatcher(getter_AddRefs(xulCommandDispatcher));
-    if (xulCommandDispatcher) {
-      nsContentUtils::AddScriptRunner(new CommandDispatcher(xulCommandDispatcher,
-                                                            anAction));
-    }
-  }
-
-  if (gSelectionCaretPrefEnabled && mDoc && anAction.EqualsLiteral("selectionchange")) {
+  }
+
+  // XXXndeakin this code will be removed by bug 1090008.
+  if (gSelectionCaretPrefEnabled && mDoc) {
     SelectionChangeEventInit init;
     init.mBubbles = true;
     if (aSel) {
       bool isTouchCaretVisible = false;
       bool isCollapsed = aSel->Collapsed();
 
       nsIPresShell *shell = mDoc->GetShell();
       if (shell) {
--- 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
@@ -27,16 +27,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
@@ -215,16 +215,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
@@ -2989,16 +2989,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"
@@ -1430,16 +1431,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
@@ -189,16 +189,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);
+  }
 };