/* 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 the case of a service worker that has a fetch * handler, but no respondWith. In this case, some process called "reset * interception" happens, and the fetch is still carried on by our code. Because * this is a bit of an edge case, it's important to have a test for this case. */ const serviceWorkerFileName = "serviceworker_no_respondWith_in_fetch_handler.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. 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() { 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); // 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. // In this test, we should have redirect markers as well as stop markers, // because this case generates internal redirects. We may want to change // that in the future, or handle this specially in the frontend. // Let's create various arrays to help assert. const parentEndMarkers = parentPairs.map(([_, stopMarker]) => stopMarker); const parentStopMarkers = parentEndMarkers.filter( marker => marker.data.status === "STATUS_STOP" ); const parentRedirectMarkers = parentEndMarkers.filter( marker => marker.data.status === "STATUS_REDIRECT" ); const contentEndMarkers = contentPairs.map(([_, stopMarker]) => stopMarker); const contentStopMarkers = contentEndMarkers.filter( marker => marker.data.status === "STATUS_STOP" ); const contentRedirectMarkers = contentEndMarkers.filter( marker => marker.data.status === "STATUS_REDIRECT" ); // These are the files requested by the page. // We should see markers for the parent thread and the content thread. const expectedFiles = [ // Please take care that the first element is the top level navigation, as // this is special-cased below. "serviceworker_page.html", "firefox-logo-nightly.svg", ].map(filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`); Assert.equal( parentStopMarkers.length, expectedFiles.length, "There should be as many stop markers in the parent process as requested files." ); Assert.equal( parentRedirectMarkers.length, expectedFiles.length * 2, // http -> intercepted, intercepted -> http "There should be twice as many redirect 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." ); // Note: there will no redirect markers in the content process for // ServiceWorker fallbacks request to network. // See Bug 1793940. Assert.equal( contentRedirectMarkers.length, 0, "There should be no redirect markers in the content process than requested files." ); for (const [i, expectedFile] of expectedFiles.entries()) { info( `Checking if "${expectedFile}" if present in the network markers in both processes.` ); const [parentRedirectMarkerIntercept, parentRedirectMarkerReset] = parentRedirectMarkers.filter( marker => marker.data.URI === expectedFile ); const parentStopMarker = parentStopMarkers.find( marker => marker.data.URI === expectedFile ); const contentStopMarker = contentStopMarkers.find( marker => marker.data.URI === expectedFile ); const commonProperties = { name: Expect.stringMatches( `Load \\d+:.*${escapeStringRegexp(expectedFile)}` ), }; Assert.objectContains(parentRedirectMarkerIntercept, commonProperties); Assert.objectContains(parentRedirectMarkerReset, commonProperties); Assert.objectContains(parentStopMarker, commonProperties); Assert.objectContains(contentStopMarker, commonProperties); // Note: there's no check for the contentRedirectMarker, because there's // no marker for a top level navigation redirect in the content process. // We get the full set of properties in this case, because we do an actual // fetch to the network. const commonDataProperties = { type: "Network", status: "STATUS_STOP", URI: expectedFile, requestMethod: "GET", contentType: Expect.stringMatches(/^(text\/html|image\/svg\+xml)$/), startTime: Expect.number(), endTime: Expect.number(), id: Expect.number(), pri: Expect.number(), count: 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(), }; const commonRedirectProperties = { type: "Network", status: "STATUS_REDIRECT", URI: expectedFile, RedirectURI: expectedFile, requestMethod: "GET", contentType: null, startTime: Expect.number(), endTime: Expect.number(), id: Expect.number(), pri: Expect.number(), redirectType: "Internal", isHttpToHttpsRedirect: false, }; if (i === 0) { // The first marker is special cased: this is the top level navigation // serviceworker_page.html, // and in this case we don't have all the same properties. Especially // the innerWindowID information is missing. Assert.objectContainsOnly(parentStopMarker.data, { ...commonDataProperties, // Note that the parent process has the "cache" information, but not the content // process. See Bug 1544821. // Also, 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)$/), }); Assert.objectContainsOnly(contentStopMarker.data, commonDataProperties); Assert.objectContainsOnly(parentRedirectMarkerIntercept.data, { ...commonRedirectProperties, redirectId: parentRedirectMarkerReset.data.id, cache: "Unresolved", }); Assert.objectContainsOnly(parentRedirectMarkerReset.data, { ...commonRedirectProperties, redirectId: parentStopMarker.data.id, }); // Note: there's no check for the contentRedirectMarker, because there's // no marker for a top level navigation redirect in the content process. } else { // This is the other file firefox-logo-nightly.svg. Assert.objectContainsOnly(parentStopMarker.data, { ...commonDataProperties, // 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)$/), innerWindowID: Expect.number(), }); Assert.objectContains(contentStopMarker, commonProperties); Assert.objectContainsOnly(contentStopMarker.data, { ...commonDataProperties, innerWindowID: Expect.number(), }); Assert.objectContainsOnly(parentRedirectMarkerIntercept.data, { ...commonRedirectProperties, innerWindowID: Expect.number(), redirectId: parentRedirectMarkerReset.data.id, cache: "Unresolved", }); Assert.objectContainsOnly(parentRedirectMarkerReset.data, { ...commonRedirectProperties, innerWindowID: Expect.number(), redirectId: parentStopMarker.data.id, }); } } }); });