Bug 1594750 - Distinguish workers in the TargetList. r=jdescottes.

Differential Revision: https://phabricator.services.mozilla.com/D73803
This commit is contained in:
Nicolas Chevobbe 2020-05-11 07:13:57 +00:00
Родитель dde23f5029
Коммит db468890fa
4 изменённых файлов: 195 добавлений и 90 удалений

Просмотреть файл

@ -47,6 +47,14 @@ class WorkerTargetFront extends TargetMixin(
return this.url.split("/").pop();
}
get isDedicatedWorker() {
return this.type === Ci.nsIWorkerDebugger.TYPE_DEDICATED;
}
get isSharedWorker() {
return this.type === Ci.nsIWorkerDebugger.TYPE_SHARED;
}
get isServiceWorker() {
return this.type === Ci.nsIWorkerDebugger.TYPE_SERVICE;
}

Просмотреть файл

@ -63,7 +63,11 @@ class EvaluationContextSelector extends Component {
return "resource://devtools/client/debugger/images/globe-small.svg";
}
if (target.type === TARGET_TYPES.WORKER) {
if (
target.type === TARGET_TYPES.WORKER ||
target.type === TARGET_TYPES.SHARED_WORKER ||
target.type === TARGET_TYPES.SERVICE_WORKER
) {
return "resource://devtools/client/debugger/images/worker.svg";
}
@ -103,12 +107,16 @@ class EvaluationContextSelector extends Component {
let mainTarget;
const frames = [];
const contentProcesses = [];
const workers = [];
const dedicatedWorkers = [];
const sharedWorkers = [];
const serviceWorkers = [];
const dict = {
[TARGET_TYPES.FRAME]: frames,
[TARGET_TYPES.PROCESS]: contentProcesses,
[TARGET_TYPES.WORKER]: workers,
[TARGET_TYPES.WORKER]: dedicatedWorkers,
[TARGET_TYPES.SHARED_WORKER]: sharedWorkers,
[TARGET_TYPES.SERVICE_WORKER]: serviceWorkers,
};
for (const target of targets) {
@ -123,25 +131,13 @@ class EvaluationContextSelector extends Component {
const items = [mainTarget];
if (frames.length > 0) {
items.push(
MenuItem({ role: "menuseparator", key: "frames-separator" }),
...frames
);
}
if (contentProcesses.length > 0) {
items.push(
MenuItem({ role: "menuseparator", key: "process-separator" }),
...contentProcesses
);
}
if (workers.length > 0) {
items.push(
MenuItem({ role: "menuseparator", key: "worker-separator" }),
...workers
);
for (const [targetType, menuItems] of Object.entries(dict)) {
if (menuItems.length > 0) {
items.push(
MenuItem({ role: "menuseparator", key: `${targetType}-separator` }),
...menuItems
);
}
}
return MenuList(

Просмотреть файл

@ -143,14 +143,13 @@ class LegacyImplementationWorkers {
// Listen for worker which will be created later
const listener = this._workerListChanged.bind(this, targetFront);
this.targetsListeners.set(targetFront, listener);
// If this is the browser toolbox, we have to listen from the RootFront
// (see comment in _workerListChanged)
if (targetFront.isParentProcess) {
this.rootFront.on("workerListChanged", listener);
} else {
targetFront.on("workerListChanged", listener);
}
// But also process the already existing ones
const front = targetFront.isParentProcess ? this.rootFront : targetFront;
front.on("workerListChanged", listener);
// We also need to process the already existing workers
await this._workerListChanged(targetFront);
}
@ -169,19 +168,28 @@ class LegacyImplementationWorkers {
this.targetsListeners.delete(targetFront);
}
_supportWorkerTarget(workerTarget) {
// subprocess workers are ignored because they take several seconds to
// attach to when opening the browser toolbox. See bug 1594597.
// When attaching we get the following error:
// JavaScript error: resource://devtools/server/startup/worker.js,
// line 37: NetworkError: WorkerDebuggerGlobalScope.loadSubScript: Failed to load worker script at resource://devtools/shared/worker/loader.js (nsresult = 0x805e0006)
return (
workerTarget.isDedicatedWorker &&
!workerTarget.url.startsWith(
"resource://gre/modules/subprocess/subprocess_worker"
)
);
}
async _workerListChanged(targetFront) {
let workers;
// Browser Toolbox codepath
if (targetFront.isParentProcess) {
// Query workers from the Root Front instead of the ParentProcessTarget
// as the ParentProcess Target filters out the workers to only show the one from the top level window,
// whereas we expect the one from all the windows, and also the window-less ones.
({ workers } = await this.rootFront.listWorkers());
} else {
// Content Toolbox codepath
//TODO: expose SW of the page, maybe optionally?
({ workers } = await targetFront.listWorkers());
}
// If we're in the Browser Toolbox, query workers from the Root Front instead of the
// ParentProcessTarget as the ParentProcess Target filters out the workers to only
// show the one from the top level window, whereas we expect the one from all the
// windows, and also the window-less ones.
// TODO: For Content Toolbox, expose SW of the page, maybe optionally?
const front = targetFront.isParentProcess ? this.rootFront : targetFront;
const { workers } = await front.listWorkers();
// Fetch the list of already existing worker targets for this process target front.
const existingTargets = this.targetsByProcess.get(targetFront);
@ -196,23 +204,20 @@ class LegacyImplementationWorkers {
existingTargets.delete(target);
}
}
const promises = workers
// subprocess workers are ignored because they take several seconds to
// attach to when opening the browser toolbox. See bug 1594597.
// When attaching we get the following error:
// JavaScript error: resource://devtools/server/startup/worker.js, line 37: NetworkError: WorkerDebuggerGlobalScope.loadSubScript: Failed to load worker script at resource://devtools/shared/worker/loader.js (nsresult = 0x805e0006)
.filter(
workerTarget =>
!workerTarget.url.startsWith(
"resource://gre/modules/subprocess/subprocess_worker"
)
)
.filter(workerTarget => !existingTargets.has(workerTarget))
.map(async workerTarget => {
// Add the new worker targets to the local list
existingTargets.add(workerTarget);
await this.onTargetAvailable(workerTarget);
});
const promises = [];
for (const workerTarget of workers) {
if (
!this._supportWorkerTarget(workerTarget) ||
existingTargets.has(workerTarget)
) {
continue;
}
// Add the new worker targets to the local list
existingTargets.add(workerTarget);
promises.push(this.onTargetAvailable(workerTarget));
}
await Promise.all(promises);
}
@ -262,6 +267,18 @@ class LegacyImplementationWorkers {
}
}
class LegacyImplementationSharedWorkers extends LegacyImplementationWorkers {
_supportWorkerTarget(workerTarget) {
return workerTarget.isSharedWorker;
}
}
class LegacyImplementationServiceWorkers extends LegacyImplementationWorkers {
_supportWorkerTarget(workerTarget) {
return workerTarget.isServiceWorker;
}
}
class TargetList {
/**
* This class helps managing, iterating over and listening for Targets.
@ -324,6 +341,16 @@ class TargetList {
this._onTargetAvailable,
this._onTargetDestroyed
),
shared_worker: new LegacyImplementationSharedWorkers(
this,
this._onTargetAvailable,
this._onTargetDestroyed
),
service_worker: new LegacyImplementationServiceWorkers(
this,
this._onTargetAvailable,
this._onTargetDestroyed
),
};
// Public flag to allow listening for workers even if the fission pref is off
@ -414,6 +441,18 @@ class TargetList {
if (this.listenForWorkers && !types.includes(TargetList.TYPES.WORKER)) {
types.push(TargetList.TYPES.WORKER);
}
if (
this.listenForWorkers &&
!types.includes(TargetList.TYPES.SHARED_WORKER)
) {
types.push(TargetList.TYPES.SHARED_WORKER);
}
if (
this.listenForWorkers &&
!types.includes(TargetList.TYPES.SERVICE_WORKER)
) {
types.push(TargetList.TYPES.SERVICE_WORKER);
}
// If no pref are set to true, nor is listenForWorkers set to true,
// we won't listen for any additional target. Only the top level target
// will be managed. We may still do target-switching.
@ -474,14 +513,27 @@ class TargetList {
const { typeName } = target;
if (typeName == "browsingContextTarget") {
return TargetList.TYPES.FRAME;
} else if (
}
if (
typeName == "contentProcessTarget" ||
typeName == "parentProcessTarget"
) {
return TargetList.TYPES.PROCESS;
} else if (typeName == "workerTarget") {
}
if (typeName == "workerTarget") {
if (target.isSharedWorker) {
return TargetList.TYPES.SHARED_WORKER;
}
if (target.isServiceWorker) {
return TargetList.TYPES.SERVICE_WORKER;
}
return TargetList.TYPES.WORKER;
}
throw new Error("Unsupported target typeName: " + typeName);
}
@ -645,6 +697,8 @@ TargetList.TYPES = TargetList.prototype.TYPES = {
PROCESS: "process",
FRAME: "frame",
WORKER: "worker",
SHARED_WORKER: "shared_worker",
SERVICE_WORKER: "service_worker",
};
TargetList.ALL_TYPES = TargetList.prototype.ALL_TYPES = Object.values(
TargetList.TYPES

Просмотреть файл

@ -6,6 +6,7 @@
// Test the TargetList API around workers
const { TargetList } = require("devtools/shared/resources/target-list");
const { TYPES } = TargetList;
const FISSION_TEST_URL = URL_ROOT_SSL + "fission_document.html";
const CHROME_WORKER_URL = CHROME_URL_ROOT + "test_worker.js";
@ -46,42 +47,78 @@ async function testBrowserWorkers(mainRoot) {
const targetList = new TargetList(mainRoot, target);
await targetList.startListening();
// Very naive sanity check against getAllTargets(worker)
const workers = await targetList.getAllTargets(TargetList.TYPES.WORKER);
// Very naive sanity check against getAllTargets(workerType)
info("Check that getAllTargets returned the expected targets");
const workers = await targetList.getAllTargets(TYPES.WORKER);
const hasWorker = workers.find(workerTarget => {
return workerTarget.url == CHROME_WORKER_URL + "#simple-worker";
});
ok(hasWorker, "retrieve the target for the worker");
const hasSharedWorker = workers.find(workerTarget => {
const sharedWorkers = await targetList.getAllTargets(TYPES.SHARED_WORKER);
const hasSharedWorker = sharedWorkers.find(workerTarget => {
return workerTarget.url == CHROME_WORKER_URL + "#shared-worker";
});
ok(hasSharedWorker, "retrieve the target for the shared worker");
const hasServiceWorker = workers.find(workerTarget => {
const serviceWorkers = await targetList.getAllTargets(TYPES.SERVICE_WORKER);
const hasServiceWorker = serviceWorkers.find(workerTarget => {
return workerTarget.url == SERVICE_WORKER_URL;
});
ok(hasServiceWorker, "retrieve the target for the service worker");
// Check that calling getAllTargets(worker) return the same target instances
const workers2 = await targetList.getAllTargets(TargetList.TYPES.WORKER);
info(
"Check that calling getAllTargets again return the same target instances"
);
const workers2 = await targetList.getAllTargets(TYPES.WORKER);
const sharedWorkers2 = await targetList.getAllTargets(TYPES.SHARED_WORKER);
const serviceWorkers2 = await targetList.getAllTargets(TYPES.SERVICE_WORKER);
is(workers2.length, workers.length, "retrieved the same number of workers");
is(
sharedWorkers2.length,
sharedWorkers.length,
"retrieved the same number of shared workers"
);
is(
serviceWorkers2.length,
serviceWorkers.length,
"retrieved the same number of service workers"
);
function sortFronts(f1, f2) {
return f1.actorID < f2.actorID;
}
workers.sort(sortFronts);
workers2.sort(sortFronts);
sharedWorkers.sort(sortFronts);
sharedWorkers2.sort(sortFronts);
serviceWorkers.sort(sortFronts);
serviceWorkers2.sort(sortFronts);
for (let i = 0; i < workers.length; i++) {
is(workers[i], workers2[i], `worker ${i} targets are the same`);
}
for (let i = 0; i < sharedWorkers2.length; i++) {
is(
sharedWorkers[i],
sharedWorkers2[i],
`shared worker ${i} targets are the same`
);
}
for (let i = 0; i < serviceWorkers2.length; i++) {
is(
serviceWorkers[i],
serviceWorkers2[i],
`service worker ${i} targets are the same`
);
}
// Assert that watchTargets will call the create callback for all existing workers
info(
"Check that watchTargets will call the create callback for all existing workers"
);
const targets = [];
const onAvailable = async ({ type, targetFront, isTopLevel }) => {
is(
type,
TargetList.TYPES.WORKER,
ok(
type === TYPES.WORKER ||
type === TYPES.SHARED_WORKER ||
type === TYPES.SERVICE_WORKER,
"We are only notified about worker targets"
);
ok(
@ -90,23 +127,33 @@ async function testBrowserWorkers(mainRoot) {
);
targets.push(targetFront);
};
await targetList.watchTargets([TargetList.TYPES.WORKER], onAvailable);
await targetList.watchTargets(
[TYPES.WORKER, TYPES.SHARED_WORKER, TYPES.SERVICE_WORKER],
onAvailable
);
is(
targets.length,
workers.length,
workers.length + sharedWorkers.length + serviceWorkers.length,
"retrieved the same number of workers via watchTargets"
);
workers.sort(sortFronts);
targets.sort(sortFronts);
for (let i = 0; i < workers.length; i++) {
const allWorkers = workers
.concat(sharedWorkers, serviceWorkers)
.sort(sortFronts);
for (let i = 0; i < allWorkers.length; i++) {
is(
workers[i],
allWorkers[i],
targets[i],
`worker ${i} targets are the same via watchTargets`
);
}
targetList.unwatchTargets([TargetList.TYPES.WORKER], onAvailable);
targetList.unwatchTargets(
[TYPES.WORKER, TYPES.SHARED_WORKER, TYPES.SERVICE_WORKER],
onAvailable
);
// Create a new worker and see if the worker target is reported
const onWorkerCreated = new Promise(resolve => {
@ -114,10 +161,10 @@ async function testBrowserWorkers(mainRoot) {
if (targets.includes(targetFront)) {
return;
}
targetList.unwatchTargets([TargetList.TYPES.WORKER], onAvailable2);
targetList.unwatchTargets([TYPES.WORKER], onAvailable2);
resolve(targetFront);
};
targetList.watchTargets([TargetList.TYPES.WORKER], onAvailable2);
targetList.watchTargets([TYPES.WORKER], onAvailable2);
});
// eslint-disable-next-line no-unused-vars
const worker2 = new Worker(CHROME_WORKER_URL + "#second");
@ -130,9 +177,9 @@ async function testBrowserWorkers(mainRoot) {
"This worker target is about the new worker"
);
const workers3 = await targetList.getAllTargets(TargetList.TYPES.WORKER);
const workers3 = await targetList.getAllTargets(TYPES.WORKER);
const hasWorker2 = workers3.find(
worderTarget => workerTarget.url == CHROME_WORKER_URL + "#second"
({ url }) => url == `${CHROME_WORKER_URL}#second`
);
ok(hasWorker2, "retrieve the target for tab via getAllTargets");
@ -160,27 +207,27 @@ async function testTabWorkers(mainRoot, tab) {
await targetList.startListening();
// Check that calling getAllTargets(workers) return the same target instances
const workers = await targetList.getAllTargets(TargetList.TYPES.WORKER);
const workers = await targetList.getAllTargets(TYPES.WORKER);
is(workers.length, 1, "retrieved the worker");
is(workers[0].url, WORKER_URL, "The first worker is the page worker");
// Assert that watchTargets will call the create callback for all existing workers
const targets = [];
const onAvailable = async ({ type, targetFront, isTopLevel }) => {
is(
type,
TargetList.TYPES.WORKER,
"We are only notified about worker targets"
);
is(type, TYPES.WORKER, "We are only notified about worker targets");
ok(!isTopLevel, "The workers are never top level");
targets.push(targetFront);
};
await targetList.watchTargets([TargetList.TYPES.WORKER], onAvailable);
await targetList.watchTargets([TYPES.WORKER], onAvailable);
is(targets.length, 1, "retrieved just the worker");
is(targets[0], workers[0], "Got the exact same target front");
targetList.unwatchTargets([TargetList.TYPES.WORKER], onAvailable);
targetList.unwatchTargets([TYPES.WORKER], onAvailable);
targetList.stopListening();
BrowserTestUtils.removeTab(tab);
}
function sortFronts(f1, f2) {
return f1.actorID < f2.actorID;
}