From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- .../browser_devtools_serviceworker_interception.js | 264 +++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 dom/serviceworkers/test/browser_devtools_serviceworker_interception.js (limited to 'dom/serviceworkers/test/browser_devtools_serviceworker_interception.js') diff --git a/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js b/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js new file mode 100644 index 0000000000..fc31159116 --- /dev/null +++ b/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js @@ -0,0 +1,264 @@ +"use strict"; + +const BASE_URI = "http://mochi.test:8888/browser/dom/serviceworkers/test/"; +const emptyDoc = BASE_URI + "empty.html"; +const fakeDoc = BASE_URI + "fake.html"; +const helloDoc = BASE_URI + "hello.html"; + +const CROSS_URI = "http://example.com/browser/dom/serviceworkers/test/"; +const crossRedirect = CROSS_URI + "redirect"; +const crossHelloDoc = CROSS_URI + "hello.html"; + +const sw = BASE_URI + "fetch.js"; + +async function checkObserver(aInput) { + let interceptedChannel = null; + + // We always get two channels which receive the "http-on-stop-request" + // notification if the service worker hijacks the request and respondWith an + // another fetch. One is for the "outer" window request when the other one is + // for the "inner" service worker request. Therefore, distinguish them by the + // order. + let waitForSecondOnStopRequest = aInput.intercepted; + + let promiseResolve; + + function observer(aSubject) { + let channel = aSubject.QueryInterface(Ci.nsIChannel); + // Since we cannot make sure that the network event triggered by the fetch() + // in this testcase is the very next event processed by ObserverService, we + // have to wait until we catch the one we want. + if (!channel.URI.spec.includes(aInput.expectedURL)) { + return; + } + + if (waitForSecondOnStopRequest) { + waitForSecondOnStopRequest = false; + return; + } + + // Wait for the service worker to intercept the request if it's expected to + // be intercepted + if (aInput.intercepted && interceptedChannel === null) { + return; + } else if (interceptedChannel) { + ok( + aInput.intercepted, + "Service worker intercepted the channel as expected" + ); + } else { + ok(!aInput.intercepted, "The channel doesn't be intercepted"); + } + + var tc = interceptedChannel + ? interceptedChannel.QueryInterface(Ci.nsITimedChannel) + : aSubject.QueryInterface(Ci.nsITimedChannel); + + // Check service worker related timings. + var serviceWorkerTimings = [ + { + start: tc.launchServiceWorkerStartTime, + end: tc.launchServiceWorkerEndTime, + }, + { + start: tc.dispatchFetchEventStartTime, + end: tc.dispatchFetchEventEndTime, + }, + { start: tc.handleFetchEventStartTime, end: tc.handleFetchEventEndTime }, + ]; + if (!aInput.swPresent) { + serviceWorkerTimings.forEach(aTimings => { + is(aTimings.start, 0, "SW timings should be 0."); + is(aTimings.end, 0, "SW timings should be 0."); + }); + } + + // Check network related timings. + var networkTimings = [ + tc.domainLookupStartTime, + tc.domainLookupEndTime, + tc.connectStartTime, + tc.connectEndTime, + tc.requestStartTime, + tc.responseStartTime, + tc.responseEndTime, + ]; + if (aInput.fetch) { + networkTimings.reduce((aPreviousTiming, aCurrentTiming) => { + ok(aPreviousTiming <= aCurrentTiming, "Checking network timings"); + return aCurrentTiming; + }); + } else { + networkTimings.forEach(aTiming => + is(aTiming, 0, "Network timings should be 0.") + ); + } + + interceptedChannel = null; + Services.obs.removeObserver(observer, topic); + promiseResolve(); + } + + function addInterceptedChannel(aSubject) { + let channel = aSubject.QueryInterface(Ci.nsIChannel); + if (!channel.URI.spec.includes(aInput.url)) { + return; + } + + // Hold the interceptedChannel until checking timing information. + // Note: It's a interceptedChannel in the type of httpChannel + interceptedChannel = channel; + Services.obs.removeObserver(addInterceptedChannel, topic_SW); + } + + const topic = "http-on-stop-request"; + const topic_SW = "service-worker-synthesized-response"; + + Services.obs.addObserver(observer, topic); + if (aInput.intercepted) { + Services.obs.addObserver(addInterceptedChannel, topic_SW); + } + + await new Promise(resolve => { + promiseResolve = resolve; + }); +} + +async function contentFetch(aURL) { + if (aURL.includes("redirect")) { + await content.window.fetch(aURL, { mode: "no-cors" }); + return; + } + await content.window.fetch(aURL); +} + +// The observer topics are fired in the parent process in parent-intercept +// and the content process in child-intercept. This function will handle running +// the check in the correct process. Note that it will block until the observers +// are notified. +async function fetchAndCheckObservers( + aFetchBrowser, + aObserverBrowser, + aTestCase +) { + let promise = null; + + promise = checkObserver(aTestCase); + + await SpecialPowers.spawn(aFetchBrowser, [aTestCase.url], contentFetch); + await promise; +} + +async function registerSWAndWaitForActive(aServiceWorker) { + let swr = await content.navigator.serviceWorker.register(aServiceWorker, { + scope: "empty.html", + }); + await new Promise(resolve => { + let worker = swr.installing || swr.waiting || swr.active; + if (worker.state === "activated") { + return resolve(); + } + + worker.addEventListener("statechange", () => { + if (worker.state === "activated") { + return resolve(); + } + }); + }); + + await new Promise(resolve => { + if (content.navigator.serviceWorker.controller) { + return resolve(); + } + + content.navigator.serviceWorker.addEventListener( + "controllerchange", + resolve, + { once: true } + ); + }); +} + +async function unregisterSW() { + let swr = await content.navigator.serviceWorker.getRegistration(); + swr.unregister(); +} + +add_task(async function test_serivce_worker_interception() { + info("Setting the prefs to having e10s enabled"); + await SpecialPowers.pushPrefEnv({ + set: [ + // Make sure observer and testing function run in the same process + ["dom.ipc.processCount", 1], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + }); + + waitForExplicitFinish(); + + info("Open the tab"); + let tab = BrowserTestUtils.addTab(gBrowser, emptyDoc); + let tabBrowser = gBrowser.getBrowserForTab(tab); + await BrowserTestUtils.browserLoaded(tabBrowser); + + info("Open the tab for observing"); + let tab_observer = BrowserTestUtils.addTab(gBrowser, emptyDoc); + let tabBrowser_observer = gBrowser.getBrowserForTab(tab_observer); + await BrowserTestUtils.browserLoaded(tabBrowser_observer); + + let testcases = [ + { + url: helloDoc, + expectedURL: helloDoc, + swPresent: false, + intercepted: false, + fetch: true, + }, + { + url: fakeDoc, + expectedURL: helloDoc, + swPresent: true, + intercepted: true, + fetch: false, // should use HTTP cache + }, + { + // Bypass http cache + url: helloDoc + "?ForBypassingHttpCache=" + Date.now(), + expectedURL: helloDoc, + swPresent: true, + intercepted: false, + fetch: true, + }, + { + // no-cors mode redirect to no-cors mode (trigger internal redirect) + url: crossRedirect + "?url=" + crossHelloDoc + "&mode=no-cors", + expectedURL: crossHelloDoc, + swPresent: true, + redirect: "hello.html", + intercepted: true, + fetch: true, + }, + ]; + + info("Test 1: Verify simple fetch"); + await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[0]); + + info("Register a service worker"); + await SpecialPowers.spawn(tabBrowser, [sw], registerSWAndWaitForActive); + + info("Test 2: Verify simple hijack"); + await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[1]); + + info("Test 3: Verify fetch without using http cache"); + await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[2]); + + info("Test 4: make a internal redirect"); + await fetchAndCheckObservers(tabBrowser, tabBrowser_observer, testcases[3]); + + info("Clean up"); + await SpecialPowers.spawn(tabBrowser, [undefined], unregisterSW); + + gBrowser.removeTab(tab); + gBrowser.removeTab(tab_observer); +}); -- cgit v1.2.3