diff options
Diffstat (limited to 'devtools/server/actors/descriptors/process.js')
-rw-r--r-- | devtools/server/actors/descriptors/process.js | 246 |
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; |