Bug 1518863: Part 3 - Initialize stub extension policies in child during startup. r=aswan, a=lizzard
☠☠ backed out by e1652912e1d5 ☠ ☠
authorKris Maglione <maglione.k@gmail.com>
Wed, 27 Feb 2019 13:26:03 -0800
changeset 516327 d94de0e08f52bbfb54a140462b0a4d2fb5e84477
parent 516326 4d96aa8bbf572eba63bc7d0d6ccfee494869702b
child 516328 11506a4e0fd8088f20b70a27e96e9b3170c9d5fb
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, lizzard
bugs1518863
milestone66.0
Bug 1518863: Part 3 - Initialize stub extension policies in child during startup. r=aswan, a=lizzard 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
@@ -1307,16 +1307,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);
@@ -1714,16 +1716,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
@@ -1826,16 +1831,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);
       }
@@ -181,32 +216,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": {