summaryrefslogtreecommitdiffstats
path: root/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js')
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js480
1 files changed, 480 insertions, 0 deletions
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js
new file mode 100644
index 0000000000..060592840a
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js
@@ -0,0 +1,480 @@
+/* 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(),
+ }),
+ });
+ });
+});