Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"use strict";
// Test the TargetCommand API around processes
const TEST_URL =
"data:text/html;charset=utf-8," + encodeURIComponent(`<div id="test"></div>`);
add_task(async function () {
// Enabled fission's pref as the TargetCommand is almost disabled without it
await pushPref("devtools.browsertoolbox.scope", "everything");
// Disable the preloaded process as it gets created lazily and may interfere
// with process count assertions
await pushPref("dom.ipc.processPrelaunch.enabled", false);
// This preference helps destroying the content process when we close the tab
await pushPref("dom.ipc.keepProcessesAlive.web", 1);
const commands = await CommandsFactory.forMainProcess();
const targetCommand = commands.targetCommand;
await targetCommand.startListening();
await testProcesses(targetCommand, targetCommand.targetFront);
targetCommand.destroy();
// Wait for all the targets to be fully attached so we don't have pending requests.
await Promise.all(
targetCommand.getAllTargets(targetCommand.ALL_TYPES).map(t => t.initialized)
);
await commands.destroy();
});
add_task(async function () {
const commands = await CommandsFactory.forMainProcess();
const targetCommand = commands.targetCommand;
await targetCommand.startListening();
const created = [];
const destroyed = [];
const onAvailable = ({ targetFront }) => {
created.push(targetFront);
};
const onDestroyed = ({ targetFront }) => {
destroyed.push(targetFront);
};
await targetCommand.watchTargets({
types: [targetCommand.TYPES.PROCESS],
onAvailable,
onDestroyed,
});
Assert.greater(created.length, 1, "We get many content process targets");
targetCommand.stopListening();
await waitFor(
() => created.length == destroyed.length,
"Wait for the destruction of all content process targets when calling stopListening"
);
is(
created.length,
destroyed.length,
"Got notification of destruction for all previously reported targets"
);
targetCommand.destroy();
// Wait for all the targets to be fully attached so we don't have pending requests.
await Promise.all(
targetCommand.getAllTargets(targetCommand.ALL_TYPES).map(t => t.initialized)
);
await commands.destroy();
});
async function testProcesses(targetCommand, target) {
info("Test TargetCommand against processes");
const { TYPES } = targetCommand;
// Note that ppmm also includes the parent process, which is considered as a frame rather than a process
const originalProcessesCount = Services.ppmm.childCount - 1;
const processes = await targetCommand.getAllTargets([TYPES.PROCESS]);
is(
processes.length,
originalProcessesCount,
"Get a target for all content processes"
);
const processes2 = await targetCommand.getAllTargets([TYPES.PROCESS]);
is(
processes2.length,
originalProcessesCount,
"retrieved the same number of processes"
);
function sortFronts(f1, f2) {
return f1.actorID < f2.actorID;
}
processes.sort(sortFronts);
processes2.sort(sortFronts);
for (let i = 0; i < processes.length; i++) {
is(processes[i], processes2[i], `process ${i} targets are the same`);
}
// Assert that watchTargets will call the create callback for all existing frames
const targets = new Set();
const pidRegExp = /^\d+$/;
const onAvailable = ({ targetFront }) => {
if (targets.has(targetFront)) {
ok(false, "The same target is notified multiple times via onAvailable");
}
is(
targetFront.targetType,
TYPES.PROCESS,
"We are only notified about process targets"
);
ok(
targetFront == target ? targetFront.isTopLevel : !targetFront.isTopLevel,
"isTopLevel property is correct"
);
ok(
pidRegExp.test(targetFront.processID),
`Target has processID of expected shape (${targetFront.processID})`
);
targets.add(targetFront);
};
const onDestroyed = ({ targetFront }) => {
if (!targets.has(targetFront)) {
ok(
false,
"A target is declared destroyed via onDestroy without being notified via onAvailable"
);
}
is(
targetFront.targetType,
TYPES.PROCESS,
"We are only notified about process targets"
);
ok(
!targetFront.isTopLevel,
"We are never notified about the top level target destruction"
);
targets.delete(targetFront);
};
await targetCommand.watchTargets({
types: [TYPES.PROCESS],
onAvailable,
onDestroyed,
});
is(
targets.size,
originalProcessesCount,
"retrieved the same number of processes via watchTargets"
);
for (let i = 0; i < processes.length; i++) {
ok(
targets.has(processes[i]),
`process ${i} targets are the same via watchTargets`
);
}
const previousTargets = new Set(targets);
// Assert that onAvailable is called for processes created *after* the call to watchTargets
const onProcessCreated = new Promise(resolve => {
const onAvailable2 = ({ targetFront }) => {
if (previousTargets.has(targetFront)) {
return;
}
targetCommand.unwatchTargets({
types: [TYPES.PROCESS],
onAvailable: onAvailable2,
});
resolve(targetFront);
};
targetCommand.watchTargets({
types: [TYPES.PROCESS],
onAvailable: onAvailable2,
});
});
info("open new tab in new process");
const tab1 = await BrowserTestUtils.openNewForegroundTab({
gBrowser,
url: TEST_URL,
forceNewProcess: true,
});
info("wait for process target to be created");
const createdTarget = await onProcessCreated;
// For some reason, creating a new tab purges processes created from previous tests
// so it is not reasonable to assert the size of `targets` as it may be lower than expected.
ok(targets.has(createdTarget), "The new tab process is in the list");
const processCountAfterTabOpen = targets.size;
// Assert that onDestroy is called for destroyed processes
const onProcessDestroyed = new Promise(resolve => {
const onAvailable3 = () => {};
const onDestroyed3 = ({ targetFront }) => {
resolve(targetFront);
targetCommand.unwatchTargets({
types: [TYPES.PROCESS],
onAvailable: onAvailable3,
onDestroyed: onDestroyed3,
});
};
targetCommand.watchTargets({
types: [TYPES.PROCESS],
onAvailable: onAvailable3,
onDestroyed: onDestroyed3,
});
});
BrowserTestUtils.removeTab(tab1);
const destroyedTarget = await onProcessDestroyed;
is(
targets.size,
processCountAfterTabOpen - 1,
"The closed tab's process has been reported as destroyed"
);
ok(
!targets.has(destroyedTarget),
"The destroyed target is no longer in the list"
);
is(
destroyedTarget,
createdTarget,
"The destroyed target is the one that has been reported as created"
);
targetCommand.unwatchTargets({
types: [TYPES.PROCESS],
onAvailable,
onDestroyed,
});
// Ensure that getAllTargets still works after the call to unwatchTargets
const processes3 = await targetCommand.getAllTargets([TYPES.PROCESS]);
is(
processes3.length,
processCountAfterTabOpen - 1,
"getAllTargets reports a new target"
);
}