Bug 1472491: Part 5e - Add ContextMenuChild actor. r=felipe
authorKris Maglione <maglione.k@gmail.com>
Sun, 29 Jul 2018 20:07:33 -0700
changeset 431459 4ec3a9d01b242ed4054a319f43a50dfbbaa6d99c
parent 431458 13dc8b7b72f3fc5f774a00335f68c4815338ff07
child 431460 f1e08d7ed69ebdc3614ddd2d2a9547994e0a2f73
push id34443
push usercsabou@mozilla.com
push dateWed, 15 Aug 2018 00:53:32 +0000
treeherdermozilla-central@b80906e2fbc9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs1472491
milestone63.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 1472491: Part 5e - Add ContextMenuChild actor. r=felipe MozReview-Commit-ID: GUJfyCPkRw8
browser/actors/ContextMenuChild.jsm
browser/actors/moz.build
browser/base/content/content.js
browser/components/extensions/child/ext-menus-child.js
browser/components/nsBrowserGlue.js
browser/modules/ContextMenu.jsm
browser/modules/moz.build
rename from browser/modules/ContextMenu.jsm
rename to browser/actors/ContextMenuChild.jsm
--- a/browser/modules/ContextMenu.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -1,21 +1,23 @@
 /* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 /* 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/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["ContextMenu"];
+var EXPORTED_SYMBOLS = ["ContextMenuChild"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
+
 XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   E10SUtils: "resource://gre/modules/E10SUtils.jsm",
   BrowserUtils: "resource://gre/modules/BrowserUtils.jsm",
   findAllCssSelectors: "resource://gre/modules/css-selector.js",
   SpellCheckHelper: "resource://gre/modules/InlineSpellChecker.jsm",
   LoginManagerContent: "resource://gre/modules/LoginManagerContent.jsm",
@@ -29,24 +31,24 @@ XPCOMUtils.defineLazyGetter(this, "PageM
   ChromeUtils.import("resource://gre/modules/PageMenu.jsm", tmp);
   return new tmp.PageMenuChild();
 });
 
 const messageListeners = {
   "ContextMenu:BookmarkFrame": function(aMessage) {
     let frame = this.getTarget(aMessage).ownerDocument;
 
-    this.global.sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
-                                 { title: frame.title });
+    this.mm.sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
+                             { title: frame.title });
   },
 
   "ContextMenu:Canvas:ToBlobURL": function(aMessage) {
     this.getTarget(aMessage).toBlob((blob) => {
       let blobURL = URL.createObjectURL(blob);
-      this.global.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
+      this.mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
     });
   },
 
   "ContextMenu:DoCustomCommand": function(aMessage) {
     E10SUtils.wrapHandlingUserInput(
       this.content,
       aMessage.data.handlingUserInput,
       () => PageMenuChild.executeMenu(aMessage.data.generatedItemId)
@@ -163,31 +165,31 @@ const messageListeners = {
 
     if (isURLEncoded) {
       postData = formData.join("&");
     } else {
       let separator = spec.includes("?") ? "&" : "?";
       spec += separator + formData.join("&");
     }
 
-    this.global.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
-                                 { spec, title, postData, charset });
+    this.mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
+                             { spec, title, postData, charset });
   },
 
   "ContextMenu:SaveVideoFrameAsImage": function(aMessage) {
     let video = this.getTarget(aMessage);
     let canvas = this.content.document.createElementNS("http://www.w3.org/1999/xhtml",
                                                        "canvas");
     canvas.width = video.videoWidth;
     canvas.height = video.videoHeight;
 
     let ctxDraw = canvas.getContext("2d");
     ctxDraw.drawImage(video, 0, 0);
 
-    this.global.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
+    this.mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage:Result", {
       dataURL: canvas.toDataURL("image/jpeg", ""),
     });
   },
 
   "ContextMenu:SetAsDesktopBackground": function(aMessage) {
     let target = this.getTarget(aMessage);
 
     // Paranoia: check disableSetDesktopBackground again, in case the
@@ -201,45 +203,58 @@ const messageListeners = {
         let canvas = this.content.document.createElement("canvas");
         canvas.width = target.naturalWidth;
         canvas.height = target.naturalHeight;
         let ctx = canvas.getContext("2d");
         ctx.drawImage(target, 0, 0);
         let dataUrl = canvas.toDataURL();
         let url = (new URL(target.ownerDocument.location.href)).pathname;
         let imageName = url.substr(url.lastIndexOf("/") + 1);
-        this.global.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
-                                     { dataUrl, imageName });
+        this.mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+                                 { dataUrl, imageName });
       } catch (e) {
         Cu.reportError(e);
         disable = true;
       }
     }
 
     if (disable) {
-      this.global.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
-                                   { disable });
+      this.mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+                               { disable });
     }
   },
 };
 
-class ContextMenu {
+let contextMenus = new WeakMap();
+
+class ContextMenuChild extends ActorChild {
   // PUBLIC
   constructor(global) {
+    super(global);
+
+    contextMenus.set(global, this);
+
     this.target = null;
     this.context = null;
-    this.global = global;
-    this.content = global.content;
     this.lastMenuTarget = null;
 
     Object.keys(messageListeners).forEach(key =>
       global.addMessageListener(key, messageListeners[key].bind(this))
     );
   }
 
+  static getTarget(global, message, key) {
+    return contextMenus.get(global).getTarget(message, key);
+  }
+
+  static getLastTarget(global) {
+    let contextMenu = contextMenus.get(global);
+    return contextMenu && contextMenu.lastMenuTarget;
+  }
+
   /**
    * Returns the event target of the context menu, using a locally stored
    * reference if possible. If not, and aMessage.objects is defined,
    * aMessage.objects[aKey] is returned. Otherwise null.
    * @param  {Object} aMessage Message with a objects property
    * @param  {String} aKey     Key for the target on aMessage.objects
    * @return {Object}          Context menu target
    */
@@ -484,17 +499,17 @@ class ContextMenu {
       referrer,
       referrerPolicy
     } = doc;
     docLocation = docLocation && docLocation.spec;
     let frameOuterWindowID = WebNavigationFrames.getFrameId(doc.defaultView);
     let loginFillInfo = LoginManagerContent.getFieldContext(aEvent.composedTarget);
 
     // The same-origin check will be done in nsContextMenu.openLinkInTab.
-    let parentAllowsMixedContent = !!this.global.docShell.mixedContentChannel;
+    let parentAllowsMixedContent = !!this.docShell.mixedContentChannel;
 
     // Get referrer attribute from clicked link and parse it
     let referrerAttrValue =
       Services.netUtils.parseAttributePolicyString(aEvent.composedTarget.
                                                    getAttribute("referrerpolicy"));
 
     if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
       referrerPolicy = referrerAttrValue;
@@ -523,17 +538,17 @@ class ContextMenu {
 
         try {
           contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
         } catch (e) {}
       } catch (e) {}
     }
 
     let selectionInfo = BrowserUtils.getSelectionDetails(this.content);
-    let loadContext = this.global.docShell.QueryInterface(Ci.nsILoadContext);
+    let loadContext = this.docShell.QueryInterface(Ci.nsILoadContext);
     let userContextId = loadContext.originAttributes.userContextId;
     let popupNodeSelectors = findAllCssSelectors(aEvent.composedTarget);
 
     this._setContext(aEvent);
     let context = this.context;
     this.target = context.target;
 
     let spellInfo = null;
@@ -547,24 +562,24 @@ class ContextMenu {
     }
 
     let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 
     if (isRemote) {
       editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.content);
 
       if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
-        spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this.global);
+        spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this.mm);
       }
 
       // Set the event target first as the copy image command needs it to
       // determine what was context-clicked on. Then, update the state of the
       // commands on the context menu.
-      this.global.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
-                          .setCommandNode(aEvent.composedTarget);
+      this.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
+                   .setCommandNode(aEvent.composedTarget);
       aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
 
       customMenuItems = PageMenuChild.build(aEvent.composedTarget);
       principal = doc.nodePrincipal;
     }
 
     let data = {
       context,
@@ -585,21 +600,21 @@ class ContextMenu {
       contentDisposition,
       frameOuterWindowID,
       popupNodeSelectors,
       disableSetDesktopBg,
       parentAllowsMixedContent,
     };
 
     if (isRemote) {
-      this.global.sendAsyncMessage("contextmenu", data, {
+      this.mm.sendAsyncMessage("contextmenu", data, {
         targetAsCPOW,
       });
     } else {
-      let browser = this.global.docShell.chromeEventHandler;
+      let browser = this.docShell.chromeEventHandler;
       let mainWin = browser.ownerGlobal;
 
       data.documentURIObject = doc.documentURIObject;
       data.disableSetDesktopBackground = data.disableSetDesktopBg;
       delete data.disableSetDesktopBg;
 
       data.context.targetAsCPOW = targetAsCPOW;
 
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -6,10 +6,11 @@
 
 with Files("PageStyleChild.jsm"):
     BUG_COMPONENT = ("Firefox", "Menus")
 
 FINAL_TARGET_FILES.actors += [
     'AboutReaderChild.jsm',
     'BrowserTabChild.jsm',
     'ContentSearchChild.jsm',
+    'ContextMenuChild.jsm',
     'PageStyleChild.jsm',
 ]
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -22,21 +22,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ContentWebRTC: "resource:///modules/ContentWebRTC.jsm",
   LoginFormFactory: "resource://gre/modules/LoginManagerContent.jsm",
   InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
   PluginContent: "resource:///modules/PluginContent.jsm",
   FormSubmitObserver: "resource:///modules/FormSubmitObserver.jsm",
   NetErrorContent: "resource:///modules/NetErrorContent.jsm",
   PageMetadata: "resource://gre/modules/PageMetadata.jsm",
   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
-  ContextMenu: "resource:///modules/ContextMenu.jsm",
-});
-
-XPCOMUtils.defineLazyProxy(this, "contextMenu", () => {
-  return new ContextMenu(global);
+  ContextMenuChild: "resource:///actors/ContextMenuChild.jsm",
 });
 
 XPCOMUtils.defineLazyProxy(this, "ClickEventHandler", () => {
   let tmp = {};
   ChromeUtils.import("resource:///modules/ClickEventHandler.jsm", tmp);
   return new tmp.ClickEventHandler(global);
 });
 
@@ -55,26 +51,24 @@ XPCOMUtils.defineLazyProxy(this, "formSu
 });
 
 XPCOMUtils.defineLazyProxy(this, "PageInfoListener",
                            "resource:///modules/PageInfoListener.jsm");
 
 XPCOMUtils.defineLazyProxy(this, "LightWeightThemeWebInstallListener",
                            "resource:///modules/LightWeightThemeWebInstallListener.jsm");
 
-Services.els.addSystemEventListener(global, "contextmenu", contextMenu, false);
-
 Services.obs.addObserver(formSubmitObserver, "invalidformsubmit", true);
 
 addMessageListener("PageInfo:getData", PageInfoListener);
 
 // NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
 addMessageListener("RemoteLogins:fillForm", function(message) {
   // intercept if ContextMenu.jsm had sent a plain object for remote targets
-  message.objects.inputElement = contextMenu.getTarget(message, "inputElement");
+  message.objects.inputElement = ContextMenuChild.getTarget(global, message, "inputElement");
   LoginManagerContent.receiveMessage(message, content);
 });
 addEventListener("DOMFormHasPassword", function(event) {
   LoginManagerContent.onDOMFormHasPassword(event, content);
   let formLike = LoginFormFactory.createFromForm(event.originalTarget);
   InsecurePasswordUtils.reportInsecurePasswords(formLike);
 });
 addEventListener("DOMInputPasswordAdded", function(event) {
@@ -264,23 +258,23 @@ var PageMetadataMessenger = {
   init() {
     addMessageListener("PageMetadata:GetPageData", this);
     addMessageListener("PageMetadata:GetMicroformats", this);
     this.init = null;
   },
   receiveMessage(message) {
     switch (message.name) {
       case "PageMetadata:GetPageData": {
-        let target = contextMenu.getTarget(message);
+        let target = ContextMenuChild.getTarget(global, message);
         let result = PageMetadata.getData(content.document, target);
         sendAsyncMessage("PageMetadata:PageDataResult", result);
         break;
       }
       case "PageMetadata:GetMicroformats": {
-        let target = contextMenu.getTarget(message);
+        let target = ContextMenuChild.getTarget(global, message);
         let result = PageMetadata.getMicroformats(content.document, target);
         sendAsyncMessage("PageMetadata:MicroformatsResult", result);
         break;
       }
     }
   }
 };
 PageMetadataMessenger.init();
--- a/browser/components/extensions/child/ext-menus-child.js
+++ b/browser/components/extensions/child/ext-menus-child.js
@@ -1,23 +1,22 @@
 "use strict";
 
+ChromeUtils.defineModuleGetter(this, "ContextMenuChild",
+                               "resource:///actors/ContextMenuChild.jsm");
+
 this.menusChild = class extends ExtensionAPI {
   getAPI(context) {
     return {
       menus: {
         getTargetElement(targetElementId) {
-          let tabChildGlobal = context.messageManager;
-          let {contextMenu} = tabChildGlobal;
           let element;
-          if (contextMenu) {
-            let {lastMenuTarget} = contextMenu;
-            if (lastMenuTarget && Math.floor(lastMenuTarget.timeStamp) === targetElementId) {
-              element = lastMenuTarget.targetRef.get();
-            }
+          let lastMenuTarget = ContextMenuChild.getLastTarget(context.messageManager);
+          if (lastMenuTarget && Math.floor(lastMenuTarget.timeStamp) === targetElementId) {
+            element = lastMenuTarget.targetRef.get();
           }
           if (element && element.getRootNode({composed: true}) === context.contentWindow.document) {
             return element;
           }
           return null;
         },
       },
     };
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -48,16 +48,25 @@ let ACTORS = {
         "Browser:Reload",
         "MixedContent:ReenableProtection",
         "SwitchDocumentDirection",
         "UpdateCharacterSet",
       ],
     },
   },
 
+  ContextMenu: {
+    child: {
+      module: "resource:///actors/ContextMenuChild.jsm",
+      events: {
+        "contextmenu": {mozSystemGroup: true},
+      },
+    },
+  },
+
   ContentSearch: {
     child: {
       module: "resource:///actors/ContentSearchChild.jsm",
       group: "browsers",
       matches: ["about:home", "about:newtab", "about:welcome",
                 "chrome://mochitests/content/*"],
       events: {
         "ContentSearchClient": {capture: true, wantUntrusted: true},
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -138,17 +138,16 @@ EXTRA_JS_MODULES += [
     'ClickEventHandler.jsm',
     'ContentClick.jsm',
     'ContentCrashHandlers.jsm',
     'ContentLinkHandler.jsm',
     'ContentMetaHandler.jsm',
     'ContentObservers.js',
     'ContentSearch.jsm',
     'ContentWebRTC.jsm',
-    'ContextMenu.jsm',
     'ExtensionsUI.jsm',
     'Feeds.jsm',
     'FormSubmitObserver.jsm',
     'FormValidationHandler.jsm',
     'HomePage.jsm',
     'LaterRun.jsm',
     'LightweightThemeChildHelper.jsm',
     'LightWeightThemeWebInstallListener.jsm',