"use strict"; // This file tests the behavior of fetch/XMLHttpRequest in content scripts, in // relation to permissions, in MV2. // In MV3, the expectations are different, test coverage for that is in // test_ext_xhr_cors.js (along with CORS tests that also apply to MV2). const server = createHttpServer({ hosts: ["xpcshell.test", "example.com", "example.org"], }); server.registerDirectory("/data/", do_get_file("data")); server.registerPathHandler("/example.txt", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write("ok"); }); server.registerPathHandler("/return_headers.sjs", (request, response) => { response.setHeader("Content-Type", "text/plain", false); let headers = {}; for (let { data: header } of request.headers) { headers[header.toLowerCase()] = request.getHeader(header); } response.write(JSON.stringify(headers)); }); /* eslint-disable mozilla/balanced-listeners */ add_task(async function test_simple() { async function runTests(cx) { function xhr(XMLHttpRequest) { return url => { return new Promise((resolve, reject) => { let req = new XMLHttpRequest(); req.open("GET", url); req.addEventListener("load", resolve); req.addEventListener("error", reject); req.send(); }); }; } function run(shouldFail, fetch) { function passListener() { browser.test.succeed(`${cx}.${fetch.name} pass listener`); } function failListener() { browser.test.fail(`${cx}.${fetch.name} fail listener`); } /* eslint-disable no-else-return */ if (shouldFail) { return fetch("http://example.org/example.txt").then( failListener, passListener ); } else { return fetch("http://example.com/example.txt").then( passListener, failListener ); } /* eslint-enable no-else-return */ } try { await run(true, xhr(XMLHttpRequest)); await run(false, xhr(XMLHttpRequest)); await run(true, xhr(window.XMLHttpRequest)); await run(false, xhr(window.XMLHttpRequest)); await run(true, fetch); await run(false, fetch); await run(true, window.fetch); await run(false, window.fetch); } catch (err) { browser.test.fail(`Error: ${err} :: ${err.stack}`); browser.test.notifyFail("permission_xhr"); } } async function background(runTestsFn) { await runTestsFn("bg"); browser.test.notifyPass("permission_xhr"); } let extensionData = { background: `(${background})(${runTests})`, manifest: { permissions: ["http://example.com/"], content_scripts: [ { matches: ["http://xpcshell.test/data/file_permission_xhr.html"], js: ["content.js"], }, ], }, files: { "content.js": `(${async runTestsFn => { await runTestsFn("content"); window.wrappedJSObject.privilegedFetch = fetch; window.wrappedJSObject.privilegedXHR = XMLHttpRequest; window.addEventListener("message", function rcv({ data }) { switch (data.msg) { case "test": break; case "assertTrue": browser.test.assertTrue(data.condition, data.description); break; case "finish": window.removeEventListener("message", rcv); browser.test.sendMessage("content-script-finished"); break; } }); window.postMessage("test", "*"); }})(${runTests})`, }, }; let extension = ExtensionTestUtils.loadExtension(extensionData); await extension.startup(); let contentPage = await ExtensionTestUtils.loadContentPage( "http://xpcshell.test/data/file_permission_xhr.html" ); await extension.awaitMessage("content-script-finished"); await contentPage.close(); await extension.awaitFinish("permission_xhr"); await extension.unload(); }); // This test case ensures that a WebExtension content script can still use the same // XMLHttpRequest and fetch APIs that the webpage can use and be recognized from // the target server with the same origin and referer headers of the target webpage // (see Bug 1295660 for a rationale). add_task(async function test_page_xhr() { async function contentScript() { const content = this.content; const { webpageFetchResult, webpageXhrResult } = await new Promise( resolve => { const listenPageMessage = event => { if (!event.data || event.data.type !== "testPageGlobals") { return; } window.removeEventListener("message", listenPageMessage); browser.test.assertEq( true, !!content.XMLHttpRequest, "The content script should have access to content.XMLHTTPRequest" ); browser.test.assertEq( true, !!content.fetch, "The content script should have access to window.pageFetch" ); resolve(event.data); }; window.addEventListener("message", listenPageMessage); window.postMessage({}, "*"); } ); const url = new URL("/return_headers.sjs", location).href; await Promise.all([ new Promise((resolve, reject) => { const req = new content.XMLHttpRequest(); req.open("GET", url); req.addEventListener("load", () => resolve(JSON.parse(req.responseText)) ); req.addEventListener("error", reject); req.send(); }), content.fetch(url).then(res => res.json()), ]) .then(async ([xhrResult, fetchResult]) => { browser.test.assertEq( webpageFetchResult.referer, fetchResult.referer, "window.pageFetch referrer is the same of a webpage fetch request" ); browser.test.assertEq( webpageFetchResult.origin, fetchResult.origin, "window.pageFetch origin is the same of a webpage fetch request" ); browser.test.assertEq( webpageXhrResult.referer, xhrResult.referer, "content.XMLHttpRequest referrer is the same of a webpage fetch request" ); }) .catch(error => { browser.test.fail(`Unexpected error: ${error}`); }); browser.test.notifyPass("content-script-page-xhr"); } let extensionData = { manifest: { content_scripts: [ { matches: ["http://xpcshell.test/*"], js: ["content.js"], }, ], }, files: { "content.js": `(${contentScript})()`, }, }; let extension = ExtensionTestUtils.loadExtension(extensionData); await extension.startup(); let contentPage = await ExtensionTestUtils.loadContentPage( "http://xpcshell.test/data/file_page_xhr.html" ); await extension.awaitFinish("content-script-page-xhr"); await contentPage.close(); await extension.unload(); });