From b0621c9d5ee88dcd2e1e5d48eea006fc7ff7e95d Mon Sep 17 00:00:00 2001 From: Julian Descottes Date: Mon, 11 May 2020 14:32:24 +0000 Subject: [PATCH] Bug 1636507 - Extract legacy watcher implementation to dedicated files r=ochameau,nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D74419 --- .../legacy-frames-watcher.js | 52 ++++ .../legacy-processes-watcher.js | 69 +++++ .../legacy-serviceworkers-watcher.js | 17 + .../legacy-sharedworkers-watcher.js | 17 + .../legacy-workers-watcher.js | 159 ++++++++++ .../legacy-target-watchers/moz.build | 11 + devtools/shared/resources/moz.build | 3 +- devtools/shared/resources/target-list.js | 293 ++---------------- 8 files changed, 347 insertions(+), 274 deletions(-) create mode 100644 devtools/shared/resources/legacy-target-watchers/legacy-frames-watcher.js create mode 100644 devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher.js create mode 100644 devtools/shared/resources/legacy-target-watchers/legacy-serviceworkers-watcher.js create mode 100644 devtools/shared/resources/legacy-target-watchers/legacy-sharedworkers-watcher.js create mode 100644 devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js create mode 100644 devtools/shared/resources/legacy-target-watchers/moz.build diff --git a/devtools/shared/resources/legacy-target-watchers/legacy-frames-watcher.js b/devtools/shared/resources/legacy-target-watchers/legacy-frames-watcher.js new file mode 100644 index 000000000000..836f8340b6a0 --- /dev/null +++ b/devtools/shared/resources/legacy-target-watchers/legacy-frames-watcher.js @@ -0,0 +1,52 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// Bug 1593937 made this code only used to support FF76- and can be removed +// once FF77 reach release channel. +class LegacyFramesWatcher { + constructor(targetList, onTargetAvailable) { + this.targetList = targetList; + this.rootFront = targetList.rootFront; + this.target = targetList.targetFront; + + this.onTargetAvailable = onTargetAvailable; + } + + async listen() { + // Note that even if we are calling listRemoteFrames on `this.target`, this ends up + // being forwarded to the RootFront. So that the Descriptors are managed + // by RootFront. + // TODO: support frame listening. For now, this only fetches already existing targets + const { frames } = await this.target.listRemoteFrames(); + + const promises = frames + .filter( + // As we listen for frameDescriptor's on the RootFront, we get + // all the frames and not only the one related to the given `target`. + // TODO: support deeply nested frames + descriptor => + descriptor.parentID == this.target.browsingContextID || + descriptor.id == this.target.browsingContextID + ) + .map(async descriptor => { + const target = await descriptor.getTarget(); + if (!target) { + console.error( + "Wasn't able to retrieve the target for", + descriptor.actorID + ); + return; + } + await this.onTargetAvailable(target); + }); + + await Promise.all(promises); + } + + unlisten() {} +} + +module.exports = { LegacyFramesWatcher }; diff --git a/devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher.js b/devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher.js new file mode 100644 index 000000000000..76472a5a593d --- /dev/null +++ b/devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher.js @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +class LegacyProcessesWatcher { + constructor(targetList, onTargetAvailable, onTargetDestroyed) { + this.targetList = targetList; + this.rootFront = targetList.rootFront; + this.target = targetList.targetFront; + + this.onTargetAvailable = onTargetAvailable; + this.onTargetDestroyed = onTargetDestroyed; + + this.descriptors = new Set(); + this._processListChanged = this._processListChanged.bind(this); + } + + async _processListChanged() { + const processes = await this.rootFront.listProcesses(); + // Process the new list to detect the ones being destroyed + // Force destroyed the descriptor as well as the target + for (const descriptor of this.descriptors) { + if (!processes.includes(descriptor)) { + // Manually call onTargetDestroyed listeners in order to + // ensure calling them *before* destroying the descriptor. + // Otherwise the descriptor will automatically destroy the target + // and may not fire the contentProcessTarget's destroy event. + const target = descriptor.getCachedTarget(); + if (target) { + this.onTargetDestroyed(target); + } + + descriptor.destroy(); + this.descriptors.delete(descriptor); + } + } + + const promises = processes + .filter(descriptor => !this.descriptors.has(descriptor)) + .map(async descriptor => { + // Add the new process descriptors to the local list + this.descriptors.add(descriptor); + const target = await descriptor.getTarget(); + if (!target) { + console.error( + "Wasn't able to retrieve the target for", + descriptor.actorID + ); + return; + } + await this.onTargetAvailable(target); + }); + + await Promise.all(promises); + } + + async listen() { + this.rootFront.on("processListChanged", this._processListChanged); + await this._processListChanged(); + } + + unlisten() { + this.rootFront.off("processListChanged", this._processListChanged); + } +} + +module.exports = { LegacyProcessesWatcher }; diff --git a/devtools/shared/resources/legacy-target-watchers/legacy-serviceworkers-watcher.js b/devtools/shared/resources/legacy-target-watchers/legacy-serviceworkers-watcher.js new file mode 100644 index 000000000000..32412c86cc21 --- /dev/null +++ b/devtools/shared/resources/legacy-target-watchers/legacy-serviceworkers-watcher.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + LegacyWorkersWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher"); + +class LegacyServiceWorkersWatcher extends LegacyWorkersWatcher { + _supportWorkerTarget(workerTarget) { + return workerTarget.isServiceWorker; + } +} + +module.exports = { LegacyServiceWorkersWatcher }; diff --git a/devtools/shared/resources/legacy-target-watchers/legacy-sharedworkers-watcher.js b/devtools/shared/resources/legacy-target-watchers/legacy-sharedworkers-watcher.js new file mode 100644 index 000000000000..9eba8451bb59 --- /dev/null +++ b/devtools/shared/resources/legacy-target-watchers/legacy-sharedworkers-watcher.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + LegacyWorkersWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher"); + +class LegacySharedWorkersWatcher extends LegacyWorkersWatcher { + _supportWorkerTarget(workerTarget) { + return workerTarget.isSharedWorker; + } +} + +module.exports = { LegacySharedWorkersWatcher }; diff --git a/devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js b/devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js new file mode 100644 index 000000000000..8b96cac98e0f --- /dev/null +++ b/devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js @@ -0,0 +1,159 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +loader.lazyRequireGetter( + this, + "TargetList", + "devtools/shared/resources/target-list", + true +); + +class LegacyWorkersWatcher { + constructor(targetList, onTargetAvailable, onTargetDestroyed) { + this.targetList = targetList; + this.rootFront = targetList.rootFront; + this.target = targetList.targetFront; + + this.onTargetAvailable = onTargetAvailable; + this.onTargetDestroyed = onTargetDestroyed; + + this.targetsByProcess = new WeakMap(); + this.targetsListeners = new WeakMap(); + + this._onProcessAvailable = this._onProcessAvailable.bind(this); + this._onProcessDestroyed = this._onProcessDestroyed.bind(this); + } + + 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) + const front = targetFront.isParentProcess ? this.rootFront : targetFront; + front.on("workerListChanged", listener); + + // We also need to process the already existing workers + 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 existingTargets) { + this.onTargetDestroyed(target); + + target.destroy(); + existingTargets.delete(target); + } + this.targetsByProcess.delete(targetFront); + 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) { + // 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); + + // 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(); + existingTargets.delete(target); + } + } + + 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); + } + + async listen() { + 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() { + 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); + } + } +} + +module.exports = { LegacyWorkersWatcher }; diff --git a/devtools/shared/resources/legacy-target-watchers/moz.build b/devtools/shared/resources/legacy-target-watchers/moz.build new file mode 100644 index 000000000000..25566563e691 --- /dev/null +++ b/devtools/shared/resources/legacy-target-watchers/moz.build @@ -0,0 +1,11 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DevToolsModules( + 'legacy-frames-watcher.js', + 'legacy-processes-watcher.js', + 'legacy-serviceworkers-watcher.js', + 'legacy-sharedworkers-watcher.js', + 'legacy-workers-watcher.js', +) diff --git a/devtools/shared/resources/moz.build b/devtools/shared/resources/moz.build index 80f4850b17ec..1a3ff8a72176 100644 --- a/devtools/shared/resources/moz.build +++ b/devtools/shared/resources/moz.build @@ -3,7 +3,8 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. DIRS += [ - 'legacy-listeners' + 'legacy-listeners', + 'legacy-target-watchers', ] DevToolsModules( diff --git a/devtools/shared/resources/target-list.js b/devtools/shared/resources/target-list.js index f3cf98a16be5..aa275d9ffa44 100644 --- a/devtools/shared/resources/target-list.js +++ b/devtools/shared/resources/target-list.js @@ -10,274 +10,21 @@ const EventEmitter = require("devtools/shared/event-emitter"); const BROWSERTOOLBOX_FISSION_ENABLED = "devtools.browsertoolbox.fission"; const CONTENTTOOLBOX_FISSION_ENABLED = "devtools.contenttoolbox.fission"; -// Intermediate components which implement the watch + unwatch -// using existing listFoo methods and fooListChanged events. -// The plan here is to followup to implement listen and unlisten -// methods directly on the target fronts. This code would then -// become the backward compatibility code which we could later remove. -class LegacyImplementationProcesses { - constructor(targetList, onTargetAvailable, onTargetDestroyed) { - this.targetList = targetList; - this.rootFront = targetList.rootFront; - this.target = targetList.targetFront; - - this.onTargetAvailable = onTargetAvailable; - this.onTargetDestroyed = onTargetDestroyed; - - this.descriptors = new Set(); - this._processListChanged = this._processListChanged.bind(this); - } - - async _processListChanged() { - const processes = await this.rootFront.listProcesses(); - // Process the new list to detect the ones being destroyed - // Force destroyed the descriptor as well as the target - for (const descriptor of this.descriptors) { - if (!processes.includes(descriptor)) { - // Manually call onTargetDestroyed listeners in order to - // ensure calling them *before* destroying the descriptor. - // Otherwise the descriptor will automatically destroy the target - // and may not fire the contentProcessTarget's destroy event. - const target = descriptor.getCachedTarget(); - if (target) { - this.onTargetDestroyed(target); - } - - descriptor.destroy(); - this.descriptors.delete(descriptor); - } - } - - const promises = processes - .filter(descriptor => !this.descriptors.has(descriptor)) - .map(async descriptor => { - // Add the new process descriptors to the local list - this.descriptors.add(descriptor); - const target = await descriptor.getTarget(); - if (!target) { - console.error( - "Wasn't able to retrieve the target for", - descriptor.actorID - ); - return; - } - await this.onTargetAvailable(target); - }); - - await Promise.all(promises); - } - - async listen() { - this.rootFront.on("processListChanged", this._processListChanged); - await this._processListChanged(); - } - - unlisten() { - this.rootFront.off("processListChanged", this._processListChanged); - } -} - -// Bug 1593937 made this code only used to support FF76- and can be removed -// once FF77 reach release channel. -class LegacyImplementationFrames { - constructor(targetList, onTargetAvailable) { - this.targetList = targetList; - this.rootFront = targetList.rootFront; - this.target = targetList.targetFront; - - this.onTargetAvailable = onTargetAvailable; - } - - async listen() { - // Note that even if we are calling listRemoteFrames on `this.target`, this ends up - // being forwarded to the RootFront. So that the Descriptors are managed - // by RootFront. - // TODO: support frame listening. For now, this only fetches already existing targets - const { frames } = await this.target.listRemoteFrames(); - - const promises = frames - .filter( - // As we listen for frameDescriptor's on the RootFront, we get - // all the frames and not only the one related to the given `target`. - // TODO: support deeply nested frames - descriptor => - descriptor.parentID == this.target.browsingContextID || - descriptor.id == this.target.browsingContextID - ) - .map(async descriptor => { - const target = await descriptor.getTarget(); - if (!target) { - console.error( - "Wasn't able to retrieve the target for", - descriptor.actorID - ); - return; - } - await this.onTargetAvailable(target); - }); - - await Promise.all(promises); - } - - unlisten() {} -} - -class LegacyImplementationWorkers { - constructor(targetList, onTargetAvailable, onTargetDestroyed) { - this.targetList = targetList; - this.rootFront = targetList.rootFront; - this.target = targetList.targetFront; - - this.onTargetAvailable = onTargetAvailable; - this.onTargetDestroyed = onTargetDestroyed; - - this.targetsByProcess = new WeakMap(); - this.targetsListeners = new WeakMap(); - - this._onProcessAvailable = this._onProcessAvailable.bind(this); - this._onProcessDestroyed = this._onProcessDestroyed.bind(this); - } - - 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) - const front = targetFront.isParentProcess ? this.rootFront : targetFront; - front.on("workerListChanged", listener); - - // We also need to process the already existing workers - 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 existingTargets) { - this.onTargetDestroyed(target); - - target.destroy(); - existingTargets.delete(target); - } - this.targetsByProcess.delete(targetFront); - 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) { - // 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); - - // 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(); - existingTargets.delete(target); - } - } - - 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); - } - - async listen() { - 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() { - 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); - } - } -} - -class LegacyImplementationSharedWorkers extends LegacyImplementationWorkers { - _supportWorkerTarget(workerTarget) { - return workerTarget.isSharedWorker; - } -} - -class LegacyImplementationServiceWorkers extends LegacyImplementationWorkers { - _supportWorkerTarget(workerTarget) { - return workerTarget.isServiceWorker; - } -} +const { + LegacyFramesWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-frames-watcher"); +const { + LegacyProcessesWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher"); +const { + LegacyServiceWorkersWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-serviceworkers-watcher"); +const { + LegacySharedWorkersWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-sharedworkers-watcher"); +const { + LegacyWorkersWatcher, +} = require("devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher"); class TargetList { /** @@ -328,27 +75,27 @@ class TargetList { this._onTargetDestroyed = this._onTargetDestroyed.bind(this); this.legacyImplementation = { - process: new LegacyImplementationProcesses( + process: new LegacyProcessesWatcher( this, this._onTargetAvailable, this._onTargetDestroyed ), - frame: new LegacyImplementationFrames( + frame: new LegacyFramesWatcher( this, this._onTargetAvailable, this._onTargetDestroyed ), - worker: new LegacyImplementationWorkers( + worker: new LegacyWorkersWatcher( this, this._onTargetAvailable, this._onTargetDestroyed ), - shared_worker: new LegacyImplementationSharedWorkers( + shared_worker: new LegacySharedWorkersWatcher( this, this._onTargetAvailable, this._onTargetDestroyed ), - service_worker: new LegacyImplementationServiceWorkers( + service_worker: new LegacyServiceWorkersWatcher( this, this._onTargetAvailable, this._onTargetDestroyed