From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../xpcshell/test_ext_webRequest_responseBody.js | 764 +++++++++++++++++++++ 1 file changed, 764 insertions(+) create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js') diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js new file mode 100644 index 0000000000..8995870ba6 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_responseBody.js @@ -0,0 +1,764 @@ +"use strict"; + +/* eslint-disable mozilla/no-arbitrary-setTimeout */ +/* eslint-disable no-shadow */ + +const { ExtensionTestCommon } = ChromeUtils.importESModule( + "resource://testing-common/ExtensionTestCommon.sys.mjs" +); + +const HOSTS = new Set(["example.com"]); + +const server = createHttpServer({ hosts: HOSTS }); + +const BASE_URL = "http://example.com"; +const FETCH_ORIGIN = "http://example.com/data/file_sample.html"; + +const SEQUENTIAL = false; + +const PARTS = [ + ` + + + + + + `, + "Lorem ipsum dolor sit amet,
", + "consectetur adipiscing elit,
", + "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
", + "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
", + "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
", + "Excepteur sint occaecat cupidatat non proident,
", + "sunt in culpa qui officia deserunt mollit anim id est laborum.
", + ` + + `, +].map(part => `${part}\n`); + +const TIMEOUT = AppConstants.DEBUG ? 4000 : 800; + +function delay(timeout = TIMEOUT) { + return new Promise(resolve => setTimeout(resolve, timeout)); +} + +server.registerPathHandler("/slow_response.sjs", async (request, response) => { + response.processAsync(); + + response.setHeader("Content-Type", "text/html", false); + response.setHeader("Cache-Control", "no-cache", false); + + await delay(); + + for (let part of PARTS) { + try { + response.write(part); + } catch (e) { + // This fails if we attempt to write data after the connection has + // been closed. + break; + } + await delay(); + } + + response.finish(); +}); + +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(); +}); + +server.registerPathHandler("/multipart", async (request, response) => { + response.processAsync(); + + response.setHeader( + "Content-Type", + 'Content-Type: multipart/x-mixed-replace; boundary="testingtesting"', + false + ); + + response.write("--testingtesting\n"); + response.write(PARTS.join("")); + response.write("--testingtesting--\n"); + + response.finish(); +}); + +server.registerPathHandler("/multipart2", async (request, response) => { + response.processAsync(); + + response.setHeader( + "Content-Type", + 'Content-Type: multipart/x-mixed-replace; boundary="testingtesting"', + false + ); + + response.write("--testingtesting\n"); + response.write(PARTS.join("")); + response.write("--testingtesting\n"); + response.write(PARTS.join("")); + response.write("--testingtesting--\n"); + + response.finish(); +}); + +server.registerDirectory("/data/", do_get_file("data")); + +const TASKS = [ + { + url: "slow_response.sjs", + task(filter, resolve, num) { + let decoder = new TextDecoder("utf-8"); + + browser.test.assertEq( + "uninitialized", + filter.status, + `(${num}): Got expected initial status` + ); + + filter.onstart = event => { + browser.test.assertEq( + "transferringdata", + filter.status, + `(${num}): Got expected onStart status` + ); + }; + + filter.onstop = event => { + browser.test.fail( + `(${num}): Got unexpected onStop event while disconnected` + ); + }; + + let n = 0; + filter.ondata = async event => { + let str = decoder.decode(event.data, { stream: true }); + + if (n < 3) { + browser.test.assertEq( + JSON.stringify(PARTS[n]), + JSON.stringify(str), + `(${num}): Got expected part` + ); + } + n++; + + filter.write(event.data); + + if (n == 3) { + filter.suspend(); + + browser.test.assertEq( + "suspended", + filter.status, + `(${num}): Got expected suspended status` + ); + + let fail = () => { + browser.test.fail( + `(${num}): Got unexpected data event while suspended` + ); + }; + filter.addEventListener("data", fail); + + await delay(TIMEOUT * 3); + + browser.test.assertEq( + "suspended", + filter.status, + `(${num}): Got expected suspended status` + ); + + filter.removeEventListener("data", fail); + filter.resume(); + browser.test.assertEq( + "transferringdata", + filter.status, + `(${num}): Got expected resumed status` + ); + } else if (n > 4) { + filter.disconnect(); + + filter.addEventListener("data", () => { + browser.test.fail( + `(${num}): Got unexpected data event while disconnected` + ); + }); + + browser.test.assertEq( + "disconnected", + filter.status, + `(${num}): Got expected disconnected status` + ); + + resolve(); + } + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal(response, PARTS.join(""), "Got expected final HTML"); + }, + }, + { + url: "slow_response.sjs", + task(filter, resolve, num) { + let decoder = new TextDecoder("utf-8"); + + filter.onstop = event => { + browser.test.fail( + `(${num}): Got unexpected onStop event while disconnected` + ); + }; + + let n = 0; + filter.ondata = async event => { + let str = decoder.decode(event.data, { stream: true }); + + if (n < 3) { + browser.test.assertEq( + JSON.stringify(PARTS[n]), + JSON.stringify(str), + `(${num}): Got expected part` + ); + } + n++; + + filter.write(event.data); + + if (n == 3) { + filter.suspend(); + + await delay(TIMEOUT * 3); + + filter.disconnect(); + + resolve(); + } + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal(response, PARTS.join(""), "Got expected final HTML"); + }, + }, + { + url: "slow_response.sjs", + task(filter, resolve, num) { + let encoder = new TextEncoder(); + + filter.onstop = event => { + browser.test.fail( + `(${num}): Got unexpected onStop event while disconnected` + ); + }; + + let n = 0; + filter.ondata = async event => { + n++; + + filter.write(event.data); + + function checkState(state) { + browser.test.assertEq( + state, + filter.status, + `(${num}): Got expected status` + ); + } + if (n == 3) { + filter.resume(); + checkState("transferringdata"); + filter.suspend(); + checkState("suspended"); + filter.suspend(); + checkState("suspended"); + filter.resume(); + checkState("transferringdata"); + filter.suspend(); + checkState("suspended"); + + await delay(TIMEOUT * 3); + + checkState("suspended"); + filter.disconnect(); + checkState("disconnected"); + + for (let method of ["suspend", "resume", "close"]) { + browser.test.assertThrows( + () => { + filter[method](); + }, + /.*/, + `(${num}): ${method}() should throw while disconnected` + ); + } + + browser.test.assertThrows( + () => { + filter.write(encoder.encode("Foo bar")); + }, + /.*/, + `(${num}): write() should throw while disconnected` + ); + + filter.disconnect(); + + resolve(); + } + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal(response, PARTS.join(""), "Got expected final HTML"); + }, + }, + { + url: "slow_response.sjs", + task(filter, resolve, num) { + let encoder = new TextEncoder(); + let decoder = new TextDecoder("utf-8"); + + filter.onstop = event => { + browser.test.fail(`(${num}): Got unexpected onStop event while closed`); + }; + + browser.test.assertThrows( + () => { + filter.write(encoder.encode("Foo bar")); + }, + /.*/, + `(${num}): write() should throw prior to connection` + ); + + let n = 0; + filter.ondata = async event => { + n++; + + filter.write(event.data); + + browser.test.log( + `(${num}): Got part ${n}: ${JSON.stringify( + decoder.decode(event.data) + )}` + ); + + function checkState(state) { + browser.test.assertEq( + state, + filter.status, + `(${num}): Got expected status` + ); + } + if (n == 3) { + filter.close(); + + checkState("closed"); + + for (let method of ["suspend", "resume", "disconnect"]) { + browser.test.assertThrows( + () => { + filter[method](); + }, + /.*/, + `(${num}): ${method}() should throw while closed` + ); + } + + browser.test.assertThrows( + () => { + filter.write(encoder.encode("Foo bar")); + }, + /.*/, + `(${num}): write() should throw while closed` + ); + + filter.close(); + + resolve(); + } + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal(response, PARTS.slice(0, 3).join(""), "Got expected final HTML"); + }, + }, + { + url: "lorem.html.gz", + task(filter, resolve, num) { + let response = ""; + let decoder = new TextDecoder("utf-8"); + + filter.onstart = event => { + browser.test.log(`(${num}): Request start`); + }; + + filter.onstop = event => { + browser.test.assertEq( + "finishedtransferringdata", + filter.status, + `(${num}): Got expected onStop status` + ); + + filter.close(); + browser.test.assertEq( + "closed", + filter.status, + `Got expected closed status` + ); + + browser.test.assertEq( + JSON.stringify(PARTS.join("")), + JSON.stringify(response), + `(${num}): Got expected response` + ); + + resolve(); + }; + + filter.ondata = event => { + let str = decoder.decode(event.data, { stream: true }); + response += str; + + filter.write(event.data); + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal(response, PARTS.join(""), "Got expected final HTML"); + }, + }, + { + url: "multipart", + task(filter, resolve, num) { + filter.onstart = event => { + browser.test.log(`(${num}): Request start`); + }; + + filter.onstop = event => { + filter.disconnect(); + resolve(); + }; + + filter.ondata = event => { + filter.write(event.data); + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal( + response, + "--testingtesting\n" + PARTS.join("") + "--testingtesting--\n", + "Got expected final HTML" + ); + }, + }, + { + url: "multipart2", + task(filter, resolve, num) { + filter.onstart = event => { + browser.test.log(`(${num}): Request start`); + }; + + filter.onstop = event => { + filter.disconnect(); + resolve(); + }; + + filter.ondata = event => { + filter.write(event.data); + }; + + filter.onerror = event => { + browser.test.fail( + `(${num}): Got unexpected error event: ${filter.error}` + ); + }; + }, + verify(response) { + equal( + response, + "--testingtesting\n" + + PARTS.join("") + + "--testingtesting\n" + + PARTS.join("") + + "--testingtesting--\n", + "Got expected final HTML" + ); + }, + }, +]; + +function serializeTest(test, num) { + let url = `${test.url}?test_num=${num}`; + let task = ExtensionTestCommon.serializeFunction(test.task); + + return `{url: ${JSON.stringify(url)}, task: ${task}}`; +} + +add_task(async function () { + function background(TASKS) { + async function runTest(test, num, details) { + browser.test.log(`Running test #${num}: ${details.url}`); + + let filter = browser.webRequest.filterResponseData(details.requestId); + + try { + await new Promise(resolve => { + test.task(filter, resolve, num, details); + }); + } catch (e) { + browser.test.fail( + `Task #${num} threw an unexpected exception: ${e} :: ${e.stack}` + ); + } + + browser.test.log(`Finished test #${num}: ${details.url}`); + browser.test.sendMessage(`finished-${num}`); + } + + browser.webRequest.onBeforeRequest.addListener( + details => { + for (let [num, test] of TASKS.entries()) { + if (details.url.endsWith(test.url)) { + runTest(test, num, details); + break; + } + } + }, + { + urls: ["http://example.com/*?test_num=*"], + }, + ["blocking"] + ); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: ` + const PARTS = ${JSON.stringify(PARTS)}; + const TIMEOUT = ${TIMEOUT}; + + ${delay} + + (${background})([${TASKS.map(serializeTest)}]) + `, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + }, + }); + + await extension.startup(); + + async function runTest(test, num) { + let url = `${BASE_URL}/${test.url}?test_num=${num}`; + + let body = await ExtensionTestUtils.fetch(FETCH_ORIGIN, url); + + await extension.awaitMessage(`finished-${num}`); + + info(`Verifying test #${num}: ${url}`); + await test.verify(body); + } + + if (SEQUENTIAL) { + for (let [num, test] of TASKS.entries()) { + await runTest(test, num); + } + } else { + await Promise.all(TASKS.map(runTest)); + } + + await extension.unload(); +}); + +// Test that registering a listener for a cached response does not cause a crash. +add_task(async function test_cachedResponse() { + if (AppConstants.platform === "android") { + return; + } + Services.prefs.setBoolPref("network.http.rcwn.enabled", false); + + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onHeadersReceived.addListener( + data => { + let filter = browser.webRequest.filterResponseData(data.requestId); + + filter.onstop = event => { + filter.close(); + }; + filter.ondata = event => { + filter.write(event.data); + }; + + if (data.fromCache) { + browser.test.sendMessage("from-cache"); + } + }, + { + urls: ["http://example.com/*/file_sample.html?r=*"], + }, + ["blocking"] + ); + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + }, + }); + + await extension.startup(); + + let url = `${BASE_URL}/data/file_sample.html?r=${Math.random()}`; + await ExtensionTestUtils.fetch(FETCH_ORIGIN, url); + await ExtensionTestUtils.fetch(FETCH_ORIGIN, url); + await extension.awaitMessage("from-cache"); + + await extension.unload(); +}); + +// Test that finishing transferring data doesn't overwrite an existing closing/closed state. +add_task(async function test_late_close() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.webRequest.onBeforeRequest.addListener( + data => { + let filter = browser.webRequest.filterResponseData(data.requestId); + + filter.onstop = event => { + browser.test.fail("Should not receive onstop after close()"); + browser.test.assertEq( + "closed", + filter.status, + "Filter status should still be 'closed'" + ); + browser.test.assertThrows(() => { + filter.close(); + }); + }; + filter.ondata = event => { + filter.write(event.data); + filter.close(); + + browser.test.sendMessage(`done-${data.url}`); + }; + }, + { + urls: ["http://example.com/*/file_sample.html?*"], + }, + ["blocking"] + ); + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + }, + }); + + await extension.startup(); + + // This issue involves a race, so several requests in parallel to increase + // the chances of triggering it. + let urls = []; + for (let i = 0; i < 32; i++) { + urls.push(`${BASE_URL}/data/file_sample.html?r=${Math.random()}`); + } + + await Promise.all( + urls.map(url => ExtensionTestUtils.fetch(FETCH_ORIGIN, url)) + ); + await Promise.all(urls.map(url => extension.awaitMessage(`done-${url}`))); + + await extension.unload(); +}); + +add_task(async function test_permissions() { + let extension = ExtensionTestUtils.loadExtension({ + background() { + browser.test.assertEq( + undefined, + browser.webRequest.filterResponseData, + "filterResponseData is undefined without blocking permissions" + ); + }, + + manifest: { + permissions: ["webRequest", "http://example.com/"], + }, + }); + + await extension.startup(); + await extension.unload(); +}); + +add_task(async function test_invalidId() { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + let filter = browser.webRequest.filterResponseData("34159628"); + + await new Promise(resolve => { + filter.onerror = resolve; + }); + + browser.test.assertEq( + "Invalid request ID", + filter.error, + "Got expected error" + ); + + browser.test.notifyPass("invalid-request-id"); + }, + + manifest: { + permissions: ["webRequest", "webRequestBlocking", "http://example.com/"], + }, + }); + + await extension.startup(); + await extension.awaitFinish("invalid-request-id"); + await extension.unload(); +}); -- cgit v1.2.3