Bug 1565200 - Add frame connector relying on JsWindowActor instead of message manager r=ochameau

Differential Revision: https://phabricator.services.mozilla.com/D51130

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Julian Descottes 2019-12-11 20:09:24 +00:00
Родитель 7375da64fd
Коммит a0e5d31c31
11 изменённых файлов: 511 добавлений и 9 удалений

Просмотреть файл

@ -26,13 +26,6 @@ const PREFERENCES = [
"Console, so that it can see and debug resources from the content " +
"processes at the same time as resources from the parent process",
],
// To enable when Bug 1565200 lands.
// [
// "devtools.fission.use-js-window-actor",
// "This is a step toward debugging fission iframes in regular toolbox. " +
// "This preference enables using JS Window Actor API instead of the " +
// "Message Manager for RDP internal implementation.",
// ],
[
"devtools.inspector.use-new-box-model-highlighter",
"Enables a new highlighter implementation that can simultaneously " +

Просмотреть файл

@ -0,0 +1,171 @@
/* 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";
var EXPORTED_SYMBOLS = ["DevToolsFrameChild"];
const { EventEmitter } = ChromeUtils.import(
"resource://gre/modules/EventEmitter.jsm"
);
const Loader = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
class DevToolsFrameChild extends JSWindowActorChild {
constructor() {
super();
// The map is indexed by the connection prefix.
// The values are objects containing the following properties:
// - connection: the DebuggerServerConnection itself
// - actor: the FrameTargetActor instance
this._connections = new Map();
this._onConnectionChange = this._onConnectionChange.bind(this);
EventEmitter.decorate(this);
}
connect(msg) {
this.useCustomLoader = this.document.nodePrincipal.isSystemPrincipal;
// When debugging chrome pages, use a new dedicated loader.
this.loader = this.useCustomLoader
? new Loader.DevToolsLoader({
invisibleToDebugger: true,
})
: Loader;
const { prefix } = msg.data;
if (this._connections.get(prefix)) {
throw new Error(
"DevToolsFrameChild connect was called more than once" +
` for the same connection (prefix: "${prefix}")`
);
}
const { connection, targetActor } = this._createConnectionAndActor(prefix);
this._connections.set(prefix, { connection, actor: targetActor });
const { actor } = this._connections.get(prefix);
return { actor: actor.form() };
}
_createConnectionAndActor(prefix) {
const { DebuggerServer } = this.loader.require(
"devtools/server/debugger-server"
);
const { ActorPool } = this.loader.require("devtools/server/actors/common");
const { FrameTargetActor } = this.loader.require(
"devtools/server/actors/targets/frame"
);
DebuggerServer.init();
// We want a special server without any root actor and only target-scoped actors.
// We are going to spawn a FrameTargetActor instance in the next few lines,
// it is going to act like a root actor without being one.
DebuggerServer.registerActors({ target: true });
DebuggerServer.on("connectionchange", this._onConnectionChange);
const connection = DebuggerServer.connectToParentWindowActor(prefix, this);
// Create the actual target actor.
const { messageManager } = this.docShell;
const targetActor = new FrameTargetActor(connection, messageManager);
// Add the newly created actor to the connection pool.
const actorPool = new ActorPool(connection);
actorPool.addActor(targetActor);
connection.addActorPool(actorPool);
return { connection, targetActor };
}
/**
* Destroy the server once its last connection closes. Note that multiple
* frame scripts may be running in parallel and reuse the same server.
*/
_onConnectionChange() {
const { DebuggerServer } = this.loader.require(
"devtools/server/debugger-server"
);
// Only destroy the server if there is no more connections to it. It may be
// used to debug another tab running in the same process.
if (DebuggerServer.hasConnection() || DebuggerServer.keepAlive) {
return;
}
if (this._destroyed) {
return;
}
this._destroyed = true;
DebuggerServer.off("connectionchange", this._onConnectionChange);
DebuggerServer.destroy();
}
disconnect(msg) {
const { prefix } = msg.data;
const connectionInfo = this._connections.get(prefix);
if (!connectionInfo) {
console.error(
"No connection available in DevToolsFrameChild::disconnect"
);
return;
}
// Call DebuggerServerConnection.close to destroy all child actors. It
// should end up calling DebuggerServerConnection.onClosed that would
// actually cleanup all actor pools.
connectionInfo.connection.close();
this._connections.delete(prefix);
}
/**
* Supported Queries
*/
async sendPacket(packet, prefix) {
return this.sendQuery("DevToolsFrameChild:packet", { packet, prefix });
}
/**
* JsWindowActor API
*/
async sendQuery(msg, args) {
try {
const res = await super.sendQuery(msg, args);
return res;
} catch (e) {
console.error("Failed to sendQuery in DevToolsFrameChild", msg);
console.error(e.toString());
throw e;
}
}
receiveMessage(data) {
switch (data.name) {
case "DevToolsFrameParent:connect":
return this.connect(data);
case "DevToolsFrameParent:disconnect":
return this.disconnect(data);
case "DevToolsFrameParent:packet":
return this.emit("packet-received", data);
default:
throw new Error(
"Unsupported message in DevToolsFrameParent: " + data.name
);
}
}
didDestroy() {
for (const [, connectionInfo] of this._connections) {
connectionInfo.connection.close();
}
if (this.useCustomLoader) {
this.loader.destroy();
}
}
}

Просмотреть файл

@ -0,0 +1,186 @@
/* 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";
var EXPORTED_SYMBOLS = ["DevToolsFrameParent"];
const { loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
loader.lazyRequireGetter(
this,
"JsWindowActorTransport",
"devtools/shared/transport/js-window-actor-transport",
true
);
const { EventEmitter } = ChromeUtils.import(
"resource://gre/modules/EventEmitter.jsm"
);
class DevToolsFrameParent extends JSWindowActorParent {
constructor() {
super();
this._destroyed = false;
// Map of DebuggerServerConnection's used to forward the messages from/to
// the client. The connections run in the parent process, as this code. We
// may have more than one when there is more than one client debugging the
// same frame. For example, a content toolbox and the browser toolbox.
//
// The map is indexed by the connection prefix.
// The values are objects containing the following properties:
// - actor: the frame target actor(as a form)
// - connection: the DebuggerServerConnection used to communicate with the
// frame target actor
// - forwardingPrefix: the forwarding prefix used by the connection to know
// how to forward packets to the frame target
// - transport: the JsWindowActorTransport
//
// Reminder about prefixes: all DebuggerServerConnections have a `prefix`
// which can be considered as a kind of id. On top of this, parent process
// DebuggerServerConnections also have forwarding prefixes because they are
// responsible for forwarding messages to content process connections.
this._connections = new Map();
this._onConnectionClosed = this._onConnectionClosed.bind(this);
EventEmitter.decorate(this);
}
async connectToFrame(connection) {
// Compute the same prefix that's used by DebuggerServerConnection when
// forwarding packets to the target frame.
const forwardingPrefix = connection.allocID("child");
try {
const { actor } = await this.connect({ prefix: forwardingPrefix });
connection.on("closed", this._onConnectionClosed);
// Create a js-window-actor based transport.
const transport = new JsWindowActorTransport(this, forwardingPrefix);
transport.hooks = {
onPacket: connection.send.bind(connection),
onClosed() {},
};
transport.ready();
connection.setForwarding(forwardingPrefix, transport);
this._connections.set(connection.prefix, {
actor,
connection,
forwardingPrefix,
transport,
});
return actor;
} catch (e) {
// Might fail if we have an actor destruction.
console.error("Failed to connect to DevToolsFrameChild actor");
console.error(e.toString());
return null;
}
}
_onConnectionClosed(prefix) {
if (this._connections.has(prefix)) {
const { connection } = this._connections.get(prefix);
this._cleanupConnection(connection);
}
}
async _cleanupConnection(connection) {
const { forwardingPrefix, transport } = this._connections.get(
connection.prefix
);
connection.off("closed", this._onConnectionClosed);
if (transport) {
// If we have a child transport, the actor has already
// been created. We need to stop using this transport.
transport.close();
}
// Notify the child process to clean the target-scoped actors.
try {
// Bug 1169643: Ignore any exception as the child process
// may already be destroyed by now.
await this.disconnect({ prefix: forwardingPrefix });
} catch (e) {
// Nothing to do
}
connection.cancelForwarding(forwardingPrefix);
this._connections.delete(connection.prefix);
if (!this._connections.size) {
this._destroy();
}
}
_destroy() {
if (this._destroyed) {
return;
}
this._destroyed = true;
for (const { actor, connection } of this._connections.values()) {
if (actor) {
// The FrameTargetActor within the child process doesn't necessary
// have time to uninitialize itself when the frame is closed/killed.
// So ensure telling the client that the related actor is detached.
connection.send({ from: actor.actor, type: "tabDetached" });
}
this._cleanupConnection(connection);
}
this.emit("devtools-frame-parent-actor-destroyed");
}
/**
* Supported Queries
*/
async connect(args) {
return this.sendQuery("DevToolsFrameParent:connect", args);
}
async disconnect(args) {
return this.sendQuery("DevToolsFrameParent:disconnect", args);
}
async sendPacket(packet, prefix) {
return this.sendQuery("DevToolsFrameParent:packet", { packet, prefix });
}
/**
* JsWindowActor API
*/
async sendQuery(msg, args) {
try {
const res = await super.sendQuery(msg, args);
return res;
} catch (e) {
console.error("Failed to sendQuery in DevToolsFrameParent", msg);
console.error(e.toString());
throw e;
}
}
receiveMessage(data) {
switch (data.name) {
case "DevToolsFrameChild:packet":
return this.emit("packet-received", data);
default:
throw new Error(
"Unsupported message in DevToolsFrameParent: " + data.name
);
}
}
didDestroy() {
this._destroy();
}
}

Просмотреть файл

@ -0,0 +1,34 @@
/* 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";
/**
* This is an alternative to devtools/server/connectors/frame-connector.js that
* will use JS window actors to spawn a DebuggerServer in the content process
* of a remote frame. The communication between the parent process
* DebuggerServer and the content process DebuggerServer will also rely on
* JsWindowActors, via a JsWindowActorTransport.
*
* See DevToolsFrameChild.jsm and DevToolsFrameParent.jsm for the implementation
* of the actors.
*
* Those actors are registered during the DevTools initial startup in
* DevToolsStartup.jsm (see method _registerDevToolsJsWindowActors).
*/
function connectToFrameWithJsWindowActor(
connection,
browsingContext,
onDestroy
) {
const parentActor = browsingContext.currentWindowGlobal.getActor(
"DevToolsFrame"
);
if (onDestroy) {
parentActor.once("devtools-frame-parent-actor-destroyed", onDestroy);
}
return parentActor.connectToFrame(connection);
}
exports.connectToFrameWithJsWindowActor = connectToFrameWithJsWindowActor;

Просмотреть файл

@ -0,0 +1,11 @@
# -*- 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(
'DevToolsFrameChild.jsm',
'DevToolsFrameParent.jsm',
'frame-js-window-actor-connector.js',
)

Просмотреть файл

@ -4,6 +4,10 @@
# 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/.
DIRS += [
'js-window-actor',
]
DevToolsModules(
'content-process-connector.js',
'frame-connector.js',

Просмотреть файл

@ -58,6 +58,8 @@ function DebuggerServerConnection(prefix, transport, socketListener) {
* packets to the server whose actors' names all begin with P + "/".
*/
this._forwardingPrefixes = new Map();
EventEmitter.decorate(this);
}
exports.DebuggerServerConnection = DebuggerServerConnection;
@ -472,7 +474,7 @@ DebuggerServerConnection.prototype = {
}
this._actorPool = null;
EventEmitter.emit(this, "closed", status);
this.emit("closed", status, this.prefix);
this._extraPools.forEach(p => p.destroy());
this._extraPools = null;

Просмотреть файл

@ -33,6 +33,12 @@ loader.lazyRequireGetter(
"devtools/shared/transport/child-transport",
true
);
loader.lazyRequireGetter(
this,
"JsWindowActorTransport",
"devtools/shared/transport/js-window-actor-transport",
true
);
loader.lazyRequireGetter(
this,
"WorkerThreadWorkerDebuggerTransport",
@ -331,6 +337,13 @@ var DebuggerServer = {
return this._onConnection(transport, prefix, true);
},
connectToParentWindowActor(prefix, devtoolsFrameActor) {
this._checkInit();
const transport = new JsWindowActorTransport(devtoolsFrameActor, prefix);
return this._onConnection(transport, prefix, true);
},
/**
* Check if the server is running in the child process.
*/
@ -398,7 +411,7 @@ var DebuggerServer = {
},
/**
* Live list of all currenctly attached child's message managers.
* Live list of all currently attached child's message managers.
*/
_childMessageManagers: new Set(),

Просмотреть файл

@ -0,0 +1,63 @@
/* 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";
/**
* DevTools transport relying on JS Window Actors. This is an experimental
* transport. It is only used when using the JS Window Actor based frame
* connector. In that case this transport will be used to communicate between
* the DebuggerServer living in the parent process and the DebuggerServer
* living in the process of the target frame.
*
* This is intended to be a replacement for child-transport.js which is a
* message-manager based transport.
*/
class JsWindowActorTransport {
constructor(jsWindowActor, prefix) {
this.hooks = null;
this._jsWindowActor = jsWindowActor;
this._prefix = prefix;
this._onPacketReceived = this._onPacketReceived.bind(this);
}
_addListener() {
this._jsWindowActor.on("packet-received", this._onPacketReceived);
}
_removeListener() {
this._jsWindowActor.off("packet-received", this._onPacketReceived);
}
ready() {
this._addListener();
}
close() {
this._removeListener();
this.hooks.onClosed();
}
_onPacketReceived(eventName, { data }) {
const { prefix, packet } = data;
if (prefix === this._prefix) {
this.hooks.onPacket(packet);
}
}
send(packet) {
this._jsWindowActor.sendPacket(packet, this._prefix);
}
startBulkSend() {
throw new Error("startBulkSend not implemented for JsWindowActorTransport");
}
swapBrowser(jsWindowActor) {
throw new Error("swapBrowser not implemented for JsWindowActorTransport");
}
}
exports.JsWindowActorTransport = JsWindowActorTransport;

Просмотреть файл

@ -8,6 +8,7 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
DevToolsModules(
'child-transport.js',
'js-window-actor-transport.js',
'local-transport.js',
'packets.js',
'stream-utils.js',

Просмотреть файл

@ -36,6 +36,11 @@ const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ActorManagerParent",
"resource://gre/modules/ActorManagerParent.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"Services",
@ -306,6 +311,8 @@ DevToolsStartup.prototype = {
const isInitialLaunch =
cmdLine.state == Ci.nsICommandLine.STATE_INITIAL_LAUNCH;
if (isInitialLaunch) {
this._registerDevToolsJsWindowActors();
// Enable devtools for all users on startup (onboarding experiment from Bug 1408969
// is over).
Services.prefs.setBoolPref(DEVTOOLS_ENABLED_PREF, true);
@ -1098,6 +1105,23 @@ DevToolsStartup.prototype = {
this.recorded = true;
},
_registerDevToolsJsWindowActors() {
ActorManagerParent.addActors({
DevToolsFrame: {
parent: {
moduleURI:
"resource://devtools/server/connectors/js-window-actor/DevToolsFrameParent.jsm",
},
child: {
moduleURI:
"resource://devtools/server/connectors/js-window-actor/DevToolsFrameChild.jsm",
},
allFrames: true,
},
});
ActorManagerParent.flush();
},
// Used by tests and the toolbox to register the same key shortcuts in toolboxes loaded
// in a window window.
get KeyShortcuts() {