Bug 1518863: Part 3 - Initialize stub extension policies in child during startup. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Feb 2019 13:26:03 -0800
changeset 461863 e75922833e757d0012dfa3b01779091bd28fbdb9
parent 461862 606364d19a36c4f6598b707bdd8fbc646ce88075
child 461864 05528e4ad2fed74be18afd20be32375c8a8ea42e
push id35631
push userrgurzau@mozilla.com
push dateFri, 01 Mar 2019 13:06:03 +0000
treeherdermozilla-central@d4e19870e27f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1518863
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1518863: Part 3 - Initialize stub extension policies in child during startup. r=aswan We already create early stub policy objects in the parent process during extension startup, so that early load attempts and policy queries can succeed during startup and extension installation. This patch does the same for child processes, which likewise may be asked to load extension URLs early during startup when they override the homepage or have pages loaded from session restore. Differential Revision: https://phabricator.services.mozilla.com/D21448
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionProcessScript.jsm
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -1357,16 +1357,18 @@ class LangpackBootstrapScope {
   shutdown(data, reason) {
     this.langpack.shutdown();
     this.langpack = null;
   }
 }
 
 let activeExtensionIDs = new Set();
 
+let pendingExtensions = new Map();
+
 /**
  * This class is the main representation of an active WebExtension
  * in the main process.
  * @extends ExtensionData
  */
 class Extension extends ExtensionData {
   constructor(addonData, startupReason) {
     super(addonData.resourceURI);
@@ -1751,16 +1753,19 @@ class Extension extends ExtensionData {
                    Management.asyncEmitManifestEntry(this, directive));
       }
     }
     updateState();
 
     activeExtensionIDs.add(this.id);
     sharedData.set("extensions/activeIDs", activeExtensionIDs);
 
+    pendingExtensions.delete(this.id);
+    sharedData.set("extensions/pending", pendingExtensions);
+
     Services.ppmm.sharedData.flush();
     this.broadcast("Extension:Startup", this.id);
 
     return Promise.all(promises);
   }
 
   /**
    * Call the close() method on the given object when this extension
@@ -1870,16 +1875,22 @@ class Extension extends ExtensionData {
       readyPromise,
     });
 
     this.policy.extension = this;
     if (!WebExtensionPolicy.getByID(this.id)) {
       this.policy.active = true;
     }
 
+    pendingExtensions.set(this.id, {
+      mozExtensionHostname: this.uuid,
+      baseURL: this.resourceURL,
+    });
+    sharedData.set("extensions/pending", pendingExtensions);
+
     ExtensionTelemetry.extensionStartup.stopwatchStart(this);
     try {
       this.state = "Startup: Loading manifest";
       await this.loadManifest();
       this.state = "Startup: Loaded manifest";
 
       if (!this.hasShutdown) {
         this.state = "Startup: Init locale";
--- a/toolkit/components/extensions/ExtensionProcessScript.jsm
+++ b/toolkit/components/extensions/ExtensionProcessScript.jsm
@@ -43,16 +43,18 @@ function getData(extension, key = "") {
 // eslint-disable-next-line mozilla/use-services
 const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 const isContentProcess = appinfo.processType == appinfo.PROCESS_TYPE_CONTENT;
 
 var extensions = new DefaultWeakMap(policy => {
   return new ExtensionChild.BrowserExtensionContent(policy);
 });
 
+var pendingExtensions = new Map();
+
 var ExtensionManager;
 
 class ExtensionGlobal {
   constructor(global) {
     this.global = global;
     this.global.addMessageListener("Extension:SetFrameData", this);
 
     this.frameData = null;
@@ -118,24 +120,57 @@ ExtensionManager = {
     Services.cpmm.addMessageListener("Extension:RegisterContentScript", this);
     Services.cpmm.addMessageListener("Extension:UnregisterContentScripts", this);
 
     // eslint-disable-next-line mozilla/balanced-listeners
     Services.obs.addObserver(
       global => this.globals.set(global, new ExtensionGlobal(global)),
       "tab-content-frameloader-created");
 
+    this.updateStubExtensions();
+
     for (let id of sharedData.get("extensions/activeIDs") || []) {
       this.initExtension(getData({id}));
     }
   },
 
+  initStubPolicy(id, data) {
+    let resolveReadyPromise;
+    let readyPromise = new Promise(resolve => {
+      resolveReadyPromise = resolve;
+    });
+
+    let policy = new WebExtensionPolicy({
+      id,
+      localizeCallback() {},
+      readyPromise,
+      allowedOrigins: new MatchPatternSet([]),
+      ...data,
+    });
+
+    try {
+      policy.active = true;
+
+      pendingExtensions.set(id, {policy, resolveReadyPromise});
+    } catch (e) {
+      Cu.reportError(e);
+    }
+  },
+
+  updateStubExtensions() {
+    for (let [id, data] of sharedData.get("extensions/pending") || []) {
+      if (!pendingExtensions.has(id)) {
+        this.initStubPolicy(id, data);
+      }
+    }
+  },
+
   initExtensionPolicy(extension) {
     let policy = WebExtensionPolicy.getByID(extension.id);
-    if (!policy) {
+    if (!policy || pendingExtensions.has(extension.id)) {
       let localizeCallback;
       if (extension.localize) {
         // We have a real Extension object.
         localizeCallback = extension.localize.bind(extension);
       } else {
         // We have serialized extension data;
         localizeCallback = str => extensions.get(policy).localize(str);
       }
@@ -179,32 +214,45 @@ ExtensionManager = {
         if ("userScriptOptions" in options) {
           script.userScriptOptions = options.userScriptOptions;
         }
 
         policy.registerContentScript(script);
         registeredContentScripts.set(scriptId, script);
       }
 
+      let stub = pendingExtensions.get(extension.id);
+      if (stub) {
+        pendingExtensions.delete(extension.id);
+        stub.policy.active = false;
+        stub.resolveReadyPromise(policy);
+      }
+
       policy.active = true;
       policy.instanceId = extension.instanceId;
       policy.optionalPermissions = extension.optionalPermissions;
     }
     return policy;
   },
 
   initExtension(data) {
     if (typeof data === "string") {
       data = getData({id: data});
     }
     let policy = this.initExtensionPolicy(data);
 
     policy.injectContentScripts();
   },
 
+  handleEvent(event) {
+    if (event.type === "change" && event.changedKeys.includes("extensions/pending")) {
+      this.updateStubExtensions();
+    }
+  },
+
   receiveMessage({name, data}) {
     try {
       switch (name) {
         case "Extension:Startup":
           this.initExtension(data);
           break;
 
         case "Extension:Shutdown": {