summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/test/allocations
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/test/allocations')
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_browser_console.ini11
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_browser_console.js69
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_debugger.ini14
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_debugger.js13
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_inspector.ini14
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_inspector.js13
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.ini14
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.js13
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.ini13
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.js42
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.ini14
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.js13
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_target.ini11
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_target.js51
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_toolbox.ini11
-rw-r--r--devtools/client/framework/test/allocations/browser_allocations_toolbox.js53
-rw-r--r--devtools/client/framework/test/allocations/docs/index.md241
-rw-r--r--devtools/client/framework/test/allocations/head.js250
-rw-r--r--devtools/client/framework/test/allocations/moz.build16
-rw-r--r--devtools/client/framework/test/allocations/reload-test.js81
-rw-r--r--devtools/client/framework/test/allocations/reloaded-page.html11
-rw-r--r--devtools/client/framework/test/allocations/reloaded.pngbin0 -> 580 bytes
22 files changed, 968 insertions, 0 deletions
diff --git a/devtools/client/framework/test/allocations/browser_allocations_browser_console.ini b/devtools/client/framework/test/allocations/browser_allocations_browser_console.ini
new file mode 100644
index 0000000000..77aec9d502
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_browser_console.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_browser_console.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_browser_console.js b/devtools/client/framework/test/allocations/browser_allocations_browser_console.js
new file mode 100644
index 0000000000..13d0171dfa
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_browser_console.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while opening and closing the Browser Console
+
+const TEST_URL =
+ "http://example.com/browser/devtools/client/framework/test/allocations/reloaded-page.html";
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ BrowserConsoleManager,
+} = require("resource://devtools/client/webconsole/browser-console-manager.js");
+
+async function testScript() {
+ // Open
+ await BrowserConsoleManager.toggleBrowserConsole();
+
+ // Reload the tab to make the test slightly more real
+ const hud = BrowserConsoleManager.getBrowserConsole();
+ const onTargetProcessed = hud.commands.targetCommand.once(
+ "processed-available-target"
+ );
+
+ gBrowser.reloadTab(gBrowser.selectedTab);
+
+ info("Wait for target of the new document to be fully processed");
+ await onTargetProcessed;
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Close
+ await BrowserConsoleManager.toggleBrowserConsole();
+
+ // Browser console still cleanup stuff after the resolution of toggleBrowserConsole.
+ // So wait for a little while to ensure it completes all cleanups.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 500));
+}
+
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["devtools.browsertoolbox.scope", "everything"]],
+ });
+
+ const tab = await addTab(TEST_URL);
+
+ // Run the test scenario first before recording in order to load all the
+ // modules. Otherwise they get reported as "still allocated" objects,
+ // whereas we do expect them to be kept in memory as they are loaded via
+ // the main DevTools loader, which keeps the module loaded until the
+ // shutdown of Firefox
+ await testScript();
+
+ // Now, run the test script. This time, we record this run.
+ await startRecordingAllocations();
+
+ for (let i = 0; i < 3; i++) {
+ await testScript();
+ }
+
+ await stopRecordingAllocations("browser-console");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.ini b/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.ini
new file mode 100644
index 0000000000..339608ca83
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+ reload-test.js
+ reloaded-page.html
+ reloaded.png
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_reload_debugger.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.js b/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.js
new file mode 100644
index 0000000000..748a5e906e
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while reloading the page with the debugger opened
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/allocations/reload-test.js",
+ this
+);
+
+add_task(createPanelReloadTest("reload-debugger", "jsdebugger"));
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.ini b/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.ini
new file mode 100644
index 0000000000..9eca40e633
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+ reload-test.js
+ reloaded-page.html
+ reloaded.png
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_reload_inspector.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.js b/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.js
new file mode 100644
index 0000000000..3369c54f24
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while reloading the page with the inspector opened
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/allocations/reload-test.js",
+ this
+);
+
+add_task(createPanelReloadTest("reload-inspector", "inspector"));
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.ini b/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.ini
new file mode 100644
index 0000000000..9323e9a8d9
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+ reload-test.js
+ reloaded-page.html
+ reloaded.png
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_reload_netmonitor.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.js b/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.js
new file mode 100644
index 0000000000..2a57652ac5
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while reloading the page with the netmonitor opened
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/allocations/reload-test.js",
+ this
+);
+
+add_task(createPanelReloadTest("reload-netmonitor", "netmonitor"));
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.ini b/devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.ini
new file mode 100644
index 0000000000..52f1ce809e
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.ini
@@ -0,0 +1,13 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+ reloaded-page.html
+ reloaded.png
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_reload_no_devtools.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.js b/devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.js
new file mode 100644
index 0000000000..e2f344fcb5
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_no_devtools.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while reloading the page without anything related to DevTools running
+
+const TEST_URL =
+ "http://example.com/browser/devtools/client/framework/test/allocations/reloaded-page.html";
+
+async function testScript() {
+ await BrowserTestUtils.reloadTab(gBrowser.selectedTab);
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+}
+
+add_task(async function () {
+ const tab = await addTab(TEST_URL);
+
+ // Run the test scenario first before recording in order to load all the
+ // modules. Otherwise they get reported as "still allocated" objects,
+ // whereas we do expect them to be kept in memory as they are loaded via
+ // the main DevTools loader, which keeps the module loaded until the
+ // shutdown of Firefox
+ await testScript();
+
+ await startRecordingAllocations({
+ alsoRecordContentProcess: true,
+ });
+
+ // Now, run the test script. This time, we record this run.
+ for (let i = 0; i < 10; i++) {
+ await testScript();
+ }
+
+ await stopRecordingAllocations("reload-no-devtools", {
+ alsoRecordContentProcess: true,
+ });
+
+ gBrowser.removeTab(tab);
+});
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.ini b/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.ini
new file mode 100644
index 0000000000..baac713e94
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+ reload-test.js
+ reloaded-page.html
+ reloaded.png
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_reload_webconsole.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.js b/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.js
new file mode 100644
index 0000000000..a60fd03b3c
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while reloading the page with the webconsole opened
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/devtools/client/framework/test/allocations/reload-test.js",
+ this
+);
+
+add_task(createPanelReloadTest("reload-webconsole", "webconsole"));
diff --git a/devtools/client/framework/test/allocations/browser_allocations_target.ini b/devtools/client/framework/test/allocations/browser_allocations_target.ini
new file mode 100644
index 0000000000..4658423b66
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_target.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_target.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_target.js b/devtools/client/framework/test/allocations/browser_allocations_target.js
new file mode 100644
index 0000000000..a93d6b51c9
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_target.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while spawning Commands and the first top level target
+
+const TEST_URL =
+ "data:text/html;charset=UTF-8,<div>Target allocations test</div>";
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ CommandsFactory,
+} = require("resource://devtools/shared/commands/commands-factory.js");
+
+async function testScript(tab) {
+ const commands = await CommandsFactory.forTab(tab);
+ await commands.targetCommand.startListening();
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // destroy the commands to also destroy the dedicated client.
+ await commands.destroy();
+
+ // Spin the event loop to ensure commands destruction is fully completed
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 0));
+}
+
+add_task(async function () {
+ const tab = await addTab(TEST_URL);
+
+ // Run the test scenario first before recording in order to load all the
+ // modules. Otherwise they get reported as "still allocated" objects,
+ // whereas we do expect them to be kept in memory as they are loaded via
+ // the main DevTools loader, which keeps the module loaded until the
+ // shutdown of Firefox
+ await testScript(tab);
+
+ await startRecordingAllocations();
+
+ // Now, run the test script. This time, we record this run.
+ await testScript(tab);
+
+ await stopRecordingAllocations("target");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/devtools/client/framework/test/allocations/browser_allocations_toolbox.ini b/devtools/client/framework/test/allocations/browser_allocations_toolbox.ini
new file mode 100644
index 0000000000..2996ef7767
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_toolbox.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ !/devtools/shared/test-helpers/allocation-tracker.js
+ head.js
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_toolbox.js]
+skip-if = os != 'linux' || debug || asan || ccov # Results should be platform agnostic - only run on linux64-opt
diff --git a/devtools/client/framework/test/allocations/browser_allocations_toolbox.js b/devtools/client/framework/test/allocations/browser_allocations_toolbox.js
new file mode 100644
index 0000000000..e0f86511bc
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_toolbox.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Record allocations while opening and closing DevTools
+
+const TEST_URL =
+ "data:text/html;charset=UTF-8,<div>Target allocations test</div>";
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const {
+ gDevTools,
+} = require("resource://devtools/client/framework/devtools.js");
+
+async function testScript(tab) {
+ const toolbox = await gDevTools.showToolboxForTab(tab, {
+ toolId: "inspector",
+ });
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ await toolbox.destroy();
+
+ // Spin the event loop to ensure toolbox destroy is fully completed
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 0));
+}
+
+add_task(async function () {
+ const tab = await addTab(TEST_URL);
+
+ // Run the test scenario first before recording in order to load all the
+ // modules. Otherwise they get reported as "still allocated" objects,
+ // whereas we do expect them to be kept in memory as they are loaded via
+ // the main DevTools loader, which keeps the module loaded until the
+ // shutdown of Firefox
+ await testScript(tab);
+
+ await startRecordingAllocations();
+
+ // Now, run the test script. This time, we record this run.
+ for (let i = 0; i < 3; i++) {
+ await testScript(tab);
+ }
+
+ await stopRecordingAllocations("toolbox");
+
+ gBrowser.removeTab(tab);
+});
diff --git a/devtools/client/framework/test/allocations/docs/index.md b/devtools/client/framework/test/allocations/docs/index.md
new file mode 100644
index 0000000000..f0d6921325
--- /dev/null
+++ b/devtools/client/framework/test/allocations/docs/index.md
@@ -0,0 +1,241 @@
+# Allocation tests
+
+The [allocations](https://searchfox.org/mozilla-central/source/devtools/client/framework/test/allocations) folder contains special mochitests which are meant to record data about the memory usage of DevTools.
+This uses Spidermonkey's Memory API implemented next to the debugger API.
+For more info, see the following doc:
+<https://searchfox.org/mozilla-central/source/js/src/doc/Debugger/Debugger.Memory.md>
+
+# Test example
+
+```javascript
+add_task(async function() {
+ // Execute preliminary setup in order to be able to run your scenario
+ // You would typicaly load modules, open a tab, a toolbox, ...
+ ...
+
+ // Run the test scenario first before recording in order to load all the
+ // modules. Otherwise they get reported as "still allocated" objects,
+ // whereas we do expect them to be kept in memory as they are loaded via
+ // the main DevTools loader, which keeps the module loaded until the
+ // shutdown of Firefox
+ await testScript();
+
+ // Pass alsoRecordContentProcess if you want to record the content process
+ // of the current tab. Otherwise it will only record parent process objects.
+ await startRecordingAllocations({ alsoRecordContentProcess: true });
+
+ // Now, run the test script. This time, we record this run.
+ await testScript(toolbox);
+
+ // This will stop the record and also publish the results to Talos database
+ // Second argument will be the name of the test displayed in Talos.
+ // Many tests will be recorded, but all of them will be prefixed with this string.
+ await stopRecordingAllocations("reload", { alsoRecordContentProcess: true });
+
+ // Then, here you can execute cleanup.
+ // You would typically close the tab, toolbox, ...
+});
+```
+
+# How to run them locally
+
+```bash
+$ ./mach mochitest --headless devtools/client/framework/test/allocations/
+```
+
+And to only see the results:
+```bash
+$ ./mach mochitest --headless devtools/client/framework/test/allocations/ | grep " test leaked "
+```
+
+# Debug leaks
+
+If you are seeing a regression or an improvement, only seeing the number of objects being leaked isn't super helpful.
+The tests includes some special debug modes which are printing lots of data to figure out what is leaking and why.
+
+You may run the test with the following env variable to turn debug mode on:
+```bash
+DEBUG_DEVTOOLS_ALLOCATIONS=leak|allocations $ ./mach mochitest --headless devtools/client/framework/test/allocations/the-fault-test.js
+```
+
+**DEBUG_DEVTOOLS_ALLOCATIONS** can enable two distinct debug output. (Only one can be enabled at a given time)
+
+**DEBUG_DEVTOOLS_ALLOCATIONS=allocations** will report all allocation sites that have been made
+while running your test. This will include allocations which has been freed.
+This view is especially useful if you want to reduce allocations in order to reduce GC overload.
+
+**DEBUG_DEVTOOLS_ALLOCATIONS=leak** will report only the allocations which are still allocated
+at the end of your test. Sometimes it will only report allocations with missing stack trace.
+Thus making the preview view helpful.
+
+## Example
+
+Let's assume we have the following code:
+
+```javascript
+ 1: exports.MyModule = {
+ 2: globalArray: [],
+ 3: test() {
+ 3: // The following object will be allocated but not leaked,
+ 5: // as we keep no reference to it anywhere
+ 6: const transientObject = {};
+ 7:
+ 8: // The following object will be allocated on this line,
+ 9: // but leaked on the following one. By storing a reference
+10: // to it in the global array which is never cleared.
+11: const leakedObject = {};
+12: this.globalArray.push(leakedObject);
+13: },
+14: };
+```
+
+And that, we have a memory test doing this:
+
+```javascript
+ const { MyModule } = require("devtools/my-module");
+
+ await startRecordingAllocations();
+
+ MyModule.test();
+
+ await stopRecordingAllocations("target");
+```
+
+We can first review all the allocations by running:
+
+```bash
+DEBUG_DEVTOOLS_ALLOCATIONS=allocations $ ./mach mochitest --headless devtools/client/framework/test/allocations/browser_allocation_myTest.js
+
+```
+
+which will print at the end:
+
+```javascript
+DEVTOOLS ALLOCATION: all allocations (which may be freed or are still allocated):
+[
+ {
+ "src": "UNKNOWN",
+ "count": 80,
+ "lines": [
+ "?: 80"
+ ]
+ },
+ {
+ "src": "resource://devtools/my-module.js",
+ "count": 2,
+ "lines": [
+ "11: 1"
+ "6: 1"
+ ]
+ }
+]
+```
+
+The first part, with `UNKNOWN` can be ignored. This is about objects with missing allocation sites.
+The second part of this logs tells us that 2 objects were allocated from my-module.js when running the test.
+One has been allocated at line 6, it is `transcientObject`.
+Another one has been allocated at line 11, it is `leakedObject`.
+
+Now, we can use the second view to focus only on objects that have been kept allocated:
+
+```bash
+DEBUG_DEVTOOLS_ALLOCATIONS=leaks $ ./mach mochitest --headless devtools/client/framework/test/allocations/browser_allocation_myTest.js
+
+```
+
+which will print at the end:
+
+```javascript
+DEVTOOLS ALLOCATION: allocations which leaked:
+[
+ {
+ "src": "UNKNOWN",
+ "count": 80,
+ "lines": [
+ "?: 80"
+ ]
+ },
+ {
+ "src": "resource://devtools/shared/commands/commands-factory.js",
+ "count": 1,
+ "lines": [
+ "11: 1"
+ ]
+ }
+]
+```
+
+Similarly, we can focus only on the second part, which tells us that only one object is being leaked
+and this object has been originally created from line 11, this is `leakedObject`.
+This doesn't tell us why the object is being kept allocated, but at least we know which one is being kept in memory.
+
+
+## Debug leaks via dominators
+
+This last feature might be the most powerful and isn't bound to DEBUG_DEVTOOLS_ALLOCATIONS.
+This is always enabled.
+Also, it requires to know which particular object is being leaked and also require to hack
+the codebase in order to pass a reference of the suspicious object to the test helper.
+
+You can instruct the test helper to track a given object by doing this:
+
+```javascript
+ 1: // Let's say it is some code running from "my-module.js"
+ 2:
+ 3: // From a DevTools CommonJS module:
+ 4: const { track } = require("devtools/shared/test-helpers/tracked-objects.sys.mjs");
+ 5: // From anything else, JSM, XPCOM module,...:
+ 6: const { track } = ChromeUtils.importESModule("resource://devtools/shared/test-helpers/tracked-objects.sys.mjs");
+ 7:
+ 8: const g = [];
+ 9: function someFunctionInDevToolsCalledBySomething() {
+10: const myLeakedObject = {};
+11: track(myLeakedObject);
+12:
+13: // Simulate a leak by holding a reference to the object in a global `g` array
+14: g.push({ seeMyCustomAttributeHere: myLeakedObject });
+15: }
+```
+
+Then, when running the test you will get such output:
+
+```bash
+ 0:41.26 GECKO(644653) # Tracing: Object@my-module:10
+ 0:40.65 GECKO(644653) ### Path(s) from root:
+ 0:41.26 GECKO(644653) - other@no-stack:undefined.WeakMap entry value
+ 0:41.26 GECKO(644653) \--> LexicalEnvironment@base-loader.sys.mjs:160.**UNKNOWN SLOT 1**
+ 0:41.26 GECKO(644653) \--> Object@base-loader.sys.mjs:155.g
+ 0:41.26 GECKO(644653) \--> Array@my-module.js:8.objectElements[0]
+ 0:41.26 GECKO(644653) \--> Object@my-module.js:14.seeMyCustomAttributeHere
+ 0:41.26 GECKO(644653) \--> Object@my-module.js:10
+```
+
+This output means that `myLeakedObject` was originally allocated from my-module.js at line 10.
+And is being held allocated because it is kept in an Object allocated from my-module.js at line 14.
+This is our custom object we stored in `g` global Array.
+This custom object it hold by the Array allocated at line 8 of my-module.js.
+And this array is held allocated from an Object, itself allocated by base-loader.sys.mjs at line 155.
+This is the global of the my-module.js's module, created by DevTools loader.
+Then we see some more low level object up to another global object, which misses its allocation site.
+
+# How to easily get data from try run
+
+```bash
+$ ./mach try fuzzy devtools/client/framework/test/allocations/ --query "'linux 'chrome-e10s 'opt '64-qr/opt"
+```
+
+You might also pass `--rebuild 3` if the test result is having some noise and you want more test runs.
+
+# Following trends for these tests
+
+You may try looking at:
+<https://firefox-dev.tools/performance-dashboard/tools/memory.html>
+
+Or at:
+<https://treeherder.mozilla.org/perfherder/graphs?highlightAlerts=1&highlightChangelogData=1&series=autoland,3887143,1,12&series=mozilla-central,3887737,1,12&series=mozilla-central,3887740,1,12&series=mozilla-central,3887743,1,12&series=mozilla-central,3896204,1,12&timerange=2592000&zoom=1630504360002,1632239562424,0,123469.11111111111>
+
+Link that you get from: <https://treeherder.mozilla.org/perfherder/graphs>
+by looking at last year data for "DevTools" in the first dropdown,
+and double clicking on the relevant line in "Tests" menulist.
+
+Significant improvements and regressions will be notified through [the following dashboard](https://treeherder.mozilla.org/perfherder/alerts?hideDwnToInv=1&page=1&framework=12).
diff --git a/devtools/client/framework/test/allocations/head.js b/devtools/client/framework/test/allocations/head.js
new file mode 100644
index 0000000000..d9bded321a
--- /dev/null
+++ b/devtools/client/framework/test/allocations/head.js
@@ -0,0 +1,250 @@
+/* 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/. */
+
+"use strict";
+
+// Load the tracker very first in order to ensure tracking all objects created by DevTools.
+// This is especially important for allocation sites. We need to catch the global the
+// earliest possible in order to ensure that all allocation objects come with a stack.
+//
+// If we want to track DevTools module loader we should ensure loading Loader.sys.mjs within
+// the `testScript` Function. i.e. after having calling startRecordingAllocations.
+let tracker, releaseTrackerLoader;
+{
+ const {
+ useDistinctSystemPrincipalLoader,
+ releaseDistinctSystemPrincipalLoader,
+ } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs"
+ );
+
+ const requester = {};
+ const loader = useDistinctSystemPrincipalLoader(requester);
+ releaseTrackerLoader = () => releaseDistinctSystemPrincipalLoader(requester);
+ const { allocationTracker } = loader.require(
+ "chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker.js"
+ );
+ tracker = allocationTracker({ watchDevToolsGlobals: true });
+}
+
+// /!\ Be careful about imports/require
+//
+// Some tests may record the very first time we load a module.
+// If we start loading them from here, we might only retrieve the already loaded
+// module from the loader's cache. This would no longer highlight the cost
+// of loading a new module from scratch.
+//
+// => Avoid loading devtools module as much as possible
+// => If you really have to, lazy load them
+
+XPCOMUtils.defineLazyGetter(this, "TrackedObjects", () => {
+ return ChromeUtils.importESModule(
+ "resource://devtools/shared/test-helpers/tracked-objects.sys.mjs"
+ );
+});
+
+// So that PERFHERDER data can be extracted from the logs.
+SimpleTest.requestCompleteLog();
+
+// We have to disable testing mode, or various debug instructions are enabled.
+// We especially want to disable redux store history, which would leak all the actions!
+SpecialPowers.pushPrefEnv({
+ set: [["devtools.testing", false]],
+});
+
+// Set DEBUG_DEVTOOLS_ALLOCATIONS=allocations|leaks in order print debug informations.
+const DEBUG_ALLOCATIONS = Services.env.get("DEBUG_DEVTOOLS_ALLOCATIONS");
+
+async function addTab(url) {
+ const tab = BrowserTestUtils.addTab(gBrowser, url);
+ gBrowser.selectedTab = tab;
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+ return tab;
+}
+
+/**
+ * This function will force some garbage collection before recording
+ * data about allocated objects.
+ *
+ * This accept an optional boolean to also record the content process objects
+ * of the current tab. That, in addition of objects from the parent process,
+ * which are always recorded.
+ *
+ * This return same data object which is meant to be passed to `stopRecordingAllocations` as-is.
+ *
+ * See README.md file in this folder.
+ */
+async function startRecordingAllocations({
+ alsoRecordContentProcess = false,
+} = {}) {
+ // Also start recording allocations in the content process, if requested
+ if (alsoRecordContentProcess) {
+ await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [DEBUG_ALLOCATIONS],
+ async debug_allocations => {
+ const { DevToolsLoader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+
+ const {
+ useDistinctSystemPrincipalLoader,
+ releaseDistinctSystemPrincipalLoader,
+ } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs"
+ );
+
+ const requester = {};
+ const loader = useDistinctSystemPrincipalLoader(requester);
+ const { allocationTracker } = loader.require(
+ "chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker.js"
+ );
+ // We watch all globals in the content process, (instead of only DevTools global in parent process)
+ // because we may easily leak web page objects, which aren't in DevTools global.
+ const tracker = allocationTracker({ watchAllGlobals: true });
+
+ // /!\ HACK: store tracker and releaseTrackerLoader on DevToolsLoader in order
+ // to be able to reuse them in a following call to SpecialPowers.spawn
+ DevToolsLoader.tracker = tracker;
+ DevToolsLoader.releaseTrackerLoader = () =>
+ releaseDistinctSystemPrincipalLoader(requester);
+
+ await tracker.startRecordingAllocations(debug_allocations);
+ }
+ );
+ // Trigger a GC in the parent process as this additional ContentTask
+ // seems to make harder to release objects created before we start recording.
+ await tracker.doGC();
+ }
+
+ await tracker.startRecordingAllocations(DEBUG_ALLOCATIONS);
+}
+
+/**
+ * See doc of startRecordingAllocations
+ */
+async function stopRecordingAllocations(
+ recordName,
+ { alsoRecordContentProcess = false } = {}
+) {
+ // Ensure that Memory API didn't ran out of buffers
+ ok(!tracker.overflowed, "Allocation were all recorded in the parent process");
+
+ // And finally, retrieve the record *after* having ran the test
+ const parentProcessData = await tracker.stopRecordingAllocations(
+ DEBUG_ALLOCATIONS
+ );
+
+ const objectNodeIds = TrackedObjects.getAllNodeIds();
+ if (objectNodeIds.length) {
+ tracker.traceObjects(objectNodeIds);
+ }
+
+ let contentProcessData = null;
+ if (alsoRecordContentProcess) {
+ contentProcessData = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [DEBUG_ALLOCATIONS],
+ debug_allocations => {
+ const { DevToolsLoader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ const { tracker } = DevToolsLoader;
+ ok(
+ !tracker.overflowed,
+ "Allocation were all recorded in the content process"
+ );
+ return tracker.stopRecordingAllocations(debug_allocations);
+ }
+ );
+ }
+
+ const trackedObjectsInContent = await SpecialPowers.spawn(
+ gBrowser.selectedBrowser,
+ [],
+ () => {
+ const TrackedObjects = ChromeUtils.importESModule(
+ "resource://devtools/shared/test-helpers/tracked-objects.sys.mjs"
+ );
+ const objectNodeIds = TrackedObjects.getAllNodeIds();
+ if (objectNodeIds.length) {
+ const { DevToolsLoader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ const { tracker } = DevToolsLoader;
+ // Record the heap snapshot from the content process,
+ // and pass the record's filepath to the parent process
+ // As only the parent process can read the file because
+ // of sandbox restrictions made to content processes regarding file I/O.
+ const snapshotFile = tracker.getSnapshotFile();
+ return { snapshotFile, objectNodeIds };
+ }
+ return null;
+ }
+ );
+ if (trackedObjectsInContent) {
+ tracker.traceObjects(
+ trackedObjectsInContent.objectNodeIds,
+ trackedObjectsInContent.snapshotFile
+ );
+ }
+
+ // Craft the JSON object required to save data in talos database
+ info(
+ `The ${recordName} test leaked ${parentProcessData.objectsWithStack} objects (${parentProcessData.objectsWithoutStack} with missing allocation site) in the parent process`
+ );
+ const PERFHERDER_DATA = {
+ framework: {
+ name: "devtools",
+ },
+ suites: [
+ {
+ name: recordName + ":parent-process",
+ subtests: [
+ {
+ name: "objects-with-stacks",
+ value: parentProcessData.objectsWithStack,
+ },
+ {
+ name: "memory",
+ value: parentProcessData.memory,
+ },
+ ],
+ },
+ ],
+ };
+ if (alsoRecordContentProcess) {
+ info(
+ `The ${recordName} test leaked ${contentProcessData.objectsWithStack} objects (${contentProcessData.objectsWithoutStack} with missing allocation site) in the content process`
+ );
+ PERFHERDER_DATA.suites.push({
+ name: recordName + ":content-process",
+ subtests: [
+ {
+ name: "objects-with-stacks",
+ value: contentProcessData.objectsWithStack,
+ },
+ {
+ name: "memory",
+ value: contentProcessData.memory,
+ },
+ ],
+ });
+
+ // Finally release the tracker loader in content process.
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ const { DevToolsLoader } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ DevToolsLoader.releaseTrackerLoader();
+ });
+ }
+
+ // And release the tracker loader in the parent process
+ releaseTrackerLoader();
+
+ // Log it to stdout so that perfherder can collect this data.
+ // This only works if we called `SimpleTest.requestCompleteLog()`!
+ info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
+}
diff --git a/devtools/client/framework/test/allocations/moz.build b/devtools/client/framework/test/allocations/moz.build
new file mode 100644
index 0000000000..37358a9075
--- /dev/null
+++ b/devtools/client/framework/test/allocations/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "browser_allocations_browser_console.ini",
+ "browser_allocations_reload_debugger.ini",
+ "browser_allocations_reload_inspector.ini",
+ "browser_allocations_reload_netmonitor.ini",
+ "browser_allocations_reload_no_devtools.ini",
+ "browser_allocations_reload_webconsole.ini",
+ "browser_allocations_target.ini",
+ "browser_allocations_toolbox.ini",
+]
diff --git a/devtools/client/framework/test/allocations/reload-test.js b/devtools/client/framework/test/allocations/reload-test.js
new file mode 100644
index 0000000000..a382998dd3
--- /dev/null
+++ b/devtools/client/framework/test/allocations/reload-test.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/. */
+
+"use strict";
+
+/* import-globals-from head.js */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+
+/**
+ * Generate a test Task to record allocation when reloading a test page
+ * while having one particular DevTools panel opened
+ *
+ * @param String recordName
+ * Name of the test recorded into PerfHerder/Talos database
+ * @param String toolId
+ * ID of the panel to open
+ */
+function createPanelReloadTest(recordName, toolId) {
+ return async function panelReloadTest() {
+ const TEST_URL =
+ "http://example.com/browser/devtools/client/framework/test/allocations/reloaded-page.html";
+
+ async function testScript(toolbox) {
+ const onTargetSwitched =
+ toolbox.commands.targetCommand.once("switched-target");
+ const onReloaded = toolbox.getCurrentPanel().once("reloaded");
+
+ gBrowser.reloadTab(gBrowser.selectedTab);
+
+ if (
+ toolbox.commands.targetCommand.targetFront.targetForm
+ .followWindowGlobalLifeCycle
+ ) {
+ info("Wait for target switched");
+ await onTargetSwitched;
+ }
+
+ info("Wait for panel reload");
+ await onReloaded;
+
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ }
+
+ const tab = await addTab(TEST_URL);
+
+ const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+ );
+ const {
+ gDevTools,
+ } = require("resource://devtools/client/framework/devtools.js");
+ const toolbox = await gDevTools.showToolboxForTab(tab, {
+ toolId,
+ });
+
+ // Run the test scenario first before recording in order to load all the
+ // modules. Otherwise they get reported as "still allocated" objects,
+ // whereas we do expect them to be kept in memory as they are loaded via
+ // the main DevTools loader, which keeps the module loaded until the
+ // shutdown of Firefox
+ await testScript(toolbox);
+
+ await startRecordingAllocations({
+ alsoRecordContentProcess: true,
+ });
+
+ // Now, run the test script. This time, we record this run.
+ for (let i = 0; i < 10; i++) {
+ await testScript(toolbox);
+ }
+
+ await stopRecordingAllocations(recordName, {
+ alsoRecordContentProcess: true,
+ });
+
+ await toolbox.destroy();
+ gBrowser.removeTab(tab);
+ };
+}
diff --git a/devtools/client/framework/test/allocations/reloaded-page.html b/devtools/client/framework/test/allocations/reloaded-page.html
new file mode 100644
index 0000000000..4f14c8a0c3
--- /dev/null
+++ b/devtools/client/framework/test/allocations/reloaded-page.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Reloaded page</title>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ The reloaded page
+ <img src="reloaded.png" />
+ </body>
+</html>
diff --git a/devtools/client/framework/test/allocations/reloaded.png b/devtools/client/framework/test/allocations/reloaded.png
new file mode 100644
index 0000000000..769c636340
--- /dev/null
+++ b/devtools/client/framework/test/allocations/reloaded.png
Binary files differ