Bug 1395287 - Order and chain content_scripts injection on startup r=kmag
authorTomislav Jovanovic <tomica@gmail.com>
Sun, 10 Sep 2017 18:56:35 +0200
changeset 379981 ce17d1d232f1cb5a809bff8cf2bb9991f61847d9
parent 379980 8b7a5231e2a46ee425ad637caba96960de6e6a72
child 379982 8b53358a5d661f87cc06dfdeb6bed53bcfd78056
push id50855
push usertomica@gmail.com
push dateSun, 10 Sep 2017 19:46:42 +0000
treeherderautoland@ce17d1d232f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1395287
milestone57.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 1395287 - Order and chain content_scripts injection on startup r=kmag 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>