Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: http3 OR http2
- This test failed 2 times in the preceding 30 days. quicksearch this test
- Manifest: toolkit/components/extensions/test/mochitest/mochitest-remote.toml includes toolkit/components/extensions/test/mochitest/mochitest-common.toml
- Manifest: toolkit/components/extensions/test/mochitest/mochitest.toml includes toolkit/components/extensions/test/mochitest/mochitest-common.toml
- Manifest: toolkit/components/extensions/test/mochitest/mochitest-serviceworker.toml
<!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() {
// Among the longest running mochitests here, often times out on ASAN builds.
if (AppConstants.ASAN || AppConstants.TSAN) {
SimpleTest.requestLongerTimeout(5);
await SpecialPowers.pushPrefEnv({
set: [["extensions.background.idle.timeout", 300_000]],
});
}
});
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/*"],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
persistAcrossSessions: false,
js: ["script-1.js"],
},
{
id: "script-2",
allFrames: true,
matches: [
"*://test1.example.com/*",
"*://example.org/*",
],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
persistAcrossSessions: false,
js: ["script-2.js"],
},
{
id: "script-3",
allFrames: true,
matches: [
"*://test1.example.com/*",
"*://example.org/*",
],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
persistAcrossSessions: false,
excludeMatches: ["*://test1.example.com/*"],
js: ["script-3.js"],
},
{
id: "script-4",
allFrames: false,
matches: ["*://test1.example.com/*"],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
persistAcrossSessions: false,
js: ["script-4-1.js", "script-4-2.js"],
},
{
id: "script-5",
allFrames: false,
matches: ["*://test1.example.com/*"],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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,
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,
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,
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,
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,
true
);
await anotherExtension.awaitMessage("script-ran");
await extension.unload();
await anotherExtension.unload();
// We remove the tab after having unloaded the extensions to avoid failures
await AppTestDelegate.removeTab(window, tab);
});
add_task(async function test_registerContentScripts_on_about_blank_frames() {
let extension = makeExtension({
async background() {
browser.test.onMessage.addListener(async msg => {
browser.test.assertEq("update-fallback-false", msg, "expected msg");
await browser.scripting.updateContentScripts([
{
id: "a-script",
matchOriginAsFallback: false,
},
]);
browser.test.sendMessage("update-fallback-false:done");
});
await browser.scripting.registerContentScripts([
{
id: "a-script",
js: ["script.js"],
allFrames: true,
matchOriginAsFallback: true,
matches: ["*://test1.example.com/*"],
// ^ we would like to match on file_with_about_blank.html only,
// but that is not possible with matchOriginAsFallback.
persistAcrossSessions: false,
},
]);
browser.test.sendMessage("background-ready");
},
files: {
"script.js": () => {
browser.test.assertEq(
origin,
`Got expected origin at ${location.href}`
);
browser.test.sendMessage("got_url:" + location.href.split("/").pop());
},
},
});
await extension.startup();
await extension.awaitMessage("background-ready");
let tab = await AppTestDelegate.openNewForegroundTab(
window,
true
);
await extension.awaitMessage("got_url:file_with_about_blank.html");
await extension.awaitMessage("got_url:about:blank");
await extension.awaitMessage("got_url:about:srcdoc");
// Confirm that without matchOriginAsFallback, that we only match once.
info("Testing again, now without matchOriginAsFallback");
extension.sendMessage("update-fallback-false");
await extension.awaitMessage("update-fallback-false:done");
let tab2 = await AppTestDelegate.openNewForegroundTab(
window,
true
);
await extension.awaitMessage("got_url:file_with_about_blank.html");
await AppTestDelegate.removeTab(window, tab2);
await extension.unload();
// We remove the tab after having unloaded the extensions to avoid failures
await AppTestDelegate.removeTab(window, tab);
});
add_task(async function test_registerContentScripts_on_top_level_about_blank() {
// This is the default behavior, but fix pref value in case that is not the
// case. The flipped-pref case is tested later in this test.
let extension = makeExtension({
async background() {
await browser.scripting.registerContentScripts([
{
id: "script-that-should-not-run",
js: ["script_should_not_run.js"],
// "matches" does not match all URLs, so the script should not run on
// top-level about:blank. Otherwise extensions may unexpectedly see
// their scripts running in more contexts than expected, as seen in
matches: ["*://test1.example.com/does_not_match_any/*"],
// matchOriginAsFallback does not match if path is not "/*".
matchOriginAsFallback: true,
persistAcrossSessions: false,
},
{
id: "script-that-should-run",
js: ["script_all_urls.js"],
matches: ["*://*/*"],
matchOriginAsFallback: true,
// Undocumented feature: to only match on "about:blank", specify
// excludeMatches that excludes the URLs of interest.
excludeMatches: ["*://*/*"],
persistAcrossSessions: false,
},
]);
browser.test.sendMessage("background-ready");
},
files: {
"script_should_not_run.js": () => {
browser.test.fail("this script should not be executed.");
},
"script_all_urls.js": () => {
browser.test.assertEq("about:blank", document.URL, "Expected URL");
browser.test.assertEq("null", origin, "Expected null origin");
browser.test.sendMessage("script-ran");
},
},
});
await extension.startup();
await extension.awaitMessage("background-ready");
let tab = await AppTestDelegate.openNewForegroundTab(
window,
"about:blank",
// Set waitForLoad to false because the Android implementation of
// openNewForegroundTab ignores "about:blank" loads.
// We don't need to wait for a full load, because the "script-ran"
// message will be sent by the content script when it is ready.
false
);
await extension.awaitMessage("script-ran");
await extension.unload();
// We remove the tab after having unloaded the extensions to avoid failures
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/*"],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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,
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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/*"],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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/*"],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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/*",
],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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/*",
],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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",
world: "ISOLATED",
},
expectedRegisteredContentScript: {
id: SCRIPT_ID,
allFrames: false,
matches: [
"*://test1.example.com/*",
"*://example.org/*",
],
matchOriginAsFallback: false,
runAt: "document_start",
world: "ISOLATED",
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",
world: "ISOLATED",
},
expectedRegisteredContentScript: {
id: SCRIPT_ID,
allFrames: true,
matches: [
"*://test1.example.com/*",
"*://example.org/*",
],
matchOriginAsFallback: false,
runAt: "document_idle",
world: "ISOLATED",
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>