Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Test that we emit network markers accordingly.
* In this file we'll test a service worker that returns a synthetized response.
* This means the service worker will make up a response by itself.
*/
const serviceWorkerFileName = "serviceworker_synthetized_response.js";
registerCleanupFunction(() => SpecialPowers.removeAllServiceWorkerData());
add_task(async function test_network_markers_service_worker_setup() {
// Disabling cache makes the result more predictable. Also this makes things
// simpler when dealing with service workers.
await SpecialPowers.pushPrefEnv({
set: [
["browser.cache.disk.enable", false],
["browser.cache.memory.enable", false],
],
});
});
add_task(async function test_network_markers_service_worker_register() {
// In this first step, we request an HTML page that will register a service
// worker. We'll wait until the service worker is fully installed before
// checking various things.
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_register.html`;
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
await SpecialPowers.spawn(
contentBrowser,
[serviceWorkerFileName],
async function (serviceWorkerFileName) {
await content.wrappedJSObject.registerServiceWorkerAndWait(
serviceWorkerFileName
);
}
);
// Let's make sure we actually have a registered service workers.
const workers = await SpecialPowers.registeredServiceWorkers();
Assert.equal(
workers.length,
1,
"One service worker should be properly registered."
);
});
});
add_task(async function test_network_markers_service_worker_use() {
// In this test, we'll first load a plain html file, then do some fetch
// requests in the context of the page. One request is served with a
// synthetized response, the other request is served with a real "fetch" done
// by the service worker.
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
startProfilerForMarkerTests();
const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_simple.html`;
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
const contentPid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
await SpecialPowers.spawn(contentBrowser, [], async () => {
// This request is served directly by the service worker as a synthetized response.
await content
.fetch("firefox-generated.svg")
.then(res => res.arrayBuffer());
// This request is served by a fetch done inside the service worker.
await content
.fetch("firefox-logo-nightly.svg")
.then(res => res.arrayBuffer());
});
const { parentThread, contentThread, profile } =
await stopProfilerNowAndGetThreads(contentPid);
// The service worker work happens in a third "thread" or process, let's try
// to find it.
// Currently the fetches happen on the main thread for the content process,
// this may change in the future and we may have to adapt this function.
// Also please note this isn't necessarily the same content process as the
// ones for the tab.
const { serviceWorkerParentThread } = findServiceWorkerThreads(profile);
ok(
serviceWorkerParentThread,
"We should find a thread for the service worker."
);
// By logging a few information about the threads we make debugging easier.
logInformationForThread("parentThread information", parentThread);
logInformationForThread("contentThread information", contentThread);
logInformationForThread(
"serviceWorkerParentThread information",
serviceWorkerParentThread
);
const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread)
// When we load a page, Firefox will check the service worker freshness
// after a few seconds. So when the test lasts a long time (with some test
// environments) we might see spurious markers about that that we're not
// interesting in in this part of the test. They're only present in the
// parent process.
.filter(marker => !marker.data.URI.includes(serviceWorkerFileName));
const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
const serviceWorkerNetworkMarkers = getInflatedNetworkMarkers(
serviceWorkerParentThread
);
// Some more logs for debugging purposes.
info(
"Parent network markers: " + JSON.stringify(parentNetworkMarkers, null, 2)
);
info(
"Content network markers: " +
JSON.stringify(contentNetworkMarkers, null, 2)
);
info(
"Serviceworker network markers: " +
JSON.stringify(serviceWorkerNetworkMarkers, null, 2)
);
const parentPairs = getPairsOfNetworkMarkers(parentNetworkMarkers);
const contentPairs = getPairsOfNetworkMarkers(contentNetworkMarkers);
const serviceWorkerPairs = getPairsOfNetworkMarkers(
serviceWorkerNetworkMarkers
);
// First, make sure we properly matched all start with stop markers. This
// means that both arrays should contain only arrays of 2 elements.
parentPairs.forEach(pair =>
Assert.equal(
pair.length,
2,
`For the URL ${pair[0].data.URI} we should get 2 markers in the parent process.`
)
);
contentPairs.forEach(pair =>
Assert.equal(
pair.length,
2,
`For the URL ${pair[0].data.URI} we should get 2 markers in the content process.`
)
);
serviceWorkerPairs.forEach(pair =>
Assert.equal(
pair.length,
2,
`For the URL ${pair[0].data.URI} we should get 2 markers in the service worker process.`
)
);
// Let's look at all pairs and make sure we requested all expected files.
// In this test, we should have redirect markers as well as stop markers,
// because this case generates internal redirects.
// Let's create various arrays to help assert.
let parentStopMarkers = parentPairs.map(([_, stopMarker]) => stopMarker);
const contentStopMarkers = contentPairs.map(
([_, stopMarker]) => stopMarker
);
// In this test we have very different results in the various threads, so
// we'll assert every case separately.
// A simple function to help constructing better assertions:
const fullUrl = filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`;
{
// In the parent process, we have 8 network markers:
// - twice the html file -- because it's not cached by the SW, we get the
// marker both for the initial request and for the request initied from the
// SW.
// - twice the firefox svg file -- similar situation
// - once the generated svg file -- this one isn't fetched by the SW but
// rather forged directly, so there's no "second fetch", and thus we have
// only one marker.
// - for each of these files, we have first an internal redirect from the
// main channel to the service worker. => 3 redirect markers more.
Assert.equal(
parentStopMarkers.length,
8, // 3 html files, 3 firefox svg files, 2 generated svg file
"There should be 8 stop markers in the parent process."
);
// The "1" requests are the initial requests that are intercepted, coming
// from the web page, while the "2" requests are requests to the network,
// coming from the service worker. The 1 were requested before 2, 2 ends
// before 1.
// "Intercept" requests are the internal redirects from the main channel
// to the service worker. They happen before others.
const [
htmlFetchIntercept,
htmlFetch1,
htmlFetch2,
generatedSvgIntercept,
generatedSvgFetch,
firefoxSvgIntercept,
firefoxSvgFetch1,
firefoxSvgFetch2,
] = parentStopMarkers;
/* ----- /HTML FILE ---- */
Assert.objectContains(htmlFetchIntercept, {
name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_REDIRECT",
URI: fullUrl("serviceworker_simple.html"),
requestMethod: "GET",
contentType: null,
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
redirectId: htmlFetch1.data.id,
redirectType: "Internal",
isHttpToHttpsRedirect: false,
RedirectURI: fullUrl("serviceworker_simple.html"),
cache: "Unresolved",
}),
});
Assert.objectContains(htmlFetch1, {
name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("serviceworker_simple.html"),
requestMethod: "GET",
contentType: "text/html",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
}),
});
Assert.objectContains(htmlFetch2, {
name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("serviceworker_simple.html"),
requestMethod: "GET",
contentType: "text/html",
// Because the request races with the cache, these 2 values are valid:
// "Missed" when the cache answered before we get a result from the network.
// "Unresolved" when we got a response from the network before the cache subsystem.
cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
startTime: Expect.number(),
endTime: Expect.number(),
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
id: Expect.number(),
count: Expect.number(),
pri: Expect.number(),
}),
});
/* ----- /HTML FILE ---- */
/* ----- GENERATED SVG FILE ---- */
Assert.objectContains(generatedSvgIntercept, {
name: Expect.stringMatches(/Load \d+:.*firefox-generated.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_REDIRECT",
URI: fullUrl("firefox-generated.svg"),
requestMethod: "GET",
contentType: null,
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
redirectId: generatedSvgFetch.data.id,
redirectType: "Internal",
isHttpToHttpsRedirect: false,
RedirectURI: fullUrl("firefox-generated.svg"),
cache: "Unresolved",
innerWindowID: Expect.number(),
}),
});
Assert.objectContains(generatedSvgFetch, {
name: Expect.stringMatches(/Load \d+:.*firefox-generated.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("firefox-generated.svg"),
requestMethod: "GET",
contentType: "image/svg+xml",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
innerWindowID: Expect.number(),
}),
});
/* ----- ∕GENERATED SVG FILE ---- */
/* ----- REQUESTED SVG FILE ---- */
Assert.objectContains(firefoxSvgIntercept, {
name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_REDIRECT",
URI: fullUrl("firefox-logo-nightly.svg"),
requestMethod: "GET",
contentType: null,
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
redirectId: firefoxSvgFetch1.data.id,
redirectType: "Internal",
isHttpToHttpsRedirect: false,
RedirectURI: fullUrl("firefox-logo-nightly.svg"),
cache: "Unresolved",
innerWindowID: Expect.number(),
}),
});
Assert.objectContains(firefoxSvgFetch1, {
name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("firefox-logo-nightly.svg"),
requestMethod: "GET",
contentType: "image/svg+xml",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
innerWindowID: Expect.number(),
}),
});
Assert.objectContains(firefoxSvgFetch2, {
name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("firefox-logo-nightly.svg"),
requestMethod: "GET",
contentType: "image/svg+xml",
// Because the request races with the cache, these 2 values are valid:
// "Missed" when the cache answered before we get a result from the network.
// "Unresolved" when we got a response from the network before the cache subsystem.
cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
startTime: Expect.number(),
endTime: Expect.number(),
domainLookupStart: Expect.number(),
domainLookupEnd: Expect.number(),
connectStart: Expect.number(),
tcpConnectEnd: Expect.number(),
connectEnd: Expect.number(),
requestStart: Expect.number(),
responseStart: Expect.number(),
responseEnd: Expect.number(),
id: Expect.number(),
count: Expect.number(),
pri: Expect.number(),
// Note: no innerWindowID here, is that a bug?
}),
});
/* ----- ∕REQUESTED SVG FILE ---- */
}
// It's possible that the service worker thread IS the content thread, in
// that case we'll get all markers in the same thread.
// The "1" requests are the initial requests that are intercepted, coming
// from the web page, while the "2" requests are the requests coming from
// the service worker.
let htmlFetch1, generatedSvgFetch1, firefoxSvgFetch1;
// First, let's handle the case where the threads are different:
if (serviceWorkerParentThread !== contentThread) {
// In the content process (that is the process for the web page), we have
// 3 network markers:
// - 1 for the HTML page
// - 1 for the generated svg file
// - 1 for the firefox svg file
// Indeed, the service worker interception is invisible from the context
// of the web page, so we just get 3 "normal" requests. However these
// requests will miss all timing information, because they're hidden by
// the service worker interception. We may want to fix this...
Assert.equal(
contentStopMarkers.length,
3, // 1 for each file
"There should be 3 stop markers in the content process."
);
[htmlFetch1, generatedSvgFetch1, firefoxSvgFetch1] = contentStopMarkers;
} else {
// Else case: the service worker parent thread IS the content thread
// (note: this is always the case with fission). In that case all network
// markers tested in the above block are together in the same object.
Assert.equal(
contentStopMarkers.length,
5,
"There should be 5 stop markers in the combined process (containing both the content page and the service worker)"
);
// Because of how the test is done, these markers are ordered by the
// position of the START markers.
[
// For the htmlFetch request, note that 2 is before 1, because that's
// the top level navigation. Indeed for the top level navigation
// everything happens first in the main process, possibly before a
// content process even exists, and the content process is merely
// notified at the end.
htmlFetch1,
generatedSvgFetch1,
firefoxSvgFetch1,
] = contentStopMarkers;
}
// Let's test first the markers coming from the content page.
Assert.objectContains(htmlFetch1, {
name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("serviceworker_simple.html"),
requestMethod: "GET",
contentType: "text/html",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
}),
});
Assert.objectContains(generatedSvgFetch1, {
name: Expect.stringMatches(/Load \d+:.*firefox-generated.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("firefox-generated.svg"),
requestMethod: "GET",
contentType: "image/svg+xml",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
innerWindowID: Expect.number(),
}),
});
Assert.objectContains(firefoxSvgFetch1, {
name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/),
data: Expect.objectContainsOnly({
type: "Network",
status: "STATUS_STOP",
URI: fullUrl("firefox-logo-nightly.svg"),
requestMethod: "GET",
contentType: "image/svg+xml",
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
innerWindowID: Expect.number(),
}),
});
});
});