Bug 1594754 - Make target-list support all workers in the browser toolbox. r=jdescottes

For now, this is only an equivalent of the existing listAllWorkerTargets.
But thanks to it using the TargetList API to listen to processes,
it can fetch only the workers from the newly-created processes
or only for the process which have their worker list updated.
Follow-up would be necessary to:
* optionally listen for SW in the content toolbox codepath
* expose SW Registrations?

Differential Revision: https://phabricator.services.mozilla.com/D63854

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Alexandre Poirot 2020-03-11 12:34:43 +00:00
Родитель 313e12e535
Коммит 3b193b3c3b
5 изменённых файлов: 159 добавлений и 41 удалений

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

@ -16,9 +16,11 @@ const CONTENTTOOLBOX_FISSION_ENABLED = "devtools.contenttoolbox.fission";
// methods directly on the target fronts. This code would then
// become the backward compatibility code which we could later remove.
class LegacyImplementationProcesses {
constructor(rootFront, target, onTargetAvailable, onTargetDestroyed) {
this.rootFront = rootFront;
this.target = target;
constructor(targetList, onTargetAvailable, onTargetDestroyed) {
this.targetList = targetList;
this.rootFront = targetList.rootFront;
this.target = targetList.targetFront;
this.onTargetAvailable = onTargetAvailable;
this.onTargetDestroyed = onTargetDestroyed;
@ -76,9 +78,11 @@ class LegacyImplementationProcesses {
}
class LegacyImplementationFrames {
constructor(rootFront, target, onTargetAvailable) {
this.rootFront = rootFront;
this.target = target;
constructor(targetList, onTargetAvailable) {
this.targetList = targetList;
this.rootFront = targetList.rootFront;
this.target = targetList.targetFront;
this.onTargetAvailable = onTargetAvailable;
}
@ -116,36 +120,95 @@ class LegacyImplementationFrames {
unlisten() {}
}
// Note that in case we need to listen for all type of workers,
// devtools/client/shared/workers-listener.js already implements such listening.
class LegacyImplementationWorkers {
constructor(rootFront, target, onTargetAvailable, onTargetDestroyed) {
this.rootFront = rootFront;
this.target = target;
constructor(targetList, onTargetAvailable, onTargetDestroyed) {
this.targetList = targetList;
this.rootFront = targetList.rootFront;
this.target = targetList.targetFront;
this.onTargetAvailable = onTargetAvailable;
this.onTargetDestroyed = onTargetDestroyed;
this.targets = new Set();
this._workerListChanged = this._workerListChanged.bind(this);
this.targetsByProcess = new WeakMap();
this.targetsListeners = new WeakMap();
this._onProcessAvailable = this._onProcessAvailable.bind(this);
this._onProcessDestroyed = this._onProcessDestroyed.bind(this);
}
async _workerListChanged() {
const { workers } = await this.target.listWorkers();
async _onProcessAvailable({ targetFront }) {
this.targetsByProcess.set(targetFront, new Set());
// 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
await this._workerListChanged(targetFront);
}
async _onProcessDestroyed({ targetFront }) {
const existingTargets = this.targetsByProcess.get(targetFront);
// Process the new list to detect the ones being destroyed
// Force destroying the targets
for (const target of this.targets) {
for (const target of existingTargets) {
this.onTargetDestroyed(target);
target.destroy();
existingTargets.delete(target);
}
this.targetsByProcess.delete(targetFront);
this.targetsListeners.delete(targetFront);
}
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());
}
// Fetch the list of already existing worker targets for this process target front.
const existingTargets = this.targetsByProcess.get(targetFront);
// Process the new list to detect the ones being destroyed
// Force destroying the targets
for (const target of existingTargets) {
if (!workers.includes(target)) {
this.onTargetDestroyed(target);
target.destroy();
this.targets.delete(target);
existingTargets.delete(target);
}
}
const promises = workers
.filter(workerTarget => !this.targets.has(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)
.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
this.targets.add(workerTarget);
existingTargets.add(workerTarget);
await this.onTargetAvailable(workerTarget);
});
@ -153,12 +216,47 @@ class LegacyImplementationWorkers {
}
async listen() {
this.target.on("workerListChanged", this._workerListChanged);
await this._workerListChanged();
if (this.target.isParentProcess) {
await this.targetList.watchTargets(
[TargetList.TYPES.PROCESS],
this._onProcessAvailable,
this._onProcessDestroyed
);
// The ParentProcessTarget front is considered to be a FRAME instead of a PROCESS.
// So process it manually here.
await this._onProcessAvailable({ targetFront: this.target });
} else {
this.targetsByProcess.set(this.target, new Set());
this._workerListChangedListener = this._workerListChanged.bind(
this,
this.target
);
this.target.on("workerListChanged", this._workerListChangedListener);
await this._workerListChanged(this.target);
}
}
unlisten() {
this.target.off("workerListChanged", this._workerListChanged);
if (this.target.isParentProcess) {
for (const targetFront of this.targetList.getAllTargets(
TargetList.TYPES.PROCESS
)) {
const listener = this.targetsListeners.get(targetFront);
targetFront.off("workerListChanged", listener);
this.targetsByProcess.delete(targetFront);
this.targetsListeners.delete(targetFront);
}
this.targetList.unwatchTargets(
[TargetList.TYPES.PROCESS],
this._onProcessAvailable,
this._onProcessDestroyed
);
} else {
this.target.off("workerListChanged", this._workerListChangedListener);
delete this._workerListChangedListener;
this.targetsByProcess.delete(this.target);
this.targetsListeners.delete(this.target);
}
}
}
@ -203,20 +301,17 @@ class TargetList {
this.legacyImplementation = {
process: new LegacyImplementationProcesses(
this.rootFront,
this.targetFront,
this,
this._onTargetAvailable,
this._onTargetDestroyed
),
frame: new LegacyImplementationFrames(
this.rootFront,
this.targetFront,
this,
this._onTargetAvailable,
this._onTargetDestroyed
),
worker: new LegacyImplementationWorkers(
this.rootFront,
this.targetFront,
this,
this._onTargetAvailable,
this._onTargetDestroyed
),

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

@ -7,6 +7,7 @@ support-files =
head.js
fission_document.html
fission_iframe.html
test_service_worker.js
test_worker.js
[browser_target_list_frames.js]

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

@ -7,9 +7,10 @@
const { TargetList } = require("devtools/shared/resources/target-list");
const FISSION_TEST_URL = URL_ROOT + "fission_document.html";
const FISSION_TEST_URL = URL_ROOT_SSL + "fission_document.html";
const CHROME_WORKER_URL = CHROME_URL_ROOT + "test_worker.js";
const WORKER_URL = URL_ROOT + "test_worker.js";
const WORKER_URL = URL_ROOT_SSL + "test_worker.js";
const SERVICE_WORKER_URL = URL_ROOT_SSL + "test_service_worker.js";
add_task(async function() {
// Enabled fission's pref as the TargetList is almost disabled without it
@ -22,8 +23,11 @@ add_task(async function() {
const client = await createLocalClient();
const mainRoot = client.mainRoot;
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
const tab = await addTab(FISSION_TEST_URL);
await testBrowserWorkers(mainRoot);
await testTabWorkers(mainRoot);
await testTabWorkers(mainRoot, tab);
await client.close();
});
@ -33,7 +37,9 @@ async function testBrowserWorkers(mainRoot) {
// Instantiate a worker in the parent process
// eslint-disable-next-line no-unused-vars
const worker = new Worker(CHROME_WORKER_URL);
const worker = new Worker(CHROME_WORKER_URL + "#simple-worker");
// eslint-disable-next-line no-unused-vars
const sharedWorker = new SharedWorker(CHROME_WORKER_URL + "#shared-worker");
const targetDescriptor = await mainRoot.getMainProcess();
const target = await targetDescriptor.getTarget();
@ -43,10 +49,20 @@ async function testBrowserWorkers(mainRoot) {
// Very naive sanity check against getAllTargets(worker)
const workers = await targetList.getAllTargets(TargetList.TYPES.WORKER);
const hasWorker = workers.find(workerTarget => {
return workerTarget.url == CHROME_WORKER_URL;
return workerTarget.url == CHROME_WORKER_URL + "#simple-worker";
});
ok(hasWorker, "retrieve the target for the worker");
const hasSharedWorker = workers.find(workerTarget => {
return workerTarget.url == CHROME_WORKER_URL + "#shared-worker";
});
ok(hasSharedWorker, "retrieve the target for the shared worker");
const hasServiceWorker = workers.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);
is(workers2.length, workers.length, "retrieved the same number of workers");
@ -62,7 +78,7 @@ async function testBrowserWorkers(mainRoot) {
// Assert that watchTargets will call the create callback for all existing workers
const targets = [];
const onAvailable = ({ type, targetFront, isTopLevel }) => {
const onAvailable = async ({ type, targetFront, isTopLevel }) => {
is(
type,
TargetList.TYPES.WORKER,
@ -94,7 +110,7 @@ async function testBrowserWorkers(mainRoot) {
// Create a new worker and see if the worker target is reported
const onWorkerCreated = new Promise(resolve => {
const onAvailable2 = ({ type, targetFront, isTopLevel }) => {
const onAvailable2 = async ({ type, targetFront, isTopLevel }) => {
if (targets.includes(targetFront)) {
return;
}
@ -105,6 +121,7 @@ async function testBrowserWorkers(mainRoot) {
});
// eslint-disable-next-line no-unused-vars
const worker2 = new Worker(CHROME_WORKER_URL + "#second");
info("Wait for the second worker to be created");
const workerTarget = await onWorkerCreated;
is(
@ -122,12 +139,10 @@ async function testBrowserWorkers(mainRoot) {
targetList.stopListening();
}
async function testTabWorkers(mainRoot) {
async function testTabWorkers(mainRoot, tab) {
info("Test TargetList against workers via a tab target");
// Create a TargetList for a given test tab
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
const tab = await addTab(FISSION_TEST_URL);
const target = await mainRoot.getTab({ tab });
// Ensure attaching the target as BrowsingContextTargetActor.listWorkers
@ -150,7 +165,7 @@ async function testTabWorkers(mainRoot) {
// Assert that watchTargets will call the create callback for all existing workers
const targets = [];
const onAvailable = ({ type, targetFront, isTopLevel }) => {
const onAvailable = async ({ type, targetFront, isTopLevel }) => {
is(
type,
TargetList.TYPES.WORKER,

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

@ -9,11 +9,13 @@
<body>
<p>Test fission iframe</p>
<iframe src="http://example.com/browser/devtools/shared/resources/tests/fission_iframe.html"></iframe>
<iframe src="https://example.com/browser/devtools/shared/resources/tests/fission_iframe.html"></iframe>
<script>
"use strict";
// eslint-disable-next-line no-unused-vars
const worker = new Worker("http://example.com/browser/devtools/shared/resources/tests/test_worker.js");
const worker = new Worker("https://example.com/browser/devtools/shared/resources/tests/test_worker.js");
navigator.serviceWorker.register("https://example.com/browser/devtools/shared/resources/tests/test_service_worker.js#service-worker");
</script>
</body>
</html>

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

@ -0,0 +1,5 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// We don't need any computation in the worker,
// just it to be alive