From 08bdf771e0beca29703f92995874f3c0805ac40c Mon Sep 17 00:00:00 2001 From: Neil Deakin Date: Tue, 9 Dec 2014 10:48:27 -0500 Subject: [PATCH] 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 | 36 ++++++++++ dom/base/nsPIWindowRoot.h | 7 +- dom/base/nsWindowRoot.cpp | 69 +++++++++++++++++++ dom/base/nsWindowRoot.h | 10 +++ dom/interfaces/base/moz.build | 1 + dom/interfaces/base/nsIRemoteBrowser.idl | 26 +++++++ dom/interfaces/base/nsITabChild.idl | 9 ++- dom/ipc/PBrowser.ipdl | 8 +++ dom/ipc/TabChild.cpp | 9 +++ dom/ipc/TabParent.cpp | 32 +++++++++ dom/ipc/TabParent.h | 3 + dom/xul/nsIController.idl | 12 ++-- .../nsBaseCommandController.cpp | 7 ++ .../nsControllerCommandTable.cpp | 24 +++++++ .../nsIControllerCommandTable.idl | 5 +- toolkit/content/widgets/remote-browser.xml | 18 ++++- toolkit/modules/RemoteController.jsm | 52 +++++++------- 17 files changed, 292 insertions(+), 36 deletions(-) create mode 100644 dom/interfaces/base/nsIRemoteBrowser.idl diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 347c9a75f2a..d5e59ad6ea2 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -9272,6 +9272,36 @@ nsGlobalWindow::ShowModalDialog(const nsAString& aURI, nsIVariant *aArgs_, 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 root = mWindow->GetTopWindowRoot(); + if (!root) { + return NS_OK; + } + + nsTArray enabledCommands, disabledCommands; + root->GetEnabledDisabledCommands(enabledCommands, disabledCommands); + if (enabledCommands.Length() || disabledCommands.Length()) { + mTabChild->EnableDisableCommands(mAction, enabledCommands, disabledCommands); + } + + return NS_OK; + } + +private: + nsRefPtr mWindow; + nsCOMPtr mTabChild; + nsString mAction; +}; + class CommandDispatcher : public nsRunnable { public: @@ -9291,6 +9321,12 @@ public: 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 child = do_GetInterface(GetDocShell())) { + nsContentUtils::AddScriptRunner(new ChildCommandDispatcher(this, child, anAction)); + return NS_OK; + } + nsPIDOMWindow *rootWindow = nsGlobalWindow::GetPrivateRoot(); if (!rootWindow) return NS_OK; diff --git a/dom/base/nsPIWindowRoot.h b/dom/base/nsPIWindowRoot.h index 36450f623cf..048eaf8ecee 100644 --- a/dom/base/nsPIWindowRoot.h +++ b/dom/base/nsPIWindowRoot.h @@ -15,8 +15,8 @@ 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 { @@ -33,6 +33,9 @@ public: nsIController** aResult) = 0; virtual nsresult GetControllers(nsIControllers** aResult) = 0; + virtual void GetEnabledDisabledCommands(nsTArray& aEnabledCommands, + nsTArray& aDisabledCommands) = 0; + virtual void SetParentTarget(mozilla::dom::EventTarget* aTarget) = 0; virtual mozilla::dom::EventTarget* GetParentTarget() = 0; virtual nsIDOMWindow* GetOwnerGlobal() MOZ_OVERRIDE = 0; diff --git a/dom/base/nsWindowRoot.cpp b/dom/base/nsWindowRoot.cpp index ec53e1d907f..e4c81199145 100644 --- a/dom/base/nsWindowRoot.cpp +++ b/dom/base/nsWindowRoot.cpp @@ -281,6 +281,75 @@ nsWindowRoot::GetControllerForCommand(const char * aCommand, return NS_OK; } +void +nsWindowRoot::GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers, + nsTHashtable& aCommandsHandled, + nsTArray& aEnabledCommands, + nsTArray& aDisabledCommands) +{ + uint32_t controllerCount; + aControllers->GetControllerCount(&controllerCount); + for (uint32_t c = 0; c < controllerCount; c++) { + nsCOMPtr controller; + aControllers->GetControllerAt(c, getter_AddRefs(controller)); + + nsCOMPtr 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& aEnabledCommands, + nsTArray& aDisabledCommands) +{ + nsTHashtable commandsHandled; + + nsCOMPtr controllers; + GetControllers(getter_AddRefs(controllers)); + if (controllers) { + GetEnabledDisabledCommandsForControllers(controllers, commandsHandled, + aEnabledCommands, aDisabledCommands); + } + + nsCOMPtr 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(focusedWindow.get()); + focusedWindow = win->GetPrivateParent(); + } +} + nsIDOMNode* nsWindowRoot::GetPopupNode() { diff --git a/dom/base/nsWindowRoot.h b/dom/base/nsWindowRoot.h index 18078483fbe..6e46b5ab570 100644 --- a/dom/base/nsWindowRoot.h +++ b/dom/base/nsWindowRoot.h @@ -23,6 +23,8 @@ class EventChainPreVisitor; #include "nsPIWindowRoot.h" #include "nsCycleCollectionParticipant.h" #include "nsAutoPtr.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" class nsWindowRoot : public nsPIWindowRoot { @@ -52,6 +54,9 @@ public: virtual nsresult GetControllerForCommand(const char * aCommand, nsIController** _retval) MOZ_OVERRIDE; + virtual void GetEnabledDisabledCommands(nsTArray& aEnabledCommands, + nsTArray& aDisabledCommands) MOZ_OVERRIDE; + virtual nsIDOMNode* GetPopupNode() MOZ_OVERRIDE; virtual void SetPopupNode(nsIDOMNode* aNode) MOZ_OVERRIDE; @@ -72,6 +77,11 @@ public: protected: virtual ~nsWindowRoot(); + void GetEnabledDisabledCommandsForControllers(nsIControllers* aControllers, + nsTHashtable& aCommandsHandled, + nsTArray& aEnabledCommands, + nsTArray& aDisabledCommands); + // Members nsCOMPtr mWindow; // We own the manager, which owns event listeners attached to us. diff --git a/dom/interfaces/base/moz.build b/dom/interfaces/base/moz.build index 20ae0d05f46..99c035d58ca 100644 --- a/dom/interfaces/base/moz.build +++ b/dom/interfaces/base/moz.build @@ -31,6 +31,7 @@ XPIDL_SOURCES += [ 'nsIFrameRequestCallback.idl', 'nsIIdleObserver.idl', 'nsIQueryContentEventResult.idl', + 'nsIRemoteBrowser.idl', 'nsIServiceWorkerManager.idl', 'nsIStructuredCloneContainer.idl', 'nsITabChild.idl', diff --git a/dom/interfaces/base/nsIRemoteBrowser.idl b/dom/interfaces/base/nsIRemoteBrowser.idl new file mode 100644 index 00000000000..0b4f4609959 --- /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); +}; + diff --git a/dom/interfaces/base/nsITabChild.idl b/dom/interfaces/base/nsITabChild.idl index a8192795e91..2cb14d9944f 100644 --- a/dom/interfaces/base/nsITabChild.idl +++ b/dom/interfaces/base/nsITabChild.idl @@ -7,7 +7,10 @@ interface nsIContentFrameMessageManager; interface nsIWebBrowserChrome3; -[scriptable, uuid(2eb3bc54-78bf-40f2-b301-a5b5b70f7da0)] +native CommandsArray(nsTArray); +[ref] native CommandsArrayRef(nsTArray); + +[scriptable, uuid(7227bac4-b6fe-4090-aeb4-bc288b790925)] interface nsITabChild : nsISupports { readonly attribute nsIContentFrameMessageManager messageManager; @@ -15,5 +18,9 @@ interface nsITabChild : nsISupports attribute nsIWebBrowserChrome3 webBrowserChrome; [notxpcom] void sendRequestFocus(in boolean canFocus); + + [noscript, notxpcom] void enableDisableCommands(in AString action, + in CommandsArrayRef enabledCommands, + in CommandsArrayRef disabledCommands); }; diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index 526e63878d5..4d9b5ad1c7a 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -244,6 +244,14 @@ parent: */ 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); diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 93b22006216..d9e89ed1b55 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -3269,6 +3269,15 @@ TabChild::SendRequestFocus(bool aCanFocus) PBrowserChild::SendRequestFocus(aCanFocus); } +void +TabChild::EnableDisableCommands(const nsAString& aAction, + nsTArray& aEnabledCommands, + nsTArray& aDisabledCommands) +{ + PBrowserChild::SendEnableDisableCommands(PromiseFlatString(aAction), + aEnabledCommands, aDisabledCommands); +} + bool TabChild::DoSendBlockingMessage(JSContext* aCx, const nsAString& aMessage, diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 1f2beabe370..f876ec22209 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -47,6 +47,7 @@ #include "nsIWindowCreator2.h" #include "nsIXULBrowserWindow.h" #include "nsIXULWindow.h" +#include "nsIRemoteBrowser.h" #include "nsViewManager.h" #include "nsIWidget.h" #include "nsIWindowWatcher.h" @@ -1471,6 +1472,37 @@ TabParent::RecvRequestFocus(const bool& aCanRaise) return true; } +bool +TabParent::RecvEnableDisableCommands(const nsString& aAction, + const nsTArray& aEnabledCommands, + const nsTArray& aDisabledCommands) +{ + nsCOMPtr remoteBrowser = do_QueryInterface(mFrameElement); + if (remoteBrowser) { + nsAutoArrayPtr 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() { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index 32037b82261..084fb671ed2 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -191,6 +191,9 @@ public: 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& aEnabledCommands, + const nsTArray& 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; diff --git a/dom/xul/nsIController.idl b/dom/xul/nsIController.idl index 13bab942415..ace5374a05c 100644 --- a/dom/xul/nsIController.idl +++ b/dom/xul/nsIController.idl @@ -7,12 +7,12 @@ [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); }; @@ -25,7 +25,7 @@ interface nsIController : nsISupports { interface nsICommandParams; -[scriptable, uuid(EBE55080-C8A9-11D5-A73C-DD620D6E04BC)] +[scriptable, uuid(EEC0B435-7F53-44FE-B00A-CF3EED65C01A)] interface nsICommandController : nsISupports { @@ -33,6 +33,8 @@ interface nsICommandController : nsISupports void doCommandWithParams(in string command, in nsICommandParams aCommandParams); + void getSupportedCommands(out unsigned long count, + [array, size_is(count), retval] out string commands); }; diff --git a/embedding/components/commandhandler/nsBaseCommandController.cpp b/embedding/components/commandhandler/nsBaseCommandController.cpp index fcd9630545e..24afacf9195 100644 --- a/embedding/components/commandhandler/nsBaseCommandController.cpp +++ b/embedding/components/commandhandler/nsBaseCommandController.cpp @@ -174,3 +174,10 @@ 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); +} diff --git a/embedding/components/commandhandler/nsControllerCommandTable.cpp b/embedding/components/commandhandler/nsControllerCommandTable.cpp index 274eda363e3..cda71828236 100644 --- a/embedding/components/commandhandler/nsControllerCommandTable.cpp +++ b/embedding/components/commandhandler/nsControllerCommandTable.cpp @@ -188,6 +188,30 @@ nsControllerCommandTable::GetCommandState(const char *aCommandName, nsICommandPa 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(aArg); + (**commands) = ToNewCString(aKey); + (*commands)++; + return PL_DHASH_NEXT; +} + +NS_IMETHODIMP +nsControllerCommandTable::GetSupportedCommands(uint32_t* aCount, + char*** aCommands) +{ + char** commands = + static_cast(NS_Alloc(sizeof(char *) * mCommandsTable.Count())); + *aCount = mCommandsTable.Count(); + *aCommands = commands; + + mCommandsTable.EnumerateRead(AddCommand, &commands); + return NS_OK; +} nsresult NS_NewControllerCommandTable(nsIControllerCommandTable** aResult) diff --git a/embedding/components/commandhandler/nsIControllerCommandTable.idl b/embedding/components/commandhandler/nsIControllerCommandTable.idl index d3a23f24c6c..fb2c86fae06 100644 --- a/embedding/components/commandhandler/nsIControllerCommandTable.idl +++ b/embedding/components/commandhandler/nsIControllerCommandTable.idl @@ -18,7 +18,7 @@ * */ -[scriptable, uuid(d1a47834-6ad4-11d7-bfad-000393636592)] +[scriptable, uuid(c847f90e-b8f3-49db-a4df-8867831f2800)] interface nsIControllerCommandTable : nsISupports { /** @@ -82,6 +82,9 @@ interface nsIControllerCommandTable : nsISupports 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); }; diff --git a/toolkit/content/widgets/remote-browser.xml b/toolkit/content/widgets/remote-browser.xml index 00f643faa93..a85ec94e270 100644 --- a/toolkit/content/widgets/remote-browser.xml +++ b/toolkit/content/widgets/remote-browser.xml @@ -10,7 +10,8 @@ - + null @@ -372,6 +373,21 @@ + + + + + + + + if (this._controller) { + this._controller.enableDisableCommands(aAction, + aEnabledLength, aEnabledCommands, + aDisabledLength, aDisabledCommands); + } + + + diff --git a/toolkit/modules/RemoteController.jsm b/toolkit/modules/RemoteController.jsm index 2d2403f8b49..34a95eee218 100644 --- a/toolkit/modules/RemoteController.jsm +++ b/toolkit/modules/RemoteController.jsm @@ -14,45 +14,45 @@ 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); + } };