summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window_access.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window_access.js')
-rw-r--r--devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window_access.js315
1 files changed, 315 insertions, 0 deletions
diff --git a/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window_access.js b/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window_access.js
new file mode 100644
index 0000000000..3b32bb0aaa
--- /dev/null
+++ b/devtools/shared/commands/inspected-window/tests/browser_webextension_inspected_window_access.js
@@ -0,0 +1,315 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function run_inspectedWindow_eval({ tab, codeToEval, extension }) {
+ const fakeExtCallerInfo = {
+ url: `moz-extension://${extension.uuid}/another/fake-caller-script.js`,
+ lineNumber: 1,
+ addonId: extension.id,
+ };
+ const commands = await CommandsFactory.forTab(tab, { isWebExtension: true });
+ await commands.targetCommand.startListening();
+ const result = await commands.inspectedWindowCommand.eval(
+ fakeExtCallerInfo,
+ codeToEval,
+ {}
+ );
+ await commands.destroy();
+ return result;
+}
+
+async function openAboutBlankTabWithExtensionOrigin(extension) {
+ const tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `moz-extension://${extension.uuid}/manifest.json`
+ );
+ const loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ await ContentTask.spawn(tab.linkedBrowser, null, () => {
+ // about:blank inherits the principal when opened from content.
+ content.wrappedJSObject.location.assign("about:blank");
+ });
+ await loaded;
+ // Sanity checks:
+ is(tab.linkedBrowser.currentURI.spec, "about:blank", "expected tab");
+ is(
+ tab.linkedBrowser.contentPrincipal.originNoSuffix,
+ `moz-extension://${extension.uuid}`,
+ "about:blank should be at the extension origin"
+ );
+ return tab;
+}
+
+async function checkEvalResult({
+ extension,
+ description,
+ url,
+ createTab = () => BrowserTestUtils.openNewForegroundTab(gBrowser, url),
+ expectedResult,
+}) {
+ const tab = await createTab();
+ is(tab.linkedBrowser.currentURI.spec, url, "Sanity check: tab URL");
+ const result = await run_inspectedWindow_eval({
+ tab,
+ codeToEval: "'code executed at ' + location.href",
+ extension,
+ });
+ BrowserTestUtils.removeTab(tab);
+ SimpleTest.isDeeply(
+ result,
+ expectedResult,
+ `eval result for devtools.inspectedWindow.eval at ${url} (${description})`
+ );
+}
+
+async function checkEvalAllowed({ extension, description, url, createTab }) {
+ info(`checkEvalAllowed: ${description} (at URL: ${url})`);
+ await checkEvalResult({
+ extension,
+ description,
+ url,
+ createTab,
+ expectedResult: { value: `code executed at ${url}` },
+ });
+}
+async function checkEvalDenied({ extension, description, url, createTab }) {
+ info(`checkEvalDenied: ${description} (at URL: ${url})`);
+ await checkEvalResult({
+ extension,
+ description,
+ url,
+ createTab,
+ expectedResult: {
+ exceptionInfo: {
+ isError: true,
+ code: "E_PROTOCOLERROR",
+ details: [
+ "This extension is not allowed on the current inspected window origin",
+ ],
+ description: "Inspector protocol error: %s",
+ },
+ },
+ });
+}
+
+add_task(async function test_eval_at_http() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_first", false]],
+ });
+
+ // eslint-disable-next-line @microsoft/sdl/no-insecure-url
+ const httpUrl = "http://example.com/";
+
+ // When running with --use-http3-server, http:-URLs cannot be loaded.
+ try {
+ await fetch(httpUrl);
+ } catch {
+ info("Skipping test_eval_at_http because http:-URL cannot be loaded");
+ return;
+ }
+
+ const extension = ExtensionTestUtils.loadExtension({});
+ await extension.startup();
+
+ await checkEvalAllowed({
+ extension,
+ description: "http:-URL",
+ url: httpUrl,
+ });
+ await extension.unload();
+
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function test_eval_at_https() {
+ const extension = ExtensionTestUtils.loadExtension({});
+ await extension.startup();
+
+ const privilegedExtension = ExtensionTestUtils.loadExtension({
+ isPrivileged: true,
+ });
+ await privilegedExtension.startup();
+
+ await checkEvalAllowed({
+ extension,
+ description: "https:-URL",
+ url: "https://example.com/",
+ });
+
+ await checkEvalDenied({
+ extension,
+ description: "a restricted domain",
+ // Domain in extensions.webextensions.restrictedDomains by browser.toml.
+ url: "https://test2.example.com/",
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.quarantinedDomains.list", "example.com"]],
+ });
+
+ await checkEvalDenied({
+ extension,
+ description: "a quarantined domain",
+ url: "https://example.com/",
+ });
+
+ await checkEvalAllowed({
+ extension: privilegedExtension,
+ description: "a quarantined domain",
+ url: "https://example.com/",
+ });
+
+ await SpecialPowers.popPrefEnv();
+
+ await extension.unload();
+ await privilegedExtension.unload();
+});
+
+add_task(async function test_eval_at_sandboxed_page() {
+ const extension = ExtensionTestUtils.loadExtension({});
+ await extension.startup();
+
+ await checkEvalAllowed({
+ extension,
+ description: "page with CSP sandbox",
+ url: "https://example.com/document-builder.sjs?headers=Content-Security-Policy:sandbox&html=x",
+ });
+ await checkEvalDenied({
+ extension,
+ description: "restricted domain with CSP sandbox",
+ url: "https://test2.example.com/document-builder.sjs?headers=Content-Security-Policy:sandbox&html=x",
+ });
+
+ await extension.unload();
+});
+
+add_task(async function test_eval_at_own_extension_origin_allowed() {
+ const extension = ExtensionTestUtils.loadExtension({
+ background() {
+ // eslint-disable-next-line no-undef
+ browser.test.sendMessage(
+ "blob_url",
+ URL.createObjectURL(new Blob(["blob: here", { type: "text/html" }]))
+ );
+ },
+ files: {
+ "mozext.html": `<!DOCTYPE html>moz-extension: here`,
+ },
+ });
+ await extension.startup();
+ const blobUrl = await extension.awaitMessage("blob_url");
+
+ await checkEvalAllowed({
+ extension,
+ description: "moz-extension:-URL from own extension",
+ url: `moz-extension://${extension.uuid}/mozext.html`,
+ });
+ await checkEvalAllowed({
+ extension,
+ description: "blob:-URL from own extension",
+ url: blobUrl,
+ });
+ await checkEvalAllowed({
+ extension,
+ description: "about:blank with origin from own extension",
+ url: "about:blank",
+ createTab: () => openAboutBlankTabWithExtensionOrigin(extension),
+ });
+
+ await extension.unload();
+});
+
+add_task(async function test_eval_at_other_extension_denied() {
+ // The extension for which we simulate devtools_page, chosen as caller of
+ // devtools.inspectedWindow.eval API calls.
+ const extension = ExtensionTestUtils.loadExtension({});
+ await extension.startup();
+
+ // The other extension, that |extension| should not be able to access:
+ const otherExt = ExtensionTestUtils.loadExtension({
+ background() {
+ // eslint-disable-next-line no-undef
+ browser.test.sendMessage(
+ "blob_url",
+ URL.createObjectURL(new Blob(["blob: here", { type: "text/html" }]))
+ );
+ },
+ files: {
+ "mozext.html": `<!DOCTYPE html>moz-extension: here`,
+ },
+ });
+ await otherExt.startup();
+ const otherExtBlobUrl = await otherExt.awaitMessage("blob_url");
+
+ await checkEvalDenied({
+ extension,
+ description: "moz-extension:-URL from another extension",
+ url: `moz-extension://${otherExt.uuid}/mozext.html`,
+ });
+ await checkEvalDenied({
+ extension,
+ description: "blob:-URL from another extension",
+ url: otherExtBlobUrl,
+ });
+ await checkEvalDenied({
+ extension,
+ description: "about:blank with origin from another extension",
+ url: "about:blank",
+ createTab: () => openAboutBlankTabWithExtensionOrigin(otherExt),
+ });
+
+ await otherExt.unload();
+ await extension.unload();
+});
+
+add_task(async function test_eval_at_about() {
+ const extension = ExtensionTestUtils.loadExtension({});
+ await extension.startup();
+ await checkEvalAllowed({
+ extension,
+ description: "about:blank (null principal)",
+ url: "about:blank",
+ });
+ await checkEvalDenied({
+ extension,
+ description: "about:addons (system principal)",
+ url: "about:addons",
+ });
+ await checkEvalDenied({
+ extension,
+ description: "about:robots (about page)",
+ url: "about:robots",
+ });
+ await extension.unload();
+});
+
+add_task(async function test_eval_at_file() {
+ // FYI: There is also an equivalent test case with a full end-to-end test at:
+ // browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_file.js
+
+ const extension = ExtensionTestUtils.loadExtension({});
+ await extension.startup();
+
+ // A dummy file URL that can be loaded in a tab.
+ const fileUrl =
+ "file://" +
+ getTestFilePath("browser_webextension_inspected_window_access.js");
+
+ // checkEvalAllowed test helper cannot be used, because the file:-URL may
+ // redirect elsewhere, so the comparison with the full URL fails.
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, fileUrl);
+ const result = await run_inspectedWindow_eval({
+ tab,
+ codeToEval: "'code executed at ' + location.protocol",
+ extension,
+ });
+ BrowserTestUtils.removeTab(tab);
+ SimpleTest.isDeeply(
+ result,
+ { value: "code executed at file:" },
+ `eval result for devtools.inspectedWindow.eval at ${fileUrl}`
+ );
+
+ await extension.unload();
+});