summaryrefslogtreecommitdiffstats
path: root/dom/serviceworkers/test/browser_devtools_serviceworker_interception.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/serviceworkers/test/browser_devtools_serviceworker_interception.js')
-rw-r--r--dom/serviceworkers/test/browser_devtools_serviceworker_interception.js264
1 files changed, 264 insertions, 0 deletions
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);
+});