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 caching service worker. This service worker will
* fetch and store requests at install time, and serve them when the page
* requests them.
*/
const serviceWorkerFileName = "serviceworker_cache_first.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"
);
startProfilerForMarkerTests();
const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_register.html`;
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
const contentPid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
await SpecialPowers.spawn(
contentBrowser,
[serviceWorkerFileName],
async function (serviceWorkerFileName) {
await content.wrappedJSObject.registerServiceWorkerAndWait(
serviceWorkerFileName
);
}
);
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);
// Here are a few sanity checks.
ok(
serviceWorkerParentThread,
"We should find a thread for the service worker."
);
Assert.notEqual(
serviceWorkerParentThread.pid,
parentThread.pid,
"We should have a different pid than the parent thread."
);
Assert.notEqual(
serviceWorkerParentThread.tid,
parentThread.tid,
"We should have a different tid than the parent thread."
);
// 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."
);
// By logging a few information about the threads we make debugging easier.
logInformationForThread("parentThread information", parentThread);
logInformationForThread("contentThread information", contentThread);
logInformationForThread(
"serviceWorkerParentThread information",
serviceWorkerParentThread
);
// Now let's check the marker payloads.
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.
const parentStopMarkers = parentPairs.map(([_, stopMarker]) => stopMarker);
// These are the files cached by the service worker. We should see markers
// for both the parent thread and the service worker thread.
const expectedFiles = [
"serviceworker_page.html",
"firefox-logo-nightly.svg",
].map(filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`);
for (const expectedFile of expectedFiles) {
info(
`Checking if "${expectedFile}" is present in the network markers in both processes.`
);
const parentMarker = parentStopMarkers.find(
marker => marker.data.URI === expectedFile
);
const expectedProperties = {
name: Expect.stringMatches(
`Load \\d+:.*${escapeStringRegexp(expectedFile)}`
),
data: Expect.objectContains({
status: "STATUS_STOP",
URI: expectedFile,
requestMethod: "GET",
contentType: Expect.stringMatches(/^(text\/html|image\/svg\+xml)$/),
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(),
}),
};
Assert.objectContains(parentMarker, expectedProperties);
}
});
});
add_task(async function test_network_markers_service_worker_use() {
// In this test we request an HTML file that itself contains resources that
// are redirected.
Assert.ok(
!Services.profiler.IsActive(),
"The profiler is not currently active"
);
startProfilerForMarkerTests();
const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_page.html`;
await BrowserTestUtils.withNewTab(url, async contentBrowser => {
const contentPid = await SpecialPowers.spawn(
contentBrowser,
[],
() => Services.appinfo.processID
);
const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
contentPid
);
// By logging a few information about the threads we make debugging easier.
logInformationForThread("parentThread information", parentThread);
logInformationForThread("contentThread information", contentThread);
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);
// Here are some logs to ease debugging.
info(
"Parent network markers: " + JSON.stringify(parentNetworkMarkers, null, 2)
);
info(
"Content network markers: " +
JSON.stringify(contentNetworkMarkers, null, 2)
);
const parentPairs = getPairsOfNetworkMarkers(parentNetworkMarkers);
const contentPairs = getPairsOfNetworkMarkers(contentNetworkMarkers);
// These are the files cached by the service worker. We should see markers
// for the parent thread and the content thread.
const expectedFiles = [
"serviceworker_page.html",
"firefox-logo-nightly.svg",
].map(filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`);
// 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.`
)
);
// Let's look at all pairs and make sure we requested all expected files.
const parentEndMarkers = parentPairs.map(([_, endMarker]) => endMarker);
const contentStopMarkers = contentPairs.map(
([_, stopMarker]) => stopMarker
);
Assert.equal(
parentEndMarkers.length,
expectedFiles.length * 2, // one redirect + one stop
"There should be twice as many end markers in the parent process as requested files."
);
Assert.equal(
contentStopMarkers.length,
expectedFiles.length,
"There should be as many stop markers in the content process as requested files."
);
for (const [i, expectedFile] of expectedFiles.entries()) {
info(
`Checking if "${expectedFile}" if present in the network markers in both processes.`
);
const [parentRedirectMarker, parentStopMarker] = parentEndMarkers.filter(
marker => marker.data.URI === expectedFile
);
const contentMarker = contentStopMarkers.find(
marker => marker.data.URI === expectedFile
);
const commonDataProperties = {
type: "Network",
URI: expectedFile,
requestMethod: "GET",
contentType: Expect.stringMatches(/^(text\/html|image\/svg\+xml)$/),
startTime: Expect.number(),
endTime: Expect.number(),
id: Expect.number(),
pri: Expect.number(),
};
const expectedProperties = {
name: Expect.stringMatches(
`Load \\d+:.*${escapeStringRegexp(expectedFile)}`
),
};
Assert.objectContains(parentRedirectMarker, expectedProperties);
Assert.objectContains(parentStopMarker, expectedProperties);
Assert.objectContains(contentMarker, expectedProperties);
if (i === 0) {
// This is the top level navigation, the HTML file.
Assert.objectContainsOnly(parentRedirectMarker.data, {
...commonDataProperties,
status: "STATUS_REDIRECT",
contentType: null,
cache: "Unresolved",
RedirectURI: expectedFile,
redirectType: "Internal",
redirectId: parentStopMarker.data.id,
isHttpToHttpsRedirect: false,
});
Assert.objectContainsOnly(parentStopMarker.data, {
...commonDataProperties,
status: "STATUS_STOP",
});
Assert.objectContainsOnly(contentMarker.data, {
...commonDataProperties,
status: "STATUS_STOP",
});
} else {
Assert.objectContainsOnly(parentRedirectMarker.data, {
...commonDataProperties,
status: "STATUS_REDIRECT",
contentType: null,
cache: "Unresolved",
innerWindowID: Expect.number(),
RedirectURI: expectedFile,
redirectType: "Internal",
redirectId: parentStopMarker.data.id,
isHttpToHttpsRedirect: false,
});
Assert.objectContainsOnly(
parentStopMarker.data,
// Note: in the future we may have more properties. We're using the
// "Only" flavor of the matcher so that we don't forget to update this
// test when this changes.
{
...commonDataProperties,
innerWindowID: Expect.number(),
status: "STATUS_STOP",
}
);
Assert.objectContainsOnly(contentMarker.data, {
...commonDataProperties,
innerWindowID: Expect.number(),
status: "STATUS_STOP",
});
}
}
});
});