summaryrefslogtreecommitdiffstats
path: root/devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js225
1 files changed, 225 insertions, 0 deletions
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 0000000000..0b0b07a897
--- /dev/null
+++ b/devtools/shared/resources/legacy-target-watchers/legacy-workers-watcher.js
@@ -0,0 +1,225 @@
+/* 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
+);
+
+const {
+ LegacyProcessesWatcher,
+} = require("devtools/shared/resources/legacy-target-watchers/legacy-processes-watcher");
+
+class LegacyWorkersWatcher {
+ constructor(targetList, onTargetAvailable, onTargetDestroyed) {
+ this.targetList = targetList;
+ this.rootFront = targetList.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.targetList.isDestroyed()) {
+ return;
+ }
+
+ const { workers } = await front.listWorkers();
+
+ // 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.targetList.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.targetList.targetFront;
+
+ 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 });
+ 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.targetList,
+ 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, new Set());
+ this._workerListChangedListener = this._workerListChanged.bind(
+ this,
+ this.target
+ );
+ this.target.on("workerListChanged", this._workerListChangedListener);
+ await this._workerListChanged(this.target);
+ }
+
+ _getProcessTargets() {
+ return this.targetList.getAllTargets([TargetList.TYPES.PROCESS]);
+ }
+
+ unlisten() {
+ // Stop listening for new process targets.
+ if (this.target.isParentProcess) {
+ this.targetList.unwatchTargets(
+ [TargetList.TYPES.PROCESS],
+ this._onProcessAvailable,
+ 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 TargetList 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);
+ 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 };