summaryrefslogtreecommitdiffstats
path: root/devtools/shared/commands/target/legacy-target-watchers
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/shared/commands/target/legacy-target-watchers
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/shared/commands/target/legacy-target-watchers')
-rw-r--r--devtools/shared/commands/target/legacy-target-watchers/legacy-processes-watcher.js72
-rw-r--r--devtools/shared/commands/target/legacy-target-watchers/legacy-serviceworkers-watcher.js316
-rw-r--r--devtools/shared/commands/target/legacy-target-watchers/legacy-sharedworkers-watcher.js19
-rw-r--r--devtools/shared/commands/target/legacy-target-watchers/legacy-workers-watcher.js238
-rw-r--r--devtools/shared/commands/target/legacy-target-watchers/moz.build10
5 files changed, 655 insertions, 0 deletions
diff --git a/devtools/shared/commands/target/legacy-target-watchers/legacy-processes-watcher.js b/devtools/shared/commands/target/legacy-target-watchers/legacy-processes-watcher.js
new file mode 100644
index 0000000000..e0c5b18d51
--- /dev/null
+++ b/devtools/shared/commands/target/legacy-target-watchers/legacy-processes-watcher.js
@@ -0,0 +1,72 @@
+/* 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(targetCommand, onTargetAvailable, onTargetDestroyed) {
+ this.targetCommand = targetCommand;
+ this.rootFront = targetCommand.rootFront;
+
+ this.onTargetAvailable = onTargetAvailable;
+ this.onTargetDestroyed = onTargetDestroyed;
+
+ this.descriptors = new Set();
+ this._processListChanged = this._processListChanged.bind(this);
+ }
+
+ async _processListChanged() {
+ if (this.targetCommand.isDestroyed()) {
+ return;
+ }
+
+ 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/commands/target/legacy-target-watchers/legacy-serviceworkers-watcher.js b/devtools/shared/commands/target/legacy-target-watchers/legacy-serviceworkers-watcher.js
new file mode 100644
index 0000000000..adaeb9def4
--- /dev/null
+++ b/devtools/shared/commands/target/legacy-target-watchers/legacy-serviceworkers-watcher.js
@@ -0,0 +1,316 @@
+/* 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 {
+ WorkersListener,
+ // eslint-disable-next-line mozilla/reject-some-requires
+} = require("resource://devtools/client/shared/workers-listener.js");
+
+const LegacyWorkersWatcher = require("resource://devtools/shared/commands/target/legacy-target-watchers/legacy-workers-watcher.js");
+
+class LegacyServiceWorkersWatcher extends LegacyWorkersWatcher {
+ // Holds the current target URL object
+ #currentTargetURL;
+
+ constructor(targetCommand, onTargetAvailable, onTargetDestroyed, commands) {
+ super(targetCommand, onTargetAvailable, onTargetDestroyed);
+ this._registrations = [];
+ this._processTargets = new Set();
+ this.commands = commands;
+
+ // We need to listen for registration changes at least in order to properly
+ // filter service workers by domain when debugging a local tab.
+ //
+ // A WorkerTarget instance has a url property, but it points to the url of
+ // the script, whereas the url property of the ServiceWorkerRegistration
+ // points to the URL controlled by the service worker.
+ //
+ // Historically we have been matching the service worker registration URL
+ // to match service workers for local tab tools (app panel & debugger).
+ // Maybe here we could have some more info on the actual worker.
+ this._workersListener = new WorkersListener(this.rootFront, {
+ registrationsOnly: true,
+ });
+
+ // Note that this is called much more often than when a registration
+ // is created or destroyed. WorkersListener notifies of anything that
+ // potentially impacted workers.
+ // I use it as a shortcut in this first patch. Listening to rootFront's
+ // "serviceWorkerRegistrationListChanged" should be enough to be notified
+ // about registrations. And if we need to also update the
+ // "debuggerServiceWorkerStatus" from here, then we would have to
+ // also listen to "registration-changed" one each registration.
+ this._onRegistrationListChanged =
+ this._onRegistrationListChanged.bind(this);
+ this._onDocumentEvent = this._onDocumentEvent.bind(this);
+
+ // Flag used from the parent class to listen to process targets.
+ // Decision tree is complicated, keep all logic in the parent methods.
+ this._isServiceWorkerWatcher = true;
+ }
+
+ /**
+ * Override from LegacyWorkersWatcher.
+ *
+ * We record all valid service worker targets (ie workers that match a service
+ * worker registration), but we will only notify about the ones which match
+ * the current domain.
+ */
+ _recordWorkerTarget(workerTarget) {
+ return !!this._getRegistrationForWorkerTarget(workerTarget);
+ }
+
+ // Override from LegacyWorkersWatcher.
+ _supportWorkerTarget(workerTarget) {
+ if (!workerTarget.isServiceWorker) {
+ return false;
+ }
+
+ const registration = this._getRegistrationForWorkerTarget(workerTarget);
+ return registration && this._isRegistrationValidForTarget(registration);
+ }
+
+ // Override from LegacyWorkersWatcher.
+ async listen() {
+ // Listen to the current target front.
+ this.target = this.targetCommand.targetFront;
+
+ if (this.targetCommand.descriptorFront.isTabDescriptor) {
+ this.#currentTargetURL = new URL(this.targetCommand.targetFront.url);
+ }
+
+ this._workersListener.addListener(this._onRegistrationListChanged);
+
+ // Fetch the registrations before calling listen, since service workers
+ // might already be available and will need to be compared with the existing
+ // registrations.
+ await this._onRegistrationListChanged();
+
+ if (this.targetCommand.descriptorFront.isTabDescriptor) {
+ await this.commands.resourceCommand.watchResources(
+ [this.commands.resourceCommand.TYPES.DOCUMENT_EVENT],
+ {
+ onAvailable: this._onDocumentEvent,
+ ignoreExistingResources: true,
+ }
+ );
+ }
+
+ await super.listen();
+ }
+
+ // Override from LegacyWorkersWatcher.
+ unlisten(...args) {
+ this._workersListener.removeListener(this._onRegistrationListChanged);
+
+ if (this.targetCommand.descriptorFront.isTabDescriptor) {
+ this.commands.resourceCommand.unwatchResources(
+ [this.commands.resourceCommand.TYPES.DOCUMENT_EVENT],
+ {
+ onAvailable: this._onDocumentEvent,
+ }
+ );
+ }
+
+ super.unlisten(...args);
+ }
+
+ // Override from LegacyWorkersWatcher.
+ async _onProcessAvailable({ targetFront }) {
+ if (this.targetCommand.descriptorFront.isTabDescriptor) {
+ // XXX: This has been ported straight from the current debugger
+ // implementation. Since pauseMatchingServiceWorkers expects an origin
+ // to filter matching workers, it only makes sense when we are debugging
+ // a tab. However in theory, parent process debugging could pause all
+ // service workers without matching anything.
+ try {
+ // To support early breakpoint we need to setup the
+ // `pauseMatchingServiceWorkers` mechanism in each process.
+ await targetFront.pauseMatchingServiceWorkers({
+ origin: this.#currentTargetURL.origin,
+ });
+ } catch (e) {
+ if (targetFront.actorID) {
+ throw e;
+ } else {
+ console.warn(
+ "Process target destroyed while calling pauseMatchingServiceWorkers"
+ );
+ }
+ }
+ }
+
+ this._processTargets.add(targetFront);
+ return super._onProcessAvailable({ targetFront });
+ }
+
+ _shouldDestroyTargetsOnNavigation() {
+ return !!this.targetCommand.destroyServiceWorkersOnNavigation;
+ }
+
+ _onProcessDestroyed({ targetFront }) {
+ this._processTargets.delete(targetFront);
+ return super._onProcessDestroyed({ targetFront });
+ }
+
+ _onDocumentEvent(resources) {
+ for (const resource of resources) {
+ if (
+ resource.resourceType !==
+ this.commands.resourceCommand.TYPES.DOCUMENT_EVENT
+ ) {
+ continue;
+ }
+
+ if (resource.name === "will-navigate") {
+ // We rely on will-navigate as the onTargetAvailable for the top-level frame can
+ // happen after the onTargetAvailable for processes (handled in _onProcessAvailable),
+ // where we need the origin we navigate to.
+ this.#currentTargetURL = new URL(resource.newURI);
+ continue;
+ }
+
+ // Note that we rely on "dom-loading" rather than "will-navigate" because the
+ // destroyed/available callbacks should be triggered after the Debugger
+ // has cleaned up its reducers, which happens on "will-navigate".
+ // On the other end, "dom-complete", which is a better mapping of "navigate", is
+ // happening too late (because of resources being throttled), and would cause failures
+ // in test (like browser_target_command_service_workers_navigation.js), as the new worker
+ // target would already be registered at this point, and seen as something that would
+ // need to be destroyed.
+ if (resource.name === "dom-loading") {
+ const allServiceWorkerTargets = this._getAllServiceWorkerTargets();
+ const shouldDestroy = this._shouldDestroyTargetsOnNavigation();
+
+ for (const target of allServiceWorkerTargets) {
+ const isRegisteredBefore =
+ this.targetCommand.isTargetRegistered(target);
+ if (shouldDestroy && isRegisteredBefore) {
+ // Instruct the target command to notify about the worker target destruction
+ // but do not destroy the front as we want to keep using it.
+ // We will notify about it again via onTargetAvailable.
+ this.onTargetDestroyed(target, { shouldDestroyTargetFront: false });
+ }
+
+ // Note: we call isTargetRegistered again because calls to
+ // onTargetDestroyed might have modified the list of registered targets.
+ const isRegisteredAfter =
+ this.targetCommand.isTargetRegistered(target);
+ const isValidTarget = this._supportWorkerTarget(target);
+ if (isValidTarget && !isRegisteredAfter) {
+ // If the target is still valid for the current top target, call
+ // onTargetAvailable as well.
+ this.onTargetAvailable(target);
+ }
+ }
+ }
+ }
+ }
+
+ async _onRegistrationListChanged() {
+ if (this.targetCommand.isDestroyed()) {
+ return;
+ }
+
+ await this._updateRegistrations();
+
+ // Everything after this point is not strictly necessary for sw support
+ // in the target list, but it makes the behavior closer to the previous
+ // listAllWorkers/WorkersListener pair.
+ const allServiceWorkerTargets = this._getAllServiceWorkerTargets();
+ for (const target of allServiceWorkerTargets) {
+ const hasRegistration = this._getRegistrationForWorkerTarget(target);
+ if (!hasRegistration) {
+ // XXX: At this point the worker target is not really destroyed, but
+ // historically, listAllWorkers* APIs stopped returning worker targets
+ // if worker registrations are no longer available.
+ if (this.targetCommand.isTargetRegistered(target)) {
+ // Only emit onTargetDestroyed if it wasn't already done by
+ // onNavigate (ie the target is still tracked by TargetCommand)
+ this.onTargetDestroyed(target);
+ }
+ // Here we only care about service workers which no longer match *any*
+ // registration. The worker will be completely destroyed soon, remove
+ // it from the legacy worker watcher internal targetsByProcess Maps.
+ this._removeTargetReferences(target);
+ }
+ }
+ }
+
+ // Delete the provided worker target from the internal targetsByProcess Maps.
+ _removeTargetReferences(target) {
+ const allProcessTargets = this._getProcessTargets().filter(t =>
+ this.targetsByProcess.get(t)
+ );
+
+ for (const processTarget of allProcessTargets) {
+ this.targetsByProcess.get(processTarget).delete(target);
+ }
+ }
+
+ async _updateRegistrations() {
+ const { registrations } =
+ await this.rootFront.listServiceWorkerRegistrations();
+
+ this._registrations = registrations;
+ }
+
+ _getRegistrationForWorkerTarget(workerTarget) {
+ return this._registrations.find(r => {
+ return (
+ r.evaluatingWorker?.id === workerTarget.id ||
+ r.activeWorker?.id === workerTarget.id ||
+ r.installingWorker?.id === workerTarget.id ||
+ r.waitingWorker?.id === workerTarget.id
+ );
+ });
+ }
+
+ _getProcessTargets() {
+ return [...this._processTargets];
+ }
+
+ // Flatten all service worker targets in all processes.
+ _getAllServiceWorkerTargets() {
+ const allProcessTargets = this._getProcessTargets().filter(target =>
+ this.targetsByProcess.get(target)
+ );
+
+ const serviceWorkerTargets = [];
+ for (const target of allProcessTargets) {
+ serviceWorkerTargets.push(...this.targetsByProcess.get(target));
+ }
+ return serviceWorkerTargets;
+ }
+
+ // Check if the registration is relevant for the current target, ie
+ // corresponds to the same domain.
+ _isRegistrationValidForTarget(registration) {
+ if (this.targetCommand.descriptorFront.isBrowserProcessDescriptor) {
+ // All registrations are valid for main process debugging.
+ return true;
+ }
+
+ if (!this.targetCommand.descriptorFront.isTabDescriptor) {
+ // No support for service worker targets outside of main process &
+ // tab debugging.
+ return false;
+ }
+
+ // For local tabs, we match ServiceWorkerRegistrations and the target
+ // if they share the same hostname for their "url" properties.
+ const targetDomain = this.#currentTargetURL.hostname;
+ try {
+ const registrationDomain = new URL(registration.url).hostname;
+ return registrationDomain === targetDomain;
+ } catch (e) {
+ // XXX: Some registrations have an empty URL.
+ return false;
+ }
+ }
+}
+
+module.exports = LegacyServiceWorkersWatcher;
diff --git a/devtools/shared/commands/target/legacy-target-watchers/legacy-sharedworkers-watcher.js b/devtools/shared/commands/target/legacy-target-watchers/legacy-sharedworkers-watcher.js
new file mode 100644
index 0000000000..b248e6aef7
--- /dev/null
+++ b/devtools/shared/commands/target/legacy-target-watchers/legacy-sharedworkers-watcher.js
@@ -0,0 +1,19 @@
+/* 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("resource://devtools/shared/commands/target/legacy-target-watchers/legacy-workers-watcher.js");
+
+class LegacySharedWorkersWatcher extends LegacyWorkersWatcher {
+ // Flag used from the parent class to listen to process targets.
+ // Decision tree is complicated, keep all logic in the parent methods.
+ _isSharedWorkerWatcher = true;
+
+ _supportWorkerTarget(workerTarget) {
+ return workerTarget.isSharedWorker;
+ }
+}
+
+module.exports = LegacySharedWorkersWatcher;
diff --git a/devtools/shared/commands/target/legacy-target-watchers/legacy-workers-watcher.js b/devtools/shared/commands/target/legacy-target-watchers/legacy-workers-watcher.js
new file mode 100644
index 0000000000..0baa14757b
--- /dev/null
+++ b/devtools/shared/commands/target/legacy-target-watchers/legacy-workers-watcher.js
@@ -0,0 +1,238 @@
+/* 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 LegacyProcessesWatcher = require("resource://devtools/shared/commands/target/legacy-target-watchers/legacy-processes-watcher.js");
+
+class LegacyWorkersWatcher {
+ constructor(targetCommand, onTargetAvailable, onTargetDestroyed) {
+ this.targetCommand = targetCommand;
+ this.rootFront = targetCommand.rootFront;
+
+ 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;
+ if (!front || front.isDestroyed() || this.targetCommand.isDestroyed()) {
+ return;
+ }
+
+ let workers;
+ try {
+ ({ workers } = await front.listWorkers());
+ } catch (e) {
+ // Workers may be added/removed at anytime so that listWorkers request
+ // can be spawn during a toolbox destroy sequence and easily fail
+ if (front.isDestroyed()) {
+ return;
+ }
+ throw e;
+ }
+
+ // Fetch the list of already existing worker targets for this process target front.
+ const existingTargets = this.targetsByProcess.get(targetFront);
+ if (!existingTargets) {
+ // unlisten was called while processing the workerListChanged callback.
+ return;
+ }
+
+ // 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 = workers.map(workerTarget =>
+ this._processNewWorkerTarget(workerTarget, existingTargets)
+ );
+ await Promise.all(promises);
+ }
+
+ // This is overloaded for Service Workers, which records all SW targets,
+ // but only notify about a subset of them.
+ _recordWorkerTarget(workerTarget) {
+ return this._supportWorkerTarget(workerTarget);
+ }
+
+ async _processNewWorkerTarget(workerTarget, existingTargets) {
+ if (
+ !this._recordWorkerTarget(workerTarget) ||
+ existingTargets.has(workerTarget) ||
+ this.targetCommand.isDestroyed()
+ ) {
+ return;
+ }
+
+ // Add the new worker targets to the local list
+ existingTargets.add(workerTarget);
+
+ if (this._supportWorkerTarget(workerTarget)) {
+ await this.onTargetAvailable(workerTarget);
+ }
+ }
+
+ async listen() {
+ // Listen to the current target front.
+ this.target = this.targetCommand.targetFront;
+
+ if (this.target.isParentProcess) {
+ await this.targetCommand.watchTargets({
+ types: [this.targetCommand.TYPES.PROCESS],
+ onAvailable: this._onProcessAvailable,
+ onDestroyed: 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 });
+ return;
+ }
+
+ if (this._isSharedWorkerWatcher) {
+ // Here we're not in the browser toolbox, and SharedWorker targets are not supported
+ // in regular toolbox (See Bug 1607778)
+ return;
+ }
+
+ if (this._isServiceWorkerWatcher) {
+ this._legacyProcessesWatcher = new LegacyProcessesWatcher(
+ this.targetCommand,
+ async targetFront => {
+ // Service workers only live in content processes.
+ if (!targetFront.isParentProcess) {
+ await this._onProcessAvailable({ targetFront });
+ }
+ },
+ targetFront => {
+ if (!targetFront.isParentProcess) {
+ this._onProcessDestroyed({ targetFront });
+ }
+ }
+ );
+ await this._legacyProcessesWatcher.listen();
+ return;
+ }
+
+ // Here, we're handling Dedicated Workers in content toolbox.
+ this.targetsByProcess.set(
+ this.target,
+ this.targetsByProcess.get(this.target) || new Set()
+ );
+ this._workerListChangedListener = this._workerListChanged.bind(
+ this,
+ this.target
+ );
+ this.target.on("workerListChanged", this._workerListChangedListener);
+ await this._workerListChanged(this.target);
+ }
+
+ _getProcessTargets() {
+ return this.targetCommand.getAllTargets([this.targetCommand.TYPES.PROCESS]);
+ }
+
+ unlisten({ isTargetSwitching } = {}) {
+ // Stop listening for new process targets.
+ if (this.target.isParentProcess) {
+ this.targetCommand.unwatchTargets({
+ types: [this.targetCommand.TYPES.PROCESS],
+ onAvailable: this._onProcessAvailable,
+ onDestroyed: this._onProcessDestroyed,
+ });
+ } else if (this._isServiceWorkerWatcher) {
+ this._legacyProcessesWatcher.unlisten();
+ }
+
+ // Cleanup the targetsByProcess/targetsListeners maps, and unsubscribe from
+ // all targetFronts. Process target fronts are either stored locally when
+ // watching service workers for the content toolbox, or can be retrieved via
+ // the TargetCommand API otherwise (see _getProcessTargets implementations).
+ if (this.target.isParentProcess || this._isServiceWorkerWatcher) {
+ for (const targetFront of this._getProcessTargets()) {
+ const listener = this.targetsListeners.get(targetFront);
+ targetFront.off("workerListChanged", listener);
+
+ // When unlisten is called from a target switch and service workers targets are not
+ // destroyed on navigation, we don't want to remove the targets from targetsByProcess
+ if (
+ !isTargetSwitching ||
+ !this._isServiceWorkerWatcher ||
+ this.targetCommand.destroyServiceWorkersOnNavigation
+ ) {
+ this.targetsByProcess.delete(targetFront);
+ }
+ this.targetsListeners.delete(targetFront);
+ }
+ } 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/commands/target/legacy-target-watchers/moz.build b/devtools/shared/commands/target/legacy-target-watchers/moz.build
new file mode 100644
index 0000000000..60fdd7ec22
--- /dev/null
+++ b/devtools/shared/commands/target/legacy-target-watchers/moz.build
@@ -0,0 +1,10 @@
+# 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-processes-watcher.js",
+ "legacy-serviceworkers-watcher.js",
+ "legacy-sharedworkers-watcher.js",
+ "legacy-workers-watcher.js",
+)