Bug 1369466 - Load RemotePageManager.jsm lazily draft
authorKirk Steuber <ksteuber@mozilla.com>
Thu, 14 Sep 2017 15:27:42 -0700
changeset 681941 8d57d07c105489540727337a389b38f389b2958e
parent 681625 f27105b62753c71ecadad2f8d632ec7e5ac96bbd
child 736262 f978ff99791c4424272c1b2c152de4c325674eeb
push id84956
push userksteuber@mozilla.com
push dateTue, 17 Oct 2017 22:27:33 +0000
bugs1369466
milestone58.0a1
Bug 1369466 - Load RemotePageManager.jsm lazily MozReview-Commit-ID: 3P5UQHIQZhr
browser/base/content/test/performance/browser_startup.js
toolkit/content/process-content.js
toolkit/modules/RemotePageManager.jsm
toolkit/mozapps/extensions/AddonManager.jsm
--- a/browser/base/content/test/performance/browser_startup.js
+++ b/browser/base/content/test/performance/browser_startup.js
@@ -34,19 +34,16 @@ const startupPhases = {
       "WebContentConverter.js", // bug 1369443
       "nsSessionStartup.js", // bug 1369456
       "PushComponents.js", // bug 1369436
     ]),
     modules: new Set([
       "resource://gre/modules/AppConstants.jsm",
       "resource://gre/modules/XPCOMUtils.jsm",
       "resource://gre/modules/Services.jsm",
-
-      // Bugs to fix: Probably loaded too early, needs investigation.
-      "resource://gre/modules/RemotePageManager.jsm", // bug 1369466
     ])
   }},
 
   // For the following phases of startup we have only a black list for now
 
   // We are at this phase after creating the first browser window (ie. after final-ui-startup).
   "before opening first browser window": {blacklist: {
     modules: new Set([
@@ -71,16 +68,17 @@ const startupPhases = {
       "resource:///modules/BrowserUITelemetry.jsm",
       "resource:///modules/BrowserUsageTelemetry.jsm",
       "resource:///modules/ContentCrashHandlers.jsm",
       "resource:///modules/DirectoryLinksProvider.jsm",
       "resource://gre/modules/NewTabUtils.jsm",
       "resource://gre/modules/PageThumbs.jsm",
       "resource://gre/modules/Promise.jsm", // imported by devtools during _delayedStartup
       "resource://gre/modules/Preferences.jsm",
+      "resource://gre/modules/RemotePageManager.jsm",
     ]),
     services: new Set([
       "@mozilla.org/browser/search-service;1",
     ])
   }},
 
   // We are at this phase once we are ready to handle user events.
   // Anything loaded at this phase or before gets in the way of the user
--- a/toolkit/content/process-content.js
+++ b/toolkit/content/process-content.js
@@ -1,23 +1,73 @@
 /* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+
 // Creates a new PageListener for this process. This will listen for page loads
 // and for those that match URLs provided by the parent process will set up
 // a dedicated message port and notify the parent process.
-Cu.import("resource://gre/modules/RemotePageManager.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RemotePageManager",
+  "resource://gre/modules/RemotePageManager.jsm");
+// Once the chrome process has loaded the RemotePageManager, trigger the load of
+// the RemotePageManager in this process immediately.
+if (gInContentProcess) {
+  if (Services.cpmm.initialProcessData["RemotePageManager:initialized"]) {
+    RemotePageManager;
+  } else {
+    Services.cpmm.addMessageListener("RemotePage:Init", function RPMInitListener() {
+      Services.cpmm.removeMessageListener("RemotePage:Init", RPMInitListener);
+      RemotePageManager;
+    });
+  }
+}
+if (!Services.cpmm.initialProcessData["RemotePageManager:initialized"]) {
+  // If the remote page manager hasn't been loaded already, wait to load it
+  // until we see a page load that it needs to observe.
+  function loadRemotePageManager(subject, topic, data) {
+    if (Services.cpmm.initialProcessData["RemotePageManager:initialized"]) {
+      // If the remote page manager is already initialized, it will already be
+      // observing this event. This additional observer is not needed.
+      Services.obs.removeObserver(loadRemotePageManager,
+                                  "chrome-document-global-created");
+      Services.obs.removeObserver(loadRemotePageManager,
+                                  "content-document-global-created");
+      return;
+    }
 
-const gInContentProcess = Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+    let url = subject.document.documentURI;
+    let watchUrls = Services.ppmm.initialProcessData["RemotePageManager:urls"];
+    watchUrls = watchUrls ? watchUrls : [];
+    if (watchUrls.includes(url)) {
+      // The remote page manager needs to observe the load of this page.
+      // Forward this event to its observer. This will also result in the
+      // RemotePageManager module being loaded.
+      RemotePageManager.observe(subject, topic, data);
+      // Now that the RemotePageManager module has been loaded, it will connect
+      // its own event listeners, so this one is no longer needed.
+      Services.obs.removeObserver(loadRemotePageManager,
+                                  "chrome-document-global-created");
+      Services.obs.removeObserver(loadRemotePageManager,
+                                  "content-document-global-created");
+    }
+  }
+  Services.obs.addObserver(loadRemotePageManager,
+                           "chrome-document-global-created");
+  Services.obs.addObserver(loadRemotePageManager,
+                           "content-document-global-created");
+}
 
 Services.cpmm.addMessageListener("gmp-plugin-crash", msg => {
   let gmpservice = Cc["@mozilla.org/gecko-media-plugin-service;1"]
                      .getService(Ci.mozIGeckoMediaPluginService);
 
   gmpservice.RunPluginCrashCallbacks(msg.data.pluginID, msg.data.pluginName);
 });
 
--- a/toolkit/modules/RemotePageManager.jsm
+++ b/toolkit/modules/RemotePageManager.jsm
@@ -529,46 +529,58 @@ var RemotePageManagerInternal = {
     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
-this.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;
+// The public API for RemotePageManagerInternal
+this.RemotePageManager = {
+  addRemotePageListener: RemotePageManagerInternal.addRemotePageListener.bind(RemotePageManagerInternal),
+  removeRemotePageListener: RemotePageManagerInternal.removeRemotePageListener.bind(RemotePageManagerInternal),
+
+  // This observer is meant to observe these two events:
+  //   "chrome-document-global-created"
+  //   "content-document-global-created"
+  // These events are connected to this observer by process-content.js
+  observe(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);
+    // 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");
+Services.obs.addObserver(this.RemotePageManager.observe,
+                         "chrome-document-global-created");
+Services.obs.addObserver(this.RemotePageManager.observe,
+                         "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);
 });
+
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
+  // Let the content processes know that the chrome process has initialized.
+  Services.ppmm.broadcastAsyncMessage("RemotePage:Init");
+}
+Services.ppmm.initialProcessData["RemotePageManager:initialized"] = true;
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -898,35 +898,59 @@ var AddonManagerInternal = {
       }
 
       // If this is a new profile just pretend that there were no changes
       if (appChanged === undefined) {
         for (let type in this.startupChanges)
           delete this.startupChanges[type];
       }
 
-      // Support for remote about:plugins. Note that this module isn't loaded
-      // at the top because Services.appinfo is defined late in tests.
-      let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {});
-
-      gPluginPageListener = new RemotePages("about:plugins");
-      gPluginPageListener.addMessageListener("RequestPlugins", this.requestPlugins);
+      // Don't initialize the remote page listener here. Let it initialize
+      // itself, then call initRemotePageListener to interface with it.
+      if (Services.cpmm.initialProcessData["RemotePageManager:initialized"]) {
+        this.initRemotePageListener();
+      } else {
+        // Add about:plugins to the RemotePageManager:urls list. This ensures
+        // that the remote page listener will be initialized when the
+        // about:plugins page is opened (if not before).
+        if (Services.ppmm.initialProcessData["RemotePageManager:urls"]) {
+          Services.ppmm.initialProcessData["RemotePageManager:urls"].push("about:plugins");
+        } else {
+          Services.ppmm.initialProcessData["RemotePageManager:urls"] = ["about:plugins"];
+        }
+        let initRemotePageListener = this.initRemotePageListener;
+        Services.cpmm.addMessageListener("RemotePage:Init", function RPMInitListener() {
+          Services.cpmm.removeMessageListener("RemotePage:Init", RPMInitListener);
+          initRemotePageListener();
+        });
+      }
 
       gStartupComplete = true;
       this.recordTimestamp("AMI_startup_end");
     } catch (e) {
       logger.error("startup failed", e);
       AddonManagerPrivate.recordException("AMI", "startup failed", e);
     }
 
     logger.debug("Completed startup sequence");
     this.callManagerListeners("onStartup");
   },
 
   /**
+   * Initializes gPluginPageListener, which provides support for remote
+   * about:plugins. Initialization needs to be done sometime after first
+   * paint to prevent RemotePageManager.jsm from being loaded too early.
+   */
+  initRemotePageListener() {
+    let { RemotePages } = Cu.import("resource://gre/modules/RemotePageManager.jsm", {});
+    gPluginPageListener = new RemotePages("about:plugins");
+    gPluginPageListener.addMessageListener("RequestPlugins", AddonManagerInternal.requestPlugins);
+  },
+
+  /**
    * Registers a new AddonProvider.
    *
    * @param  aProvider
    *         The provider to register
    * @param  aTypes
    *         An optional array of add-on types
    */
   registerProvider(aProvider, aTypes) {
@@ -1107,18 +1131,20 @@ var AddonManagerInternal = {
     gShutdownInProgress = true;
     // Clean up listeners
     Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
     Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
     Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
     Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
     Services.prefs.removeObserver(PREF_EM_HOTFIX_ID, this);
-    gPluginPageListener.destroy();
-    gPluginPageListener = null;
+    if (gPluginPageListener) {
+      gPluginPageListener.destroy();
+      gPluginPageListener = null;
+    }
 
     let savedError = null;
     // Only shut down providers if they've been started.
     if (gStarted) {
       try {
         await gShutdownBarrier.wait();
       } catch (err) {
         savedError = err;