Bug 1395287 - Order and chain content_scripts injection on startup
MozReview-Commit-ID: 19MH2zLV99s
--- 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>