Bug 1364768: Part 8 - Wait for delayed startup before loading most background pages. r?aswan,zombie draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 28 May 2017 16:10:52 -0700
changeset 585784 b568fd81512bb739bf6948e04e4315d1b20853d0
parent 585783 4f03300e81bcd126d8dfe73e1a4b03bdaec469ea
child 630795 964ae940ff33a8e39d4ba22536fd01d91af70c0f
push id61190
push usermaglione.k@gmail.com
push dateSun, 28 May 2017 23:49:44 +0000
reviewersaswan, zombie
bugs1364768
milestone55.0a1
Bug 1364768: Part 8 - Wait for delayed startup before loading most background pages. r?aswan,zombie We shouldn't actually need to load background pages until fairly late, except when blocking web request listeners are used. Prior to these patches, at least for most of the 55 cycle, all background pages generally loaded well after the first window was shown, as a consequence of the repeated trips through the event loop that were required to complete the startup process. After them, they generally load much earlier, before first paint. In order to avoid a regression in first paint performance from these patches, I'd like to guarantee that background pages aren't loaded prior to first paint unless they have the webRequestBlocking permission. In the future, I'd like to delay even those add-ons until a request is made which matches one of the extension's origin permissions. MozReview-Commit-ID: 7miaXZRAFSt
toolkit/components/extensions/ExtensionParent.jsm
toolkit/components/extensions/ext-backgroundPage.js
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -27,16 +27,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
                                   "resource://gre/modules/NativeMessaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+                                  "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gAddonPolicyService",
                                    "@mozilla.org/addons/policy-service;1",
                                    "nsIAddonPolicyService");
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
@@ -1002,16 +1004,38 @@ function promiseExtensionViewLoaded(brow
   return new Promise(resolve => {
     browser.messageManager.addMessageListener("Extension:ExtensionViewLoaded", function onLoad({data}) {
       browser.messageManager.removeMessageListener("Extension:ExtensionViewLoaded", onLoad);
       resolve(data.childId && ParentAPIManager.getContextById(data.childId));
     });
   });
 }
 
+let _delayedStartupPromise;
+
+function promiseDelayedStartup() {
+  if (!_delayedStartupPromise) {
+    if (AppConstants.platform == "android") {
+      _delayedStartupPromise = Promise.resolve();
+    } else {
+      let win = RecentWindow.getMostRecentBrowserWindow();
+      if (win && win.delayedStartupPromise) {
+        // Create a new promise to avoid keeping the browser window's
+        // compartment alive.
+        _delayedStartupPromise = Promise.resolve(win.delayedStartupPromise);
+      }
+      if (!_delayedStartupPromise) {
+        _delayedStartupPromise = promiseObserved("browser-delayed-startup-finished").then(() => {});
+      }
+    }
+  }
+
+  return _delayedStartupPromise;
+}
+
 /**
  * This helper is used to subscribe a listener (e.g. in the ext-devtools API implementation)
  * to be called for every ExtensionProxyContext created for an extension page given
  * its related extension, viewType and browser element (both the top level context and any context
  * created for the extension urls running into its iframe descendants).
  *
  * @param {object} params.extension
  *        The Extension on which we are going to listen for the newly created ExtensionProxyContext.
@@ -1080,12 +1104,13 @@ const ExtensionParent = {
     let manifest = types.find(type => type.id === "WebExtensionManifest");
     if (!manifest) {
       throw new Error("Unable to find base manifest properties");
     }
 
     gBaseManifestProperties = Object.getOwnPropertyNames(manifest.properties);
     return gBaseManifestProperties;
   },
+  promiseDelayedStartup,
   promiseExtensionViewLoaded,
   watchExtensionProxyContextLoad,
   DebugUtils,
 };
--- a/toolkit/components/extensions/ext-backgroundPage.js
+++ b/toolkit/components/extensions/ext-backgroundPage.js
@@ -51,19 +51,33 @@ class BackgroundPage extends HiddenExten
   }
 
   shutdown() {
     super.shutdown();
   }
 }
 
 this.backgroundPage = class extends ExtensionAPI {
-  onManifestEntry(entryName) {
+  async onManifestEntry(entryName) {
     let {manifest} = this.extension;
 
+    // Unless the extension needs to be able to modify and block web
+    // requests, delay loading the background page until after the
+    // first browser window is visible and interactive.
+    if (!this.extension.hasPermission("webRequestBlocking")) {
+      await ExtensionParent.promiseDelayedStartup();
+
+      // Also wait for an idle callback, so we can avoid adding jank
+      // early in the session, as much as possible.
+      let win = Services.wm.getMostRecentWindow("navigator:browser");
+      if (win) {
+        await new Promise(resolve => win.requestIdleCallback(resolve));
+      }
+    }
+
     this.bgPage = new BackgroundPage(this.extension, manifest.background);
 
     return this.bgPage.build();
   }
 
   onShutdown() {
     this.bgPage.shutdown();
   }