diff --git a/devtools/server/actors/descriptors/moz.build b/devtools/server/actors/descriptors/moz.build new file mode 100644 index 000000000000..55544d0f218b --- /dev/null +++ b/devtools/server/actors/descriptors/moz.build @@ -0,0 +1,10 @@ +# -*- 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', +) + diff --git a/devtools/server/actors/descriptors/process.js b/devtools/server/actors/descriptors/process.js new file mode 100644 index 000000000000..56b3fdc946b0 --- /dev/null +++ b/devtools/server/actors/descriptors/process.js @@ -0,0 +1,111 @@ +/* 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 { DebuggerServer } = require("devtools/server/main"); +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 +); + +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.isParent = options.parent; + this.destroy = this.destroy.bind(this); + }, + + _parentProcessConnect() { + const env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + const isXpcshell = env.exists("XPCSHELL_TEST_PROFILE_DIR"); + let targetActor = null; + 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.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 = Services.ppmm.getChildAt(id); + if (!mm) { + return { + error: "noProcess", + message: "There is no process with id '" + id + "'.", + }; + } + const childTargetForm = await DebuggerServer.connectToContentProcess( + this.conn, + mm, + this.destroy + ); + return childTargetForm; + }, + + /** + * Connect the a process actor. + */ + async getTarget() { + if (!DebuggerServer.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(); + }, + + form() { + return { + actor: this.actorID, + id: this.id, + isParent: this.isParent, + }; + }, +}); + +exports.ProcessDescriptorActor = ProcessDescriptorActor; diff --git a/devtools/server/actors/moz.build b/devtools/server/actors/moz.build index 241fe0cb7d8d..144eff2183a5 100644 --- a/devtools/server/actors/moz.build +++ b/devtools/server/actors/moz.build @@ -7,6 +7,7 @@ DIRS += [ 'accessibility', 'addon', + 'descriptors', 'emulation', 'highlighters', 'inspector', diff --git a/devtools/shared/fronts/descriptors/moz.build b/devtools/shared/fronts/descriptors/moz.build new file mode 100644 index 000000000000..55544d0f218b --- /dev/null +++ b/devtools/shared/fronts/descriptors/moz.build @@ -0,0 +1,10 @@ +# -*- 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', +) + diff --git a/devtools/shared/fronts/descriptors/process.js b/devtools/shared/fronts/descriptors/process.js new file mode 100644 index 000000000000..580fc0037ec9 --- /dev/null +++ b/devtools/shared/fronts/descriptors/process.js @@ -0,0 +1,94 @@ +/* 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 { + processDescriptorSpec, +} = require("devtools/shared/specs/descriptors/process"); +const { + BrowsingContextTargetFront, +} = require("devtools/shared/fronts/targets/browsing-context"); +const { + ContentProcessTargetFront, +} = require("devtools/shared/fronts/targets/content-process"); +const { + FrontClassWithSpec, + registerFront, +} = require("devtools/shared/protocol"); + +class ProcessDescriptorFront extends FrontClassWithSpec(processDescriptorSpec) { + constructor(client) { + super(client); + this.isParent = false; + this._processTargetFront = null; + this._targetFrontPromise = null; + this._client = client; + } + + form(json) { + this.id = json.id; + this.isParent = json.isParent; + } + + async _createProcessTargetFront(form) { + let front = null; + // the request to getTarget may return a ContentProcessTargetActor or a + // ParentProcessTargetActor. In most cases getProcess(0) will return the + // main process target actor, which is a ParentProcessTargetActor, but + // not in xpcshell, which uses a ContentProcessTargetActor. So select + // the right front based on the actor ID. + if (form.actor.includes("parentProcessTarget")) { + // ParentProcessTargetActor doesn't have a specific front, instead it uses + // BrowsingContextTargetFront on the client side. + front = new BrowsingContextTargetFront(this._client); + } else { + front = new ContentProcessTargetFront(this._client); + } + // As these fronts aren't instantiated by protocol.js, we have to set their actor ID + // manually like that: + front.actorID = form.actor; + front.form(form); + this.manage(front); + return front; + } + + async getTarget() { + if (this._processTargetFront && this._processTargetFront.actorID) { + return this._processTargetFront; + } + if (this._targetFrontPromise) { + return this._targetFrontPromise; + } + this._targetFrontPromise = (async () => { + try { + const targetForm = await super.getTarget(); + this._processTargetFront = await this._createProcessTargetFront( + targetForm + ); + await this._processTargetFront.attach(); + // clear the promise if we are finished so that we can re-connect if + // necessary + this._targetFrontPromise = null; + return this._processTargetFront; + } catch (e) { + // This is likely to happen if we get a lot of events which drop previous + // processes. + console.log( + `Request to connect to ProcessDescriptor "${this.id}" failed: ${e}` + ); + return null; + } + })(); + return this._targetFrontPromise; + } + + destroy() { + this._processTargetFront = null; + this._targetFrontPromise = null; + super.destroy(); + } +} + +exports.ProcessDescriptorFront = ProcessDescriptorFront; +registerFront(ProcessDescriptorFront); diff --git a/devtools/shared/fronts/moz.build b/devtools/shared/fronts/moz.build index 0a2531e2f576..d9bc16f4767a 100644 --- a/devtools/shared/fronts/moz.build +++ b/devtools/shared/fronts/moz.build @@ -6,6 +6,7 @@ DIRS += [ 'addon', + 'descriptors', 'inspector', 'targets', 'worker', diff --git a/devtools/shared/specs/descriptors/moz.build b/devtools/shared/specs/descriptors/moz.build new file mode 100644 index 000000000000..55544d0f218b --- /dev/null +++ b/devtools/shared/specs/descriptors/moz.build @@ -0,0 +1,10 @@ +# -*- 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', +) + diff --git a/devtools/shared/specs/descriptors/process.js b/devtools/shared/specs/descriptors/process.js new file mode 100644 index 000000000000..2727a01887f6 --- /dev/null +++ b/devtools/shared/specs/descriptors/process.js @@ -0,0 +1,19 @@ +/* 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 { generateActorSpec, RetVal } = require("devtools/shared/protocol"); + +const processDescriptorSpec = generateActorSpec({ + typeName: "processDescriptor", + + methods: { + getTarget: { + request: {}, + response: { process: RetVal("json") }, + }, + }, +}); + +exports.processDescriptorSpec = processDescriptorSpec; diff --git a/devtools/shared/specs/index.js b/devtools/shared/specs/index.js index a7417ff279b5..033166eff0ef 100644 --- a/devtools/shared/specs/index.js +++ b/devtools/shared/specs/index.js @@ -57,6 +57,11 @@ const Types = (exports.__TypesForTests = [ spec: "devtools/shared/specs/css-properties", front: "devtools/shared/fronts/css-properties", }, + { + types: ["processDescriptor"], + spec: "devtools/shared/specs/descriptors/process", + front: "devtools/shared/fronts/descriptors/process", + }, { types: ["device"], spec: "devtools/shared/specs/device", diff --git a/devtools/shared/specs/moz.build b/devtools/shared/specs/moz.build index 30163c788f42..3d7326adecf7 100644 --- a/devtools/shared/specs/moz.build +++ b/devtools/shared/specs/moz.build @@ -6,6 +6,7 @@ DIRS += [ 'addon', + 'descriptors', 'targets', 'worker', ]