summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/mochitest/test_ext_scripting_contentScripts.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/mochitest/test_ext_scripting_contentScripts.html')
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_scripting_contentScripts.html1532
1 files changed, 1532 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_scripting_contentScripts.html b/toolkit/components/extensions/test/mochitest/test_ext_scripting_contentScripts.html
new file mode 100644
index 0000000000..c8457b6d36
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_scripting_contentScripts.html
@@ -0,0 +1,1532 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Tests scripting.*ContentScripts()</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+
+"use strict";
+
+const MOCHITEST_HOST_PERMISSIONS = [
+ "*://mochi.test/",
+ "*://mochi.xorigin-test/",
+ "*://test1.example.com/",
+];
+
+const makeExtension = ({ manifest: manifestProps, ...otherProps }) => {
+ return ExtensionTestUtils.loadExtension({
+ manifest: {
+ manifest_version: 3,
+ permissions: ["scripting"],
+ host_permissions: [
+ ...MOCHITEST_HOST_PERMISSIONS,
+ // Used in `file_contains_iframe.html`
+ "*://example.org/",
+ ],
+ granted_host_permissions: true,
+ ...manifestProps,
+ },
+ useAddonManager: "temporary",
+ ...otherProps,
+ });
+};
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.manifestV3.enabled", true]],
+ });
+});
+
+add_task(async function test_validate_registerContentScripts_params() {
+ let extension = makeExtension({
+ async background() {
+ const TEST_CASES = [
+ {
+ title: "no js and no css",
+ params: [
+ {
+ id: "script",
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "At least one js or css must be specified.",
+ },
+ {
+ title: "empty js",
+ params: [
+ {
+ id: "script",
+ js: [],
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "At least one js or css must be specified.",
+ },
+ {
+ title: "empty css",
+ params: [
+ {
+ id: "script",
+ css: [],
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "At least one js or css must be specified.",
+ },
+ {
+ title: "no matches",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "matches must be specified.",
+ },
+ {
+ title: "empty matches",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ matches: [],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "matches must be specified.",
+ },
+ {
+ title: "one empty match",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ matches: [""],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid url pattern: ",
+ },
+ {
+ title: "invalid match",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ matches: ["not-a-pattern"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid url pattern: not-a-pattern",
+ },
+ {
+ title: "invalid match and valid match",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*", "not-a-pattern"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid url pattern: not-a-pattern",
+ },
+ {
+ title: "one empty value in excludeMatches",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*"],
+ excludeMatches: [""],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid url pattern: ",
+ },
+ {
+ title: "invalid value in excludeMatches",
+ params: [
+ {
+ id: "script",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*"],
+ excludeMatches: ["not-a-pattern"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid url pattern: not-a-pattern",
+ },
+ {
+ title: "duplicate IDs",
+ params: [
+ {
+ id: "script-1",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ {
+ id: "script-1",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: `Script ID "script-1" found more than once in 'scripts' array.`,
+ },
+ {
+ title: "empty id",
+ params: [
+ {
+ id: "",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid content script id.",
+ },
+ {
+ title: "id starting with _",
+ params: [
+ {
+ id: "_foo",
+ js: ["script.js"],
+ matches: ["*://mochi.test/*"],
+ persistAcrossSessions: false,
+ },
+ ],
+ expectedError: "Invalid content script id.",
+ },
+ ];
+
+ for (const { title, params, expectedError } of TEST_CASES) {
+ await browser.test.assertRejects(
+ browser.scripting.registerContentScripts(params),
+ expectedError,
+ `${title} - got expected error`
+ );
+ }
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no registered script");
+
+ browser.test.notifyPass("test-finished");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("test-finished");
+ await extension.unload();
+});
+
+add_task(async function test_registerContentScripts_with_already_registered_id() {
+ let extension = makeExtension({
+ async background() {
+ const script = {
+ id: "script-1",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ };
+
+ await browser.scripting.registerContentScripts([script]);
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+
+ await browser.test.assertRejects(
+ browser.scripting.registerContentScripts([script]),
+ `Content script with id "${script.id}" is already registered.`,
+ "got expected error"
+ );
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+
+ browser.test.notifyPass("test-finished");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("test-finished");
+ await extension.unload();
+});
+
+add_task(async function test_validate_getRegisteredContentScripts_params() {
+ let extension = makeExtension({
+ async background() {
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ scripts = await browser.scripting.getRegisteredContentScripts({
+ ids: ["non-existent-id"]
+ });
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ browser.test.log("test call with undefined filter and a chrome-compatible callback");
+ scripts = await new Promise(resolve => {
+ browser.scripting.getRegisteredContentScripts(undefined, resolve);
+ });
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ browser.test.log("test call with only the chrome-compatible callback");
+ scripts = await new Promise(resolve => {
+ browser.scripting.getRegisteredContentScripts(resolve);
+ });
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ browser.test.notifyPass("test-finished");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("test-finished");
+ await extension.unload();
+});
+
+add_task(async function test_getRegisteredContentScripts() {
+ let extension = makeExtension({
+ async background() {
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ const aScript = {
+ id: "a-script",
+ js: ["script.js"],
+ matches: ["<all_urls>"],
+ persistAcrossSessions: false,
+ };
+
+ await browser.scripting.registerContentScripts([aScript]);
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+ browser.test.assertEq(aScript.id, scripts[0].id, "expected correct id");
+
+ // This should return no registered scripts.
+ scripts = await browser.scripting.getRegisteredContentScripts({ ids: [] });
+ browser.test.assertEq(0, scripts.length, "expected 0 registered script");
+
+ // Verify that invalid IDs are omitted but valid IDs are used to return
+ // registered scripts.
+ scripts = await browser.scripting.getRegisteredContentScripts({
+ ids: ["non-existent-id", aScript.id]
+ });
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+ browser.test.assertEq(aScript.id, scripts[0].id, "expected correct id");
+
+ browser.test.notifyPass("test-finished");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("test-finished");
+ await extension.unload();
+});
+
+add_task(async function test_registerContentScripts_js() {
+ let extension = makeExtension({
+ async background() {
+ const TEST_CASES = [
+ // This should have no effect but it should not throw.
+ {
+ title: "no script",
+ params: [],
+ },
+ {
+ title: "one script",
+ params: [
+ {
+ id: "script-1",
+ js: ["script-1.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ }
+ ],
+ },
+ {
+ title: "one script in all frames",
+ params: [
+ {
+ id: "script-2",
+ js: ["script-2.js"],
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ allFrames: true,
+ persistAcrossSessions: false,
+ }
+ ],
+ },
+ {
+ title: "one script in all frames with excludeMatches set",
+ params: [
+ {
+ id: "script-3",
+ js: ["script-3.js"],
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ allFrames: true,
+ excludeMatches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ }
+ ],
+ },
+ {
+ title: "one script, two js paths",
+ params: [
+ {
+ id: "script-4",
+ js: ["script-4-1.js", "script-4-2.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ }
+ ],
+ },
+ {
+ title: "empty excludeMatches",
+ params: [
+ {
+ id: "script-5",
+ // This path should be normalized.
+ js: ["/script-5.js"],
+ matches: ["*://test1.example.com/*"],
+ excludeMatches: [],
+ persistAcrossSessions: false,
+ }
+ ],
+ },
+ ];
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no registered script");
+
+ for (const { title, params } of TEST_CASES) {
+ const res = await browser.scripting.registerContentScripts(params);
+ browser.test.assertEq(
+ undefined,
+ res,
+ `${title} - expected no result`
+ );
+
+ const script = await browser.scripting.getRegisteredContentScripts({
+ ids: params.map(param => param.id)
+ });
+ browser.test.assertEq(
+ params.length,
+ script.length,
+ `${title} - got the expected number of registered scripts`
+ );
+ }
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(
+ // A test case declared above does not contain any script to register.
+ TEST_CASES.length - 1,
+ scripts.length,
+ "got the expected number of registered scripts"
+ );
+ browser.test.assertEq(
+ JSON.stringify([
+ {
+ id: "script-1",
+ allFrames: false,
+ matches: ["*://test1.example.com/*"],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-1.js"],
+ },
+ {
+ id: "script-2",
+ allFrames: true,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-2.js"],
+ },
+ {
+ id: "script-3",
+ allFrames: true,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ excludeMatches: ["*://test1.example.com/*"],
+ js: ["script-3.js"],
+ },
+ {
+ id: "script-4",
+ allFrames: false,
+ matches: ["*://test1.example.com/*"],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-4-1.js", "script-4-2.js"],
+ },
+ {
+ id: "script-5",
+ allFrames: false,
+ matches: ["*://test1.example.com/*"],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-5.js"],
+ },
+ ]),
+ JSON.stringify(scripts),
+ "got expected scripts"
+ );
+
+ browser.test.sendMessage("background-ready");
+ },
+ files: {
+ "script-1.js": () => {
+ browser.test.sendMessage(
+ "script-ran",
+ { file: "script-1.js", value: document.title }
+ );
+ },
+ "script-2.js": () => {
+ browser.test.sendMessage(
+ "script-ran",
+ { file: "script-2.js", value: document.title }
+ );
+ },
+ "script-3.js": () => {
+ browser.test.sendMessage(
+ "script-ran",
+ { file: "script-3.js", value: document.title }
+ );
+ },
+ "script-4-1.js": () => {
+ // We inject this script (first) as well as the one defined right
+ // after. The order should be respected, which is why we define a
+ // property here and check it in the second script.
+ window.SCRIPT_4_INJECTED = "SCRIPT_4_INJECTED";
+ },
+ "script-4-2.js": () => {
+ browser.test.sendMessage(
+ "script-ran",
+ { file: "script-4-2.js", value: window.SCRIPT_4_INJECTED }
+ );
+ delete window.SCRIPT_4_INJECTED;
+ },
+ "script-5.js": () => {
+ browser.test.sendMessage(
+ "script-ran",
+ { file: "script-5.js", value: document.title }
+ );
+ },
+ },
+ });
+
+ let scriptsRan = 0;
+ let results = [];
+ let completePromise = new Promise(resolve => {
+ extension.onMessage("script-ran", result => {
+ results.push(result);
+ scriptsRan++;
+
+ // The value below should be updated when TEST_CASES above is changed.
+ if (scriptsRan === 6) {
+ resolve();
+ }
+ });
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-ready");
+
+ // Load a page that will trigger the content scripts previously registered.
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ // Wait for all content scripts to be executed.
+ await completePromise;
+
+ // Verify that the scripts have been executed correctly. We sort the results
+ // to compare them against expected values.
+ results.sort((a, b) => {
+ return a.file.localeCompare(b.file) || a.value.localeCompare(b.value);
+ });
+ ok(
+ JSON.stringify([
+ { file: "script-1.js", value: "file contains iframe" },
+ // script-2.js should be injected in two frames
+ { file: "script-2.js", value: "file contains iframe" },
+ { file: "script-2.js", value: "file contains img" },
+ { file: "script-3.js", value: "file contains img" },
+ // script-4-1.js will add a prop to the `window` object, which should be
+ // read by `script-4-2.js`.
+ { file: "script-4-2.js", value: "SCRIPT_4_INJECTED" },
+ { file: "script-5.js", value: "file contains iframe" },
+ ]) === JSON.stringify(results),
+ "got expected script results" + JSON.stringify(results)
+ );
+
+ await AppTestDelegate.removeTab(window, tab);
+ await extension.unload();
+});
+
+add_task(async function test_registerContentScripts_are_not_unregistered() {
+ let extension = makeExtension({
+ files: {
+ "background.html": `<!DOCTYPE html>
+ <html>
+ <head><meta charset="utf-8"></head>
+ <body>
+ <script src="background.js"><\/script>
+ </body>
+ </html>
+ `,
+ "background.js": async () => {
+ await browser.scripting.registerContentScripts([
+ {
+ id: "a-script",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.sendMessage("background-executed");
+ },
+ "script.js": () => {
+ browser.test.sendMessage("script-executed");
+ },
+ },
+ });
+
+ await extension.startup();
+
+ // Load the background page that registers a content script.
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ `moz-extension://${extension.uuid}/background.html`,
+ true
+ );
+ await extension.awaitMessage("background-executed");
+ await AppTestDelegate.removeTab(window, tab);
+
+ // Load a page that will trigger the content scripts previously registered.
+ tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+
+ await extension.awaitMessage("script-executed");
+
+ await AppTestDelegate.removeTab(window, tab);
+ await extension.unload();
+});
+
+add_task(async function test_scripts_dont_run_after_shutdown() {
+ let extension = makeExtension({
+ async background() {
+ await browser.scripting.registerContentScripts([
+ {
+ id: "script-that-should-not-run",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.sendMessage("background-ready");
+ },
+ files: {
+ "script.js": () => {
+ browser.test.fail("this script should not be executed.");
+ },
+ },
+ });
+ // We use a second extension to wait enough time to confirm that the script
+ // registered in the previous extension has not been executed at all, in case
+ // the tab closes before the scheduled content script has had a chance to
+ // run.
+ let anotherExtension = makeExtension({
+ async background() {
+ await browser.scripting.registerContentScripts([
+ {
+ id: "this-script-should-run",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.sendMessage("background-ready");
+ },
+ files: {
+ "script.js": () => {
+ browser.test.sendMessage("script-ran");
+ },
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-ready");
+
+ await anotherExtension.startup();
+ await anotherExtension.awaitMessage("background-ready");
+
+ await extension.unload();
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+ await anotherExtension.awaitMessage("script-ran");
+ await AppTestDelegate.removeTab(window, tab);
+
+ await anotherExtension.unload();
+});
+
+add_task(async function test_registerContentScripts_with_wrong_matches() {
+ let extension = makeExtension({
+ async background() {
+ // Register a content script that should not be injected in this test
+ // case because the `matches` values don't match the host permissions.
+ await browser.scripting.registerContentScripts([
+ {
+ id: "script-that-should-not-run",
+ js: ["script.js"],
+ matches: ["*://mozilla.org/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.sendMessage("background-ready");
+ },
+ files: {
+ "script.js": () => {
+ browser.test.fail("this script should not be executed.");
+ },
+ },
+ });
+ // We use a second extension to wait enough time to confirm that the script
+ // registered in the previous extension has not been executed at all, in case
+ // the tab closes before the scheduled content script has had a chance to
+ // run.
+ let anotherExtension = makeExtension({
+ async background() {
+ await browser.scripting.registerContentScripts([
+ {
+ id: "this-script-should-run",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.sendMessage("background-ready");
+ },
+ files: {
+ "script.js": () => {
+ browser.test.sendMessage("script-ran");
+ },
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-ready");
+
+ await anotherExtension.startup();
+ await anotherExtension.awaitMessage("background-ready");
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html",
+ true
+ );
+ await anotherExtension.awaitMessage("script-ran");
+
+ await extension.unload();
+ await anotherExtension.unload();
+
+ // We remove the tab after having unloaded the extensions to avoid failures
+ // on Windows, see: Bug 1761550.
+ await AppTestDelegate.removeTab(window, tab);
+});
+
+add_task(async function test_registerContentScripts_twice_with_same_id() {
+ let extension = makeExtension({
+ async background() {
+ const script = {
+ id: "script-that-should-not-run",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ };
+
+ const results = await Promise.allSettled([
+ browser.scripting.registerContentScripts([script]),
+ browser.scripting.registerContentScripts([script]),
+ ]);
+
+ browser.test.assertEq(2, results.length, "got expected length");
+ browser.test.assertEq(
+ "fulfilled",
+ results[0].status,
+ "expected fulfilled promise"
+ );
+ browser.test.assertEq(
+ "rejected",
+ results[1].status,
+ "expected rejected promise"
+ );
+ browser.test.assertEq(
+ `Content script with id "script-that-should-not-run" is already registered.`,
+ results[1].reason.message,
+ "expected reason"
+ );
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+
+ browser.test.sendMessage("background-done");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_getRegisteredContentScripts_during_a_registration() {
+ let extension = makeExtension({
+ async background() {
+ browser.scripting.registerContentScripts([
+ {
+ id: "a-script",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ const scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(
+ JSON.stringify([
+ {
+ id: "a-script",
+ allFrames: false,
+ matches: ["*://test1.example.com/*"],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script.js"],
+ },
+ ]),
+ JSON.stringify(scripts),
+ "expected 1 registered script"
+ );
+
+ browser.test.sendMessage("background-done");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_validate_unregisterContentScripts_params() {
+ let extension = makeExtension({
+ async background() {
+ const TEST_CASES = [
+ {
+ title: "unknown id",
+ params: {
+ ids: ["non-existent-id"],
+ },
+ expectedError: `Content script with id "non-existent-id" does not exist.`
+ },
+ {
+ title: "invalid id",
+ params: {
+ ids: ["_invalid-id"],
+ },
+ expectedError: "Invalid content script id.",
+ },
+ ];
+
+ for (const { title, params, expectedError } of TEST_CASES) {
+ await browser.test.assertRejects(
+ browser.scripting.unregisterContentScripts(params),
+ expectedError,
+ `${title} - got expected error`
+ );
+ }
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no script");
+
+ browser.test.sendMessage("background-done");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_unregisterContentScripts_with_chrome_compatible_callback() {
+ let extension = makeExtension({
+ async background() {
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no script");
+
+ // Register a script that we can unregister after.
+ await browser.scripting.registerContentScripts([
+ {
+ id: "script-1",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+
+ browser.test.log("test call with undefined filter and a chrome-compatible callback");
+ await new Promise(resolve => {
+ browser.scripting.unregisterContentScripts(undefined, resolve);
+ });
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ // Re-register a script that we can unregister after.
+ await browser.scripting.registerContentScripts([
+ {
+ id: "script-1",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.log("test call with only the chrome-compatible callback");
+ await new Promise(resolve => {
+ browser.scripting.unregisterContentScripts(resolve);
+ });
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no registered scripts");
+
+ browser.test.sendMessage("background-done");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_unregisterContentScripts() {
+ let extension = makeExtension({
+ async background() {
+ const script1 = {
+ id: "script-1",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ };
+ const script2 = {
+ id: "script-2",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ }
+ const script3 = {
+ id: "script-3",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ }
+
+ let res = await browser.scripting.registerContentScripts([
+ script1,
+ script2,
+ script3,
+ ]);
+ browser.test.assertEq(undefined, res, "expected no result");
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(3, scripts.length, "expected 3 scripts");
+ browser.test.assertEq(script1.id, scripts[0].id, "expected correct id");
+ browser.test.assertEq(script2.id, scripts[1].id, "expected correct id");
+ browser.test.assertEq(script3.id, scripts[2].id, "expected correct id");
+
+ // No unregistration when unknown IDs are passed along with valid IDs.
+ await browser.test.assertRejects(
+ browser.scripting.unregisterContentScripts({
+ ids: [script2.id, "non-existent-id"],
+ }),
+ `Content script with id "non-existent-id" does not exist.`
+ );
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(3, scripts.length, "expected 3 scripts");
+
+ // Unregister 1 script.
+ res = await browser.scripting.unregisterContentScripts({
+ ids: [script2.id]
+ });
+ browser.test.assertEq(undefined, res, "expected no result");
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(2, scripts.length, "expected 2 scripts");
+ browser.test.assertEq(script1.id, scripts[0].id, "expected correct id");
+ browser.test.assertEq(script3.id, scripts[1].id, "expected correct id");
+
+ // This should unregister all the remaining registered scripts.
+ res = await browser.scripting.unregisterContentScripts();
+ browser.test.assertEq(undefined, res, "expected no result");
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected no script");
+
+ browser.test.sendMessage("background-done");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_unregisterContentScripts_twice_with_same_id() {
+ let extension = makeExtension({
+ async background() {
+ const script = {
+ id: "script-to-unregister",
+ js: ["script.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ };
+
+ await browser.scripting.registerContentScripts([script]);
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+
+ const results = await Promise.allSettled([
+ browser.scripting.unregisterContentScripts({ ids: [script.id] }),
+ browser.scripting.unregisterContentScripts({ ids: [script.id] }),
+ ]);
+
+ browser.test.assertEq(2, results.length, "got expected length");
+ browser.test.assertEq(
+ "fulfilled",
+ results[0].status,
+ "expected fulfilled promise"
+ );
+ browser.test.assertEq(
+ "rejected",
+ results[1].status,
+ "expected rejected promise"
+ );
+ browser.test.assertEq(
+ `Content script with id "script-to-unregister" does not exist.`,
+ results[1].reason.message,
+ "expected reason"
+ );
+
+ scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(0, scripts.length, "expected 0 registered script");
+
+ browser.test.sendMessage("background-done");
+ },
+ files: {
+ "script.js": "",
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_validate_updateContentScripts_params() {
+ let extension = makeExtension({
+ async background() {
+ const script = {
+ id: "registered-script",
+ js: ["script-1.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ };
+
+ const TEST_CASES = [
+ {
+ title: "invalid script ID",
+ params: [
+ {
+ id: "_invalid-id",
+ },
+ ],
+ expectedError: 'Invalid content script id.',
+ },
+ {
+ title: "empty script ID",
+ params: [
+ {
+ id: "",
+ },
+ ],
+ expectedError: 'Invalid content script id.',
+ },
+ {
+ title: "unknown script ID",
+ params: [
+ {
+ id: "unknown-id",
+ },
+ ],
+ expectedError: 'Content script with id "unknown-id" does not exist.',
+ },
+ {
+ title: "duplicate valid script IDs",
+ params: [
+ {
+ id: script.id,
+ },
+ {
+ id: script.id,
+ },
+ ],
+ expectedError: `Script ID "${script.id}" found more than once in 'scripts' array.`,
+ },
+ {
+ title: "empty matches",
+ params: [
+ {
+ id: script.id,
+ matches: [],
+ },
+ ],
+ expectedError: "matches must be specified.",
+ },
+ {
+ title: "one empty match",
+ params: [
+ {
+ id: script.id,
+ matches: [""],
+ },
+ ],
+ expectedError: "Invalid url pattern: ",
+ },
+ {
+ title: "invalid match",
+ params: [
+ {
+ id: script.id,
+ matches: ["not-a-pattern"],
+ },
+ ],
+ expectedError: "Invalid url pattern: not-a-pattern",
+ },
+ {
+ title: "invalid match and valid match",
+ params: [
+ {
+ id: script.id,
+ matches: ["*://mochi.test/*", "not-a-pattern"],
+ },
+ ],
+ expectedError: "Invalid url pattern: not-a-pattern",
+ },
+ {
+ title: "one empty value in excludeMatches",
+ params: [
+ {
+ id: script.id,
+ excludeMatches: [""],
+ },
+ ],
+ expectedError: "Invalid url pattern: ",
+ },
+ {
+ title: "invalid value in excludeMatches",
+ params: [
+ {
+ id: script.id,
+ excludeMatches: ["not-a-pattern"],
+ },
+ ],
+ expectedError: "Invalid url pattern: not-a-pattern",
+ },
+ {
+ title: "empty js",
+ params: [
+ {
+ id: script.id,
+ js: [],
+ },
+ ],
+ expectedError: "At least one js or css must be specified.",
+ },
+ {
+ title: "empty js and css",
+ params: [
+ {
+ id: script.id,
+ js: [],
+ css: [],
+ },
+ ],
+ expectedError: "At least one js or css must be specified.",
+ },
+ ];
+
+ // Register a valid script so that we can verify update params beyond
+ // script IDs.
+ await browser.scripting.registerContentScripts([script]);
+
+ for (const { title, params, expectedError } of TEST_CASES) {
+ await browser.test.assertRejects(
+ browser.scripting.updateContentScripts(params),
+ expectedError,
+ `${title} - got expected error`
+ );
+ }
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(1, scripts.length, "expected 1 registered script");
+ browser.test.assertEq(
+ JSON.stringify([
+ {
+ id: script.id,
+ allFrames: false,
+ matches: script.matches,
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: script.js,
+ },
+ ]),
+ JSON.stringify(scripts),
+ "expected script to not have been modified"
+ );
+
+ browser.test.sendMessage("background-done");
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitMessage("background-done");
+ await extension.unload();
+});
+
+add_task(async function test_updateContentScripts() {
+ let extension = makeExtension({
+ async background() {
+ const SCRIPT_ID = "script-to-update";
+
+ await browser.scripting.registerContentScripts([
+ {
+ id: SCRIPT_ID,
+ js: ["script-1.js"],
+ matches: ["*://test1.example.com/*"],
+ persistAcrossSessions: false,
+ },
+ ]);
+
+ browser.test.onMessage.addListener(async (msg, params) => {
+ switch (msg) {
+ case "updateContentScripts": {
+ const {
+ title,
+ updateContentScriptsParams,
+ expectedRegisteredContentScript
+ } = params;
+
+ let result = await browser.scripting.updateContentScripts([
+ updateContentScriptsParams,
+ ]);
+ browser.test.assertEq(
+ undefined,
+ result,
+ `${title} - expected no return value`
+ );
+
+ let scripts = await browser.scripting.getRegisteredContentScripts();
+ browser.test.assertEq(
+ JSON.stringify([expectedRegisteredContentScript]),
+ JSON.stringify(scripts),
+ `${title} - expected registered script`
+ );
+
+ browser.test.sendMessage(`${msg}-done`);
+ break;
+ }
+
+ default:
+ browser.test.fail(`invalid message received: ${msg}`);
+ }
+ });
+
+ browser.test.sendMessage("background-ready");
+ },
+ files: {
+ "script-1.js": () => {
+ browser.test.sendMessage(
+ `script-1 executed in ${location.pathname.split("/").pop()}`
+ );
+ },
+ "script-2.js": () => {
+ browser.test.sendMessage(
+ `script-2 executed in ${location.pathname.split("/").pop()}`
+ );
+ },
+ "script-3.js": () => {
+ browser.test.sendMessage(
+ `script-3 executed in ${location.pathname.split("/").pop()}`
+ );
+ },
+ "script-4.js": () => {
+ browser.test.sendMessage(
+ `script-4 executed in ${location.pathname.split("/").pop()}`
+ );
+ },
+ "style.css": "body { background-color: rgb(0, 255, 0); }",
+ "script-check-style.js": () => {
+ browser.test.assertEq(
+ "rgb(0, 255, 0)",
+ getComputedStyle(document.querySelector('body')).backgroundColor,
+ "expected background color"
+ );
+ browser.test.sendMessage(
+ `script-check-style executed in ${location.pathname.split("/").pop()}`
+ );
+ },
+ },
+ });
+
+ const SCRIPT_ID = "script-to-update";
+ const TEST_PAGE = "https://test1.example.com/tests/toolkit/components/extensions/test/mochitest/file_contains_iframe.html";
+
+ const runTestCase = async ({
+ title,
+ updateContentScriptsParams,
+ expectedRegisteredContentScript,
+ expectedMessages
+ }) => {
+ // Register content script and verify results.
+ extension.sendMessage("updateContentScripts", {
+ title,
+ updateContentScriptsParams,
+ expectedRegisteredContentScript,
+ });
+ await extension.awaitMessage("updateContentScripts-done");
+
+ let tab = await AppTestDelegate.openNewForegroundTab(
+ window,
+ TEST_PAGE,
+ true
+ );
+
+ await Promise.all(expectedMessages.map(msg => extension.awaitMessage(msg)));
+
+ await AppTestDelegate.removeTab(window, tab);
+ };
+
+ await extension.startup();
+ await extension.awaitMessage("background-ready");
+
+ // Load a page that will trigger the content script initially registered.
+ let tab = await AppTestDelegate.openNewForegroundTab(window, TEST_PAGE, true);
+ await extension.awaitMessage("script-1 executed in file_contains_iframe.html");
+ await AppTestDelegate.removeTab(window, tab);
+
+ // Now, let's update this content script a few times.
+ await runTestCase({
+ title: "update ID only",
+ updateContentScriptsParams: {
+ id: SCRIPT_ID,
+ },
+ expectedRegisteredContentScript: {
+ id: SCRIPT_ID,
+ allFrames: false,
+ matches: ["*://test1.example.com/*"],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-1.js"],
+ },
+ expectedMessages: ["script-1 executed in file_contains_iframe.html"],
+ });
+
+ await runTestCase({
+ title: "update js",
+ updateContentScriptsParams: {
+ id: SCRIPT_ID,
+ js: ["script-2.js"],
+ },
+ expectedRegisteredContentScript: {
+ id: SCRIPT_ID,
+ allFrames: false,
+ matches: ["*://test1.example.com/*"],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-2.js"],
+ },
+ expectedMessages: ["script-2 executed in file_contains_iframe.html"],
+ });
+
+ await runTestCase({
+ title: "update allFrames and matches",
+ updateContentScriptsParams: {
+ id: SCRIPT_ID,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ allFrames: true,
+ },
+ expectedRegisteredContentScript: {
+ id: SCRIPT_ID,
+ allFrames: true,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ js: ["script-2.js"],
+ },
+ expectedMessages: [
+ "script-2 executed in file_contains_iframe.html",
+ "script-2 executed in file_contains_img.html",
+ ],
+ });
+
+ await runTestCase({
+ title: "update excludeMatches and js",
+ updateContentScriptsParams: {
+ id: SCRIPT_ID,
+ js: ["script-3.js"],
+ excludeMatches: ["*://test1.example.com/*"],
+ allFrames: true,
+ },
+ expectedRegisteredContentScript: {
+ id: SCRIPT_ID,
+ allFrames: true,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ excludeMatches: ["*://test1.example.com/*"],
+ js: ["script-3.js"],
+ },
+ expectedMessages: [
+ "script-3 executed in file_contains_img.html",
+ ],
+ });
+
+ await runTestCase({
+ title: "update allFrames, excludeMatches, js and runAt",
+ updateContentScriptsParams: {
+ id: SCRIPT_ID,
+ allFrames: false,
+ excludeMatches: [],
+ js: ["script-4.js"],
+ runAt: "document_start",
+ },
+ expectedRegisteredContentScript: {
+ id: SCRIPT_ID,
+ allFrames: false,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ runAt: "document_start",
+ persistAcrossSessions: false,
+ js: ["script-4.js"],
+ },
+ expectedMessages: [
+ "script-4 executed in file_contains_iframe.html",
+ ],
+ });
+
+ await runTestCase({
+ title: "update allFrames, css, js and runAt",
+ updateContentScriptsParams: {
+ id: SCRIPT_ID,
+ allFrames: true,
+ css: ["style.css"],
+ js: ["script-check-style.js"],
+ runAt: "document_idle",
+ },
+ expectedRegisteredContentScript: {
+ id: SCRIPT_ID,
+ allFrames: true,
+ matches: [
+ "*://test1.example.com/*",
+ "*://example.org/*",
+ ],
+ runAt: "document_idle",
+ persistAcrossSessions: false,
+ css: ["style.css"],
+ js: ["script-check-style.js"],
+ },
+ expectedMessages: [
+ "script-check-style executed in file_contains_iframe.html",
+ "script-check-style executed in file_contains_img.html",
+ ],
+ });
+
+ await extension.unload();
+});
+
+</script>
+
+</body>
+</html>