Bug 1346316 - Expose content frame global in Browser Content Toolbox. r=ochameau
This makes the Browser Content Toolbox much more convenient by exposing the same
environment a frame script for the currently selected browser would have access
to, which simplifies exploring the content process state in many cases, since
you no longer have to go digging around for the right objects.
MozReview-Commit-ID: KReOqlwuY2b
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -339,30 +339,30 @@ var gDevToolsBrowser = exports.gDevTools
return onNewNode.then(() => {
// Now that the node has been selected, wait until the inspector is
// fully updated.
return inspector.once("inspector-updated");
});
});
},
- _getContentProcessTarget: function (processId) {
+ _getContentProcessTarget: function (processID, outerWindowID) {
// Create a DebuggerServer in order to connect locally to it
if (!DebuggerServer.initialized) {
DebuggerServer.init();
DebuggerServer.addBrowserActors();
}
DebuggerServer.allowChromeProcess = true;
let transport = DebuggerServer.connectPipe();
let client = new DebuggerClient(transport);
let deferred = defer();
client.connect().then(() => {
- client.getProcess(processId)
+ client.getProcess(processID, outerWindowID)
.then(response => {
let options = {
form: response.form,
client: client,
chrome: true,
isTabActor: false
};
return TargetFactory.forRemoteTab(options);
@@ -380,27 +380,29 @@ var gDevToolsBrowser = exports.gDevTools
return deferred.promise;
},
// Used by menus.js
openContentProcessToolbox: function (gBrowser) {
let { childCount } = Services.ppmm;
// Get the process message manager for the current tab
- let mm = gBrowser.selectedBrowser.messageManager.processMessageManager;
- let processId = null;
+ let browser = gBrowser.selectedBrowser;
+ let mm = browser.messageManager.processMessageManager;
+ let outerWindowID = browser.outerWindowID;
+ let processID = null;
for (let i = 1; i < childCount; i++) {
let child = Services.ppmm.getChildAt(i);
if (child == mm) {
- processId = i;
+ processID = i;
break;
}
}
- if (processId) {
- this._getContentProcessTarget(processId)
+ if (processID) {
+ this._getContentProcessTarget(processID, outerWindowID)
.then(target => {
// Display a new toolbox, in a new window, with debugger by default
return gDevTools.showToolbox(target, "jsdebugger",
Toolbox.HostType.WINDOW);
});
} else {
let msg = L10N.getStr("toolbox.noContentProcessForTab.message");
Services.prompt.alert(null, "", msg);
--- a/devtools/server/actors/child-process.js
+++ b/devtools/server/actors/child-process.js
@@ -7,36 +7,53 @@
const { Cc, Ci, Cu } = require("chrome");
const { ChromeDebuggerActor } = require("devtools/server/actors/script");
const { WebConsoleActor } = require("devtools/server/actors/webconsole");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
const { ActorPool } = require("devtools/server/main");
const { assert } = require("devtools/shared/DevToolsUtils");
const { TabSources } = require("./utils/TabSources");
+const Services = require("Services");
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
-function ChildProcessActor(connection) {
+function ChildProcessActor(connection, outerWindowID) {
this.conn = connection;
this._contextPool = new ActorPool(this.conn);
this.conn.addActorPool(this._contextPool);
this.threadActor = null;
// Use a see-everything debugger
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: dbg => dbg.findAllGlobals(),
shouldAddNewGlobalAsDebuggee: global => true
});
+ let sandboxPrototype = {};
+ if (outerWindowID) {
+ let currentWindow = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (currentWindow) {
+ let contentMM = currentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ sandboxPrototype = contentMM;
+ }
+ }
+
// Scope into which the webconsole executes:
- // An empty sandbox with chrome privileges
+ // If an outerWindowID was supplied, then a chrome sandbox that has the content frame
+ // message manager as its prototype (similar to the frame script environement).
+ // If there's no outerWindowID, then this will be an empty chrome sandbox.
let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
- let sandbox = Cu.Sandbox(systemPrincipal);
+ let sandbox = Cu.Sandbox(systemPrincipal, {
+ sandboxPrototype,
+ });
this._consoleScope = sandbox;
this._workerList = null;
this._workerActorPool = null;
this._onWorkerListChanged = this._onWorkerListChanged.bind(this);
}
exports.ChildProcessActor = ChildProcessActor;
--- a/devtools/server/actors/root.js
+++ b/devtools/server/actors/root.js
@@ -511,17 +511,17 @@ RootActor.prototype = {
};
},
onProcessListChanged: function () {
this.conn.send({ from: this.actorID, type: "processListChanged" });
this._parameters.processList.onListChanged = null;
},
- onGetProcess: function (request) {
+ onGetProcess: async function (request) {
if (!DebuggerServer.allowChromeProcess) {
return { error: "forbidden",
message: "You are not allowed to debug chrome." };
}
if (("id" in request) && typeof (request.id) != "number") {
return { error: "wrongParameter",
message: "getProcess requires a valid `id` attribute." };
}
@@ -546,20 +546,20 @@ RootActor.prototype = {
}
let form = this._processActors.get(id);
if (form) {
return { form };
}
let onDestroy = () => {
this._processActors.delete(id);
};
- return DebuggerServer.connectToContent(this.conn, mm, onDestroy).then(formResult => {
- this._processActors.set(id, formResult);
- return { form: formResult };
- });
+ form = await DebuggerServer.connectToContent(this.conn, mm, onDestroy,
+ request.outerWindowID);
+ this._processActors.set(id, form);
+ return { form };
},
/* This is not in the spec, but it's used by tests. */
onEcho: function (request) {
/*
* Request packets are frozen. Copy request, so that
* DebuggerServerConnection.onPacket can attach a 'from' property.
*/
--- a/devtools/server/content-server.jsm
+++ b/devtools/server/content-server.jsm
@@ -47,30 +47,30 @@ function setupServer(mm) {
});
return gLoader;
}
function init(msg) {
let mm = msg.target;
mm.QueryInterface(Ci.nsISyncMessageSender);
- let prefix = msg.data.prefix;
+ let { prefix, outerWindowID } = msg.data;
// Setup a server if none started yet
let loader = setupServer(mm);
// Connect both parent/child processes debugger servers RDP via message
// managers
let { DebuggerServer } = loader.require("devtools/server/main");
let conn = DebuggerServer.connectToParent(prefix, mm);
conn.parentMessageManager = mm;
let { ChildProcessActor } =
loader.require("devtools/server/actors/child-process");
let { ActorPool } = loader.require("devtools/server/main");
- let actor = new ChildProcessActor(conn);
+ let actor = new ChildProcessActor(conn, outerWindowID);
let actorPool = new ActorPool(conn);
actorPool.addActor(actor);
conn.addActorPool(actorPool);
let response = { actor: actor.form() };
mm.sendAsyncMessage("debug:content-process-actor", response);
}
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -722,17 +722,17 @@ var DebuggerServer = {
let transport = isWorker ?
new WorkerDebuggerTransport(scopeOrManager, prefix) :
new ChildDebuggerTransport(scopeOrManager, prefix);
return this._onConnection(transport, prefix, true);
},
- connectToContent(connection, mm, onDestroy) {
+ connectToContent(connection, mm, onDestroy, outerWindowID) {
let deferred = SyncPromise.defer();
let prefix = connection.allocID("content-process");
let actor, childTransport;
mm.addMessageListener("debug:content-process-actor", function listener(msg) {
// Arbitrarily choose the first content process to reply
// XXX: This code needs to be updated if we use more than one content process
@@ -751,17 +751,18 @@ var DebuggerServer = {
dumpn("establishing forwarding for process with prefix " + prefix);
actor = msg.json.actor;
deferred.resolve(actor);
});
mm.sendAsyncMessage("DevTools:InitDebuggerServer", {
- prefix: prefix
+ prefix,
+ outerWindowID,
});
function onClose() {
Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
events.off(connection, "closed", onClose);
if (childTransport) {
// If we have a child transport, the actor has already
// been created. We need to stop using this message manager.
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -612,27 +612,34 @@ DebuggerClient.prototype = {
return [response, traceClient];
});
},
/**
* Fetch the ChromeActor for the main process or ChildProcessActor for a
* a given child process ID.
*
- * @param number id
+ * @param number processID
* The ID for the process to attach (returned by `listProcesses`).
* Connected to the main process if omitted, or is 0.
+ * @param number outerWindowID
+ * The outerWindowID for the currently selected browser in the process.
+ * Optional. If supplied, the chrome scope of the browser will be
+ * accessible in the console.
*/
- getProcess: function (id) {
+ getProcess: function (processID, outerWindowID) {
let packet = {
to: "root",
type: "getProcess"
};
- if (typeof (id) == "number") {
- packet.id = id;
+ if (typeof processID == "number") {
+ packet.id = processID;
+ }
+ if (typeof outerWindowID == "number") {
+ packet.outerWindowID = outerWindowID;
}
return this.request(packet);
},
/**
* Release an object actor.
*
* @param string actor