255 lines
7.4 KiB
JavaScript
255 lines
7.4 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
// Load the tracker in a dedicated loader using invisibleToDebugger and freshCompartment
|
|
// so that it can inspect any other module/compartment, even DevTools, chrome,
|
|
// and this script!
|
|
const { DevToolsLoader } = ChromeUtils.importESModule(
|
|
"resource://devtools/shared/loader/Loader.sys.mjs"
|
|
);
|
|
const loader = new DevToolsLoader({
|
|
invisibleToDebugger: true,
|
|
freshCompartment: true,
|
|
});
|
|
const { allocationTracker } = loader.require(
|
|
"chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker"
|
|
);
|
|
const TrackedObjects = loader.require(
|
|
"resource://devtools/shared/test-helpers/tracked-objects.sys.mjs"
|
|
);
|
|
|
|
// This test record multiple times complete heap snapshot,
|
|
// so that it can take a little bit to complete.
|
|
requestLongerTimeout(2);
|
|
|
|
add_task(async function () {
|
|
// Use a sandbox to allocate test javascript object in order to avoid any
|
|
// external noise
|
|
const global = Cu.Sandbox("http://example.com");
|
|
|
|
const tracker = allocationTracker({ watchGlobal: global });
|
|
const before = tracker.stillAllocatedObjects();
|
|
|
|
/* eslint-disable no-undef */
|
|
// This will allocation 1001 objects. The array and 1000 elements in it.
|
|
Cu.evalInSandbox(
|
|
"let list; new " +
|
|
function () {
|
|
list = [];
|
|
for (let i = 0; i < 1000; i++) {
|
|
list.push({});
|
|
}
|
|
},
|
|
global,
|
|
undefined,
|
|
"test-file.js",
|
|
1,
|
|
/* enforceFilenameRestrictions */ false
|
|
);
|
|
/* eslint-enable no-undef */
|
|
|
|
const allocations = tracker.countAllocations();
|
|
Assert.greaterOrEqual(
|
|
allocations,
|
|
1001,
|
|
`At least 1001 objects are reported as created (${allocations})`
|
|
);
|
|
|
|
// Uncomment this and comment the call to `countAllocations` to debug the allocations.
|
|
// The call to `countAllocations` will reset the allocation record.
|
|
// tracker.logAllocationSites();
|
|
|
|
const afterCreation = tracker.stillAllocatedObjects();
|
|
is(
|
|
afterCreation.objectsWithStack - before.objectsWithStack,
|
|
1001,
|
|
"We got exactly the expected number of objects recorded with an allocation site"
|
|
);
|
|
Assert.greater(
|
|
afterCreation.objectsWithStack,
|
|
before.objectsWithStack,
|
|
"We got some random number of objects without an allocation site"
|
|
);
|
|
|
|
Cu.evalInSandbox(
|
|
"list = null;",
|
|
global,
|
|
undefined,
|
|
"test-file.js",
|
|
7,
|
|
/* enforceFilenameRestrictions */ false
|
|
);
|
|
|
|
Cu.forceGC();
|
|
Cu.forceCC();
|
|
|
|
const afterGC = tracker.stillAllocatedObjects();
|
|
is(
|
|
afterCreation.objectsWithStack - afterGC.objectsWithStack,
|
|
1001,
|
|
"All the expected objects were reported freed in the count with allocation sites"
|
|
);
|
|
Assert.less(
|
|
afterGC.objectsWithoutStack,
|
|
afterCreation.objectsWithoutStack,
|
|
"And we released some random number of objects without an allocation site"
|
|
);
|
|
|
|
tracker.stop();
|
|
});
|
|
|
|
add_task(async function () {
|
|
const leaked = {};
|
|
TrackedObjects.track(leaked);
|
|
let transient = {};
|
|
TrackedObjects.track(transient);
|
|
|
|
is(TrackedObjects.getAllNodeIds().length, 2, "The two objects are reported");
|
|
|
|
info("Free the transient object");
|
|
transient = null;
|
|
Cu.forceGC();
|
|
|
|
is(
|
|
TrackedObjects.getAllNodeIds().length,
|
|
1,
|
|
"We now only have the leaked object"
|
|
);
|
|
TrackedObjects.clear();
|
|
});
|
|
|
|
add_task(async function () {
|
|
info("Test start and stop recording without any debug mode");
|
|
const tracker = allocationTracker({ watchDevToolsGlobals: true });
|
|
await tracker.startRecordingAllocations();
|
|
await tracker.stopRecordingAllocations();
|
|
tracker.stop();
|
|
});
|
|
|
|
add_task(async function () {
|
|
info("Test start and stop recording with 'allocations' debug mode");
|
|
const tracker = allocationTracker({ watchDevToolsGlobals: true });
|
|
await tracker.startRecordingAllocations("allocations");
|
|
await tracker.stopRecordingAllocations("allocations");
|
|
tracker.stop();
|
|
});
|
|
|
|
add_task(async function () {
|
|
info("Test start and stop recording with 'leaks' debug mode");
|
|
const tracker = allocationTracker({ watchDevToolsGlobals: true });
|
|
await tracker.startRecordingAllocations("leaks");
|
|
await tracker.stopRecordingAllocations("leaks");
|
|
tracker.stop();
|
|
});
|
|
|
|
add_task(async function () {
|
|
info("Test start and stop recording with tracked objects");
|
|
|
|
const leaked = {};
|
|
TrackedObjects.track(leaked);
|
|
|
|
const tracker = allocationTracker({ watchAllGlobals: true });
|
|
await tracker.startRecordingAllocations();
|
|
await tracker.stopRecordingAllocations();
|
|
tracker.stop();
|
|
|
|
TrackedObjects.clear();
|
|
});
|
|
|
|
add_task(async function () {
|
|
info("Test start and stop recording with tracked objects");
|
|
|
|
const sandbox = Cu.Sandbox(window);
|
|
const tracker = allocationTracker({ watchGlobal: sandbox });
|
|
await tracker.startRecordingAllocations("leaks");
|
|
|
|
Cu.evalInSandbox("this.foo = {};", sandbox, null, "sandbox.js", 1);
|
|
|
|
const record = await tracker.stopRecordingAllocations("leaks");
|
|
is(
|
|
record.objectsWithStack,
|
|
1,
|
|
"We get only one leaked objects, the foo object of the sandbox."
|
|
);
|
|
Assert.greater(
|
|
record.objectsWithoutStack,
|
|
10,
|
|
"We get an handful of objects without stacks. Most likely created by Memory API itself."
|
|
);
|
|
|
|
is(
|
|
record.leaks.length,
|
|
2,
|
|
"We get the one leak and the objects with missing stacks"
|
|
);
|
|
is(
|
|
record.leaks[0].src,
|
|
"UNKNOWN",
|
|
"First item is the objects with missing stacks"
|
|
);
|
|
// In theory the two following values should be equal,
|
|
// but they aren't always because of some dark matter around objects with missing stacks.
|
|
// `count` is computed out of `takeCensus`, while `objectsWithoutStack` uses `drainAllocationsLog`
|
|
// While the first go through the current GC graph, the second is a record of allocations over time,
|
|
// this probably explain why there is some subtle difference
|
|
Assert.lessOrEqual(
|
|
record.leaks[0].count,
|
|
record.objectsWithoutStack,
|
|
"For now, the leak report intermittently assume there is less leaked objects than the summary"
|
|
);
|
|
is(record.leaks[1].src, "sandbox.js", "Second item if about our 'foo' leak");
|
|
is(record.leaks[1].count, 1, "We leak one object on this file");
|
|
is(record.leaks[1].lines.length, 1, "We leak from only one line");
|
|
is(record.leaks[1].lines[0], "1: 1", "On first line, we leak one object");
|
|
tracker.stop();
|
|
|
|
TrackedObjects.clear();
|
|
});
|
|
|
|
add_task(async function () {
|
|
info("Test that transient globals are not leaked");
|
|
|
|
const tracker = allocationTracker({ watchAllGlobals: true });
|
|
|
|
let sandboxBefore = Cu.Sandbox(window);
|
|
// We need to allocate at least one object from the global to reproduce the leak
|
|
Cu.evalInSandbox(
|
|
"this.foo = {};",
|
|
sandboxBefore,
|
|
null,
|
|
"sandbox-before.js",
|
|
1
|
|
);
|
|
const weakBefore = Cu.getWeakReference(sandboxBefore);
|
|
sandboxBefore = null;
|
|
|
|
await tracker.startRecordingAllocations();
|
|
|
|
ok(
|
|
!weakBefore.get(),
|
|
"Sandbox created before the record should have been freed by GCs done by startRecordingAllocations"
|
|
);
|
|
|
|
let sandboxDuring = Cu.Sandbox(window);
|
|
// We need to allocate at least one object from the global to reproduce the leak
|
|
Cu.evalInSandbox(
|
|
"this.bar = {};",
|
|
sandboxDuring,
|
|
null,
|
|
"sandbox-during.js",
|
|
1
|
|
);
|
|
const weakDuring = Cu.getWeakReference(sandboxDuring);
|
|
sandboxDuring = null;
|
|
|
|
await tracker.stopRecordingAllocations();
|
|
|
|
ok(
|
|
!weakDuring.get(),
|
|
"Sandbox should have been freed by GCs done by stopRecordingAllocations"
|
|
);
|
|
|
|
tracker.stop();
|
|
});
|