Bug 1588801 - Add test coverage for JS files loaded from XPIs. r=mixedpuppy,robwu

JS files in packed webextensions (XPIs) are loaded via `nsJARChannel`,
which checks the content type of a file based on the extension by
calling `nsIMIMEService::GetTypeFromExtension()`.

We might not be able to guarantee the exact set of JS file extensions
because this method also asks the OS but it should still be reliable.
We allow `.js`, `.jsm` and `.mjs` (defined in `defaultMimeEntries` and
`extraMimeEntries`).

Differential Revision: https://phabricator.services.mozilla.com/D104450
This commit is contained in:
William Durand 2021-02-18 10:02:43 +00:00
parent da85975c5c
commit e0091f942e
4 changed files with 390 additions and 1 deletions

View File

@ -143,6 +143,7 @@ skip-if = os == 'android' # Bug 1615427
[test_ext_runtime_connect_twoway.html] [test_ext_runtime_connect_twoway.html]
[test_ext_runtime_connect2.html] [test_ext_runtime_connect2.html]
[test_ext_runtime_disconnect.html] [test_ext_runtime_disconnect.html]
[test_ext_script_filenames.html]
[test_ext_sendmessage_doublereply.html] [test_ext_sendmessage_doublereply.html]
[test_ext_sendmessage_frameId.html] [test_ext_sendmessage_frameId.html]
[test_ext_sendmessage_no_receiver.html] [test_ext_sendmessage_no_receiver.html]

View File

@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Script Filenames Test</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";
add_task(async function test_tabs_executeScript() {
let validFileName = "script.js";
let invalidFileName = "script.xyz";
async function background() {
await browser.tabs.executeScript({ file: "script.js" });
await browser.test.assertRejects(
browser.tabs.executeScript({ file: "script.xyz" }),
Error,
"invalid filename does not execute"
);
browser.test.notifyPass("execute-script");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["<all_urls>"],
},
background,
files: {
[validFileName]: function contentScript1() {
browser.test.sendMessage("content-script-loaded");
},
[invalidFileName]: function contentScript2() {
browser.test.fail("this script should not be loaded");
},
},
});
let tab = await AppTestDelegate.openNewForegroundTab(
window,
"http://mochi.test:8888/",
true
);
await extension.startup();
await extension.awaitMessage("content-script-loaded");
await extension.awaitFinish("execute-script");
await extension.unload();
await AppTestDelegate.removeTab(window, tab);
});
</script>
</body>
</html>

View File

@ -0,0 +1,325 @@
"use strict";
const server = createHttpServer();
server.registerDirectory("/data/", do_get_file("data"));
const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
// This function represents a dummy content or background script that the test
// cases below should attempt to load but it shouldn't be loaded because we
// check the extensions of JavaScript files in `nsJARChannel`.
function scriptThatShouldNotBeLoaded() {
browser.test.fail("this should not be executed");
}
function scriptThatAlwaysRuns() {
browser.test.sendMessage("content-script-loaded");
}
// We use these variables in combination with `scriptThatAlwaysRuns()` to send a
// signal to the extension and avoid the page to be closed too soon.
const alwaysRunsFileName = "always_run.js";
const alwaysRunsContentScript = {
matches: ["<all_urls>"],
js: [alwaysRunsFileName],
run_at: "document_start",
};
add_task(async function test_content_script_filename_without_extension() {
// Filenames without any extension should not be loaded.
let invalidFileName = "content_script";
let extensionData = {
manifest: {
content_scripts: [
alwaysRunsContentScript,
{
matches: ["<all_urls>"],
js: [invalidFileName],
},
],
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
[alwaysRunsFileName]: scriptThatAlwaysRuns,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
let contentPage = await ExtensionTestUtils.loadContentPage(
`${BASE_URL}/file_sample.html`
);
await extension.awaitMessage("content-script-loaded");
await contentPage.close();
await extension.unload();
});
add_task(async function test_content_script_filename_with_invalid_extension() {
let validFileName = "content_script.js";
let invalidFileName = "content_script.xyz";
let extensionData = {
manifest: {
content_scripts: [
alwaysRunsContentScript,
{
matches: ["<all_urls>"],
js: [validFileName, invalidFileName],
},
],
},
files: {
// This makes sure that, when one of the content scripts fails to load,
// none of the content scripts are executed.
[validFileName]: scriptThatShouldNotBeLoaded,
[invalidFileName]: scriptThatShouldNotBeLoaded,
[alwaysRunsFileName]: scriptThatAlwaysRuns,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
let contentPage = await ExtensionTestUtils.loadContentPage(
`${BASE_URL}/file_sample.html`
);
await extension.awaitMessage("content-script-loaded");
await contentPage.close();
await extension.unload();
});
add_task(async function test_bg_script_injects_script_with_invalid_ext() {
function backgroundScript() {
browser.test.sendMessage("background-script-loaded");
}
let validFileName = "background.js";
let invalidFileName = "invalid_background.xyz";
let extensionData = {
background() {
const script = document.createElement("script");
script.src = "./invalid_background.xyz";
document.head.appendChild(script);
const validScript = document.createElement("script");
validScript.src = "./background.js";
document.head.appendChild(validScript);
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
[validFileName]: backgroundScript,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
await extension.awaitMessage("background-script-loaded");
await extension.unload();
});
add_task(async function test_background_scripts() {
function backgroundScript() {
browser.test.sendMessage("background-script-loaded");
}
let validFileName = "background.js";
let invalidFileName = "invalid_background.xyz";
let extensionData = {
manifest: {
background: {
scripts: [invalidFileName, validFileName],
},
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
[validFileName]: backgroundScript,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
await extension.awaitMessage("background-script-loaded");
await extension.unload();
});
add_task(async function test_background_page_injects_scripts_inline() {
function injectedBackgroundScript() {
browser.test.sendMessage("background-script-loaded");
}
let backgroundHtmlPage = "background_page.html";
let validFileName = "injected_background.js";
let invalidFileName = "invalid_background.xyz";
// This is needed to "easily" generate a valid CSP script hash because it
// generates a one-line JS payload. If the content below is modified, the
// hash has to be updated in `content_security_policy` too.
let inlineScript = [
'const script = document.createElement("script");',
'script.src = "./invalid_background.xyz";',
"document.head.appendChild(script);",
'const validScript = document.createElement("script");',
'validScript.src = "./injected_background.js";',
"document.head.appendChild(validScript);",
].join("");
let extensionData = {
manifest: {
background: { page: backgroundHtmlPage },
content_security_policy: [
"script-src",
"'self'",
"'sha256-2xldfE9/s8HWuAO69FOx5nKY4YDuAVQpUH2d7nX9KIM='",
";",
"object-src",
"'self'",
].join(" "),
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
[validFileName]: injectedBackgroundScript,
[backgroundHtmlPage]: `
<html>
<head>
<meta charset="utf-8"></head>
<script>${inlineScript}</script>
</head>
</html>`,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
await extension.awaitMessage("background-script-loaded");
await extension.unload();
});
add_task(async function test_background_page_injects_scripts() {
// This is the initial background script loaded in the HTML page.
function backgroundScript() {
const script = document.createElement("script");
script.src = "./invalid_background.xyz";
document.head.appendChild(script);
const validScript = document.createElement("script");
validScript.src = "./injected_background.js";
document.head.appendChild(validScript);
}
// This is the script injected by the script defined in `backgroundScript()`.
function injectedBackgroundScript() {
browser.test.sendMessage("background-script-loaded");
}
let backgroundHtmlPage = "background_page.html";
let validFileName = "injected_background.js";
let invalidFileName = "invalid_background.xyz";
let extensionData = {
manifest: {
background: { page: backgroundHtmlPage },
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
[validFileName]: injectedBackgroundScript,
[backgroundHtmlPage]: `
<html>
<head>
<meta charset="utf-8"></head>
<script src="./background.js"></script>
</head>
</html>`,
"background.js": backgroundScript,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
await extension.awaitMessage("background-script-loaded");
await extension.unload();
});
add_task(async function test_background_script_registers_content_script() {
let invalidFileName = "content_script";
let extensionData = {
manifest: {
permissions: ["<all_urls>"],
},
async background() {
await browser.contentScripts.register({
js: [{ file: "/content_script" }],
matches: ["<all_urls>"],
});
browser.test.sendMessage("background-script-loaded");
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
let contentPage = await ExtensionTestUtils.loadContentPage(
`${BASE_URL}/file_sample.html`
);
await extension.awaitMessage("background-script-loaded");
await contentPage.close();
await extension.unload();
});
add_task(async function test_web_accessible_resources() {
function contentScript() {
const script = document.createElement("script");
script.src = browser.runtime.getURL("content_script.css");
script.onerror = () => {
browser.test.sendMessage("second-content-script-loaded");
};
document.head.appendChild(script);
}
let contentScriptFileName = "content_script.js";
let invalidFileName = "content_script.css";
let extensionData = {
manifest: {
web_accessible_resources: [invalidFileName],
content_scripts: [
alwaysRunsContentScript,
{
matches: ["<all_urls>"],
js: [contentScriptFileName],
},
],
},
files: {
[invalidFileName]: scriptThatShouldNotBeLoaded,
[contentScriptFileName]: contentScript,
[alwaysRunsFileName]: scriptThatAlwaysRuns,
},
};
let extension = ExtensionTestUtils.loadExtension(extensionData);
await extension.startup();
let contentPage = await ExtensionTestUtils.loadContentPage(
`${BASE_URL}/file_sample.html`
);
await extension.awaitMessage("content-script-loaded");
await extension.awaitMessage("second-content-script-loaded");
await contentPage.close();
await extension.unload();
});

View File

@ -155,6 +155,7 @@ skip-if = ccov && os == 'linux' # bug 1607581
[test_ext_sandbox_var.js] [test_ext_sandbox_var.js]
[test_ext_schema.js] [test_ext_schema.js]
skip-if = os == "android" # Android: Bug 1680132 skip-if = os == "android" # Android: Bug 1680132
[test_ext_script_filenames.js]
[test_ext_shared_workers.js] [test_ext_shared_workers.js]
[test_ext_shutdown_cleanup.js] [test_ext_shutdown_cleanup.js]
[test_ext_simple.js] [test_ext_simple.js]