diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js new file mode 100644 index 0000000000..0b826be08f --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js @@ -0,0 +1,607 @@ +"use strict"; + +const HOSTS = new Set(["example.com", "example.org", "example.net"]); + +const server = createHttpServer({ hosts: HOSTS }); + +const FETCH_ORIGIN = "http://example.com/dummy"; + +server.registerDirectory("/data/", do_get_file("data")); + +server.registerPathHandler("/redirect", (request, response) => { + let params = new URLSearchParams(request.queryString); + response.setStatusLine(request.httpVersion, 302, "Moved Temporarily"); + response.setHeader("Location", params.get("redirect_uri")); + response.setHeader("Access-Control-Allow-Origin", "*"); +}); + +server.registerPathHandler("/redirect301", (request, response) => { + let params = new URLSearchParams(request.queryString); + response.setStatusLine(request.httpVersion, 301, "Moved Permanently"); + response.setHeader("Location", params.get("redirect_uri")); + response.setHeader("Access-Control-Allow-Origin", "*"); +}); + +server.registerPathHandler("/script302.js", (request, response) => { + response.setStatusLine(request.httpVersion, 302, "Moved Temporarily"); + response.setHeader("Location", "http://example.com/script.js"); +}); + +server.registerPathHandler("/script.js", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/javascript"); + response.write(String.raw`console.log("HELLO!");`); +}); + +server.registerPathHandler("/302.html", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/html"); + response.write(String.raw` + <script type="application/javascript" src="http://example.com/script302.js"></script> + `); +}); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.write("ok"); +}); + +server.registerPathHandler("/dummy.xhtml", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/xhtml+xml"); + response.write(String.raw`<?xml version="1.0"?> + <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml"> + <head/> + <body/> + </html> + `); +}); + +server.registerPathHandler("/lorem.html.gz", async (request, response) => { + response.processAsync(); + + response.setHeader( + "Content-Type", + "Content-Type: text/html; charset=utf-8", + false + ); + response.setHeader("Content-Encoding", "gzip", false); + + let data = await IOUtils.read(do_get_file("data/lorem.html.gz").path); + response.write(String.fromCharCode(...data)); + + response.finish(); +}); + +// Test re-encoding the data stream for bug 1590898. +add_task(async function test_stream_encoding_data() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onBeforeRequest.addListener( + request => { + let filter = browser.webRequest.filterResponseData(request.requestId); + let decoder = new TextDecoder("utf-8"); + let encoder = new TextEncoder(); + + filter.ondata = event => { + let str = decoder.decode(event.data, { stream: true }); + filter.write(encoder.encode(str)); + filter.disconnect(); + }; + }, + { + urls: ["http://example.com/lorem.html.gz"], + types: ["main_frame"], + }, + ["blocking"] + ); + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + }, + }); + + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/lorem.html.gz" + ); + + let content = await contentPage.spawn([], () => { + return this.content.document.body.textContent; + }); + + ok( + content.includes("Lorem ipsum dolor sit amet"), + `expected content received` + ); + + await contentPage.close(); + await extension.unload(); +}); + +// Tests that the stream filter request is added to the document's load +// group, and blocks an XML document's load event until after the filter +// stops sending data. +add_task(async function test_xml_document_loadgroup_blocking() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onBeforeRequest.addListener( + request => { + let filter = browser.webRequest.filterResponseData(request.requestId); + + let data = []; + filter.ondata = event => { + data.push(event.data); + }; + filter.onstop = async () => { + browser.test.sendMessage("phase", "original-onstop"); + + // Make a few trips through the event loop. + for (let i = 0; i < 10; i++) { + await new Promise(resolve => setTimeout(resolve, 0)); + } + + for (let buffer of data) { + filter.write(buffer); + } + browser.test.sendMessage("phase", "filter-onstop"); + filter.close(); + }; + }, + { + urls: ["http://example.com/dummy.xhtml"], + }, + ["blocking"] + ); + }, + + files: { + "content_script.js"() { + browser.test.sendMessage("phase", "content-script-start"); + window.addEventListener( + "DOMContentLoaded", + () => { + browser.test.sendMessage("phase", "content-script-domload"); + }, + { once: true } + ); + window.addEventListener( + "load", + () => { + browser.test.sendMessage("phase", "content-script-load"); + }, + { once: true } + ); + }, + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + + content_scripts: [ + { + matches: ["http://example.com/dummy.xhtml"], + run_at: "document_start", + js: ["content_script.js"], + }, + ], + }, + }); + + await extension.startup(); + + const EXPECTED = [ + "original-onstop", + "filter-onstop", + "content-script-start", + "content-script-domload", + "content-script-load", + ]; + + let done = new Promise(resolve => { + let phases = []; + extension.onMessage("phase", phase => { + phases.push(phase); + if (phases.length === EXPECTED.length) { + resolve(phases); + } + }); + }); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/dummy.xhtml" + ); + + deepEqual(await done, EXPECTED, "Things happened, and in the right order"); + + await contentPage.close(); + await extension.unload(); +}); + +add_task(async function test_filter_content_fetch() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + let pending = []; + + browser.webRequest.onBeforeRequest.addListener( + data => { + let filter = browser.webRequest.filterResponseData(data.requestId); + + let url = new URL(data.url); + + if (url.searchParams.get("redirect_uri")) { + pending.push( + new Promise(resolve => { + filter.onerror = resolve; + }).then(() => { + browser.test.assertEq( + "Channel redirected", + filter.error, + "Got correct error for redirected filter" + ); + }) + ); + } + + filter.onstart = () => { + filter.write(new TextEncoder().encode(data.url)); + }; + filter.ondata = event => { + let str = new TextDecoder().decode(event.data); + browser.test.assertEq( + "ok", + str, + `Got unfiltered data for ${data.url}` + ); + }; + filter.onstop = () => { + filter.close(); + }; + }, + { + urls: ["<all_urls>"], + }, + ["blocking"] + ); + + browser.test.onMessage.addListener(async msg => { + if (msg === "done") { + await Promise.all(pending); + browser.test.notifyPass("stream-filter"); + } + }); + }, + + manifest: { + permissions: [ + "webRequest", + "webRequestBlocking", + "http://example.com/", + "http://example.org/", + ], + }, + }); + + await extension.startup(); + + let results = [ + ["http://example.com/dummy", "http://example.com/dummy"], + ["http://example.org/dummy", "http://example.org/dummy"], + ["http://example.net/dummy", "ok"], + [ + "http://example.com/redirect?redirect_uri=http://example.com/dummy", + "http://example.com/dummy", + ], + [ + "http://example.com/redirect?redirect_uri=http://example.org/dummy", + "http://example.org/dummy", + ], + ["http://example.com/redirect?redirect_uri=http://example.net/dummy", "ok"], + [ + "http://example.net/redirect?redirect_uri=http://example.com/dummy", + "http://example.com/dummy", + ], + ].map(async ([url, expectedResponse]) => { + let text = await ExtensionTestUtils.fetch(FETCH_ORIGIN, url); + equal(text, expectedResponse, `Expected response for ${url}`); + }); + + await Promise.all(results); + + extension.sendMessage("done"); + await extension.awaitFinish("stream-filter"); + await extension.unload(); +}); + +add_task(async function test_filter_301() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onHeadersReceived.addListener( + data => { + if (data.statusCode !== 200) { + return; + } + let filter = browser.webRequest.filterResponseData(data.requestId); + + filter.onstop = () => { + filter.close(); + browser.test.notifyPass("stream-filter"); + }; + filter.onerror = () => { + browser.test.fail(`unexpected ${filter.error}`); + }; + }, + { + urls: ["<all_urls>"], + }, + ["blocking"] + ); + }, + + manifest: { + permissions: [ + "webRequest", + "webRequestBlocking", + "http://example.com/", + "http://example.org/", + ], + }, + }); + + await extension.startup(); + + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/redirect301?redirect_uri=http://example.org/dummy" + ); + + await extension.awaitFinish("stream-filter"); + + await contentPage.close(); + await extension.unload(); +}); + +add_task(async function test_filter_302() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onBeforeRequest.addListener( + details => { + let filter = browser.webRequest.filterResponseData(details.requestId); + browser.test.sendMessage("filter-created"); + + filter.ondata = event => { + const script = "forceError();"; + filter.write(new Uint8Array(new TextEncoder().encode(script))); + filter.close(); + browser.test.sendMessage("filter-ondata"); + }; + + filter.onerror = () => { + browser.test.assertEq(filter.error, "Channel redirected"); + browser.test.sendMessage("filter-redirect"); + }; + }, + { + urls: ["http://example.com/*.js"], + }, + ["blocking"] + ); + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + }, + }); + + await extension.startup(); + + let { messages } = await promiseConsoleOutput(async () => { + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/302.html" + ); + + await extension.awaitMessage("filter-created"); + await extension.awaitMessage("filter-redirect"); + await extension.awaitMessage("filter-created"); + await extension.awaitMessage("filter-ondata"); + await contentPage.close(); + }); + AddonTestUtils.checkMessages(messages, { + expected: [{ message: /forceError is not defined/ }], + }); + + await extension.unload(); +}); + +add_task(async function test_alternate_cached_data() { + Services.prefs.setBoolPref("dom.script_loader.bytecode_cache.enabled", true); + Services.prefs.setIntPref("dom.script_loader.bytecode_cache.strategy", -1); + + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onBeforeRequest.addListener( + details => { + let filter = browser.webRequest.filterResponseData(details.requestId); + let decoder = new TextDecoder("utf-8"); + let encoder = new TextEncoder(); + + filter.ondata = event => { + let str = decoder.decode(event.data, { stream: true }); + filter.write(encoder.encode(str)); + filter.disconnect(); + browser.test.assertTrue( + str.startsWith(`"use strict";`), + "ondata received decoded data" + ); + browser.test.sendMessage("onBeforeRequest"); + }; + + filter.onerror = () => { + // onBeforeRequest will always beat the cache race, so we should always + // get valid data in ondata. + browser.test.fail("error-received", filter.error); + }; + }, + { + urls: ["http://example.com/data/file_script_good.js"], + }, + ["blocking"] + ); + browser.webRequest.onHeadersReceived.addListener( + details => { + let filter = browser.webRequest.filterResponseData(details.requestId); + let decoder = new TextDecoder("utf-8"); + let encoder = new TextEncoder(); + + // Because cache is always a race, intermittently we will succesfully + // beat the cache, in which case we pass in ondata. If cache wins, + // we pass in onerror. + // Running the test with --verify hits this cache race issue, as well + // it seems that the cache primarily looses on linux1804. + let gotone = false; + filter.ondata = event => { + browser.test.assertFalse(gotone, "cache lost the race"); + gotone = true; + let str = decoder.decode(event.data, { stream: true }); + filter.write(encoder.encode(str)); + filter.disconnect(); + browser.test.assertTrue( + str.startsWith(`"use strict";`), + "ondata received decoded data" + ); + browser.test.sendMessage("onHeadersReceived"); + }; + + filter.onerror = () => { + browser.test.assertFalse(gotone, "cache won the race"); + gotone = true; + browser.test.assertEq( + filter.error, + "Channel is delivering cached alt-data" + ); + browser.test.sendMessage("onHeadersReceived"); + }; + }, + { + urls: ["http://example.com/data/file_script_bad.js"], + }, + ["blocking"] + ); + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/*"], + }, + }); + + // Prime the cache so we have the script byte-cached. + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_script.html" + ); + await contentPage.close(); + + await extension.startup(); + + let page_cached = await await ExtensionTestUtils.loadContentPage( + "http://example.com/data/file_script.html" + ); + await Promise.all([ + extension.awaitMessage("onBeforeRequest"), + extension.awaitMessage("onHeadersReceived"), + ]); + await page_cached.close(); + await extension.unload(); + + Services.prefs.clearUserPref("dom.script_loader.bytecode_cache.enabled"); + Services.prefs.clearUserPref("dom.script_loader.bytecode_cache.strategy"); +}); + +add_task(async function test_webRequestFilterResponse_permission() { + function background() { + browser.test.onMessage.addListener(async (msg, ...args) => { + if (msg !== "testFilterResponseData") { + browser.test.fail(`Unexpected test message: ${msg}`); + return; + } + + const [{ expectMissingPermissionError }] = args; + + if (expectMissingPermissionError) { + browser.test.assertThrows( + () => browser.webRequest.filterResponseData("fake-response-id"), + /Missing required "webRequestFilterResponse" permission/, + "Expected missing webRequestFilterResponse permission error" + ); + } else { + // Expect the generic error raised on invalid response id + // if the missing permission error isn't expected. + browser.test.assertTrue( + browser.webRequest.filterResponseData("fake-response-id"), + "Expected no missing webRequestFilterResponse permission error" + ); + } + + browser.test.notifyPass(); + }); + } + + info( + "Verify MV2 extension does not require webRequestFilterResponse permission" + ); + const extMV2 = ExtensionTestUtils.loadExtension({ + background, + manifest: { + manifest_version: 2, + permissions: ["webRequest", "webRequestBlocking"], + }, + }); + + await extMV2.startup(); + extMV2.sendMessage("testFilterResponseData", { + expectMissingPermissionError: false, + }); + await extMV2.awaitFinish(); + await extMV2.unload(); + + info( + "Verify filterResponseData throws on MV3 extension without webRequestFilterResponse permission" + ); + const extMV3NoPerm = ExtensionTestUtils.loadExtension({ + background, + manifest: { + manifest_version: 3, + permissions: ["webRequest", "webRequestBlocking"], + }, + }); + + await extMV3NoPerm.startup(); + extMV3NoPerm.sendMessage("testFilterResponseData", { + expectMissingPermissionError: true, + }); + await extMV3NoPerm.awaitFinish(); + await extMV3NoPerm.unload(); + + info( + "Verify filterResponseData does not throw on MV3 extension without webRequestFilterResponse permission" + ); + const extMV3WithPerm = ExtensionTestUtils.loadExtension({ + background, + manifest: { + manifest_version: 3, + permissions: [ + "webRequest", + "webRequestBlocking", + "webRequestFilterResponse", + ], + }, + }); + + await extMV3WithPerm.startup(); + extMV3WithPerm.sendMessage("testFilterResponseData", { + expectMissingPermissionError: false, + }); + await extMV3WithPerm.awaitFinish(); + await extMV3WithPerm.unload(); +}); |