summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/worker
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/worker')
-rw-r--r--devtools/server/actors/worker/moz.build14
-rw-r--r--devtools/server/actors/worker/push-subscription.js41
-rw-r--r--devtools/server/actors/worker/service-worker-process.js41
-rw-r--r--devtools/server/actors/worker/service-worker-registration-list.js113
-rw-r--r--devtools/server/actors/worker/service-worker-registration.js335
-rw-r--r--devtools/server/actors/worker/service-worker.js45
-rw-r--r--devtools/server/actors/worker/worker-descriptor-actor-list.js212
7 files changed, 801 insertions, 0 deletions
diff --git a/devtools/server/actors/worker/moz.build b/devtools/server/actors/worker/moz.build
new file mode 100644
index 0000000000..4c9023879b
--- /dev/null
+++ b/devtools/server/actors/worker/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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(
+ "push-subscription.js",
+ "service-worker-process.js",
+ "service-worker-registration-list.js",
+ "service-worker-registration.js",
+ "service-worker.js",
+ "worker-descriptor-actor-list.js",
+)
diff --git a/devtools/server/actors/worker/push-subscription.js b/devtools/server/actors/worker/push-subscription.js
new file mode 100644
index 0000000000..d2fa904383
--- /dev/null
+++ b/devtools/server/actors/worker/push-subscription.js
@@ -0,0 +1,41 @@
+/* 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 protocol = require("devtools/shared/protocol");
+const {
+ pushSubscriptionSpec,
+} = require("devtools/shared/specs/worker/push-subscription");
+
+const PushSubscriptionActor = protocol.ActorClassWithSpec(
+ pushSubscriptionSpec,
+ {
+ initialize(conn, subscription) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this._subscription = subscription;
+ },
+
+ form() {
+ const subscription = this._subscription;
+
+ // Note: subscription.pushCount & subscription.lastPush are no longer
+ // returned here because the corresponding getters throw on GeckoView.
+ // Since they were not used in DevTools they were removed from the
+ // actor in Bug 1637687. If they are reintroduced, make sure to provide
+ // meaningful fallback values when debugging a GeckoView runtime.
+ return {
+ actor: this.actorID,
+ endpoint: subscription.endpoint,
+ quota: subscription.quota,
+ };
+ },
+
+ destroy() {
+ this._subscription = null;
+ protocol.Actor.prototype.destroy.call(this);
+ },
+ }
+);
+exports.PushSubscriptionActor = PushSubscriptionActor;
diff --git a/devtools/server/actors/worker/service-worker-process.js b/devtools/server/actors/worker/service-worker-process.js
new file mode 100644
index 0000000000..8d492ab5cd
--- /dev/null
+++ b/devtools/server/actors/worker/service-worker-process.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+/* global addMessageListener */
+
+"use strict";
+
+/*
+ * Process script used to control service workers via a DevTools actor.
+ * Loaded into content processes by the service worker actors.
+ */
+
+const swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+ Ci.nsIServiceWorkerManager
+);
+
+addMessageListener("serviceWorkerRegistration:start", message => {
+ const { data } = message;
+ const array = swm.getAllRegistrations();
+
+ // Find the service worker registration with the desired scope.
+ for (let i = 0; i < array.length; i++) {
+ const registration = array.queryElementAt(
+ i,
+ Ci.nsIServiceWorkerRegistrationInfo
+ );
+ // XXX: In some rare cases, `registration.activeWorker` can be null for a
+ // brief moment (e.g. while the service worker is first installing, or if
+ // there was an unhandled exception during install that will cause the
+ // registration to be removed). We can't do much about it here, simply
+ // ignore these cases.
+ if (registration.scope === data.scope && registration.activeWorker) {
+ // Briefly attaching a debugger to the active service worker will cause
+ // it to start running.
+ registration.activeWorker.attachDebugger();
+ registration.activeWorker.detachDebugger();
+ return;
+ }
+ }
+});
diff --git a/devtools/server/actors/worker/service-worker-registration-list.js b/devtools/server/actors/worker/service-worker-registration-list.js
new file mode 100644
index 0000000000..e0b6939a90
--- /dev/null
+++ b/devtools/server/actors/worker/service-worker-registration-list.js
@@ -0,0 +1,113 @@
+/* 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 { Ci } = require("chrome");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+loader.lazyRequireGetter(
+ this,
+ "ServiceWorkerRegistrationActor",
+ "devtools/server/actors/worker/service-worker-registration",
+ true
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "swm",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+function ServiceWorkerRegistrationActorList(conn) {
+ this._conn = conn;
+ this._actors = new Map();
+ this._onListChanged = null;
+ this._mustNotify = false;
+ this.onRegister = this.onRegister.bind(this);
+ this.onUnregister = this.onUnregister.bind(this);
+}
+
+ServiceWorkerRegistrationActorList.prototype = {
+ getList() {
+ // Create a set of registrations.
+ const registrations = new Set();
+ const array = swm.getAllRegistrations();
+ for (let index = 0; index < array.length; ++index) {
+ registrations.add(
+ array.queryElementAt(index, Ci.nsIServiceWorkerRegistrationInfo)
+ );
+ }
+
+ // Delete each actor for which we don't have a registration.
+ for (const [registration] of this._actors) {
+ if (!registrations.has(registration)) {
+ this._actors.delete(registration);
+ }
+ }
+
+ // Create an actor for each registration for which we don't have one.
+ for (const registration of registrations) {
+ if (!this._actors.has(registration)) {
+ this._actors.set(
+ registration,
+ new ServiceWorkerRegistrationActor(this._conn, registration)
+ );
+ }
+ }
+
+ if (!this._mustNotify) {
+ if (this._onListChanged !== null) {
+ swm.addListener(this);
+ }
+ this._mustNotify = true;
+ }
+
+ const actors = [];
+ for (const [, actor] of this._actors) {
+ actors.push(actor);
+ }
+
+ return Promise.resolve(actors);
+ },
+
+ get onListchanged() {
+ return this._onListchanged;
+ },
+
+ set onListChanged(onListChanged) {
+ if (typeof onListChanged !== "function" && onListChanged !== null) {
+ throw new Error("onListChanged must be either a function or null.");
+ }
+
+ if (this._mustNotify) {
+ if (this._onListChanged === null && onListChanged !== null) {
+ swm.addListener(this);
+ }
+ if (this._onListChanged !== null && onListChanged === null) {
+ swm.removeListener(this);
+ }
+ }
+ this._onListChanged = onListChanged;
+ },
+
+ _notifyListChanged() {
+ this._onListChanged();
+
+ if (this._onListChanged !== null) {
+ swm.removeListener(this);
+ }
+ this._mustNotify = false;
+ },
+
+ onRegister(registration) {
+ this._notifyListChanged();
+ },
+
+ onUnregister(registration) {
+ this._notifyListChanged();
+ },
+};
+
+exports.ServiceWorkerRegistrationActorList = ServiceWorkerRegistrationActorList;
diff --git a/devtools/server/actors/worker/service-worker-registration.js b/devtools/server/actors/worker/service-worker-registration.js
new file mode 100644
index 0000000000..bb6361f94b
--- /dev/null
+++ b/devtools/server/actors/worker/service-worker-registration.js
@@ -0,0 +1,335 @@
+/* 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 ChromeUtils = require("ChromeUtils");
+const Services = require("Services");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const protocol = require("devtools/shared/protocol");
+const {
+ serviceWorkerRegistrationSpec,
+} = require("devtools/shared/specs/worker/service-worker-registration");
+const {
+ PushSubscriptionActor,
+} = require("devtools/server/actors/worker/push-subscription");
+const {
+ ServiceWorkerActor,
+} = require("devtools/server/actors/worker/service-worker");
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "swm",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "PushService",
+ "@mozilla.org/push/Service;1",
+ "nsIPushService"
+);
+
+// Lazily load the service-worker-process.js process script only once.
+let _serviceWorkerProcessScriptLoaded = false;
+
+const ServiceWorkerRegistrationActor = protocol.ActorClassWithSpec(
+ serviceWorkerRegistrationSpec,
+ {
+ /**
+ * Create the ServiceWorkerRegistrationActor
+ * @param DevToolsServerConnection conn
+ * The server connection.
+ * @param ServiceWorkerRegistrationInfo registration
+ * The registration's information.
+ */
+ initialize(conn, registration) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this._conn = conn;
+ this._registration = registration;
+ this._pushSubscriptionActor = null;
+
+ // A flag to know if preventShutdown has been called and we should
+ // try to allow the shutdown of the SW when the actor is destroyed
+ this._preventedShutdown = false;
+
+ this._registration.addListener(this);
+
+ this._createServiceWorkerActors();
+
+ Services.obs.addObserver(this, PushService.subscriptionModifiedTopic);
+ },
+
+ onChange() {
+ this._destroyServiceWorkerActors();
+ this._createServiceWorkerActors();
+ this.emit("registration-changed");
+ },
+
+ form() {
+ const registration = this._registration;
+ const evaluatingWorker = this._evaluatingWorker.form();
+ const installingWorker = this._installingWorker.form();
+ const waitingWorker = this._waitingWorker.form();
+ const activeWorker = this._activeWorker.form();
+
+ const newestWorker =
+ activeWorker || waitingWorker || installingWorker || evaluatingWorker;
+
+ const isMultiE10sWithOldImplementation =
+ Services.appinfo.browserTabsRemoteAutostart &&
+ !swm.isParentInterceptEnabled();
+ return {
+ actor: this.actorID,
+ scope: registration.scope,
+ url: registration.scriptSpec,
+ evaluatingWorker,
+ installingWorker,
+ waitingWorker,
+ activeWorker,
+ fetch: newestWorker?.fetch,
+ // - In old multi e10s: only active registrations are available.
+ // - In non-e10s or new implementaion: check if we have an active worker
+ active: isMultiE10sWithOldImplementation ? true : !!activeWorker,
+ lastUpdateTime: registration.lastUpdateTime,
+ traits: {},
+ };
+ },
+
+ destroy() {
+ protocol.Actor.prototype.destroy.call(this);
+
+ // Ensure resuming the service worker in case the connection drops
+ if (
+ swm.isParentInterceptEnabled() &&
+ this._registration.activeWorker &&
+ this._preventedShutdown
+ ) {
+ this.allowShutdown();
+ }
+
+ Services.obs.removeObserver(this, PushService.subscriptionModifiedTopic);
+ this._registration.removeListener(this);
+ this._registration = null;
+ if (this._pushSubscriptionActor) {
+ this._pushSubscriptionActor.destroy();
+ }
+ this._pushSubscriptionActor = null;
+
+ this._destroyServiceWorkerActors();
+
+ this._evaluatingWorker = null;
+ this._installingWorker = null;
+ this._waitingWorker = null;
+ this._activeWorker = null;
+ },
+
+ /**
+ * Standard observer interface to listen to push messages and changes.
+ */
+ observe(subject, topic, data) {
+ const scope = this._registration.scope;
+ if (data !== scope) {
+ // This event doesn't concern us, pretend nothing happened.
+ return;
+ }
+ switch (topic) {
+ case PushService.subscriptionModifiedTopic:
+ if (this._pushSubscriptionActor) {
+ this._pushSubscriptionActor.destroy();
+ this._pushSubscriptionActor = null;
+ }
+ this.emit("push-subscription-modified");
+ break;
+ }
+ },
+
+ start() {
+ if (swm.isParentInterceptEnabled()) {
+ const { activeWorker } = this._registration;
+
+ // TODO: don't return "started" if there's no active worker.
+ if (activeWorker) {
+ // This starts up the Service Worker if it's not already running.
+ // Note that with parent-intercept (i.e. swm.isParentInterceptEnabled /
+ // dom.serviceWorkers.parent_intercept=true), the Service Workers exist
+ // in content processes but are managed from the parent process. This is
+ // why we call `attachDebugger` here (in the parent process) instead of
+ // in a process script.
+ activeWorker.attachDebugger();
+ activeWorker.detachDebugger();
+ }
+
+ return { type: "started" };
+ }
+
+ if (!_serviceWorkerProcessScriptLoaded) {
+ Services.ppmm.loadProcessScript(
+ "resource://devtools/server/actors/worker/service-worker-process.js",
+ true
+ );
+ _serviceWorkerProcessScriptLoaded = true;
+ }
+
+ // XXX: Send the permissions down to the content process before starting
+ // the service worker within the content process. As we don't know what
+ // content process we're starting the service worker in (as we're using a
+ // broadcast channel to talk to it), we just broadcast the permissions to
+ // everyone as well.
+ //
+ // This call should be replaced with a proper implementation when
+ // ServiceWorker debugging is improved to support multiple content processes
+ // correctly.
+ Services.perms.broadcastPermissionsForPrincipalToAllContentProcesses(
+ this._registration.principal
+ );
+
+ Services.ppmm.broadcastAsyncMessage("serviceWorkerRegistration:start", {
+ scope: this._registration.scope,
+ });
+ return { type: "started" };
+ },
+
+ unregister() {
+ const { principal, scope } = this._registration;
+ const unregisterCallback = {
+ unregisterSucceeded: function() {},
+ unregisterFailed: function() {
+ console.error("Failed to unregister the service worker for " + scope);
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIServiceWorkerUnregisterCallback",
+ ]),
+ };
+ swm.propagateUnregister(principal, unregisterCallback, scope);
+
+ return { type: "unregistered" };
+ },
+
+ push() {
+ if (!swm.isParentInterceptEnabled()) {
+ throw new Error(
+ "ServiceWorkerRegistrationActor.push can only be used " +
+ "in parent-intercept mode"
+ );
+ }
+
+ const { principal, scope } = this._registration;
+ const originAttributes = ChromeUtils.originAttributesToSuffix(
+ principal.originAttributes
+ );
+ swm.sendPushEvent(originAttributes, scope);
+ },
+
+ /**
+ * Prevent the current active worker to shutdown after the idle timeout.
+ */
+ preventShutdown() {
+ if (!swm.isParentInterceptEnabled()) {
+ // In non parent-intercept mode, this is handled by the WorkerDescriptorActor attach().
+ throw new Error(
+ "ServiceWorkerRegistrationActor.preventShutdown can only be used " +
+ "in parent-intercept mode"
+ );
+ }
+
+ if (!this._registration.activeWorker) {
+ throw new Error(
+ "ServiceWorkerRegistrationActor.preventShutdown could not find " +
+ "activeWorker in parent-intercept mode"
+ );
+ }
+
+ // attachDebugger has to be called from the parent process in parent-intercept mode.
+ this._registration.activeWorker.attachDebugger();
+ this._preventedShutdown = true;
+ },
+
+ /**
+ * Allow the current active worker to shut down again.
+ */
+ allowShutdown() {
+ if (!swm.isParentInterceptEnabled()) {
+ // In non parent-intercept mode, this is handled by the WorkerDescriptorActor detach().
+ throw new Error(
+ "ServiceWorkerRegistrationActor.allowShutdown can only be used " +
+ "in parent-intercept mode"
+ );
+ }
+
+ if (!this._registration.activeWorker) {
+ throw new Error(
+ "ServiceWorkerRegistrationActor.allowShutdown could not find " +
+ "activeWorker in parent-intercept mode"
+ );
+ }
+
+ this._registration.activeWorker.detachDebugger();
+ this._preventedShutdown = false;
+ },
+
+ getPushSubscription() {
+ const registration = this._registration;
+ let pushSubscriptionActor = this._pushSubscriptionActor;
+ if (pushSubscriptionActor) {
+ return Promise.resolve(pushSubscriptionActor);
+ }
+ return new Promise((resolve, reject) => {
+ PushService.getSubscription(
+ registration.scope,
+ registration.principal,
+ (result, subscription) => {
+ if (!subscription) {
+ resolve(null);
+ return;
+ }
+ pushSubscriptionActor = new PushSubscriptionActor(
+ this._conn,
+ subscription
+ );
+ this._pushSubscriptionActor = pushSubscriptionActor;
+ resolve(pushSubscriptionActor);
+ }
+ );
+ });
+ },
+
+ _destroyServiceWorkerActors() {
+ this._evaluatingWorker.destroy();
+ this._installingWorker.destroy();
+ this._waitingWorker.destroy();
+ this._activeWorker.destroy();
+ },
+
+ _createServiceWorkerActors() {
+ const {
+ evaluatingWorker,
+ installingWorker,
+ waitingWorker,
+ activeWorker,
+ } = this._registration;
+
+ this._evaluatingWorker = new ServiceWorkerActor(
+ this._conn,
+ evaluatingWorker
+ );
+ this._installingWorker = new ServiceWorkerActor(
+ this._conn,
+ installingWorker
+ );
+ this._waitingWorker = new ServiceWorkerActor(this._conn, waitingWorker);
+ this._activeWorker = new ServiceWorkerActor(this._conn, activeWorker);
+
+ // Add the ServiceWorker actors as children of this ServiceWorkerRegistration actor,
+ // assigning them valid actorIDs.
+ this.manage(this._evaluatingWorker);
+ this.manage(this._installingWorker);
+ this.manage(this._waitingWorker);
+ this.manage(this._activeWorker);
+ },
+ }
+);
+
+exports.ServiceWorkerRegistrationActor = ServiceWorkerRegistrationActor;
diff --git a/devtools/server/actors/worker/service-worker.js b/devtools/server/actors/worker/service-worker.js
new file mode 100644
index 0000000000..d77d5c1e9c
--- /dev/null
+++ b/devtools/server/actors/worker/service-worker.js
@@ -0,0 +1,45 @@
+/* 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 { Ci } = require("chrome");
+const protocol = require("devtools/shared/protocol");
+const {
+ serviceWorkerSpec,
+} = require("devtools/shared/specs/worker/service-worker");
+
+const ServiceWorkerActor = protocol.ActorClassWithSpec(serviceWorkerSpec, {
+ initialize(conn, worker) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this._worker = worker;
+ },
+
+ form() {
+ if (!this._worker) {
+ return null;
+ }
+
+ // handlesFetchEvents is not available if the worker's main script is in the
+ // evaluating state.
+ const isEvaluating =
+ this._worker.state == Ci.nsIServiceWorkerInfo.STATE_PARSED;
+ const fetch = isEvaluating ? undefined : this._worker.handlesFetchEvents;
+
+ return {
+ actor: this.actorID,
+ url: this._worker.scriptSpec,
+ state: this._worker.state,
+ fetch,
+ id: this._worker.id,
+ };
+ },
+
+ destroy() {
+ protocol.Actor.prototype.destroy.call(this);
+ this._worker = null;
+ },
+});
+
+exports.ServiceWorkerActor = ServiceWorkerActor;
diff --git a/devtools/server/actors/worker/worker-descriptor-actor-list.js b/devtools/server/actors/worker/worker-descriptor-actor-list.js
new file mode 100644
index 0000000000..fa6aa12a75
--- /dev/null
+++ b/devtools/server/actors/worker/worker-descriptor-actor-list.js
@@ -0,0 +1,212 @@
+/* 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 { Ci } = require("chrome");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+loader.lazyRequireGetter(
+ this,
+ "WorkerDescriptorActor",
+ "devtools/server/actors/descriptors/worker",
+ true
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "wdm",
+ "@mozilla.org/dom/workers/workerdebuggermanager;1",
+ "nsIWorkerDebuggerManager"
+);
+
+function matchWorkerDebugger(dbg, options) {
+ if ("type" in options && dbg.type !== options.type) {
+ return false;
+ }
+ if ("window" in options) {
+ let window = dbg.window;
+ while (window !== null && window.parent !== window) {
+ window = window.parent;
+ }
+
+ if (window !== options.window) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+function matchServiceWorker(dbg, origin) {
+ return (
+ dbg.type == Ci.nsIWorkerDebugger.TYPE_SERVICE &&
+ new URL(dbg.url).origin == origin
+ );
+}
+
+// When a new worker appears, in some cases (i.e. the debugger is running) we
+// want it to pause during registration until a later time (i.e. the debugger
+// finishes attaching to the worker). This is an optional WorkderDebuggerManager
+// listener that can be installed in addition to the WorkerDescriptorActorList
+// listener. It always listens to new workers and pauses any matching filters
+// which have been set on it.
+//
+// Two kinds of filters are supported:
+//
+// setPauseMatching(true) will pause all workers which match the options strcut
+// passed in on creation.
+//
+// setPauseServiceWorkers(origin) will pause all service workers which have the
+// specified origin.
+//
+// FIXME Bug 1601279 separate WorkerPauser from WorkerDescriptorActorList and give
+// it a more consistent interface.
+function WorkerPauser(options) {
+ this._options = options;
+ this._pauseMatching = null;
+ this._pauseServiceWorkerOrigin = null;
+
+ this.onRegister = this._onRegister.bind(this);
+ this.onUnregister = () => {};
+
+ wdm.addListener(this);
+}
+
+WorkerPauser.prototype = {
+ destroy() {
+ wdm.removeListener(this);
+ },
+
+ _onRegister(dbg) {
+ if (
+ (this._pauseMatching && matchWorkerDebugger(dbg, this._options)) ||
+ (this._pauseServiceWorkerOrigin &&
+ matchServiceWorker(dbg, this._pauseServiceWorkerOrigin))
+ ) {
+ // Prevent the debuggee from executing in this worker until the debugger
+ // has finished attaching to it.
+ dbg.setDebuggerReady(false);
+ }
+ },
+
+ setPauseMatching(shouldPause) {
+ this._pauseMatching = shouldPause;
+ },
+
+ setPauseServiceWorkers(origin) {
+ this._pauseServiceWorkerOrigin = origin;
+ },
+};
+
+function WorkerDescriptorActorList(conn, options) {
+ this._conn = conn;
+ this._options = options;
+ this._actors = new Map();
+ this._onListChanged = null;
+ this._workerPauser = null;
+ this._mustNotify = false;
+ this.onRegister = this.onRegister.bind(this);
+ this.onUnregister = this.onUnregister.bind(this);
+}
+
+WorkerDescriptorActorList.prototype = {
+ destroy() {
+ this.onListChanged = null;
+ if (this._workerPauser) {
+ this._workerPauser.destroy();
+ this._workerPauser = null;
+ }
+ },
+
+ getList() {
+ // Create a set of debuggers.
+ const dbgs = new Set();
+ for (const dbg of wdm.getWorkerDebuggerEnumerator()) {
+ if (matchWorkerDebugger(dbg, this._options)) {
+ dbgs.add(dbg);
+ }
+ }
+
+ // Delete each actor for which we don't have a debugger.
+ for (const [dbg] of this._actors) {
+ if (!dbgs.has(dbg)) {
+ this._actors.delete(dbg);
+ }
+ }
+
+ // Create an actor for each debugger for which we don't have one.
+ for (const dbg of dbgs) {
+ if (!this._actors.has(dbg)) {
+ this._actors.set(dbg, new WorkerDescriptorActor(this._conn, dbg));
+ }
+ }
+
+ const actors = [];
+ for (const [, actor] of this._actors) {
+ actors.push(actor);
+ }
+
+ if (!this._mustNotify) {
+ if (this._onListChanged !== null) {
+ wdm.addListener(this);
+ }
+ this._mustNotify = true;
+ }
+
+ return Promise.resolve(actors);
+ },
+
+ get onListChanged() {
+ return this._onListChanged;
+ },
+
+ set onListChanged(onListChanged) {
+ if (typeof onListChanged !== "function" && onListChanged !== null) {
+ throw new Error("onListChanged must be either a function or null.");
+ }
+ if (onListChanged === this._onListChanged) {
+ return;
+ }
+
+ if (this._mustNotify) {
+ if (this._onListChanged === null && onListChanged !== null) {
+ wdm.addListener(this);
+ }
+ if (this._onListChanged !== null && onListChanged === null) {
+ wdm.removeListener(this);
+ }
+ }
+ this._onListChanged = onListChanged;
+ },
+
+ _notifyListChanged() {
+ this._onListChanged();
+
+ if (this._onListChanged !== null) {
+ wdm.removeListener(this);
+ }
+ this._mustNotify = false;
+ },
+
+ onRegister(dbg) {
+ if (matchWorkerDebugger(dbg, this._options)) {
+ this._notifyListChanged();
+ }
+ },
+
+ onUnregister(dbg) {
+ if (matchWorkerDebugger(dbg, this._options)) {
+ this._notifyListChanged();
+ }
+ },
+
+ get workerPauser() {
+ if (!this._workerPauser) {
+ this._workerPauser = new WorkerPauser(this._options);
+ }
+ return this._workerPauser;
+ },
+};
+
+exports.WorkerDescriptorActorList = WorkerDescriptorActorList;