"use strict"; const { TestUtils } = ChromeUtils.importESModule( "resource://testing-common/TestUtils.sys.mjs" ); const { XPCShellContentUtils } = ChromeUtils.importESModule( "resource://testing-common/XPCShellContentUtils.sys.mjs" ); const { ExtensionTestUtils } = ChromeUtils.import( "resource://testing-common/ExtensionXPCShellUtils.jsm" ); XPCShellContentUtils.init(this); ExtensionTestUtils.init(this); const server = XPCShellContentUtils.createHttpServer({ hosts: ["example.com", "example.org"], }); server.registerPathHandler("/static_frames", (request, response) => { response.setHeader("Content-Type", "text/html"); response.write(` `); }); server.registerPathHandler("/frame", (request, response) => { response.setHeader("Content-Type", "text/html"); response.write(`

HTTP Subframe

`); }); server.registerPathHandler("/redirect", (request, response) => { let redirect; if (request.queryString == "com") { redirect = "http://example.com/frame"; } else if (request.queryString == "org") { redirect = "http://example.org/frame"; } else { response.setStatusLine(request.httpVersion, 404, "Not found"); return; } response.setStatusLine(request.httpVersion, 302, "Found"); response.setHeader("Location", redirect); }); server.registerPathHandler("/client_replace", (request, response) => { let redirect; if (request.queryString == "blank") { redirect = "about:blank"; } else if (request.queryString == "data") { redirect = "data:text/html,

Data Subframe

"; } else { response.setStatusLine(request.httpVersion, 404, "Not found"); return; } response.setHeader("Content-Type", "text/html"); response.write(` `); }); add_task(async function sandboxed_precursor() { // Bug 1725345: Make XPCShellContentUtils.createHttpServer support https Services.prefs.setBoolPref("dom.security.https_first", false); let extension = await ExtensionTestUtils.loadExtension({ manifest: { permissions: ["webRequest", "webRequestBlocking", ""], }, background() { // eslint-disable-next-line no-undef browser.webRequest.onBeforeRequest.addListener( details => { let url = new URL(details.url); if (!url.pathname.includes("ext_redirect")) { return {}; } let redirectUrl; if (url.search == "?com") { redirectUrl = "http://example.com/frame"; } else if (url.search == "?org") { redirectUrl = "http://example.org/frame"; } else if (url.search == "?data") { redirectUrl = "data:text/html,

Data Subframe

"; } return { redirectUrl }; }, { urls: [""] }, ["blocking"] ); }, }); await extension.startup(); registerCleanupFunction(async function() { await extension.unload(); }); for (let userContextId of [undefined, 1]) { let comURI = Services.io.newURI("http://example.com"); let comPrin = Services.scriptSecurityManager.createContentPrincipal( comURI, { userContextId } ); let orgURI = Services.io.newURI("http://example.org"); let orgPrin = Services.scriptSecurityManager.createContentPrincipal( orgURI, { userContextId } ); let page = await XPCShellContentUtils.loadContentPage( "http://example.com/static_frames", { remote: true, remoteSubframes: true, userContextId, } ); let bc = page.browsingContext; ok( bc.currentWindowGlobal.documentPrincipal.equals(comPrin), "toplevel principal matches" ); // XXX: This is sketchy as heck, but it's also the easiest way to wait for // the `window.location.replace` loads to finish. await TestUtils.waitForCondition( () => bc.children.every( child => !child.currentWindowGlobal.documentURI.spec.includes( "/client_replace" ) ), "wait for every client_replace global to be replaced" ); let principals = {}; for (let child of bc.children) { notEqual(child.name, "", "child frames must have names"); ok(!(child.name in principals), "duplicate child frame name"); principals[child.name] = child.currentWindowGlobal.documentPrincipal; } function principal_is(name, expected) { let principal = principals[name]; info(`${name} = ${principal.origin}`); ok(principal.equals(expected), `${name} is correct`); } function precursor_is(name, precursor) { let principal = principals[name]; info(`${name} = ${principal.origin}`); ok(principal.isNullPrincipal, `${name} is null`); ok( principal.precursorPrincipal.equals(precursor), `${name} has the correct precursor` ); } // Basic loads should have the principals or precursor principals for the // document being loaded. principal_is("same_origin", comPrin); precursor_is("same_origin_sandbox", comPrin); principal_is("cross_origin", orgPrin); precursor_is("cross_origin_sandbox", orgPrin); // Loads of a data: URI should complete with a sandboxed principal based on // the principal which tried to perform the load. precursor_is("data_uri", comPrin); precursor_is("data_uri_sandbox", comPrin); // Loads which inherit principals, such as srcdoc an about:blank loads, // should also inherit sandboxed precursor principals. principal_is("srcdoc", comPrin); precursor_is("srcdoc_sandbox", comPrin); principal_is("blank", comPrin); precursor_is("blank_sandbox", comPrin); // Redirects shouldn't interfere with the final principal, and it should be // based only on the final URI. principal_is("redirect_com", comPrin); precursor_is("redirect_com_sandbox", comPrin); principal_is("redirect_org", orgPrin); precursor_is("redirect_org_sandbox", orgPrin); // Extension redirects should act like normal redirects, and still resolve // with the principal or sandboxed principal of the final URI. principal_is("ext_redirect_com_com", comPrin); precursor_is("ext_redirect_com_com_sandbox", comPrin); principal_is("ext_redirect_com_org", orgPrin); precursor_is("ext_redirect_com_org_sandbox", orgPrin); principal_is("ext_redirect_org_com", comPrin); precursor_is("ext_redirect_org_com_sandbox", comPrin); principal_is("ext_redirect_org_org", orgPrin); precursor_is("ext_redirect_org_org_sandbox", orgPrin); // When an extension redirects to a data: URI, we use the last non-data: URI // in the chain as the precursor principal. // FIXME: This should perhaps use the extension's principal instead? precursor_is("ext_redirect_com_data", comPrin); precursor_is("ext_redirect_com_data_sandbox", comPrin); precursor_is("ext_redirect_org_data", orgPrin); precursor_is("ext_redirect_org_data_sandbox", orgPrin); // Check that navigations triggred by script within the frames will have the // correct behaviour when navigating to blank and data URIs. principal_is("client_replace_org_blank", orgPrin); precursor_is("client_replace_org_blank_sandbox", orgPrin); precursor_is("client_replace_org_data", orgPrin); precursor_is("client_replace_org_data_sandbox", orgPrin); await page.close(); } Services.prefs.clearUserPref("dom.security.https_first"); });