summaryrefslogtreecommitdiffstats
path: root/tools/profiler/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/profiler/tests/browser/browser.ini102
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_ipcmessages.js109
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_jsallocations.js78
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_nostacksampling.js76
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_preferencereads.js116
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_cancel.js71
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_private_browsing.js91
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_redirect.js341
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_serviceworker_cache_first.js388
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_fetch_handler.js218
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js296
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js558
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_simple.js81
-rw-r--r--tools/profiler/tests/browser/browser_test_marker_network_sts.js130
-rw-r--r--tools/profiler/tests/browser/browser_test_markers_gc_cc.js49
-rw-r--r--tools/profiler/tests/browser/browser_test_markers_parent_process.js37
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_capture_by_pid.js206
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_fission.js196
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js83
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js134
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_slow_capture.js104
-rw-r--r--tools/profiler/tests/browser/do_work_500ms.html41
-rw-r--r--tools/profiler/tests/browser/firefox-logo-nightly.svg1
-rw-r--r--tools/profiler/tests/browser/head.js159
-rw-r--r--tools/profiler/tests/browser/multi_frame.html11
-rw-r--r--tools/profiler/tests/browser/page_with_resources.html11
-rw-r--r--tools/profiler/tests/browser/redirect.sjs8
-rw-r--r--tools/profiler/tests/browser/serviceworkers/firefox-logo-nightly.svg1
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker-utils.js39
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_cache_first.js34
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_no_fetch_handler.js4
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_no_respondWith_in_fetch_handler.js9
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_page.html10
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_register.html9
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_simple.html9
-rw-r--r--tools/profiler/tests/browser/serviceworkers/serviceworker_synthetized_response.js27
-rw-r--r--tools/profiler/tests/browser/simple.html9
-rw-r--r--tools/profiler/tests/browser/single_frame.html10
38 files changed, 3856 insertions, 0 deletions
diff --git a/tools/profiler/tests/browser/browser.ini b/tools/profiler/tests/browser/browser.ini
new file mode 100644
index 0000000000..9a02b673d2
--- /dev/null
+++ b/tools/profiler/tests/browser/browser.ini
@@ -0,0 +1,102 @@
+[DEFAULT]
+skip-if = tsan # Bug 1804081 - TSan times out on pretty much all of these tests
+support-files =
+ ../shared-head.js
+ head.js
+
+[browser_test_feature_ipcmessages.js]
+support-files = simple.html
+
+[browser_test_feature_jsallocations.js]
+support-files = do_work_500ms.html
+
+[browser_test_feature_nostacksampling.js]
+support-files = do_work_500ms.html
+
+[browser_test_feature_preferencereads.js]
+support-files = single_frame.html
+
+[browser_test_markers_parent_process.js]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+
+[browser_test_markers_gc_cc.js]
+
+[browser_test_profile_capture_by_pid.js]
+skip-if = os == "win" && os_version == "6.1" # No thread names on win7, needed for these tests
+https_first_disabled = true
+support-files = single_frame.html
+
+[browser_test_profile_fission.js]
+support-files = single_frame.html
+
+[browser_test_profile_single_frame_page_info.js]
+https_first_disabled = true
+support-files = single_frame.html
+
+[browser_test_profile_slow_capture.js]
+https_first_disabled = true
+support-files = single_frame.html
+skip-if = !debug
+
+[browser_test_profile_multi_frame_page_info.js]
+https_first_disabled = true
+support-files =
+ multi_frame.html
+ single_frame.html
+
+[browser_test_marker_network_simple.js]
+https_first_disabled = true
+support-files = simple.html
+
+[browser_test_marker_network_private_browsing.js]
+support-files = simple.html
+
+[browser_test_marker_network_cancel.js]
+https_first_disabled = true
+support-files = simple.html
+
+[browser_test_marker_network_sts.js]
+support-files = simple.html
+
+[browser_test_marker_network_redirect.js]
+https_first_disabled = true
+support-files =
+ redirect.sjs
+ simple.html
+ page_with_resources.html
+ firefox-logo-nightly.svg
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+
+[browser_test_marker_network_serviceworker_cache_first.js]
+support-files =
+ serviceworkers/serviceworker-utils.js
+ serviceworkers/serviceworker_register.html
+ serviceworkers/serviceworker_page.html
+ serviceworkers/firefox-logo-nightly.svg
+ serviceworkers/serviceworker_cache_first.js
+
+[browser_test_marker_network_serviceworker_no_fetch_handler.js]
+support-files =
+ serviceworkers/serviceworker-utils.js
+ serviceworkers/serviceworker_register.html
+ serviceworkers/serviceworker_page.html
+ serviceworkers/firefox-logo-nightly.svg
+ serviceworkers/serviceworker_no_fetch_handler.js
+
+[browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js]
+support-files =
+ serviceworkers/serviceworker-utils.js
+ serviceworkers/serviceworker_register.html
+ serviceworkers/serviceworker_page.html
+ serviceworkers/firefox-logo-nightly.svg
+ serviceworkers/serviceworker_no_respondWith_in_fetch_handler.js
+
+[browser_test_marker_network_serviceworker_synthetized_response.js]
+support-files =
+ serviceworkers/serviceworker-utils.js
+ serviceworkers/serviceworker_register.html
+ serviceworkers/serviceworker_simple.html
+ serviceworkers/firefox-logo-nightly.svg
+ serviceworkers/serviceworker_synthetized_response.js
diff --git a/tools/profiler/tests/browser/browser_test_feature_ipcmessages.js b/tools/profiler/tests/browser/browser_test_feature_ipcmessages.js
new file mode 100644
index 0000000000..4691be6434
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_ipcmessages.js
@@ -0,0 +1,109 @@
+/* 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/. */
+
+requestLongerTimeout(10);
+
+async function waitForLoad() {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return new Promise(function(resolve) {
+ if (content.document.readyState !== "complete") {
+ content.document.addEventListener("readystatechange", () => {
+ if (content.document.readyState === "complete") {
+ resolve();
+ }
+ });
+ } else {
+ resolve();
+ }
+ });
+ });
+}
+
+/**
+ * Test the IPCMessages feature.
+ */
+add_task(async function test_profile_feature_ipcmessges() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ const url = BASE_URL + "simple.html";
+
+ info("Open a tab while profiling IPC messages.");
+ await startProfiler({ features: ["js", "ipcmessages"] });
+ info("Started the profiler sucessfully! Now, let's open a tab.");
+
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ info("We opened a tab!");
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+ info("Now let's wait until it's fully loaded.");
+ await waitForLoad();
+
+ info(
+ "Check that some IPC profile markers were generated when " +
+ "the feature is enabled."
+ );
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await waitSamplingAndStopProfilerAndGetThreads(contentPid);
+
+ Assert.greater(
+ getPayloadsOfType(parentThread, "IPC").length,
+ 0,
+ "IPC profile markers were recorded for the parent process' main " +
+ "thread when the IPCMessages feature was turned on."
+ );
+
+ Assert.greater(
+ getPayloadsOfType(contentThread, "IPC").length,
+ 0,
+ "IPC profile markers were recorded for the content process' main " +
+ "thread when the IPCMessages feature was turned on."
+ );
+ }
+ });
+
+ info("Now open a tab without profiling IPC messages.");
+ await startProfiler({ features: ["js"] });
+
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+ await waitForLoad();
+
+ info(
+ "Check that no IPC profile markers were recorded when the " +
+ "feature is turned off."
+ );
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await waitSamplingAndStopProfilerAndGetThreads(contentPid);
+ Assert.equal(
+ getPayloadsOfType(parentThread, "IPC").length,
+ 0,
+ "No IPC profile markers were recorded for the parent process' main " +
+ "thread when the IPCMessages feature was turned off."
+ );
+
+ Assert.equal(
+ getPayloadsOfType(contentThread, "IPC").length,
+ 0,
+ "No IPC profile markers were recorded for the content process' main " +
+ "thread when the IPCMessages feature was turned off."
+ );
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_feature_jsallocations.js b/tools/profiler/tests/browser/browser_test_feature_jsallocations.js
new file mode 100644
index 0000000000..0ae3b53a14
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_jsallocations.js
@@ -0,0 +1,78 @@
+/* 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/. */
+
+requestLongerTimeout(10);
+
+/**
+ * Test the JS Allocations feature. This is done as a browser test to ensure that
+ * we realistically try out how the JS allocations are running. This ensures that
+ * we are collecting allocations for the content process and the parent process.
+ */
+add_task(async function test_profile_feature_jsallocations() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ await startProfiler({ features: ["js", "jsallocations"] });
+
+ const url = BASE_URL + "do_work_500ms.html";
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ // Wait 500ms so that the tab finishes executing.
+ await wait(500);
+
+ // Check that we can get some allocations when the feature is turned on.
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await waitSamplingAndStopProfilerAndGetThreads(contentPid);
+ Assert.greater(
+ getPayloadsOfType(parentThread, "JS allocation").length,
+ 0,
+ "Allocations were recorded for the parent process' main thread when the " +
+ "JS Allocation feature was turned on."
+ );
+ Assert.greater(
+ getPayloadsOfType(contentThread, "JS allocation").length,
+ 0,
+ "Allocations were recorded for the content process' main thread when the " +
+ "JS Allocation feature was turned on."
+ );
+ }
+
+ await startProfiler({ features: ["js"] });
+ // Now reload the tab with a clean run.
+ gBrowser.reload();
+ await wait(500);
+
+ // Check that no allocations were recorded, and allocation tracking was correctly
+ // turned off.
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await waitSamplingAndStopProfilerAndGetThreads(contentPid);
+ Assert.equal(
+ getPayloadsOfType(parentThread, "JS allocation").length,
+ 0,
+ "No allocations were recorded for the parent processes' main thread when " +
+ "JS allocation was not turned on."
+ );
+
+ Assert.equal(
+ getPayloadsOfType(contentThread, "JS allocation").length,
+ 0,
+ "No allocations were recorded for the content processes' main thread when " +
+ "JS allocation was not turned on."
+ );
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_feature_nostacksampling.js b/tools/profiler/tests/browser/browser_test_feature_nostacksampling.js
new file mode 100644
index 0000000000..695e6031aa
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_nostacksampling.js
@@ -0,0 +1,76 @@
+/* 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 the No Stack Sampling feature.
+ */
+add_task(async function test_profile_feature_nostacksampling() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ await startProfiler({ features: ["js", "nostacksampling"] });
+
+ const url = BASE_URL + "do_work_500ms.html";
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ // Wait 500ms so that the tab finishes executing.
+ await wait(500);
+
+ // Check that we can get no stacks when the feature is turned on.
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await stopProfilerNowAndGetThreads(contentPid);
+ Assert.equal(
+ parentThread.samples.data.length,
+ 0,
+ "Stack samples were recorded from the parent process' main thread" +
+ "when the No Stack Sampling feature was turned on."
+ );
+ Assert.equal(
+ contentThread.samples.data.length,
+ 0,
+ "Stack samples were recorded from the content process' main thread" +
+ "when the No Stack Sampling feature was turned on."
+ );
+ }
+
+ // Flush out any straggling allocation markers that may have not been collected
+ // yet by starting and stopping the profiler once.
+ await startProfiler({ features: ["js"] });
+
+ // Now reload the tab with a clean run.
+ gBrowser.reload();
+ await wait(500);
+
+ // Check that stack samples were recorded.
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await waitSamplingAndStopProfilerAndGetThreads(contentPid);
+ Assert.greater(
+ parentThread.samples.data.length,
+ 0,
+ "No Stack samples were recorded from the parent process' main thread" +
+ "when the No Stack Sampling feature was not turned on."
+ );
+
+ Assert.greater(
+ contentThread.samples.data.length,
+ 0,
+ "No Stack samples were recorded from the content process' main thread" +
+ "when the No Stack Sampling feature was not turned on."
+ );
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_feature_preferencereads.js b/tools/profiler/tests/browser/browser_test_feature_preferencereads.js
new file mode 100644
index 0000000000..d0f16054d8
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_preferencereads.js
@@ -0,0 +1,116 @@
+/* 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/. */
+
+requestLongerTimeout(10);
+
+const kContentPref = "font.size.variable.x-western";
+
+function countPrefReadsInThread(pref, thread) {
+ let count = 0;
+ for (let payload of getPayloadsOfType(thread, "PreferenceRead")) {
+ if (payload.prefName === pref) {
+ count++;
+ }
+ }
+ return count;
+}
+
+async function waitForPaintAfterLoad() {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return new Promise(function(resolve) {
+ function listener() {
+ if (content.document.readyState == "complete") {
+ content.requestAnimationFrame(() => content.setTimeout(resolve, 0));
+ }
+ }
+ if (content.document.readyState != "complete") {
+ content.document.addEventListener("readystatechange", listener);
+ } else {
+ listener();
+ }
+ });
+ });
+}
+
+/**
+ * Test the PreferenceRead feature.
+ */
+add_task(async function test_profile_feature_preferencereads() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ await startProfiler({ features: ["js", "preferencereads"] });
+
+ const url = BASE_URL + "single_frame.html";
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ await waitForPaintAfterLoad();
+
+ // Ensure we read a pref in the content process.
+ await SpecialPowers.spawn(contentBrowser, [kContentPref], pref => {
+ Services.prefs.getIntPref(pref);
+ });
+
+ // Check that some PreferenceRead profile markers were generated when the
+ // feature is enabled.
+ {
+ const { contentThread } = await stopProfilerNowAndGetThreads(contentPid);
+
+ Assert.greater(
+ countPrefReadsInThread(kContentPref, contentThread),
+ 0,
+ `PreferenceRead profile markers for ${kContentPref} were recorded ` +
+ "when the PreferenceRead feature was turned on."
+ );
+ }
+
+ await startProfiler({ features: ["js"] });
+ // Now reload the tab with a clean run.
+ await ContentTask.spawn(contentBrowser, null, () => {
+ return new Promise(resolve => {
+ addEventListener("pageshow", () => resolve(), {
+ capturing: true,
+ once: true,
+ });
+ content.location.reload();
+ });
+ });
+
+ await waitForPaintAfterLoad();
+
+ // Ensure we read a pref in the content process.
+ await SpecialPowers.spawn(contentBrowser, [kContentPref], pref => {
+ Services.prefs.getIntPref(pref);
+ });
+
+ // Check that no PreferenceRead markers were recorded when the feature
+ // is turned off.
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await stopProfilerNowAndGetThreads(contentPid);
+ Assert.equal(
+ getPayloadsOfType(parentThread, "PreferenceRead").length,
+ 0,
+ "No PreferenceRead profile were recorded " +
+ "when the PreferenceRead feature was turned off."
+ );
+
+ Assert.equal(
+ getPayloadsOfType(contentThread, "PreferenceRead").length,
+ 0,
+ "No PreferenceRead profile were recorded " +
+ "when the PreferenceRead feature was turned off."
+ );
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_cancel.js b/tools/profiler/tests/browser/browser_test_marker_network_cancel.js
new file mode 100644
index 0000000000..ee7f1ef3ca
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_cancel.js
@@ -0,0 +1,71 @@
+/* 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 with the cancel status.
+ */
+add_task(async function test_network_markers_early_cancel() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfilerForMarkerTests();
+
+ const url = BASE_URL + "simple.html?cacheBust=" + Math.random();
+ const options = {
+ gBrowser,
+ url: "about:blank",
+ waitForLoad: false,
+ };
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(options);
+ const loadPromise = BrowserTestUtils.waitForDocLoadAndStopIt(url, tab);
+ BrowserTestUtils.loadURI(tab.linkedBrowser, url);
+ const contentPid = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+ await loadPromise;
+ const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
+ contentPid
+ );
+ BrowserTestUtils.removeTab(tab);
+
+ const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
+ const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
+
+ info("parent process: " + JSON.stringify(parentNetworkMarkers, null, 2));
+ info("content process: " + JSON.stringify(contentNetworkMarkers, null, 2));
+
+ Assert.equal(
+ parentNetworkMarkers.length,
+ 2,
+ `We should get a pair of network markers in the parent thread.`
+ );
+
+ // We don't test the markers in the content process, because depending on some
+ // timing we can have 0 or 1 (and maybe even 2 (?)).
+
+ const parentStopMarker = parentNetworkMarkers[1];
+
+ const expectedProperties = {
+ name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
+ data: Expect.objectContainsOnly({
+ type: "Network",
+ status: "STATUS_CANCEL",
+ URI: url,
+ requestMethod: "GET",
+ contentType: null,
+ startTime: Expect.number(),
+ endTime: Expect.number(),
+ id: Expect.number(),
+ pri: Expect.number(),
+ cache: "Unresolved",
+ }),
+ };
+
+ Assert.objectContains(parentStopMarker, expectedProperties);
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_private_browsing.js b/tools/profiler/tests/browser/browser_test_marker_network_private_browsing.js
new file mode 100644
index 0000000000..0a10ea7157
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_private_browsing.js
@@ -0,0 +1,91 @@
+/* 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
+ */
+add_task(async function test_network_markers() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfilerForMarkerTests();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ fission: true,
+ });
+ try {
+ const url = BASE_URL_HTTPS + "simple.html?cacheBust=" + Math.random();
+ const contentBrowser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.loadURI(contentBrowser, url);
+ await BrowserTestUtils.browserLoaded(contentBrowser, false, url);
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
+ contentPid
+ );
+
+ const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
+ const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
+ info(JSON.stringify(parentNetworkMarkers, null, 2));
+ info(JSON.stringify(contentNetworkMarkers, null, 2));
+
+ Assert.equal(
+ parentNetworkMarkers.length,
+ 2,
+ `We should get a pair of network markers in the parent thread.`
+ );
+ Assert.equal(
+ contentNetworkMarkers.length,
+ 2,
+ `We should get a pair of network markers in the content thread.`
+ );
+
+ const parentStopMarker = parentNetworkMarkers[1];
+ const contentStopMarker = contentNetworkMarkers[1];
+
+ const expectedProperties = {
+ name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
+ data: Expect.objectContains({
+ status: "STATUS_STOP",
+ URI: url,
+ requestMethod: "GET",
+ contentType: "text/html",
+ 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(),
+ isPrivateBrowsing: true,
+ }),
+ };
+
+ Assert.objectContains(parentStopMarker, expectedProperties);
+ // The cache information is missing from the content marker, it's only part
+ // of the parent marker. See Bug 1544821.
+ Assert.objectContains(parentStopMarker.data, {
+ // 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.objectContains(contentStopMarker, expectedProperties);
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_redirect.js b/tools/profiler/tests/browser/browser_test_marker_network_redirect.js
new file mode 100644
index 0000000000..28478c2b3b
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_redirect.js
@@ -0,0 +1,341 @@
+/* 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 redirect cases.
+ */
+add_task(async function test_network_markers_service_worker_setup() {
+ // Disabling cache makes the result more predictable especially in verify mode.
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.cache.disk.enable", false],
+ ["browser.cache.memory.enable", false],
+ ],
+ });
+});
+
+add_task(async function test_network_markers_redirect_simple() {
+ // In this test, we request an HTML page that gets redirected. This is a
+ // top-level navigation.
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfilerForMarkerTests();
+
+ const targetFileNameWithCacheBust = "simple.html";
+ const url =
+ BASE_URL +
+ "redirect.sjs?" +
+ encodeURIComponent(targetFileNameWithCacheBust);
+ const targetUrl = BASE_URL + targetFileNameWithCacheBust;
+
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
+ contentPid
+ );
+
+ const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
+ const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
+ info(JSON.stringify(parentNetworkMarkers, null, 2));
+ info(JSON.stringify(contentNetworkMarkers, null, 2));
+
+ Assert.equal(
+ parentNetworkMarkers.length,
+ 4,
+ `We should get 2 pairs of network markers in the parent thread.`
+ );
+
+ /* It looks like that for a redirection for the top level navigation, the
+ * content thread sees the markers for the second request only.
+ * See Bug 1692879. */
+ Assert.equal(
+ contentNetworkMarkers.length,
+ 2,
+ `We should get one pair of network markers in the content thread.`
+ );
+
+ const parentRedirectMarker = parentNetworkMarkers[1];
+ const parentStopMarker = parentNetworkMarkers[3];
+ // There's no content redirect marker for the reason outlined above.
+ const contentStopMarker = contentNetworkMarkers[1];
+
+ Assert.objectContains(parentRedirectMarker, {
+ name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
+ data: Expect.objectContainsOnly({
+ type: "Network",
+ status: "STATUS_REDIRECT",
+ URI: url,
+ RedirectURI: targetUrl,
+ requestMethod: "GET",
+ contentType: null,
+ 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(),
+ redirectId: parentStopMarker.data.id,
+ pri: Expect.number(),
+ cache: Expect.stringMatches(/Missed|Unresolved/),
+ redirectType: "Permanent",
+ isHttpToHttpsRedirect: false,
+ }),
+ });
+
+ const expectedProperties = {
+ name: Expect.stringMatches(
+ `Load \\d+:.*${escapeStringRegexp(targetUrl)}`
+ ),
+ };
+ const expectedDataProperties = {
+ type: "Network",
+ status: "STATUS_STOP",
+ URI: targetUrl,
+ requestMethod: "GET",
+ contentType: "text/html",
+ 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(parentStopMarker, expectedProperties);
+ Assert.objectContains(contentStopMarker, expectedProperties);
+
+ // The cache information is missing from the content marker, it's only part
+ // of the parent marker. See Bug 1544821.
+ Assert.objectContainsOnly(parentStopMarker.data, {
+ ...expectedDataProperties,
+ // 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, expectedDataProperties);
+ });
+});
+
+add_task(async function test_network_markers_redirect_resources() {
+ // 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 + "page_with_resources.html?cacheBust=" + Math.random();
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
+ contentPid
+ );
+
+ const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
+ const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
+ info(JSON.stringify(parentNetworkMarkers, null, 2));
+ info(JSON.stringify(contentNetworkMarkers, null, 2));
+
+ Assert.equal(
+ parentNetworkMarkers.length,
+ 8,
+ `We should get 4 pairs of network markers in the parent thread.`
+ // 1 - The main page
+ // 2 - The SVG
+ // 3 - The redirected request for the second SVG request.
+ // 4 - The SVG, again
+ );
+
+ /* In this second test, the top level navigation request isn't redirected.
+ * Contrary to Bug 1692879 we get all network markers for redirected
+ * resources. */
+ Assert.equal(
+ contentNetworkMarkers.length,
+ 8,
+ `We should get 4 pairs of network markers in the content thread.`
+ );
+
+ // The same resource firefox-logo-nightly.svg is requested twice, but the
+ // second time it is redirected.
+ // We're not interested in the main page, as we test that in other files.
+ // In this page we're only interested in the marker for requested resources.
+
+ 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.`
+ )
+ );
+
+ const parentFirstStopMarker = parentPairs[1][1];
+ const parentRedirectMarker = parentPairs[2][1];
+ const parentSecondStopMarker = parentPairs[3][1];
+ const contentFirstStopMarker = contentPairs[1][1];
+ const contentRedirectMarker = contentPairs[2][1];
+ const contentSecondStopMarker = contentPairs[3][1];
+
+ const expectedCommonDataProperties = {
+ type: "Network",
+ requestMethod: "GET",
+ startTime: Expect.number(),
+ endTime: Expect.number(),
+ id: Expect.number(),
+ pri: Expect.number(),
+ innerWindowID: Expect.number(),
+ };
+
+ // These properties are present when a connection is fully opened. This is
+ // most often the case, unless we're in verify mode, because in that case
+ // we run the same tests several times in the same Firefox and they might be
+ // cached, or in chaos mode Firefox may make all requests sequentially on
+ // the same connection.
+ // In these cases, these properties won't always be present.
+ const expectedConnectionProperties = {
+ 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 expectedPropertiesForStopMarker = {
+ name: Expect.stringMatches(/Load \d+:.*\/firefox-logo-nightly\.svg/),
+ };
+
+ const expectedDataPropertiesForStopMarker = {
+ ...expectedCommonDataProperties,
+ ...expectedConnectionProperties,
+ status: "STATUS_STOP",
+ URI: Expect.stringContains("/firefox-logo-nightly.svg"),
+ contentType: "image/svg+xml",
+ count: Expect.number(),
+ };
+
+ const expectedPropertiesForRedirectMarker = {
+ name: Expect.stringMatches(
+ /Load \d+:.*\/redirect.sjs\?firefox-logo-nightly\.svg/
+ ),
+ };
+
+ const expectedDataPropertiesForRedirectMarker = {
+ ...expectedCommonDataProperties,
+ ...expectedConnectionProperties,
+ status: "STATUS_REDIRECT",
+ URI: Expect.stringContains("/redirect.sjs?firefox-logo-nightly.svg"),
+ RedirectURI: Expect.stringContains("/firefox-logo-nightly.svg"),
+ contentType: null,
+ redirectType: "Permanent",
+ isHttpToHttpsRedirect: false,
+ };
+
+ Assert.objectContains(
+ parentFirstStopMarker,
+ expectedPropertiesForStopMarker
+ );
+ Assert.objectContainsOnly(parentFirstStopMarker.data, {
+ ...expectedDataPropertiesForStopMarker,
+ // The cache information is missing from the content marker, it's only part
+ // of the parent marker. 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.objectContains(
+ contentFirstStopMarker,
+ expectedPropertiesForStopMarker
+ );
+ Assert.objectContainsOnly(
+ contentFirstStopMarker.data,
+ expectedDataPropertiesForStopMarker
+ );
+
+ Assert.objectContains(
+ parentRedirectMarker,
+ expectedPropertiesForRedirectMarker
+ );
+ Assert.objectContainsOnly(parentRedirectMarker.data, {
+ ...expectedDataPropertiesForRedirectMarker,
+ redirectId: parentSecondStopMarker.data.id,
+ // See above for the full explanation about the cache property.
+ cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
+ });
+
+ Assert.objectContains(
+ contentRedirectMarker,
+ expectedPropertiesForRedirectMarker
+ );
+ Assert.objectContainsOnly(contentRedirectMarker.data, {
+ ...expectedDataPropertiesForRedirectMarker,
+ redirectId: contentSecondStopMarker.data.id,
+ });
+
+ Assert.objectContains(
+ parentSecondStopMarker,
+ expectedPropertiesForStopMarker
+ );
+ Assert.objectContainsOnly(parentSecondStopMarker.data, {
+ ...expectedDataPropertiesForStopMarker,
+ // The "count" property is absent from the content marker.
+ count: Expect.number(),
+ // See above for the full explanation about the cache property.
+ cache: Expect.stringMatches(/^(Missed|Unresolved)$/),
+ });
+
+ Assert.objectContains(
+ contentSecondStopMarker,
+ expectedPropertiesForStopMarker
+ );
+ Assert.objectContainsOnly(
+ contentSecondStopMarker.data,
+ expectedDataPropertiesForStopMarker
+ );
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_cache_first.js b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_cache_first.js
new file mode 100644
index 0000000000..4b715efa1c
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_cache_first.js
@@ -0,0 +1,388 @@
+/* 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);
+ const serviceWorkerStopMarkers = serviceWorkerPairs.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 serviceWorkerMarker = serviceWorkerStopMarkers.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);
+ Assert.objectContains(serviceWorkerMarker, 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",
+ });
+ }
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_fetch_handler.js b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_fetch_handler.js
new file mode 100644
index 0000000000..775e81f565
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_fetch_handler.js
@@ -0,0 +1,218 @@
+/* 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 no fetch
+ * handlers. In this case, a fetch is done to the network. There may be
+ * shortcuts in our code in this case, that's why it's important to test it
+ * separately.
+ */
+
+const serviceWorkerFileName = "serviceworker_no_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.
+ const parentStopMarkers = parentPairs.map(([_, stopMarker]) => stopMarker);
+ const contentStopMarkers = contentPairs.map(
+ ([_, stopMarker]) => stopMarker
+ );
+
+ // 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(
+ 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 parentMarker = parentStopMarkers.find(
+ marker => marker.data.URI === expectedFile
+ );
+ const contentMarker = contentStopMarkers.find(
+ marker => marker.data.URI === expectedFile
+ );
+
+ const commonProperties = {
+ name: Expect.stringMatches(
+ `Load \\d+:.*${escapeStringRegexp(expectedFile)}`
+ ),
+ };
+ Assert.objectContains(parentMarker, commonProperties);
+ Assert.objectContains(contentMarker, commonProperties);
+
+ // 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(),
+ };
+
+ 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(parentMarker.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(contentMarker.data, commonDataProperties);
+ } else {
+ // This is the other file firefox-logo-nightly.svg.
+ Assert.objectContainsOnly(parentMarker.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.objectContainsOnly(contentMarker.data, {
+ ...commonDataProperties,
+ innerWindowID: Expect.number(),
+ });
+ }
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js
new file mode 100644
index 0000000000..65686257de
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js
@@ -0,0 +1,296 @@
+/* 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,
+ });
+ }
+ }
+ });
+});
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..41690e11a6
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js
@@ -0,0 +1,558 @@
+/* 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
+ );
+ const serviceWorkerStopMarkers = serviceWorkerPairs.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,
+ htmlFetch2,
+ generatedSvgFetch1,
+ firefoxSvgFetch1,
+ firefoxSvgFetch2;
+
+ // 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;
+
+ // In the service worker parent thread, we have 2 network markers:
+ // - the HTML file
+ // - the firefox SVG file.
+ // Remember that the generated SVG file is returned directly by the SW.
+ Assert.equal(
+ serviceWorkerStopMarkers.length,
+ 2,
+ "There should be 2 stop markers in the service worker thread."
+ );
+
+ [htmlFetch2, firefoxSvgFetch2] = serviceWorkerStopMarkers;
+ } 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.
+ htmlFetch2,
+ htmlFetch1,
+ generatedSvgFetch1,
+ firefoxSvgFetch1,
+ firefoxSvgFetch2,
+ ] = 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(),
+ }),
+ });
+
+ // Now let's test the markers coming from the service worker.
+ 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",
+ 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?
+ // Note: no cache either, this is bug 1544821.
+ }),
+ });
+
+ 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",
+ 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?
+ // Note: no cache either, this is bug 1544821.
+ }),
+ });
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_simple.js b/tools/profiler/tests/browser/browser_test_marker_network_simple.js
new file mode 100644
index 0000000000..15894305a7
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_simple.js
@@ -0,0 +1,81 @@
+/* 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
+ */
+add_task(async function test_network_markers() {
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfilerForMarkerTests();
+
+ const url = BASE_URL + "simple.html?cacheBust=" + Math.random();
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
+ contentPid
+ );
+
+ const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
+ const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
+ info(JSON.stringify(parentNetworkMarkers, null, 2));
+ info(JSON.stringify(contentNetworkMarkers, null, 2));
+
+ Assert.equal(
+ parentNetworkMarkers.length,
+ 2,
+ `We should get a pair of network markers in the parent thread.`
+ );
+ Assert.equal(
+ contentNetworkMarkers.length,
+ 2,
+ `We should get a pair of network markers in the content thread.`
+ );
+
+ const parentStopMarker = parentNetworkMarkers[1];
+ const contentStopMarker = contentNetworkMarkers[1];
+
+ const expectedProperties = {
+ name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
+ data: Expect.objectContains({
+ status: "STATUS_STOP",
+ URI: url,
+ requestMethod: "GET",
+ contentType: "text/html",
+ 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(parentStopMarker, expectedProperties);
+ // The cache information is missing from the content marker, it's only part
+ // of the parent marker. See Bug 1544821.
+ Assert.objectContains(parentStopMarker.data, {
+ // 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.objectContains(contentStopMarker, expectedProperties);
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_marker_network_sts.js b/tools/profiler/tests/browser/browser_test_marker_network_sts.js
new file mode 100644
index 0000000000..26f2a1c756
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_marker_network_sts.js
@@ -0,0 +1,130 @@
+/* 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 that we behave properly with STS redirections.
+ */
+
+add_task(async function test_network_markers_service_worker_setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disabling cache makes the result more predictable especially in verify mode.
+ ["browser.cache.disk.enable", false],
+ ["browser.cache.memory.enable", false],
+ // We want to test upgrading requests
+ ["dom.security.https_only_mode", true],
+ ],
+ });
+});
+
+add_task(async function test_network_markers_redirect_to_https() {
+ // In this test, we request an HTML page with http that gets redirected to https.
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfilerForMarkerTests();
+
+ const url = BASE_URL + "simple.html";
+ const targetUrl = BASE_URL_HTTPS + "simple.html";
+
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ const { parentThread, contentThread } = await stopProfilerNowAndGetThreads(
+ contentPid
+ );
+
+ const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread);
+ const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread);
+ info(JSON.stringify(parentNetworkMarkers, null, 2));
+ info(JSON.stringify(contentNetworkMarkers, null, 2));
+
+ Assert.equal(
+ parentNetworkMarkers.length,
+ 4,
+ `We should get 2 pairs of network markers in the parent thread.`
+ );
+
+ /* It looks like that for a redirection for the top level navigation, the
+ * content thread sees the markers for the second request only.
+ * See Bug 1692879. */
+ Assert.equal(
+ contentNetworkMarkers.length,
+ 2,
+ `We should get one pair of network markers in the content thread.`
+ );
+
+ const parentRedirectMarker = parentNetworkMarkers[1];
+ const parentStopMarker = parentNetworkMarkers[3];
+ // There's no content redirect marker for the reason outlined above.
+ const contentStopMarker = contentNetworkMarkers[1];
+
+ Assert.objectContains(parentRedirectMarker, {
+ name: Expect.stringMatches(`Load \\d+:.*${escapeStringRegexp(url)}`),
+ data: Expect.objectContainsOnly({
+ type: "Network",
+ status: "STATUS_REDIRECT",
+ URI: url,
+ RedirectURI: targetUrl,
+ requestMethod: "GET",
+ contentType: null,
+ startTime: Expect.number(),
+ endTime: Expect.number(),
+ id: Expect.number(),
+ redirectId: parentStopMarker.data.id,
+ pri: Expect.number(),
+ cache: "Unresolved",
+ redirectType: "Permanent",
+ isHttpToHttpsRedirect: true,
+ }),
+ });
+
+ const expectedProperties = {
+ name: Expect.stringMatches(
+ `Load \\d+:.*${escapeStringRegexp(targetUrl)}`
+ ),
+ };
+ const expectedDataProperties = {
+ type: "Network",
+ status: "STATUS_STOP",
+ URI: targetUrl,
+ requestMethod: "GET",
+ contentType: "text/html",
+ 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(parentStopMarker, expectedProperties);
+ Assert.objectContains(contentStopMarker, expectedProperties);
+
+ // The cache information is missing from the content marker, it's only part
+ // of the parent marker. See Bug 1544821.
+ Assert.objectContainsOnly(parentStopMarker.data, {
+ ...expectedDataProperties,
+ // 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, expectedDataProperties);
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_markers_gc_cc.js b/tools/profiler/tests/browser/browser_test_markers_gc_cc.js
new file mode 100644
index 0000000000..a4a94d60cc
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_markers_gc_cc.js
@@ -0,0 +1,49 @@
+/* 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/. */
+
+add_task(async function test_markers_gc_cc() {
+ info("Test GC&CC markers.");
+
+ info("Create a throwaway profile.");
+ await startProfiler({});
+ let tempProfileContainer = { profile: null };
+ tempProfileContainer.profile = await waitSamplingAndStopAndGetProfile();
+
+ info("Restart the profiler.");
+ await startProfiler({});
+
+ info("Throw away the previous profile, which should be garbage-collected.");
+ Assert.equal(
+ typeof tempProfileContainer.profile,
+ "object",
+ "Previously-captured profile should be an object"
+ );
+ delete tempProfileContainer.profile;
+ Assert.equal(
+ typeof tempProfileContainer.profile,
+ "undefined",
+ "Deleted profile should now be undefined"
+ );
+
+ info("Force GC&CC");
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.gc();
+ SpecialPowers.forceShrinkingGC();
+ SpecialPowers.forceCC();
+
+ info("Stop the profiler and get the profile.");
+ const profile = await waitSamplingAndStopAndGetProfile();
+
+ const markers = getInflatedMarkerData(profile.threads[0]);
+ Assert.ok(
+ markers.some(({ data }) => data?.type === "GCSlice"),
+ "A GCSlice marker was recorded"
+ );
+ Assert.ok(
+ markers.some(({ data }) => data?.type === "CCSlice"),
+ "A CCSlice marker was recorded"
+ );
+});
diff --git a/tools/profiler/tests/browser/browser_test_markers_parent_process.js b/tools/profiler/tests/browser/browser_test_markers_parent_process.js
new file mode 100644
index 0000000000..28b82f8054
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_markers_parent_process.js
@@ -0,0 +1,37 @@
+/* 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/. */
+
+add_task(async function test_markers_parent_process() {
+ info("Test markers that are generated by the browser's parent process.");
+
+ info("Start the profiler in nostacksampling mode.");
+ await startProfiler({ features: ["nostacksampling"] });
+
+ info("Dispatch a DOMEvent");
+ window.dispatchEvent(new Event("synthetic"));
+
+ info("Stop the profiler and get the profile.");
+ const profile = await stopNowAndGetProfile();
+
+ const markers = getInflatedMarkerData(profile.threads[0]);
+ {
+ const domEventStart = markers.find(
+ ({ phase, data }) =>
+ phase === INTERVAL_START && data?.eventType === "synthetic"
+ );
+ const domEventEnd = markers.find(
+ ({ phase, data }) =>
+ phase === INTERVAL_END && data?.eventType === "synthetic"
+ );
+ ok(domEventStart, "A start DOMEvent was generated");
+ ok(domEventEnd, "An end DOMEvent was generated");
+ ok(
+ domEventEnd.data.latency > 0,
+ "DOMEvent had a a latency value generated."
+ );
+ ok(domEventEnd.data.type === "DOMEvent");
+ ok(domEventEnd.name === "DOMEvent");
+ }
+ // Add more marker tests.
+});
diff --git a/tools/profiler/tests/browser/browser_test_profile_capture_by_pid.js b/tools/profiler/tests/browser/browser_test_profile_capture_by_pid.js
new file mode 100644
index 0000000000..3694ffd93e
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_capture_by_pid.js
@@ -0,0 +1,206 @@
+/* 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/. */
+
+function ProcessHasSamplerThread(process) {
+ return process.threads.some(t => t.name == "SamplerThread");
+}
+
+async function GetPidsWithSamplerThread() {
+ let parentProc = await ChromeUtils.requestProcInfo();
+
+ let pids = parentProc.children
+ .filter(ProcessHasSamplerThread)
+ .map(proc => proc.pid);
+ if (ProcessHasSamplerThread(parentProc)) {
+ pids.unshift(parentProc.pid);
+ }
+ return pids;
+}
+
+// fnFilterWithContentId: Called with content child pid, returns filters to use.
+// E.g.: 123 => ["GeckoMain", "pid:123"], or 123 => ["pid:456"].
+async function test_with_filter(fnFilterWithContentId) {
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info("Open a tab with single_frame.html in it.");
+ const url = BASE_URL + "single_frame.html";
+ return BrowserTestUtils.withNewTab(url, async function(contentBrowser) {
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ Assert.deepEqual(
+ await GetPidsWithSamplerThread(),
+ [],
+ "There should be no SamplerThreads before starting the profiler"
+ );
+
+ info("Start the profiler to test filters including 'pid:<content>'.");
+ await startProfiler({ threads: fnFilterWithContentId(contentPid) });
+
+ let pidsWithSamplerThread = null;
+ await TestUtils.waitForCondition(
+ async function() {
+ let pidsStringBefore = JSON.stringify(pidsWithSamplerThread);
+ pidsWithSamplerThread = await GetPidsWithSamplerThread();
+ return JSON.stringify(pidsWithSamplerThread) == pidsStringBefore;
+ },
+ "Wait for sampler threads to stabilize after profiler start",
+ /* interval (ms) */ 250,
+ /* maxTries */ 10
+ );
+
+ info("Capture the profile data.");
+ const profile = await waitSamplingAndStopAndGetProfile();
+
+ await TestUtils.waitForCondition(async function() {
+ return !(await GetPidsWithSamplerThread()).length;
+ }, "Wait for all sampler threads to stop after profiler stop");
+
+ return { contentPid, pidsWithSamplerThread, profile };
+ });
+}
+
+add_task(async function browser_test_profile_capture_along_with_content_pid() {
+ const {
+ contentPid,
+ pidsWithSamplerThread,
+ profile,
+ } = await test_with_filter(contentPid => ["GeckoMain", "pid:" + contentPid]);
+
+ Assert.greater(
+ pidsWithSamplerThread.length,
+ 2,
+ "There should be lots of SamplerThreads after starting the profiler"
+ );
+
+ let contentProcessIndex = profile.processes.findIndex(
+ p => p.threads[0].pid == contentPid
+ );
+ Assert.notEqual(
+ contentProcessIndex,
+ -1,
+ "The content process should be present"
+ );
+
+ // Note: Some threads may not be registered, so we can't expect that many. But
+ // 10 is much more than the default 4.
+ Assert.greater(
+ profile.processes[contentProcessIndex].threads.length,
+ 10,
+ "The content process should have many threads"
+ );
+
+ Assert.equal(
+ profile.threads.length,
+ 1,
+ "The parent process should have only one thread"
+ );
+ Assert.equal(
+ profile.threads[0].name,
+ "GeckoMain",
+ "The parent process should have the main thread"
+ );
+});
+
+add_task(async function browser_test_profile_capture_along_with_other_pid() {
+ const parentPid = Services.appinfo.processID;
+ const {
+ contentPid,
+ pidsWithSamplerThread,
+ profile,
+ } = await test_with_filter(contentPid => ["GeckoMain", "pid:" + parentPid]);
+
+ Assert.greater(
+ pidsWithSamplerThread.length,
+ 2,
+ "There should be lots of SamplerThreads after starting the profiler"
+ );
+
+ let contentProcessIndex = profile.processes.findIndex(
+ p => p.threads[0].pid == contentPid
+ );
+ Assert.notEqual(
+ contentProcessIndex,
+ -1,
+ "The content process should be present"
+ );
+
+ Assert.equal(
+ profile.processes[contentProcessIndex].threads.length,
+ 1,
+ "The content process should have only one thread"
+ );
+
+ // Note: Some threads may not be registered, so we can't expect that many. But
+ // 10 is much more than the default 4.
+ Assert.greater(
+ profile.threads.length,
+ 10,
+ "The parent process should have many threads"
+ );
+});
+
+add_task(async function browser_test_profile_capture_by_only_content_pid() {
+ const parentPid = Services.appinfo.processID;
+ const {
+ contentPid,
+ pidsWithSamplerThread,
+ profile,
+ } = await test_with_filter(contentPid => ["pid:" + contentPid]);
+
+ // The sampler thread always runs in the parent process, see bug 1754100.
+ Assert.deepEqual(
+ pidsWithSamplerThread,
+ [parentPid, contentPid],
+ "There should only be SamplerThreads in the parent and the target child"
+ );
+
+ Assert.equal(
+ profile.processes.length,
+ 1,
+ "There should only be one child process"
+ );
+ // Note: Some threads may not be registered, so we can't expect that many. But
+ // 10 is much more than the default 4.
+ Assert.greater(
+ profile.processes[0].threads.length,
+ 10,
+ "The child process should have many threads"
+ );
+ Assert.equal(
+ profile.processes[0].threads[0].pid,
+ contentPid,
+ "The only child process should be our content"
+ );
+});
+
+add_task(async function browser_test_profile_capture_by_only_parent_pid() {
+ const parentPid = Services.appinfo.processID;
+ const {
+ pidsWithSamplerThread,
+ profile,
+ } = await test_with_filter(contentPid => ["pid:" + parentPid]);
+
+ Assert.deepEqual(
+ pidsWithSamplerThread,
+ [parentPid],
+ "There should only be a SamplerThread in the parent"
+ );
+
+ // Note: Some threads may not be registered, so we can't expect that many. But
+ // 10 is much more than the default 4.
+ Assert.greater(
+ profile.threads.length,
+ 10,
+ "The parent process should have many threads"
+ );
+ Assert.equal(
+ profile.processes.length,
+ 0,
+ "There should be no child processes"
+ );
+});
diff --git a/tools/profiler/tests/browser/browser_test_profile_fission.js b/tools/profiler/tests/browser/browser_test_profile_fission.js
new file mode 100644
index 0000000000..f3d8016569
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_fission.js
@@ -0,0 +1,196 @@
+/* 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/. */
+
+if (SpecialPowers.useRemoteSubframes) {
+ // Bug 1586105: these tests could time out in some extremely slow conditions,
+ // when fission is enabled.
+ // Requesting a longer timeout should make it pass.
+ requestLongerTimeout(2);
+}
+
+add_task(async function test_profile_fission_no_private_browsing() {
+ // Requesting the complete log to be able to debug Bug 1586105.
+ SimpleTest.requestCompleteLog();
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still have some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with single frame page."
+ );
+ await startProfiler();
+
+ info("Open a private window with single_frame.html in it.");
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+
+ try {
+ const url = BASE_URL_HTTPS + "single_frame.html";
+ const contentBrowser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.loadURI(contentBrowser, url);
+ await BrowserTestUtils.browserLoaded(contentBrowser, false, url);
+
+ const parentPid = Services.appinfo.processID;
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ // Getting the active Browser ID to assert the page info tabID later.
+ const activeTabID = contentBrowser.browsingContext.browserId;
+
+ info("Capture the profile data.");
+ const {
+ profile,
+ contentProcess,
+ contentThread,
+ } = await stopProfilerNowAndGetThreads(contentPid);
+
+ Assert.equal(
+ contentThread.isPrivateBrowsing,
+ false,
+ "The content process has the private browsing flag set to false."
+ );
+
+ Assert.equal(
+ contentThread.userContextId,
+ 0,
+ "The content process has the information about the container used for this process"
+ );
+
+ info(
+ "Check if the captured page is the one with correct values we created."
+ );
+
+ let pageFound = false;
+ for (const page of contentProcess.pages) {
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ Assert.equal(typeof page.isPrivateBrowsing, "boolean");
+ Assert.equal(page.isPrivateBrowsing, false);
+ pageFound = true;
+ break;
+ }
+ }
+ Assert.equal(pageFound, true);
+
+ info("Check that the profiling logs exist with the expected properties.");
+ Assert.equal(typeof profile.profilingLog, "object");
+ Assert.equal(typeof profile.profilingLog[parentPid], "object");
+ const parentLog = profile.profilingLog[parentPid];
+ Assert.equal(typeof parentLog.profilingLogBegin_TSms, "number");
+ Assert.equal(typeof parentLog.profilingLogEnd_TSms, "number");
+ Assert.equal(typeof parentLog.bufferGlobalController, "object");
+ Assert.equal(
+ typeof parentLog.bufferGlobalController.controllerCreationTime_TSms,
+ "number"
+ );
+
+ Assert.equal(typeof profile.profileGatheringLog, "object");
+ Assert.equal(typeof profile.profileGatheringLog[parentPid], "object");
+ Assert.equal(
+ typeof profile.profileGatheringLog[parentPid]
+ .profileGatheringLogBegin_TSms,
+ "number"
+ );
+ Assert.equal(
+ typeof profile.profileGatheringLog[parentPid].profileGatheringLogEnd_TSms,
+ "number"
+ );
+
+ Assert.equal(typeof contentProcess.profilingLog, "object");
+ Assert.equal(typeof contentProcess.profilingLog[contentPid], "object");
+ Assert.equal(
+ typeof contentProcess.profilingLog[contentPid].profilingLogBegin_TSms,
+ "number"
+ );
+ Assert.equal(
+ typeof contentProcess.profilingLog[contentPid].profilingLogEnd_TSms,
+ "number"
+ );
+
+ Assert.equal(typeof contentProcess.profileGatheringLog, "undefined");
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
+
+add_task(async function test_profile_fission_private_browsing() {
+ // Requesting the complete log to be able to debug Bug 1586105.
+ SimpleTest.requestCompleteLog();
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still have some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with single frame page."
+ );
+ await startProfiler();
+
+ info("Open a private window with single_frame.html in it.");
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ fission: true,
+ });
+
+ try {
+ const url = BASE_URL_HTTPS + "single_frame.html";
+ const contentBrowser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.loadURI(contentBrowser, url);
+ await BrowserTestUtils.browserLoaded(contentBrowser, false, url);
+
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ // Getting the active Browser ID to assert the page info tabID later.
+ const activeTabID = contentBrowser.browsingContext.browserId;
+
+ info("Capture the profile data.");
+ const {
+ contentProcess,
+ contentThread,
+ } = await stopProfilerNowAndGetThreads(contentPid);
+
+ Assert.equal(
+ contentThread.isPrivateBrowsing,
+ true,
+ "The content process has the private browsing flag set to true."
+ );
+
+ Assert.equal(
+ contentThread.userContextId,
+ 0,
+ "The content process has the information about the container used for this process"
+ );
+
+ info(
+ "Check if the captured page is the one with correct values we created."
+ );
+
+ let pageFound = false;
+ for (const page of contentProcess.pages) {
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ Assert.equal(typeof page.isPrivateBrowsing, "boolean");
+ Assert.equal(page.isPrivateBrowsing, true);
+ pageFound = true;
+ break;
+ }
+ }
+ Assert.equal(pageFound, true);
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js b/tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js
new file mode 100644
index 0000000000..6d48306432
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js
@@ -0,0 +1,83 @@
+/* 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/. */
+
+if (SpecialPowers.useRemoteSubframes) {
+ // Bug 1586105: these tests could time out in some extremely slow conditions,
+ // when fission is enabled.
+ // Requesting a longer timeout should make it pass.
+ requestLongerTimeout(2);
+}
+
+add_task(async function test_profile_multi_frame_page_info() {
+ // Requesting the complete log to be able to debug Bug 1586105.
+ SimpleTest.requestCompleteLog();
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still have some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with multi frame page."
+ );
+ await startProfiler();
+
+ info("Open a tab with multi_frame.html in it.");
+ // multi_frame.html embeds single_frame.html inside an iframe.
+ const url = BASE_URL + "multi_frame.html";
+ await BrowserTestUtils.withNewTab(url, async function(contentBrowser) {
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ // Getting the active Browser ID to assert the page info tabID later.
+ const win = Services.wm.getMostRecentWindow("navigator:browser");
+ const activeTabID = win.gBrowser.selectedBrowser.browsingContext.browserId;
+
+ info("Capture the profile data.");
+ const { contentProcess } = await stopProfilerNowAndGetThreads(contentPid);
+
+ info(
+ "Check if the captured pages are the ones with correct values we created."
+ );
+
+ let parentPage;
+ let foundPage = 0;
+ for (const page of contentProcess.pages) {
+ // Parent page
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ Assert.equal(typeof page.isPrivateBrowsing, "boolean");
+ Assert.equal(page.isPrivateBrowsing, false);
+ parentPage = page;
+ foundPage++;
+ break;
+ }
+ }
+
+ Assert.notEqual(typeof parentPage, "undefined");
+
+ for (const page of contentProcess.pages) {
+ // Child page (iframe)
+ if (page.url == BASE_URL + "single_frame.html") {
+ Assert.equal(page.url, BASE_URL + "single_frame.html");
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ Assert.equal(typeof page.embedderInnerWindowID, "number");
+ Assert.notEqual(typeof parentPage, "undefined");
+ Assert.equal(page.embedderInnerWindowID, parentPage.innerWindowID);
+ Assert.equal(typeof page.isPrivateBrowsing, "boolean");
+ Assert.equal(page.isPrivateBrowsing, false);
+ foundPage++;
+ break;
+ }
+ }
+
+ Assert.equal(foundPage, 2);
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js b/tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js
new file mode 100644
index 0000000000..6f59ae6a7b
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js
@@ -0,0 +1,134 @@
+/* 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/. */
+
+if (SpecialPowers.useRemoteSubframes) {
+ // Bug 1586105: these tests could time out in some extremely slow conditions,
+ // when fission is enabled.
+ // Requesting a longer timeout should make it pass.
+ requestLongerTimeout(2);
+}
+
+add_task(async function test_profile_single_frame_page_info() {
+ // Requesting the complete log to be able to debug Bug 1586105.
+ SimpleTest.requestCompleteLog();
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still have some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with single frame page."
+ );
+ await startProfiler();
+
+ info("Open a tab with single_frame.html in it.");
+ const url = BASE_URL + "single_frame.html";
+ await BrowserTestUtils.withNewTab(url, async function(contentBrowser) {
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ // Getting the active Browser ID to assert the page info tabID later.
+ const win = Services.wm.getMostRecentWindow("navigator:browser");
+ const activeTabID = win.gBrowser.selectedBrowser.browsingContext.browserId;
+
+ info("Capture the profile data.");
+ const { contentProcess } = await stopProfilerNowAndGetThreads(contentPid);
+
+ info(
+ "Check if the captured page is the one with correct values we created."
+ );
+
+ let pageFound = false;
+ for (const page of contentProcess.pages) {
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ Assert.equal(typeof page.isPrivateBrowsing, "boolean");
+ Assert.equal(page.isPrivateBrowsing, false);
+ pageFound = true;
+ break;
+ }
+ }
+ Assert.equal(pageFound, true);
+ });
+});
+
+add_task(async function test_profile_private_browsing() {
+ // Requesting the complete log to be able to debug Bug 1586105.
+ SimpleTest.requestCompleteLog();
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still have some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with single frame page."
+ );
+ await startProfiler();
+
+ info("Open a private window with single_frame.html in it.");
+ const win = await BrowserTestUtils.openNewBrowserWindow({
+ fission: false,
+ private: true,
+ });
+
+ try {
+ const url = BASE_URL_HTTPS + "single_frame.html";
+ const contentBrowser = win.gBrowser.selectedBrowser;
+ BrowserTestUtils.loadURI(contentBrowser, url);
+ await BrowserTestUtils.browserLoaded(contentBrowser, false, url);
+
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ // Getting the active Browser ID to assert the page info tabID later.
+ const activeTabID = contentBrowser.browsingContext.browserId;
+
+ info("Capture the profile data.");
+ const {
+ contentProcess,
+ contentThread,
+ } = await stopProfilerNowAndGetThreads(contentPid);
+
+ // This information is available with fission only.
+ Assert.equal(
+ contentThread.isPrivateBrowsing,
+ undefined,
+ "The content process has no private browsing flag."
+ );
+
+ Assert.equal(
+ contentThread.userContextId,
+ undefined,
+ "The content process has no information about the container used for this process."
+ );
+
+ info(
+ "Check if the captured page is the one with correct values we created."
+ );
+
+ let pageFound = false;
+ for (const page of contentProcess.pages) {
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ Assert.equal(typeof page.isPrivateBrowsing, "boolean");
+ Assert.equal(page.isPrivateBrowsing, true);
+ pageFound = true;
+ break;
+ }
+ }
+ Assert.equal(pageFound, true);
+ } finally {
+ await BrowserTestUtils.closeWindow(win);
+ }
+});
diff --git a/tools/profiler/tests/browser/browser_test_profile_slow_capture.js b/tools/profiler/tests/browser/browser_test_profile_slow_capture.js
new file mode 100644
index 0000000000..83795aaf38
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_slow_capture.js
@@ -0,0 +1,104 @@
+/* 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/. */
+
+add_task(async function browser_test_profile_slow_capture() {
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with single frame page."
+ );
+ await startProfiler({ threads: ["GeckoMain", "test-debug-child-slow-json"] });
+
+ info("Open a tab with single_frame.html in it.");
+ const url = BASE_URL + "single_frame.html";
+ await BrowserTestUtils.withNewTab(url, async function(contentBrowser) {
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ // Getting the active Browser ID to assert the page info tabID later.
+ const win = Services.wm.getMostRecentWindow("navigator:browser");
+ const activeTabID = win.gBrowser.selectedBrowser.browsingContext.browserId;
+
+ info("Capture the profile data.");
+ const profile = await waitSamplingAndStopAndGetProfile();
+
+ let pageFound = false;
+ // We need to find the correct content process for that tab.
+ let contentProcess = profile.processes.find(
+ p => p.threads[0].pid == contentPid
+ );
+
+ if (!contentProcess) {
+ throw new Error(
+ `Could not find the content process with given pid: ${contentPid}`
+ );
+ }
+
+ info(
+ "Check if the captured page is the one with correct values we created."
+ );
+
+ for (const page of contentProcess.pages) {
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.tabID, "number");
+ Assert.equal(page.tabID, activeTabID);
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ pageFound = true;
+ break;
+ }
+ }
+ Assert.equal(pageFound, true);
+
+ info("Flush slow processes with a quick profile.");
+ await startProfiler();
+ for (let i = 0; i < 10; ++i) {
+ await Services.profiler.waitOnePeriodicSampling();
+ }
+ await stopNowAndGetProfile();
+ });
+});
+
+add_task(async function browser_test_profile_very_slow_capture() {
+ Assert.ok(!Services.profiler.IsActive());
+ info("Clear the previous pages just in case we still some open tabs.");
+ await Services.profiler.ClearAllPages();
+
+ info(
+ "Start the profiler to test the page information with single frame page."
+ );
+ await startProfiler({
+ threads: ["GeckoMain", "test-debug-child-very-slow-json"],
+ });
+
+ info("Open a tab with single_frame.html in it.");
+ const url = BASE_URL + "single_frame.html";
+ await BrowserTestUtils.withNewTab(url, async function(contentBrowser) {
+ const contentPid = await SpecialPowers.spawn(contentBrowser, [], () => {
+ return Services.appinfo.processID;
+ });
+
+ info("Capture the profile data.");
+ const profile = await waitSamplingAndStopAndGetProfile();
+
+ info("Check that the content process is missing.");
+
+ let contentProcessIndex = profile.processes.findIndex(
+ p => p.threads[0].pid == contentPid
+ );
+ Assert.equal(contentProcessIndex, -1);
+
+ info("Flush slow processes with a quick profile.");
+ await startProfiler();
+ for (let i = 0; i < 10; ++i) {
+ await Services.profiler.waitOnePeriodicSampling();
+ }
+ await stopNowAndGetProfile();
+ });
+});
diff --git a/tools/profiler/tests/browser/do_work_500ms.html b/tools/profiler/tests/browser/do_work_500ms.html
new file mode 100644
index 0000000000..9713a80671
--- /dev/null
+++ b/tools/profiler/tests/browser/do_work_500ms.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Do some work for 500ms</title>
+ <script>
+ const milliseconds = 500;
+ const millisecondsPerBatch = 10;
+ const end = Date.now() + milliseconds;
+ window.total = 0;
+ let i = 0;
+
+ /**
+ * Do work for a set number of milliseconds, but only do the work in batches
+ * so the browser does not get unresponsive.
+ */
+ function doWork() {
+ const batchEnd = Date.now() + millisecondsPerBatch;
+ // Do some work for a set amount of time.
+ while (Date.now() < end) {
+ // Do some kind of work that is non-deterministic to guard against optimizations.
+ window.total += Math.random();
+ i++;
+
+ // Check if a batch is done yet.
+ if (Date.now() > batchEnd) {
+ // Defer the rest of the work into a micro task. Keep on doing this until
+ // the total milliseconds have elapsed.
+ setTimeout(doWork, 0);
+ return;
+ }
+ }
+ }
+
+ doWork();
+ </script>
+</head>
+<body>
+ Do some work for 500ms.
+</body>
+</html>
diff --git a/tools/profiler/tests/browser/firefox-logo-nightly.svg b/tools/profiler/tests/browser/firefox-logo-nightly.svg
new file mode 100644
index 0000000000..f1af370d87
--- /dev/null
+++ b/tools/profiler/tests/browser/firefox-logo-nightly.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 953.37 984"><defs><linearGradient id="linear-gradient" x1="-14706.28" y1="9250.14" x2="-14443.04" y2="9250.14" gradientTransform="matrix(0.76, 0.03, 0.05, -1.12, 11485.47, 11148)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.1" stop-color="#0092f8"/><stop offset="0.31" stop-color="#00abeb"/><stop offset="0.52" stop-color="#00bee1"/><stop offset="0.75" stop-color="#00c8dc"/><stop offset="1" stop-color="#00ccda"/></linearGradient><radialGradient id="radial-gradient" cx="-7588.66" cy="8866.53" r="791.23" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0.02" stop-color="#005fe7"/><stop offset="0.18" stop-color="#0042b4"/><stop offset="0.32" stop-color="#002989"/><stop offset="0.4" stop-color="#002079"/><stop offset="0.47" stop-color="#131d78"/><stop offset="0.66" stop-color="#3b1676"/><stop offset="0.75" stop-color="#4a1475"/></radialGradient><linearGradient id="linear-gradient-2" x1="539.64" y1="254.8" x2="348.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 1, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000f43" stop-opacity="0.4"/><stop offset="0.48" stop-color="#001962" stop-opacity="0.17"/><stop offset="1" stop-color="#002079" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-3" x1="540.64" y1="254.8" x2="349.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" xlink:href="#linear-gradient-2"/><linearGradient id="linear-gradient-4" x1="-8367.12" y1="7348.87" x2="-8482.36" y2="7357.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#812cc9"/><stop offset="1" stop-color="#005fe7"/></linearGradient><linearGradient id="linear-gradient-5" x1="-8449.89" y1="7496.97" x2="-8341.94" y2="7609.09" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.05" stop-color="#005fe7"/><stop offset="0.18" stop-color="#065de6"/><stop offset="0.35" stop-color="#1856e1"/><stop offset="0.56" stop-color="#354adb"/><stop offset="0.78" stop-color="#5d3ad1"/><stop offset="0.95" stop-color="#812cc9"/></linearGradient><linearGradient id="linear-gradient-6" x1="-8653.41" y1="7245.3" x2="-8422.52" y2="7244.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002079"/><stop offset="0.99" stop-color="#a238ff"/></linearGradient><radialGradient id="radial-gradient-2" cx="644.11" cy="599.83" fx="785.0454815336918" fy="470.6889181532662" r="793.95" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#00fdff"/><stop offset="0.26" stop-color="#0af1ff"/><stop offset="0.37" stop-color="#23d2ff"/><stop offset="0.52" stop-color="#4da0ff"/><stop offset="0.69" stop-color="#855bff"/><stop offset="0.77" stop-color="#a238ff"/><stop offset="0.81" stop-color="#a738fd"/><stop offset="0.86" stop-color="#b539f9"/><stop offset="0.9" stop-color="#cd39f1"/><stop offset="0.96" stop-color="#ee3ae6"/><stop offset="0.98" stop-color="#ff3be0"/></radialGradient><linearGradient id="linear-gradient-7" x1="-7458.97" y1="9093.17" x2="-7531.06" y2="8282.84" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00"/><stop offset="0.1" stop-color="#00e244"/><stop offset="0.22" stop-color="#00d694"/><stop offset="0.31" stop-color="#00cfc7"/><stop offset="0.35" stop-color="#00ccda"/><stop offset="0.42" stop-color="#0bc2dd" stop-opacity="0.92"/><stop offset="0.57" stop-color="#29a7e4" stop-opacity="0.72"/><stop offset="0.77" stop-color="#597df0" stop-opacity="0.4"/><stop offset="1" stop-color="#9448ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-8" x1="-8926.61" y1="7680.53" x2="-8790.14" y2="7680.53" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005fe7"/><stop offset="0.46" stop-color="#0071f3" stop-opacity="0.51"/><stop offset="0.83" stop-color="#007efc" stop-opacity="0.14"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><radialGradient id="radial-gradient-3" cx="-8914.62" cy="7721.05" r="165.97" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.63" stop-color="#ffe302" stop-opacity="0"/><stop offset="0.67" stop-color="#ffe302" stop-opacity="0.05"/><stop offset="0.75" stop-color="#ffe302" stop-opacity="0.19"/><stop offset="0.86" stop-color="#ffe302" stop-opacity="0.4"/><stop offset="0.99" stop-color="#ffe302" stop-opacity="0.7"/></radialGradient><linearGradient id="linear-gradient-9" x1="214.02" y1="2032.47" x2="96.19" y2="2284.31" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, -250.1, 2306.29)" gradientUnits="userSpaceOnUse"><stop offset="0.19" stop-color="#4a1475" stop-opacity="0.5"/><stop offset="0.62" stop-color="#2277ac" stop-opacity="0.23"/><stop offset="0.94" stop-color="#00ccda" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-10" x1="-38.44" y1="278.18" x2="55.67" y2="171.29" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0.01" stop-color="#002079" stop-opacity="0.5"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-11" x1="142.45" y1="96.25" x2="142.5" y2="149.68" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a1475" stop-opacity="0.9"/><stop offset="0.18" stop-color="#6720a2" stop-opacity="0.6"/><stop offset="0.38" stop-color="#812acb" stop-opacity="0.34"/><stop offset="0.57" stop-color="#9332e8" stop-opacity="0.15"/><stop offset="0.76" stop-color="#9e36f9" stop-opacity="0.04"/><stop offset="0.93" stop-color="#a238ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-12" x1="620.52" y1="947.88" x2="926.18" y2="264.39" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00" stop-opacity="0"/><stop offset="0.28" stop-color="#00dc6d" stop-opacity="0.5"/><stop offset="0.5" stop-color="#00d1bb" stop-opacity="0.86"/><stop offset="0.6" stop-color="#00ccda"/><stop offset="0.68" stop-color="#04c9db"/><stop offset="0.75" stop-color="#0fc1df"/><stop offset="0.83" stop-color="#23b2e6"/><stop offset="0.9" stop-color="#3e9ef0"/><stop offset="0.98" stop-color="#6184fc"/><stop offset="0.99" stop-color="#6680fe"/></linearGradient><linearGradient id="linear-gradient-13" x1="680.88" y1="554.79" x2="536.1" y2="166.04" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.04" stop-color="#0083ff" stop-opacity="0.92"/><stop offset="0.14" stop-color="#0083ff" stop-opacity="0.71"/><stop offset="0.26" stop-color="#0083ff" stop-opacity="0.52"/><stop offset="0.37" stop-color="#0083ff" stop-opacity="0.36"/><stop offset="0.49" stop-color="#0083ff" stop-opacity="0.23"/><stop offset="0.61" stop-color="#0083ff" stop-opacity="0.13"/><stop offset="0.73" stop-color="#0083ff" stop-opacity="0.06"/><stop offset="0.86" stop-color="#0083ff" stop-opacity="0.01"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient></defs><title>firefox-logo-nightly</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Layer_2-2" data-name="Layer 2"><g id="Firefox"><path d="M770.28,91.56c-23.95,27.88-35.1,90.64-10.82,154.26s61.5,49.8,84.7,114.67c30.62,85.6,16.37,200.59,16.37,200.59s36.81,106.61,62.47-6.63C979.79,341.74,770.28,143.94,770.28,91.56Z" style="fill:url(#linear-gradient)"/><path id="_Path_" data-name=" Path " d="M476.92,972.83c245.24,0,443.9-199.74,443.9-446s-198.66-446-443.66-446S33.5,280.51,33.5,526.8C33,773.33,231.92,972.83,476.92,972.83Z" style="fill:url(#radial-gradient)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-2)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-3)"/><path d="M711.1,866.71c162.87-18.86,235-186.7,142.38-190C769.85,674,634,875.61,711.1,866.71Z" style="fill:url(#linear-gradient-4)"/><path d="M865.21,642.42C977.26,577.21,948,436.34,948,436.34s-43.25,50.24-72.62,130.32C846.4,646,797.84,681.81,865.21,642.42Z" style="fill:url(#linear-gradient-5)"/><path d="M509.47,950.06C665.7,999.91,800,876.84,717.21,835.74,642,798.68,435.32,926.49,509.47,950.06Z" style="fill:url(#linear-gradient-6)"/><path d="M638.58,21.42l.53-.57A1.7,1.7,0,0,0,638.58,21.42ZM876.85,702.23c3.8-5.36,8.94-22.53,13.48-30.21,27.58-44.52,27.78-80,27.78-80.84,16.66-83.22,15.15-117.2,4.9-180-8.25-50.6-44.32-123.09-75.57-158-32.2-36-9.51-24.25-40.69-50.52-27.33-30.29-53.82-60.29-68.25-72.36C634.22,43.09,636.57,24.58,638.58,21.42c-.34.37-.84.92-1.47,1.64C635.87,18.14,635,14,635,14s-57,57-69,152c-7.83,62,15.38,126.68,49,168a381.62,381.62,0,0,0,59,58h0c25.4,36.48,39.38,81.49,39.38,129.91,0,121.24-98.34,219.53-219.65,219.53a220.14,220.14,0,0,1-49.13-5.52c-57.24-10.92-90.3-39.8-106.78-59.41-9.45-11.23-13.46-19.42-13.46-19.42,51.28,18.37,108,14.53,142.47-4.52,34.75-19.26,55.77-33.55,72.84-27.92,16.82,5.61,30.21-10.67,18.2-27.54-11.77-16.85-42.4-41-87.88-34.29-34.79,5.07-66.66,29.76-112.24,5.84a97.34,97.34,0,0,1-8.55-5c-3-1.77,9.77,2.69,6.79.68-8.87-4.32-24.57-13.73-28.64-17.07-.68-.56,6.88,2.16,6.2,1.6-42.62-31.45-37.3-52.69-36-66,1.07-10.66,8.81-24.32,21.86-29.86,6.3,3.08,10.23,5.43,10.23,5.43s-2.69-4.92-4.14-7.51c.51-.19,1-.15,1.5-.34,5.16,2.23,16.58,8,22.59,11.57,7.83,4.95,10.32,9.36,10.32,9.36s2.06-1,.54-5.33c-.56-1.77-2.93-7.39-10.68-13.07h.48a91.65,91.65,0,0,1,13.13,8.17c2.19-7.12,6.12-14.56,5.25-27.86-.53-9.35-.28-11.78-2.12-15.39-1.65-3.1.92-4.31,3.78-1.09a29.73,29.73,0,0,0-2.44-7.34v-.24c3.57-11.14,75.53-40.12,80.77-43.51a70.24,70.24,0,0,0,21.17-20.63c4-5.72,7-13.73,7.75-25.89.25-5.48-1.44-9.82-20.5-14-11.44-2.49-29.14-4.91-56.43-7.47-19.9-1.76-31.58-14.68-38.21-26.6-1.21-2.57-2.45-4.9-3.68-7.22a53.41,53.41,0,0,1-2.83-8.36,158.47,158.47,0,0,1,61.28-76.06c1.6-1.31-6.4.33-4.8-1,1.87-1.52,14.06-5.93,16.37-6.92,2.81-1.19-12-6.84-25.16-5.47-13.36,1.35-16.19,2.78-23.32,5.49,3-2.64,12.37-6.1,10.16-6.08-14.4,2-32.3,9.48-47.6,18a9.72,9.72,0,0,1,.92-4.31c-7.13,2.71-24.64,13.67-29.73,23a39.79,39.79,0,0,0,.29-5.35,88.55,88.55,0,0,0-14.6,13.7l-.27.22C258.14,196,221.75,195,191,201.72c-6.74-6.06-17.57-15.23-32.89-45.4-1-1.82-1.6,3.75-2.4,2-6-13.81-9.55-36.44-9-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.87-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.37,12.62-1.38,4-3.32,6.27-4.56,11.29l-.29.46c-.1-1.48.37-6.08,0-5.14A235.4,235.4,0,0,0,95.34,186c-5.49,18-11.88,42.61-12.89,74.57-.24,2.42,0,5.14-.25,7.32-13,14.83-21.86,27.39-25.2,33.91-16.81,26-35.33,66.44-53.29,130.46a319.35,319.35,0,0,1,28.54-50C17.32,416.25,2.89,469.62,0,551.8a436.92,436.92,0,0,1,13.87-50.24C11.29,556.36,17.68,624.3,52.32,701c20.57,45,67.92,136.6,183.62,208h0s39.36,29.3,107,51.26c5,1.81,10.06,3.6,15.23,5.33q-2.43-1-4.71-2A484.9,484.9,0,0,0,492.27,984c175.18.15,226.85-70.2,226.85-70.2l-.51.38q3.71-3.49,7.14-7.26c-27.64,26.08-90.75,27.84-114.3,26,40.22-11.81,66.69-21.81,118.17-41.52q9-3.36,18.48-7.64l2-.94c1.25-.58,2.49-1.13,3.75-1.74a349.3,349.3,0,0,0,70.26-44c51.7-41.3,63-81.56,68.83-108.1-.82,2.54-3.37,8.47-5.17,12.32-13.31,28.48-42.84,46-74.91,61a689.05,689.05,0,0,0,42.38-62.44C865.77,729.39,869,713.15,876.85,702.23Z" style="fill:url(#radial-gradient-2)"/><path d="M813.92,801c21.08-23.24,40-49.82,54.35-80,36.9-77.58,94-206.58,49-341.31C881.77,273.22,833,215,771.11,158.12,670.56,65.76,642.48,24.52,642.48,0c0,0-116.09,129.41-65.74,264.38s153.46,130,221.68,270.87c80.27,165.74-64.95,346.61-185,397.24,7.35-1.63,267-60.38,280.61-208.88C893.68,726.34,887.83,767.41,813.92,801Z" style="fill:url(#linear-gradient-7)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="fill:url(#linear-gradient-8)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="opacity:0.5;isolation:isolate;fill:url(#radial-gradient-3)"/><path d="M158.31,156.47c-1-1.82-1.6,3.75-2.4,2-6-13.81-9.58-36.2-8.72-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.86-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.35,12.38-1.65,4.24-3.35,6.52-4.61,11.77-.39,1.43.39-6.32,0-5.38C84.72,201.68,80.19,271,82.69,268,133.17,214.14,191,201.36,191,201.36c-6.15-4.53-19.53-17.63-32.7-44.89Z" style="fill:url(#linear-gradient-9)"/><path d="M349.84,720.1c-69.72-29.77-149-71.75-146-167.14C207.92,427.35,321,452.18,321,452.18c-4.27,1-15.68,9.16-19.72,17.82-4.27,10.83-12.07,35.28,11.55,60.9,37.09,40.19-76.2,95.36,98.66,199.57,4.41,2.4-41-1.43-61.64-10.36Z" style="fill:url(#linear-gradient-10)"/><path d="M325.07,657.5c49.44,17.21,107,14.19,141.52-4.86,23.09-12.85,52.7-33.43,70.92-28.35-15.78-6.24-27.73-9.15-42.1-9.86-2.45,0-5.38,0-8-.32a136,136,0,0,0-15.76.86c-8.9.82-18.77,6.43-27.74,5.53-.48,0,8.7-3.77,8-3.61-4.75,1-9.92,1.21-15.37,1.88-3.47.39-6.45.82-9.89,1-103,8.73-190-55.81-190-55.81-7.41,25,33.17,74.3,88.52,93.57Z" style="opacity:0.5;isolation:isolate;fill:url(#linear-gradient-11)"/><path d="M813.74,801.65c104.16-102.27,156.86-226.58,134.58-366,0,0,8.9,71.5-24.85,144.63,16.21-71.39,18.1-160.11-25-252C841,205.64,746.45,141.11,710.35,114.19,655.66,73.4,633,31.87,632.57,23.3c-16.34,33.48-65.77,148.2-5.31,247,56.64,92.56,145.86,120,208.33,205C950.67,631.67,813.74,801.65,813.74,801.65Z" style="fill:url(#linear-gradient-12)"/><path d="M798.81,535.55C762.41,460.35,717,427.55,674,392c5,7,6.23,9.47,9,14,37.83,40.32,93.61,138.66,53.11,262.11C659.88,900.48,355,791.06,323,760.32,335.93,894.81,561,959.16,707.6,872,791,793,858.47,658.79,798.81,535.55Z" style="fill:url(#linear-gradient-13)"/></g></g></g></g></svg> \ No newline at end of file
diff --git a/tools/profiler/tests/browser/head.js b/tools/profiler/tests/browser/head.js
new file mode 100644
index 0000000000..ef0e3128c0
--- /dev/null
+++ b/tools/profiler/tests/browser/head.js
@@ -0,0 +1,159 @@
+/* import-globals-from ../shared-head.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js",
+ this
+);
+
+const BASE_URL = "http://example.com/browser/tools/profiler/tests/browser/";
+const BASE_URL_HTTPS =
+ "https://example.com/browser/tools/profiler/tests/browser/";
+
+registerCleanupFunction(async () => {
+ if (Services.profiler.IsActive()) {
+ info(
+ "The profiler was found to still be running at the end of the test, which means that some error likely occured. Let's stop it to prevent issues with following tests!"
+ );
+ await Services.profiler.StopProfiler();
+ }
+});
+
+/**
+ * This is a helper function that will stop the profiler and returns the main
+ * threads for the parent process and the content process with PID contentPid.
+ * This happens immediately, without waiting for any sampling to happen or
+ * finish. Use waitSamplingAndStopProfilerAndGetThreads below instead to wait
+ * for samples before stopping.
+ * This returns also the full profile in case the caller wants more information.
+ *
+ * @param {number} contentPid
+ * @returns {Promise<{profile, parentThread, contentProcess, contentThread}>}
+ */
+async function stopProfilerNowAndGetThreads(contentPid) {
+ const profile = await stopNowAndGetProfile();
+
+ const parentThread = profile.threads[0];
+ const contentProcess = profile.processes.find(
+ p => p.threads[0].pid == contentPid
+ );
+ if (!contentProcess) {
+ throw new Error(
+ `Could not find the content process with given pid: ${contentPid}`
+ );
+ }
+
+ if (!parentThread) {
+ throw new Error("The parent thread was not found in the profile.");
+ }
+
+ const contentThread = contentProcess.threads[0];
+ if (!contentThread) {
+ throw new Error("The content thread was not found in the profile.");
+ }
+
+ return { profile, parentThread, contentProcess, contentThread };
+}
+
+/**
+ * This is a helper function that will stop the profiler and returns the main
+ * threads for the parent process and the content process with PID contentPid.
+ * As opposed to stopProfilerNowAndGetThreads (with "Now") above, the profiler
+ * in that PID will not stop until there is at least one periodic sample taken.
+ *
+ * @param {number} contentPid
+ * @returns {Promise<{profile, parentThread, contentProcess, contentThread}>}
+ */
+async function waitSamplingAndStopProfilerAndGetThreads(contentPid) {
+ await Services.profiler.waitOnePeriodicSampling();
+
+ return stopProfilerNowAndGetThreads(contentPid);
+}
+
+/** This tries to find the service worker thread by targeting a very specific
+ * UserTiming marker. Indeed we use performance.mark to add this marker from the
+ * service worker's events.
+ * Then from this thread we get its parent thread. Indeed the parent thread is
+ * where all network stuff happens, so this is useful for network marker tests.
+ *
+ * @param {Object} profile
+ * @returns {{ serviceWorkerThread: Object, serviceWorkerParentThread: Object }} the found threads
+ */
+function findServiceWorkerThreads(profile) {
+ const allThreads = [
+ profile.threads,
+ ...profile.processes.map(process => process.threads),
+ ].flat();
+
+ const serviceWorkerThread = allThreads.find(
+ ({ processType, markers }) =>
+ processType === "tab" &&
+ markers.data.some(markerTuple => {
+ const data = markerTuple[markers.schema.data];
+ return (
+ data &&
+ data.type === "UserTiming" &&
+ data.name === "__serviceworker_event"
+ );
+ })
+ );
+
+ if (!serviceWorkerThread) {
+ info(
+ "We couldn't find a service worker thread. Here are all the threads in this profile:"
+ );
+ allThreads.forEach(logInformationForThread.bind(null, ""));
+ return null;
+ }
+
+ const serviceWorkerParentThread = allThreads.find(
+ ({ name, pid }) => pid === serviceWorkerThread.pid && name === "GeckoMain"
+ );
+
+ if (!serviceWorkerParentThread) {
+ info(
+ `We couldn't find a parent thread for the service worker thread (pid: ${serviceWorkerThread.pid}, tid: ${serviceWorkerThread.tid}).`
+ );
+ info("Here are all the threads in this profile:");
+ allThreads.forEach(logInformationForThread.bind(null, ""));
+
+ // Let's write the profile on disk if MOZ_UPLOAD_DIR is present
+ const path = Services.env.get("MOZ_UPLOAD_DIR");
+ if (path) {
+ const profileName = `profile_${Date.now()}.json`;
+ const profilePath = PathUtils.join(path, profileName);
+ info(
+ `We wrote down the profile on disk as an artifact, with name ${profileName}.`
+ );
+ // This function returns a Promise, but we're not waiting on it because
+ // we're in a synchronous function. Hopefully writing will be finished
+ // when the process ends.
+ IOUtils.writeJSON(profilePath, profile).catch(err =>
+ console.error("An error happened when writing the profile on disk", err)
+ );
+ }
+ throw new Error(
+ "We couldn't find a parent thread for the service worker thread. Please read logs to find more information."
+ );
+ }
+
+ return { serviceWorkerThread, serviceWorkerParentThread };
+}
+
+/**
+ * This logs some basic information about the passed thread.
+ *
+ * @param {string} prefix
+ * @param {Object} thread
+ */
+function logInformationForThread(prefix, thread) {
+ if (!thread) {
+ info(prefix + ": thread is null or undefined.");
+ return;
+ }
+
+ const { name, pid, tid, processName, processType } = thread;
+ info(
+ `${prefix}: ` +
+ `name(${name}) pid(${pid}) tid(${tid}) processName(${processName}) processType(${processType})`
+ );
+}
diff --git a/tools/profiler/tests/browser/multi_frame.html b/tools/profiler/tests/browser/multi_frame.html
new file mode 100644
index 0000000000..b2efcedd50
--- /dev/null
+++ b/tools/profiler/tests/browser/multi_frame.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Multi Frame</title>
+</head>
+<body>
+ Multi Frame
+ <iframe src="single_frame.html"></iframe>
+</body>
+</html>
diff --git a/tools/profiler/tests/browser/page_with_resources.html b/tools/profiler/tests/browser/page_with_resources.html
new file mode 100644
index 0000000000..9d2bb8f218
--- /dev/null
+++ b/tools/profiler/tests/browser/page_with_resources.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ Testing
+ <img src='firefox-logo-nightly.svg' width="24"/>
+ <img src='redirect.sjs?firefox-logo-nightly.svg' width="24"/>
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/redirect.sjs b/tools/profiler/tests/browser/redirect.sjs
new file mode 100644
index 0000000000..2a325c3d0b
--- /dev/null
+++ b/tools/profiler/tests/browser/redirect.sjs
@@ -0,0 +1,8 @@
+function handleRequest(request, response) {
+ response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
+ response.setHeader(
+ "Location",
+ decodeURIComponent(request.queryString),
+ false
+ );
+}
diff --git a/tools/profiler/tests/browser/serviceworkers/firefox-logo-nightly.svg b/tools/profiler/tests/browser/serviceworkers/firefox-logo-nightly.svg
new file mode 100644
index 0000000000..f1af370d87
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/firefox-logo-nightly.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 953.37 984"><defs><linearGradient id="linear-gradient" x1="-14706.28" y1="9250.14" x2="-14443.04" y2="9250.14" gradientTransform="matrix(0.76, 0.03, 0.05, -1.12, 11485.47, 11148)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.1" stop-color="#0092f8"/><stop offset="0.31" stop-color="#00abeb"/><stop offset="0.52" stop-color="#00bee1"/><stop offset="0.75" stop-color="#00c8dc"/><stop offset="1" stop-color="#00ccda"/></linearGradient><radialGradient id="radial-gradient" cx="-7588.66" cy="8866.53" r="791.23" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0.02" stop-color="#005fe7"/><stop offset="0.18" stop-color="#0042b4"/><stop offset="0.32" stop-color="#002989"/><stop offset="0.4" stop-color="#002079"/><stop offset="0.47" stop-color="#131d78"/><stop offset="0.66" stop-color="#3b1676"/><stop offset="0.75" stop-color="#4a1475"/></radialGradient><linearGradient id="linear-gradient-2" x1="539.64" y1="254.8" x2="348.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 1, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000f43" stop-opacity="0.4"/><stop offset="0.48" stop-color="#001962" stop-opacity="0.17"/><stop offset="1" stop-color="#002079" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-3" x1="540.64" y1="254.8" x2="349.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" xlink:href="#linear-gradient-2"/><linearGradient id="linear-gradient-4" x1="-8367.12" y1="7348.87" x2="-8482.36" y2="7357.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#812cc9"/><stop offset="1" stop-color="#005fe7"/></linearGradient><linearGradient id="linear-gradient-5" x1="-8449.89" y1="7496.97" x2="-8341.94" y2="7609.09" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.05" stop-color="#005fe7"/><stop offset="0.18" stop-color="#065de6"/><stop offset="0.35" stop-color="#1856e1"/><stop offset="0.56" stop-color="#354adb"/><stop offset="0.78" stop-color="#5d3ad1"/><stop offset="0.95" stop-color="#812cc9"/></linearGradient><linearGradient id="linear-gradient-6" x1="-8653.41" y1="7245.3" x2="-8422.52" y2="7244.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002079"/><stop offset="0.99" stop-color="#a238ff"/></linearGradient><radialGradient id="radial-gradient-2" cx="644.11" cy="599.83" fx="785.0454815336918" fy="470.6889181532662" r="793.95" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#00fdff"/><stop offset="0.26" stop-color="#0af1ff"/><stop offset="0.37" stop-color="#23d2ff"/><stop offset="0.52" stop-color="#4da0ff"/><stop offset="0.69" stop-color="#855bff"/><stop offset="0.77" stop-color="#a238ff"/><stop offset="0.81" stop-color="#a738fd"/><stop offset="0.86" stop-color="#b539f9"/><stop offset="0.9" stop-color="#cd39f1"/><stop offset="0.96" stop-color="#ee3ae6"/><stop offset="0.98" stop-color="#ff3be0"/></radialGradient><linearGradient id="linear-gradient-7" x1="-7458.97" y1="9093.17" x2="-7531.06" y2="8282.84" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00"/><stop offset="0.1" stop-color="#00e244"/><stop offset="0.22" stop-color="#00d694"/><stop offset="0.31" stop-color="#00cfc7"/><stop offset="0.35" stop-color="#00ccda"/><stop offset="0.42" stop-color="#0bc2dd" stop-opacity="0.92"/><stop offset="0.57" stop-color="#29a7e4" stop-opacity="0.72"/><stop offset="0.77" stop-color="#597df0" stop-opacity="0.4"/><stop offset="1" stop-color="#9448ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-8" x1="-8926.61" y1="7680.53" x2="-8790.14" y2="7680.53" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005fe7"/><stop offset="0.46" stop-color="#0071f3" stop-opacity="0.51"/><stop offset="0.83" stop-color="#007efc" stop-opacity="0.14"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><radialGradient id="radial-gradient-3" cx="-8914.62" cy="7721.05" r="165.97" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.63" stop-color="#ffe302" stop-opacity="0"/><stop offset="0.67" stop-color="#ffe302" stop-opacity="0.05"/><stop offset="0.75" stop-color="#ffe302" stop-opacity="0.19"/><stop offset="0.86" stop-color="#ffe302" stop-opacity="0.4"/><stop offset="0.99" stop-color="#ffe302" stop-opacity="0.7"/></radialGradient><linearGradient id="linear-gradient-9" x1="214.02" y1="2032.47" x2="96.19" y2="2284.31" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, -250.1, 2306.29)" gradientUnits="userSpaceOnUse"><stop offset="0.19" stop-color="#4a1475" stop-opacity="0.5"/><stop offset="0.62" stop-color="#2277ac" stop-opacity="0.23"/><stop offset="0.94" stop-color="#00ccda" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-10" x1="-38.44" y1="278.18" x2="55.67" y2="171.29" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0.01" stop-color="#002079" stop-opacity="0.5"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-11" x1="142.45" y1="96.25" x2="142.5" y2="149.68" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a1475" stop-opacity="0.9"/><stop offset="0.18" stop-color="#6720a2" stop-opacity="0.6"/><stop offset="0.38" stop-color="#812acb" stop-opacity="0.34"/><stop offset="0.57" stop-color="#9332e8" stop-opacity="0.15"/><stop offset="0.76" stop-color="#9e36f9" stop-opacity="0.04"/><stop offset="0.93" stop-color="#a238ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-12" x1="620.52" y1="947.88" x2="926.18" y2="264.39" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00" stop-opacity="0"/><stop offset="0.28" stop-color="#00dc6d" stop-opacity="0.5"/><stop offset="0.5" stop-color="#00d1bb" stop-opacity="0.86"/><stop offset="0.6" stop-color="#00ccda"/><stop offset="0.68" stop-color="#04c9db"/><stop offset="0.75" stop-color="#0fc1df"/><stop offset="0.83" stop-color="#23b2e6"/><stop offset="0.9" stop-color="#3e9ef0"/><stop offset="0.98" stop-color="#6184fc"/><stop offset="0.99" stop-color="#6680fe"/></linearGradient><linearGradient id="linear-gradient-13" x1="680.88" y1="554.79" x2="536.1" y2="166.04" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.04" stop-color="#0083ff" stop-opacity="0.92"/><stop offset="0.14" stop-color="#0083ff" stop-opacity="0.71"/><stop offset="0.26" stop-color="#0083ff" stop-opacity="0.52"/><stop offset="0.37" stop-color="#0083ff" stop-opacity="0.36"/><stop offset="0.49" stop-color="#0083ff" stop-opacity="0.23"/><stop offset="0.61" stop-color="#0083ff" stop-opacity="0.13"/><stop offset="0.73" stop-color="#0083ff" stop-opacity="0.06"/><stop offset="0.86" stop-color="#0083ff" stop-opacity="0.01"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient></defs><title>firefox-logo-nightly</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Layer_2-2" data-name="Layer 2"><g id="Firefox"><path d="M770.28,91.56c-23.95,27.88-35.1,90.64-10.82,154.26s61.5,49.8,84.7,114.67c30.62,85.6,16.37,200.59,16.37,200.59s36.81,106.61,62.47-6.63C979.79,341.74,770.28,143.94,770.28,91.56Z" style="fill:url(#linear-gradient)"/><path id="_Path_" data-name=" Path " d="M476.92,972.83c245.24,0,443.9-199.74,443.9-446s-198.66-446-443.66-446S33.5,280.51,33.5,526.8C33,773.33,231.92,972.83,476.92,972.83Z" style="fill:url(#radial-gradient)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-2)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-3)"/><path d="M711.1,866.71c162.87-18.86,235-186.7,142.38-190C769.85,674,634,875.61,711.1,866.71Z" style="fill:url(#linear-gradient-4)"/><path d="M865.21,642.42C977.26,577.21,948,436.34,948,436.34s-43.25,50.24-72.62,130.32C846.4,646,797.84,681.81,865.21,642.42Z" style="fill:url(#linear-gradient-5)"/><path d="M509.47,950.06C665.7,999.91,800,876.84,717.21,835.74,642,798.68,435.32,926.49,509.47,950.06Z" style="fill:url(#linear-gradient-6)"/><path d="M638.58,21.42l.53-.57A1.7,1.7,0,0,0,638.58,21.42ZM876.85,702.23c3.8-5.36,8.94-22.53,13.48-30.21,27.58-44.52,27.78-80,27.78-80.84,16.66-83.22,15.15-117.2,4.9-180-8.25-50.6-44.32-123.09-75.57-158-32.2-36-9.51-24.25-40.69-50.52-27.33-30.29-53.82-60.29-68.25-72.36C634.22,43.09,636.57,24.58,638.58,21.42c-.34.37-.84.92-1.47,1.64C635.87,18.14,635,14,635,14s-57,57-69,152c-7.83,62,15.38,126.68,49,168a381.62,381.62,0,0,0,59,58h0c25.4,36.48,39.38,81.49,39.38,129.91,0,121.24-98.34,219.53-219.65,219.53a220.14,220.14,0,0,1-49.13-5.52c-57.24-10.92-90.3-39.8-106.78-59.41-9.45-11.23-13.46-19.42-13.46-19.42,51.28,18.37,108,14.53,142.47-4.52,34.75-19.26,55.77-33.55,72.84-27.92,16.82,5.61,30.21-10.67,18.2-27.54-11.77-16.85-42.4-41-87.88-34.29-34.79,5.07-66.66,29.76-112.24,5.84a97.34,97.34,0,0,1-8.55-5c-3-1.77,9.77,2.69,6.79.68-8.87-4.32-24.57-13.73-28.64-17.07-.68-.56,6.88,2.16,6.2,1.6-42.62-31.45-37.3-52.69-36-66,1.07-10.66,8.81-24.32,21.86-29.86,6.3,3.08,10.23,5.43,10.23,5.43s-2.69-4.92-4.14-7.51c.51-.19,1-.15,1.5-.34,5.16,2.23,16.58,8,22.59,11.57,7.83,4.95,10.32,9.36,10.32,9.36s2.06-1,.54-5.33c-.56-1.77-2.93-7.39-10.68-13.07h.48a91.65,91.65,0,0,1,13.13,8.17c2.19-7.12,6.12-14.56,5.25-27.86-.53-9.35-.28-11.78-2.12-15.39-1.65-3.1.92-4.31,3.78-1.09a29.73,29.73,0,0,0-2.44-7.34v-.24c3.57-11.14,75.53-40.12,80.77-43.51a70.24,70.24,0,0,0,21.17-20.63c4-5.72,7-13.73,7.75-25.89.25-5.48-1.44-9.82-20.5-14-11.44-2.49-29.14-4.91-56.43-7.47-19.9-1.76-31.58-14.68-38.21-26.6-1.21-2.57-2.45-4.9-3.68-7.22a53.41,53.41,0,0,1-2.83-8.36,158.47,158.47,0,0,1,61.28-76.06c1.6-1.31-6.4.33-4.8-1,1.87-1.52,14.06-5.93,16.37-6.92,2.81-1.19-12-6.84-25.16-5.47-13.36,1.35-16.19,2.78-23.32,5.49,3-2.64,12.37-6.1,10.16-6.08-14.4,2-32.3,9.48-47.6,18a9.72,9.72,0,0,1,.92-4.31c-7.13,2.71-24.64,13.67-29.73,23a39.79,39.79,0,0,0,.29-5.35,88.55,88.55,0,0,0-14.6,13.7l-.27.22C258.14,196,221.75,195,191,201.72c-6.74-6.06-17.57-15.23-32.89-45.4-1-1.82-1.6,3.75-2.4,2-6-13.81-9.55-36.44-9-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.87-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.37,12.62-1.38,4-3.32,6.27-4.56,11.29l-.29.46c-.1-1.48.37-6.08,0-5.14A235.4,235.4,0,0,0,95.34,186c-5.49,18-11.88,42.61-12.89,74.57-.24,2.42,0,5.14-.25,7.32-13,14.83-21.86,27.39-25.2,33.91-16.81,26-35.33,66.44-53.29,130.46a319.35,319.35,0,0,1,28.54-50C17.32,416.25,2.89,469.62,0,551.8a436.92,436.92,0,0,1,13.87-50.24C11.29,556.36,17.68,624.3,52.32,701c20.57,45,67.92,136.6,183.62,208h0s39.36,29.3,107,51.26c5,1.81,10.06,3.6,15.23,5.33q-2.43-1-4.71-2A484.9,484.9,0,0,0,492.27,984c175.18.15,226.85-70.2,226.85-70.2l-.51.38q3.71-3.49,7.14-7.26c-27.64,26.08-90.75,27.84-114.3,26,40.22-11.81,66.69-21.81,118.17-41.52q9-3.36,18.48-7.64l2-.94c1.25-.58,2.49-1.13,3.75-1.74a349.3,349.3,0,0,0,70.26-44c51.7-41.3,63-81.56,68.83-108.1-.82,2.54-3.37,8.47-5.17,12.32-13.31,28.48-42.84,46-74.91,61a689.05,689.05,0,0,0,42.38-62.44C865.77,729.39,869,713.15,876.85,702.23Z" style="fill:url(#radial-gradient-2)"/><path d="M813.92,801c21.08-23.24,40-49.82,54.35-80,36.9-77.58,94-206.58,49-341.31C881.77,273.22,833,215,771.11,158.12,670.56,65.76,642.48,24.52,642.48,0c0,0-116.09,129.41-65.74,264.38s153.46,130,221.68,270.87c80.27,165.74-64.95,346.61-185,397.24,7.35-1.63,267-60.38,280.61-208.88C893.68,726.34,887.83,767.41,813.92,801Z" style="fill:url(#linear-gradient-7)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="fill:url(#linear-gradient-8)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="opacity:0.5;isolation:isolate;fill:url(#radial-gradient-3)"/><path d="M158.31,156.47c-1-1.82-1.6,3.75-2.4,2-6-13.81-9.58-36.2-8.72-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.86-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.35,12.38-1.65,4.24-3.35,6.52-4.61,11.77-.39,1.43.39-6.32,0-5.38C84.72,201.68,80.19,271,82.69,268,133.17,214.14,191,201.36,191,201.36c-6.15-4.53-19.53-17.63-32.7-44.89Z" style="fill:url(#linear-gradient-9)"/><path d="M349.84,720.1c-69.72-29.77-149-71.75-146-167.14C207.92,427.35,321,452.18,321,452.18c-4.27,1-15.68,9.16-19.72,17.82-4.27,10.83-12.07,35.28,11.55,60.9,37.09,40.19-76.2,95.36,98.66,199.57,4.41,2.4-41-1.43-61.64-10.36Z" style="fill:url(#linear-gradient-10)"/><path d="M325.07,657.5c49.44,17.21,107,14.19,141.52-4.86,23.09-12.85,52.7-33.43,70.92-28.35-15.78-6.24-27.73-9.15-42.1-9.86-2.45,0-5.38,0-8-.32a136,136,0,0,0-15.76.86c-8.9.82-18.77,6.43-27.74,5.53-.48,0,8.7-3.77,8-3.61-4.75,1-9.92,1.21-15.37,1.88-3.47.39-6.45.82-9.89,1-103,8.73-190-55.81-190-55.81-7.41,25,33.17,74.3,88.52,93.57Z" style="opacity:0.5;isolation:isolate;fill:url(#linear-gradient-11)"/><path d="M813.74,801.65c104.16-102.27,156.86-226.58,134.58-366,0,0,8.9,71.5-24.85,144.63,16.21-71.39,18.1-160.11-25-252C841,205.64,746.45,141.11,710.35,114.19,655.66,73.4,633,31.87,632.57,23.3c-16.34,33.48-65.77,148.2-5.31,247,56.64,92.56,145.86,120,208.33,205C950.67,631.67,813.74,801.65,813.74,801.65Z" style="fill:url(#linear-gradient-12)"/><path d="M798.81,535.55C762.41,460.35,717,427.55,674,392c5,7,6.23,9.47,9,14,37.83,40.32,93.61,138.66,53.11,262.11C659.88,900.48,355,791.06,323,760.32,335.93,894.81,561,959.16,707.6,872,791,793,858.47,658.79,798.81,535.55Z" style="fill:url(#linear-gradient-13)"/></g></g></g></g></svg> \ No newline at end of file
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker-utils.js b/tools/profiler/tests/browser/serviceworkers/serviceworker-utils.js
new file mode 100644
index 0000000000..16a9f0c91f
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker-utils.js
@@ -0,0 +1,39 @@
+// Most of this file has been stolen from dom/serviceworkers/test/utils.js.
+
+function waitForState(worker, state) {
+ return new Promise((resolve, reject) => {
+ function onStateChange() {
+ if (worker.state === state) {
+ worker.removeEventListener("statechange", onStateChange);
+ resolve();
+ }
+ if (worker.state === "redundant") {
+ worker.removeEventListener("statechange", onStateChange);
+ reject(new Error("The service worker failed to install."));
+ }
+ }
+
+ // First add an event listener, so we won't miss any change that happens
+ // before we check the current state.
+ worker.addEventListener("statechange", onStateChange);
+
+ // Now check if the worker is already in the desired state.
+ onStateChange();
+ });
+}
+
+async function registerServiceWorkerAndWait(serviceWorkerFile) {
+ if (!serviceWorkerFile) {
+ throw new Error(
+ "No service worker filename has been specified. Please specify a valid filename."
+ );
+ }
+
+ console.log(`...registering the serviceworker "${serviceWorkerFile}"`);
+ const reg = await navigator.serviceWorker.register(`./${serviceWorkerFile}`, {
+ scope: "./",
+ });
+ console.log("...waiting for activation");
+ await waitForState(reg.installing, "activated");
+ console.log("...activated!");
+}
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_cache_first.js b/tools/profiler/tests/browser/serviceworkers/serviceworker_cache_first.js
new file mode 100644
index 0000000000..baa07fd6d8
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_cache_first.js
@@ -0,0 +1,34 @@
+const files = ["serviceworker_page.html", "firefox-logo-nightly.svg"];
+const cacheName = "v1";
+
+self.addEventListener("install", event => {
+ performance.mark("__serviceworker_event");
+ console.log("[SW]:", "Install event");
+
+ event.waitUntil(cacheAssets());
+});
+
+async function cacheAssets() {
+ const cache = await caches.open(cacheName);
+ await cache.addAll(files);
+}
+
+self.addEventListener("fetch", event => {
+ performance.mark("__serviceworker_event");
+ console.log("Handling fetch event for", event.request.url);
+ event.respondWith(handleFetch(event.request));
+});
+
+async function handleFetch(request) {
+ const cachedResponse = await caches.match(request);
+ if (cachedResponse) {
+ console.log("Found response in cache:", cachedResponse);
+
+ return cachedResponse;
+ }
+ console.log("No response found in cache. About to fetch from network...");
+
+ const networkResponse = await fetch(request);
+ console.log("Response from network is:", networkResponse);
+ return networkResponse;
+}
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_no_fetch_handler.js b/tools/profiler/tests/browser/serviceworkers/serviceworker_no_fetch_handler.js
new file mode 100644
index 0000000000..f656665ca0
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_no_fetch_handler.js
@@ -0,0 +1,4 @@
+self.addEventListener("install", event => {
+ performance.mark("__serviceworker_event");
+ console.log("[SW]:", "Install event");
+});
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_no_respondWith_in_fetch_handler.js b/tools/profiler/tests/browser/serviceworkers/serviceworker_no_respondWith_in_fetch_handler.js
new file mode 100644
index 0000000000..255c8269a1
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_no_respondWith_in_fetch_handler.js
@@ -0,0 +1,9 @@
+self.addEventListener("install", event => {
+ performance.mark("__serviceworker_event");
+ console.log("[SW]:", "Install event");
+});
+
+self.addEventListener("fetch", event => {
+ performance.mark("__serviceworker_event");
+ console.log("Handling fetch event for", event.request.url);
+});
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_page.html b/tools/profiler/tests/browser/serviceworkers/serviceworker_page.html
new file mode 100644
index 0000000000..1c2100a9d6
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_page.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <meta name='viewport' content='initial-scale=1'>
+ </head>
+ <body>
+ <img src='firefox-logo-nightly.svg' width="24">
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_register.html b/tools/profiler/tests/browser/serviceworkers/serviceworker_register.html
new file mode 100644
index 0000000000..86719787f4
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_register.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset='utf-8'>
+ <script src='serviceworker-utils.js'></script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_simple.html b/tools/profiler/tests/browser/serviceworkers/serviceworker_simple.html
new file mode 100644
index 0000000000..f7c32d02c3
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_simple.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ Testing
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/serviceworkers/serviceworker_synthetized_response.js b/tools/profiler/tests/browser/serviceworkers/serviceworker_synthetized_response.js
new file mode 100644
index 0000000000..891b679a5f
--- /dev/null
+++ b/tools/profiler/tests/browser/serviceworkers/serviceworker_synthetized_response.js
@@ -0,0 +1,27 @@
+self.addEventListener("install", event => {
+ performance.mark("__serviceworker_event");
+ dump("[SW]:", "Install event\n");
+});
+
+self.addEventListener("fetch", event => {
+ performance.mark("__serviceworker_event");
+ dump(`Handling fetch event for ${event.request.url}\n`);
+ event.respondWith(handleFetch(event.request));
+});
+
+async function handleFetch(request) {
+ if (request.url.endsWith("-generated.svg")) {
+ dump(
+ "An icon file that should be generated was requested, let's answer directly.\n"
+ );
+ return new Response(
+ `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 953.37 984"><defs><linearGradient id="linear-gradient" x1="-14706.28" y1="9250.14" x2="-14443.04" y2="9250.14" gradientTransform="matrix(0.76, 0.03, 0.05, -1.12, 11485.47, 11148)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.1" stop-color="#0092f8"/><stop offset="0.31" stop-color="#00abeb"/><stop offset="0.52" stop-color="#00bee1"/><stop offset="0.75" stop-color="#00c8dc"/><stop offset="1" stop-color="#00ccda"/></linearGradient><radialGradient id="radial-gradient" cx="-7588.66" cy="8866.53" r="791.23" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0.02" stop-color="#005fe7"/><stop offset="0.18" stop-color="#0042b4"/><stop offset="0.32" stop-color="#002989"/><stop offset="0.4" stop-color="#002079"/><stop offset="0.47" stop-color="#131d78"/><stop offset="0.66" stop-color="#3b1676"/><stop offset="0.75" stop-color="#4a1475"/></radialGradient><linearGradient id="linear-gradient-2" x1="539.64" y1="254.8" x2="348.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 1, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#000f43" stop-opacity="0.4"/><stop offset="0.48" stop-color="#001962" stop-opacity="0.17"/><stop offset="1" stop-color="#002079" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-3" x1="540.64" y1="254.8" x2="349.2" y2="881.03" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" xlink:href="#linear-gradient-2"/><linearGradient id="linear-gradient-4" x1="-8367.12" y1="7348.87" x2="-8482.36" y2="7357.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#812cc9"/><stop offset="1" stop-color="#005fe7"/></linearGradient><linearGradient id="linear-gradient-5" x1="-8449.89" y1="7496.97" x2="-8341.94" y2="7609.09" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.05" stop-color="#005fe7"/><stop offset="0.18" stop-color="#065de6"/><stop offset="0.35" stop-color="#1856e1"/><stop offset="0.56" stop-color="#354adb"/><stop offset="0.78" stop-color="#5d3ad1"/><stop offset="0.95" stop-color="#812cc9"/></linearGradient><linearGradient id="linear-gradient-6" x1="-8653.41" y1="7245.3" x2="-8422.52" y2="7244.76" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#002079"/><stop offset="0.99" stop-color="#a238ff"/></linearGradient><radialGradient id="radial-gradient-2" cx="644.11" cy="599.83" fx="785.0454815336918" fy="470.6889181532662" r="793.95" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0.2" stop-color="#00fdff"/><stop offset="0.26" stop-color="#0af1ff"/><stop offset="0.37" stop-color="#23d2ff"/><stop offset="0.52" stop-color="#4da0ff"/><stop offset="0.69" stop-color="#855bff"/><stop offset="0.77" stop-color="#a238ff"/><stop offset="0.81" stop-color="#a738fd"/><stop offset="0.86" stop-color="#b539f9"/><stop offset="0.9" stop-color="#cd39f1"/><stop offset="0.96" stop-color="#ee3ae6"/><stop offset="0.98" stop-color="#ff3be0"/></radialGradient><linearGradient id="linear-gradient-7" x1="-7458.97" y1="9093.17" x2="-7531.06" y2="8282.84" gradientTransform="matrix(1.23, 0, 0, -1.22, 9958.21, 11048.11)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00"/><stop offset="0.1" stop-color="#00e244"/><stop offset="0.22" stop-color="#00d694"/><stop offset="0.31" stop-color="#00cfc7"/><stop offset="0.35" stop-color="#00ccda"/><stop offset="0.42" stop-color="#0bc2dd" stop-opacity="0.92"/><stop offset="0.57" stop-color="#29a7e4" stop-opacity="0.72"/><stop offset="0.77" stop-color="#597df0" stop-opacity="0.4"/><stop offset="1" stop-color="#9448ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-8" x1="-8926.61" y1="7680.53" x2="-8790.14" y2="7680.53" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005fe7"/><stop offset="0.46" stop-color="#0071f3" stop-opacity="0.51"/><stop offset="0.83" stop-color="#007efc" stop-opacity="0.14"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><radialGradient id="radial-gradient-3" cx="-8914.62" cy="7721.05" r="165.97" gradientTransform="matrix(1.22, 0.12, 0.12, -1.22, 10241.06, 10765.32)" gradientUnits="userSpaceOnUse"><stop offset="0.63" stop-color="#ffe302" stop-opacity="0"/><stop offset="0.67" stop-color="#ffe302" stop-opacity="0.05"/><stop offset="0.75" stop-color="#ffe302" stop-opacity="0.19"/><stop offset="0.86" stop-color="#ffe302" stop-opacity="0.4"/><stop offset="0.99" stop-color="#ffe302" stop-opacity="0.7"/></radialGradient><linearGradient id="linear-gradient-9" x1="214.02" y1="2032.47" x2="96.19" y2="2284.31" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, -250.1, 2306.29)" gradientUnits="userSpaceOnUse"><stop offset="0.19" stop-color="#4a1475" stop-opacity="0.5"/><stop offset="0.62" stop-color="#2277ac" stop-opacity="0.23"/><stop offset="0.94" stop-color="#00ccda" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-10" x1="-38.44" y1="278.18" x2="55.67" y2="171.29" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0.01" stop-color="#002079" stop-opacity="0.5"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-11" x1="142.45" y1="96.25" x2="142.5" y2="149.68" gradientTransform="matrix(0.99, 0.1, 0.1, -0.99, 229.04, 745.87)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4a1475" stop-opacity="0.9"/><stop offset="0.18" stop-color="#6720a2" stop-opacity="0.6"/><stop offset="0.38" stop-color="#812acb" stop-opacity="0.34"/><stop offset="0.57" stop-color="#9332e8" stop-opacity="0.15"/><stop offset="0.76" stop-color="#9e36f9" stop-opacity="0.04"/><stop offset="0.93" stop-color="#a238ff" stop-opacity="0"/></linearGradient><linearGradient id="linear-gradient-12" x1="620.52" y1="947.88" x2="926.18" y2="264.39" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#00ec00" stop-opacity="0"/><stop offset="0.28" stop-color="#00dc6d" stop-opacity="0.5"/><stop offset="0.5" stop-color="#00d1bb" stop-opacity="0.86"/><stop offset="0.6" stop-color="#00ccda"/><stop offset="0.68" stop-color="#04c9db"/><stop offset="0.75" stop-color="#0fc1df"/><stop offset="0.83" stop-color="#23b2e6"/><stop offset="0.9" stop-color="#3e9ef0"/><stop offset="0.98" stop-color="#6184fc"/><stop offset="0.99" stop-color="#6680fe"/></linearGradient><linearGradient id="linear-gradient-13" x1="680.88" y1="554.79" x2="536.1" y2="166.04" gradientTransform="matrix(1, 0, 0, -1, 0, 984)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0083ff"/><stop offset="0.04" stop-color="#0083ff" stop-opacity="0.92"/><stop offset="0.14" stop-color="#0083ff" stop-opacity="0.71"/><stop offset="0.26" stop-color="#0083ff" stop-opacity="0.52"/><stop offset="0.37" stop-color="#0083ff" stop-opacity="0.36"/><stop offset="0.49" stop-color="#0083ff" stop-opacity="0.23"/><stop offset="0.61" stop-color="#0083ff" stop-opacity="0.13"/><stop offset="0.73" stop-color="#0083ff" stop-opacity="0.06"/><stop offset="0.86" stop-color="#0083ff" stop-opacity="0.01"/><stop offset="1" stop-color="#0083ff" stop-opacity="0"/></linearGradient></defs><title>firefox-logo-nightly</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Layer_2-2" data-name="Layer 2"><g id="Firefox"><path d="M770.28,91.56c-23.95,27.88-35.1,90.64-10.82,154.26s61.5,49.8,84.7,114.67c30.62,85.6,16.37,200.59,16.37,200.59s36.81,106.61,62.47-6.63C979.79,341.74,770.28,143.94,770.28,91.56Z" style="fill:url(#linear-gradient)"/><path id="_Path_" data-name=" Path " d="M476.92,972.83c245.24,0,443.9-199.74,443.9-446s-198.66-446-443.66-446S33.5,280.51,33.5,526.8C33,773.33,231.92,972.83,476.92,972.83Z" style="fill:url(#radial-gradient)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-2)"/><path d="M810.67,803.64a246.8,246.8,0,0,1-30.12,18.18,705.31,705.31,0,0,0,38.3-63c9.46-10.47,18.13-20.65,25.19-31.65,3.44-5.41,7.31-12.08,11.42-19.82,24.92-44.9,52.4-117.56,53.18-192.2v-5.66a257.25,257.25,0,0,0-5.71-55.75c.2,1.43.38,2.86.56,4.29-.22-1.1-.41-2.21-.64-3.31.37,2,.66,4,1,6,5.09,43.22,1.47,85.37-16.68,116.45-.29.45-.58.88-.87,1.32,9.41-47.23,12.56-99.39,2.09-151.6,0,0-4.19-25.38-35.38-102.44-18-44.35-49.83-80.72-78-107.21-24.69-30.55-47.11-51-59.47-64.06C689.72,126,678.9,105.61,674.45,92.31c-3.85-1.93-53.14-49.81-57.05-51.63-21.51,33.35-89.16,137.67-57,235.15,14.58,44.17,51.47,90,90.07,115.74,1.69,1.94,23,25,33.09,77.16,10.45,53.85,5,95.86-16.54,158C641.73,681.24,577,735.12,516.3,740.63c-129.67,11.78-177.15-65.11-177.15-65.11C385.49,694,436.72,690.17,467.87,671c31.4-19.43,50.39-33.83,65.81-28.15C548.86,648.43,561,632,550.1,615a78.5,78.5,0,0,0-79.4-34.57c-31.43,5.11-60.23,30-101.41,5.89a86.29,86.29,0,0,1-7.73-5.06c-2.71-1.79,8.83,2.72,6.13.69-8-4.35-22.2-13.84-25.88-17.22-.61-.56,6.22,2.18,5.61,1.62-38.51-31.71-33.7-53.13-32.49-66.57,1-10.75,8-24.52,19.75-30.11,5.69,3.11,9.24,5.48,9.24,5.48s-2.43-5-3.74-7.58c.46-.2.9-.15,1.36-.34,4.66,2.25,15,8.1,20.41,11.67,7.07,5,9.33,9.44,9.33,9.44s1.86-1,.48-5.37c-.5-1.78-2.65-7.45-9.65-13.17h.44A81.61,81.61,0,0,1,374.42,478c2-7.18,5.53-14.68,4.75-28.09-.48-9.43-.26-11.87-1.92-15.51-1.49-3.13.83-4.35,3.42-1.1a32.5,32.5,0,0,0-2.21-7.4v-.24c3.23-11.24,68.25-40.46,73-43.88A67.2,67.2,0,0,0,470.59,361c3.62-5.76,6.34-13.85,7-26.11.36-8.84-3.76-14.73-69.51-21.62-18-1.77-28.53-14.8-34.53-26.82-1.09-2.59-2.21-4.94-3.33-7.28a57.68,57.68,0,0,1-2.56-8.43c10.75-30.87,28.81-57,55.37-76.7,1.45-1.32-5.78.34-4.34-1,1.69-1.54,12.71-6,14.79-7,2.54-1.2-10.88-6.9-22.73-5.51-12.07,1.36-14.63,2.8-21.07,5.53,2.67-2.66,11.17-6.15,9.18-6.13-13,2-29.18,9.56-43,18.12a10.66,10.66,0,0,1,.83-4.35c-6.44,2.73-22.26,13.79-26.87,23.14a44.29,44.29,0,0,0,.27-5.4,84.17,84.17,0,0,0-13.19,13.82l-.24.22c-37.36-15-70.23-16-98.05-9.28-6.09-6.11-9.06-1.64-22.91-32.07-.94-1.83.72,1.81,0,0-2.28-5.9,1.39,7.87,0,0-23.28,18.37-53.92,39.19-68.63,53.89-.18.59,17.16-4.9,0,0-6,1.72-5.6,5.28-6.51,37.5-.22,2.44,0,5.18-.22,7.38-11.75,15-19.75,27.64-22.78,34.21-15.19,26.18-31.93,67-48.15,131.55A334.82,334.82,0,0,1,75.2,398.36C61.71,432.63,48.67,486.44,46.07,569.3A482.08,482.08,0,0,1,58.6,518.64,473,473,0,0,0,93.33,719.71c9.33,22.82,24.76,57.46,51,95.4C226.9,902,343.31,956,472.21,956,606.79,956,727.64,897.13,810.67,803.64Z" style="fill:url(#linear-gradient-3)"/><path d="M711.1,866.71c162.87-18.86,235-186.7,142.38-190C769.85,674,634,875.61,711.1,866.71Z" style="fill:url(#linear-gradient-4)"/><path d="M865.21,642.42C977.26,577.21,948,436.34,948,436.34s-43.25,50.24-72.62,130.32C846.4,646,797.84,681.81,865.21,642.42Z" style="fill:url(#linear-gradient-5)"/><path d="M509.47,950.06C665.7,999.91,800,876.84,717.21,835.74,642,798.68,435.32,926.49,509.47,950.06Z" style="fill:url(#linear-gradient-6)"/><path d="M638.58,21.42l.53-.57A1.7,1.7,0,0,0,638.58,21.42ZM876.85,702.23c3.8-5.36,8.94-22.53,13.48-30.21,27.58-44.52,27.78-80,27.78-80.84,16.66-83.22,15.15-117.2,4.9-180-8.25-50.6-44.32-123.09-75.57-158-32.2-36-9.51-24.25-40.69-50.52-27.33-30.29-53.82-60.29-68.25-72.36C634.22,43.09,636.57,24.58,638.58,21.42c-.34.37-.84.92-1.47,1.64C635.87,18.14,635,14,635,14s-57,57-69,152c-7.83,62,15.38,126.68,49,168a381.62,381.62,0,0,0,59,58h0c25.4,36.48,39.38,81.49,39.38,129.91,0,121.24-98.34,219.53-219.65,219.53a220.14,220.14,0,0,1-49.13-5.52c-57.24-10.92-90.3-39.8-106.78-59.41-9.45-11.23-13.46-19.42-13.46-19.42,51.28,18.37,108,14.53,142.47-4.52,34.75-19.26,55.77-33.55,72.84-27.92,16.82,5.61,30.21-10.67,18.2-27.54-11.77-16.85-42.4-41-87.88-34.29-34.79,5.07-66.66,29.76-112.24,5.84a97.34,97.34,0,0,1-8.55-5c-3-1.77,9.77,2.69,6.79.68-8.87-4.32-24.57-13.73-28.64-17.07-.68-.56,6.88,2.16,6.2,1.6-42.62-31.45-37.3-52.69-36-66,1.07-10.66,8.81-24.32,21.86-29.86,6.3,3.08,10.23,5.43,10.23,5.43s-2.69-4.92-4.14-7.51c.51-.19,1-.15,1.5-.34,5.16,2.23,16.58,8,22.59,11.57,7.83,4.95,10.32,9.36,10.32,9.36s2.06-1,.54-5.33c-.56-1.77-2.93-7.39-10.68-13.07h.48a91.65,91.65,0,0,1,13.13,8.17c2.19-7.12,6.12-14.56,5.25-27.86-.53-9.35-.28-11.78-2.12-15.39-1.65-3.1.92-4.31,3.78-1.09a29.73,29.73,0,0,0-2.44-7.34v-.24c3.57-11.14,75.53-40.12,80.77-43.51a70.24,70.24,0,0,0,21.17-20.63c4-5.72,7-13.73,7.75-25.89.25-5.48-1.44-9.82-20.5-14-11.44-2.49-29.14-4.91-56.43-7.47-19.9-1.76-31.58-14.68-38.21-26.6-1.21-2.57-2.45-4.9-3.68-7.22a53.41,53.41,0,0,1-2.83-8.36,158.47,158.47,0,0,1,61.28-76.06c1.6-1.31-6.4.33-4.8-1,1.87-1.52,14.06-5.93,16.37-6.92,2.81-1.19-12-6.84-25.16-5.47-13.36,1.35-16.19,2.78-23.32,5.49,3-2.64,12.37-6.1,10.16-6.08-14.4,2-32.3,9.48-47.6,18a9.72,9.72,0,0,1,.92-4.31c-7.13,2.71-24.64,13.67-29.73,23a39.79,39.79,0,0,0,.29-5.35,88.55,88.55,0,0,0-14.6,13.7l-.27.22C258.14,196,221.75,195,191,201.72c-6.74-6.06-17.57-15.23-32.89-45.4-1-1.82-1.6,3.75-2.4,2-6-13.81-9.55-36.44-9-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.87-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.37,12.62-1.38,4-3.32,6.27-4.56,11.29l-.29.46c-.1-1.48.37-6.08,0-5.14A235.4,235.4,0,0,0,95.34,186c-5.49,18-11.88,42.61-12.89,74.57-.24,2.42,0,5.14-.25,7.32-13,14.83-21.86,27.39-25.2,33.91-16.81,26-35.33,66.44-53.29,130.46a319.35,319.35,0,0,1,28.54-50C17.32,416.25,2.89,469.62,0,551.8a436.92,436.92,0,0,1,13.87-50.24C11.29,556.36,17.68,624.3,52.32,701c20.57,45,67.92,136.6,183.62,208h0s39.36,29.3,107,51.26c5,1.81,10.06,3.6,15.23,5.33q-2.43-1-4.71-2A484.9,484.9,0,0,0,492.27,984c175.18.15,226.85-70.2,226.85-70.2l-.51.38q3.71-3.49,7.14-7.26c-27.64,26.08-90.75,27.84-114.3,26,40.22-11.81,66.69-21.81,118.17-41.52q9-3.36,18.48-7.64l2-.94c1.25-.58,2.49-1.13,3.75-1.74a349.3,349.3,0,0,0,70.26-44c51.7-41.3,63-81.56,68.83-108.1-.82,2.54-3.37,8.47-5.17,12.32-13.31,28.48-42.84,46-74.91,61a689.05,689.05,0,0,0,42.38-62.44C865.77,729.39,869,713.15,876.85,702.23Z" style="fill:url(#radial-gradient-2)"/><path d="M813.92,801c21.08-23.24,40-49.82,54.35-80,36.9-77.58,94-206.58,49-341.31C881.77,273.22,833,215,771.11,158.12,670.56,65.76,642.48,24.52,642.48,0c0,0-116.09,129.41-65.74,264.38s153.46,130,221.68,270.87c80.27,165.74-64.95,346.61-185,397.24,7.35-1.63,267-60.38,280.61-208.88C893.68,726.34,887.83,767.41,813.92,801Z" style="fill:url(#linear-gradient-7)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="fill:url(#linear-gradient-8)"/><path d="M477.59,319.37c.39-8.77-4.16-14.66-76.68-21.46-29.84-2.76-41.26-30.33-44.75-41.94-10.61,27.56-15,56.49-12.64,91.48,1.61,22.92,17,47.52,24.37,62,0,0,1.64-2.13,2.39-2.91,13.86-14.43,71.94-36.42,77.39-39.54C453.69,363.16,476.58,346.44,477.59,319.37Z" style="opacity:0.5;isolation:isolate;fill:url(#radial-gradient-3)"/><path d="M158.31,156.47c-1-1.82-1.6,3.75-2.4,2-6-13.81-9.58-36.2-8.72-52,0,0-12.32,5.61-22.51,29.06-1.89,4.21-3.11,6.54-4.32,8.86-.56.68,1.27-7.7,1-7.24-1.77,3-6.36,7.19-8.35,12.38-1.65,4.24-3.35,6.52-4.61,11.77-.39,1.43.39-6.32,0-5.38C84.72,201.68,80.19,271,82.69,268,133.17,214.14,191,201.36,191,201.36c-6.15-4.53-19.53-17.63-32.7-44.89Z" style="fill:url(#linear-gradient-9)"/><path d="M349.84,720.1c-69.72-29.77-149-71.75-146-167.14C207.92,427.35,321,452.18,321,452.18c-4.27,1-15.68,9.16-19.72,17.82-4.27,10.83-12.07,35.28,11.55,60.9,37.09,40.19-76.2,95.36,98.66,199.57,4.41,2.4-41-1.43-61.64-10.36Z" style="fill:url(#linear-gradient-10)"/><path d="M325.07,657.5c49.44,17.21,107,14.19,141.52-4.86,23.09-12.85,52.7-33.43,70.92-28.35-15.78-6.24-27.73-9.15-42.1-9.86-2.45,0-5.38,0-8-.32a136,136,0,0,0-15.76.86c-8.9.82-18.77,6.43-27.74,5.53-.48,0,8.7-3.77,8-3.61-4.75,1-9.92,1.21-15.37,1.88-3.47.39-6.45.82-9.89,1-103,8.73-190-55.81-190-55.81-7.41,25,33.17,74.3,88.52,93.57Z" style="opacity:0.5;isolation:isolate;fill:url(#linear-gradient-11)"/><path d="M813.74,801.65c104.16-102.27,156.86-226.58,134.58-366,0,0,8.9,71.5-24.85,144.63,16.21-71.39,18.1-160.11-25-252C841,205.64,746.45,141.11,710.35,114.19,655.66,73.4,633,31.87,632.57,23.3c-16.34,33.48-65.77,148.2-5.31,247,56.64,92.56,145.86,120,208.33,205C950.67,631.67,813.74,801.65,813.74,801.65Z" style="fill:url(#linear-gradient-12)"/><path d="M798.81,535.55C762.41,460.35,717,427.55,674,392c5,7,6.23,9.47,9,14,37.83,40.32,93.61,138.66,53.11,262.11C659.88,900.48,355,791.06,323,760.32,335.93,894.81,561,959.16,707.6,872,791,793,858.47,658.79,798.81,535.55Z" style="fill:url(#linear-gradient-13)"/></g></g></g></g></svg>`,
+ { headers: { "content-type": "image/svg+xml" } }
+ );
+ }
+
+ dump(
+ `A normal URL ${request.url} has been requested, let's fetch it from the network.\n`
+ );
+ return fetch(request);
+}
diff --git a/tools/profiler/tests/browser/simple.html b/tools/profiler/tests/browser/simple.html
new file mode 100644
index 0000000000..f7c32d02c3
--- /dev/null
+++ b/tools/profiler/tests/browser/simple.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ Testing
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/single_frame.html b/tools/profiler/tests/browser/single_frame.html
new file mode 100644
index 0000000000..ebdfc41da2
--- /dev/null
+++ b/tools/profiler/tests/browser/single_frame.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Single Frame</title>
+</head>
+<body>
+ Single Frame
+</body>
+</html>