Backed out 6 changesets (bug 1505909) for bc failures complaining about WebNavigationChild and browser_e10s_about_page_triggeringprincipal.js CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Fri, 07 Jun 2019 06:15:16 +0300
changeset 477747 a313214fd6486359816abe95d5c15603062a3c0c
parent 477746 aae7e4d626d364f3a02bcd2ce3c9ad4b4723ac45
child 477748 9183f80b6bf0d03510336ac8faa0b98f50b56e9f
push id113372
push userdvarga@mozilla.com
push dateFri, 07 Jun 2019 10:07:35 +0000
treeherdermozilla-inbound@9909cd207cc2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1505909
milestone69.0a1
backs out56449fd37aee9aaf1faf0aeefab2cd80d6c20d03
3ff09b79821c151ea9c45ed3c56d7116517b0bad
a1a2a9efe22fab9c125cd2325f537e8fe93c0724
8aeb77291207055ca1883e7e9f558cd3ced9fd63
4aa17e28ee5431c9ee955f6164d0c092e5775a57
dbe6803d979eb46cb36dacbf959023f78ef9a0f6
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
Backed out 6 changesets (bug 1505909) for bc failures complaining about WebNavigationChild and browser_e10s_about_page_triggeringprincipal.js CLOSED TREE Backed out changeset 56449fd37aee (bug 1505909) Backed out changeset 3ff09b79821c (bug 1505909) Backed out changeset a1a2a9efe22f (bug 1505909) Backed out changeset 8aeb77291207 (bug 1505909) Backed out changeset 4aa17e28ee54 (bug 1505909) Backed out changeset dbe6803d979e (bug 1505909)
browser/actors/ContextMenuChild.jsm
browser/actors/ContextMenuParent.jsm
browser/actors/ContextMenuSpecialProcessChild.jsm
browser/actors/PluginChild.jsm
browser/actors/moz.build
browser/base/content/browser.js
browser/base/content/content.js
browser/base/content/nsContextMenu.js
browser/base/content/tabbrowser.js
browser/base/content/test/general/browser_addKeywordSearch.js
browser/components/BrowserGlue.jsm
browser/components/extensions/child/ext-menus-child.js
browser/components/places/PlacesUIUtils.jsm
toolkit/actors/InlineSpellCheckerChild.jsm
toolkit/actors/InlineSpellCheckerParent.jsm
toolkit/actors/moz.build
toolkit/components/passwordmgr/LoginManagerContent.jsm
toolkit/components/passwordmgr/LoginManagerContextMenu.jsm
toolkit/components/passwordmgr/LoginManagerParent.jsm
toolkit/modules/ActorManagerParent.jsm
toolkit/modules/ContentDOMReference.jsm
toolkit/modules/InlineSpellChecker.jsm
toolkit/modules/InlineSpellCheckerContent.jsm
toolkit/modules/PageMenu.jsm
toolkit/modules/moz.build
toolkit/mozapps/extensions/content/extensions.js
--- a/browser/actors/ContextMenuChild.jsm
+++ b/browser/actors/ContextMenuChild.jsm
@@ -6,235 +6,235 @@
 
 "use strict";
 
 var EXPORTED_SYMBOLS = ["ContextMenuChild"];
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+const {ActorChild} = 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",
   WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   InlineSpellCheckerContent: "resource://gre/modules/InlineSpellCheckerContent.jsm",
-  ContentDOMReference: "resource://gre/modules/ContentDOMReference.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "PageMenuChild", () => {
   let tmp = {};
   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.mm.sendAsyncMessage("ContextMenu:BookmarkFrame:Result",
+                             { title: frame.title });
+  },
+
+  "ContextMenu:Canvas:ToBlobURL": function(aMessage) {
+    this.getTarget(aMessage).toBlob((blob) => {
+      let blobURL = URL.createObjectURL(blob);
+      this.mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL:Result", { blobURL });
+    });
+  },
+
+  "ContextMenu:DoCustomCommand": function(aMessage) {
+    E10SUtils.wrapHandlingUserInput(
+      this.content,
+      aMessage.data.handlingUserInput,
+      () => PageMenuChild.executeMenu(aMessage.data.generatedItemId)
+    );
+  },
+
+  "ContextMenu:Hiding": function() {
+    this.context = null;
+    this.target = null;
+  },
+
+  "ContextMenu:MediaCommand": function(aMessage) {
+    E10SUtils.wrapHandlingUserInput(
+      this.content, aMessage.data.handlingUserInput, () => {
+        let media = this.getTarget(aMessage, "element");
+
+        switch (aMessage.data.command) {
+          case "play":
+            media.play();
+            break;
+          case "pause":
+            media.pause();
+            break;
+          case "loop":
+            media.loop = !media.loop;
+            break;
+          case "mute":
+            media.muted = true;
+            break;
+          case "unmute":
+            media.muted = false;
+            break;
+          case "playbackRate":
+            media.playbackRate = aMessage.data.data;
+            break;
+          case "hidecontrols":
+            media.removeAttribute("controls");
+            break;
+          case "showcontrols":
+            media.setAttribute("controls", "true");
+            break;
+          case "fullscreen":
+            if (this.content.document.fullscreenEnabled) {
+              media.requestFullscreen();
+            }
+            break;
+          case "pictureinpicture":
+            let event = new this.content.CustomEvent("MozTogglePictureInPicture", {
+              bubbles: true,
+            }, this.content);
+            media.dispatchEvent(event);
+            break;
+        }
+      }
+    );
+  },
+
+  "ContextMenu:ReloadFrame": function(aMessage) {
+    let forceReload = aMessage.objects && aMessage.objects.forceReload;
+    this.getTarget(aMessage).ownerDocument.location.reload(forceReload);
+  },
+
+  "ContextMenu:ReloadImage": function(aMessage) {
+    let image = this.getTarget(aMessage);
+
+    if (image instanceof Ci.nsIImageLoadingContent) {
+      image.forceReload();
+    }
+  },
+
+  "ContextMenu:SearchFieldBookmarkData": function(aMessage) {
+    let node = this.getTarget(aMessage);
+    let charset = node.ownerDocument.characterSet;
+    let formBaseURI = Services.io.newURI(node.form.baseURI, charset);
+    let formURI = Services.io.newURI(node.form.getAttribute("action"),
+                                     charset, formBaseURI);
+    let spec = formURI.spec;
+    let isURLEncoded =  (node.form.method.toUpperCase() == "POST" &&
+                         (node.form.enctype == "application/x-www-form-urlencoded" ||
+                          node.form.enctype == ""));
+    let title = node.ownerDocument.title;
+
+    function escapeNameValuePair([aName, aValue]) {
+      if (isURLEncoded) {
+        return escape(aName + "=" + aValue);
+      }
+
+      return escape(aName) + "=" + escape(aValue);
+    }
+    let formData = new this.content.FormData(node.form);
+    formData.delete(node.name);
+    formData = Array.from(formData).map(escapeNameValuePair);
+    formData.push(escape(node.name) + (isURLEncoded ? escape("=%s") : "=%s"));
+
+    let postData;
+
+    if (isURLEncoded) {
+      postData = formData.join("&");
+    } else {
+      let separator = spec.includes("?") ? "&" : "?";
+      spec += separator + formData.join("&");
+    }
+
+    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.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
+    // image changed since the context menu was initiated.
+    let disable = this._disableSetDesktopBackground(target);
+
+    if (!disable) {
+      try {
+        BrowserUtils.urlSecurityCheck(target.currentURI.spec,
+                                      target.ownerDocument.nodePrincipal);
+        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.mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+                                 { dataUrl, imageName });
+      } catch (e) {
+        Cu.reportError(e);
+        disable = true;
+      }
+    }
+
+    if (disable) {
+      this.mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+                               { disable });
+    }
+  },
+};
+
 let contextMenus = new WeakMap();
 
-class ContextMenuChild extends JSWindowActorChild {
+class ContextMenuChild extends ActorChild {
   // PUBLIC
-  constructor() {
-    super();
+  constructor(dispatcher) {
+    super(dispatcher);
+
+    contextMenus.set(this.mm, this);
 
     this.target = null;
     this.context = null;
     this.lastMenuTarget = null;
-  }
 
-  static getTarget(browsingContext, message, key) {
-    let actor = contextMenus.get(browsingContext);
-    if (!actor) {
-      throw new Error("Can't find ContextMenu actor for browsing context with " +
-                      "ID: " + browsingContext.id);
-    }
-    return actor.getTarget(message, key);
-  }
-
-  static getLastTarget(browsingContext) {
-    let contextMenu = contextMenus.get(browsingContext);
-    return contextMenu && contextMenu.lastMenuTarget;
+    Object.keys(messageListeners).forEach(key =>
+      this.mm.addMessageListener(key, messageListeners[key].bind(this))
+    );
   }
 
-  receiveMessage(message) {
-    switch (message.name) {
-      case "ContextMenu:GetFrameTitle": {
-        let target = ContentDOMReference.resolve(message.data.targetIdentifier);
-        return Promise.resolve(target.ownerDocument.title);
-      }
-
-      case "ContextMenu:Canvas:ToBlobURL": {
-        let target = ContentDOMReference.resolve(message.data.targetIdentifier);
-        return new Promise(resolve => {
-          target.toBlob(blob => {
-            let blobURL = URL.createObjectURL(blob);
-            resolve(blobURL);
-          });
-        });
-      }
-
-      case "ContextMenu:DoCustomCommand": {
-        E10SUtils.wrapHandlingUserInput(
-          this.contentWindow,
-          message.data.handlingUserInput,
-          () => PageMenuChild.executeMenu(message.data.generatedItemId)
-        );
-        break;
-      }
-
-      case "ContextMenu:Hiding": {
-        this.context = null;
-        this.target = null;
-        break;
-      }
-
-      case "ContextMenu:MediaCommand": {
-        E10SUtils.wrapHandlingUserInput(
-          this.contentWindow, message.data.handlingUserInput, () => {
-            let media = ContentDOMReference.resolve(message.data.targetIdentifier);
-
-            switch (message.data.command) {
-              case "play":
-                media.play();
-                break;
-              case "pause":
-                media.pause();
-                break;
-              case "loop":
-                media.loop = !media.loop;
-                break;
-              case "mute":
-                media.muted = true;
-                break;
-              case "unmute":
-                media.muted = false;
-                break;
-              case "playbackRate":
-                media.playbackRate = message.data.data;
-                break;
-              case "hidecontrols":
-                media.removeAttribute("controls");
-                break;
-              case "showcontrols":
-                media.setAttribute("controls", "true");
-                break;
-              case "fullscreen":
-                if (this.document.fullscreenEnabled) {
-                  media.requestFullscreen();
-                }
-                break;
-              case "pictureinpicture":
-                let event = new this.contentWindow.CustomEvent("MozTogglePictureInPicture", {
-                  bubbles: true,
-                }, this.contentWindow);
-                media.dispatchEvent(event);
-                break;
-            }
-          }
-        );
-        break;
-      }
-
-      case "ContextMenu:ReloadFrame": {
-        let target = ContentDOMReference.resolve(message.data.targetIdentifier);
-        target.ownerDocument.location.reload(message.data.forceReload);
-        break;
-      }
+  static getTarget(mm, message, key) {
+    return contextMenus.get(mm).getTarget(message, key);
+  }
 
-      case "ContextMenu:ReloadImage": {
-        let image = ContentDOMReference.resolve(message.data.targetIdentifier);
-
-        if (image instanceof Ci.nsIImageLoadingContent) {
-          image.forceReload();
-        }
-        break;
-      }
-
-      case "ContextMenu:SearchFieldBookmarkData": {
-        let node =  ContentDOMReference.resolve(message.data.targetIdentifier);
-        let charset = node.ownerDocument.characterSet;
-        let formBaseURI = Services.io.newURI(node.form.baseURI, charset);
-        let formURI = Services.io.newURI(node.form.getAttribute("action"),
-                                         charset, formBaseURI);
-        let spec = formURI.spec;
-        let isURLEncoded =  (node.form.method.toUpperCase() == "POST" &&
-                             (node.form.enctype == "application/x-www-form-urlencoded" ||
-                              node.form.enctype == ""));
-        let title = node.ownerDocument.title;
-
-        function escapeNameValuePair([aName, aValue]) {
-          if (isURLEncoded) {
-            return escape(aName + "=" + aValue);
-          }
-
-          return escape(aName) + "=" + escape(aValue);
-        }
-        let formData = new this.contentWindow.FormData(node.form);
-        formData.delete(node.name);
-        formData = Array.from(formData).map(escapeNameValuePair);
-        formData.push(escape(node.name) + (isURLEncoded ? escape("=%s") : "=%s"));
-
-        let postData;
-
-        if (isURLEncoded) {
-          postData = formData.join("&");
-        } else {
-          let separator = spec.includes("?") ? "&" : "?";
-          spec += separator + formData.join("&");
-        }
-
-        return Promise.resolve({ spec, title, postData, charset });
-      }
-
-      case "ContextMenu:SaveVideoFrameAsImage": {
-        let video = ContentDOMReference.resolve(message.data.targetIdentifier);
-        let canvas = this.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);
-
-        return Promise.resolve(canvas.toDataURL("image/jpeg", ""));
-      }
-
-      case "ContextMenu:SetAsDesktopBackground": {
-        let target = ContentDOMReference.resolve(message.data.targetIdentifier);
-
-        // Paranoia: check disableSetDesktopBackground again, in case the
-        // image changed since the context menu was initiated.
-        let disable = this._disableSetDesktopBackground(target);
-
-        if (!disable) {
-          try {
-            BrowserUtils.urlSecurityCheck(target.currentURI.spec,
-                                          target.ownerDocument.nodePrincipal);
-            let canvas = this.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);
-            return Promise.resolve({ failed: false, dataURL, imageName });
-          } catch (e) {
-            Cu.reportError(e);
-          }
-        }
-
-        return Promise.resolve({ failed: true, dataURL: null, imageName: null });
-      }
-    }
-
-    return undefined;
+  static getLastTarget(mm) {
+    let contextMenu = contextMenus.get(mm);
+    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
@@ -329,17 +329,17 @@ class ContextMenuChild extends JSWindowA
     let text = "";
     let node = root.firstChild;
     let depth = 1;
     while (node && depth > 0) {
       // See if this node is text.
       if (node.nodeType == node.TEXT_NODE) {
         // Add this text to our collection.
         text += " " + node.data;
-      } else if (node instanceof this.contentWindow.HTMLImageElement) {
+      } else if (node instanceof this.content.HTMLImageElement) {
         // If it has an "alt" attribute, add that.
         let altText = node.getAttribute( "alt" );
         if ( altText && altText != "" ) {
           text += " " + altText;
         }
       }
       // Find next node to test.
       // First, see if this node has children.
@@ -394,21 +394,21 @@ class ContextMenuChild extends JSWindowA
     if (aURL.startsWith("blob:")) {
       return URL.isValidURL(aURL);
     }
 
     return true;
   }
 
   _isTargetATextBox(node) {
-    if (node instanceof this.contentWindow.HTMLInputElement) {
+    if (node instanceof this.content.HTMLInputElement) {
       return node.mozIsTextField(false);
     }
 
-    return (node instanceof this.contentWindow.HTMLTextAreaElement);
+    return (node instanceof this.content.HTMLTextAreaElement);
   }
 
   _isSpellCheckEnabled(aNode) {
     // We can always force-enable spellchecking on textboxes
     if (this._isTargetATextBox(aNode)) {
       return true;
     }
 
@@ -447,18 +447,16 @@ class ContextMenuChild extends JSWindowA
     if (!request) {
       return true;
     }
 
     return false;
   }
 
   handleEvent(aEvent) {
-    contextMenus.set(this.browsingContext, this);
-
     let defaultPrevented = aEvent.defaultPrevented;
 
     if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
       let plugin = null;
 
       try {
         plugin = aEvent.composedTarget.QueryInterface(Ci.nsIObjectLoadingContent);
       } catch (e) {}
@@ -483,26 +481,25 @@ class ContextMenuChild extends JSWindowA
     } = 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.docShell.mixedContentChannel;
 
-    let disableSetDesktopBackground = null;
+    let disableSetDesktopBg = null;
 
     // Media related cache info parent needs for saving
     let contentType = null;
     let contentDisposition = null;
     if (aEvent.composedTarget.nodeType == aEvent.composedTarget.ELEMENT_NODE &&
         aEvent.composedTarget instanceof Ci.nsIImageLoadingContent &&
         aEvent.composedTarget.currentURI) {
-      disableSetDesktopBackground =
-        this._disableSetDesktopBackground(aEvent.composedTarget);
+      disableSetDesktopBg = this._disableSetDesktopBackground(aEvent.composedTarget);
 
       try {
         let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                                                          .getImgCacheForDocument(doc);
         // The image cache's notion of where this image is located is
         // the currentURI of the image loading content.
         let props = imageCache.findEntryProperties(aEvent.composedTarget.currentURI, doc);
 
@@ -511,97 +508,106 @@ class ContextMenuChild extends JSWindowA
         } catch (e) {}
 
         try {
           contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
         } catch (e) {}
       } catch (e) {}
     }
 
-    let selectionInfo = BrowserUtils.getSelectionDetails(this.contentWindow);
+    let selectionInfo = BrowserUtils.getSelectionDetails(this.content);
     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;
     let editFlags = null;
     let principal = null;
     let customMenuItems = null;
 
     let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(Ci.nsIReferrerInfo);
     referrerInfo.initWithNode(context.onLink ? context.link : aEvent.composedTarget);
 
-    let target = context.target;
-    if (target) {
+    let targetAsCPOW = context.target;
+    if (targetAsCPOW) {
       this._cleanContext();
     }
 
-    editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.contentWindow);
+    let isRemote = Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
 
-    if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
-      spellInfo = InlineSpellCheckerContent.initContextMenu(aEvent, editFlags, this);
-    }
+    if (isRemote) {
+      editFlags = SpellCheckHelper.isEditable(aEvent.composedTarget, this.content);
+
+      if (editFlags & SpellCheckHelper.SPELLCHECKABLE) {
+        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.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
-        .setCommandNode(aEvent.composedTarget);
-    aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
+      // 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.docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
+                   .setCommandNode(aEvent.composedTarget);
+      aEvent.composedTarget.ownerGlobal.updateCommands("contentcontextmenu");
 
-    principal = doc.nodePrincipal;
+      customMenuItems = PageMenuChild.build(aEvent.composedTarget);
+      principal = doc.nodePrincipal;
+    }
 
     let data = {
       context,
       charSet,
       baseURI,
+      isRemote,
       referrerInfo,
       editFlags,
       principal,
       spellInfo,
       contentType,
       docLocation,
       loginFillInfo,
       selectionInfo,
       userContextId,
       customMenuItems,
       contentDisposition,
       frameOuterWindowID,
       popupNodeSelectors,
-      disableSetDesktopBackground,
+      disableSetDesktopBg,
       parentAllowsMixedContent,
     };
 
     if (context.inFrame && !context.inSrcdocFrame) {
       data.frameReferrerInfo = doc.referrerInfo;
     }
 
-    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+    Services.obs.notifyObservers({wrappedJSObject: data}, "on-prepare-contextmenu");
+
+    if (isRemote) {
       data.referrerInfo = E10SUtils.serializeReferrerInfo(data.referrerInfo);
       if (data.frameReferrerInfo) {
         data.frameReferrerInfo = E10SUtils.serializeReferrerInfo(data.frameReferrerInfo);
       }
 
-      data.customMenuItems = PageMenuChild.build(aEvent.composedTarget);
-    }
-
-    Services.obs.notifyObservers({wrappedJSObject: data}, "on-prepare-contextmenu");
+      this.mm.sendAsyncMessage("contextmenu", data, {
+        targetAsCPOW,
+      });
+    } else {
+      let browser = this.docShell.chromeEventHandler;
+      let mainWin = browser.ownerGlobal;
 
-    // In the event that the content is running in the parent process, we don't
-    // actually want the contextmenu events to reach the parent - we'll dispatch
-    // a new contextmenu event after the async message has reached the parent
-    // instead.
-    aEvent.preventDefault();
-    aEvent.stopPropagation();
+      data.documentURIObject = doc.documentURIObject;
+      data.disableSetDesktopBackground = data.disableSetDesktopBg;
+      delete data.disableSetDesktopBg;
 
-    this.sendAsyncMessage("contextmenu", data);
+      data.context.targetAsCPOW = targetAsCPOW;
+      mainWin.setContextMenuContentData(data);
+    }
   }
 
   /**
    * Some things are not serializable, so we either have to only send
    * their needed data or regenerate them in nsContextMenu.js
    * - target and target.ownerDocument
    * - link
    * - linkURI
@@ -667,23 +673,23 @@ class ContextMenuChild extends JSWindowA
     context.screenX = aEvent.screenX;
     context.screenY = aEvent.screenY;
     context.mozInputSource = aEvent.mozInputSource;
 
     let node = aEvent.composedTarget;
 
     // Set the node to containing <video>/<audio>/<embed>/<object> if the node
     // is in the videocontrols/pluginProblem UA Widget.
-    if (this.contentWindow.ShadowRoot) {
+    if (this.content.ShadowRoot) {
       let n = node;
       while (n) {
-        if (n instanceof this.contentWindow.ShadowRoot) {
-          if (n.host instanceof this.contentWindow.HTMLMediaElement ||
-              n.host instanceof this.contentWindow.HTMLEmbedElement ||
-              n.host instanceof this.contentWindow.HTMLObjectElement) {
+        if (n instanceof this.content.ShadowRoot) {
+          if (n.host instanceof this.content.HTMLMediaElement ||
+              n.host instanceof this.content.HTMLEmbedElement ||
+              n.host instanceof this.content.HTMLObjectElement) {
             node = n.host;
             break;
           }
           break;
         }
         n = n.parentNode;
       }
     }
@@ -695,18 +701,18 @@ class ContextMenuChild extends JSWindowA
     if (node.nodeType == node.DOCUMENT_NODE ||
         // Don't display for XUL element unless <label class="text-link">
         (node.namespaceURI == XUL_NS && !this._isXULTextLinkLabel(node))) {
       context.shouldDisplay = false;
       return;
     }
 
     const isAboutDevtoolsToolbox =
-          this.document.documentURI.startsWith("about:devtools-toolbox");
-    const editFlags = SpellCheckHelper.isEditable(node, this.contentWindow);
+          this.content.document.documentURI.startsWith("about:devtools-toolbox");
+    const editFlags = SpellCheckHelper.isEditable(node, this.content);
 
     if (isAboutDevtoolsToolbox && (editFlags & SpellCheckHelper.TEXTINPUT) === 0) {
       // Don't display for about:devtools-toolbox page unless the source was text input.
       context.shouldDisplay = false;
       return;
     }
 
     // Initialize context to be sent to nsContextMenu
@@ -752,17 +758,16 @@ class ContextMenuChild extends JSWindowA
     context.onSaveableLink      = false;
     context.onSpellcheckable    = false;
     context.onTextInput         = false;
     context.onVideo             = false;
 
     // Remember the node and its owner document that was clicked
     // This may be modifed before sending to nsContextMenu
     context.target = node;
-    context.targetIdentifier = ContentDOMReference.get(node);
 
     context.principal = context.target.ownerDocument.nodePrincipal;
     context.csp = E10SUtils.serializeCSP(context.target.ownerDocument.csp);
 
     context.frameOuterWindowID = WebNavigationFrames.getFrameId(context.target.ownerGlobal);
 
     // Check if we are in a synthetic document (stand alone image, video, etc.).
     context.inSyntheticDoc = context.target.ownerDocument.mozSyntheticDocument;
@@ -846,19 +851,19 @@ class ContextMenuChild extends JSWindowA
       context.mediaURL = (context.target.currentRequestFinalURI || context.target.currentURI).spec;
 
       const descURL = context.target.getAttribute("longdesc");
 
       if (descURL) {
         context.imageDescURL = this._makeURLAbsolute(context.target.ownerDocument.body.baseURI,
                                                     descURL);
       }
-    } else if (context.target instanceof this.contentWindow.HTMLCanvasElement) {
+    } else if (context.target instanceof this.content.HTMLCanvasElement) {
       context.onCanvas = true;
-    } else if (context.target instanceof this.contentWindow.HTMLVideoElement) {
+    } else if (context.target instanceof this.content.HTMLVideoElement) {
       const mediaURL = context.target.currentSrc || context.target.src;
 
       if (this._isMediaURLReusable(mediaURL)) {
         context.mediaURL = mediaURL;
       }
 
       if (this._isProprietaryDRM()) {
         context.onDRMMedia = true;
@@ -872,17 +877,17 @@ class ContextMenuChild extends JSWindowA
       // directly. If the media is actually audio, be smarter and provide a
       // context menu with audio operations.
       if (context.target.readyState >= context.target.HAVE_METADATA &&
           (context.target.videoWidth == 0 || context.target.videoHeight == 0)) {
         context.onAudio = true;
       } else {
         context.onVideo = true;
       }
-    } else if (context.target instanceof this.contentWindow.HTMLAudioElement) {
+    } else if (context.target instanceof this.content.HTMLAudioElement) {
       context.onAudio = true;
       const mediaURL = context.target.currentSrc || context.target.src;
 
       if (this._isMediaURLReusable(mediaURL)) {
         context.mediaURL = mediaURL;
       }
 
       if (this._isProprietaryDRM()) {
@@ -897,17 +902,17 @@ class ContextMenuChild extends JSWindowA
 
       // This is guaranteed to be an input or textarea because of the condition above,
       // so the no-children flag is always correct. We deal with contenteditable elsewhere.
       if (context.onSpellcheckable) {
         context.shouldInitInlineSpellCheckerUINoChildren = true;
       }
 
       context.onKeywordField = (editFlags & SpellCheckHelper.KEYWORD);
-    } else if (context.target instanceof this.contentWindow.HTMLHtmlElement) {
+    } else if (context.target instanceof this.content.HTMLHtmlElement) {
       const bodyElt = context.target.ownerDocument.body;
 
       if (bodyElt) {
         let computedURL;
 
         try {
           computedURL = this._getComputedURL(bodyElt, "background-image");
           context.hasMultipleBGImages = false;
@@ -916,20 +921,20 @@ class ContextMenuChild extends JSWindowA
         }
 
         if (computedURL) {
           context.hasBGImage = true;
           context.bgImageURL = this._makeURLAbsolute(bodyElt.baseURI,
                                                     computedURL);
         }
       }
-    } else if ((context.target instanceof this.contentWindow.HTMLEmbedElement ||
-               context.target instanceof this.contentWindow.HTMLObjectElement) &&
-               context.target.displayedType == this.contentWindow.HTMLObjectElement.TYPE_NULL &&
-               context.target.pluginFallbackType == this.contentWindow.HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
+    } else if ((context.target instanceof this.content.HTMLEmbedElement ||
+               context.target instanceof this.content.HTMLObjectElement) &&
+               context.target.displayedType == this.content.HTMLObjectElement.TYPE_NULL &&
+               context.target.pluginFallbackType == this.content.HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
       context.onCTPPlugin = true;
     }
 
     context.canSpellCheck = this._isSpellCheckEnabled(context.target);
   }
 
   /**
    * Sets up the parts of the context menu for when when nodes have children.
@@ -948,21 +953,21 @@ class ContextMenuChild extends JSWindowA
       if (elem.nodeType == elem.ELEMENT_NODE) {
         // Link?
         const XLINK_NS = "http://www.w3.org/1999/xlink";
 
         if (!context.onLink &&
             // Be consistent with what hrefAndLinkNodeForClickEvent
             // does in browser.js
             (this._isXULTextLinkLabel(elem) ||
-            (elem instanceof this.contentWindow.HTMLAnchorElement && elem.href) ||
-            (elem instanceof this.contentWindow.SVGAElement &&
+            (elem instanceof this.content.HTMLAnchorElement && elem.href) ||
+            (elem instanceof this.content.SVGAElement &&
             (elem.href || elem.hasAttributeNS(XLINK_NS, "href"))) ||
-            (elem instanceof this.contentWindow.HTMLAreaElement && elem.href) ||
-            elem instanceof this.contentWindow.HTMLLinkElement ||
+            (elem instanceof this.content.HTMLAreaElement && elem.href) ||
+            elem instanceof this.content.HTMLLinkElement ||
             elem.getAttributeNS(XLINK_NS, "type") == "simple")) {
           // Target is a link or a descendant of a link.
           context.onLink = true;
 
           // Remember corresponding element.
           context.link = elem;
           context.linkURL = this._getLinkURL();
           context.linkURI = this._getLinkURI();
deleted file mode 100644
--- a/browser/actors/ContextMenuParent.jsm
+++ /dev/null
@@ -1,84 +0,0 @@
-/* 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 = ["ContextMenuParent"];
-
-class ContextMenuParent extends JSWindowActorParent {
-  receiveMessage(message) {
-    let browser = this.manager.rootFrameLoader.ownerElement;
-    let win = browser.ownerGlobal;
-    // It's possible that the <xul:browser> associated with this
-    // ContextMenu message doesn't belong to a window that actually
-    // loads nsContextMenu.js. In that case, try to find the chromeEventHandler,
-    // since that'll likely be the "top" <xul:browser>, and then use its window's
-    // nsContextMenu instance instead.
-    if (!win.openContextMenu) {
-      let topBrowser = browser.ownerGlobal.docShell.chromeEventHandler;
-      win = topBrowser.ownerGlobal;
-    }
-
-    win.openContextMenu(message, browser, this);
-  }
-
-  hiding() {
-    this.sendAsyncMessage("ContextMenu:Hiding", {});
-  }
-
-  reloadFrame(targetIdentifier, forceReload) {
-    this.sendAsyncMessage("ContextMenu:ReloadFrame", {
-      targetIdentifier,
-      forceReload,
-    });
-  }
-
-  reloadImage(targetIdentifier) {
-    this.sendAsyncMessage("ContextMenu:ReloadImage", { targetIdentifier });
-  }
-
-  getFrameTitle(targetIdentifier) {
-    return this.sendQuery("ContextMenu:GetFrameTitle", { targetIdentifier });
-  }
-
-  mediaCommand(targetIdentifier, command, data) {
-    let windowGlobal = this.manager.browsingContext.currentWindowGlobal;
-    let browser = windowGlobal.rootFrameLoader.ownerElement;
-    let win = browser.ownerGlobal;
-    let windowUtils = win.windowUtils;
-    this.sendAsyncMessage("ContextMenu:MediaCommand", {
-      targetIdentifier,
-      command,
-      data,
-      handlingUserInput: windowUtils.isHandlingUserInput,
-    });
-  }
-
-  canvasToBlobURL(targetIdentifier) {
-    return this.sendQuery("ContextMenu:Canvas:ToBlobURL",
-                          { targetIdentifier });
-  }
-
-  saveVideoFrameAsImage(targetIdentifier) {
-    return this.sendQuery("ContextMenu:SaveVideoFrameAsImage",
-                          { targetIdentifier });
-  }
-
-  setAsDesktopBackground(targetIdentifier) {
-    return this.sendQuery("ContextMenu:SetAsDesktopBackground",
-                          { targetIdentifier });
-  }
-
-  getSearchFieldBookmarkData(targetIdentifier) {
-    return this.sendQuery("ContextMenu:SearchFieldBookmarkData",
-                          { targetIdentifier });
-  }
-
-  doCustomCommand(generatedItemId, handlingUserInput) {
-    this.sendAsyncMessage("ContextMenu:DoCustomCommand", {
-      generatedItemId,
-      handlingUserInput,
-    });
-  }
-}
deleted file mode 100644
--- a/browser/actors/ContextMenuSpecialProcessChild.jsm
+++ /dev/null
@@ -1,25 +0,0 @@
-/* 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 = ["ContextMenuSpecialProcessChild"];
-
-const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const {E10SUtils} = ChromeUtils.import("resource://gre/modules/E10SUtils.jsm");
-
-/**
- * This module is a workaround for bug 1555154, where the contextmenu event doesn't
- * cause the JS Window Actor Child to be constructed automatically in the parent
- * process or extension process documents.
- */
-class ContextMenuSpecialProcessChild extends ActorChild {
-  handleEvent(event) {
-    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT ||
-        Services.appinfo.remoteType == E10SUtils.EXTENSION_REMOTE_TYPE) {
-      this.content.getWindowGlobalChild().getActor("ContextMenu").handleEvent(event);
-    }
-  }
-}
--- a/browser/actors/PluginChild.jsm
+++ b/browser/actors/PluginChild.jsm
@@ -48,20 +48,20 @@ class PluginChild extends ActorChild {
   receiveMessage(msg) {
     switch (msg.name) {
       case "BrowserPlugins:ActivatePlugins":
         this.activatePlugins(msg.data.pluginInfo, msg.data.newState);
         break;
       case "BrowserPlugins:ContextMenuCommand":
         switch (msg.data.command) {
           case "play":
-            this._showClickToPlayNotification(ContextMenuChild.getTarget(this.docShell.browsingContext, msg, "plugin"), true);
+            this._showClickToPlayNotification(ContextMenuChild.getTarget(this.mm, msg, "plugin"), true);
             break;
           case "hide":
-            this.hideClickToPlayOverlay(ContextMenuChild.getTarget(this.docShell.browsingContext, msg, "plugin"));
+            this.hideClickToPlayOverlay(ContextMenuChild.getTarget(this.mm, msg, "plugin"));
             break;
         }
         break;
       case "BrowserPlugins:NPAPIPluginProcessCrashed":
         this.NPAPIPluginProcessCrashed({
           pluginName: msg.data.pluginName,
           runID: msg.data.runID,
           state: msg.data.state,
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -24,18 +24,16 @@ with Files("WebRTCChild.jsm"):
 
 FINAL_TARGET_FILES.actors += [
     'AboutReaderChild.jsm',
     'BlockedSiteChild.jsm',
     'BrowserTabChild.jsm',
     'ClickHandlerChild.jsm',
     'ContentSearchChild.jsm',
     'ContextMenuChild.jsm',
-    'ContextMenuParent.jsm',
-    'ContextMenuSpecialProcessChild.jsm',
     'DOMFullscreenChild.jsm',
     'FormValidationChild.jsm',
     'LightweightThemeChild.jsm',
     'LinkHandlerChild.jsm',
     'NetErrorChild.jsm',
     'OfflineAppsChild.jsm',
     'PageInfoChild.jsm',
     'PageStyleChild.jsm',
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -119,17 +119,18 @@ XPCOMUtils.defineLazyScriptGetter(this, 
 XPCOMUtils.defineLazyScriptGetter(this, ["gGestureSupport", "gHistorySwipeAnimation"],
                                   "chrome://browser/content/browser-gestureSupport.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gSafeBrowsing",
                                   "chrome://browser/content/browser-safebrowsing.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gSync",
                                   "chrome://browser/content/browser-sync.js");
 XPCOMUtils.defineLazyScriptGetter(this, "gBrowserThumbnails",
                                   "chrome://browser/content/browser-thumbnails.js");
-XPCOMUtils.defineLazyScriptGetter(this, ["openContextMenu", "nsContextMenu"],
+XPCOMUtils.defineLazyScriptGetter(this, ["setContextMenuContentData",
+                                         "openContextMenu", "nsContextMenu"],
                                   "chrome://browser/content/nsContextMenu.js");
 XPCOMUtils.defineLazyScriptGetter(this, ["DownloadsPanel",
                                          "DownloadsOverlayLoader",
                                          "DownloadsSubview",
                                          "DownloadsView", "DownloadsViewUI",
                                          "DownloadsViewController",
                                          "DownloadsSummary", "DownloadsFooter",
                                          "DownloadsBlockedSubview"],
@@ -7623,21 +7624,37 @@ function BrowserOpenAddonsMgr(aView) {
       aSubject.QueryInterface(Ci.nsIDOMWindow);
       aSubject.focus();
       resolve(aSubject);
     }, "EM-loaded");
   });
 }
 
 function AddKeywordForSearchField() {
-  if (!gContextMenu) {
-    throw new Error("Context menu doesn't seem to be open.");
-  }
-
-  gContextMenu.addKeywordForSearchField();
+  let mm = gBrowser.selectedBrowser.messageManager;
+
+  let onMessage = (message) => {
+    mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+    let bookmarkData = message.data;
+    let title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+                                                    [bookmarkData.title]);
+    PlacesUIUtils.showBookmarkDialog({ action: "add",
+                                       type: "bookmark",
+                                       uri: makeURI(bookmarkData.spec),
+                                       title,
+                                       keyword: "",
+                                       postData: bookmarkData.postData,
+                                       charSet: bookmarkData.charset,
+                                       hiddenRows: [ "location", "tags" ],
+                                     }, window);
+  };
+  mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+  mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", {}, { target: gContextMenu.target });
 }
 
 /**
  * Re-open a closed tab.
  * @param aIndex
  *        The index of the tab (via SessionStore.getClosedTabData)
  * @returns a reference to the reopened tab.
  */
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -13,28 +13,30 @@ var {XPCOMUtils} = ChromeUtils.import("r
 
 // BrowserChildGlobal
 var global = this;
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   ContentMetaHandler: "resource:///modules/ContentMetaHandler.jsm",
   LoginFormFactory: "resource://gre/modules/LoginFormFactory.jsm",
   InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.jsm",
+  ContextMenuChild: "resource:///actors/ContextMenuChild.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "LoginManagerContent", () => {
   let tmp = {};
   ChromeUtils.import("resource://gre/modules/LoginManagerContent.jsm", tmp);
   tmp.LoginManagerContent.setupEventListeners(global);
   return tmp.LoginManagerContent;
 });
 
 // NOTE: Much of this logic is duplicated in BrowserCLH.js for Android.
 addMessageListener("PasswordManager:fillForm", function(message) {
   // intercept if ContextMenu.jsm had sent a plain object for remote targets
+  message.objects.inputElement = ContextMenuChild.getTarget(global, message, "inputElement");
   LoginManagerContent.receiveMessage(message, content);
 });
 
 function shouldIgnoreLoginManagerEvent(event) {
   // If we have a null principal then prevent any more password manager code from running and
   // incorrectly using the document `location`.
   return event.target.nodePrincipal.isNullPrincipal;
 }
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -22,69 +22,80 @@ XPCOMUtils.defineLazyModuleGetters(this,
 
 XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
   Components.Constructor("@mozilla.org/referrer-info;1",
                          "nsIReferrerInfo",
                          "init"));
 
 var gContextMenuContentData = null;
 
-function openContextMenu(aMessage, aBrowser, aActor) {
+function setContextMenuContentData(data) {
+  gContextMenuContentData = data;
+}
+
+function openContextMenu(aMessage) {
   let data = aMessage.data;
-  let browser = aBrowser;
-  let actor = aActor;
+  let browser = aMessage.target;
   let spellInfo = data.spellInfo;
   let frameReferrerInfo = data.frameReferrerInfo;
 
+  // ContextMenu.jsm sends us the target as a CPOW only so that
+  // we can send that CPOW back down to the content process and
+  // have it resolve to a DOM node. The parent should not attempt
+  // to access any properties on this CPOW (in fact, doing so
+  // will throw an exception).
+  data.context.targetAsCPOW = aMessage.objects.targetAsCPOW;
+
   if (spellInfo) {
-    spellInfo.target = browser.messageManager;
+    spellInfo.target = aMessage.target.messageManager;
   }
 
   let documentURIObject = makeURI(data.docLocation,
                                   data.charSet,
                                   makeURI(data.baseURI));
 
   if (frameReferrerInfo) {
     frameReferrerInfo =
       E10SUtils.deserializeReferrerInfo(frameReferrerInfo);
   }
 
   gContextMenuContentData = { context: data.context,
+                              isRemote: data.isRemote,
                               popupNodeSelectors: data.popupNodeSelectors,
                               browser,
-                              actor,
                               editFlags: data.editFlags,
                               spellInfo,
                               principal: data.principal,
                               customMenuItems: data.customMenuItems,
                               documentURIObject,
                               docLocation: data.docLocation,
                               charSet: data.charSet,
                               referrerInfo: E10SUtils.deserializeReferrerInfo(data.referrerInfo),
                               frameReferrerInfo,
                               contentType: data.contentType,
                               contentDisposition: data.contentDisposition,
                               frameOuterWindowID: data.frameOuterWindowID,
                               selectionInfo: data.selectionInfo,
-                              disableSetDesktopBackground: data.disableSetDesktopBackground,
+                              disableSetDesktopBackground: data.disableSetDesktopBg,
                               loginFillInfo: data.loginFillInfo,
                               parentAllowsMixedContent: data.parentAllowsMixedContent,
                               userContextId: data.userContextId,
                               webExtContextData: data.webExtContextData,
                             };
 
   let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
   let context = gContextMenuContentData.context;
 
   // The event is a CPOW that can't be passed into the native openPopupAtScreen
   // function. Therefore we synthesize a new MouseEvent to propagate the
   // inputSource to the subsequently triggered popupshowing event.
   var newEvent = document.createEvent("MouseEvent");
   newEvent.initNSMouseEvent("contextmenu", true, true, null, 0, context.screenX, context.screenY,
                             0, 0, false, false, false, false, 0, null, 0, context.mozInputSource);
+
   popup.openPopupAtScreen(newEvent.screenX, newEvent.screenY, true, newEvent);
 }
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
   this.initMenu(aXulMenu, aIsShift);
 }
 
@@ -95,19 +106,23 @@ nsContextMenu.prototype = {
     this.setContext();
 
     if (!this.shouldDisplay)
       return;
 
     this.hasPageMenu = false;
     this.isContentSelected = !this.selectionInfo.docSelectionIsCollapsed;
     if (!aIsShift) {
-      this.hasPageMenu =
-        PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
-                                  this.browser, aXulMenu);
+      if (this.isRemote) {
+        this.hasPageMenu =
+          PageMenuParent.addToPopup(gContextMenuContentData.customMenuItems,
+                                    this.browser, aXulMenu);
+      } else {
+        this.hasPageMenu = PageMenuParent.buildAndAddToPopup(this.target, aXulMenu);
+      }
 
       let tab = gBrowser && gBrowser.getTabForBrowser ?
         gBrowser.getTabForBrowser(this.browser) : undefined;
 
       let subject = {
         menu: aXulMenu,
         tab,
         timeStamp: this.timeStamp,
@@ -155,20 +170,22 @@ nsContextMenu.prototype = {
       BookmarkingUI.onCurrentPageContextPopupShowing();
 
     // Initialize (disable/remove) menu items.
     this.initItems();
   },
 
   setContext() {
     let context = Object.create(null);
+    this.isRemote = false;
 
     if (gContextMenuContentData) {
       context = gContextMenuContentData.context;
       gContextMenuContentData.context = null;
+      this.isRemote = gContextMenuContentData.isRemote;
     }
 
     this.shouldDisplay = context.shouldDisplay;
     this.timeStamp = context.timeStamp;
 
     // Assign what's _possibly_ needed from `context` sent by ContextMenuChild.jsm
     // Keep this consistent with the similar code in ContextMenu's _setContext
     this.bgImageURL          = context.bgImageURL;
@@ -209,72 +226,85 @@ nsContextMenu.prototype = {
     this.onMozExtLink        = context.onMozExtLink;
     this.onNumeric           = context.onNumeric;
     this.onPassword          = context.onPassword;
     this.onSaveableLink      = context.onSaveableLink;
     this.onSpellcheckable    = context.onSpellcheckable;
     this.onTextInput         = context.onTextInput;
     this.onVideo             = context.onVideo;
 
-    this.target = context.target;
-    this.targetIdentifier = context.targetIdentifier;
+    this.target = this.isRemote ? context.target : document.popupNode;
+    this.targetAsCPOW = context.targetAsCPOW;
 
     this.principal = context.principal;
     this.frameOuterWindowID = context.frameOuterWindowID;
 
     this.inSyntheticDoc = context.inSyntheticDoc;
     this.inAboutDevtoolsToolbox = context.inAboutDevtoolsToolbox;
 
+
     // Everything after this isn't sent directly from ContextMenu
-    if (this.target) {
-      this.ownerDoc = this.target.ownerDocument;
-    }
+    this.ownerDoc = this.target.ownerDocument;
 
     this.csp = E10SUtils.deserializeCSP(context.csp);
 
     // Remember the CSS selectors corresponding to clicked node. gContextMenuContentData
     // can be null if the menu was triggered by tests in which case use an empty array.
     this.targetSelectors = gContextMenuContentData
                            ? gContextMenuContentData.popupNodeSelectors
                            : [];
 
-    if (gContextMenuContentData) {
+    if (this.isRemote) {
       this.browser = gContextMenuContentData.browser;
       this.selectionInfo = gContextMenuContentData.selectionInfo;
-      this.actor = gContextMenuContentData.actor;
     } else {
       this.browser = this.ownerDoc.defaultView.docShell.chromeEventHandler;
       this.selectionInfo = BrowserUtils.getSelectionDetails(window);
-      this.actor = this.browser.browsingContext.currentWindowGlobal.getActor("ContextMenu");
     }
 
     const {gBrowser} = this.browser.ownerGlobal;
 
     this.textSelected      = this.selectionInfo.text;
     this.isTextSelected    = this.textSelected.length != 0;
     this.webExtBrowserType = this.browser.getAttribute("webextension-view-type");
     this.inWebExtBrowser   = !!this.webExtBrowserType;
     this.inTabBrowser      = gBrowser && gBrowser.getTabForBrowser ?
       !!gBrowser.getTabForBrowser(this.browser) : false;
 
     if (context.shouldInitInlineSpellCheckerUINoChildren) {
-      InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo, this.actor.manager);
+      if (this.isRemote) {
+        InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+      } else {
+        InlineSpellCheckerUI.init(this.target.editor);
+        InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
+                                           document.popupRangeOffset);
+      }
     }
 
     if (context.shouldInitInlineSpellCheckerUIWithChildren) {
-      InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo, this.actor.manager);
+      if (this.isRemote) {
+        InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
+      } else {
+        var targetWin = this.ownerDoc.defaultView;
+        var {editingSession} = targetWin.docShell;
+
+        InlineSpellCheckerUI.init(editingSession.getEditorForWindow(targetWin));
+        InlineSpellCheckerUI.initFromEvent(document.popupRangeParent,
+                                           document.popupRangeOffset);
+      }
+
       let canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
       this.showItem("spell-check-enabled", canSpell);
       this.showItem("spell-separator", canSpell);
     }
   },  // setContext
 
   hiding: function CM_hiding() {
-    if (this.actor) {
-      this.actor.hiding();
+    if (this.browser && this.browser.messageManager) {
+      this.browser.messageManager.sendAsyncMessage("ContextMenu:Hiding");
     }
 
     gContextMenuContentData = null;
     InlineSpellCheckerUI.clearSuggestionsFromMenu();
     InlineSpellCheckerUI.clearDictionaryListFromMenu();
     InlineSpellCheckerUI.uninit();
     if (Cu.isModuleLoaded("resource://gre/modules/LoginManagerContextMenu.jsm")) {
       LoginManagerContextMenu.clearLoginsFromMenu(document);
@@ -727,21 +757,18 @@ nsContextMenu.prototype = {
     } else {
       fillMenu.setAttribute("label", fillMenu.getAttribute("label-login"));
       fillMenu.setAttribute("accesskey", fillMenu.getAttribute("accesskey-login"));
     }
 
     if (!showFill || disableFill) {
       return;
     }
-
     let documentURI = gContextMenuContentData.documentURIObject;
-    let fragment = LoginManagerContextMenu.addLoginsToMenu(this.targetIdentifier,
-                                                           this.browser,
-                                                           documentURI);
+    let fragment = LoginManagerContextMenu.addLoginsToMenu(this.targetAsCPOW, this.browser, documentURI);
 
     this.showItem("fill-login-no-logins", !fragment);
 
     if (!fragment) {
       return;
     }
     let popup = document.getElementById("fill-login-popup");
     let insertBeforeElement = document.getElementById("fill-login-no-logins");
@@ -772,16 +799,22 @@ nsContextMenu.prototype = {
                    originPrincipal: this.principal,
                    triggeringPrincipal: this.principal,
                    csp: this.csp,
                    frameOuterWindowID: gContextMenuContentData.frameOuterWindowID};
     for (let p in extra) {
       params[p] = extra[p];
     }
 
+    if (!this.isRemote) {
+      // Propagate the frameOuterWindowID value saved when
+      // the context menu has been opened.
+      params.frameOuterWindowID = this.frameOuterWindowID;
+    }
+
     let referrerInfo = gContextMenuContentData.referrerInfo;
     // If we want to change userContextId, we must be sure that we don't
     // propagate the referrer.
     if (("userContextId" in params &&
         params.userContextId != gContextMenuContentData.userContextId) ||
       this.onPlainTextLink) {
       referrerInfo = new ReferrerInfo(referrerInfo.referrerPolicy, false,
         referrerInfo.originalReferrer);
@@ -841,17 +874,18 @@ nsContextMenu.prototype = {
                  triggeringPrincipal: this.browser.contentPrincipal,
                  csp: this.browser.csp,
                  referrerInfo: gContextMenuContentData.frameReferrerInfo });
   },
 
   // Reload clicked-in frame.
   reloadFrame(aEvent) {
     let forceReload = aEvent.shiftKey;
-    this.actor.reloadFrame(this.targetIdentifier, forceReload);
+    this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadFrame",
+                                                 null, { target: this.target, forceReload });
   },
 
   // Open clicked-in frame in its own window.
   openFrame() {
     openLinkIn(gContextMenuContentData.docLocation, "window",
                { charset: gContextMenuContentData.charSet,
                  triggeringPrincipal: this.browser.contentPrincipal,
                  csp: this.browser.csp,
@@ -934,74 +968,93 @@ nsContextMenu.prototype = {
     BrowserPageInfo(gContextMenuContentData.docLocation, null, null,
                     this.frameOuterWindowID, this.browser);
   },
 
   reloadImage() {
     urlSecurityCheck(this.mediaURL,
                      this.principal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
-    this.actor.reloadImage(this.targetIdentifier);
+
+    this.browser.messageManager.sendAsyncMessage("ContextMenu:ReloadImage",
+                                                 null, { target: this.target });
   },
 
-  _canvasToBlobURL(targetIdentifier) {
-    return this.actor.canvasToBlobURL(targetIdentifier);
+  _canvasToBlobURL(target) {
+    let mm = this.browser.messageManager;
+    return new Promise(function(resolve) {
+      mm.sendAsyncMessage("ContextMenu:Canvas:ToBlobURL", {}, { target });
+
+      let onMessage = (message) => {
+        mm.removeMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
+        resolve(message.data.blobURL);
+      };
+      mm.addMessageListener("ContextMenu:Canvas:ToBlobURL:Result", onMessage);
+    });
   },
 
   // Change current window to the URL of the image, video, or audio.
   viewMedia(e) {
     let referrerInfo = gContextMenuContentData.referrerInfo;
     let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
     if (this.onCanvas) {
-      this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
+      this._canvasToBlobURL(this.target).then(function(blobURL) {
         openUILink(blobURL, e, { referrerInfo,
                                  triggeringPrincipal: systemPrincipal});
       }, Cu.reportError);
     } else {
       urlSecurityCheck(this.mediaURL,
                        this.principal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
       openUILink(this.mediaURL, e, { referrerInfo,
                                      forceAllowDataURI: true,
                                      triggeringPrincipal: this.principal,
                                      csp: this.csp,
       });
     }
   },
 
   saveVideoFrameAsImage() {
+    let mm = this.browser.messageManager;
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
 
     let name = "";
     if (this.mediaURL) {
       try {
         let uri = makeURI(this.mediaURL);
         let url = uri.QueryInterface(Ci.nsIURL);
         if (url.fileBaseName)
           name = decodeURI(url.fileBaseName) + ".jpg";
       } catch (e) { }
     }
     if (!name)
       name = "snapshot.jpg";
 
+    mm.sendAsyncMessage("ContextMenu:SaveVideoFrameAsImage", {}, {
+      target: this.target,
+    });
+
     // Cache this because we fetch the data async
     let {documentURIObject} = gContextMenuContentData;
 
-    this.actor.saveVideoFrameAsImage(this.targetIdentifier).then(dataURL => {
+    let onMessage = (message) => {
+      mm.removeMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
       // FIXME can we switch this to a blob URL?
+      let dataURL = message.data.dataURL;
       saveImageURL(dataURL, name, "SaveImageTitle",
                    true, // bypass cache
                    false, // don't skip prompt for where to save
                    documentURIObject, // referrer
                    null, // document
                    null, // content type
                    null, // content disposition
                    isPrivate,
                    this.principal);
-    });
+    };
+    mm.addMessageListener("ContextMenu:SaveVideoFrameAsImage:Result", onMessage);
   },
 
   leaveDOMFullScreen() {
     document.exitFullscreen();
   },
 
   // Change current window to the URL of the background image.
   viewBGImage(e) {
@@ -1011,27 +1064,33 @@ nsContextMenu.prototype = {
 
     openUILink(this.bgImageURL, e, { referrerInfo: gContextMenuContentData.referrerInfo,
                                      triggeringPrincipal: this.principal,
                                      csp: this.csp,
     });
   },
 
   setDesktopBackground() {
-    if (!Services.policies.isAllowed("setDesktopBackground")) {
-      return;
-    }
+    let mm = this.browser.messageManager;
+
+    mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
+                        { target: this.target });
 
-    this.actor.setAsDesktopBackground(this.targetIdentifier).then(({ failed, dataURL, imageName }) => {
-      if (failed) {
+    let onMessage = (message) => {
+      mm.removeMessageListener("ContextMenu:SetAsDesktopBackground:Result",
+                               onMessage);
+
+      if (message.data.disable ||
+          !Services.policies.isAllowed("setDesktopBackground")) {
         return;
       }
 
       let image = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
-      image.src = dataURL;
+      image.src = message.data.dataUrl;
+      let imageName = message.data.imageName;
 
       // Confirm since it's annoying if you hit this accidentally.
       const kDesktopBackgroundURL =
                     "chrome://browser/content/setDesktopBackground.xul";
 
       if (AppConstants.platform == "macosx") {
         // On Mac, the Set Desktop Background window is not modal.
         // Don't open more than one Set Desktop Background window.
@@ -1045,17 +1104,19 @@ nsContextMenu.prototype = {
                      image, imageName);
         }
       } else {
         // On non-Mac platforms, the Set Wallpaper dialog is modal.
         openDialog(kDesktopBackgroundURL, "",
                    "centerscreen,chrome,dialog,modal,dependent",
                    image, imageName);
       }
-    });
+    };
+
+    mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
   },
 
   // Save URL of clicked-on frame.
   saveFrame() {
     saveBrowser(this.browser, false, this.frameOuterWindowID);
   },
 
   // Helper function to wait for appropriate MIME-type headers and
@@ -1200,39 +1261,39 @@ nsContextMenu.prototype = {
                            timer.TYPE_ONE_SHOT);
 
     // kick off the channel with our proxy object as the listener
     channel.asyncOpen(new saveAsListener(this.principal));
   },
 
   // Save URL of clicked-on link.
   saveLink() {
-    let isContentWindowPrivate = this.ownerDoc.isPrivate;
+    let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
     this.saveHelper(this.linkURL, this.linkTextStr, null, true, this.ownerDoc,
                     gContextMenuContentData.documentURIObject,
                     this.frameOuterWindowID,
                     this.linkDownload,
                     isContentWindowPrivate);
   },
 
   // Backwards-compatibility wrapper
   saveImage() {
     if (this.onCanvas || this.onImage)
         this.saveMedia();
   },
 
   // Save URL of the clicked upon image, video, or audio.
   saveMedia() {
     let doc = this.ownerDoc;
-    let isContentWindowPrivate = this.ownerDoc.isPrivate;
+    let isContentWindowPrivate = this.isRemote ? this.ownerDoc.isPrivate : undefined;
     let referrerURI = gContextMenuContentData.documentURIObject;
     let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
     if (this.onCanvas) {
       // Bypass cache, since it's a data: URL.
-      this._canvasToBlobURL(this.targetIdentifier).then(function(blobURL) {
+      this._canvasToBlobURL(this.target).then(function(blobURL) {
         saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
                      true, false, referrerURI, null, null, null,
                      isPrivate,
                      document.nodePrincipal /* system, because blob: */);
       }, Cu.reportError);
     } else if (this.onImage) {
       urlSecurityCheck(this.mediaURL, this.principal);
       saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
@@ -1293,32 +1354,16 @@ nsContextMenu.prototype = {
   copyLink() {
     // If we're in a view source tab, remove the view-source: prefix
     let linkURL = this.linkURL.replace(/^view-source:/, "");
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(linkURL);
   },
 
-  addKeywordForSearchField() {
-    this.actor.getSearchFieldBookmarkData(this.targetIdentifier).then(data => {
-      let title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
-                                                      [data.title]);
-      PlacesUIUtils.showBookmarkDialog({ action: "add",
-                                         type: "bookmark",
-                                         uri: makeURI(data.spec),
-                                         title,
-                                         keyword: "",
-                                         postData: data.postData,
-                                         charSet: data.charset,
-                                         hiddenRows: [ "location", "tags" ],
-                                       }, window);
-    });
-  },
-
   /**
    * Utilities
    */
 
   /**
    * Show/hide one item (specified via name or the item element itself).
    * If the element is not found, then this function finishes silently.
    *
@@ -1426,37 +1471,50 @@ nsContextMenu.prototype = {
 
   bookmarkLink: function CM_bookmarkLink() {
     window.top.PlacesCommandHook.bookmarkLink(this.linkURL, this.linkTextStr)
                                 .catch(Cu.reportError);
   },
 
   addBookmarkForFrame: function CM_addBookmarkForFrame() {
     let uri = gContextMenuContentData.documentURIObject;
+    let mm = this.browser.messageManager;
 
-    this.actor.getFrameTitle(this.targetIdentifier).then(title => {
-      window.top.PlacesCommandHook.bookmarkLink(uri.spec, title)
+    let onMessage = (message) => {
+      mm.removeMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
+
+      window.top.PlacesCommandHook.bookmarkLink(uri.spec, message.data.title)
                                   .catch(Cu.reportError);
-    });
+    };
+    mm.addMessageListener("ContextMenu:BookmarkFrame:Result", onMessage);
+
+    mm.sendAsyncMessage("ContextMenu:BookmarkFrame", null, { target: this.target });
   },
 
   savePageAs: function CM_savePageAs() {
     saveBrowser(this.browser);
   },
 
   printFrame: function CM_printFrame() {
     PrintUtils.printWindow(this.frameOuterWindowID, this.browser);
   },
 
   switchPageDirection: function CM_switchPageDirection() {
     this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
   },
 
   mediaCommand: function CM_mediaCommand(command, data) {
-    this.actor.mediaCommand(this.targetIdentifier, command, data);
+    let mm = this.browser.messageManager;
+    let win = this.browser.ownerGlobal;
+    let windowUtils = win.windowUtils;
+    mm.sendAsyncMessage("ContextMenu:MediaCommand",
+                        {command,
+                         data,
+                         handlingUserInput: windowUtils.isHandlingUserInput},
+                        {element: this.target});
   },
 
   copyMediaLocation() {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(this.mediaURL);
   },
 
@@ -1509,13 +1567,9 @@ nsContextMenu.prototype = {
 
   createContainerMenu(aEvent) {
     let createMenuOptions = {
       isContextMenu: true,
       excludeUserContextId: gContextMenuContentData.userContextId,
     };
     return createUserContextMenu(aEvent, createMenuOptions);
   },
-
-  doCustomCommand(generatedItemId, handlingUserInput) {
-    this.actor.doCustomCommand(generatedItemId, handlingUserInput);
-  },
 };
--- a/browser/base/content/tabbrowser.js
+++ b/browser/base/content/tabbrowser.js
@@ -37,21 +37,19 @@ window._gBrowser = {
     if (Services.prefs.getBoolPref("browser.display.use_system_colors")) {
       this.tabpanels.style.backgroundColor = "-moz-default-background-color";
     } else if (Services.prefs.getIntPref("browser.display.document_color_use") == 2) {
       this.tabpanels.style.backgroundColor =
         Services.prefs.getCharPref("browser.display.background_color");
     }
 
     let messageManager = window.getGroupMessageManager("browsers");
-    window.messageManager.addMessageListener("contextmenu", this);
-
     if (gMultiProcessBrowser) {
       messageManager.addMessageListener("DOMTitleChanged", this);
-      messageManager.addMessageListener("DOMWindowClose", this);
+      window.messageManager.addMessageListener("contextmenu", this);
       messageManager.addMessageListener("Browser:Init", this);
     } else {
       this._outerWindowIDBrowserMap.set(this.selectedBrowser.outerWindowID,
         this.selectedBrowser);
     }
     messageManager.addMessageListener("RefreshBlocker:Blocked", this);
     messageManager.addMessageListener("Browser:WindowCreated", this);
 
--- a/browser/base/content/test/general/browser_addKeywordSearch.js
+++ b/browser/base/content/test/general/browser_addKeywordSearch.js
@@ -43,30 +43,38 @@ add_task(async function() {
         element.setAttribute("name", args.param);
         form.appendChild(element);
         doc.body.appendChild(form);
       });
 
       await BrowserTestUtils.synthesizeMouseAtCenter(`#${id} > input`,
                                                      { type: "contextmenu", button: 2 },
                                                      tab.linkedBrowser);
-      await contextMenuPromise;
-      let url = action || tab.linkedBrowser.currentURI.spec;
-      let actor = gContextMenu.actor;
+      let target = await contextMenuPromise;
 
-      let data = await actor.getSearchFieldBookmarkData(gContextMenu.targetIdentifier);
-      if (method == "GET") {
-        ok(data.spec.endsWith(`${param}=%s`),
-           `Check expected url for field named ${param} and action ${action}`);
-      } else {
-        is(data.spec, url,
-          `Check expected url for field named ${param} and action ${action}`);
-        is(data.postData, `${param}%3D%25s`,
-          `Check expected POST data for field named ${param} and action ${action}`);
-      }
+      await new Promise(resolve => {
+        let url = action || tab.linkedBrowser.currentURI.spec;
+        let mm = tab.linkedBrowser.messageManager;
+        let onMessage = (message) => {
+          mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+          if (method == "GET") {
+            ok(message.data.spec.endsWith(`${param}=%s`),
+             `Check expected url for field named ${param} and action ${action}`);
+          } else {
+            is(message.data.spec, url,
+             `Check expected url for field named ${param} and action ${action}`);
+            is(message.data.postData, `${param}%3D%25s`,
+             `Check expected POST data for field named ${param} and action ${action}`);
+          }
+          resolve();
+        };
+        mm.addMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
+
+        mm.sendAsyncMessage("ContextMenu:SearchFieldBookmarkData", null, { target });
+      });
 
       let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
       contextMenu.hidePopup();
       await popupHiddenPromise;
     }
   }
 
   BrowserTestUtils.removeTab(tab);
--- a/browser/components/BrowserGlue.jsm
+++ b/browser/components/BrowserGlue.jsm
@@ -11,31 +11,16 @@ const {Services} = ChromeUtils.import("r
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ActorManagerParent",
                                "resource://gre/modules/ActorManagerParent.jsm");
 
 const PREF_PDFJS_ENABLED_CACHE_STATE = "pdfjs.enabledCache.state";
 
 let ACTORS = {
-  ContextMenu: {
-    parent: {
-      moduleURI: "resource:///actors/ContextMenuParent.jsm",
-    },
-
-    child: {
-      moduleURI: "resource:///actors/ContextMenuChild.jsm",
-      events: {
-        "contextmenu": { mozSystemGroup: true },
-      },
-    },
-
-    allFrames: true,
-  },
-
   SubframeCrash: {
     parent: {
       moduleURI: "resource:///actors/SubframeCrashParent.jsm",
     },
 
     child: {
       moduleURI: "resource:///actors/SubframeCrashChild.jsm",
     },
@@ -126,41 +111,40 @@ let LEGACY_ACTORS = {
       module: "resource:///actors/ClickHandlerChild.jsm",
       events: {
         "click": {capture: true, mozSystemGroup: true},
         "auxclick": {capture: true, mozSystemGroup: true},
       },
     },
   },
 
+  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", "about:privatebrowsing",
                 "chrome://mochitests/content/*"],
       events: {
         "ContentSearchClient": {capture: true, wantUntrusted: true},
       },
       messages: [
         "ContentSearch",
       ],
     },
   },
 
-  ContextMenuSpecialProcess: {
-    child: {
-      module: "resource:///actors/ContextMenuSpecialProcessChild.jsm",
-      events: {
-        "contextmenu": {mozSystemGroup: true},
-      },
-    },
-    allFrames: true,
-  },
-
   DOMFullscreen: {
     child: {
       module: "resource:///actors/DOMFullscreenChild.jsm",
       group: "browsers",
       events: {
         "MozDOMFullscreen:Request": {},
         "MozDOMFullscreen:Entered": {},
         "MozDOMFullscreen:NewOrigin": {},
--- a/browser/components/extensions/child/ext-menus-child.js
+++ b/browser/components/extensions/child/ext-menus-child.js
@@ -4,17 +4,17 @@ ChromeUtils.defineModuleGetter(this, "Co
                                "resource:///actors/ContextMenuChild.jsm");
 
 this.menusChild = class extends ExtensionAPI {
   getAPI(context) {
     return {
       menus: {
         getTargetElement(targetElementId) {
           let element;
-          let lastMenuTarget = ContextMenuChild.getLastTarget(context.contentWindow.docShell.browsingContext);
+          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/places/PlacesUIUtils.jsm
+++ b/browser/components/places/PlacesUIUtils.jsm
@@ -327,20 +327,16 @@ var PlacesUIUtils = {
    * Returns the closet ancestor places view for the given DOM node
    * @param aNode
    *        a DOM node
    * @return the closet ancestor places view if exists, null otherwsie.
    */
   getViewForNode: function PUIU_getViewForNode(aNode) {
     let node = aNode;
 
-    if (Cu.isDeadWrapper(node)) {
-      return null;
-    }
-
     if (node.localName == "panelview" && node._placesView) {
       return node._placesView;
     }
 
     // The view for a <menu> of which its associated menupopup is a places
     // view, is the menupopup.
     if (node.localName == "menu" && !node._placesNode &&
         node.lastChild._placesView)
deleted file mode 100644
--- a/toolkit/actors/InlineSpellCheckerChild.jsm
+++ /dev/null
@@ -1,38 +0,0 @@
-/* -*- 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 = ["InlineSpellCheckerChild"];
-
-ChromeUtils.defineModuleGetter(this, "InlineSpellCheckerContent",
-                               "resource://gre/modules/InlineSpellCheckerContent.jsm");
-
-class InlineSpellCheckerChild extends JSWindowActorChild {
-  receiveMessage(msg) {
-    switch (msg.name) {
-      case "InlineSpellChecker:selectDictionary":
-        InlineSpellCheckerContent.selectDictionary(msg.data.localeCode);
-        break;
-
-      case "InlineSpellChecker:replaceMisspelling":
-        InlineSpellCheckerContent.replaceMisspelling(msg.data.index);
-        break;
-
-      case "InlineSpellChecker:toggleEnabled":
-        InlineSpellCheckerContent.toggleEnabled();
-        break;
-
-      case "InlineSpellChecker:recheck":
-        InlineSpellCheckerContent.recheck();
-        break;
-
-      case "InlineSpellChecker:uninit":
-        InlineSpellCheckerContent.uninitContextMenu();
-        break;
-    }
-  }
-}
deleted file mode 100644
--- a/toolkit/actors/InlineSpellCheckerParent.jsm
+++ /dev/null
@@ -1,33 +0,0 @@
-/* -*- 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 = ["InlineSpellCheckerParent"];
-
-class InlineSpellCheckerParent extends JSWindowActorParent {
-  selectDictionary({ localeCode }) {
-    this.sendAsyncMessage("InlineSpellChecker:selectDictionary",
-                          { localeCode });
-  }
-
-  replaceMisspelling({ index }) {
-    this.sendAsyncMessage("InlineSpellChecker:replaceMisspelling",
-                          { index });
-  }
-
-  toggleEnabled() {
-    this.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {});
-  }
-
-  recheckSpelling() {
-    this.sendAsyncMessage("InlineSpellChecker:recheck", {});
-  }
-
-  uninit() {
-    this.sendAsyncMessage("InlineSpellChecker:uninit", {});
-  }
-}
--- a/toolkit/actors/moz.build
+++ b/toolkit/actors/moz.build
@@ -23,18 +23,16 @@ FINAL_TARGET_FILES.actors += [
     'AutoplayChild.jsm',
     'BrowserElementChild.jsm',
     'BrowserElementParent.jsm',
     'ControllersChild.jsm',
     'DateTimePickerChild.jsm',
     'ExtFindChild.jsm',
     'FindBarChild.jsm',
     'FinderChild.jsm',
-    'InlineSpellCheckerChild.jsm',
-    'InlineSpellCheckerParent.jsm',
     'KeyPressEventModelCheckerChild.jsm',
     'PictureInPictureChild.jsm',
     'PopupBlockingChild.jsm',
     'PrintingChild.jsm',
     'PurgeSessionHistoryChild.jsm',
     'SelectChild.jsm',
     'SelectionSourceChild.jsm',
     'ThumbnailsChild.jsm',
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -29,18 +29,16 @@ ChromeUtils.defineModuleGetter(this, "Fo
 ChromeUtils.defineModuleGetter(this, "LoginFormFactory",
                                "resource://gre/modules/LoginFormFactory.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginRecipesContent",
                                "resource://gre/modules/LoginRecipes.jsm");
 ChromeUtils.defineModuleGetter(this, "LoginHelper",
                                "resource://gre/modules/LoginHelper.jsm");
 ChromeUtils.defineModuleGetter(this, "InsecurePasswordUtils",
                                "resource://gre/modules/InsecurePasswordUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "ContentDOMReference",
-                               "resource://gre/modules/ContentDOMReference.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gNetUtil",
                                    "@mozilla.org/network/util;1",
                                    "nsINetUtil");
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   let logger = LoginHelper.createLogger("LoginManagerContent");
   return logger.log.bind(logger);
@@ -301,17 +299,17 @@ this.LoginManagerContent = {
 
   receiveMessage(msg, topWindow) {
     if (msg.name == "PasswordManager:fillForm") {
       this.fillForm({
         topDocument: topWindow.document,
         loginFormOrigin: msg.data.loginFormOrigin,
         loginsFound: LoginHelper.vanillaObjectsToLogins(msg.data.logins),
         recipes: msg.data.recipes,
-        inputElementIdentifier: msg.data.inputElementIdentifier,
+        inputElement: msg.objects.inputElement,
       });
       return;
     }
 
     switch (msg.name) {
       case "PasswordManager:loginsFound": {
         let loginsFound = LoginHelper.vanillaObjectsToLogins(msg.data.logins);
         let request = this._takeRequest(msg);
@@ -681,32 +679,25 @@ this.LoginManagerContent = {
    *            This must match the origin of the form used for the fill.
    *          loginsFound:
    *            Array containing the login to fill. While other messages may
    *            have more logins, for this use case this is expected to have
    *            exactly one element. The origin of the login may be different
    *            from the origin of the form used for the fill.
    *          recipes:
    *            Fill recipes transmitted together with the original message.
-   *          inputElementIdentifier:
-   *            An identifier generated for the input element via ContentDOMReference.
+   *          inputElement:
+   *            Username or password input element from the form we want to fill.
    *        }
    */
-  fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElementIdentifier }) {
-    if (!inputElementIdentifier) {
+  fillForm({ topDocument, loginFormOrigin, loginsFound, recipes, inputElement }) {
+    if (!inputElement) {
       log("fillForm: No input element specified");
       return;
     }
-
-    let inputElement = ContentDOMReference.resolve(inputElementIdentifier);
-    if (!inputElement) {
-      log("fillForm: Could not resolve inputElementIdentifier to a living element.");
-      return;
-    }
-
     if (LoginHelper.getLoginOrigin(topDocument.documentURI) != loginFormOrigin) {
       if (!inputElement ||
           LoginHelper.getLoginOrigin(inputElement.ownerDocument.documentURI) != loginFormOrigin) {
         log("fillForm: The requested origin doesn't match the one from the",
             "document. This may mean we navigated to a document from a different",
             "site before we had a chance to indicate this change in the user",
             "interface.");
         return;
--- a/toolkit/components/passwordmgr/LoginManagerContextMenu.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContextMenu.jsm
@@ -16,27 +16,27 @@ ChromeUtils.defineModuleGetter(this, "Lo
 
 /**
  * Password manager object for the browser contextual menu.
  */
 this.LoginManagerContextMenu = {
   /**
    * Look for login items and add them to the contextual menu.
    *
-   * @param {Object} inputElementIdentifier
-   *        An identifier generated for the input element via ContentDOMReference.
+   * @param {HTMLInputElement} inputElement
+   *        The target input element of the context menu click.
    * @param {xul:browser} browser
    *        The browser for the document the context menu was open on.
    * @param {nsIURI} documentURI
    *        The URI of the document that the context menu was activated from.
    *        This isn't the same as the browser's top-level document URI
    *        when subframes are involved.
    * @returns {DocumentFragment} a document fragment with all the login items.
    */
-  addLoginsToMenu(inputElementIdentifier, browser, documentURI) {
+  addLoginsToMenu(inputElement, browser, documentURI) {
     let foundLogins = this._findLogins(documentURI);
 
     if (!foundLogins.length) {
       return null;
     }
 
     let fragment = browser.ownerDocument.createDocumentFragment();
     let duplicateUsernames = this._findDuplicates(foundLogins);
@@ -53,17 +53,17 @@ this.LoginManagerContextMenu = {
         let time = this.dateAndTimeFormatter.format(new Date(meta.timePasswordChanged));
         username = this._getLocalizedString("loginHostAge", [username, time]);
       }
       item.setAttribute("label", username);
       item.setAttribute("class", "context-login-item");
 
       // login is bound so we can keep the reference to each object.
       item.addEventListener("command", function(login, event) {
-        this._fillTargetField(login, inputElementIdentifier, browser, documentURI);
+        this._fillTargetField(login, inputElement, browser, documentURI);
       }.bind(this, login));
 
       fragment.appendChild(item);
     }
 
     return fragment;
   },
 
@@ -144,31 +144,31 @@ this.LoginManagerContextMenu = {
       seen.add(login.username);
     }
     return duplicates;
   },
 
   /**
    * @param {nsILoginInfo} login
    *        The login we want to fill the form with.
-   * @param {Object} inputElementIdentifier
-   *        An identifier generated for the input element via ContentDOMReference.
+   * @param {Element} inputElement
+   *        The target input element we want to fill.
    * @param {xul:browser} browser
    *        The target tab browser.
    * @param {nsIURI} documentURI
    *        URI of the document owning the form we want to fill.
    *        This isn't the same as the browser's top-level
    *        document URI when subframes are involved.
    */
-  _fillTargetField(login, inputElementIdentifier, browser, documentURI) {
+  _fillTargetField(login, inputElement, browser, documentURI) {
     LoginManagerParent.fillForm({
       browser,
-      inputElementIdentifier,
       loginFormOrigin: documentURI.displayPrePath,
       login,
+      inputElement,
     }).catch(Cu.reportError);
   },
 
   /**
    * @param {string} key
    *        The localized string key
    * @param {string[]} formatArgs
    *        An array of formatting argument string
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -163,39 +163,39 @@ this.LoginManagerParent = {
 
     return undefined;
   },
 
   /**
    * Trigger a login form fill and send relevant data (e.g. logins and recipes)
    * to the child process (LoginManagerContent).
    */
-  async fillForm({ browser, loginFormOrigin, login, inputElementIdentifier }) {
+  async fillForm({ browser, loginFormOrigin, login, inputElement }) {
     let recipes = [];
     if (loginFormOrigin) {
       let formHost;
       try {
         formHost = (new URL(loginFormOrigin)).host;
         let recipeManager = await this.recipeParentPromise;
         recipes = recipeManager.getRecipesForHost(formHost);
       } catch (ex) {
         // Some schemes e.g. chrome aren't supported by URL
       }
     }
 
     // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
     // doesn't support structured cloning.
     let jsLogins = [LoginHelper.loginToVanillaObject(login)];
 
+    let objects = inputElement ? {inputElement} : null;
     browser.messageManager.sendAsyncMessage("PasswordManager:fillForm", {
-      inputElementIdentifier,
       loginFormOrigin,
       logins: jsLogins,
       recipes,
-    });
+    }, objects);
   },
 
   /**
    * Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
    */
   async sendLoginDataToChild(formOrigin, actionOrigin, requestId, target, {
     guid,
     showMasterPassword,
--- a/toolkit/modules/ActorManagerParent.jsm
+++ b/toolkit/modules/ActorManagerParent.jsm
@@ -110,28 +110,16 @@ let ACTORS = {
       moduleURI: "resource://gre/actors/BrowserElementChild.jsm",
       events: {
         "DOMWindowClose": {},
       },
     },
 
     allFrames: true,
   },
-
-  InlineSpellChecker: {
-    parent: {
-      moduleURI: "resource://gre/actors/InlineSpellCheckerParent.jsm",
-    },
-
-    child: {
-      moduleURI: "resource://gre/actors/InlineSpellCheckerChild.jsm",
-    },
-
-    allFrames: true,
-  },
 };
 
 let LEGACY_ACTORS = {
   AudioPlayback: {
     child: {
       module: "resource://gre/actors/AudioPlaybackChild.jsm",
       messages: [
         "AudioPlayback",
deleted file mode 100644
--- a/toolkit/modules/ContentDOMReference.jsm
+++ /dev/null
@@ -1,141 +0,0 @@
-/* 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/. */
-
-/**
- * This module holds weak references to DOM elements that exist within the
- * current content process, and converts them to a unique identifier that can be
- * passed between processes. The identifer, if received by the same content process
- * that issued it, can then be converted back into the DOM element (presuming the
- * element hasn't had all of its other references dropped).
- *
- * The hope is that this module can eliminate the need for passing CPOW references
- * between processes during runtime.
- */
-
-var EXPORTED_SYMBOLS = ["ContentDOMReference"];
-
-const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
-                                   "@mozilla.org/uuid-generator;1",
-                                   "nsIUUIDGenerator");
-
-/**
- * An identifier generated by ContentDOMReference is a unique pair of BrowsingContext
- * ID and a UUID. gRegistry maps BrowsingContext's to an object with the following
- * properties:
- *
- *   UUIDToElement:
- *     A Map of UUID strings to WeakReference's to the elements they refer to.
- *
- *   elementToUUID:
- *     A WeakMap from a DOM element to a UUID that refers to it.
- */
-var gRegistry = new WeakMap();
-
-var ContentDOMReference = {
-  /**
-   * Generate and return an identifier for a given DOM element.
-   *
-   * @param {Element} element The DOM element to generate the identifier for.
-   * @return {Object} The identifier for the DOM element that can be passed between
-   * processes as a message.
-   */
-  get(element) {
-    if (!element) {
-      throw new Error("Can't create a ContentDOMReference identifier for " +
-                      "non-existant nodes.");
-    }
-
-    let browsingContext = element.ownerGlobal.getWindowGlobalChild().browsingContext;
-    let mappings = gRegistry.get(browsingContext);
-    if (!mappings) {
-      mappings = {
-        UUIDToElement: new Map(),
-        elementToUUID: new WeakMap(),
-      };
-      gRegistry.set(browsingContext, mappings);
-    }
-
-    let uuid = mappings.elementToUUID.get(element);
-    if (uuid) {
-      // We already had this element registered, so return the pre-existing UUID.
-      return { browsingContextId: browsingContext.id, uuid };
-    }
-
-    // We must be registering a new element at this point.
-    uuid = gUUIDGenerator.generateUUID().toString();
-    mappings.elementToUUID.set(element, uuid);
-    mappings.UUIDToElement.set(uuid, Cu.getWeakReference(element));
-
-    return { browsingContextId: browsingContext.id, uuid };
-  },
-
-  /**
-   * Resolves an identifier back into the DOM Element that it was generated from.
-   *
-   * @param {Object} The identifier generated via ContentDOMReference.get for a
-   * DOM element.
-   * @return {Element} The DOM element that the identifier was generated for, or
-   * null if the element does not still exist.
-   */
-  resolve(identifier) {
-    let browsingContext = BrowsingContext.get(identifier.browsingContextId);
-    let uuid = identifier.uuid;
-    return this._resolveUUIDToElement(browsingContext, uuid);
-  },
-
-  /**
-   * Removes an identifier from the registry so that subsequent attempts
-   * to resolve it will result in null. This is generally a good idea to avoid
-   * identifiers lying around taking up space (identifiers don't keep the
-   * DOM element alive, but the weak pointers themselves consume memory, and
-   * that's what we reclaim when revoking).
-   *
-   * @param {Object} The identifier to revoke, issued by ContentDOMReference.get for
-   * a DOM element.
-   */
-  revoke(identifier) {
-    let browsingContext = BrowsingContext.get(identifier.browsingContextId);
-    let uuid = identifier.uuid;
-
-    let mappings = gRegistry.get(browsingContext);
-    if (!mappings) {
-      return;
-    }
-
-    let element = this._resolveUUIDToElement(browsingContext, uuid);
-    if (element) {
-      mappings.elementToUUID.delete(element);
-    }
-
-    mappings.UUIDToElement.delete(uuid);
-  },
-
-  /**
-   * Private helper function that resolves a BrowsingContext and UUID (the
-   * pair that makes up an identifier) to a DOM element.
-   *
-   * @param {BrowsingContext} browsingContext The BrowsingContext that was hosting
-   * the DOM element at the time that the identifier was generated.
-   * @param {String} uuid The UUID generated for the DOM element.
-   *
-   * @return {Element} The DOM element that the identifier was generated for, or
-   * null if the element does not still exist.
-   */
-  _resolveUUIDToElement(browsingContext, uuid) {
-    let mappings = gRegistry.get(browsingContext);
-    if (!mappings) {
-      return null;
-    }
-
-    let weakReference = mappings.UUIDToElement.get(uuid);
-    if (!weakReference) {
-      return null;
-    }
-
-    return weakReference.get();
-  },
-};
--- a/toolkit/modules/InlineSpellChecker.jsm
+++ b/toolkit/modules/InlineSpellChecker.jsm
@@ -21,24 +21,24 @@ InlineSpellChecker.prototype = {
     try {
       this.mInlineSpellChecker = this.mEditor.getInlineSpellChecker(true);
       // note: this might have been NULL if there is no chance we can spellcheck
     } catch (e) {
       this.mInlineSpellChecker = null;
     }
   },
 
-  initFromRemote(aSpellInfo, aWindowGlobalParent) {
+  initFromRemote(aSpellInfo) {
     if (this.mRemote)
       throw new Error("Unexpected state");
     this.uninit();
 
     if (!aSpellInfo)
       return;
-    this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(aSpellInfo, aWindowGlobalParent);
+    this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(aSpellInfo);
     this.mOverMisspelling = aSpellInfo.overMisspelling;
     this.mMisspelling = aSpellInfo.misspelling;
   },
 
   // call this to clear state
   uninit() {
     if (this.mRemote) {
       this.mRemote.uninit();
@@ -423,20 +423,19 @@ var SpellCheckHelper = {
           flags |= this.CONTENTEDITABLE | this.SPELLCHECKABLE;
       }
     }
 
     return flags;
   },
 };
 
-function RemoteSpellChecker(aSpellInfo, aWindowGlobalParent) {
+function RemoteSpellChecker(aSpellInfo) {
   this._spellInfo = aSpellInfo;
   this._suggestionGenerator = null;
-  this._actor = aWindowGlobalParent.getActor("InlineSpellChecker");
 }
 
 RemoteSpellChecker.prototype = {
   get canSpellCheck() { return this._spellInfo.canSpellCheck; },
   get spellCheckPending() { return this._spellInfo.initialSpellCheckPending; },
   get overMisspelling() { return this._spellInfo.overMisspelling; },
   get enableRealTimeSpell() { return this._spellInfo.enableRealTimeSpell; },
 
@@ -455,48 +454,49 @@ RemoteSpellChecker.prototype = {
     }
     return next.value;
   },
 
   get currentDictionary() { return this._spellInfo.currentDictionary; },
   get dictionaryList() { return this._spellInfo.dictionaryList.slice(); },
 
   selectDictionary(localeCode) {
-    this._actor.selectDictionary({ localeCode });
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:selectDictionary",
+                                            { localeCode });
   },
 
   replaceMisspelling(index) {
-    this._actor.replaceMisspelling({ index });
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:replaceMisspelling",
+                                            { index });
   },
 
-  toggleEnabled() {
-    this._actor.toggleEnabled();
-  },
+  toggleEnabled() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {}); },
   addToDictionary() {
     // This is really ugly. There is an nsISpellChecker somewhere in the
     // parent that corresponds to our current element's spell checker in the
     // child, but it's hard to access it. However, we know that
     // addToDictionary adds the word to the singleton personal dictionary, so
     // we just do that here.
     // NB: We also rely on the fact that we only ever pass an empty string in
     // as the "lang".
 
     let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
                        .getService(Ci.mozIPersonalDictionary);
     dictionary.addWord(this._spellInfo.misspelling);
-    this._actor.recheckSpelling();
+
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
   },
   undoAddToDictionary(word) {
     let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
                        .getService(Ci.mozIPersonalDictionary);
     dictionary.removeWord(word);
-    this._actor.recheckSpelling();
+
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
   },
   ignoreWord() {
     let dictionary = Cc["@mozilla.org/spellchecker/personaldictionary;1"]
                        .getService(Ci.mozIPersonalDictionary);
     dictionary.ignoreWord(this._spellInfo.misspelling);
-    this._actor.recheckSpelling();
+
+    this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:recheck", {});
   },
-  uninit() {
-    this._actor.uninit();
-  },
+  uninit() { this._spellInfo.target.sendAsyncMessage("InlineSpellChecker:uninit", {}); },
 };
--- a/toolkit/modules/InlineSpellCheckerContent.jsm
+++ b/toolkit/modules/InlineSpellCheckerContent.jsm
@@ -7,36 +7,38 @@
 
 var { InlineSpellChecker, SpellCheckHelper } =
   ChromeUtils.import("resource://gre/modules/InlineSpellChecker.jsm");
 
 var EXPORTED_SYMBOLS = [ "InlineSpellCheckerContent" ];
 
 var InlineSpellCheckerContent = {
   _spellChecker: null,
-  _actor: null,
+  _manager: null,
 
-  initContextMenu(event, editFlags, actor) {
-    this._actor = actor;
+  initContextMenu(event, editFlags, messageManager) {
+    this._manager = messageManager;
 
     let spellChecker;
     if (!(editFlags & (SpellCheckHelper.TEXTAREA | SpellCheckHelper.INPUT))) {
       // Get the editor off the window.
       let win = event.target.ownerGlobal;
       let editingSession = win.docShell.editingSession;
       spellChecker = this._spellChecker =
         new InlineSpellChecker(editingSession.getEditorForWindow(win));
     } else {
       // Use the element's editor.
       spellChecker = this._spellChecker =
         new InlineSpellChecker(event.composedTarget.editor);
     }
 
     this._spellChecker.initFromEvent(event.rangeParent, event.rangeOffset);
 
+    this._addMessageListeners();
+
     if (!spellChecker.canSpellCheck) {
       return { canSpellCheck: false,
                initialSpellCheckPending: true,
                enableRealTimeSpell: false };
     }
 
     if (!spellChecker.mInlineSpellChecker.enableRealTimeSpell) {
       return { canSpellCheck: true,
@@ -59,17 +61,20 @@ var InlineSpellCheckerContent = {
              overMisspelling: spellChecker.overMisspelling,
              misspelling: spellChecker.mMisspelling,
              spellSuggestions: this._generateSpellSuggestions(),
              currentDictionary: spellChecker.mInlineSpellChecker.spellChecker.GetCurrentDictionary(),
              dictionaryList };
   },
 
   uninitContextMenu() {
-    this._actor = null;
+    for (let i of this._messages)
+      this._manager.removeMessageListener(i, this);
+
+    this._manager = null;
     this._spellChecker = null;
   },
 
   _generateSpellSuggestions() {
     let spellChecker = this._spellChecker.mInlineSpellChecker.spellChecker;
     try {
       spellChecker.CheckCurrentWord(this._spellChecker.mMisspelling);
     } catch (e) {
@@ -84,24 +89,47 @@ var InlineSpellCheckerContent = {
         break;
       }
     }
 
     this._spellChecker.mSpellSuggestions = suggestions;
     return suggestions;
   },
 
-  selectDictionary(localeCode) {
-    this._spellChecker.selectDictionary(localeCode);
-  },
+  _messages: [
+      "InlineSpellChecker:selectDictionary",
+      "InlineSpellChecker:replaceMisspelling",
+      "InlineSpellChecker:toggleEnabled",
+
+      "InlineSpellChecker:recheck",
 
-  replaceMisspelling(index) {
-    this._spellChecker.replaceMisspelling(index);
+      "InlineSpellChecker:uninit",
+    ],
+
+  _addMessageListeners() {
+    for (let i of this._messages)
+      this._manager.addMessageListener(i, this);
   },
 
-  toggleEnabled() {
-    this._spellChecker.toggleEnabled();
-  },
+  receiveMessage(msg) {
+    switch (msg.name) {
+      case "InlineSpellChecker:selectDictionary":
+        this._spellChecker.selectDictionary(msg.data.localeCode);
+        break;
+
+      case "InlineSpellChecker:replaceMisspelling":
+        this._spellChecker.replaceMisspelling(msg.data.index);
+        break;
 
-  recheck() {
-    this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
+      case "InlineSpellChecker:toggleEnabled":
+        this._spellChecker.toggleEnabled();
+        break;
+
+      case "InlineSpellChecker:recheck":
+        this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
+        break;
+
+      case "InlineSpellChecker:uninit":
+        this.uninitContextMenu();
+        break;
+    }
   },
 };
--- a/toolkit/modules/PageMenu.jsm
+++ b/toolkit/modules/PageMenu.jsm
@@ -156,18 +156,20 @@ PageMenu.prototype = {
       // If a builder is assigned, call click on it directly. Otherwise, this is
       // likely a menu with data from another process, so send a message to the
       // browser to execute the menuitem.
       if (this._builder) {
         this._builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
       } else if (this._browser) {
         let win = target.ownerGlobal;
         let windowUtils = win.windowUtils;
-        win.gContextMenu.doCustomCommand(target.getAttribute(this.GENERATEDITEMID_ATTR),
-                                         windowUtils.isHandlingUserInput);
+        this._browser.messageManager.sendAsyncMessage("ContextMenu:DoCustomCommand", {
+          generatedItemId: target.getAttribute(this.GENERATEDITEMID_ATTR),
+          handlingUserInput: windowUtils.isHandlingUserInput,
+        });
       }
     } else if (type == "popuphidden" && this._popup == target) {
       this.removeGeneratedContent(this._popup);
 
       this._popup.removeEventListener("popuphidden", this);
       this._popup.removeEventListener("command", this);
 
       this._popup = null;
@@ -237,18 +239,36 @@ PageMenu.prototype = {
 };
 
 // This object is expected to be used from a parent process.
 function PageMenuParent() {
 }
 
 PageMenuParent.prototype = {
   __proto__: PageMenu.prototype,
+
   /*
-   * Given a JSON menu object and popup, add the context menu to the popup.
+   * Given a target node and popup, add the context menu to the popup. This is
+   * intended to be called when a single process is used. This is equivalent to
+   * calling PageMenuChild.build and PageMenuParent.addToPopup in sequence.
+   *
+   * Returns true if custom menu items were present.
+   */
+  buildAndAddToPopup(aTarget, aPopup) {
+    let menuObject = this.maybeBuild(aTarget);
+    if (!menuObject) {
+      return false;
+    }
+
+    return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
+  },
+
+  /*
+   * Given a JSON menu object and popup, add the context menu to the popup. This
+   * is intended to be called when the child page is in a different process.
    * aBrowser should be the browser containing the page the context menu is
    * displayed for, which may be null.
    *
    * Returns true if custom menu items were present.
    */
   addToPopup(aMenu, aBrowser, aPopup) {
     return this.buildAndAttachMenuWithObject(aMenu, aBrowser, aPopup);
   },
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -170,17 +170,16 @@ EXTRA_JS_MODULES += [
     'AutoScrollController.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CanonicalJSON.jsm',
     'CertUtils.jsm',
     'CharsetMenu.jsm',
     'Color.jsm',
     'Console.jsm',
-    'ContentDOMReference.jsm',
     'CreditCard.jsm',
     'css-selector.js',
     'DateTimePickerPanel.jsm',
     'DateTimePickerParent.jsm',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'E10SUtils.jsm',
     'EventEmitter.jsm',
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -3326,16 +3326,23 @@ var gDetailView = {
         const parentContextMenuPopup = parentChromeWindow.document.getElementById("contentAreaContextMenu");
 
         // Override openPopupAtScreen on the dummy menupopup element, so that we can forward
         // "nsContextMenu.js openContextMenu"'s calls related to the extensions "options page"
         // context menu events.
         document.getElementById("contentAreaContextMenu").openPopupAtScreen = (...args) => {
           return parentContextMenuPopup.openPopupAtScreen(...args);
         };
+
+        // Subscribe a "contextmenu" listener to handle the context menus for the extension option page
+        // running in the extension process (the context menu will be handled only for extension running
+        // in OOP mode, but that's ok as it is the default on any platform that uses these extensions
+        // options pages).
+        browser.messageManager.addMessageListener(
+          "contextmenu", message => parentChromeWindow.openContextMenu(message));
       });
     } else {
       readyPromise = promiseEvent("load", browser, true);
     }
 
     stack.appendChild(browser);
     parentNode.appendChild(stack);