/* eslint-env webextensions */ "use strict"; const PRINT_POSTDATA = httpURL("print_postdata.sjs"); const FILE_DUMMY = fileURL("dummy_page.html"); const DATA_URL = "data:text/html,Hello%2C World!"; const DATA_STRING = "Hello, World!"; async function performLoad(browser, opts, action) { let loadedPromise = BrowserTestUtils.browserLoaded( browser, false, opts.url, opts.maybeErrorPage ); await action(); await loadedPromise; } const EXTENSION_DATA = { manifest: { name: "Simple extension test", version: "1.0", manifest_version: 2, description: "", permissions: ["proxy", "webRequest", "webRequestBlocking", ""], }, files: { "dummy.html": "webext dummy", "redirect.html": "webext redirect", }, extUrl: "", async background() { browser.test.log("background script running"); browser.webRequest.onAuthRequired.addListener( async details => { browser.test.log("webRequest onAuthRequired"); // A blocking request that returns a promise exercises a codepath that // sets the notificationCallbacks on the channel to a JS object that we // can't do directly QueryObject on with expected results. // This triggered a crash which was fixed in bug 1528188. return new Promise((resolve, reject) => { setTimeout(resolve, 0); }); }, { urls: ["*://*/*"] }, ["blocking"] ); browser.webRequest.onBeforeRequest.addListener( async details => { browser.test.log("webRequest onBeforeRequest"); let isRedirect = details.originUrl == browser.runtime.getURL("redirect.html") && details.url.endsWith("print_postdata.sjs"); let url = this.extUrl ? this.extUrl : details.url + "?redirected"; return isRedirect ? { redirectUrl: url } : {}; }, { urls: ["*://*/*"] }, ["blocking"] ); browser.test.onMessage.addListener(async ({ method, url }) => { if (method == "setRedirectUrl") { this.extUrl = url; } browser.test.sendMessage("done"); }); }, }; async function withExtensionDummy(callback) { let extension = ExtensionTestUtils.loadExtension(EXTENSION_DATA); await extension.startup(); let rv = await callback(`moz-extension://${extension.uuid}/`, extension); await extension.unload(); return rv; } async function postFrom(start, target) { return BrowserTestUtils.withNewTab( { gBrowser, url: start, }, async function (browser) { info("Test tab ready: postFrom " + start); // Create the form element in our loaded URI. await SpecialPowers.spawn(browser, [{ target }], function ({ target }) { content.document.body.innerHTML = `
`; }); // Perform a form POST submit load. info("Performing POST submission"); await performLoad( browser, { url(url) { let enable = url.startsWith(PRINT_POSTDATA) || url == target || url == DATA_URL; if (!enable) { info(`url ${url} is invalid to perform load`); } return enable; }, maybeErrorPage: true, }, async () => { await SpecialPowers.spawn(browser, [], () => { content.document.querySelector("#submit").click(); }); } ); // Check that the POST data was submitted. info("Fetching results"); return SpecialPowers.spawn(browser, [], () => { return { remoteType: Services.appinfo.remoteType, location: "" + content.location.href, body: content.document.body.textContent, }; }); } ); } async function loadAndGetProcessID(browser, target) { info(`Performing GET load: ${target}`); await performLoad( browser, { maybeErrorPage: true, }, () => { BrowserTestUtils.startLoadingURIString(browser, target); } ); info(`Navigated to: ${target}`); browser = gBrowser.selectedBrowser; let processID = await SpecialPowers.spawn(browser, [], () => { return Services.appinfo.processID; }); return processID; } async function testLoadAndRedirect( target, expectedProcessSwitch, testRedirect ) { let start = httpURL(`dummy_page.html`); return BrowserTestUtils.withNewTab( { gBrowser, url: start, }, async function (_browser) { info("Test tab ready: getFrom " + start); let browser = gBrowser.selectedBrowser; let firstProcessID = await SpecialPowers.spawn(browser, [], () => { return Services.appinfo.processID; }); info(`firstProcessID: ${firstProcessID}`); let secondProcessID = await loadAndGetProcessID(browser, target); info(`secondProcessID: ${secondProcessID}`); Assert.equal(firstProcessID != secondProcessID, expectedProcessSwitch); if (!testRedirect) { return; } let thirdProcessID = await loadAndGetProcessID(browser, add307(target)); info(`thirdProcessID: ${thirdProcessID}`); Assert.equal(firstProcessID != thirdProcessID, expectedProcessSwitch); Assert.ok(secondProcessID == thirdProcessID); } ); } add_task(async function test_enabled() { // Force only one webIsolated content process to ensure same-origin loads // always end in the same process. await SpecialPowers.pushPrefEnv({ set: [["dom.ipc.processCount.webIsolated", 1]], }); // URIs should correctly switch processes & the POST // should succeed. info("ENABLED -- FILE -- raw URI load"); let resp = await postFrom(FILE_DUMMY, PRINT_POSTDATA); ok(E10SUtils.isWebRemoteType(resp.remoteType), "process switch"); is(resp.location, PRINT_POSTDATA, "correct location"); is(resp.body, "initialRemoteType=file", "correct POST body"); info("ENABLED -- FILE -- 307-redirect URI load"); let resp307 = await postFrom(FILE_DUMMY, add307(PRINT_POSTDATA)); ok(E10SUtils.isWebRemoteType(resp307.remoteType), "process switch"); is(resp307.location, PRINT_POSTDATA, "correct location"); is(resp307.body, "initialRemoteType=file", "correct POST body"); // Same with extensions await withExtensionDummy(async extOrigin => { info("ENABLED -- EXTENSION -- raw URI load"); let respExt = await postFrom(extOrigin + "dummy.html", PRINT_POSTDATA); ok(E10SUtils.isWebRemoteType(respExt.remoteType), "process switch"); is(respExt.location, PRINT_POSTDATA, "correct location"); is(respExt.body, "initialRemoteType=extension", "correct POST body"); info("ENABLED -- EXTENSION -- extension-redirect URI load"); let respExtRedirect = await postFrom( extOrigin + "redirect.html", PRINT_POSTDATA ); ok(E10SUtils.isWebRemoteType(respExtRedirect.remoteType), "process switch"); is( respExtRedirect.location, PRINT_POSTDATA + "?redirected", "correct location" ); is( respExtRedirect.body, "initialRemoteType=extension?redirected", "correct POST body" ); info("ENABLED -- EXTENSION -- 307-redirect URI load"); let respExt307 = await postFrom( extOrigin + "dummy.html", add307(PRINT_POSTDATA) ); ok(E10SUtils.isWebRemoteType(respExt307.remoteType), "process switch"); is(respExt307.location, PRINT_POSTDATA, "correct location"); is(respExt307.body, "initialRemoteType=extension", "correct POST body"); }); }); async function sendMessage(ext, method, url) { ext.sendMessage({ method, url }); await ext.awaitMessage("done"); } // TODO: Currently no test framework for ftp://. add_task(async function test_protocol() { // TODO: Processes should be switched due to navigation of different origins. await testLoadAndRedirect("data:,foo", false, true); // Redirecting to file:// is not allowed. await testLoadAndRedirect(FILE_DUMMY, true, false); await withExtensionDummy(async (extOrigin, extension) => { await sendMessage(extension, "setRedirectUrl", DATA_URL); let respExtRedirect = await postFrom( extOrigin + "redirect.html", PRINT_POSTDATA ); ok(E10SUtils.isWebRemoteType(respExtRedirect.remoteType), "process switch"); is(respExtRedirect.location, DATA_URL, "correct location"); is(respExtRedirect.body, DATA_STRING, "correct POST body"); }); });