Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

"use strict";
let gcExperimentAPIs = {
gcHelper: {
schema: "schema.json",
child: {
scopes: ["addon_child"],
script: "child.js",
paths: [["gcHelper"]],
},
},
};
let gcExperimentFiles = {
"schema.json": JSON.stringify([
{
namespace: "gcHelper",
functions: [
{
name: "forceGarbageCollect",
type: "function",
parameters: [],
async: true,
},
{
name: "registerWitness",
type: "function",
parameters: [
{
name: "obj",
// Expected type is "object", but using "any" here to ensure that
// the parameter is untouched (not normalized).
type: "any",
},
],
returns: { type: "number" },
},
{
name: "isGarbageCollected",
type: "function",
parameters: [
{
name: "witnessId",
description: "return value of registerWitness",
type: "number",
},
],
returns: { type: "boolean" },
},
],
},
]),
"child.js": () => {
let { setTimeout } = ChromeUtils.importESModule(
);
/* globals ExtensionAPI */
this.gcHelper = class extends ExtensionAPI {
getAPI() {
let witnesses = new Map();
return {
gcHelper: {
async forceGarbageCollect() {
// Logic copied from test_ext_contexts_gc.js
for (let i = 0; i < 3; ++i) {
Cu.forceShrinkingGC();
Cu.forceCC();
Cu.forceGC();
await new Promise(resolve => setTimeout(resolve, 0));
}
},
registerWitness(obj) {
let witnessId = witnesses.size;
witnesses.set(witnessId, Cu.getWeakReference(obj));
return witnessId;
},
isGarbageCollected(witnessId) {
return witnesses.get(witnessId).get() === null;
},
},
};
}
};
},
};
// Verify that the experiment is working as intended before using it in tests.
add_task(async function test_gc_experiment() {
let extension = ExtensionTestUtils.loadExtension({
isPrivileged: true,
manifest: {
experiment_apis: gcExperimentAPIs,
},
files: gcExperimentFiles,
async background() {
let obj1 = {};
let obj2 = {};
let witness1 = browser.gcHelper.registerWitness(obj1);
let witness2 = browser.gcHelper.registerWitness(obj2);
obj1 = null;
await browser.gcHelper.forceGarbageCollect();
browser.test.assertTrue(
browser.gcHelper.isGarbageCollected(witness1),
"obj1 should have been garbage-collected"
);
browser.test.assertFalse(
browser.gcHelper.isGarbageCollected(witness2),
"obj2 should not have been garbage-collected"
);
browser.test.sendMessage("done");
},
});
await extension.startup();
await extension.awaitMessage("done");
await extension.unload();
});
add_task(async function test_port_gc() {
let extension = ExtensionTestUtils.loadExtension({
isPrivileged: true,
manifest: {
experiment_apis: gcExperimentAPIs,
},
files: gcExperimentFiles,
async background() {
let witnessPortSender;
let witnessPortReceiver;
browser.runtime.onConnect.addListener(port => {
browser.test.assertEq("daName", port.name, "expected port");
witnessPortReceiver = browser.gcHelper.registerWitness(port);
port.disconnect();
});
// runtime.connect() only triggers onConnect for different contexts,
// so create a frame to have a different context.
// A blank frame in a moz-extension:-document will have access to the
// extension APIs.
let frameWindow = await new Promise(resolve => {
let f = document.createElement("iframe");
f.onload = () => resolve(f.contentWindow);
document.body.append(f);
});
await new Promise(resolve => {
let port = frameWindow.browser.runtime.connect({ name: "daName" });
witnessPortSender = browser.gcHelper.registerWitness(port);
port.onDisconnect.addListener(() => resolve());
});
await browser.gcHelper.forceGarbageCollect();
browser.test.assertTrue(
browser.gcHelper.isGarbageCollected(witnessPortSender),
"runtime.connect() port should have been garbage-collected"
);
browser.test.assertTrue(
browser.gcHelper.isGarbageCollected(witnessPortReceiver),
"runtime.onConnect port should have been garbage-collected"
);
browser.test.sendMessage("done");
},
});
await extension.startup();
await extension.awaitMessage("done");
await extension.unload();
});