зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
7375da64fd
Коммит
a0e5d31c31
|
@ -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() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче