Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' && debug OR os == 'linux' && bits == 64
- 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
<!DOCTYPE HTML>
<html>
<head>
<title>Test the web_accessible_resources manifest directive</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
<script type="text/javascript" src="head.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="text/javascript">
"use strict";
// add_setup not available in mochitest
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({set: [["extensions.manifestV3.enabled", true]]});
})
let image = atob(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
"ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
);
const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0))
.buffer;
const ANDROID = navigator.userAgent.includes("Android");
async function testImageLoading(src, expectedAction) {
let imageLoadingPromise = new Promise((resolve) => {
let cleanupListeners;
let testImage = document.createElement("img");
// Set the src via wrappedJSObject so the load is triggered with the
// content page's principal rather than ours.
testImage.wrappedJSObject.setAttribute("src", src);
let loadListener = () => {
cleanupListeners();
resolve(expectedAction === "loaded");
};
let errorListener = () => {
cleanupListeners();
resolve(expectedAction === "blocked");
};
cleanupListeners = () => {
testImage.removeEventListener("load", loadListener);
testImage.removeEventListener("error", errorListener);
};
testImage.addEventListener("load", loadListener);
testImage.addEventListener("error", errorListener);
document.body.appendChild(testImage);
});
let success = await imageLoadingPromise;
browser.runtime.sendMessage({
name: "image-loading",
expectedAction,
success,
});
}
async function _test_web_accessible_resources({
manifest,
expectShouldLoadByDefault = true,
usePagePrincipal = false,
}) {
function background(shouldLoad, usePagePrincipal) {
let gotURL;
let tabId;
let expectBrowserAPI;
function loadFrame(url, sandbox = null, srcdoc = false) {
return new Promise(resolve => {
browser.tabs.sendMessage(
tabId,
["load-iframe", url, sandbox, srcdoc, usePagePrincipal],
reply => {
resolve(reply);
}
);
});
}
// shouldLoad will be true unless we expect all attempts to fail.
let urls = [
// { url, shouldLoad, sandbox, srcdoc }
{
url: browser.runtime.getURL("accessible.html"),
shouldLoad,
},
{
url: browser.runtime.getURL("accessible.html") + "?foo=bar",
shouldLoad,
},
{
url: browser.runtime.getURL("accessible.html") + "#!foo=bar",
shouldLoad,
},
{
url: browser.runtime.getURL("accessible.html"),
shouldLoad,
sandbox: "allow-scripts",
},
{
url: browser.runtime.getURL("accessible.html"),
shouldLoad,
sandbox: "allow-same-origin allow-scripts",
},
{
url: browser.runtime.getURL("accessible.html"),
shouldLoad,
sandbox: "allow-scripts",
srcdoc: true,
},
{
url: browser.runtime.getURL("inaccessible.html"),
shouldLoad: false,
},
{
url: browser.runtime.getURL("inaccessible.html"),
shouldLoad: false,
sandbox: "allow-same-origin allow-scripts",
},
{
url: browser.runtime.getURL("inaccessible.html"),
shouldLoad: false,
sandbox: "allow-same-origin allow-scripts",
srcdoc: true,
},
{
url: browser.runtime.getURL("wild1.html"),
shouldLoad,
},
{
url: browser.runtime.getURL("wild2.htm"),
shouldLoad: false,
},
];
async function runTests() {
for (let { url, shouldLoad, sandbox, srcdoc } of urls) {
// Sandboxed pages with an opaque origin do not get browser api.
expectBrowserAPI = !sandbox || sandbox.includes("allow-same-origin");
let success = await loadFrame(url, sandbox, srcdoc);
browser.test.assertEq(shouldLoad, success, "Load was successful");
if (shouldLoad && !srcdoc) {
browser.test.assertEq(url, gotURL, "Got expected url");
} else {
browser.test.assertEq(undefined, gotURL, "Got no url");
}
gotURL = undefined;
}
browser.test.notifyPass("web-accessible-resources");
}
browser.runtime.onMessage.addListener(
([msg, url, hasBrowserAPI], sender) => {
if (msg == "content-script-ready") {
tabId = sender.tab.id;
runTests();
} else if (msg == "page-script") {
browser.test.assertEq(
undefined,
gotURL,
"Should have gotten only one message"
);
browser.test.assertEq("string", typeof url, "URL should be a string");
browser.test.assertEq(
expectBrowserAPI,
hasBrowserAPI,
"has access to browser api"
);
gotURL = url;
}
}
);
browser.test.sendMessage("ready");
}
function contentScript() {
window.addEventListener("message", event => {
// bounce the postmessage to the background script
if (event.data[0] == "page-script") {
browser.runtime.sendMessage(event.data);
}
});
browser.runtime.onMessage.addListener(
([msg, url, sandboxed, srcdoc, usePagePrincipal], sender, respond) => {
if (msg == "load-iframe") {
// construct the frame using srcdoc if requested.
if (srcdoc) {
sandboxed = sandboxed !== null ? `sandbox="${sandboxed}"` : "";
let frameSrc = `<iframe ${sandboxed} src="${url}" onload="parent.postMessage(true, '*')" onerror="parent.postMessage(false, '*')">`;
let frame = document.createElement("iframe");
frame.setAttribute("srcdoc", frameSrc);
window.addEventListener("message", function listener(event) {
if (event.source === frame.contentWindow) {
window.removeEventListener("message", listener);
respond(event.data);
}
});
document.body.appendChild(frame);
return true;
}
let iframe = document.createElement("iframe");
if (sandboxed !== null) {
iframe.setAttribute("sandbox", sandboxed);
}
if (usePagePrincipal) {
// Test using the page principal
iframe.wrappedJSObject.src = url;
} else {
// Test using the expanded principal
iframe.src = url;
}
iframe.addEventListener("load", () => {
respond(true);
});
iframe.addEventListener("error", () => {
respond(false);
});
document.body.appendChild(iframe);
return true;
}
}
);
browser.runtime.sendMessage(["content-script-ready"]);
}
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
content_scripts: [
{
js: ["content_script.js"],
run_at: "document_idle",
},
],
...manifest,
},
background: `(${background})(${expectShouldLoadByDefault}, ${usePagePrincipal})`,
files: {
"content_script.js": contentScript,
"accessible.html": `<!DOCTYPE html><html><head>
<meta charset="utf-8">
<script src="pagescript.js"><\/script>
</head></html>`,
"inaccessible.html": `<!DOCTYPE html><html><head>
<meta charset="utf-8">
<script src="pagescript.js"><\/script>
</head></html>`,
"wild1.html": `<!DOCTYPE html><html><head>
<meta charset="utf-8">
<script src="pagescript.js"><\/script>
</head></html>`,
"wild2.htm": `<!DOCTYPE html><html><head>
<meta charset="utf-8">
<script src="pagescript.js"><\/script>
</head></html>`,
"pagescript.js":
// We postmessage so we can determine when browser is not available
'window.parent.postMessage(["page-script", location.href, typeof browser !== "undefined"], "*");',
},
});
await extension.startup();
await extension.awaitMessage("ready");
await extension.awaitFinish("web-accessible-resources");
win.close();
await extension.unload();
};
add_task(async function test_web_accessible_resources_v2() {
await SpecialPowers.pushPrefEnv({set: [["extensions.content_web_accessible.enabled", true]]});
consoleMonitor.start([
{message: /Content at https:\/\/example.com\/ may not load or link to.*inaccessible.html/},
]);
await _test_web_accessible_resources({
manifest: {
manifest_version: 2,
web_accessible_resources: ["/accessible.html", "wild*.html"],
}
});
await consoleMonitor.finished();
await SpecialPowers.popPrefEnv();
});
// Same test as above, but using only the content principal
add_task(async function test_web_accessible_resources_v2_content() {
await SpecialPowers.pushPrefEnv({set: [["extensions.content_web_accessible.enabled", true]]});
consoleMonitor.start([
{message: /Content at https:\/\/example.com\/ may not load or link to.*inaccessible.html/},
]);
await _test_web_accessible_resources({
manifest: {
manifest_version: 2,
web_accessible_resources: ["/accessible.html", "wild*.html"],
},
usePagePrincipal: true,
});
await consoleMonitor.finished();
await SpecialPowers.popPrefEnv();
});
add_task(async function test_web_accessible_resources_v3() {
// MV3 always requires this, pref off to ensure it works.
await SpecialPowers.pushPrefEnv({set: [["extensions.content_web_accessible.enabled", false]]});
consoleMonitor.start([
{message: /Content at https:\/\/example.com\/ may not load or link to.*inaccessible.html/},
]);
await _test_web_accessible_resources({
manifest: {
manifest_version: 3,
web_accessible_resources: [
{
resources: ["/accessible.html", "wild*.html"],
matches: ["*://example.com/*"]
},
],
host_permissions: ["*://example.com/*"],
granted_host_permissions: true,
}
});
await consoleMonitor.finished();
await SpecialPowers.popPrefEnv();
});
add_task(async function test_web_accessible_resources_v3_by_id() {
consoleMonitor.start([
{message: /Content at https:\/\/example.com\/ may not load or link to.*accessible.html/},
{message: /Content at https:\/\/example.com\/ may not load or link to.*inaccessible.html/},
]);
await _test_web_accessible_resources({
manifest: {
manifest_version: 3,
browser_specific_settings: {
gecko: {
id: "extension_wac@mochitest",
},
},
web_accessible_resources: [
{
resources: ["/accessible.html", "wild*.html"],
extension_ids: ["extension_wac@mochitest"]
},
],
host_permissions: ["*://example.com/*"],
granted_host_permissions: true,
},
expectShouldLoadByDefault: false,
});
await consoleMonitor.finished();
});
add_task(async function test_web_accessible_resources_mixed_content() {
function background() {
browser.runtime.onMessage.addListener(msg => {
if (msg.name === "image-loading") {
browser.test.assertTrue(msg.success, `Image was ${msg.expectedAction}`);
browser.test.sendMessage(`image-${msg.expectedAction}`);
} else {
browser.test.sendMessage(msg);
if (msg === "accessible-script-loaded") {
browser.test.notifyPass("mixed-test");
}
}
});
browser.test.sendMessage("background-ready");
}
async function content() {
await testImageLoading(
"blocked"
);
await testImageLoading(browser.runtime.getURL("image.png"), "loaded");
let testScriptElement = document.createElement("script");
// Set the src via wrappedJSObject so the load is triggered with the
// content page's principal rather than ours.
testScriptElement.wrappedJSObject.setAttribute(
"src",
browser.runtime.getURL("test_script.js")
);
document.head.appendChild(testScriptElement);
window.addEventListener("message", event => {
browser.runtime.sendMessage(event.data);
});
}
function testScript() {
window.postMessage("accessible-script-loaded", "*");
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
content_scripts: [
{
run_at: "document_end",
js: ["content_script_helper.js", "content_script.js"],
},
],
web_accessible_resources: ["image.png", "test_script.js"],
},
background,
files: {
"content_script_helper.js": `${testImageLoading}`,
"content_script.js": content,
"test_script.js": testScript,
"image.png": IMAGE_ARRAYBUFFER,
},
});
await SpecialPowers.pushPrefEnv({set: [
["security.mixed_content.upgrade_display_content", false],
["security.mixed_content.block_display_content", true],
]});
await Promise.all([
extension.startup(),
extension.awaitMessage("background-ready"),
]);
let win = window.open(
);
await Promise.all([
extension.awaitMessage("image-blocked"),
extension.awaitMessage("image-loaded"),
extension.awaitMessage("accessible-script-loaded"),
]);
await extension.awaitFinish("mixed-test");
win.close();
await extension.unload();
await SpecialPowers.popPrefEnv();
});
// test that MV2 extensions continue to open other MV2 extension pages
// when they are not listed in web_accessible_resources. This test also
// covers mobile/android tab creation.
add_task(async function test_web_accessible_resources_extensions_MV2() {
function background() {
let newtab;
let win;
let expectUrl;
browser.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (!expectUrl || tab.url != expectUrl || changeInfo.status !== "complete") {
return;
}
expectUrl = undefined;
browser.test.log(`onUpdated ${JSON.stringify(changeInfo)} ${tab.url}`);
browser.test.sendMessage("onUpdated", tab.url);
});
browser.test.onMessage.addListener(async (msg, url) => {
browser.test.log(`onMessage ${msg} ${url}`);
expectUrl = url;
if (msg == "create") {
newtab = await browser.tabs.create({ url });
browser.test.assertTrue(
newtab.id !== browser.tabs.TAB_ID_NONE,
"New tab was created."
);
} else if (msg == "update") {
await browser.tabs.update(newtab.id, { url });
} else if (msg == "remove") {
await browser.tabs.remove(newtab.id);
newtab = null;
browser.test.sendMessage("completed");
} else if (msg == "open-window") {
win = await browser.windows.create({ url });
} else if (msg == "close-window") {
await browser.windows.remove(win.id);
browser.test.sendMessage("completed");
win = null;
}
});
}
let extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_specific_settings: { gecko: { id: "this-mv2@mochitest" } },
},
background,
files: {
"page.html": `<!DOCTYPE html><html><head>
<meta charset="utf-8">
</head></html>`,
},
});
async function testTabsAction(ext, action, url) {
ext.sendMessage(action, url);
is(await ext.awaitMessage("onUpdated"), url, "extension url was loaded");
}
await extension.startup();
// Test opening its own pages
await testTabsAction(extension, "create", `${extensionUrl}?q=1`);
await testTabsAction(extension, "update", `${extensionUrl}?q=2`);
extension.sendMessage("remove");
await extension.awaitMessage("completed");
if (!ANDROID) {
await testTabsAction(extension, "open-window", `${extensionUrl}?q=3`);
extension.sendMessage("close-window");
await extension.awaitMessage("completed");
}
// Extension used to open the homepage in a new window.
let other = ExtensionTestUtils.loadExtension({
manifest: {
permissions: ["tabs", "<all_urls>"],
},
background,
});
await other.startup();
// Test opening another extensions pages
await testTabsAction(other, "create", `${extensionUrl}?q=4`);
await testTabsAction(other, "update", `${extensionUrl}?q=5`);
other.sendMessage("remove");
await other.awaitMessage("completed");
if (!ANDROID) {
await testTabsAction(other, "open-window", `${extensionUrl}?q=6`);
other.sendMessage("close-window");
await other.awaitMessage("completed");
}
await extension.unload();
await other.unload();
});
</script>
</body>
</html>