From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- tools/profiler/tests/browser/browser.ini | 102 +++++ .../browser/browser_test_feature_ipcmessages.js | 100 +++++ .../browser/browser_test_feature_jsallocations.js | 74 ++++ .../browser_test_feature_nostacksampling.js | 72 ++++ .../browser/browser_test_marker_network_cancel.js | 71 +++ ...browser_test_marker_network_private_browsing.js | 91 ++++ .../browser_test_marker_network_redirect.js | 341 +++++++++++++++ ...est_marker_network_serviceworker_cache_first.js | 378 ++++++++++++++++ ...arker_network_serviceworker_no_fetch_handler.js | 218 ++++++++++ ...erviceworker_no_respondWith_in_fetch_handler.js | 294 +++++++++++++ ...r_network_serviceworker_synthetized_response.js | 480 +++++++++++++++++++++ .../browser/browser_test_marker_network_simple.js | 81 ++++ .../browser/browser_test_marker_network_sts.js | 130 ++++++ .../tests/browser/browser_test_markers_gc_cc.js | 49 +++ .../browser/browser_test_markers_parent_process.js | 37 ++ .../browser_test_markers_preferencereads.js | 73 ++++ .../browser/browser_test_profile_capture_by_pid.js | 199 +++++++++ .../tests/browser/browser_test_profile_fission.js | 191 ++++++++ .../browser_test_profile_multi_frame_page_info.js | 83 ++++ .../browser_test_profile_single_frame_page_info.js | 132 ++++++ .../browser/browser_test_profile_slow_capture.js | 104 +++++ tools/profiler/tests/browser/do_work_500ms.html | 41 ++ .../tests/browser/firefox-logo-nightly.svg | 1 + tools/profiler/tests/browser/head.js | 159 +++++++ tools/profiler/tests/browser/multi_frame.html | 11 + .../tests/browser/page_with_resources.html | 11 + tools/profiler/tests/browser/redirect.sjs | 8 + .../serviceworkers/firefox-logo-nightly.svg | 1 + .../browser/serviceworkers/serviceworker-utils.js | 39 ++ .../serviceworkers/serviceworker_cache_first.js | 34 ++ .../serviceworker_no_fetch_handler.js | 4 + ...erviceworker_no_respondWith_in_fetch_handler.js | 9 + .../browser/serviceworkers/serviceworker_page.html | 10 + .../serviceworkers/serviceworker_register.html | 9 + .../serviceworkers/serviceworker_simple.html | 9 + .../serviceworker_synthetized_response.js | 27 ++ tools/profiler/tests/browser/simple.html | 9 + tools/profiler/tests/browser/single_frame.html | 10 + 38 files changed, 3692 insertions(+) create mode 100644 tools/profiler/tests/browser/browser.ini create mode 100644 tools/profiler/tests/browser/browser_test_feature_ipcmessages.js create mode 100644 tools/profiler/tests/browser/browser_test_feature_jsallocations.js create mode 100644 tools/profiler/tests/browser/browser_test_feature_nostacksampling.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_cancel.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_private_browsing.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_redirect.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_serviceworker_cache_first.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_fetch_handler.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_simple.js create mode 100644 tools/profiler/tests/browser/browser_test_marker_network_sts.js create mode 100644 tools/profiler/tests/browser/browser_test_markers_gc_cc.js create mode 100644 tools/profiler/tests/browser/browser_test_markers_parent_process.js create mode 100644 tools/profiler/tests/browser/browser_test_markers_preferencereads.js create mode 100644 tools/profiler/tests/browser/browser_test_profile_capture_by_pid.js create mode 100644 tools/profiler/tests/browser/browser_test_profile_fission.js create mode 100644 tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js create mode 100644 tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js create mode 100644 tools/profiler/tests/browser/browser_test_profile_slow_capture.js create mode 100644 tools/profiler/tests/browser/do_work_500ms.html create mode 100644 tools/profiler/tests/browser/firefox-logo-nightly.svg create mode 100644 tools/profiler/tests/browser/head.js create mode 100644 tools/profiler/tests/browser/multi_frame.html create mode 100644 tools/profiler/tests/browser/page_with_resources.html create mode 100644 tools/profiler/tests/browser/redirect.sjs create mode 100644 tools/profiler/tests/browser/serviceworkers/firefox-logo-nightly.svg create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker-utils.js create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_cache_first.js create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_no_fetch_handler.js create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_no_respondWith_in_fetch_handler.js create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_page.html create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_register.html create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_simple.html create mode 100644 tools/profiler/tests/browser/serviceworkers/serviceworker_synthetized_response.js create mode 100644 tools/profiler/tests/browser/simple.html create mode 100644 tools/profiler/tests/browser/single_frame.html (limited to 'tools/profiler/tests/browser') diff --git a/tools/profiler/tests/browser/browser.ini b/tools/profiler/tests/browser/browser.ini new file mode 100644 index 0000000000..654446e36e --- /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_markers_parent_process.js] +skip-if = + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure + +[browser_test_markers_preferencereads.js] +support-files = single_frame.html + +[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..f5fb2921a1 --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_feature_ipcmessages.js @@ -0,0 +1,100 @@ +/* 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() { + 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..60d072bed9 --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_feature_jsallocations.js @@ -0,0 +1,74 @@ +/* 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..323a87e191 --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_feature_nostacksampling.js @@ -0,0 +1,72 @@ +/* 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_marker_network_cancel.js b/tools/profiler/tests/browser/browser_test_marker_network_cancel.js new file mode 100644 index 0000000000..0a850487af --- /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.loadURIString(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..f898ebda29 --- /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.loadURIString(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..c1ad49b262 --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_cache_first.js @@ -0,0 +1,378 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test that we emit network markers accordingly. + * In this file we'll test a caching service worker. This service worker will + * fetch and store requests at install time, and serve them when the page + * requests them. + */ + +const serviceWorkerFileName = "serviceworker_cache_first.js"; +registerCleanupFunction(() => SpecialPowers.removeAllServiceWorkerData()); + +add_task(async function test_network_markers_service_worker_setup() { + // Disabling cache makes the result more predictable. Also this makes things + // simpler when dealing with service workers. + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.cache.disk.enable", false], + ["browser.cache.memory.enable", false], + ], + }); +}); + +add_task(async function test_network_markers_service_worker_register() { + // In this first step, we request an HTML page that will register a service + // worker. We'll wait until the service worker is fully installed before + // checking various things. + Assert.ok( + !Services.profiler.IsActive(), + "The profiler is not currently active" + ); + + startProfilerForMarkerTests(); + + const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_register.html`; + await BrowserTestUtils.withNewTab(url, async contentBrowser => { + const contentPid = await SpecialPowers.spawn( + contentBrowser, + [], + () => Services.appinfo.processID + ); + + await SpecialPowers.spawn( + contentBrowser, + [serviceWorkerFileName], + async function (serviceWorkerFileName) { + await content.wrappedJSObject.registerServiceWorkerAndWait( + serviceWorkerFileName + ); + } + ); + + const { parentThread, contentThread, profile } = + await stopProfilerNowAndGetThreads(contentPid); + + // The service worker work happens in a third "thread" or process, let's try + // to find it. + // Currently the fetches happen on the main thread for the content process, + // this may change in the future and we may have to adapt this function. + // Also please note this isn't necessarily the same content process as the + // ones for the tab. + const { serviceWorkerParentThread } = findServiceWorkerThreads(profile); + + // Here are a few sanity checks. + ok( + serviceWorkerParentThread, + "We should find a thread for the service worker." + ); + + Assert.notEqual( + serviceWorkerParentThread.pid, + parentThread.pid, + "We should have a different pid than the parent thread." + ); + Assert.notEqual( + serviceWorkerParentThread.tid, + parentThread.tid, + "We should have a different tid than the parent thread." + ); + + // Let's make sure we actually have a registered service workers. + const workers = await SpecialPowers.registeredServiceWorkers(); + Assert.equal( + workers.length, + 1, + "One service worker should be properly registered." + ); + + // By logging a few information about the threads we make debugging easier. + logInformationForThread("parentThread information", parentThread); + logInformationForThread("contentThread information", contentThread); + logInformationForThread( + "serviceWorkerParentThread information", + serviceWorkerParentThread + ); + + // Now let's check the marker payloads. + const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread) + // When we load a page, Firefox will check the service worker freshness + // after a few seconds. So when the test lasts a long time (with some test + // environments) we might see spurious markers about that that we're not + // interesting in in this part of the test. They're only present in the + // parent process. + .filter(marker => !marker.data.URI.includes(serviceWorkerFileName)); + const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread); + const serviceWorkerNetworkMarkers = getInflatedNetworkMarkers( + serviceWorkerParentThread + ); + + // Some more logs for debugging purposes. + info( + "Parent network markers: " + JSON.stringify(parentNetworkMarkers, null, 2) + ); + info( + "Content network markers: " + + JSON.stringify(contentNetworkMarkers, null, 2) + ); + info( + "Serviceworker network markers: " + + JSON.stringify(serviceWorkerNetworkMarkers, null, 2) + ); + + const parentPairs = getPairsOfNetworkMarkers(parentNetworkMarkers); + const contentPairs = getPairsOfNetworkMarkers(contentNetworkMarkers); + const serviceWorkerPairs = getPairsOfNetworkMarkers( + serviceWorkerNetworkMarkers + ); + + // First, make sure we properly matched all start with stop markers. This + // means that both arrays should contain only arrays of 2 elements. + parentPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the parent process.` + ) + ); + contentPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the content process.` + ) + ); + serviceWorkerPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the service worker process.` + ) + ); + + // Let's look at all pairs and make sure we requested all expected files. + const parentStopMarkers = parentPairs.map(([_, stopMarker]) => stopMarker); + + // These are the files cached by the service worker. We should see markers + // for both the parent thread and the service worker thread. + const expectedFiles = [ + "serviceworker_page.html", + "firefox-logo-nightly.svg", + ].map(filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`); + + for (const expectedFile of expectedFiles) { + info( + `Checking if "${expectedFile}" is present in the network markers in both processes.` + ); + const parentMarker = parentStopMarkers.find( + marker => marker.data.URI === expectedFile + ); + + const expectedProperties = { + name: Expect.stringMatches( + `Load \\d+:.*${escapeStringRegexp(expectedFile)}` + ), + data: Expect.objectContains({ + status: "STATUS_STOP", + URI: expectedFile, + requestMethod: "GET", + contentType: Expect.stringMatches(/^(text\/html|image\/svg\+xml)$/), + startTime: Expect.number(), + endTime: Expect.number(), + domainLookupStart: Expect.number(), + domainLookupEnd: Expect.number(), + connectStart: Expect.number(), + tcpConnectEnd: Expect.number(), + connectEnd: Expect.number(), + requestStart: Expect.number(), + responseStart: Expect.number(), + responseEnd: Expect.number(), + id: Expect.number(), + count: Expect.number(), + pri: Expect.number(), + }), + }; + + Assert.objectContains(parentMarker, expectedProperties); + } + }); +}); + +add_task(async function test_network_markers_service_worker_use() { + // In this test we request an HTML file that itself contains resources that + // are redirected. + Assert.ok( + !Services.profiler.IsActive(), + "The profiler is not currently active" + ); + + startProfilerForMarkerTests(); + + const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_page.html`; + await BrowserTestUtils.withNewTab(url, async contentBrowser => { + const contentPid = await SpecialPowers.spawn( + contentBrowser, + [], + () => Services.appinfo.processID + ); + + const { parentThread, contentThread } = await stopProfilerNowAndGetThreads( + contentPid + ); + + // By logging a few information about the threads we make debugging easier. + logInformationForThread("parentThread information", parentThread); + logInformationForThread("contentThread information", contentThread); + + const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread) + // When we load a page, Firefox will check the service worker freshness + // after a few seconds. So when the test lasts a long time (with some test + // environments) we might see spurious markers about that that we're not + // interesting in in this part of the test. They're only present in the + // parent process. + .filter(marker => !marker.data.URI.includes(serviceWorkerFileName)); + const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread); + + // Here are some logs to ease debugging. + info( + "Parent network markers: " + JSON.stringify(parentNetworkMarkers, null, 2) + ); + info( + "Content network markers: " + + JSON.stringify(contentNetworkMarkers, null, 2) + ); + + const parentPairs = getPairsOfNetworkMarkers(parentNetworkMarkers); + const contentPairs = getPairsOfNetworkMarkers(contentNetworkMarkers); + + // These are the files cached by the service worker. We should see markers + // for the parent thread and the content thread. + const expectedFiles = [ + "serviceworker_page.html", + "firefox-logo-nightly.svg", + ].map(filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`); + + // First, make sure we properly matched all start with stop markers. This + // means that both arrays should contain only arrays of 2 elements. + parentPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the parent process.` + ) + ); + + contentPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the content process.` + ) + ); + + // Let's look at all pairs and make sure we requested all expected files. + const parentEndMarkers = parentPairs.map(([_, endMarker]) => endMarker); + const contentStopMarkers = contentPairs.map( + ([_, stopMarker]) => stopMarker + ); + + Assert.equal( + parentEndMarkers.length, + expectedFiles.length * 2, // one redirect + one stop + "There should be twice as many end markers in the parent process as requested files." + ); + Assert.equal( + contentStopMarkers.length, + expectedFiles.length, + "There should be as many stop markers in the content process as requested files." + ); + + for (const [i, expectedFile] of expectedFiles.entries()) { + info( + `Checking if "${expectedFile}" if present in the network markers in both processes.` + ); + const [parentRedirectMarker, parentStopMarker] = parentEndMarkers.filter( + marker => marker.data.URI === expectedFile + ); + const contentMarker = contentStopMarkers.find( + marker => marker.data.URI === expectedFile + ); + + const commonDataProperties = { + type: "Network", + URI: expectedFile, + requestMethod: "GET", + contentType: Expect.stringMatches(/^(text\/html|image\/svg\+xml)$/), + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + }; + + const expectedProperties = { + name: Expect.stringMatches( + `Load \\d+:.*${escapeStringRegexp(expectedFile)}` + ), + }; + + Assert.objectContains(parentRedirectMarker, expectedProperties); + Assert.objectContains(parentStopMarker, expectedProperties); + Assert.objectContains(contentMarker, expectedProperties); + if (i === 0) { + // This is the top level navigation, the HTML file. + Assert.objectContainsOnly(parentRedirectMarker.data, { + ...commonDataProperties, + status: "STATUS_REDIRECT", + contentType: null, + cache: "Unresolved", + RedirectURI: expectedFile, + redirectType: "Internal", + redirectId: parentStopMarker.data.id, + isHttpToHttpsRedirect: false, + }); + + Assert.objectContainsOnly(parentStopMarker.data, { + ...commonDataProperties, + status: "STATUS_STOP", + }); + + Assert.objectContainsOnly(contentMarker.data, { + ...commonDataProperties, + status: "STATUS_STOP", + }); + } else { + Assert.objectContainsOnly(parentRedirectMarker.data, { + ...commonDataProperties, + status: "STATUS_REDIRECT", + contentType: null, + cache: "Unresolved", + innerWindowID: Expect.number(), + RedirectURI: expectedFile, + redirectType: "Internal", + redirectId: parentStopMarker.data.id, + isHttpToHttpsRedirect: false, + }); + + Assert.objectContainsOnly( + parentStopMarker.data, + // Note: in the future we may have more properties. We're using the + // "Only" flavor of the matcher so that we don't forget to update this + // test when this changes. + { + ...commonDataProperties, + innerWindowID: Expect.number(), + status: "STATUS_STOP", + } + ); + + Assert.objectContainsOnly(contentMarker.data, { + ...commonDataProperties, + innerWindowID: Expect.number(), + status: "STATUS_STOP", + }); + } + } + }); +}); 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..ad2cc81661 --- /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..973ae61a7f --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_no_respondWith_in_fetch_handler.js @@ -0,0 +1,294 @@ +/* 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..060592840a --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_marker_network_serviceworker_synthetized_response.js @@ -0,0 +1,480 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Test that we emit network markers accordingly. + * In this file we'll test a service worker that returns a synthetized response. + * This means the service worker will make up a response by itself. + */ + +const serviceWorkerFileName = "serviceworker_synthetized_response.js"; +registerCleanupFunction(() => SpecialPowers.removeAllServiceWorkerData()); + +add_task(async function test_network_markers_service_worker_setup() { + // Disabling cache makes the result more predictable. Also this makes things + // simpler when dealing with service workers. + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.cache.disk.enable", false], + ["browser.cache.memory.enable", false], + ], + }); +}); + +add_task(async function test_network_markers_service_worker_register() { + // In this first step, we request an HTML page that will register a service + // worker. We'll wait until the service worker is fully installed before + // checking various things. + Assert.ok( + !Services.profiler.IsActive(), + "The profiler is not currently active" + ); + + const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_register.html`; + await BrowserTestUtils.withNewTab(url, async contentBrowser => { + await SpecialPowers.spawn( + contentBrowser, + [serviceWorkerFileName], + async function (serviceWorkerFileName) { + await content.wrappedJSObject.registerServiceWorkerAndWait( + serviceWorkerFileName + ); + } + ); + + // Let's make sure we actually have a registered service workers. + const workers = await SpecialPowers.registeredServiceWorkers(); + Assert.equal( + workers.length, + 1, + "One service worker should be properly registered." + ); + }); +}); + +add_task(async function test_network_markers_service_worker_use() { + // In this test, we'll first load a plain html file, then do some fetch + // requests in the context of the page. One request is served with a + // synthetized response, the other request is served with a real "fetch" done + // by the service worker. + Assert.ok( + !Services.profiler.IsActive(), + "The profiler is not currently active" + ); + + startProfilerForMarkerTests(); + + const url = `${BASE_URL_HTTPS}serviceworkers/serviceworker_simple.html`; + await BrowserTestUtils.withNewTab(url, async contentBrowser => { + const contentPid = await SpecialPowers.spawn( + contentBrowser, + [], + () => Services.appinfo.processID + ); + + await SpecialPowers.spawn(contentBrowser, [], async () => { + // This request is served directly by the service worker as a synthetized response. + await content + .fetch("firefox-generated.svg") + .then(res => res.arrayBuffer()); + + // This request is served by a fetch done inside the service worker. + await content + .fetch("firefox-logo-nightly.svg") + .then(res => res.arrayBuffer()); + }); + + const { parentThread, contentThread, profile } = + await stopProfilerNowAndGetThreads(contentPid); + + // The service worker work happens in a third "thread" or process, let's try + // to find it. + // Currently the fetches happen on the main thread for the content process, + // this may change in the future and we may have to adapt this function. + // Also please note this isn't necessarily the same content process as the + // ones for the tab. + const { serviceWorkerParentThread } = findServiceWorkerThreads(profile); + + ok( + serviceWorkerParentThread, + "We should find a thread for the service worker." + ); + + // By logging a few information about the threads we make debugging easier. + logInformationForThread("parentThread information", parentThread); + logInformationForThread("contentThread information", contentThread); + logInformationForThread( + "serviceWorkerParentThread information", + serviceWorkerParentThread + ); + + const parentNetworkMarkers = getInflatedNetworkMarkers(parentThread) + // When we load a page, Firefox will check the service worker freshness + // after a few seconds. So when the test lasts a long time (with some test + // environments) we might see spurious markers about that that we're not + // interesting in in this part of the test. They're only present in the + // parent process. + .filter(marker => !marker.data.URI.includes(serviceWorkerFileName)); + + const contentNetworkMarkers = getInflatedNetworkMarkers(contentThread); + const serviceWorkerNetworkMarkers = getInflatedNetworkMarkers( + serviceWorkerParentThread + ); + + // Some more logs for debugging purposes. + info( + "Parent network markers: " + JSON.stringify(parentNetworkMarkers, null, 2) + ); + info( + "Content network markers: " + + JSON.stringify(contentNetworkMarkers, null, 2) + ); + info( + "Serviceworker network markers: " + + JSON.stringify(serviceWorkerNetworkMarkers, null, 2) + ); + + const parentPairs = getPairsOfNetworkMarkers(parentNetworkMarkers); + const contentPairs = getPairsOfNetworkMarkers(contentNetworkMarkers); + const serviceWorkerPairs = getPairsOfNetworkMarkers( + serviceWorkerNetworkMarkers + ); + + // First, make sure we properly matched all start with stop markers. This + // means that both arrays should contain only arrays of 2 elements. + parentPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the parent process.` + ) + ); + + contentPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the content process.` + ) + ); + serviceWorkerPairs.forEach(pair => + Assert.equal( + pair.length, + 2, + `For the URL ${pair[0].data.URI} we should get 2 markers in the service worker process.` + ) + ); + + // Let's look at all pairs and make sure we requested all expected files. + // In this test, we should have redirect markers as well as stop markers, + // because this case generates internal redirects. + // Let's create various arrays to help assert. + + let parentStopMarkers = parentPairs.map(([_, stopMarker]) => stopMarker); + const contentStopMarkers = contentPairs.map( + ([_, stopMarker]) => stopMarker + ); + // In this test we have very different results in the various threads, so + // we'll assert every case separately. + // A simple function to help constructing better assertions: + const fullUrl = filename => `${BASE_URL_HTTPS}serviceworkers/${filename}`; + + { + // In the parent process, we have 8 network markers: + // - twice the html file -- because it's not cached by the SW, we get the + // marker both for the initial request and for the request initied from the + // SW. + // - twice the firefox svg file -- similar situation + // - once the generated svg file -- this one isn't fetched by the SW but + // rather forged directly, so there's no "second fetch", and thus we have + // only one marker. + // - for each of these files, we have first an internal redirect from the + // main channel to the service worker. => 3 redirect markers more. + Assert.equal( + parentStopMarkers.length, + 8, // 3 html files, 3 firefox svg files, 2 generated svg file + "There should be 8 stop markers in the parent process." + ); + + // The "1" requests are the initial requests that are intercepted, coming + // from the web page, while the "2" requests are requests to the network, + // coming from the service worker. The 1 were requested before 2, 2 ends + // before 1. + // "Intercept" requests are the internal redirects from the main channel + // to the service worker. They happen before others. + const [ + htmlFetchIntercept, + htmlFetch1, + htmlFetch2, + generatedSvgIntercept, + generatedSvgFetch, + firefoxSvgIntercept, + firefoxSvgFetch1, + firefoxSvgFetch2, + ] = parentStopMarkers; + + /* ----- /HTML FILE ---- */ + Assert.objectContains(htmlFetchIntercept, { + name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_REDIRECT", + URI: fullUrl("serviceworker_simple.html"), + requestMethod: "GET", + contentType: null, + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + redirectId: htmlFetch1.data.id, + redirectType: "Internal", + isHttpToHttpsRedirect: false, + RedirectURI: fullUrl("serviceworker_simple.html"), + cache: "Unresolved", + }), + }); + + Assert.objectContains(htmlFetch1, { + name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("serviceworker_simple.html"), + requestMethod: "GET", + contentType: "text/html", + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + }), + }); + Assert.objectContains(htmlFetch2, { + name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("serviceworker_simple.html"), + requestMethod: "GET", + contentType: "text/html", + // Because the request races with the cache, these 2 values are valid: + // "Missed" when the cache answered before we get a result from the network. + // "Unresolved" when we got a response from the network before the cache subsystem. + cache: Expect.stringMatches(/^(Missed|Unresolved)$/), + startTime: Expect.number(), + endTime: Expect.number(), + domainLookupStart: Expect.number(), + domainLookupEnd: Expect.number(), + connectStart: Expect.number(), + tcpConnectEnd: Expect.number(), + connectEnd: Expect.number(), + requestStart: Expect.number(), + responseStart: Expect.number(), + responseEnd: Expect.number(), + id: Expect.number(), + count: Expect.number(), + pri: Expect.number(), + }), + }); + /* ----- /HTML FILE ---- */ + + /* ----- GENERATED SVG FILE ---- */ + Assert.objectContains(generatedSvgIntercept, { + name: Expect.stringMatches(/Load \d+:.*firefox-generated.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_REDIRECT", + URI: fullUrl("firefox-generated.svg"), + requestMethod: "GET", + contentType: null, + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + redirectId: generatedSvgFetch.data.id, + redirectType: "Internal", + isHttpToHttpsRedirect: false, + RedirectURI: fullUrl("firefox-generated.svg"), + cache: "Unresolved", + innerWindowID: Expect.number(), + }), + }); + Assert.objectContains(generatedSvgFetch, { + name: Expect.stringMatches(/Load \d+:.*firefox-generated.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("firefox-generated.svg"), + requestMethod: "GET", + contentType: "image/svg+xml", + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + innerWindowID: Expect.number(), + }), + }); + /* ----- ∕GENERATED SVG FILE ---- */ + /* ----- REQUESTED SVG FILE ---- */ + Assert.objectContains(firefoxSvgIntercept, { + name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_REDIRECT", + URI: fullUrl("firefox-logo-nightly.svg"), + requestMethod: "GET", + contentType: null, + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + redirectId: firefoxSvgFetch1.data.id, + redirectType: "Internal", + isHttpToHttpsRedirect: false, + RedirectURI: fullUrl("firefox-logo-nightly.svg"), + cache: "Unresolved", + innerWindowID: Expect.number(), + }), + }); + Assert.objectContains(firefoxSvgFetch1, { + name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("firefox-logo-nightly.svg"), + requestMethod: "GET", + contentType: "image/svg+xml", + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + innerWindowID: Expect.number(), + }), + }); + Assert.objectContains(firefoxSvgFetch2, { + name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("firefox-logo-nightly.svg"), + requestMethod: "GET", + contentType: "image/svg+xml", + // Because the request races with the cache, these 2 values are valid: + // "Missed" when the cache answered before we get a result from the network. + // "Unresolved" when we got a response from the network before the cache subsystem. + cache: Expect.stringMatches(/^(Missed|Unresolved)$/), + startTime: Expect.number(), + endTime: Expect.number(), + domainLookupStart: Expect.number(), + domainLookupEnd: Expect.number(), + connectStart: Expect.number(), + tcpConnectEnd: Expect.number(), + connectEnd: Expect.number(), + requestStart: Expect.number(), + responseStart: Expect.number(), + responseEnd: Expect.number(), + id: Expect.number(), + count: Expect.number(), + pri: Expect.number(), + // Note: no innerWindowID here, is that a bug? + }), + }); + /* ----- ∕REQUESTED SVG FILE ---- */ + } + + // It's possible that the service worker thread IS the content thread, in + // that case we'll get all markers in the same thread. + // The "1" requests are the initial requests that are intercepted, coming + // from the web page, while the "2" requests are the requests coming from + // the service worker. + let htmlFetch1, generatedSvgFetch1, firefoxSvgFetch1; + + // First, let's handle the case where the threads are different: + if (serviceWorkerParentThread !== contentThread) { + // In the content process (that is the process for the web page), we have + // 3 network markers: + // - 1 for the HTML page + // - 1 for the generated svg file + // - 1 for the firefox svg file + // Indeed, the service worker interception is invisible from the context + // of the web page, so we just get 3 "normal" requests. However these + // requests will miss all timing information, because they're hidden by + // the service worker interception. We may want to fix this... + Assert.equal( + contentStopMarkers.length, + 3, // 1 for each file + "There should be 3 stop markers in the content process." + ); + + [htmlFetch1, generatedSvgFetch1, firefoxSvgFetch1] = contentStopMarkers; + } else { + // Else case: the service worker parent thread IS the content thread + // (note: this is always the case with fission). In that case all network + // markers tested in the above block are together in the same object. + Assert.equal( + contentStopMarkers.length, + 5, + "There should be 5 stop markers in the combined process (containing both the content page and the service worker)" + ); + + // Because of how the test is done, these markers are ordered by the + // position of the START markers. + [ + // For the htmlFetch request, note that 2 is before 1, because that's + // the top level navigation. Indeed for the top level navigation + // everything happens first in the main process, possibly before a + // content process even exists, and the content process is merely + // notified at the end. + htmlFetch1, + generatedSvgFetch1, + firefoxSvgFetch1, + ] = contentStopMarkers; + } + + // Let's test first the markers coming from the content page. + Assert.objectContains(htmlFetch1, { + name: Expect.stringMatches(/Load \d+:.*serviceworker_simple.html/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("serviceworker_simple.html"), + requestMethod: "GET", + contentType: "text/html", + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + }), + }); + Assert.objectContains(generatedSvgFetch1, { + name: Expect.stringMatches(/Load \d+:.*firefox-generated.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("firefox-generated.svg"), + requestMethod: "GET", + contentType: "image/svg+xml", + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + innerWindowID: Expect.number(), + }), + }); + Assert.objectContains(firefoxSvgFetch1, { + name: Expect.stringMatches(/Load \d+:.*firefox-logo-nightly.svg/), + data: Expect.objectContainsOnly({ + type: "Network", + status: "STATUS_STOP", + URI: fullUrl("firefox-logo-nightly.svg"), + requestMethod: "GET", + contentType: "image/svg+xml", + startTime: Expect.number(), + endTime: Expect.number(), + id: Expect.number(), + pri: Expect.number(), + innerWindowID: Expect.number(), + }), + }); + }); +}); 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_markers_preferencereads.js b/tools/profiler/tests/browser/browser_test_markers_preferencereads.js new file mode 100644 index 0000000000..0ae183f874 --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_markers_preferencereads.js @@ -0,0 +1,73 @@ +/* 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, "Preference")) { + 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 Preference Read markers. + */ +add_task(async function test_profile_preferencereads_markers() { + Assert.ok( + !Services.profiler.IsActive(), + "The profiler is not currently active" + ); + + await startProfiler({ features: ["js"] }); + + 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 Preference Read profile markers were generated. + { + const { contentThread } = await stopProfilerNowAndGetThreads(contentPid); + + Assert.greater( + countPrefReadsInThread(kContentPref, contentThread), + 0, + `Preference Read profile markers for ${kContentPref} were recorded.` + ); + } + }); +}); 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..14d76dbcaf --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_profile_capture_by_pid.js @@ -0,0 +1,199 @@ +/* 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:'."); + 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..775fc8048e --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_profile_fission.js @@ -0,0 +1,191 @@ +/* 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.loadURIString(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.loadURIString(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..854587678d --- /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..240213be56 --- /dev/null +++ b/tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js @@ -0,0 +1,132 @@ +/* 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.loadURIString(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..4a675b84d1 --- /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 @@ + + + + + Do some work for 500ms + + + + Do some work for 500ms. + + 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 @@ +firefox-logo-nightly \ 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 @@ + + + + + Multi Frame + + + Multi Frame + + + 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 @@ + + + + + + + Testing + + + + 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 @@ +firefox-logo-nightly \ 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 @@ + + + + + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + Testing + + 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( + `firefox-logo-nightly`, + { 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 @@ + + + + + + + Testing + + 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 @@ + + + + + Single Frame + + + Single Frame + + -- cgit v1.2.3