Bug 1395287 - Order and chain content_scripts injection on startup draft
authorTomislav Jovanovic <tomica@gmail.com>
Sun, 10 Sep 2017 18:56:35 +0200
changeset 662049 8cc924973aef377d948cc91572164037d33671f7
parent 657984 a46a5879b8781ae9ea99f37b5d34a891f0f75047
child 730730 5d8a61acc60879d5c11aa9b797516cda4273533b
push id78935
push userbmo:tomica@gmail.com
push dateSun, 10 Sep 2017 19:37:53 +0000
bugs1395287
milestone57.0a1
Bug 1395287 - Order and chain content_scripts injection on startup MozReview-Commit-ID: 19MH2zLV99s
toolkit/components/extensions/extension-process-script.js
toolkit/components/extensions/test/mochitest/test_ext_contentscript_async_loading.html
--- a/toolkit/components/extensions/extension-process-script.js
+++ b/toolkit/components/extensions/extension-process-script.js
@@ -187,21 +187,32 @@ DocumentManager = {
       this.initGlobal(subject);
     }
   },
 
   // Script loading
 
   injectExtensionScripts(extension) {
     for (let window of this.enumerateWindows()) {
+      let runAt = {document_start: [], document_end: [], document_idle: []};
+
       for (let script of extension.contentScripts) {
         if (script.matchesWindow(window)) {
-          contentScripts.get(script).injectInto(window);
+          runAt[script.runAt].push(script);
         }
       }
+
+      let inject = matcher => contentScripts.get(matcher).injectInto(window);
+      let injectAll = matchers => Promise.all(matchers.map(inject));
+
+      // Intentionally using `.then` instead of `await`, we only need to
+      // chain injecting other scripts into *this* window, not all windows.
+      injectAll(runAt.document_start)
+        .then(() => injectAll(runAt.document_end))
+        .then(() => injectAll(runAt.document_idle));
     }
   },
 
   /**
    * Checks that all parent frames for the given withdow either have the
    * same add-on ID, or are special chrome-privileged documents such as
    * about:addons or developer tools panels.
    *
--- a/toolkit/components/extensions/test/mochitest/test_ext_contentscript_async_loading.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript_async_loading.html
@@ -1,54 +1,82 @@
-<!doctype html>
+<!DOCTYPE html>
 <html>
 <head>
   <title>Test content script async loading</title>
   <script src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
   <script src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <script>
 "use strict";
 
 add_task(async function test_async_loading() {
   const adder = `(function add(a = 1) { this.count += a; })();\n`;
-  const extension = ExtensionTestUtils.loadExtension({
+
+  const extension = {
     manifest: {
-      content_scripts: [{
-        matches: ["https://example.org/"],
-        js: ["first.js", "second.js"],
-      }],
+      content_scripts: [
+        {
+          run_at: "document_start",
+          matches: ["https://example.org/"],
+          js: ["first.js", "second.js"],
+        },
+        {
+          run_at: "document_end",
+          matches: ["https://example.org/"],
+          js: ["third.js"],
+        },
+      ],
     },
     files: {
       "first.js": `
         this.count = 0;
         ${adder.repeat(50000)};  // 2Mb
         browser.test.assertEq(this.count, 50000, "A 50k line script");
 
         this.order = (this.order || 0) + 1;
         browser.test.sendMessage("first", this.order);
       `,
       "second.js": `
         this.order = (this.order || 0) + 1;
         browser.test.sendMessage("second", this.order);
       `,
+      "third.js": `
+        this.order = (this.order || 0) + 1;
+        browser.test.sendMessage("third", this.order);
+      `,
     },
-  });
+  };
 
-  await extension.startup();
-  const win = window.open("https://example.org/");
+  async function checkOrder(extension) {
+    const [first, second, third] = await Promise.all([
+      extension.awaitMessage("first"),
+      extension.awaitMessage("second"),
+      extension.awaitMessage("third"),
+    ]);
 
-  const [first, second] = await Promise.all([
-    extension.awaitMessage("first"),
-    extension.awaitMessage("second"),
-  ]);
+    is(first, 1, "first.js finished execution first.");
+    is(second, 2, "second.js finished execution second.");
+    is(third, 3, "third.js finished execution third.");
+  }
+
+  info("Test pages observed while extension is running");
+  const observed = ExtensionTestUtils.loadExtension(extension);
+  await observed.startup();
 
-  is(first, 1, "first.js finished execution first.");
-  is(second, 2, "second.js finished execution second.");
+  const win = window.open("https://example.org/");
+  await checkOrder(observed);
+  await observed.unload();
 
-  await extension.unload();
+  info("Test pages already existing on extension startup");
+  const existing = ExtensionTestUtils.loadExtension(extension);
+
+  await existing.startup();
+  await checkOrder(existing);
+  await existing.unload();
+
   win.close();
 });
 
 </script>