summaryrefslogtreecommitdiffstats
path: root/tools/profiler/tests/browser
diff options
context:
space:
mode:
Diffstat (limited to 'tools/profiler/tests/browser')
-rw-r--r--tools/profiler/tests/browser/browser.ini18
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_ipcmessages.js110
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_jsallocations.js81
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_nostacksampling.js78
-rw-r--r--tools/profiler/tests/browser/browser_test_feature_preferencereads.js109
-rw-r--r--tools/profiler/tests/browser/browser_test_markers_parent_process.js40
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js88
-rw-r--r--tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js67
-rw-r--r--tools/profiler/tests/browser/do_work_500ms.html41
-rw-r--r--tools/profiler/tests/browser/fixed_height.html18
-rw-r--r--tools/profiler/tests/browser/head.js70
-rw-r--r--tools/profiler/tests/browser/multi_frame.html11
-rw-r--r--tools/profiler/tests/browser/simple.html9
-rw-r--r--tools/profiler/tests/browser/single_frame.html10
14 files changed, 750 insertions, 0 deletions
diff --git a/tools/profiler/tests/browser/browser.ini b/tools/profiler/tests/browser/browser.ini
new file mode 100644
index 0000000000..afbccd1b00
--- /dev/null
+++ b/tools/profiler/tests/browser/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+skip-if = tsan # TSan times out on pretty much all of these tests
+support-files =
+ ../shared-head.js
+ head.js
+ do_work_500ms.html
+ fixed_height.html
+ multi_frame.html
+ simple.html
+ single_frame.html
+
+[browser_test_feature_ipcmessages.js]
+[browser_test_feature_jsallocations.js]
+[browser_test_feature_nostacksampling.js]
+[browser_test_feature_preferencereads.js]
+[browser_test_markers_parent_process.js]
+[browser_test_profile_single_frame_page_info.js]
+[browser_test_profile_multi_frame_page_info.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..b199f74198
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_ipcmessages.js
@@ -0,0 +1,110 @@
+/* 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() {
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ const url = BASE_URL + "simple.html";
+
+ info("Open a tab while profiling IPC messages.");
+ startProfiler({ features: ["threads", "leaf", "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 stopProfilerAndGetThreads(
+ 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.");
+ startProfiler({ features: ["threads", "leaf"] });
+
+ 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 stopProfilerAndGetThreads(
+ 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..ae925e1fcd
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_jsallocations.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/. */
+
+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() {
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfiler({ features: ["threads", "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 stopProfilerNowAndGetThreads(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."
+ );
+ }
+
+ startProfiler({ features: ["threads", "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 stopProfilerNowAndGetThreads(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..0fdae614ee
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_nostacksampling.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test the No Stack Sampling feature.
+ */
+add_task(async function test_profile_feature_nostacksampling() {
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfiler({ features: ["threads", "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.
+ startProfiler({ features: ["threads", "js"] });
+
+ // Now reload the tab with a clean run.
+ gBrowser.reload();
+ await wait(500);
+
+ // Check that stack samples were recorded.
+ {
+ const { parentThread, contentThread } = await stopProfilerAndGetThreads(
+ contentPid
+ );
+ Assert.greater(
+ parentThread.samples.data.length,
+ 0,
+ "No Stack samples were recorded from the parent process' main thread" +
+ "when the No Stack Sampling feature was not turned on."
+ );
+
+ Assert.greater(
+ contentThread.samples.data.length,
+ 0,
+ "No Stack samples were recorded from the content process' main thread" +
+ "when the No Stack Sampling feature was not turned on."
+ );
+ }
+ });
+});
diff --git a/tools/profiler/tests/browser/browser_test_feature_preferencereads.js b/tools/profiler/tests/browser/browser_test_feature_preferencereads.js
new file mode 100644
index 0000000000..ff1b869a47
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_feature_preferencereads.js
@@ -0,0 +1,109 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+requestLongerTimeout(10);
+
+function countDpiPrefReadsInThread(thread) {
+ let count = 0;
+ for (let payload of getPayloadsOfType(thread, "PreferenceRead")) {
+ if (payload.prefName === "layout.css.dpi") {
+ count++;
+ }
+ }
+ return count;
+}
+
+async function waitForPaintAfterLoad() {
+ return SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ return new Promise(function(resolve) {
+ function listener() {
+ if (content.document.readyState == "complete") {
+ content.requestAnimationFrame(() => content.setTimeout(resolve, 0));
+ }
+ }
+ if (content.document.readyState != "complete") {
+ content.document.addEventListener("readystatechange", listener);
+ } else {
+ listener();
+ }
+ });
+ });
+}
+
+/**
+ * Test the PreferenceRead feature.
+ */
+add_task(async function test_profile_feature_preferencereads() {
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ Assert.ok(
+ !Services.profiler.IsActive(),
+ "The profiler is not currently active"
+ );
+
+ startProfiler({ features: ["threads", "leaf", "preferencereads"] });
+
+ const url = BASE_URL + "fixed_height.html";
+ await BrowserTestUtils.withNewTab(url, async contentBrowser => {
+ const contentPid = await SpecialPowers.spawn(
+ contentBrowser,
+ [],
+ () => Services.appinfo.processID
+ );
+
+ await waitForPaintAfterLoad();
+
+ // Check that some PreferenceRead profile markers were generated when the
+ // feature is enabled.
+ {
+ const { contentThread } = await stopProfilerNowAndGetThreads(contentPid);
+
+ const timesReadDpiInContent = countDpiPrefReadsInThread(contentThread);
+
+ Assert.greater(
+ timesReadDpiInContent,
+ 0,
+ "PreferenceRead profile markers for layout.css.dpi were recorded " +
+ "when the PreferenceRead feature was turned on."
+ );
+ }
+
+ startProfiler({ features: ["threads", "leaf"] });
+ // Now reload the tab with a clean run.
+ await ContentTask.spawn(contentBrowser, null, () => {
+ return new Promise(resolve => {
+ addEventListener("pageshow", () => resolve(), {
+ capturing: true,
+ once: true,
+ });
+ content.location.reload();
+ });
+ });
+
+ await waitForPaintAfterLoad();
+
+ // Check that no PreferenceRead markers were recorded when the feature
+ // is turned off.
+ {
+ const {
+ parentThread,
+ contentThread,
+ } = await stopProfilerNowAndGetThreads(contentPid);
+ Assert.equal(
+ getPayloadsOfType(parentThread, "PreferenceRead").length,
+ 0,
+ "No PreferenceRead profile markers for layout.css.dpi were recorded " +
+ "when the PreferenceRead feature was turned on."
+ );
+
+ Assert.equal(
+ getPayloadsOfType(contentThread, "PreferenceRead").length,
+ 0,
+ "No PreferenceRead profile markers for layout.css.dpi were recorded " +
+ "when the PreferenceRead feature was turned on."
+ );
+ }
+ });
+});
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..177222c3a9
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_markers_parent_process.js
@@ -0,0 +1,40 @@
+/* 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.");
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+
+ info("Start the profiler in nostacksampling mode.");
+ startProfiler({ features: ["nostacksampling"] });
+
+ info("Dispatch a DOMEvent");
+ window.dispatchEvent(new Event("synthetic"));
+
+ info("Stop the profiler and get the profile.");
+ const profile = await stopAndGetProfile();
+
+ const markers = getInflatedMarkerData(profile.threads[0]);
+ {
+ const domEventStart = markers.find(
+ ({ phase, data }) =>
+ phase === INTERVAL_START && data?.eventType === "synthetic"
+ );
+ const domEventEnd = markers.find(
+ ({ phase, data }) =>
+ phase === INTERVAL_END && data?.eventType === "synthetic"
+ );
+ ok(domEventStart, "A start DOMEvent was generated");
+ ok(domEventEnd, "An end DOMEvent was generated");
+ ok(
+ domEventEnd.data.latency > 0,
+ "DOMEvent had a a latency value generated."
+ );
+ ok(domEventEnd.data.type === "DOMEvent");
+ ok(domEventEnd.name === "DOMEvent");
+ }
+ // Add more marker tests.
+});
diff --git a/tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js b/tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js
new file mode 100644
index 0000000000..2549c83e39
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_multi_frame_page_info.js
@@ -0,0 +1,88 @@
+/* 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();
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ 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 multi frame page."
+ );
+ 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;
+ });
+
+ info("Capture the profile data.");
+ const profile = await Services.profiler.getProfileDataAsync();
+ Services.profiler.StopProfiler();
+
+ let foundPage = 0;
+ // 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 pages are the ones with correct values we created."
+ );
+
+ let parentPage;
+ for (const page of contentProcess.pages) {
+ // Parent page
+ if (page.url == url) {
+ Assert.equal(page.url, url);
+ Assert.equal(typeof page.browsingContextID, "number");
+ Assert.equal(typeof page.innerWindowID, "number");
+ // Top level document will have no embedder.
+ Assert.equal(page.embedderInnerWindowID, 0);
+ 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.browsingContextID, "number");
+ Assert.equal(typeof page.innerWindowID, "number");
+ Assert.equal(typeof page.embedderInnerWindowID, "number");
+ Assert.notEqual(typeof parentPage, "undefined");
+ Assert.equal(page.embedderInnerWindowID, parentPage.innerWindowID);
+ 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..4acd6df51d
--- /dev/null
+++ b/tools/profiler/tests/browser/browser_test_profile_single_frame_page_info.js
@@ -0,0 +1,67 @@
+/* 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();
+ if (!AppConstants.MOZ_GECKO_PROFILER) {
+ return;
+ }
+ 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."
+ );
+ 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;
+ });
+
+ info("Capture the profile data.");
+ const profile = await Services.profiler.getProfileDataAsync();
+ Services.profiler.StopProfiler();
+
+ 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.browsingContextID, "number");
+ 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);
+ });
+});
diff --git a/tools/profiler/tests/browser/do_work_500ms.html b/tools/profiler/tests/browser/do_work_500ms.html
new file mode 100644
index 0000000000..9713a80671
--- /dev/null
+++ b/tools/profiler/tests/browser/do_work_500ms.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Do some work for 500ms</title>
+ <script>
+ const milliseconds = 500;
+ const millisecondsPerBatch = 10;
+ const end = Date.now() + milliseconds;
+ window.total = 0;
+ let i = 0;
+
+ /**
+ * Do work for a set number of milliseconds, but only do the work in batches
+ * so the browser does not get unresponsive.
+ */
+ function doWork() {
+ const batchEnd = Date.now() + millisecondsPerBatch;
+ // Do some work for a set amount of time.
+ while (Date.now() < end) {
+ // Do some kind of work that is non-deterministic to guard against optimizations.
+ window.total += Math.random();
+ i++;
+
+ // Check if a batch is done yet.
+ if (Date.now() > batchEnd) {
+ // Defer the rest of the work into a micro task. Keep on doing this until
+ // the total milliseconds have elapsed.
+ setTimeout(doWork, 0);
+ return;
+ }
+ }
+ }
+
+ doWork();
+ </script>
+</head>
+<body>
+ Do some work for 500ms.
+</body>
+</html>
diff --git a/tools/profiler/tests/browser/fixed_height.html b/tools/profiler/tests/browser/fixed_height.html
new file mode 100644
index 0000000000..7d21f3b746
--- /dev/null
+++ b/tools/profiler/tests/browser/fixed_height.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ <!--
+ Using an absolute value here should invoke Firefox to get
+ the layout.css.dpi preference.
+ -->
+ <style type="text/css">
+ div.fixed_height {
+ height: 15in;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="fixed_height">Testing</div>
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/head.js b/tools/profiler/tests/browser/head.js
new file mode 100644
index 0000000000..eebcf4d383
--- /dev/null
+++ b/tools/profiler/tests/browser/head.js
@@ -0,0 +1,70 @@
+/* import-globals-from ../shared-head.js */
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/tools/profiler/tests/browser/shared-head.js",
+ this
+);
+
+const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm"
+);
+
+const BASE_URL = "http://example.com/browser/tools/profiler/tests/browser/";
+
+registerCleanupFunction(() => {
+ 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!"
+ );
+ Services.profiler.StopProfiler();
+ }
+});
+
+/**
+ * This is a helper function that will stop the profiler of the browser running
+ * with PID contentPid.
+ * This happens immediately, without waiting for any sampling to happen or
+ * finish. Use stopProfilerAndGetThreads (without "Now") below instead to wait
+ * for samples before stopping.
+ *
+ * @param {number} contentPid
+ * @returns {Promise}
+ */
+async function stopProfilerNowAndGetThreads(contentPid) {
+ const profile = await Services.profiler.getProfileDataAsync();
+ Services.profiler.StopProfiler();
+
+ 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.");
+ }
+ const contentThread = contentProcess.threads[0];
+
+ if (!parentThread) {
+ throw new Error("The parent thread was not found in the profile.");
+ }
+
+ if (!contentThread) {
+ throw new Error("The content thread was not found in the profile.");
+ }
+
+ return { parentThread, contentThread };
+}
+
+/**
+ * This is a helper function that will stop the profiler of the browser running
+ * 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}
+ */
+async function stopProfilerAndGetThreads(contentPid) {
+ await Services.profiler.waitOnePeriodicSampling();
+
+ return stopProfilerNowAndGetThreads(contentPid);
+}
diff --git a/tools/profiler/tests/browser/multi_frame.html b/tools/profiler/tests/browser/multi_frame.html
new file mode 100644
index 0000000000..b2efcedd50
--- /dev/null
+++ b/tools/profiler/tests/browser/multi_frame.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Multi Frame</title>
+</head>
+<body>
+ Multi Frame
+ <iframe src="single_frame.html"></iframe>
+</body>
+</html>
diff --git a/tools/profiler/tests/browser/simple.html b/tools/profiler/tests/browser/simple.html
new file mode 100644
index 0000000000..f7c32d02c3
--- /dev/null
+++ b/tools/profiler/tests/browser/simple.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ Testing
+ </body>
+</html>
diff --git a/tools/profiler/tests/browser/single_frame.html b/tools/profiler/tests/browser/single_frame.html
new file mode 100644
index 0000000000..ebdfc41da2
--- /dev/null
+++ b/tools/profiler/tests/browser/single_frame.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Single Frame</title>
+</head>
+<body>
+ Single Frame
+</body>
+</html>