diff options
Diffstat (limited to 'toolkit/components/remotebrowserutils')
15 files changed, 1649 insertions, 0 deletions
diff --git a/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs b/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs new file mode 100644 index 0000000000..2d33706427 --- /dev/null +++ b/toolkit/components/remotebrowserutils/RemoteWebNavigation.sys.mjs @@ -0,0 +1,209 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 2 -*- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", +}); + +// This object implements the JS parts of nsIWebNavigation. +export class RemoteWebNavigation { + constructor(aBrowser) { + this._browser = aBrowser; + this._cancelContentJSEpoch = 1; + this._currentURI = null; + this._canGoBack = false; + this._canGoForward = false; + this.referringURI = null; + } + + swapBrowser(aBrowser) { + this._browser = aBrowser; + } + + maybeCancelContentJSExecution(aNavigationType, aOptions = {}) { + const epoch = this._cancelContentJSEpoch++; + this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution( + aNavigationType, + { ...aOptions, epoch } + ); + return epoch; + } + + get canGoBack() { + if (Services.appinfo.sessionHistoryInParent) { + return this._browser.browsingContext.sessionHistory?.index > 0; + } + return this._canGoBack; + } + + get canGoForward() { + if (Services.appinfo.sessionHistoryInParent) { + let sessionHistory = this._browser.browsingContext.sessionHistory; + return sessionHistory?.index < sessionHistory?.count - 1; + } + return this._canGoForward; + } + + goBack(requireUserInteraction = false) { + let cancelContentJSEpoch = this.maybeCancelContentJSExecution( + Ci.nsIRemoteTab.NAVIGATE_BACK + ); + this._browser.browsingContext.goBack( + cancelContentJSEpoch, + requireUserInteraction, + true + ); + } + goForward(requireUserInteraction = false) { + let cancelContentJSEpoch = this.maybeCancelContentJSExecution( + Ci.nsIRemoteTab.NAVIGATE_FORWARD + ); + this._browser.browsingContext.goForward( + cancelContentJSEpoch, + requireUserInteraction, + true + ); + } + gotoIndex(aIndex) { + let cancelContentJSEpoch = this.maybeCancelContentJSExecution( + Ci.nsIRemoteTab.NAVIGATE_INDEX, + { index: aIndex } + ); + this._browser.browsingContext.goToIndex(aIndex, cancelContentJSEpoch, true); + } + + _speculativeConnect(uri, loadURIOptions) { + try { + // Let's start a network connection before the content process asks. + // Note that we might have already set up the speculative connection in + // some cases, especially when the url is from location bar or its popup + // menu. + if (uri.schemeIs("http") || uri.schemeIs("https")) { + let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate( + this._browser + ); + let principal = loadURIOptions.triggeringPrincipal; + // We usually have a triggeringPrincipal assigned, but in case we + // don't have one or if it's a SystemPrincipal, let's create it with OA + // inferred from the current context. + if (!principal || principal.isSystemPrincipal) { + let attrs = { + userContextId: this._browser.getAttribute("usercontextid") || 0, + privateBrowsingId: isBrowserPrivate ? 1 : 0, + }; + principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + attrs + ); + } + Services.io.speculativeConnect(uri, principal, null, false); + } + } catch (ex) { + // Can't setup speculative connection for this uri for some + // reason, just ignore it. + } + } + + loadURI(uri, loadURIOptions) { + this._speculativeConnect(uri, loadURIOptions); + let cancelContentJSEpoch = this.maybeCancelContentJSExecution( + Ci.nsIRemoteTab.NAVIGATE_URL, + { uri } + ); + this._browser.browsingContext.loadURI(uri, { + ...loadURIOptions, + cancelContentJSEpoch, + }); + } + + fixupAndLoadURIString(uriString, loadURIOptions) { + let uri; + try { + let fixupFlags = Services.uriFixup.webNavigationFlagsToFixupFlags( + uriString, + loadURIOptions.loadFlags + ); + let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate( + this._browser + ); + if (isBrowserPrivate) { + fixupFlags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT; + } + + uri = Services.uriFixup.getFixupURIInfo( + uriString, + fixupFlags + ).preferredURI; + } catch (ex) { + // In rare cases `uriFixup` can throw. We ignore this here, but it's + // likely that the fixupAndLoadURIString call below will still throw, + // hopefully with more details. + } + if (uri) { + this._speculativeConnect(uri, loadURIOptions); + } + + let cancelContentJSEpoch = this.maybeCancelContentJSExecution( + Ci.nsIRemoteTab.NAVIGATE_URL, + { uri } + ); + // For now, continue to use fixup here, but note that ideally we should be + // doing fixup only once and reusing the URI we created above. Addressing + // this is bug 1815509. + this._browser.browsingContext.fixupAndLoadURIString(uriString, { + ...loadURIOptions, + cancelContentJSEpoch, + }); + } + + reload(aReloadFlags) { + this._browser.browsingContext.reload(aReloadFlags); + } + stop(aStopFlags) { + this._browser.browsingContext.stop(aStopFlags); + } + + get document() { + return this._browser.contentDocument; + } + + get currentURI() { + if (!this._currentURI) { + this._currentURI = Services.io.newURI("about:blank"); + } + return this._currentURI; + } + set currentURI(aURI) { + // Bug 1498600 verify usages of systemPrincipal here + let loadURIOptions = { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }; + this.loadURI(aURI.spec, loadURIOptions); + } + + // Bug 1233803 - accessing the sessionHistory of remote browsers should be + // done in content scripts. + get sessionHistory() { + throw new Components.Exception( + "Not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + set sessionHistory(aValue) { + throw new Components.Exception( + "Not implemented", + Cr.NS_ERROR_NOT_IMPLEMENTED + ); + } + + _sendMessage(aMessage, aData) { + try { + this._browser.sendMessageToActor(aMessage, aData, "WebNavigation"); + } catch (e) { + console.error(e); + } + } +} diff --git a/toolkit/components/remotebrowserutils/moz.build b/toolkit/components/remotebrowserutils/moz.build new file mode 100644 index 0000000000..0723c716ff --- /dev/null +++ b/toolkit/components/remotebrowserutils/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: Navigation") + +EXTRA_JS_MODULES += [ + "RemoteWebNavigation.sys.mjs", +] + +BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"] diff --git a/toolkit/components/remotebrowserutils/tests/browser/307redirect.sjs b/toolkit/components/remotebrowserutils/tests/browser/307redirect.sjs new file mode 100644 index 0000000000..1a28b8c5c3 --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/307redirect.sjs @@ -0,0 +1,6 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 307, "Temporary Redirect"); + let location = request.queryString; + response.setHeader("Location", location, false); + response.write("Hello world!"); +} diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser.ini b/toolkit/components/remotebrowserutils/tests/browser/browser.ini new file mode 100644 index 0000000000..a7d0a4786d --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser.ini @@ -0,0 +1,16 @@ +[DEFAULT] +support-files = + dummy_page.html + print_postdata.sjs + 307redirect.sjs + head.js + coop_header.sjs + file_postmsg_parent.html + +[browser_RemoteWebNavigation.js] +https_first_disabled = true +[browser_documentChannel.js] +[browser_httpCrossOriginOpenerPolicy.js] +[browser_httpToFileHistory.js] +[browser_oopProcessSwap.js] +[browser_externalLinkBlanksPage.js] diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser_RemoteWebNavigation.js b/toolkit/components/remotebrowserutils/tests/browser/browser_RemoteWebNavigation.js new file mode 100644 index 0000000000..22952e85f5 --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser_RemoteWebNavigation.js @@ -0,0 +1,238 @@ +const SYSTEMPRINCIPAL = Services.scriptSecurityManager.getSystemPrincipal(); +const DUMMY1 = + "http://test1.example.org/browser/toolkit/modules/tests/browser/dummy_page.html"; +const DUMMY2 = + "http://test2.example.org/browser/toolkit/modules/tests/browser/dummy_page.html"; +const LOAD_URI_OPTIONS = { triggeringPrincipal: SYSTEMPRINCIPAL }; + +function waitForLoad(uri) { + return BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri); +} + +function waitForPageShow(browser = gBrowser.selectedBrowser) { + return BrowserTestUtils.waitForContentEvent(browser, "pageshow", true); +} + +// Tests that loadURI accepts a referrer and it is included in the load. +add_task(async function test_referrer() { + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + let browser = gBrowser.selectedBrowser; + let ReferrerInfo = Components.Constructor( + "@mozilla.org/referrer-info;1", + "nsIReferrerInfo", + "init" + ); + + let loadURIOptionsWithReferrer = { + triggeringPrincipal: SYSTEMPRINCIPAL, + referrerInfo: new ReferrerInfo( + Ci.nsIReferrerInfo.EMPTY, + true, + Services.io.newURI(DUMMY2) + ), + }; + browser.webNavigation.loadURI( + Services.io.newURI(DUMMY1), + loadURIOptionsWithReferrer + ); + await waitForLoad(DUMMY1); + + await SpecialPowers.spawn( + browser, + [[DUMMY1, DUMMY2]], + function ([dummy1, dummy2]) { + function getExpectedReferrer(referrer) { + let defaultPolicy = Services.prefs.getIntPref( + "network.http.referer.defaultPolicy" + ); + ok( + [2, 3].indexOf(defaultPolicy) > -1, + "default referrer policy should be either strict-origin-when-cross-origin(2) or no-referrer-when-downgrade(3)" + ); + if (defaultPolicy == 2) { + return referrer.match(/https?:\/\/[^\/]+\/?/i)[0]; + } + return referrer; + } + + is(content.location.href, dummy1, "Should have loaded the right URL"); + is( + content.document.referrer, + getExpectedReferrer(dummy2), + "Should have the right referrer" + ); + } + ); + + gBrowser.removeCurrentTab(); +}); + +// Tests that remote access to webnavigation.sessionHistory works. +add_task(async function test_history() { + async function checkHistoryIndex(browser, n) { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + return SpecialPowers.spawn(browser, [n], function (n) { + let history = + docShell.browsingContext.childSessionHistory.legacySHistory; + + is(history.index, n, "Should be at the right place in history"); + }); + } + + let history = browser.browsingContext.sessionHistory; + is(history.index, n, "Should be at the right place in history"); + return null; + } + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + let browser = gBrowser.selectedBrowser; + + browser.webNavigation.loadURI(Services.io.newURI(DUMMY1), LOAD_URI_OPTIONS); + await waitForLoad(DUMMY1); + + browser.webNavigation.loadURI(Services.io.newURI(DUMMY2), LOAD_URI_OPTIONS); + await waitForLoad(DUMMY2); + + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + await SpecialPowers.spawn( + browser, + [[DUMMY1, DUMMY2]], + function ([dummy1, dummy2]) { + let history = + docShell.browsingContext.childSessionHistory.legacySHistory; + + is(history.count, 2, "Should be two history items"); + is(history.index, 1, "Should be at the right place in history"); + let entry = history.getEntryAtIndex(0); + is(entry.URI.spec, dummy1, "Should have the right history entry"); + entry = history.getEntryAtIndex(1); + is(entry.URI.spec, dummy2, "Should have the right history entry"); + } + ); + } else { + let history = browser.browsingContext.sessionHistory; + + is(history.count, 2, "Should be two history items"); + is(history.index, 1, "Should be at the right place in history"); + let entry = history.getEntryAtIndex(0); + is(entry.URI.spec, DUMMY1, "Should have the right history entry"); + entry = history.getEntryAtIndex(1); + is(entry.URI.spec, DUMMY2, "Should have the right history entry"); + } + + let promise = waitForPageShow(); + browser.webNavigation.goBack(); + await promise; + await checkHistoryIndex(browser, 0); + + promise = waitForPageShow(); + browser.webNavigation.goForward(); + await promise; + await checkHistoryIndex(browser, 1); + + promise = waitForPageShow(); + browser.webNavigation.gotoIndex(0); + await promise; + await checkHistoryIndex(browser, 0); + + gBrowser.removeCurrentTab(); +}); + +// Tests that load flags are passed through to the content process. +add_task(async function test_flags() { + async function checkHistory(browser, { count, index }) { + if (!SpecialPowers.Services.appinfo.sessionHistoryInParent) { + return SpecialPowers.spawn( + browser, + [[DUMMY2, count, index]], + function ([dummy2, count, index]) { + let history = + docShell.browsingContext.childSessionHistory.legacySHistory; + is(history.count, count, "Should be one history item"); + is(history.index, index, "Should be at the right place in history"); + let entry = history.getEntryAtIndex(index); + is(entry.URI.spec, dummy2, "Should have the right history entry"); + } + ); + } + + let history = browser.browsingContext.sessionHistory; + is(history.count, count, "Should be one history item"); + is(history.index, index, "Should be at the right place in history"); + let entry = history.getEntryAtIndex(index); + is(entry.URI.spec, DUMMY2, "Should have the right history entry"); + + return null; + } + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + let browser = gBrowser.selectedBrowser; + + browser.webNavigation.loadURI(Services.io.newURI(DUMMY1), LOAD_URI_OPTIONS); + await waitForLoad(DUMMY1); + let loadURIOptionsReplaceHistory = { + triggeringPrincipal: SYSTEMPRINCIPAL, + loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY, + }; + browser.webNavigation.loadURI( + Services.io.newURI(DUMMY2), + loadURIOptionsReplaceHistory + ); + await waitForLoad(DUMMY2); + await checkHistory(browser, { count: 1, index: 0 }); + let loadURIOptionsBypassHistory = { + triggeringPrincipal: SYSTEMPRINCIPAL, + loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, + }; + browser.webNavigation.loadURI( + Services.io.newURI(DUMMY1), + loadURIOptionsBypassHistory + ); + await waitForLoad(DUMMY1); + await checkHistory(browser, { count: 1, index: 0 }); + + gBrowser.removeCurrentTab(); +}); + +// Tests that attempts to use unsupported arguments throw an exception. +add_task(async function test_badarguments() { + if (!gMultiProcessBrowser) { + return; + } + + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser); + let browser = gBrowser.selectedBrowser; + + try { + let loadURIOptionsBadPostData = { + triggeringPrincipal: SYSTEMPRINCIPAL, + postData: {}, + }; + browser.webNavigation.loadURI( + Services.io.newURI(DUMMY1), + loadURIOptionsBadPostData + ); + ok( + false, + "Should have seen an exception from trying to pass some postdata" + ); + } catch (e) { + ok(true, "Should have seen an exception from trying to pass some postdata"); + } + + try { + let loadURIOptionsBadHeader = { + triggeringPrincipal: SYSTEMPRINCIPAL, + headers: {}, + }; + browser.webNavigation.loadURI( + Services.io.newURI(DUMMY1), + loadURIOptionsBadHeader + ); + ok(false, "Should have seen an exception from trying to pass some headers"); + } catch (e) { + ok(true, "Should have seen an exception from trying to pass some headers"); + } + + gBrowser.removeCurrentTab(); +}); diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser_documentChannel.js b/toolkit/components/remotebrowserutils/tests/browser/browser_documentChannel.js new file mode 100644 index 0000000000..362bc9ee6d --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser_documentChannel.js @@ -0,0 +1,280 @@ +/* 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", "<all_urls>"], + }, + + files: { + "dummy.html": "<html>webext dummy</html>", + "redirect.html": "<html>webext redirect</html>", + }, + + 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 }) { + // eslint-disable-next-line no-unsanitized/property + content.document.body.innerHTML = ` + <form method="post" action="${target}"> + <input type="text" name="initialRemoteType" value="${Services.appinfo.remoteType}"> + <input type="submit" id="submit"> + </form>`; + }); + + // 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.loadURIString(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"); + }); +}); diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser_externalLinkBlanksPage.js b/toolkit/components/remotebrowserutils/tests/browser/browser_externalLinkBlanksPage.js new file mode 100644 index 0000000000..3cdfed48cc --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser_externalLinkBlanksPage.js @@ -0,0 +1,79 @@ +/* + * Test that following a link with a scheme that opens externally (like + * irc:) does not blank the page (Bug 1630757). + */ + +const { HandlerServiceTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/HandlerServiceTestUtils.sys.mjs" +); + +let gHandlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService( + Ci.nsIHandlerService +); + +let Pages = [httpURL("dummy_page.html"), fileURL("dummy_page.html")]; + +/** + * Creates dummy protocol handler + */ +function initTestHandlers() { + let handlerInfo = + HandlerServiceTestUtils.getBlankHandlerInfo("test-proto://"); + + let appHandler = Cc[ + "@mozilla.org/uriloader/local-handler-app;1" + ].createInstance(Ci.nsILocalHandlerApp); + // This is a dir and not executable, but that's enough for here. + appHandler.executable = Services.dirsvc.get("XCurProcD", Ci.nsIFile); + handlerInfo.possibleApplicationHandlers.appendElement(appHandler); + handlerInfo.preferredApplicationHandler = appHandler; + handlerInfo.preferredAction = handlerInfo.useHelperApp; + handlerInfo.alwaysAskBeforeHandling = false; + gHandlerService.store(handlerInfo); + + registerCleanupFunction(() => { + gHandlerService.remove(handlerInfo); + }); +} + +async function runTest() { + initTestHandlers(); + + for (let page of Pages) { + await BrowserTestUtils.withNewTab(page, async function (aBrowser) { + await SpecialPowers.spawn(aBrowser, [], async () => { + let h = content.document.createElement("h1"); + ok(h); + h.innerHTML = "My heading"; + h.id = "my-heading"; + content.document.body.append(h); + is(content.document.getElementById("my-heading"), h, "h exists"); + + let a = content.document.createElement("a"); + ok(a); + a.innerHTML = "my link"; + a.id = "my-link"; + content.document.body.append(a); + }); + + await SpecialPowers.spawn(aBrowser, [], async () => { + let url = "test-proto://some-thing"; + + let a = content.document.getElementById("my-link"); + ok(a); + a.href = url; + a.click(); + }); + + await SpecialPowers.spawn(aBrowser, [], async () => { + ok( + content.document.getElementById("my-heading"), + "Page contents not erased" + ); + }); + }); + } + await SpecialPowers.popPrefEnv(); +} + +add_task(runTest); diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser_httpCrossOriginOpenerPolicy.js b/toolkit/components/remotebrowserutils/tests/browser/browser_httpCrossOriginOpenerPolicy.js new file mode 100644 index 0000000000..a99db3b289 --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser_httpCrossOriginOpenerPolicy.js @@ -0,0 +1,410 @@ +"use strict"; + +const COOP_PREF = "browser.tabs.remote.useCrossOriginOpenerPolicy"; + +async function setPref() { + await SpecialPowers.pushPrefEnv({ + set: [[COOP_PREF, true]], + }); +} + +async function unsetPref() { + await SpecialPowers.pushPrefEnv({ + set: [[COOP_PREF, false]], + }); +} + +function httpURL(filename, host = "https://example.com") { + let root = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + host + ); + return root + filename; +} + +async function performLoad(browser, opts, action) { + let loadedPromise = BrowserTestUtils.browserStopped( + browser, + opts.url, + opts.maybeErrorPage + ); + await action(); + await loadedPromise; +} + +async function test_coop( + start, + target, + expectedProcessSwitch, + startRemoteTypeCheck, + targetRemoteTypeCheck +) { + return BrowserTestUtils.withNewTab( + { + gBrowser, + url: start, + waitForStateStop: true, + }, + async function (_browser) { + info(`test_coop: Test tab ready: ${start}`); + + let browser = gBrowser.selectedBrowser; + let firstRemoteType = browser.remoteType; + let firstBC = browser.browsingContext; + + info(`firstBC: ${firstBC.id} remoteType: ${firstRemoteType}`); + + if (startRemoteTypeCheck) { + startRemoteTypeCheck(firstRemoteType); + } + + await performLoad( + browser, + { + url: target, + maybeErrorPage: false, + }, + async () => BrowserTestUtils.loadURIString(browser, target) + ); + + info(`Navigated to: ${target}`); + browser = gBrowser.selectedBrowser; + let secondRemoteType = browser.remoteType; + let secondBC = browser.browsingContext; + + info(`secondBC: ${secondBC.id} remoteType: ${secondRemoteType}`); + if (targetRemoteTypeCheck) { + targetRemoteTypeCheck(secondRemoteType); + } + if (expectedProcessSwitch) { + Assert.notEqual(firstBC.id, secondBC.id, `from: ${start} to ${target}`); + } else { + Assert.equal(firstBC.id, secondBC.id, `from: ${start} to ${target}`); + } + } + ); +} + +function waitForDownloadWindow() { + return new Promise(resolve => { + var listener = { + onOpenWindow: aXULWindow => { + info("Download window shown..."); + Services.wm.removeListener(listener); + + function downloadOnLoad() { + domwindow.removeEventListener("load", downloadOnLoad, true); + + is( + domwindow.document.location.href, + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + "Download page appeared" + ); + resolve(domwindow); + } + + var domwindow = aXULWindow.docShell.domWindow; + domwindow.addEventListener("load", downloadOnLoad, true); + }, + onCloseWindow: aXULWindow => {}, + }; + + Services.wm.addListener(listener); + }); +} + +async function waitForDownloadUI() { + return BrowserTestUtils.waitForEvent(DownloadsPanel.panel, "popupshown"); +} + +async function cleanupDownloads(downloadList) { + info("cleaning up downloads"); + let [download] = await downloadList.getAll(); + await downloadList.remove(download); + await download.finalize(true); + + try { + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } catch (error) { + info("The file " + download.target.path + " is not removed, " + error); + } + + if (DownloadsPanel.panel.state !== "closed") { + let hiddenPromise = BrowserTestUtils.waitForEvent( + DownloadsPanel.panel, + "popuphidden" + ); + DownloadsPanel.hidePanel(); + await hiddenPromise; + } + is( + DownloadsPanel.panel.state, + "closed", + "Check that the download panel is closed" + ); +} + +async function test_download_from(initCoop, downloadCoop) { + return BrowserTestUtils.withNewTab("about:blank", async function (_browser) { + info(`test_download: Test tab ready`); + + let start = httpURL( + "coop_header.sjs?downloadPage&coop=" + initCoop, + "https://example.com" + ); + await performLoad( + _browser, + { + url: start, + maybeErrorPage: false, + }, + async () => { + info(`test_download: Loading download page ${start}`); + return BrowserTestUtils.loadURIString(_browser, start); + } + ); + + info(`test_download: Download page ready ${start}`); + info(`Downloading ${downloadCoop}`); + + let expectDialog = Services.prefs.getBoolPref( + "browser.download.always_ask_before_handling_new_types", + false + ); + let resultPromise = expectDialog + ? waitForDownloadWindow() + : waitForDownloadUI(); + let browser = gBrowser.selectedBrowser; + SpecialPowers.spawn(browser, [downloadCoop], downloadCoop => { + content.document.getElementById(downloadCoop).click(); + }); + + // if the download page doesn't appear, the promise leads a timeout. + if (expectDialog) { + let win = await resultPromise; + await BrowserTestUtils.closeWindow(win); + } else { + // verify link target will get automatically downloaded + await resultPromise; + let downloadList = await Downloads.getList(Downloads.PUBLIC); + is((await downloadList.getAll()).length, 1, "Target was downloaded"); + await cleanupDownloads(downloadList); + is((await downloadList.getAll()).length, 0, "Downloads were cleaned up"); + } + }); +} + +// Check that multiple navigations of the same tab will only switch processes +// when it's expected. +add_task(async function test_multiple_nav_process_switches() { + await setPref(); + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: httpURL("coop_header.sjs", "https://example.org"), + waitForStateStop: true, + }, + async function (browser) { + let prevBC = browser.browsingContext; + + let target = httpURL("coop_header.sjs?.", "https://example.org"); + await performLoad( + browser, + { + url: target, + maybeErrorPage: false, + }, + async () => BrowserTestUtils.loadURIString(browser, target) + ); + + Assert.equal(prevBC, browser.browsingContext); + prevBC = browser.browsingContext; + + target = httpURL( + "coop_header.sjs?coop=same-origin", + "https://example.org" + ); + await performLoad( + browser, + { + url: target, + maybeErrorPage: false, + }, + async () => BrowserTestUtils.loadURIString(browser, target) + ); + + Assert.notEqual(prevBC, browser.browsingContext); + prevBC = browser.browsingContext; + + target = httpURL( + "coop_header.sjs?coop=same-origin", + "https://example.com" + ); + await performLoad( + browser, + { + url: target, + maybeErrorPage: false, + }, + async () => BrowserTestUtils.loadURIString(browser, target) + ); + + Assert.notEqual(prevBC, browser.browsingContext); + prevBC = browser.browsingContext; + + target = httpURL( + "coop_header.sjs?coop=same-origin&index=4", + "https://example.com" + ); + await performLoad( + browser, + { + url: target, + maybeErrorPage: false, + }, + async () => BrowserTestUtils.loadURIString(browser, target) + ); + + Assert.equal(prevBC, browser.browsingContext); + } + ); +}); + +add_task(async function test_disabled() { + await unsetPref(); + await test_coop( + httpURL("coop_header.sjs", "https://example.com"), + httpURL("coop_header.sjs", "https://example.com"), + false + ); + await test_coop( + httpURL("coop_header.sjs?coop=same-origin", "http://example.com"), + httpURL("coop_header.sjs", "http://example.com"), + false + ); + await test_coop( + httpURL("coop_header.sjs", "http://example.com"), + httpURL("coop_header.sjs?coop=same-origin", "http://example.com"), + false + ); +}); + +add_task(async function test_enabled() { + await setPref(); + + function checkIsCoopRemoteType(remoteType) { + Assert.ok( + remoteType.startsWith(E10SUtils.WEB_REMOTE_COOP_COEP_TYPE_PREFIX), + `${remoteType} expected to be coop` + ); + } + + function checkIsNotCoopRemoteType(remoteType) { + if (gFissionBrowser) { + Assert.ok( + remoteType.startsWith("webIsolated="), + `${remoteType} expected to start with webIsolated=` + ); + } else { + Assert.equal( + remoteType, + E10SUtils.WEB_REMOTE_TYPE, + `${remoteType} expected to be web` + ); + } + } + + await test_coop( + httpURL("coop_header.sjs", "https://example.com"), + httpURL("coop_header.sjs", "https://example.com"), + false, + checkIsNotCoopRemoteType, + checkIsNotCoopRemoteType + ); + await test_coop( + httpURL("coop_header.sjs", "https://example.com"), + httpURL("coop_header.sjs?coop=same-origin", "https://example.org"), + true, + checkIsNotCoopRemoteType, + checkIsNotCoopRemoteType + ); + await test_coop( + httpURL("coop_header.sjs?coop=same-origin&index=1", "https://example.com"), + httpURL("coop_header.sjs?coop=same-origin&index=1", "https://example.org"), + true, + checkIsNotCoopRemoteType, + checkIsNotCoopRemoteType + ); + await test_coop( + httpURL("coop_header.sjs", "https://example.com"), + httpURL( + "coop_header.sjs?coop=same-origin&coep=require-corp", + "https://example.com" + ), + true, + checkIsNotCoopRemoteType, + checkIsCoopRemoteType + ); + await test_coop( + httpURL( + "coop_header.sjs?coop=same-origin&coep=require-corp&index=2", + "https://example.com" + ), + httpURL( + "coop_header.sjs?coop=same-origin&coep=require-corp&index=3", + "https://example.com" + ), + false, + checkIsCoopRemoteType, + checkIsCoopRemoteType + ); + await test_coop( + httpURL( + "coop_header.sjs?coop=same-origin&coep=require-corp&index=4", + "https://example.com" + ), + httpURL("coop_header.sjs", "https://example.com"), + true, + checkIsCoopRemoteType, + checkIsNotCoopRemoteType + ); + await test_coop( + httpURL( + "coop_header.sjs?coop=same-origin&coep=require-corp&index=5", + "https://example.com" + ), + httpURL( + "coop_header.sjs?coop=same-origin&coep=require-corp&index=6", + "https://example.org" + ), + true, + checkIsCoopRemoteType, + checkIsCoopRemoteType + ); +}); + +add_task(async function test_download() { + requestLongerTimeout(4); + await setPref(); + + let initCoopArray = ["", "same-origin"]; + + let downloadCoopArray = [ + "no-coop", + "same-origin", + "same-origin-allow-popups", + ]; + + // If the coop mismatch between current page and download link, clicking the + // download link will make the page empty and popup the download window. That + // forces us to reload the page every time. + for (var initCoop of initCoopArray) { + for (var downloadCoop of downloadCoopArray) { + await test_download_from(initCoop, downloadCoop); + } + } +}); diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser_httpToFileHistory.js b/toolkit/components/remotebrowserutils/tests/browser/browser_httpToFileHistory.js new file mode 100644 index 0000000000..56151ae94e --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser_httpToFileHistory.js @@ -0,0 +1,115 @@ +const HISTORY = [ + { url: httpURL("dummy_page.html") }, + { url: fileURL("dummy_page.html") }, + { url: httpURL("dummy_page.html") }, +]; + +function reversed(list) { + let copy = list.slice(); + copy.reverse(); + return copy; +} + +function butLast(list) { + return list.slice(0, -1); +} + +async function runTest() { + await BrowserTestUtils.withNewTab({ gBrowser }, async function (aBrowser) { + // Perform initial load of each URL in the history. + let count = 0; + let index = -1; + for (let { url } of HISTORY) { + BrowserTestUtils.loadURIString(aBrowser, url); + + await BrowserTestUtils.browserLoaded(aBrowser, false, loaded => { + return ( + Services.io.newURI(loaded).scheme == Services.io.newURI(url).scheme + ); + }); + + count++; + index++; + await SpecialPowers.spawn( + aBrowser, + [{ count, index, url }], + async function ({ count, index, url }) { + docShell.QueryInterface(Ci.nsIWebNavigation); + + is( + docShell.sessionHistory.count, + count, + "Initial Navigation Count Match" + ); + is( + docShell.sessionHistory.index, + index, + "Initial Navigation Index Match" + ); + + let real = Services.io.newURI(content.location.href); + let expect = Services.io.newURI(url); + is(real.scheme, expect.scheme, "Initial Navigation URL Scheme"); + } + ); + } + + // Go back to the first entry. + for (let { url } of reversed(HISTORY).slice(1)) { + SpecialPowers.spawn(aBrowser, [], () => { + content.history.back(); + }); + await BrowserTestUtils.browserLoaded(aBrowser, false, loaded => { + return ( + Services.io.newURI(loaded).scheme == Services.io.newURI(url).scheme + ); + }); + + index--; + await SpecialPowers.spawn( + aBrowser, + [{ count, index, url }], + async function ({ count, index, url }) { + docShell.QueryInterface(Ci.nsIWebNavigation); + + is(docShell.sessionHistory.count, count, "Go Back Count Match"); + is(docShell.sessionHistory.index, index, "Go Back Index Match"); + + let real = Services.io.newURI(content.location.href); + let expect = Services.io.newURI(url); + is(real.scheme, expect.scheme, "Go Back URL Scheme"); + } + ); + } + + // Go forward to the last entry. + for (let { url } of HISTORY.slice(1)) { + SpecialPowers.spawn(aBrowser, [], () => { + content.history.forward(); + }); + await BrowserTestUtils.browserLoaded(aBrowser, false, loaded => { + return ( + Services.io.newURI(loaded).scheme == Services.io.newURI(url).scheme + ); + }); + + index++; + await SpecialPowers.spawn( + aBrowser, + [{ count, index, url }], + async function ({ count, index, url }) { + docShell.QueryInterface(Ci.nsIWebNavigation); + + is(docShell.sessionHistory.count, count, "Go Forward Count Match"); + is(docShell.sessionHistory.index, index, "Go Forward Index Match"); + + let real = Services.io.newURI(content.location.href); + let expect = Services.io.newURI(url); + is(real.scheme, expect.scheme, "Go Forward URL Scheme"); + } + ); + } + }); +} + +add_task(runTest); diff --git a/toolkit/components/remotebrowserutils/tests/browser/browser_oopProcessSwap.js b/toolkit/components/remotebrowserutils/tests/browser/browser_oopProcessSwap.js new file mode 100644 index 0000000000..3286227d37 --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/browser_oopProcessSwap.js @@ -0,0 +1,161 @@ +add_task(async function oopProcessSwap() { + const FILE = fileURL("dummy_page.html"); + const WEB = httpURL("file_postmsg_parent.html"); + + let win = await BrowserTestUtils.openNewBrowserWindow({ fission: true }); + + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url: FILE }, + async browser => { + is(browser.browsingContext.children.length, 0); + + info("creating an in-process frame"); + let frameId = await SpecialPowers.spawn( + browser, + [{ FILE }], + async ({ FILE }) => { + let iframe = content.document.createElement("iframe"); + iframe.setAttribute("src", FILE); + content.document.body.appendChild(iframe); + + // The nested URI should be same-process + ok(iframe.browsingContext.docShell, "Should be in-process"); + + return iframe.browsingContext.id; + } + ); + + is(browser.browsingContext.children.length, 1); + + info("navigating to x-process frame"); + let oopinfo = await SpecialPowers.spawn( + browser, + [{ WEB }], + async ({ WEB }) => { + let iframe = content.document.querySelector("iframe"); + + iframe.contentWindow.location = WEB; + + let data = await new Promise(resolve => { + content.window.addEventListener( + "message", + function (evt) { + info("oop iframe loaded"); + is(evt.source, iframe.contentWindow); + resolve(evt.data); + }, + { once: true } + ); + }); + + is(iframe.browsingContext.docShell, null, "Should be out-of-process"); + is( + iframe.browsingContext.embedderElement, + iframe, + "correct embedder" + ); + + return { + location: data.location, + browsingContextId: iframe.browsingContext.id, + }; + } + ); + + is(browser.browsingContext.children.length, 1); + + if (Services.prefs.getBoolPref("fission.preserve_browsing_contexts")) { + is( + frameId, + oopinfo.browsingContextId, + `BrowsingContext should not have changed (${frameId} != ${oopinfo.browsingContextId})` + ); + } + is(oopinfo.location, WEB, "correct location"); + } + ); + + await BrowserTestUtils.closeWindow(win); +}); + +add_task(async function oopOriginProcessSwap() { + const COM_DUMMY = httpURL("dummy_page.html", "https://example.com/"); + const ORG_POSTMSG = httpURL( + "file_postmsg_parent.html", + "https://example.org/" + ); + + let win = await BrowserTestUtils.openNewBrowserWindow({ fission: true }); + + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url: COM_DUMMY }, + async browser => { + is(browser.browsingContext.children.length, 0); + + info("creating an in-process frame"); + let frameId = await SpecialPowers.spawn( + browser, + [{ COM_DUMMY }], + async ({ COM_DUMMY }) => { + let iframe = content.document.createElement("iframe"); + iframe.setAttribute("src", COM_DUMMY); + content.document.body.appendChild(iframe); + + // The nested URI should be same-process + ok(iframe.browsingContext.docShell, "Should be in-process"); + + return iframe.browsingContext.id; + } + ); + + is(browser.browsingContext.children.length, 1); + + info("navigating to x-process frame"); + let oopinfo = await SpecialPowers.spawn( + browser, + [{ ORG_POSTMSG }], + async ({ ORG_POSTMSG }) => { + let iframe = content.document.querySelector("iframe"); + + iframe.contentWindow.location = ORG_POSTMSG; + + let data = await new Promise(resolve => { + content.window.addEventListener( + "message", + function (evt) { + info("oop iframe loaded"); + is(evt.source, iframe.contentWindow); + resolve(evt.data); + }, + { once: true } + ); + }); + + is(iframe.browsingContext.docShell, null, "Should be out-of-process"); + is( + iframe.browsingContext.embedderElement, + iframe, + "correct embedder" + ); + + return { + location: data.location, + browsingContextId: iframe.browsingContext.id, + }; + } + ); + + is(browser.browsingContext.children.length, 1); + if (Services.prefs.getBoolPref("fission.preserve_browsing_contexts")) { + is( + frameId, + oopinfo.browsingContextId, + `BrowsingContext should not have changed (${frameId} != ${oopinfo.browsingContextId})` + ); + } + is(oopinfo.location, ORG_POSTMSG, "correct location"); + } + ); + + await BrowserTestUtils.closeWindow(win); +}); diff --git a/toolkit/components/remotebrowserutils/tests/browser/coop_header.sjs b/toolkit/components/remotebrowserutils/tests/browser/coop_header.sjs new file mode 100644 index 0000000000..c6b537d770 --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/coop_header.sjs @@ -0,0 +1,58 @@ +function handleRequest(request, response) { + Cu.importGlobalProperties(["URLSearchParams"]); + let query = new URLSearchParams(request.queryString); + + response.setStatusLine(request.httpVersion, 200, "OK"); + + // The tests for Cross-Origin-Opener-Policy unfortunately depend on + // BFCacheInParent not kicking in, as with that enabled, it is not possible to + // tell whether the BrowsingContext switch was caused by the BFCache + // navigation or by the COOP mismatch. This header disables BFCache for the + // coop documents, and should avoid the issue. + response.setHeader("Cache-Control", "no-store", false); + + let isDownloadPage = false; + let isDownloadFile = false; + + query.forEach((value, name) => { + if (name === "downloadPage") { + isDownloadPage = true; + } else if (name === "downloadFile") { + isDownloadFile = true; + } else if (name == "coop") { + response.setHeader("Cross-Origin-Opener-Policy", unescape(value), false); + } else if (name == "coep") { + response.setHeader( + "Cross-Origin-Embedder-Policy", + unescape(value), + false + ); + } + }); + + let downloadHTML = ""; + if (isDownloadPage) { + ["no-coop", "same-origin", "same-origin-allow-popups"].forEach(coop => { + downloadHTML += + '<a href="https://example.com/browser/toolkit/components/remotebrowserutils/tests/browser/coop_header.sjs?downloadFile&' + + (coop === "no-coop" ? "" : coop) + + '" id="' + + coop + + '" download>' + + unescape(coop) + + "</a> <br>"; + }); + } + + if (isDownloadFile) { + response.setHeader("Content-Type", "application/octet-stream", false); + response.write("BINARY_DATA"); + } else { + response.setHeader("Content-Type", "text/html; charset=utf-8", false); + response.write( + "<!DOCTYPE html><html><body><p>Hello world</p> " + + downloadHTML + + "</body></html>" + ); + } +} diff --git a/toolkit/components/remotebrowserutils/tests/browser/dummy_page.html b/toolkit/components/remotebrowserutils/tests/browser/dummy_page.html new file mode 100644 index 0000000000..8205e90d5d --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/dummy_page.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +<html> +<body> +<p>Page</p> + <script> + // Prevent this page from being stored in the bfcache for the + // browser_httpToFileHistory.js test + window.blockBFCache = new RTCPeerConnection(); + </script> +</body> +</html> diff --git a/toolkit/components/remotebrowserutils/tests/browser/file_postmsg_parent.html b/toolkit/components/remotebrowserutils/tests/browser/file_postmsg_parent.html new file mode 100644 index 0000000000..d5f640775b --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/file_postmsg_parent.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<html> +<body onload="parent.postMessage({location: window.location.href}, '*')"> +</body> +</html> diff --git a/toolkit/components/remotebrowserutils/tests/browser/head.js b/toolkit/components/remotebrowserutils/tests/browser/head.js new file mode 100644 index 0000000000..ffdd9375cf --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/head.js @@ -0,0 +1,17 @@ +function fileURL(filename) { + let ifile = getChromeDir(getResolvedURI(gTestPath)); + ifile.append(filename); + return Services.io.newFileURI(ifile).spec; +} + +function httpURL(filename, host = "https://example.com/") { + let root = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + host + ); + return root + filename; +} + +function add307(url, host = "https://example.com/") { + return httpURL("307redirect.sjs?" + url, host); +} diff --git a/toolkit/components/remotebrowserutils/tests/browser/print_postdata.sjs b/toolkit/components/remotebrowserutils/tests/browser/print_postdata.sjs new file mode 100644 index 0000000000..bd9c18f7ea --- /dev/null +++ b/toolkit/components/remotebrowserutils/tests/browser/print_postdata.sjs @@ -0,0 +1,28 @@ +const CC = Components.Constructor; +const BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + if (request.method == "GET") { + response.write(request.queryString); + } else { + var body = new BinaryInputStream(request.bodyInputStream); + + var avail; + var bytes = []; + + while ((avail = body.available()) > 0) { + Array.prototype.push.apply(bytes, body.readByteArray(avail)); + } + + var data = String.fromCharCode.apply(null, bytes); + if (request.queryString) { + data = data + "?" + request.queryString; + } + response.bodyOutputStream.write(data, data.length); + } +} |