Bug 1379148: Part 3 - Inject scripts synchronously if they're already available. r?mixedpuppy draft
authorKris Maglione <maglione.k@gmail.com>
Wed, 20 Sep 2017 17:00:44 -0700
changeset 667977 502a142bccef8f9237b2639d6566a5d8458a5d3f
parent 667976 63245f7d9a5dc93028d782a77640103eeb050bd2
child 667978 5bee303b5e28ab7c496d2e4b22a071ae7e5606d6
push id80907
push usermaglione.k@gmail.com
push dateThu, 21 Sep 2017 00:03:55 +0000
reviewersmixedpuppy
bugs1379148
milestone57.0a1
Bug 1379148: Part 3 - Inject scripts synchronously if they're already available. r?mixedpuppy MozReview-Commit-ID: J6vheuxsqJR
toolkit/components/extensions/ExtensionContent.jsm
toolkit/components/extensions/ExtensionUtils.jsm
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -132,18 +132,26 @@ class CacheMap extends DefaultMap {
         this.delete(url);
       }
     }
   }
 }
 
 class ScriptCache extends CacheMap {
   constructor(options) {
-    super(SCRIPT_EXPIRY_TIMEOUT_MS,
-          url => ChromeUtils.compileScript(url, options));
+    super(SCRIPT_EXPIRY_TIMEOUT_MS);
+    this.options = options;
+  }
+
+  defaultConstructor(url) {
+    let promise = ChromeUtils.compileScript(url, this.options);
+    promise.then(script => {
+      promise.script = script;
+    });
+    return promise;
   }
 }
 
 class CSSCache extends CacheMap {
   constructor(sheetType) {
     super(CSS_EXPIRY_TIMEOUT_MS, url => {
       let uri = Services.io.newURI(url);
       return styleSheetService.preloadSheetAsync(uri, sheetType).then(sheet => {
@@ -312,27 +320,35 @@ class Script {
             this.cssCache.addDocument(url, window.document);
 
             runSafeSyncWithoutClone(winUtils.addSheet, sheet, type);
           }
         });
       }
     }
 
-    let scriptsPromise = Promise.all(this.compileScripts());
+    let scriptPromises = this.compileScripts();
+
+    let scripts = scriptPromises.map(promise => promise.script);
+    // If not all scripts are already available in the cache, block
+    // parsing and wait all promises to resolve.
+    if (!scripts.every(script => script)) {
+      let scriptsPromise = Promise.all(scriptPromises);
 
-    // If we're supposed to inject at the start of the document load,
-    // and we haven't already missed that point, block further parsing
-    // until the scripts have been loaded.
-    let {document} = context.contentWindow;
-    if (this.runAt === "document_start" && document.readyState !== "complete") {
-      document.blockParsing(scriptsPromise, {blockScriptCreated: false});
+      // If we're supposed to inject at the start of the document load,
+      // and we haven't already missed that point, block further parsing
+      // until the scripts have been loaded.
+      let {document} = context.contentWindow;
+      if (this.runAt === "document_start" && document.readyState !== "complete") {
+        document.blockParsing(scriptsPromise, {blockScriptCreated: false});
+      }
+
+      scripts = await scriptsPromise;
     }
 
-    let scripts = await scriptsPromise;
     let result;
 
     // The evaluations below may throw, in which case the promise will be
     // automatically rejected.
     TelemetryStopwatch.start(CONTENT_SCRIPT_INJECTION_HISTOGRAM, context);
     try {
       for (let script of scripts) {
         result = script.executeInGlobal(context.cloneScope);
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -62,35 +62,39 @@ function instanceOf(value, type) {
           ChromeUtils.getClassName(value) === type);
 }
 
 /**
  * Similar to a WeakMap, but creates a new key with the given
  * constructor if one is not present.
  */
 class DefaultWeakMap extends WeakMap {
-  constructor(defaultConstructor, init) {
+  constructor(defaultConstructor = undefined, init = undefined) {
     super(init);
-    this.defaultConstructor = defaultConstructor;
+    if (defaultConstructor) {
+      this.defaultConstructor = defaultConstructor;
+    }
   }
 
   get(key) {
     let value = super.get(key);
     if (value === undefined && !this.has(key)) {
       value = this.defaultConstructor(key);
       this.set(key, value);
     }
     return value;
   }
 }
 
 class DefaultMap extends Map {
-  constructor(defaultConstructor, init) {
+  constructor(defaultConstructor = undefined, init = undefined) {
     super(init);
-    this.defaultConstructor = defaultConstructor;
+    if (defaultConstructor) {
+      this.defaultConstructor = defaultConstructor;
+    }
   }
 
   get(key) {
     let value = super.get(key);
     if (value === undefined && !this.has(key)) {
       value = this.defaultConstructor(key);
       this.set(key, value);
     }