Bug 1368152: Part 2 - Move extension policy registration out of ExtensionManagement.jsm. r=aswan,mixedpuppy
authorKris Maglione <maglione.k@gmail.com>
Fri, 26 May 2017 12:04:18 -0700
changeset 410382 73737d0a388808aabadd578053374f01a46cd315
parent 410381 e3fdbc5921e3815356cc81af1fc7284a2e67bb76
child 410383 1733d96aacacb985f240dd7aae724c9de011b393
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan, mixedpuppy
bugs1368152
milestone55.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 1368152: Part 2 - Move extension policy registration out of ExtensionManagement.jsm. r=aswan,mixedpuppy MozReview-Commit-ID: Ls1ZvJLcjlR
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/ExtensionManagement.jsm
toolkit/components/extensions/extension-process-script.js
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -74,24 +74,31 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "require",
                                   "resource://devtools/shared/Loader.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
 
-Cu.import("resource://gre/modules/ExtensionManagement.jsm");
+XPCOMUtils.defineLazyGetter(
+  this, "processScript",
+  () => Cc["@mozilla.org/webextensions/extension-process-script;1"]
+          .getService().wrappedJSObject);
+
 Cu.import("resource://gre/modules/ExtensionParent.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidGen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyPreferenceGetter(this, "useRemoteWebExtensions",
+                                      "extensions.webextensions.remote", false);
+
 var {
   GlobalManager,
   ParentAPIManager,
   apiManager: Management,
 } = ExtensionParent;
 
 const {
   classifyPermission,
@@ -699,17 +706,17 @@ this.Extension = class extends Extension
 
     this.addonData = addonData;
     this.startupReason = startupReason;
 
     if (["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)) {
       StartupCache.clearAddonData(addonData.id);
     }
 
-    this.remote = ExtensionManagement.useRemoteWebExtensions;
+    this.remote = useRemoteWebExtensions;
 
     if (this.remote && processCount !== 1) {
       throw new Error("Out-of-process WebExtensions are not supported with multiple child processes");
     }
     if (this.remote && !Services.prefs.getBoolPref("layers.popups.compositing.enabled", false)) {
       Cu.reportError(new Error("Remote extensions should not be enabled without also setting " +
                                "the layers.popups.compositing.enabled preference to true"));
     }
@@ -911,23 +918,16 @@ this.Extension = class extends Extension
       Services.obs.addObserver(observer, "message-manager-close");
       Services.obs.addObserver(observer, "message-manager-disconnect");
 
       ppmm.broadcastAsyncMessage(msg, data);
     });
   }
 
   runManifest(manifest) {
-    let resources = [];
-    if (manifest.web_accessible_resources) {
-      resources = manifest.web_accessible_resources.map(path => path.replace(/^\/*/, "/"));
-    }
-
-    this.webAccessibleResources = resources.map(res => new MatchGlob(res));
-
     let promises = [];
     for (let directive in manifest) {
       if (manifest[directive] !== null) {
         promises.push(Management.emit(`manifest_${directive}`, directive, this, manifest));
 
         promises.push(Management.asyncEmitManifestEntry(this, directive));
       }
     }
@@ -978,24 +978,19 @@ this.Extension = class extends Extension
 
   startup() {
     this.startupPromise = this._startup();
     return this.startupPromise;
   }
 
   async _startup() {
     TelemetryStopwatch.start("WEBEXT_EXTENSION_STARTUP_MS", this);
-    this.started = false;
-
     try {
       let [, perms] = await Promise.all([this.loadManifest(), ExtensionPermissions.get(this)]);
 
-      ExtensionManagement.startupExtension(this.uuid, this.addonData.resourceURI, this);
-      this.started = true;
-
       if (!this.hasShutdown) {
         await this.initLocale();
       }
 
       if (this.errors.length) {
         return Promise.reject({errors: this.errors});
       }
 
@@ -1011,35 +1006,43 @@ this.Extension = class extends Extension
       }
       if (perms.origins.length > 0) {
         let patterns = this.whiteListedHosts.patterns.map(host => host.pattern);
 
         this.whiteListedHosts = new MatchPatternSet([...patterns, ...perms.origins],
                                                     {ignorePath: true});
       }
 
+      // Normalize all patterns to contain a single leading /
+      let resources = (this.manifest.web_accessible_resources || [])
+          .map(path => path.replace(/^\/*/, "/"));
+
+      this.webAccessibleResources = resources.map(res => new MatchGlob(res));
+
+
+      this.policy = processScript.initExtension(this.serialize(), this);
+
       // The "startup" Management event sent on the extension instance itself
       // is emitted just before the Management "startup" event,
       // and it is used to run code that needs to be executed before
       // any of the "startup" listeners.
       this.emit("startup", this);
       Management.emit("startup", this);
 
       await this.runManifest(this.manifest);
 
       Management.emit("ready", this);
       this.emit("ready");
       TelemetryStopwatch.finish("WEBEXT_EXTENSION_STARTUP_MS", this);
     } catch (e) {
       dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
       Cu.reportError(e);
 
-      if (this.started) {
-        this.started = false;
-        ExtensionManagement.shutdownExtension(this);
+      if (this.policy) {
+        this.policy.active = false;
       }
 
       this.cleanupGeneratedFile();
 
       throw e;
     }
 
     this.startupPromise = null;
@@ -1071,32 +1074,32 @@ this.Extension = class extends Extension
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
     this.shutdownReason = reason;
     this.hasShutdown = true;
 
-    if (!this.started) {
+    if (!this.policy) {
       return;
     }
 
     if (this.cleanupFile ||
         ["ADDON_INSTALL", "ADDON_UNINSTALL", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(reason)) {
       StartupCache.clearAddonData(this.id);
     }
 
     let data = Services.ppmm.initialProcessData;
     data["Extension:Extensions"] = data["Extension:Extensions"].filter(e => e.id !== this.id);
 
     Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);
 
     if (!this.manifest) {
-      ExtensionManagement.shutdownExtension(this);
+      this.policy.active = false;
 
       this.cleanupGeneratedFile();
       return;
     }
 
     GlobalManager.uninit(this);
 
     for (let obj of this.onShutdown) {
@@ -1111,17 +1114,17 @@ this.Extension = class extends Extension
 
     Management.emit("shutdown", this);
     this.emit("shutdown");
 
     await this.broadcast("Extension:Shutdown", {id: this.id});
 
     MessageChannel.abortResponses({extensionId: this.id});
 
-    ExtensionManagement.shutdownExtension(this);
+    this.policy.active = false;
 
     return this.cleanupGeneratedFile();
   }
 
   observe(subject, topic, data) {
     if (topic === "xpcom-shutdown") {
       this.cleanupGeneratedFile();
     }
--- a/toolkit/components/extensions/ExtensionManagement.jsm
+++ b/toolkit/components/extensions/ExtensionManagement.jsm
@@ -1,105 +1,47 @@
 /* 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";
 
+/* exported ExtensionManagement */
+
 this.EXPORTED_SYMBOLS = ["ExtensionManagement"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
-                                  "resource:///modules/E10SUtils.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "UUIDMap", () => {
-  let {UUIDMap} = Cu.import("resource://gre/modules/Extension.jsm", {});
-  return UUIDMap;
-});
-
 /*
  * This file should be kept short and simple since it's loaded even
  * when no extensions are running.
  */
 
-function parseScriptOptions(options) {
-  return {
-    allFrames: options.all_frames,
-    matchAboutBlank: options.match_about_blank,
-    frameID: options.frame_id,
-    runAt: options.run_at,
-
-    matches: new MatchPatternSet(options.matches),
-    excludeMatches: new MatchPatternSet(options.exclude_matches || []),
-    includeGlobs: options.include_globs && options.include_globs.map(glob => new MatchGlob(glob)),
-    excludeGlobs: options.include_globs && options.exclude_globs.map(glob => new MatchGlob(glob)),
-
-    jsPaths: options.js || [],
-    cssPaths: options.css || [],
-  };
-}
-
-function getURLForExtension(id, path = "") {
-  let uuid = UUIDMap.get(id, false);
-  if (!uuid) {
-    Cu.reportError(`Called getURLForExtension on unmapped extension ${id}`);
-    return null;
-  }
-  return `moz-extension://${uuid}/${path}`;
-}
-
 let cacheInvalidated = 0;
 function onCacheInvalidate() {
   cacheInvalidated++;
 }
 Services.obs.addObserver(onCacheInvalidate, "startupcache-invalidate");
 
 var ExtensionManagement = {
   get cacheInvalidated() {
     return cacheInvalidated;
   },
 
   get isExtensionProcess() {
     return WebExtensionPolicy.isExtensionProcess;
   },
 
-  // Called when a new extension is loaded.
-  startupExtension(uuid, uri, extension) {
-    let policy = new WebExtensionPolicy({
-      id: extension.id,
-      mozExtensionHostname: uuid,
-      baseURL: uri.spec,
-
-      permissions: Array.from(extension.permissions),
-      allowedOrigins: extension.whiteListedHosts,
-      webAccessibleResources: extension.webAccessibleResources || [],
-
-      contentSecurityPolicy: extension.manifest.content_security_policy,
-
-      localizeCallback: extension.localize.bind(extension),
-
-      backgroundScripts: (extension.manifest.background &&
-                          extension.manifest.background.scripts),
-
-      contentScripts: (extension.manifest.content_scripts || []).map(parseScriptOptions),
-    });
-
-    extension.policy = policy;
-    policy.active = true;
+  getURLForExtension(id, path = "") {
+    let policy = WebExtensionPolicy.getByID(id);
+    if (!policy) {
+      Cu.reportError(`Called getURLForExtension on unmapped extension ${id}`);
+      return null;
+    }
+    return policy.getURL(path);
   },
-
-  // Called when an extension is unloaded.
-  shutdownExtension(extension) {
-    extension.policy.active = false;
-  },
-
-  getURLForExtension,
 };
-
-XPCOMUtils.defineLazyPreferenceGetter(ExtensionManagement, "useRemoteWebExtensions",
-                                      "extensions.webextensions.remote", false);
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -289,44 +289,50 @@ ExtensionManager = {
         procData["Extension:Schemas"] = new Map();
       }
       this.schemaJSON = procData["Extension:Schemas"];
 
       Services.cpmm.addMessageListener("Schema:Add", this);
     }
   },
 
-  initExtension(data) {
-    let policy;
-    if (isContentProcess) {
+  initExtensionPolicy(data, extension) {
+    let policy = WebExtensionPolicy.getByID(data.id);
+    if (!policy) {
+      let localizeCallback = (
+        extension ? extension.localize.bind(extension)
+                  : str => extensions.get(policy).localize(str));
+
       policy = new WebExtensionPolicy({
         id: data.id,
         mozExtensionHostname: data.uuid,
         baseURL: data.resourceURL,
 
         permissions: Array.from(data.permissions),
         allowedOrigins: new MatchPatternSet(data.whiteListedHosts),
         webAccessibleResources: data.webAccessibleResources.map(host => new MatchGlob(host)),
 
         contentSecurityPolicy: data.manifest.content_security_policy,
 
-        localizeCallback: str => extensions.get(policy).localize(str),
+        localizeCallback,
 
         backgroundScripts: (data.manifest.background &&
                             data.manifest.background.scripts),
 
         contentScripts: (data.manifest.content_scripts || []).map(parseScriptOptions),
       });
 
       policy.active = true;
-    } else {
-      policy = WebExtensionPolicy.getByID(data.id);
+      policy.initData = data;
     }
+    return policy;
+  },
 
-    policy.initData = data;
+  initExtension(data) {
+    let policy = this.initExtensionPolicy(data);
 
     DocumentManager.initExtension(policy);
   },
 
   receiveMessage({name, data}) {
     switch (name) {
       case "Extension:Startup": {
         this.initExtension(data);
@@ -371,16 +377,22 @@ function ExtensionProcessScript() {
 }
 
 ExtensionProcessScript.singleton = null;
 
 ExtensionProcessScript.prototype = {
   classID: Components.ID("{21f9819e-4cdf-49f9-85a0-850af91a5058}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.mozIExtensionProcessScript]),
 
+  get wrappedJSObject() { return this; },
+
+  initExtension(data, extension) {
+    return ExtensionManager.initExtensionPolicy(data, extension);
+  },
+
   initExtensionDocument(policy, doc) {
     if (DocumentManager.globals.has(getMessageManager(doc.defaultView))) {
       DocumentManager.loadInto(policy, doc.defaultView);
     }
   },
 
   preloadContentScript(contentScript) {
     contentScripts.get(contentScript).preload();