Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: true
- Manifest: dom/serviceworkers/test/browser-dFPI.toml includes dom/serviceworkers/test/browser-common.toml
- Manifest: dom/serviceworkers/test/browser.toml includes dom/serviceworkers/test/browser-common.toml
"use strict";
const emptyDoc = BASE_URI + "empty.html";
const fakeDoc = BASE_URI + "fake.html";
const helloDoc = BASE_URI + "hello.html";
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) => {
Assert.lessOrEqual(
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") {
resolve();
return;
}
worker.addEventListener("statechange", () => {
if (worker.state === "activated") {
resolve();
}
});
});
await new Promise(resolve => {
if (content.navigator.serviceWorker.controller) {
resolve();
return;
}
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);
});