summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/descriptors/process.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/descriptors/process.js')
-rw-r--r--devtools/server/actors/descriptors/process.js246
1 files changed, 246 insertions, 0 deletions
diff --git a/devtools/server/actors/descriptors/process.js b/devtools/server/actors/descriptors/process.js
new file mode 100644
index 0000000000..19944c7d03
--- /dev/null
+++ b/devtools/server/actors/descriptors/process.js
@@ -0,0 +1,246 @@
+/* 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 any process running in Firefox.
+ * This can be:
+ * - the parent process, where all top level chrome window runs:
+ * like browser.xhtml, sidebars, devtools iframes, the browser console, ...
+ * - any content process
+ *
+ * There is some special cases in the class around:
+ * - xpcshell, where there is only one process which doesn't expose any DOM document
+ * And instead of exposing a ParentProcessTargetActor, getTarget will return
+ * a ContentProcessTargetActor.
+ * - background task, similarly to xpcshell, they don't expose any DOM document
+ * and this also works with a ContentProcessTargetActor.
+ *
+ * See devtools/docs/backend/actor-hierarchy.md for more details.
+ */
+
+const { Actor } = require("resource://devtools/shared/protocol.js");
+const {
+ processDescriptorSpec,
+} = require("resource://devtools/shared/specs/descriptors/process.js");
+
+const {
+ DevToolsServer,
+} = require("resource://devtools/server/devtools-server.js");
+
+const {
+ createBrowserSessionContext,
+ createContentProcessSessionContext,
+} = require("resource://devtools/server/actors/watcher/session-context.js");
+
+loader.lazyRequireGetter(
+ this,
+ "ContentProcessTargetActor",
+ "resource://devtools/server/actors/targets/content-process.js",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "ParentProcessTargetActor",
+ "resource://devtools/server/actors/targets/parent-process.js",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "connectToContentProcess",
+ "resource://devtools/server/connectors/content-process-connector.js",
+ true
+);
+loader.lazyRequireGetter(
+ this,
+ "WatcherActor",
+ "resource://devtools/server/actors/watcher.js",
+ true
+);
+
+class ProcessDescriptorActor extends Actor {
+ constructor(connection, options = {}) {
+ super(connection, processDescriptorSpec);
+
+ if ("id" in options && typeof options.id != "number") {
+ throw Error("process connect requires a valid `id` attribute.");
+ }
+
+ this.id = options.id;
+ this._windowGlobalTargetActor = null;
+ this.isParent = options.parent;
+ this.destroy = this.destroy.bind(this);
+ }
+
+ get browsingContextID() {
+ if (this._windowGlobalTargetActor) {
+ return this._windowGlobalTargetActor.docShell.browsingContext.id;
+ }
+ return null;
+ }
+
+ get isWindowlessParent() {
+ return this.isParent && (this.isXpcshell || this.isBackgroundTaskMode);
+ }
+
+ get isXpcshell() {
+ return Services.env.exists("XPCSHELL_TEST_PROFILE_DIR");
+ }
+
+ get isBackgroundTaskMode() {
+ const bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
+ Ci.nsIBackgroundTasks
+ );
+ return bts && bts.isBackgroundTaskMode;
+ }
+
+ _parentProcessConnect() {
+ let targetActor;
+ if (this.isWindowlessParent) {
+ // Check if we are running on xpcshell or in background task mode.
+ // In these modes, there is no valid browsing context to attach to
+ // and so ParentProcessTargetActor doesn't make sense as it inherits from
+ // WindowGlobalTargetActor. So instead use ContentProcessTargetActor, which
+ // matches the needs of these modes.
+ targetActor = new ContentProcessTargetActor(this.conn, {
+ isXpcShellTarget: true,
+ sessionContext: createContentProcessSessionContext(),
+ });
+ } 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 target actor is special and will stay alive as long
+ // as the toolbox/client is alive. It is the original top level target for
+ // the BrowserToolbox and isTopLevelTarget should always be true here.
+ // (It isn't the typical behavior of WindowGlobalTargetActor's base class)
+ isTopLevelTarget: true,
+ sessionContext: createBrowserSessionContext(),
+ });
+ // 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._windowGlobalTargetActor = 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, createBrowserSessionContext());
+ this.manage(this.watcher);
+ }
+ return this.watcher;
+ }
+
+ form() {
+ return {
+ actor: this.actorID,
+ id: this.id,
+ isParent: this.isParent,
+ isWindowlessParent: this.isWindowlessParent,
+ traits: {
+ // Supports the Watcher actor. Can be removed as part of Bug 1680280.
+ // Bug 1687461: WatcherActor only supports the parent process, where we debug everything.
+ // For the "Browser Content Toolbox", where we debug only one content process,
+ // we will still be using legacy listeners.
+ watcher: this.isParent,
+ // ParentProcessTargetActor can be reloaded.
+ supportsReloadDescriptor: this.isParent && !this.isWindowlessParent,
+ },
+ };
+ }
+
+ async reloadDescriptor() {
+ if (!this.isParent || this.isWindowlessParent) {
+ throw new Error(
+ "reloadDescriptor is only available for parent process descriptors"
+ );
+ }
+
+ // Reload for the parent process will restart the whole browser
+ //
+ // This aims at replicate `DevelopmentHelpers.quickRestart`
+ // This allows a user to do a full firefox restart + session restore
+ // Via Ctrl+Alt+R on the Browser Console/Toolbox
+
+ // Maximize the chance of fetching new source content by clearing the cache
+ Services.obs.notifyObservers(null, "startupcache-invalidate");
+
+ // Avoid safemode popup from appearing on restart
+ Services.env.set("MOZ_DISABLE_SAFE_MODE_KEY", "1");
+
+ Services.startup.quit(
+ Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart
+ );
+ }
+
+ destroy() {
+ this.emit("descriptor-destroyed");
+
+ this._windowGlobalTargetActor = null;
+ super.destroy();
+ }
+}
+
+exports.ProcessDescriptorActor = ProcessDescriptorActor;