summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/descriptors
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/descriptors')
-rw-r--r--devtools/server/actors/descriptors/moz.build12
-rw-r--r--devtools/server/actors/descriptors/process.js169
-rw-r--r--devtools/server/actors/descriptors/tab.js216
-rw-r--r--devtools/server/actors/descriptors/webextension.js252
-rw-r--r--devtools/server/actors/descriptors/worker.js253
5 files changed, 902 insertions, 0 deletions
diff --git a/devtools/server/actors/descriptors/moz.build b/devtools/server/actors/descriptors/moz.build
new file mode 100644
index 0000000000..bf297b3dcb
--- /dev/null
+++ b/devtools/server/actors/descriptors/moz.build
@@ -0,0 +1,12 @@
+# -*- 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(
+ "process.js",
+ "tab.js",
+ "webextension.js",
+ "worker.js",
+)
diff --git a/devtools/server/actors/descriptors/process.js b/devtools/server/actors/descriptors/process.js
new file mode 100644
index 0000000000..b15e6b0b19
--- /dev/null
+++ b/devtools/server/actors/descriptors/process.js
@@ -0,0 +1,169 @@
+/* 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 Services = require("Services");
+const { DevToolsServer } = require("devtools/server/devtools-server");
+const { Cc, Ci } = require("chrome");
+
+const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
+const {
+ processDescriptorSpec,
+} = require("devtools/shared/specs/descriptors/process");
+
+loader.lazyRequireGetter(
+ this,
+ "ContentProcessTargetActor",
+ "devtools/server/actors/targets/content-process",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "ParentProcessTargetActor",
+ "devtools/server/actors/targets/parent-process",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "connectToContentProcess",
+ "devtools/server/connectors/content-process-connector",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "WatcherActor",
+ "devtools/server/actors/watcher",
+ true
+);
+
+const ProcessDescriptorActor = ActorClassWithSpec(processDescriptorSpec, {
+ initialize(connection, options = {}) {
+ if ("id" in options && typeof options.id != "number") {
+ throw Error("process connect requires a valid `id` attribute.");
+ }
+ Actor.prototype.initialize.call(this, connection);
+ this.id = options.id;
+ this._browsingContextTargetActor = null;
+ this.isParent = options.parent;
+ this.destroy = this.destroy.bind(this);
+ },
+
+ get browsingContextID() {
+ if (this._browsingContextTargetActor) {
+ return this._browsingContextTargetActor.docShell.browsingContext.id;
+ }
+ return null;
+ },
+
+ _parentProcessConnect() {
+ const env = Cc["@mozilla.org/process/environment;1"].getService(
+ Ci.nsIEnvironment
+ );
+ const isXpcshell = env.exists("XPCSHELL_TEST_PROFILE_DIR");
+ let targetActor;
+ if (isXpcshell) {
+ // Check if we are running on xpcshell.
+ // When running on xpcshell, there is no valid browsing context to attach to
+ // and so ParentProcessTargetActor doesn't make sense as it inherits from
+ // BrowsingContextTargetActor. So instead use ContentProcessTargetActor, which
+ // matches xpcshell needs.
+ targetActor = new ContentProcessTargetActor(this.conn);
+ } else {
+ // Create the target actor for the parent process, which is in the same process
+ // as this target. Because we are in the same process, we have a true actor that
+ // should be managed by the ProcessDescriptorActor.
+ targetActor = new ParentProcessTargetActor(this.conn);
+ // this is a special field that only parent process with a browsing context
+ // have, as they are the only processes at the moment that have child
+ // browsing contexts
+ this._browsingContextTargetActor = targetActor;
+ }
+ this.manage(targetActor);
+ // to be consistent with the return value of the _childProcessConnect, we are returning
+ // the form here. This might be memoized in the future
+ return targetActor.form();
+ },
+
+ /**
+ * Connect to a remote process actor, always a ContentProcess target.
+ */
+ async _childProcessConnect() {
+ const { id } = this;
+ const mm = this._lookupMessageManager(id);
+ if (!mm) {
+ return {
+ error: "noProcess",
+ message: "There is no process with id '" + id + "'.",
+ };
+ }
+ const childTargetForm = await connectToContentProcess(
+ this.conn,
+ mm,
+ this.destroy
+ );
+ return childTargetForm;
+ },
+
+ _lookupMessageManager(id) {
+ for (let i = 0; i < Services.ppmm.childCount; i++) {
+ const mm = Services.ppmm.getChildAt(i);
+
+ // A zero id is used for the parent process, instead of its actual pid.
+ if (id ? mm.osPid == id : mm.isInProcess) {
+ return mm;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Connect the a process actor.
+ */
+ async getTarget() {
+ if (!DevToolsServer.allowChromeProcess) {
+ return {
+ error: "forbidden",
+ message: "You are not allowed to debug processes.",
+ };
+ }
+ if (this.isParent) {
+ return this._parentProcessConnect();
+ }
+ // This is a remote process we are connecting to
+ return this._childProcessConnect();
+ },
+
+ /**
+ * Return a Watcher actor, allowing to keep track of targets which
+ * already exists or will be created. It also helps knowing when they
+ * are destroyed.
+ */
+ getWatcher() {
+ if (!this.watcher) {
+ this.watcher = new WatcherActor(this.conn);
+ this.manage(this.watcher);
+ }
+ return this.watcher;
+ },
+
+ form() {
+ return {
+ actor: this.actorID,
+ id: this.id,
+ isParent: this.isParent,
+ traits: {
+ // Supports the Watcher actor. Can be removed as part of Bug 1680280.
+ watcher: true,
+ },
+ };
+ },
+
+ destroy() {
+ this._browsingContextTargetActor = null;
+ Actor.prototype.destroy.call(this);
+ },
+});
+
+exports.ProcessDescriptorActor = ProcessDescriptorActor;
diff --git a/devtools/server/actors/descriptors/tab.js b/devtools/server/actors/descriptors/tab.js
new file mode 100644
index 0000000000..b08cad801f
--- /dev/null
+++ b/devtools/server/actors/descriptors/tab.js
@@ -0,0 +1,216 @@
+/* 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";
+
+/*
+ * Descriptor Actor that represents a Tab in the parent process. It
+ * launches a FrameTargetActor in the content process to do the real work and tunnels the
+ * data.
+ *
+ * See devtools/docs/backend/actor-hierarchy.md for more details.
+ */
+
+const Services = require("Services");
+const {
+ connectToFrame,
+} = require("devtools/server/connectors/frame-connector");
+loader.lazyImporter(
+ this,
+ "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm"
+);
+const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
+const { tabDescriptorSpec } = require("devtools/shared/specs/descriptors/tab");
+const { AppConstants } = require("resource://gre/modules/AppConstants.jsm");
+
+loader.lazyRequireGetter(
+ this,
+ "WatcherActor",
+ "devtools/server/actors/watcher",
+ true
+);
+
+/**
+ * Creates a target actor proxy for handling requests to a single browser frame.
+ * Both <xul:browser> and <iframe mozbrowser> are supported.
+ * This actor is a shim that connects to a FrameTargetActor in a remote browser process.
+ * All RDP packets get forwarded using the message manager.
+ *
+ * @param connection The main RDP connection.
+ * @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
+ */
+const TabDescriptorActor = ActorClassWithSpec(tabDescriptorSpec, {
+ initialize(connection, browser) {
+ Actor.prototype.initialize.call(this, connection);
+ this._conn = connection;
+ this._browser = browser;
+ },
+
+ form() {
+ const form = {
+ actor: this.actorID,
+ browsingContextID:
+ this._browser && this._browser.browsingContext
+ ? this._browser.browsingContext.id
+ : null,
+ isZombieTab: this._isZombieTab(),
+ outerWindowID: this._getOuterWindowId(),
+ selected: this.selected,
+ title: this._getTitle(),
+ traits: {
+ // Supports the Watcher actor. Can be removed as part of Bug 1680280.
+ watcher: true,
+ },
+ url: this._getUrl(),
+ };
+
+ return form;
+ },
+
+ _getTitle() {
+ // If the content already provides a title, use it.
+ if (this._browser.contentTitle) {
+ return this._browser.contentTitle;
+ }
+
+ // For zombie or lazy tabs (tab created, but content has not been loaded),
+ // try to retrieve the title from the XUL Tab itself.
+ // Note: this only works on Firefox desktop.
+ if (this._tabbrowser) {
+ const tab = this._tabbrowser.getTabForBrowser(this._browser);
+ if (tab) {
+ return tab.label;
+ }
+ }
+
+ // No title available.
+ return null;
+ },
+
+ _getUrl() {
+ if (!this._browser || !this._browser.browsingContext) {
+ return "";
+ }
+
+ const { browsingContext } = this._browser;
+ return browsingContext.currentWindowGlobal.documentURI.spec;
+ },
+
+ _getOuterWindowId() {
+ if (!this._browser || !this._browser.browsingContext) {
+ return "";
+ }
+
+ const { browsingContext } = this._browser;
+ return browsingContext.currentWindowGlobal.outerWindowId;
+ },
+
+ get selected() {
+ // getMostRecentBrowserWindow will find the appropriate window on Firefox
+ // Desktop and on GeckoView.
+ const topAppWindow = Services.wm.getMostRecentBrowserWindow();
+
+ const selectedBrowser = topAppWindow?.gBrowser?.selectedBrowser;
+ if (!selectedBrowser) {
+ // Note: gBrowser is not available on GeckoView.
+ // We should find another way to know if this browser is the selected
+ // browser. See Bug 1631020.
+ return false;
+ }
+
+ return this._browser === selectedBrowser;
+ },
+
+ async getTarget() {
+ if (!this._conn) {
+ return {
+ error: "tabDestroyed",
+ message: "Tab destroyed while performing a TabDescriptorActor update",
+ };
+ }
+
+ /* eslint-disable-next-line no-async-promise-executor */
+ return new Promise(async (resolve, reject) => {
+ const onDestroy = () => {
+ // Reject the update promise if the tab was destroyed while requesting an update
+ reject({
+ error: "tabDestroyed",
+ message: "Tab destroyed while performing a TabDescriptorActor update",
+ });
+ };
+
+ try {
+ // Check if the browser is still connected before calling connectToFrame
+ if (!this._browser.isConnected) {
+ onDestroy();
+ return;
+ }
+
+ const connectForm = await connectToFrame(
+ this._conn,
+ this._browser,
+ onDestroy
+ );
+
+ resolve(connectForm);
+ } catch (e) {
+ reject({
+ error: "tabDestroyed",
+ message: "Tab destroyed while connecting to the frame",
+ });
+ }
+ });
+ },
+
+ /**
+ * Return a Watcher actor, allowing to keep track of targets which
+ * already exists or will be created. It also helps knowing when they
+ * are destroyed.
+ */
+ getWatcher() {
+ if (!this.watcher) {
+ this.watcher = new WatcherActor(this.conn, { browser: this._browser });
+ this.manage(this.watcher);
+ }
+ return this.watcher;
+ },
+
+ get _tabbrowser() {
+ if (this._browser && typeof this._browser.getTabBrowser == "function") {
+ return this._browser.getTabBrowser();
+ }
+ return null;
+ },
+
+ async getFavicon() {
+ if (!AppConstants.MOZ_PLACES) {
+ // PlacesUtils is not supported
+ return null;
+ }
+
+ try {
+ const { data } = await PlacesUtils.promiseFaviconData(this._getUrl());
+ return data;
+ } catch (e) {
+ // Favicon unavailable for this url.
+ return null;
+ }
+ },
+
+ _isZombieTab() {
+ // Note: GeckoView doesn't support zombie tabs
+ const tabbrowser = this._tabbrowser;
+ const tab = tabbrowser ? tabbrowser.getTabForBrowser(this._browser) : null;
+ return tab?.hasAttribute && tab.hasAttribute("pending");
+ },
+
+ destroy() {
+ this._browser = null;
+
+ Actor.prototype.destroy.call(this);
+ },
+});
+
+exports.TabDescriptorActor = TabDescriptorActor;
diff --git a/devtools/server/actors/descriptors/webextension.js b/devtools/server/actors/descriptors/webextension.js
new file mode 100644
index 0000000000..bd4079c676
--- /dev/null
+++ b/devtools/server/actors/descriptors/webextension.js
@@ -0,0 +1,252 @@
+/* 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";
+
+/*
+ * Represents a WebExtension add-on in the parent process. This gives some metadata about
+ * the add-on and watches for uninstall events. This uses a proxy to access the
+ * WebExtension in the WebExtension process via the message manager.
+ *
+ * See devtools/docs/backend/actor-hierarchy.md for more details.
+ */
+
+const protocol = require("devtools/shared/protocol");
+const {
+ webExtensionDescriptorSpec,
+} = require("devtools/shared/specs/descriptors/webextension");
+const {
+ connectToFrame,
+} = require("devtools/server/connectors/frame-connector");
+
+loader.lazyImporter(
+ this,
+ "AddonManager",
+ "resource://gre/modules/AddonManager.jsm"
+);
+loader.lazyImporter(
+ this,
+ "ExtensionParent",
+ "resource://gre/modules/ExtensionParent.jsm"
+);
+
+/**
+ * Creates the actor that represents the addon in the parent process, which connects
+ * itself to a WebExtensionTargetActor counterpart which is created in the extension
+ * process (or in the main process if the WebExtensions OOP mode is disabled).
+ *
+ * The WebExtensionDescriptorActor subscribes itself as an AddonListener on the AddonManager
+ * and forwards this events to child actor (e.g. on addon reload or when the addon is
+ * uninstalled completely) and connects to the child extension process using a `browser`
+ * element provided by the extension internals (it is not related to any single extension,
+ * but it will be created automatically to the currently selected "WebExtensions OOP mode"
+ * and it persist across the extension reloads (it is destroyed once the actor exits).
+ * WebExtensionDescriptorActor is a child of RootActor, it can be retrieved via
+ * RootActor.listAddons request.
+ *
+ * @param {DevToolsServerConnection} conn
+ * The connection to the client.
+ * @param {AddonWrapper} addon
+ * The target addon.
+ */
+const WebExtensionDescriptorActor = protocol.ActorClassWithSpec(
+ webExtensionDescriptorSpec,
+ {
+ initialize(conn, addon) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this.addon = addon;
+ this.addonId = addon.id;
+ this._childFormPromise = null;
+
+ // Called when the debug browser element has been destroyed
+ this._extensionFrameDisconnect = this._extensionFrameDisconnect.bind(
+ this
+ );
+ this._onChildExit = this._onChildExit.bind(this);
+ AddonManager.addAddonListener(this);
+ },
+
+ form() {
+ const policy = ExtensionParent.WebExtensionPolicy.getByID(this.addonId);
+ return {
+ actor: this.actorID,
+ debuggable: this.addon.isDebuggable,
+ hidden: this.addon.hidden,
+ // iconDataURL is available after calling loadIconDataURL
+ iconDataURL: this._iconDataURL,
+ iconURL: this.addon.iconURL,
+ id: this.addonId,
+ isSystem: this.addon.isSystem,
+ isWebExtension: this.addon.isWebExtension,
+ manifestURL: policy && policy.getURL("manifest.json"),
+ name: this.addon.name,
+ temporarilyInstalled: this.addon.temporarilyInstalled,
+ traits: {},
+ url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
+ warnings: ExtensionParent.DebugUtils.getExtensionManifestWarnings(
+ this.addonId
+ ),
+ };
+ },
+
+ async getTarget() {
+ const form = await this._extensionFrameConnect();
+ // Merge into the child actor form, some addon metadata
+ // (e.g. the addon name shown in the addon debugger window title).
+ return Object.assign(form, {
+ iconURL: this.addon.iconURL,
+ id: this.addon.id,
+ // Set the isOOP attribute on the connected child actor form.
+ isOOP: this.isOOP,
+ name: this.addon.name,
+ });
+ },
+
+ getChildren() {
+ return [];
+ },
+
+ async _extensionFrameConnect() {
+ if (this._browser) {
+ throw new Error(
+ "This actor is already connected to the extension process"
+ );
+ }
+
+ this._browser = await ExtensionParent.DebugUtils.getExtensionProcessBrowser(
+ this
+ );
+
+ this._form = await connectToFrame(
+ this.conn,
+ this._browser,
+ this._extensionFrameDisconnect,
+ { addonId: this.addonId }
+ );
+
+ this._childActorID = this._form.actor;
+
+ // Exit the proxy child actor if the child actor has been destroyed.
+ this._mm.addMessageListener("debug:webext_child_exit", this._onChildExit);
+
+ return this._form;
+ },
+
+ /** WebExtension Actor Methods **/
+ async reload() {
+ await this.addon.reload();
+ return {};
+ },
+
+ // This function will be called from RootActor in case that the devtools client
+ // retrieves list of addons with `iconDataURL` option.
+ async loadIconDataURL() {
+ this._iconDataURL = await this.getIconDataURL();
+ },
+
+ async getIconDataURL() {
+ if (!this.addon.iconURL) {
+ return null;
+ }
+
+ const xhr = new XMLHttpRequest();
+ xhr.responseType = "blob";
+ xhr.open("GET", this.addon.iconURL, true);
+
+ if (this.addon.iconURL.toLowerCase().endsWith(".svg")) {
+ // Maybe SVG, thus force to change mime type.
+ xhr.overrideMimeType("image/svg+xml");
+ }
+
+ try {
+ const blob = await new Promise((resolve, reject) => {
+ xhr.onload = () => resolve(xhr.response);
+ xhr.onerror = reject;
+ xhr.send();
+ });
+
+ const reader = new FileReader();
+ return await new Promise((resolve, reject) => {
+ reader.onloadend = () => resolve(reader.result);
+ reader.onerror = reject;
+ reader.readAsDataURL(blob);
+ });
+ } catch (_) {
+ console.warn(`Failed to create data url from [${this.addon.iconURL}]`);
+ return null;
+ }
+ },
+
+ // TODO: check if we need this, as it is only used in a test
+ get isOOP() {
+ return this._browser ? this._browser.isRemoteBrowser : undefined;
+ },
+
+ // Private Methods
+ get _mm() {
+ return (
+ this._browser &&
+ (this._browser.messageManager ||
+ this._browser.frameLoader.messageManager)
+ );
+ },
+
+ _extensionFrameDisconnect() {
+ AddonManager.removeAddonListener(this);
+
+ this.addon = null;
+ if (this._mm) {
+ this._mm.removeMessageListener(
+ "debug:webext_child_exit",
+ this._onChildExit
+ );
+
+ this._mm.sendAsyncMessage("debug:webext_parent_exit", {
+ actor: this._childActorID,
+ });
+
+ ExtensionParent.DebugUtils.releaseExtensionProcessBrowser(this);
+ }
+
+ this._browser = null;
+ this._childActorID = null;
+ },
+
+ /**
+ * Handle the child actor exit.
+ */
+ _onChildExit(msg) {
+ if (msg.json.actor !== this._childActorID) {
+ return;
+ }
+
+ this._extensionFrameDisconnect();
+ },
+
+ // AddonManagerListener callbacks.
+ onInstalled(addon) {
+ if (addon.id != this.addonId) {
+ return;
+ }
+
+ // Update the AddonManager's addon object on reload/update.
+ this.addon = addon;
+ },
+
+ onUninstalled(addon) {
+ if (addon != this.addon) {
+ return;
+ }
+
+ this._extensionFrameDisconnect();
+ },
+
+ destroy() {
+ this._extensionFrameDisconnect();
+ protocol.Actor.prototype.destroy.call(this);
+ },
+ }
+);
+
+exports.WebExtensionDescriptorActor = WebExtensionDescriptorActor;
diff --git a/devtools/server/actors/descriptors/worker.js b/devtools/server/actors/descriptors/worker.js
new file mode 100644
index 0000000000..e1f3275b53
--- /dev/null
+++ b/devtools/server/actors/descriptors/worker.js
@@ -0,0 +1,253 @@
+/* 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";
+
+/*
+ * Target actor for any of the various kinds of workers.
+ *
+ * See devtools/docs/backend/actor-hierarchy.md for more details.
+ */
+
+const { Ci } = require("chrome");
+const ChromeUtils = require("ChromeUtils");
+const { DevToolsServer } = require("devtools/server/devtools-server");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const protocol = require("devtools/shared/protocol");
+const {
+ workerDescriptorSpec,
+} = require("devtools/shared/specs/descriptors/worker");
+
+loader.lazyRequireGetter(this, "ChromeUtils");
+
+loader.lazyRequireGetter(
+ this,
+ "connectToWorker",
+ "devtools/server/connectors/worker-connector",
+ true
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "swm",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+const WorkerDescriptorActor = protocol.ActorClassWithSpec(
+ workerDescriptorSpec,
+ {
+ initialize(conn, dbg) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this._dbg = dbg;
+ this._attached = false;
+ this._threadActor = null;
+ this._transport = null;
+
+ this._dbgListener = {
+ onClose: this._onWorkerClose.bind(this),
+ onError: this._onWorkerError.bind(this),
+ };
+ },
+
+ form() {
+ const form = {
+ actor: this.actorID,
+ consoleActor: this._consoleActor,
+ threadActor: this._threadActor,
+ id: this._dbg.id,
+ url: this._dbg.url,
+ traits: {},
+ type: this._dbg.type,
+ };
+ if (this._dbg.type === Ci.nsIWorkerDebugger.TYPE_SERVICE) {
+ /**
+ * With parent-intercept mode, the ServiceWorkerManager in content
+ * processes don't maintain ServiceWorkerRegistrations; record the
+ * ServiceWorker's ID, and this data will be merged with the
+ * corresponding registration in the parent process.
+ */
+ if (
+ !swm.isParentInterceptEnabled() ||
+ !DevToolsServer.isInChildProcess
+ ) {
+ const registration = this._getServiceWorkerRegistrationInfo();
+ form.scope = registration.scope;
+ const newestWorker =
+ registration.activeWorker ||
+ registration.waitingWorker ||
+ registration.installingWorker;
+ form.fetch = newestWorker?.handlesFetchEvents;
+ }
+ }
+ return form;
+ },
+
+ attach() {
+ if (this._dbg.isClosed) {
+ return { error: "closed" };
+ }
+
+ if (!this._attached) {
+ const isServiceWorker =
+ this._dbg.type == Ci.nsIWorkerDebugger.TYPE_SERVICE;
+ if (isServiceWorker) {
+ this._preventServiceWorkerShutdown();
+ }
+ this._dbg.addListener(this._dbgListener);
+ this._attached = true;
+ }
+
+ return {
+ type: "attached",
+ url: this._dbg.url,
+ };
+ },
+
+ detach() {
+ if (!this._attached) {
+ return { error: "wrongState" };
+ }
+
+ this._detach();
+
+ return { type: "detached" };
+ },
+
+ destroy() {
+ if (this._attached) {
+ this._detach();
+ }
+ protocol.Actor.prototype.destroy.call(this);
+ },
+
+ async getTarget() {
+ if (!this._attached) {
+ return { error: "wrongState" };
+ }
+
+ if (this._threadActor !== null) {
+ return {
+ type: "connected",
+ threadActor: this._threadActor,
+ };
+ }
+
+ try {
+ const { transport, workerTargetForm } = await connectToWorker(
+ this.conn,
+ this._dbg,
+ this.actorID,
+ {}
+ );
+
+ this._threadActor = workerTargetForm.threadActor;
+ this._consoleActor = workerTargetForm.consoleActor;
+ this._transport = transport;
+
+ return {
+ type: "connected",
+ threadActor: this._threadActor,
+ consoleActor: this._consoleActor,
+ url: this._dbg.url,
+ };
+ } catch (error) {
+ return { error: error.toString() };
+ }
+ },
+
+ push() {
+ if (this._dbg.type !== Ci.nsIWorkerDebugger.TYPE_SERVICE) {
+ return { error: "wrongType" };
+ }
+ const registration = this._getServiceWorkerRegistrationInfo();
+ const originAttributes = ChromeUtils.originAttributesToSuffix(
+ this._dbg.principal.originAttributes
+ );
+ swm.sendPushEvent(originAttributes, registration.scope);
+ return { type: "pushed" };
+ },
+
+ _onWorkerClose() {
+ if (this._attached) {
+ this._detach();
+ }
+
+ this.conn.sendActorEvent(this.actorID, "close");
+ },
+
+ _onWorkerError(filename, lineno, message) {
+ reportError("ERROR:" + filename + ":" + lineno + ":" + message + "\n");
+ },
+
+ _getServiceWorkerRegistrationInfo() {
+ return swm.getRegistrationByPrincipal(this._dbg.principal, this._dbg.url);
+ },
+
+ _getServiceWorkerInfo() {
+ const registration = this._getServiceWorkerRegistrationInfo();
+ return registration.getWorkerByID(this._dbg.serviceWorkerID);
+ },
+
+ _detach() {
+ if (this._threadActor !== null) {
+ this._transport.close();
+ this._transport = null;
+ this._threadActor = null;
+ }
+
+ // If the worker is already destroyed, nsIWorkerDebugger.type throws
+ // (_dbg.closed appears to be false when it throws)
+ let type;
+ try {
+ type = this._dbg.type;
+ } catch (e) {
+ // nothing
+ }
+
+ const isServiceWorker = type == Ci.nsIWorkerDebugger.TYPE_SERVICE;
+ if (isServiceWorker) {
+ this._allowServiceWorkerShutdown();
+ }
+
+ this._dbg.removeListener(this._dbgListener);
+ this._attached = false;
+ },
+
+ /**
+ * Automatically disable the internal sw timeout that shut them down by calling
+ * nsIWorkerInfo.attachDebugger().
+ * This can be removed when Bug 1496997 lands.
+ */
+ _preventServiceWorkerShutdown() {
+ if (swm.isParentInterceptEnabled()) {
+ // In parentIntercept mode, the worker target actor cannot call attachDebugger
+ // because this API can only be called from the parent process. This will be
+ // done by the worker target front.
+ return;
+ }
+
+ const worker = this._getServiceWorkerInfo();
+ if (worker) {
+ worker.attachDebugger();
+ }
+ },
+
+ /**
+ * Allow the service worker to time out. See _preventServiceWorkerShutdown.
+ */
+ _allowServiceWorkerShutdown() {
+ if (swm.isParentInterceptEnabled()) {
+ return;
+ }
+
+ const worker = this._getServiceWorkerInfo();
+ if (worker) {
+ worker.detachDebugger();
+ }
+ },
+ }
+);
+
+exports.WorkerDescriptorActor = WorkerDescriptorActor;