Bug 1368102: Part 6 - Remove StubExtension and use WebExtensionPolicy directly. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Thu, 25 May 2017 21:57:48 -0700
changeset 585215 123aafcd5e345ae1086494dcfb3ab99f8b744dbd
parent 585214 760921ea68741aec84f97eb6c573d9507ac8aafc
child 585216 2acc1c9f2bc623d32a34044f15a1fc88abe9ef1c
push id61052
push usermaglione.k@gmail.com
push dateFri, 26 May 2017 17:14:32 +0000
reviewersmixedpuppy
bugs1368102
milestone55.0a1
Bug 1368102: Part 6 - Remove StubExtension and use WebExtensionPolicy directly. r?mixedpuppy MozReview-Commit-ID: 9rvdPamZrF
toolkit/components/extensions/extension-process-script.js
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -53,27 +53,32 @@ function parseScriptOptions(options) {
     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 || [],
   };
 }
 
+var extensions = new DefaultWeakMap(policy => {
+  let extension = new ExtensionChild.BrowserExtensionContent(policy.initData);
+  extension.policy = policy;
+  return extension;
+});
+
 class ScriptMatcher {
-  constructor(extension, matcher) {
-    this.extension = extension;
+  constructor(matcher) {
     this.matcher = matcher;
 
     this._script = null;
   }
 
   get script() {
     if (!this._script) {
-      this._script = new ExtensionContent.Script(this.extension.realExtension,
+      this._script = new ExtensionContent.Script(extensions.get(this.matcher.extension),
                                                  this.matcher);
     }
     return this._script;
   }
 
   preload() {
     let {script} = this;
 
@@ -125,42 +130,40 @@ class ExtensionGlobal {
 
   receiveMessage({target, messageName, recipient, data}) {
     switch (messageName) {
       case "Extension:Capture":
         return ExtensionContent.handleExtensionCapture(this.global, data.width, data.height, data.options);
       case "Extension:DetectLanguage":
         return ExtensionContent.handleDetectLanguage(this.global, target);
       case "Extension:Execute":
-        let extension = ExtensionManager.get(recipient.extensionId);
+        let policy = WebExtensionPolicy.getByID(recipient.extensionId);
 
-        let matcher = new WebExtensionContentScript(extension.policy, parseScriptOptions(data.options));
+        let matcher = new WebExtensionContentScript(policy, parseScriptOptions(data.options));
 
         let options = Object.assign(matcher, {
           wantReturnValue: data.options.wantReturnValue,
           removeCSS: data.options.remove_css,
           cssOrigin: data.options.cssOrigin,
           cssCode: data.options.cssCode,
           jsCode: data.options.jsCode,
         });
 
-        let script = new ScriptMatcher(extension, options);
+        let script = new ScriptMatcher(options);
 
         return ExtensionContent.handleExtensionExecute(this.global, target, data.options, script);
       case "WebNavigation:GetFrame":
         return ExtensionContent.handleWebNavigationGetFrame(this.global, data.options);
       case "WebNavigation:GetAllFrames":
         return ExtensionContent.handleWebNavigationGetAllFrames(this.global);
     }
   }
 }
 
-let stubExtensions = new WeakMap();
-let scriptMatchers = new DefaultWeakMap(matcher => new ScriptMatcher(stubExtensions.get(matcher.extension),
-                                                                     matcher));
+let scriptMatchers = new DefaultWeakMap(matcher => new ScriptMatcher(matcher));
 
 // Responsible for creating ExtensionContexts and injecting content
 // scripts into them when new documents are created.
 DocumentManager = {
   globals: new Map(),
 
   // Initialize listeners that we need regardless of whether extensions are
   // enabled.
@@ -251,17 +254,17 @@ DocumentManager = {
   observe(subject, topic, data) {
     this.observers[topic].call(this, subject, topic, data);
   },
 
   // Script loading
 
   injectExtensionScripts(extension) {
     for (let window of this.enumerateWindows()) {
-      for (let script of extension.policy.contentScripts) {
+      for (let script of extension.contentScripts) {
         if (script.matchesWindow(window)) {
           scriptMatchers.get(script).injectInto(window);
         }
       }
     }
   },
 
   /**
@@ -307,29 +310,30 @@ DocumentManager = {
   },
 
   loadInto(window) {
     let {addonId} = Cu.getObjectPrincipal(window);
     if (!addonId) {
       return;
     }
 
-    let extension = ExtensionManager.get(addonId);
-    if (!extension) {
+    let policy = WebExtensionPolicy.getByID(addonId);
+    if (!policy) {
       throw new Error(`No registered extension for ID ${addonId}`);
     }
 
+    let extension = extensions.get(policy);
     if (this.checkParentFrames(window, addonId) && ExtensionManagement.isExtensionProcess) {
       // We're in a top-level extension frame, or a sub-frame thereof,
       // in the extension process. Inject the full extension page API.
-      ExtensionPageChild.initExtensionContext(extension.realExtension, window);
+      ExtensionPageChild.initExtensionContext(extension, window);
     } else {
       // We're in a content sub-frame or not in the extension process.
       // Only inject a minimal content script API.
-      ExtensionContent.initExtensionContext(extension.realExtension, window);
+      ExtensionContent.initExtensionContext(extension, window);
     }
   },
 
   // Helpers
 
   * enumerateWindows(docShell) {
     if (docShell) {
       let enum_ = docShell.getDocShellEnumerator(docShell.typeContent,
@@ -341,132 +345,93 @@ DocumentManager = {
     } else {
       for (let global of this.globals.keys()) {
         yield* this.enumerateWindows(global.docShell);
       }
     }
   },
 };
 
-/**
- * This class is a minimal stub extension object which loads and instantiates a
- * real extension object when non-basic functionality is needed.
- */
-class StubExtension {
-  constructor(data) {
-    this.data = data;
-    this.id = data.id;
-    this.uuid = data.uuid;
-    this.instanceId = data.instanceId;
-    this.manifest = data.manifest;
-    this.permissions = data.permissions;
-    this.whiteListedHosts = new MatchPatternSet(data.whiteListedHosts);
-    this.webAccessibleResources = data.webAccessibleResources.map(path => new MatchGlob(path));
-
-    this._realExtension = null;
-
-    this.startup();
-
-    this.scripts = this.policy.contentScripts.map(matcher => new ScriptMatcher(this, matcher));
-  }
-
-  startup() {
-    // Extension.jsm takes care of this in the parent.
-    if (isContentProcess) {
-      let uri = Services.io.newURI(this.data.resourceURL);
-      ExtensionManagement.startupExtension(this.uuid, uri, this);
-    } else {
-      this.policy = WebExtensionPolicy.getByID(this.id);
-    }
-
-    stubExtensions.set(this.policy, this);
-  }
-
-  shutdown() {
-    if (isContentProcess) {
-      ExtensionManagement.shutdownExtension(this);
-    }
-    if (this._realExtension) {
-      this._realExtension.shutdown();
-    }
-  }
-
-  // Lazily create the real extension object when needed.
-  get realExtension() {
-    if (!this._realExtension) {
-      this._realExtension = new ExtensionChild.BrowserExtensionContent(this.data);
-      this._realExtension.policy = this.policy;
-    }
-    return this._realExtension;
-  }
-
-  // Forward functions needed by ExtensionManagement.
-  hasPermission(...args) {
-    return this.realExtension.hasPermission(...args);
-  }
-  localize(...args) {
-    return this.realExtension.localize(...args);
-  }
-}
-
 ExtensionManager = {
-  // Map[extensionId -> StubExtension]
-  extensions: new Map(),
-
   init() {
     MessageChannel.setupMessageManagers([Services.cpmm]);
 
     Services.cpmm.addMessageListener("Extension:Startup", this);
     Services.cpmm.addMessageListener("Extension:Shutdown", this);
     Services.cpmm.addMessageListener("Extension:FlushJarCache", this);
 
     let procData = Services.cpmm.initialProcessData || {};
 
     for (let data of procData["Extension:Extensions"] || []) {
-      let extension = new StubExtension(data);
-      this.extensions.set(data.id, extension);
-      DocumentManager.initExtension(extension);
+      this.initExtension(data);
     }
 
     if (isContentProcess) {
       // Make sure we handle new schema data until Schemas.jsm is loaded.
       if (!procData["Extension:Schemas"]) {
         procData["Extension:Schemas"] = new Map();
       }
       this.schemaJSON = procData["Extension:Schemas"];
 
       Services.cpmm.addMessageListener("Schema:Add", this);
     }
   },
 
-  get(extensionId) {
-    return this.extensions.get(extensionId);
+  initExtension(data) {
+    let policy;
+    if (isContentProcess) {
+      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),
+
+        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;
+
+    DocumentManager.initExtension(policy);
   },
 
   receiveMessage({name, data}) {
     switch (name) {
       case "Extension:Startup": {
-        let extension = new StubExtension(data);
-
-        this.extensions.set(data.id, extension);
-
-        DocumentManager.initExtension(extension);
+        this.initExtension(data);
 
         Services.cpmm.sendAsyncMessage("Extension:StartupComplete");
         break;
       }
 
       case "Extension:Shutdown": {
-        let extension = this.extensions.get(data.id);
-        this.extensions.delete(data.id);
+        let policy = WebExtensionPolicy.getByID(data.id);
 
-        if (extension) {
-          extension.shutdown();
+        if (extensions.has(policy)) {
+          extensions.get(policy).shutdown();
+        }
 
-          DocumentManager.uninitExtension(extension);
+        DocumentManager.uninitExtension(policy);
+
+        if (isContentProcess) {
+          policy.active = false;
         }
         break;
       }
 
       case "Extension:FlushJarCache": {
         ExtensionUtils.flushJarCache(data.path);
         Services.cpmm.sendAsyncMessage("Extension:FlushJarCacheComplete");
         break;