Bug 1369466 - Split the RemotePageManager.jsm code into parent/child sides, and put it in a new location. r=Mossop draft
authorFelipe Gomes <felipc@gmail.com>
Thu, 26 Jul 2018 22:08:40 -0300
changeset 823334 0bb889e552926289f7c77565cf08c556cb325e29
parent 823333 8f2f847b2f9dc5643eeb9935d603a3b30686f972
child 823335 23e8de70022a12abc3585f5a080dd3267479544c
push id117637
push userfelipc@gmail.com
push dateFri, 27 Jul 2018 01:14:51 +0000
reviewersMossop
bugs1369466
milestone63.0a1
Bug 1369466 - Split the RemotePageManager.jsm code into parent/child sides, and put it in a new location. r=Mossop The RemotePageManager.jsm contains code both for the parent and child processes, which makes some code to be unecessarily loaded twice. This patch splits it up in various parts: RemotePageManagerParent.jsm - code meant for the parent process RemotePageManagerChild.jsm - code meant for child processes MessagePort.jsm - code used by both the parent and child code paths (the base MessagePort and MessageListener) MozReview-Commit-ID: 1fUoQ5Y29xM
toolkit/components/moz.build
toolkit/components/remotepagemanager/MessagePort.jsm
toolkit/components/remotepagemanager/RemotePageManagerChild.jsm
toolkit/components/remotepagemanager/RemotePageManagerParent.jsm
toolkit/components/remotepagemanager/moz.build
tools/lint/eslint/modules.json
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -51,16 +51,17 @@ DIRS += [
     'places',
     'privatebrowsing',
     'processsingleton',
     'promiseworker',
     'prompts',
     'protobuf',
     'reader',
     'remotebrowserutils',
+    'remotepagemanager',
     'reflect',
     'reputationservice',
     'resistfingerprinting',
     'securityreporter',
     'sessionstore',
     'startup',
     'statusfilter',
     'telemetry',
copy from toolkit/modules/RemotePageManager.jsm
copy to toolkit/components/remotepagemanager/MessagePort.jsm
--- a/toolkit/modules/RemotePageManager.jsm
+++ b/toolkit/components/remotepagemanager/MessagePort.jsm
@@ -1,40 +1,28 @@
 /* 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 = ["RemotePages", "RemotePageManager", "PageListener"];
-
-/*
- * Using the RemotePageManager:
- * * Create a new page listener by calling 'new RemotePages(URI)' which
- *   then injects functions like RPMGetBoolPref() into the registered page.
- *   One can then use those exported functions to communicate between
- *   child and parent.
- *
- * * When adding a new consumer of RPM that relies on other functionality
- *   then simple message passing provided by the RPM, then one has to
- *   whitelist permissions for the new URI within the RPMAccessManager.
- *
- * Please note that prefs that one wants to update need to be whitelisted
- * within AsyncPrefs.jsm.
- */
+var EXPORTED_SYMBOLS = ["MessagePort", "MessageListener"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.defineModuleGetter(this, "AsyncPrefs",
   "resource://gre/modules/AsyncPrefs.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 /*
  * Used for all kinds of permissions checks which requires explicit
  * whitelisting of specific permissions granted through RPM.
+ *
+ * Please note that prefs that one wants to update need to be
+ * whitelisted within AsyncPrefs.jsm.
  */
 let RPMAccessManager = {
   accessMap: {
     "about:privatebrowsing": {
       // "sendAsyncMessage": handled within AboutPrivateBrowsingHandler.jsm
       // "setBoolPref": handled within AsyncPrefs.jsm and uses the pref
       //                ["privacy.trackingprotection.pbmode.enabled"],
       "getBoolPref": ["privacy.trackingprotection.enabled",
@@ -118,181 +106,16 @@ MessageListener.prototype = {
   removeMessageListener(name, callback) {
     if (!this.listeners.has(name))
       return;
 
     this.listeners.get(name).delete(callback);
   },
 };
 
-
-/**
- * Creates a RemotePages object which listens for new remote pages of some
- * particular URLs. A "RemotePage:Init" message will be dispatched to this
- * object for every page loaded. Message listeners added to this object receive
- * messages from all loaded pages from the requested urls.
- */
-var RemotePages = function(urls) {
-  this.urls = Array.isArray(urls) ? urls : [urls];
-  this.messagePorts = new Set();
-  this.listener = new MessageListener();
-  this.destroyed = false;
-
-  this.portCreated = this.portCreated.bind(this);
-  this.portMessageReceived = this.portMessageReceived.bind(this);
-
-  for (const url of this.urls) {
-    RemotePageManager.addRemotePageListener(url, this.portCreated);
-  }
-};
-
-RemotePages.prototype = {
-  urls: null,
-  messagePorts: null,
-  listener: null,
-  destroyed: null,
-
-  destroy() {
-    for (const url of this.urls) {
-      RemotePageManager.removeRemotePageListener(url);
-    }
-
-    for (let port of this.messagePorts.values()) {
-      this.removeMessagePort(port);
-    }
-
-    this.messagePorts = null;
-    this.listener = null;
-    this.destroyed = true;
-  },
-
-  // Called when a page matching one of the urls has loaded in a frame.
-  portCreated(port) {
-    this.messagePorts.add(port);
-
-    port.loaded = false;
-    port.addMessageListener("RemotePage:Load", this.portMessageReceived);
-    port.addMessageListener("RemotePage:Unload", this.portMessageReceived);
-
-    for (let name of this.listener.keys()) {
-      this.registerPortListener(port, name);
-    }
-
-    this.listener.callListeners({ target: port, name: "RemotePage:Init" });
-  },
-
-  // A message has been received from one of the pages
-  portMessageReceived(message) {
-    switch (message.name) {
-      case "RemotePage:Load":
-        message.target.loaded = true;
-        break;
-      case "RemotePage:Unload":
-        message.target.loaded = false;
-        this.removeMessagePort(message.target);
-        break;
-    }
-
-    this.listener.callListeners(message);
-  },
-
-  // A page has closed
-  removeMessagePort(port) {
-    for (let name of this.listener.keys()) {
-      port.removeMessageListener(name, this.portMessageReceived);
-    }
-
-    port.removeMessageListener("RemotePage:Load", this.portMessageReceived);
-    port.removeMessageListener("RemotePage:Unload", this.portMessageReceived);
-    this.messagePorts.delete(port);
-  },
-
-  registerPortListener(port, name) {
-    port.addMessageListener(name, this.portMessageReceived);
-  },
-
-  // Sends a message to all known pages
-  sendAsyncMessage(name, data = null) {
-    for (let port of this.messagePorts.values()) {
-      try {
-        port.sendAsyncMessage(name, data);
-      } catch (e) {
-        // Unless the port is in the process of unloading, something strange
-        // happened but allow other ports to receive the message
-        if (e.result !== Cr.NS_ERROR_NOT_INITIALIZED)
-          Cu.reportError(e);
-      }
-    }
-  },
-
-  addMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("RemotePages has been destroyed");
-    }
-
-    if (!this.listener.has(name)) {
-      for (let port of this.messagePorts.values()) {
-        this.registerPortListener(port, name);
-      }
-    }
-
-    this.listener.addMessageListener(name, callback);
-  },
-
-  removeMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("RemotePages has been destroyed");
-    }
-
-    this.listener.removeMessageListener(name, callback);
-  },
-
-  portsForBrowser(browser) {
-    return [...this.messagePorts].filter(port => port.browser == browser);
-  },
-};
-
-
-// Only exposes the public properties of the MessagePort
-function publicMessagePort(port) {
-  let properties = ["addMessageListener", "removeMessageListener",
-                    "sendAsyncMessage", "destroy"];
-
-  let clean = {};
-  for (let property of properties) {
-    clean[property] = port[property].bind(port);
-  }
-
-  Object.defineProperty(clean, "portID", {
-    enumerable: true,
-    get() {
-      return port.portID;
-    }
-  });
-
-  if (port instanceof ChromeMessagePort) {
-    Object.defineProperty(clean, "browser", {
-      enumerable: true,
-      get() {
-        return port.browser;
-      }
-    });
-
-    Object.defineProperty(clean, "url", {
-      enumerable: true,
-      get() {
-        return port.url;
-      }
-    });
-  }
-
-  return clean;
-}
-
-
 /*
  * A message port sits on each side of the process boundary for every remote
  * page. Each has a port ID that is unique to the message manager it talks
  * through.
  *
  * We roughly implement the same contract as nsIMessageSender and
  * nsIMessageListenerManager
  */
@@ -403,285 +226,8 @@ MessagePort.prototype = {
   isWindowPrivate() {
     let principal = this.window.document.nodePrincipal;
     if (!RPMAccessManager.checkAllowAccess(principal, "isWindowPrivate", "yes")) {
       throw new Error("RPMAccessManager does not allow access to isWindowPrivate");
     }
     return PrivateBrowsingUtils.isContentWindowPrivate(this.window);
   },
 };
-
-
-// The chome side of a message port
-function ChromeMessagePort(browser, portID, url) {
-  MessagePort.call(this, browser.messageManager, portID);
-
-  this._browser = browser;
-  this._permanentKey = browser.permanentKey;
-  this._url = url;
-
-  Services.obs.addObserver(this, "message-manager-disconnect");
-  this.publicPort = publicMessagePort(this);
-
-  this.swapBrowsers = this.swapBrowsers.bind(this);
-  this._browser.addEventListener("SwapDocShells", this.swapBrowsers);
-}
-
-ChromeMessagePort.prototype = Object.create(MessagePort.prototype);
-
-Object.defineProperty(ChromeMessagePort.prototype, "browser", {
-  get() {
-    return this._browser;
-  }
-});
-
-Object.defineProperty(ChromeMessagePort.prototype, "url", {
-  get() {
-    return this._url;
-  }
-});
-
-// Called when the docshell is being swapped with another browser. We have to
-// update to use the new browser's message manager
-ChromeMessagePort.prototype.swapBrowsers = function({ detail: newBrowser }) {
-  // We can see this event for the new browser before the swap completes so
-  // check that the browser we're tracking has our permanentKey.
-  if (this._browser.permanentKey != this._permanentKey)
-    return;
-
-  this._browser.removeEventListener("SwapDocShells", this.swapBrowsers);
-
-  this._browser = newBrowser;
-  this.swapMessageManager(newBrowser.messageManager);
-
-  this._browser.addEventListener("SwapDocShells", this.swapBrowsers);
-};
-
-// Called when a message manager has been disconnected indicating that the
-// tab has closed or crashed
-ChromeMessagePort.prototype.observe = function(messageManager) {
-  if (messageManager != this.messageManager)
-    return;
-
-  this.listener.callListeners({
-    target: this.publicPort,
-    name: "RemotePage:Unload",
-    data: null,
-  });
-  this.destroy();
-};
-
-// Called when a message is received from the message manager. This could
-// have come from any port in the message manager so verify the port ID.
-ChromeMessagePort.prototype.message = function({ data: messagedata }) {
-  if (this.destroyed || (messagedata.portID != this.portID)) {
-    return;
-  }
-
-  let message = {
-    target: this.publicPort,
-    name: messagedata.name,
-    data: messagedata.data,
-  };
-  this.listener.callListeners(message);
-
-  if (messagedata.name == "RemotePage:Unload")
-    this.destroy();
-};
-
-ChromeMessagePort.prototype.destroy = function() {
-  try {
-    this._browser.removeEventListener(
-        "SwapDocShells", this.swapBrowsers);
-  } catch (e) {
-    // It's possible the browser instance is already dead so we can just ignore
-    // this error.
-  }
-
-  this._browser = null;
-  Services.obs.removeObserver(this, "message-manager-disconnect");
-  MessagePort.prototype.destroy.call(this);
-};
-
-
-// The content side of a message port
-function ChildMessagePort(contentFrame, window) {
-  let portID = Services.appinfo.processID + ":" + ChildMessagePort.prototype.nextPortID++;
-  MessagePort.call(this, contentFrame, portID);
-
-  this.window = window;
-
-  // Add functionality to the content page
-  Cu.exportFunction(this.sendAsyncMessage.bind(this), window, {
-    defineAs: "RPMSendAsyncMessage",
-  });
-  Cu.exportFunction(this.addMessageListener.bind(this), window, {
-    defineAs: "RPMAddMessageListener",
-    allowCallbacks: true,
-  });
-  Cu.exportFunction(this.removeMessageListener.bind(this), window, {
-    defineAs: "RPMRemoveMessageListener",
-    allowCallbacks: true,
-  });
-  Cu.exportFunction(this.getBoolPref.bind(this), window, {
-    defineAs: "RPMGetBoolPref",
-  });
-  Cu.exportFunction(this.setBoolPref.bind(this), window, {
-    defineAs: "RPMSetBoolPref",
-  });
-  Cu.exportFunction(this.getFormatURLPref.bind(this), window, {
-    defineAs: "RPMGetFormatURLPref",
-  });
-  Cu.exportFunction(this.isWindowPrivate.bind(this), window, {
-    defineAs: "RPMIsWindowPrivate",
-  });
-
-  // Send a message for load events
-  let loadListener = () => {
-    this.sendAsyncMessage("RemotePage:Load");
-    window.removeEventListener("load", loadListener);
-  };
-  window.addEventListener("load", loadListener);
-
-  // Destroy the port when the window is unloaded
-  window.addEventListener("unload", () => {
-    try {
-      this.sendAsyncMessage("RemotePage:Unload");
-    } catch (e) {
-      // If the tab has been closed the frame message manager has already been
-      // destroyed
-    }
-    this.destroy();
-  });
-
-  // Tell the main process to set up its side of the message pipe.
-  this.messageManager.sendAsyncMessage("RemotePage:InitPort", {
-    portID,
-    url: window.document.documentURI.replace(/[\#|\?].*$/, ""),
-  });
-}
-
-ChildMessagePort.prototype = Object.create(MessagePort.prototype);
-
-ChildMessagePort.prototype.nextPortID = 0;
-
-// Called when a message is received from the message manager. This could
-// have come from any port in the message manager so verify the port ID.
-ChildMessagePort.prototype.message = function({ data: messagedata }) {
-  if (this.destroyed || (messagedata.portID != this.portID)) {
-    return;
-  }
-
-  let message = {
-    name: messagedata.name,
-    data: messagedata.data,
-  };
-  this.listener.callListeners(Cu.cloneInto(message, this.window));
-};
-
-ChildMessagePort.prototype.destroy = function() {
-  this.window = null;
-  MessagePort.prototype.destroy.call(this);
-};
-
-// Allows callers to register to connect to specific content pages. Registration
-// is done through the addRemotePageListener method
-var RemotePageManagerInternal = {
-  // The currently registered remote pages
-  pages: new Map(),
-
-  // Initialises all the needed listeners
-  init() {
-    Services.mm.addMessageListener("RemotePage:InitPort", this.initPort.bind(this));
-    this.updateProcessUrls();
-  },
-
-  updateProcessUrls() {
-    Services.ppmm.initialProcessData["RemotePageManager:urls"] = Array.from(this.pages.keys());
-  },
-
-  // Registers interest in a remote page. A callback is called with a port for
-  // the new page when loading begins (i.e. the page hasn't actually loaded yet).
-  // Only one callback can be registered per URL.
-  addRemotePageListener(url, callback) {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-      throw new Error("RemotePageManager can only be used in the main process.");
-
-    if (this.pages.has(url)) {
-      throw new Error("Remote page already registered: " + url);
-    }
-
-    this.pages.set(url, callback);
-    this.updateProcessUrls();
-
-    // Notify all the frame scripts of the new registration
-    Services.ppmm.broadcastAsyncMessage("RemotePage:Register", { urls: [url] });
-  },
-
-  // Removes any interest in a remote page.
-  removeRemotePageListener(url) {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-      throw new Error("RemotePageManager can only be used in the main process.");
-
-    if (!this.pages.has(url)) {
-      throw new Error("Remote page is not registered: " + url);
-    }
-
-    // Notify all the frame scripts of the removed registration
-    Services.ppmm.broadcastAsyncMessage("RemotePage:Unregister", { urls: [url] });
-    this.pages.delete(url);
-    this.updateProcessUrls();
-  },
-
-  // A remote page has been created and a port is ready in the content side
-  initPort({ target: browser, data: { url, portID } }) {
-    let callback = this.pages.get(url);
-    if (!callback) {
-      Cu.reportError("Unexpected remote page load: " + url);
-      return;
-    }
-
-    let port = new ChromeMessagePort(browser, portID, url);
-    callback(port.publicPort);
-  }
-};
-
-if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-  RemotePageManagerInternal.init();
-
-// The public API for the above object
-var RemotePageManager = {
-  addRemotePageListener: RemotePageManagerInternal.addRemotePageListener.bind(RemotePageManagerInternal),
-  removeRemotePageListener: RemotePageManagerInternal.removeRemotePageListener.bind(RemotePageManagerInternal),
-};
-
-// Listen for pages in any process we're loaded in
-var registeredURLs = new Set(Services.cpmm.initialProcessData["RemotePageManager:urls"]);
-
-var observer = (window) => {
-  // Strip the hash from the URL, because it's not part of the origin.
-  let url = window.document.documentURI.replace(/[\#|\?].*$/, "");
-  if (!registeredURLs.has(url))
-    return;
-
-  // Get the frame message manager for this window so we can associate this
-  // page with a browser element
-  let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIDocShell)
-                             .QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIContentFrameMessageManager);
-  // Set up the child side of the message port
-  new ChildMessagePort(messageManager, window);
-};
-Services.obs.addObserver(observer, "chrome-document-global-created");
-Services.obs.addObserver(observer, "content-document-global-created");
-
-// A message from chrome telling us what pages to listen for
-Services.cpmm.addMessageListener("RemotePage:Register", ({ data }) => {
-  for (let url of data.urls)
-    registeredURLs.add(url);
-});
-
-// A message from chrome telling us what pages to stop listening for
-Services.cpmm.addMessageListener("RemotePage:Unregister", ({ data }) => {
-  for (let url of data.urls)
-    registeredURLs.delete(url);
-});
copy from toolkit/modules/RemotePageManager.jsm
copy to toolkit/components/remotepagemanager/RemotePageManagerChild.jsm
--- a/toolkit/modules/RemotePageManager.jsm
+++ b/toolkit/components/remotepagemanager/RemotePageManagerChild.jsm
@@ -1,511 +1,18 @@
 /* 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 = ["RemotePages", "RemotePageManager", "PageListener"];
-
-/*
- * Using the RemotePageManager:
- * * Create a new page listener by calling 'new RemotePages(URI)' which
- *   then injects functions like RPMGetBoolPref() into the registered page.
- *   One can then use those exported functions to communicate between
- *   child and parent.
- *
- * * When adding a new consumer of RPM that relies on other functionality
- *   then simple message passing provided by the RPM, then one has to
- *   whitelist permissions for the new URI within the RPMAccessManager.
- *
- * Please note that prefs that one wants to update need to be whitelisted
- * within AsyncPrefs.jsm.
- */
+var EXPORTED_SYMBOLS = ["ChildMessagePort"];
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "AsyncPrefs",
-  "resource://gre/modules/AsyncPrefs.jsm");
-ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
-  "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-/*
- * Used for all kinds of permissions checks which requires explicit
- * whitelisting of specific permissions granted through RPM.
- */
-let RPMAccessManager = {
-  accessMap: {
-    "about:privatebrowsing": {
-      // "sendAsyncMessage": handled within AboutPrivateBrowsingHandler.jsm
-      // "setBoolPref": handled within AsyncPrefs.jsm and uses the pref
-      //                ["privacy.trackingprotection.pbmode.enabled"],
-      "getBoolPref": ["privacy.trackingprotection.enabled",
-                      "privacy.trackingprotection.pbmode.enabled"],
-      "getFormatURLPref": ["privacy.trackingprotection.introURL",
-                           "app.support.baseURL"],
-      "isWindowPrivate": ["yes"],
-    },
-  },
-
-  checkAllowAccess(aPrincipal, aFeature, aValue) {
-    // if there is no content principal; deny access
-    if (!aPrincipal || !aPrincipal.URI) {
-      return false;
-    }
-    let uri = aPrincipal.URI.asciiSpec;
-
-    // check if there is an entry for that requestying URI in the accessMap;
-    // if not, deny access.
-    let accessMapForURI = this.accessMap[uri];
-    if (!accessMapForURI) {
-      Cu.reportError("RPMAccessManager does not allow access to Feature: " + aFeature + " for: " + uri);
-      return false;
-    }
-
-    // check if the feature is allowed to be accessed for that URI;
-    // if not, deny access.
-    let accessMapForFeature = accessMapForURI[aFeature];
-    if (!accessMapForFeature) {
-      Cu.reportError("RPMAccessManager does not allow access to Feature: " + aFeature + " for: " + uri);
-      return false;
-    }
-
-    // if the actual value is in the whitelist for that feature;
-    // allow access
-    if (accessMapForFeature.includes(aValue)) {
-      return true;
-    }
-
-    // otherwise deny access
-    Cu.reportError("RPMAccessManager does not allow access to Feature: " + aFeature + " for: " + uri);
-    return false;
-  },
-};
-
-function MessageListener() {
-  this.listeners = new Map();
-}
-
-MessageListener.prototype = {
-  keys() {
-    return this.listeners.keys();
-  },
-
-  has(name) {
-    return this.listeners.has(name);
-  },
-
-  callListeners(message) {
-    let listeners = this.listeners.get(message.name);
-    if (!listeners) {
-      return;
-    }
-
-    for (let listener of listeners.values()) {
-      try {
-        listener(message);
-      } catch (e) {
-        Cu.reportError(e);
-      }
-    }
-  },
-
-  addMessageListener(name, callback) {
-    if (!this.listeners.has(name))
-      this.listeners.set(name, new Set([callback]));
-    else
-      this.listeners.get(name).add(callback);
-  },
-
-  removeMessageListener(name, callback) {
-    if (!this.listeners.has(name))
-      return;
-
-    this.listeners.get(name).delete(callback);
-  },
-};
-
-
-/**
- * Creates a RemotePages object which listens for new remote pages of some
- * particular URLs. A "RemotePage:Init" message will be dispatched to this
- * object for every page loaded. Message listeners added to this object receive
- * messages from all loaded pages from the requested urls.
- */
-var RemotePages = function(urls) {
-  this.urls = Array.isArray(urls) ? urls : [urls];
-  this.messagePorts = new Set();
-  this.listener = new MessageListener();
-  this.destroyed = false;
-
-  this.portCreated = this.portCreated.bind(this);
-  this.portMessageReceived = this.portMessageReceived.bind(this);
-
-  for (const url of this.urls) {
-    RemotePageManager.addRemotePageListener(url, this.portCreated);
-  }
-};
-
-RemotePages.prototype = {
-  urls: null,
-  messagePorts: null,
-  listener: null,
-  destroyed: null,
-
-  destroy() {
-    for (const url of this.urls) {
-      RemotePageManager.removeRemotePageListener(url);
-    }
-
-    for (let port of this.messagePorts.values()) {
-      this.removeMessagePort(port);
-    }
-
-    this.messagePorts = null;
-    this.listener = null;
-    this.destroyed = true;
-  },
-
-  // Called when a page matching one of the urls has loaded in a frame.
-  portCreated(port) {
-    this.messagePorts.add(port);
-
-    port.loaded = false;
-    port.addMessageListener("RemotePage:Load", this.portMessageReceived);
-    port.addMessageListener("RemotePage:Unload", this.portMessageReceived);
-
-    for (let name of this.listener.keys()) {
-      this.registerPortListener(port, name);
-    }
-
-    this.listener.callListeners({ target: port, name: "RemotePage:Init" });
-  },
-
-  // A message has been received from one of the pages
-  portMessageReceived(message) {
-    switch (message.name) {
-      case "RemotePage:Load":
-        message.target.loaded = true;
-        break;
-      case "RemotePage:Unload":
-        message.target.loaded = false;
-        this.removeMessagePort(message.target);
-        break;
-    }
-
-    this.listener.callListeners(message);
-  },
-
-  // A page has closed
-  removeMessagePort(port) {
-    for (let name of this.listener.keys()) {
-      port.removeMessageListener(name, this.portMessageReceived);
-    }
-
-    port.removeMessageListener("RemotePage:Load", this.portMessageReceived);
-    port.removeMessageListener("RemotePage:Unload", this.portMessageReceived);
-    this.messagePorts.delete(port);
-  },
-
-  registerPortListener(port, name) {
-    port.addMessageListener(name, this.portMessageReceived);
-  },
-
-  // Sends a message to all known pages
-  sendAsyncMessage(name, data = null) {
-    for (let port of this.messagePorts.values()) {
-      try {
-        port.sendAsyncMessage(name, data);
-      } catch (e) {
-        // Unless the port is in the process of unloading, something strange
-        // happened but allow other ports to receive the message
-        if (e.result !== Cr.NS_ERROR_NOT_INITIALIZED)
-          Cu.reportError(e);
-      }
-    }
-  },
-
-  addMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("RemotePages has been destroyed");
-    }
-
-    if (!this.listener.has(name)) {
-      for (let port of this.messagePorts.values()) {
-        this.registerPortListener(port, name);
-      }
-    }
-
-    this.listener.addMessageListener(name, callback);
-  },
-
-  removeMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("RemotePages has been destroyed");
-    }
-
-    this.listener.removeMessageListener(name, callback);
-  },
-
-  portsForBrowser(browser) {
-    return [...this.messagePorts].filter(port => port.browser == browser);
-  },
-};
-
-
-// Only exposes the public properties of the MessagePort
-function publicMessagePort(port) {
-  let properties = ["addMessageListener", "removeMessageListener",
-                    "sendAsyncMessage", "destroy"];
-
-  let clean = {};
-  for (let property of properties) {
-    clean[property] = port[property].bind(port);
-  }
-
-  Object.defineProperty(clean, "portID", {
-    enumerable: true,
-    get() {
-      return port.portID;
-    }
-  });
-
-  if (port instanceof ChromeMessagePort) {
-    Object.defineProperty(clean, "browser", {
-      enumerable: true,
-      get() {
-        return port.browser;
-      }
-    });
-
-    Object.defineProperty(clean, "url", {
-      enumerable: true,
-      get() {
-        return port.url;
-      }
-    });
-  }
-
-  return clean;
-}
-
-
-/*
- * A message port sits on each side of the process boundary for every remote
- * page. Each has a port ID that is unique to the message manager it talks
- * through.
- *
- * We roughly implement the same contract as nsIMessageSender and
- * nsIMessageListenerManager
- */
-function MessagePort(messageManager, portID) {
-  this.messageManager = messageManager;
-  this.portID = portID;
-  this.destroyed = false;
-  this.listener = new MessageListener();
-
-  this.message = this.message.bind(this);
-  this.messageManager.addMessageListener("RemotePage:Message", this.message);
-}
-
-MessagePort.prototype = {
-  messageManager: null,
-  portID: null,
-  destroyed: null,
-  listener: null,
-  _browser: null,
-  remotePort: null,
-
-  // Called when the message manager used to connect to the other process has
-  // changed, i.e. when a tab is detached.
-  swapMessageManager(messageManager) {
-    this.messageManager.removeMessageListener("RemotePage:Message", this.message);
-
-    this.messageManager = messageManager;
-
-    this.messageManager.addMessageListener("RemotePage:Message", this.message);
-  },
-
-  /* Adds a listener for messages. Many callbacks can be registered for the
-   * same message if necessary. An attempt to register the same callback for the
-   * same message twice will be ignored. When called the callback is passed an
-   * object with these properties:
-   *   target: This message port
-   *   name:   The message name
-   *   data:   Any data sent with the message
-   */
-  addMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("Message port has been destroyed");
-    }
-
-    this.listener.addMessageListener(name, callback);
-  },
-
-  /*
-   * Removes a listener for messages.
-   */
-  removeMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("Message port has been destroyed");
-    }
-
-    this.listener.removeMessageListener(name, callback);
-  },
-
-  // Sends a message asynchronously to the other process
-  sendAsyncMessage(name, data = null) {
-    if (this.destroyed) {
-      throw new Error("Message port has been destroyed");
-    }
-
-    this.messageManager.sendAsyncMessage("RemotePage:Message", {
-      portID: this.portID,
-      name,
-      data,
-    });
-  },
-
-  // Called to destroy this port
-  destroy() {
-    try {
-      // This can fail in the child process if the tab has already been closed
-      this.messageManager.removeMessageListener("RemotePage:Message", this.message);
-    } catch (e) { }
-    this.messageManager = null;
-    this.destroyed = true;
-    this.portID = null;
-    this.listener = null;
-  },
-
-  getBoolPref(aPref) {
-    let principal = this.window.document.nodePrincipal;
-    if (!RPMAccessManager.checkAllowAccess(principal, "getBoolPref", aPref)) {
-      throw new Error("RPMAccessManager does not allow access to getBoolPref");
-    }
-    return Services.prefs.getBoolPref(aPref);
-  },
-
-  setBoolPref(aPref, aVal) {
-    return new this.window.Promise(function(resolve) {
-      AsyncPrefs.set(aPref, aVal).then(function() {
-        resolve();
-      });
-    });
-  },
-
-  getFormatURLPref(aFormatURL) {
-    let principal = this.window.document.nodePrincipal;
-    if (!RPMAccessManager.checkAllowAccess(principal, "getFormatURLPref", aFormatURL)) {
-      throw new Error("RPMAccessManager does not allow access to getFormatURLPref");
-    }
-    return Services.urlFormatter.formatURLPref(aFormatURL);
-  },
-
-  isWindowPrivate() {
-    let principal = this.window.document.nodePrincipal;
-    if (!RPMAccessManager.checkAllowAccess(principal, "isWindowPrivate", "yes")) {
-      throw new Error("RPMAccessManager does not allow access to isWindowPrivate");
-    }
-    return PrivateBrowsingUtils.isContentWindowPrivate(this.window);
-  },
-};
-
-
-// The chome side of a message port
-function ChromeMessagePort(browser, portID, url) {
-  MessagePort.call(this, browser.messageManager, portID);
-
-  this._browser = browser;
-  this._permanentKey = browser.permanentKey;
-  this._url = url;
-
-  Services.obs.addObserver(this, "message-manager-disconnect");
-  this.publicPort = publicMessagePort(this);
-
-  this.swapBrowsers = this.swapBrowsers.bind(this);
-  this._browser.addEventListener("SwapDocShells", this.swapBrowsers);
-}
-
-ChromeMessagePort.prototype = Object.create(MessagePort.prototype);
-
-Object.defineProperty(ChromeMessagePort.prototype, "browser", {
-  get() {
-    return this._browser;
-  }
-});
-
-Object.defineProperty(ChromeMessagePort.prototype, "url", {
-  get() {
-    return this._url;
-  }
-});
-
-// Called when the docshell is being swapped with another browser. We have to
-// update to use the new browser's message manager
-ChromeMessagePort.prototype.swapBrowsers = function({ detail: newBrowser }) {
-  // We can see this event for the new browser before the swap completes so
-  // check that the browser we're tracking has our permanentKey.
-  if (this._browser.permanentKey != this._permanentKey)
-    return;
-
-  this._browser.removeEventListener("SwapDocShells", this.swapBrowsers);
-
-  this._browser = newBrowser;
-  this.swapMessageManager(newBrowser.messageManager);
-
-  this._browser.addEventListener("SwapDocShells", this.swapBrowsers);
-};
-
-// Called when a message manager has been disconnected indicating that the
-// tab has closed or crashed
-ChromeMessagePort.prototype.observe = function(messageManager) {
-  if (messageManager != this.messageManager)
-    return;
-
-  this.listener.callListeners({
-    target: this.publicPort,
-    name: "RemotePage:Unload",
-    data: null,
-  });
-  this.destroy();
-};
-
-// Called when a message is received from the message manager. This could
-// have come from any port in the message manager so verify the port ID.
-ChromeMessagePort.prototype.message = function({ data: messagedata }) {
-  if (this.destroyed || (messagedata.portID != this.portID)) {
-    return;
-  }
-
-  let message = {
-    target: this.publicPort,
-    name: messagedata.name,
-    data: messagedata.data,
-  };
-  this.listener.callListeners(message);
-
-  if (messagedata.name == "RemotePage:Unload")
-    this.destroy();
-};
-
-ChromeMessagePort.prototype.destroy = function() {
-  try {
-    this._browser.removeEventListener(
-        "SwapDocShells", this.swapBrowsers);
-  } catch (e) {
-    // It's possible the browser instance is already dead so we can just ignore
-    // this error.
-  }
-
-  this._browser = null;
-  Services.obs.removeObserver(this, "message-manager-disconnect");
-  MessagePort.prototype.destroy.call(this);
-};
-
+ChromeUtils.import("resource://gre/modules/remotepagemanager/MessagePort.jsm");
 
 // The content side of a message port
 function ChildMessagePort(contentFrame, window) {
   let portID = Services.appinfo.processID + ":" + ChildMessagePort.prototype.nextPortID++;
   MessagePort.call(this, contentFrame, portID);
 
   this.window = window;
 
@@ -577,93 +84,24 @@ ChildMessagePort.prototype.message = fun
   this.listener.callListeners(Cu.cloneInto(message, this.window));
 };
 
 ChildMessagePort.prototype.destroy = function() {
   this.window = null;
   MessagePort.prototype.destroy.call(this);
 };
 
-// Allows callers to register to connect to specific content pages. Registration
-// is done through the addRemotePageListener method
-var RemotePageManagerInternal = {
-  // The currently registered remote pages
-  pages: new Map(),
-
-  // Initialises all the needed listeners
-  init() {
-    Services.mm.addMessageListener("RemotePage:InitPort", this.initPort.bind(this));
-    this.updateProcessUrls();
-  },
-
-  updateProcessUrls() {
-    Services.ppmm.initialProcessData["RemotePageManager:urls"] = Array.from(this.pages.keys());
-  },
-
-  // Registers interest in a remote page. A callback is called with a port for
-  // the new page when loading begins (i.e. the page hasn't actually loaded yet).
-  // Only one callback can be registered per URL.
-  addRemotePageListener(url, callback) {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-      throw new Error("RemotePageManager can only be used in the main process.");
-
-    if (this.pages.has(url)) {
-      throw new Error("Remote page already registered: " + url);
-    }
-
-    this.pages.set(url, callback);
-    this.updateProcessUrls();
-
-    // Notify all the frame scripts of the new registration
-    Services.ppmm.broadcastAsyncMessage("RemotePage:Register", { urls: [url] });
-  },
-
-  // Removes any interest in a remote page.
-  removeRemotePageListener(url) {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-      throw new Error("RemotePageManager can only be used in the main process.");
-
-    if (!this.pages.has(url)) {
-      throw new Error("Remote page is not registered: " + url);
-    }
-
-    // Notify all the frame scripts of the removed registration
-    Services.ppmm.broadcastAsyncMessage("RemotePage:Unregister", { urls: [url] });
-    this.pages.delete(url);
-    this.updateProcessUrls();
-  },
-
-  // A remote page has been created and a port is ready in the content side
-  initPort({ target: browser, data: { url, portID } }) {
-    let callback = this.pages.get(url);
-    if (!callback) {
-      Cu.reportError("Unexpected remote page load: " + url);
-      return;
-    }
-
-    let port = new ChromeMessagePort(browser, portID, url);
-    callback(port.publicPort);
-  }
-};
-
-if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-  RemotePageManagerInternal.init();
-
-// The public API for the above object
-var RemotePageManager = {
-  addRemotePageListener: RemotePageManagerInternal.addRemotePageListener.bind(RemotePageManagerInternal),
-  removeRemotePageListener: RemotePageManagerInternal.removeRemotePageListener.bind(RemotePageManagerInternal),
-};
 
 // Listen for pages in any process we're loaded in
 var registeredURLs = new Set(Services.cpmm.initialProcessData["RemotePageManager:urls"]);
 
 var observer = (window) => {
   // Strip the hash from the URL, because it's not part of the origin.
   let url = window.document.documentURI.replace(/[\#|\?].*$/, "");
+
   if (!registeredURLs.has(url))
     return;
 
   // Get the frame message manager for this window so we can associate this
   // page with a browser element
   let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell)
                              .QueryInterface(Ci.nsIInterfaceRequestor)
copy from toolkit/modules/RemotePageManager.jsm
copy to toolkit/components/remotepagemanager/RemotePageManagerParent.jsm
--- a/toolkit/modules/RemotePageManager.jsm
+++ b/toolkit/components/remotepagemanager/RemotePageManagerParent.jsm
@@ -1,133 +1,31 @@
 /* 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 = ["RemotePages", "RemotePageManager", "PageListener"];
+var EXPORTED_SYMBOLS = ["RemotePages", "RemotePageManager"];
 
 /*
  * Using the RemotePageManager:
  * * Create a new page listener by calling 'new RemotePages(URI)' which
  *   then injects functions like RPMGetBoolPref() into the registered page.
  *   One can then use those exported functions to communicate between
  *   child and parent.
  *
  * * When adding a new consumer of RPM that relies on other functionality
  *   then simple message passing provided by the RPM, then one has to
- *   whitelist permissions for the new URI within the RPMAccessManager.
- *
- * Please note that prefs that one wants to update need to be whitelisted
- * within AsyncPrefs.jsm.
+ *   whitelist permissions for the new URI within the RPMAccessManager
+ *   from MessagePort.jsm.
  */
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "AsyncPrefs",
-  "resource://gre/modules/AsyncPrefs.jsm");
-ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
-  "resource://gre/modules/PrivateBrowsingUtils.jsm");
-
-/*
- * Used for all kinds of permissions checks which requires explicit
- * whitelisting of specific permissions granted through RPM.
- */
-let RPMAccessManager = {
-  accessMap: {
-    "about:privatebrowsing": {
-      // "sendAsyncMessage": handled within AboutPrivateBrowsingHandler.jsm
-      // "setBoolPref": handled within AsyncPrefs.jsm and uses the pref
-      //                ["privacy.trackingprotection.pbmode.enabled"],
-      "getBoolPref": ["privacy.trackingprotection.enabled",
-                      "privacy.trackingprotection.pbmode.enabled"],
-      "getFormatURLPref": ["privacy.trackingprotection.introURL",
-                           "app.support.baseURL"],
-      "isWindowPrivate": ["yes"],
-    },
-  },
-
-  checkAllowAccess(aPrincipal, aFeature, aValue) {
-    // if there is no content principal; deny access
-    if (!aPrincipal || !aPrincipal.URI) {
-      return false;
-    }
-    let uri = aPrincipal.URI.asciiSpec;
-
-    // check if there is an entry for that requestying URI in the accessMap;
-    // if not, deny access.
-    let accessMapForURI = this.accessMap[uri];
-    if (!accessMapForURI) {
-      Cu.reportError("RPMAccessManager does not allow access to Feature: " + aFeature + " for: " + uri);
-      return false;
-    }
-
-    // check if the feature is allowed to be accessed for that URI;
-    // if not, deny access.
-    let accessMapForFeature = accessMapForURI[aFeature];
-    if (!accessMapForFeature) {
-      Cu.reportError("RPMAccessManager does not allow access to Feature: " + aFeature + " for: " + uri);
-      return false;
-    }
-
-    // if the actual value is in the whitelist for that feature;
-    // allow access
-    if (accessMapForFeature.includes(aValue)) {
-      return true;
-    }
-
-    // otherwise deny access
-    Cu.reportError("RPMAccessManager does not allow access to Feature: " + aFeature + " for: " + uri);
-    return false;
-  },
-};
-
-function MessageListener() {
-  this.listeners = new Map();
-}
-
-MessageListener.prototype = {
-  keys() {
-    return this.listeners.keys();
-  },
-
-  has(name) {
-    return this.listeners.has(name);
-  },
-
-  callListeners(message) {
-    let listeners = this.listeners.get(message.name);
-    if (!listeners) {
-      return;
-    }
-
-    for (let listener of listeners.values()) {
-      try {
-        listener(message);
-      } catch (e) {
-        Cu.reportError(e);
-      }
-    }
-  },
-
-  addMessageListener(name, callback) {
-    if (!this.listeners.has(name))
-      this.listeners.set(name, new Set([callback]));
-    else
-      this.listeners.get(name).add(callback);
-  },
-
-  removeMessageListener(name, callback) {
-    if (!this.listeners.has(name))
-      return;
-
-    this.listeners.get(name).delete(callback);
-  },
-};
-
+ChromeUtils.import("resource://gre/modules/remotepagemanager/MessagePort.jsm");
 
 /**
  * Creates a RemotePages object which listens for new remote pages of some
  * particular URLs. A "RemotePage:Init" message will be dispatched to this
  * object for every page loaded. Message listeners added to this object receive
  * messages from all loaded pages from the requested urls.
  */
 var RemotePages = function(urls) {
@@ -282,139 +180,16 @@ function publicMessagePort(port) {
         return port.url;
       }
     });
   }
 
   return clean;
 }
 
-
-/*
- * A message port sits on each side of the process boundary for every remote
- * page. Each has a port ID that is unique to the message manager it talks
- * through.
- *
- * We roughly implement the same contract as nsIMessageSender and
- * nsIMessageListenerManager
- */
-function MessagePort(messageManager, portID) {
-  this.messageManager = messageManager;
-  this.portID = portID;
-  this.destroyed = false;
-  this.listener = new MessageListener();
-
-  this.message = this.message.bind(this);
-  this.messageManager.addMessageListener("RemotePage:Message", this.message);
-}
-
-MessagePort.prototype = {
-  messageManager: null,
-  portID: null,
-  destroyed: null,
-  listener: null,
-  _browser: null,
-  remotePort: null,
-
-  // Called when the message manager used to connect to the other process has
-  // changed, i.e. when a tab is detached.
-  swapMessageManager(messageManager) {
-    this.messageManager.removeMessageListener("RemotePage:Message", this.message);
-
-    this.messageManager = messageManager;
-
-    this.messageManager.addMessageListener("RemotePage:Message", this.message);
-  },
-
-  /* Adds a listener for messages. Many callbacks can be registered for the
-   * same message if necessary. An attempt to register the same callback for the
-   * same message twice will be ignored. When called the callback is passed an
-   * object with these properties:
-   *   target: This message port
-   *   name:   The message name
-   *   data:   Any data sent with the message
-   */
-  addMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("Message port has been destroyed");
-    }
-
-    this.listener.addMessageListener(name, callback);
-  },
-
-  /*
-   * Removes a listener for messages.
-   */
-  removeMessageListener(name, callback) {
-    if (this.destroyed) {
-      throw new Error("Message port has been destroyed");
-    }
-
-    this.listener.removeMessageListener(name, callback);
-  },
-
-  // Sends a message asynchronously to the other process
-  sendAsyncMessage(name, data = null) {
-    if (this.destroyed) {
-      throw new Error("Message port has been destroyed");
-    }
-
-    this.messageManager.sendAsyncMessage("RemotePage:Message", {
-      portID: this.portID,
-      name,
-      data,
-    });
-  },
-
-  // Called to destroy this port
-  destroy() {
-    try {
-      // This can fail in the child process if the tab has already been closed
-      this.messageManager.removeMessageListener("RemotePage:Message", this.message);
-    } catch (e) { }
-    this.messageManager = null;
-    this.destroyed = true;
-    this.portID = null;
-    this.listener = null;
-  },
-
-  getBoolPref(aPref) {
-    let principal = this.window.document.nodePrincipal;
-    if (!RPMAccessManager.checkAllowAccess(principal, "getBoolPref", aPref)) {
-      throw new Error("RPMAccessManager does not allow access to getBoolPref");
-    }
-    return Services.prefs.getBoolPref(aPref);
-  },
-
-  setBoolPref(aPref, aVal) {
-    return new this.window.Promise(function(resolve) {
-      AsyncPrefs.set(aPref, aVal).then(function() {
-        resolve();
-      });
-    });
-  },
-
-  getFormatURLPref(aFormatURL) {
-    let principal = this.window.document.nodePrincipal;
-    if (!RPMAccessManager.checkAllowAccess(principal, "getFormatURLPref", aFormatURL)) {
-      throw new Error("RPMAccessManager does not allow access to getFormatURLPref");
-    }
-    return Services.urlFormatter.formatURLPref(aFormatURL);
-  },
-
-  isWindowPrivate() {
-    let principal = this.window.document.nodePrincipal;
-    if (!RPMAccessManager.checkAllowAccess(principal, "isWindowPrivate", "yes")) {
-      throw new Error("RPMAccessManager does not allow access to isWindowPrivate");
-    }
-    return PrivateBrowsingUtils.isContentWindowPrivate(this.window);
-  },
-};
-
-
 // The chome side of a message port
 function ChromeMessagePort(browser, portID, url) {
   MessagePort.call(this, browser.messageManager, portID);
 
   this._browser = browser;
   this._permanentKey = browser.permanentKey;
   this._url = url;
 
@@ -497,96 +272,16 @@ ChromeMessagePort.prototype.destroy = fu
   }
 
   this._browser = null;
   Services.obs.removeObserver(this, "message-manager-disconnect");
   MessagePort.prototype.destroy.call(this);
 };
 
 
-// The content side of a message port
-function ChildMessagePort(contentFrame, window) {
-  let portID = Services.appinfo.processID + ":" + ChildMessagePort.prototype.nextPortID++;
-  MessagePort.call(this, contentFrame, portID);
-
-  this.window = window;
-
-  // Add functionality to the content page
-  Cu.exportFunction(this.sendAsyncMessage.bind(this), window, {
-    defineAs: "RPMSendAsyncMessage",
-  });
-  Cu.exportFunction(this.addMessageListener.bind(this), window, {
-    defineAs: "RPMAddMessageListener",
-    allowCallbacks: true,
-  });
-  Cu.exportFunction(this.removeMessageListener.bind(this), window, {
-    defineAs: "RPMRemoveMessageListener",
-    allowCallbacks: true,
-  });
-  Cu.exportFunction(this.getBoolPref.bind(this), window, {
-    defineAs: "RPMGetBoolPref",
-  });
-  Cu.exportFunction(this.setBoolPref.bind(this), window, {
-    defineAs: "RPMSetBoolPref",
-  });
-  Cu.exportFunction(this.getFormatURLPref.bind(this), window, {
-    defineAs: "RPMGetFormatURLPref",
-  });
-  Cu.exportFunction(this.isWindowPrivate.bind(this), window, {
-    defineAs: "RPMIsWindowPrivate",
-  });
-
-  // Send a message for load events
-  let loadListener = () => {
-    this.sendAsyncMessage("RemotePage:Load");
-    window.removeEventListener("load", loadListener);
-  };
-  window.addEventListener("load", loadListener);
-
-  // Destroy the port when the window is unloaded
-  window.addEventListener("unload", () => {
-    try {
-      this.sendAsyncMessage("RemotePage:Unload");
-    } catch (e) {
-      // If the tab has been closed the frame message manager has already been
-      // destroyed
-    }
-    this.destroy();
-  });
-
-  // Tell the main process to set up its side of the message pipe.
-  this.messageManager.sendAsyncMessage("RemotePage:InitPort", {
-    portID,
-    url: window.document.documentURI.replace(/[\#|\?].*$/, ""),
-  });
-}
-
-ChildMessagePort.prototype = Object.create(MessagePort.prototype);
-
-ChildMessagePort.prototype.nextPortID = 0;
-
-// Called when a message is received from the message manager. This could
-// have come from any port in the message manager so verify the port ID.
-ChildMessagePort.prototype.message = function({ data: messagedata }) {
-  if (this.destroyed || (messagedata.portID != this.portID)) {
-    return;
-  }
-
-  let message = {
-    name: messagedata.name,
-    data: messagedata.data,
-  };
-  this.listener.callListeners(Cu.cloneInto(message, this.window));
-};
-
-ChildMessagePort.prototype.destroy = function() {
-  this.window = null;
-  MessagePort.prototype.destroy.call(this);
-};
-
 // Allows callers to register to connect to specific content pages. Registration
 // is done through the addRemotePageListener method
 var RemotePageManagerInternal = {
   // The currently registered remote pages
   pages: new Map(),
 
   // Initialises all the needed listeners
   init() {
@@ -597,35 +292,29 @@ var RemotePageManagerInternal = {
   updateProcessUrls() {
     Services.ppmm.initialProcessData["RemotePageManager:urls"] = Array.from(this.pages.keys());
   },
 
   // Registers interest in a remote page. A callback is called with a port for
   // the new page when loading begins (i.e. the page hasn't actually loaded yet).
   // Only one callback can be registered per URL.
   addRemotePageListener(url, callback) {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-      throw new Error("RemotePageManager can only be used in the main process.");
-
     if (this.pages.has(url)) {
       throw new Error("Remote page already registered: " + url);
     }
 
     this.pages.set(url, callback);
     this.updateProcessUrls();
 
     // Notify all the frame scripts of the new registration
     Services.ppmm.broadcastAsyncMessage("RemotePage:Register", { urls: [url] });
   },
 
   // Removes any interest in a remote page.
   removeRemotePageListener(url) {
-    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-      throw new Error("RemotePageManager can only be used in the main process.");
-
     if (!this.pages.has(url)) {
       throw new Error("Remote page is not registered: " + url);
     }
 
     // Notify all the frame scripts of the removed registration
     Services.ppmm.broadcastAsyncMessage("RemotePage:Unregister", { urls: [url] });
     this.pages.delete(url);
     this.updateProcessUrls();
@@ -639,49 +328,20 @@ var RemotePageManagerInternal = {
       return;
     }
 
     let port = new ChromeMessagePort(browser, portID, url);
     callback(port.publicPort);
   }
 };
 
-if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
-  RemotePageManagerInternal.init();
+if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+  throw new Error("RemotePageManager can only be used in the main process.");
+}
+
+RemotePageManagerInternal.init();
 
 // The public API for the above object
 var RemotePageManager = {
   addRemotePageListener: RemotePageManagerInternal.addRemotePageListener.bind(RemotePageManagerInternal),
   removeRemotePageListener: RemotePageManagerInternal.removeRemotePageListener.bind(RemotePageManagerInternal),
 };
 
-// Listen for pages in any process we're loaded in
-var registeredURLs = new Set(Services.cpmm.initialProcessData["RemotePageManager:urls"]);
-
-var observer = (window) => {
-  // Strip the hash from the URL, because it's not part of the origin.
-  let url = window.document.documentURI.replace(/[\#|\?].*$/, "");
-  if (!registeredURLs.has(url))
-    return;
-
-  // Get the frame message manager for this window so we can associate this
-  // page with a browser element
-  let messageManager = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIDocShell)
-                             .QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIContentFrameMessageManager);
-  // Set up the child side of the message port
-  new ChildMessagePort(messageManager, window);
-};
-Services.obs.addObserver(observer, "chrome-document-global-created");
-Services.obs.addObserver(observer, "content-document-global-created");
-
-// A message from chrome telling us what pages to listen for
-Services.cpmm.addMessageListener("RemotePage:Register", ({ data }) => {
-  for (let url of data.urls)
-    registeredURLs.add(url);
-});
-
-// A message from chrome telling us what pages to stop listening for
-Services.cpmm.addMessageListener("RemotePage:Unregister", ({ data }) => {
-  for (let url of data.urls)
-    registeredURLs.delete(url);
-});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/remotepagemanager/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files('**'):
+    BUG_COMPONENT = ('Toolkit', 'General')
+
+EXTRA_JS_MODULES.remotepagemanager = [
+    'MessagePort.jsm',
+    'RemotePageManagerChild.jsm',
+    'RemotePageManagerParent.jsm',
+]
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -119,16 +119,17 @@
   "logmanager.js": ["LogManager"],
   "lz4.js": ["Lz4"],
   "lz4_internal.js": ["Primitives"],
   "main.js": ["Weave"],
   "MatchURLFilters.jsm": ["MatchURLFilters"],
   "mcc_iso3166_table.jsm": ["MCC_ISO3166_TABLE"],
   "message.js": ["Command", "Message", "MessageOrigin", "Response"],
   "MessageContext.jsm": ["MessageContext"],
+  "MessagePort.jsm": ["MessagePort", "MessageListener"],
   "Messaging.jsm": ["sendMessageToJava", "Messaging", "EventDispatcher"],
   "microformat-shiv.js": ["Microformats"],
   "MigrationUtils.jsm": ["MigrationUtils", "MigratorPrototype"],
   "MulticastDNSAndroid.jsm": ["MulticastDNS"],
   "NativeMessaging.jsm": ["NativeApp"],
   "NotificationDB.jsm": [],
   "nsFormAutoCompleteResult.jsm": ["FormAutoCompleteResult"],
   "observers.js": ["Observers"],
@@ -163,16 +164,18 @@
   "quit.js": ["goQuitApplication"],
   "Readability.js": ["Readability"],
   "record.js": ["WBORecord", "RecordManager", "CryptoWrapper", "CollectionKeyManager", "Collection"],
   "recursive_importA.jsm": ["foo", "bar"],
   "recursive_importB.jsm": ["baz", "qux"],
   "reflect.jsm": ["Reflect"],
   "RemoteFinder.jsm": ["RemoteFinder", "RemoteFinderListener"],
   "RemotePageManager.jsm": ["RemotePages", "RemotePageManager", "PageListener"],
+  "RemotePageManagerChild.jsm": ["ChildMessagePort"],
+  "RemotePageManagerParent.jsm": ["RemotePages", "RemotePageManager"],
   "RemoteWebProgress.jsm": ["RemoteWebProgressManager"],
   "resource.js": ["AsyncResource", "Resource"],
   "rest.js": ["RESTRequest", "RESTResponse", "TokenAuthenticatedRESTRequest"],
   "rotaryengine.js": ["RotaryEngine", "RotaryRecord", "RotaryStore", "RotaryTracker"],
   "require.js": ["require"],
   "RTCStatsReport.jsm": ["convertToRTCStatsReport"],
   "Schemas.jsm": ["SchemaRoot", "Schemas"],
   "scratchpad-manager.jsm": ["ScratchpadManager"],