Bug 1039952 - Part 0: Make the parent actors manage the set of debuggees. r=fitzgen

This commit is contained in:
Nick Fitzgerald 2014-07-28 15:01:00 +02:00
parent 6c42c43a3f
commit 6ba4a89244
13 changed files with 411 additions and 418 deletions

View File

@ -112,7 +112,7 @@ EventEmitter.prototype = {
/**
* Emit an event. All arguments to this method will
* be sent to listner functions.
* be sent to listener functions.
*/
emit: function EventEmitter_emit(aEvent) {
this.logEvent(aEvent, arguments);

View File

@ -8,3 +8,4 @@ libs::
$(INSTALL) $(IFLAGS1) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
$(INSTALL) $(IFLAGS1) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/server
$(INSTALL) $(IFLAGS1) $(srcdir)/actors/*.js $(FINAL_TARGET)/modules/devtools/server/actors
$(INSTALL) $(IFLAGS1) $(srcdir)/actors/utils/*.js $(FINAL_TARGET)/modules/devtools/server/actors/utils

View File

@ -63,7 +63,7 @@ let MemoryActor = protocol.ActorClass({
} catch (e) {
console.error(e);
let url = this.tabActor.url;
console.error("Error getting size of "+url);
console.error("Error getting size of " + url);
}
return result;

View File

@ -11,6 +11,7 @@ const Services = require("Services");
const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
const { DebuggerServer } = require("devtools/server/main");
const { dumpProtocolSpec } = require("devtools/server/protocol");
const makeDebugger = require("./utils/make-debugger");
/* Root actor for the remote debugging protocol. */
@ -92,6 +93,12 @@ function RootActor(aConnection, aParameters) {
this._onTabListChanged = this.onTabListChanged.bind(this);
this._onAddonListChanged = this.onAddonListChanged.bind(this);
this._extraActors = {};
// This creates a Debugger instance for chrome debugging all globals.
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: dbg => dbg.findAllGlobals(),
shouldAddNewGlobalAsDebuggee: () => true
});
}
RootActor.prototype = {

View File

@ -16,6 +16,7 @@ const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
const promise = require("promise");
const Debugger = require("Debugger");
const xpcInspector = require("xpcInspector");
const mapURIToAddonID = require("./utils/map-uri-to-addon-id");
const { defer, resolve, reject, all } = require("devtools/toolkit/deprecated-sync-thenables");
const { CssLogic } = require("devtools/styleinspector/css-logic");
@ -24,8 +25,6 @@ DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
});
let B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
"Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
"Float64Array"];
@ -34,34 +33,6 @@ let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
// collections, etc.
let OBJECT_PREVIEW_MAX_ITEMS = 10;
let addonManager = null;
/**
* This is a wrapper around amIAddonManager.mapURIToAddonID which always returns
* false on B2G to avoid loading the add-on manager there and reports any
* exceptions rather than throwing so that the caller doesn't have to worry
* about them.
*/
function mapURIToAddonID(uri, id) {
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT ||
(Services.appinfo.ID || undefined) == B2G_ID) {
return false;
}
if (!addonManager) {
addonManager = Cc["@mozilla.org/addons/integration;1"].
getService(Ci.amIAddonManager);
}
try {
return addonManager.mapURIToAddonID(uri, id);
}
catch (e) {
DevToolsUtils.reportException("mapURIToAddonID", e);
return false;
}
}
/**
* BreakpointStore objects keep track of all breakpoints that get set so that we
* can reset them when the same script is introduced to the thread again (such
@ -481,41 +452,53 @@ EventLoop.prototype = {
/**
* JSD2 actors.
*/
/**
* Creates a ThreadActor.
*
* ThreadActors manage a JSInspector object and manage execution/inspection
* of debuggees.
*
* @param aHooks object
* An object with preNest and postNest methods for calling when entering
* and exiting a nested event loop.
* @param aParent object
* This |ThreadActor|'s parent actor. It must implement the following
* properties:
* - url: The URL string of the debuggee.
* - window: The global window object.
* - preNest: Function called before entering a nested event loop.
* - postNest: Function called after exiting a nested event loop.
* - makeDebugger: A function that takes no arguments and instantiates
* a Debugger that manages its globals on its own.
* @param aGlobal object [optional]
* An optional (for content debugging only) reference to the content
* window.
*/
function ThreadActor(aHooks, aGlobal)
function ThreadActor(aParent, aGlobal)
{
this._state = "detached";
this._frameActors = [];
this._hooks = aHooks;
this.global = aGlobal;
// A map of actorID -> actor for breakpoints created and managed by the server.
this._hiddenBreakpoints = new Map();
this.findGlobals = this.globalManager.findGlobals.bind(this);
this.onNewGlobal = this.globalManager.onNewGlobal.bind(this);
this.onNewSource = this.onNewSource.bind(this);
this._allEventsListener = this._allEventsListener.bind(this);
this._parent = aParent;
this._dbg = null;
this._gripDepth = 0;
this._threadLifetimePool = null;
this._tabClosed = false;
this._options = {
useSourceMaps: false,
autoBlackBox: false
};
this._gripDepth = 0;
this._threadLifetimePool = null;
this._tabClosed = false;
// A map of actorID -> actor for breakpoints created and managed by the
// server.
this._hiddenBreakpoints = new Map();
this.global = aGlobal;
this._allEventsListener = this._allEventsListener.bind(this);
this.onNewGlobal = this.onNewGlobal.bind(this);
this.onNewSource = this.onNewSource.bind(this);
this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
this.onNewScript = this.onNewScript.bind(this);
}
/**
@ -530,6 +513,23 @@ ThreadActor.prototype = {
actorPrefix: "context",
get dbg() {
if (!this._dbg) {
this._dbg = this._parent.makeDebugger();
this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook;
this._dbg.onDebuggerStatement = this.onDebuggerStatement;
this._dbg.onNewScript = this.onNewScript;
this._dbg.on("newGlobal", this.onNewGlobal);
// Keep the debugger disabled until a client attaches.
this._dbg.enabled = this._state != "detached";
}
return this._dbg;
},
get globalDebugObject() {
return this.dbg.makeGlobalObjectReference(this._parent.window);
},
get state() { return this._state; },
get attached() this.state == "attached" ||
this.state == "running" ||
@ -618,126 +618,23 @@ ThreadActor.prototype = {
* Remove all debuggees and clear out the thread's sources.
*/
clearDebuggees: function () {
if (this.dbg) {
if (this._dbg) {
this.dbg.removeAllDebuggees();
}
this._sources = null;
},
/**
* Add a debuggee global to the Debugger object.
*
* @returns the Debugger.Object that corresponds to the global.
* Listener for our |Debugger|'s "newGlobal" event.
*/
addDebuggee: function (aGlobal) {
let globalDebugObject;
try {
globalDebugObject = this.dbg.addDebuggee(aGlobal);
} catch (e) {
// Ignore attempts to add the debugger's compartment as a debuggee.
dumpn("Ignoring request to add the debugger's compartment as a debuggee");
}
return globalDebugObject;
},
/**
* Initialize the Debugger.
*/
_initDebugger: function () {
this.dbg = new Debugger();
this.dbg.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
this.dbg.onDebuggerStatement = this.onDebuggerStatement.bind(this);
this.dbg.onNewScript = this.onNewScript.bind(this);
this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
// Keep the debugger disabled until a client attaches.
this.dbg.enabled = this._state != "detached";
},
/**
* Remove a debuggee global from the JSInspector.
*/
removeDebugee: function (aGlobal) {
try {
this.dbg.removeDebuggee(aGlobal);
} catch(ex) {
// XXX: This debuggee has code currently executing on the stack,
// we need to save this for later.
}
},
/**
* Add the provided window and all windows in its frame tree as debuggees.
*
* @returns the Debugger.Object that corresponds to the window.
*/
_addDebuggees: function (aWindow) {
let globalDebugObject = this.addDebuggee(aWindow);
let frames = aWindow.frames;
if (frames) {
for (let i = 0; i < frames.length; i++) {
this._addDebuggees(frames[i]);
}
}
return globalDebugObject;
},
/**
* An object that will be used by ThreadActors to tailor their behavior
* depending on the debugging context being required (chrome or content).
*/
globalManager: {
findGlobals: function () {
const { getContentGlobals } = require("devtools/server/content-globals");
this.globalDebugObject = this._addDebuggees(this.global);
// global may not be a window
try {
getContentGlobals({
'inner-window-id': getInnerId(this.global)
}).forEach(this.addDebuggee.bind(this));
}
catch(e) {}
},
/**
* A function that the engine calls when a new global object
* (for example a sandbox) has been created.
*
* @param aGlobal Debugger.Object
* The new global object that was created.
*/
onNewGlobal: function (aGlobal) {
let useGlobal = (aGlobal.hostAnnotations &&
aGlobal.hostAnnotations.type == "document" &&
aGlobal.hostAnnotations.element === this.global);
// check if the global is a sdk page-mod sandbox
if (!useGlobal) {
let metadata = {};
let id = "";
try {
id = getInnerId(this.global);
metadata = Cu.getSandboxMetadata(aGlobal.unsafeDereference());
}
catch (e) {}
useGlobal = (metadata['inner-window-id'] && metadata['inner-window-id'] == id);
}
// Content debugging only cares about new globals in the contant window,
// like iframe children.
if (useGlobal) {
this.addDebuggee(aGlobal);
// Notify the client.
this.conn.send({
from: this.actorID,
type: "newGlobal",
// TODO: after bug 801084 lands see if we need to JSONify this.
hostAnnotations: aGlobal.hostAnnotations
});
}
}
onNewGlobal: function (aGlobal) {
// Notify the client.
this.conn.send({
from: this.actorID,
type: "newGlobal",
// TODO: after bug 801084 lands see if we need to JSONify this.
hostAnnotations: aGlobal.hostAnnotations
});
},
disconnect: function () {
@ -759,11 +656,11 @@ ThreadActor.prototype = {
this._prettyPrintWorker = null;
}
if (!this.dbg) {
if (!this._dbg) {
return;
}
this.dbg.enabled = false;
this.dbg = null;
this._dbg.enabled = false;
this._dbg = null;
},
/**
@ -792,15 +689,12 @@ ThreadActor.prototype = {
// Initialize an event loop stack. This can't be done in the constructor,
// because this.conn is not yet initialized by the actor pool at that time.
this._nestedEventLoops = new EventLoopStack({
hooks: this._hooks,
hooks: this._parent,
connection: this.conn,
thread: this
});
if (!this.dbg) {
this._initDebugger();
}
this.findGlobals();
this.dbg.addDebuggees();
this.dbg.enabled = true;
try {
// Put ourselves in the paused state.
@ -1132,8 +1026,8 @@ ThreadActor.prototype = {
// different tabs or multiple debugger clients connected to the same tab)
// only allow resumption in a LIFO order.
if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl
&& (this._nestedEventLoops.lastPausedUrl !== this._hooks.url
|| this._nestedEventLoops.lastConnection !== this.conn)) {
&& (this._nestedEventLoops.lastPausedUrl !== this._parent.url
|| this._nestedEventLoops.lastConnection !== this.conn)) {
return {
error: "wrongOrder",
message: "trying to resume in the wrong order.",
@ -1925,6 +1819,7 @@ ThreadActor.prototype = {
aFrame.onStep = undefined;
aFrame.onPop = undefined;
}
// Clear DOM event breakpoints.
// XPCShell tests don't use actual DOM windows for globals and cause
// removeListenerForAllEvents to throw.
@ -4836,13 +4731,13 @@ Object.defineProperty(Debugger.Frame.prototype, "line", {
* is associated. (Currently unused, but required to make this
* constructor usable with addGlobalActor.)
*
* @param aHooks object
* An object with preNest and postNest methods for calling when entering
* and exiting a nested event loop.
* @param aParent object
* This actor's parent actor. See ThreadActor for a list of expected
* properties.
*/
function ChromeDebuggerActor(aConnection, aHooks)
function ChromeDebuggerActor(aConnection, aParent)
{
ThreadActor.call(this, aHooks);
ThreadActor.call(this, aParent);
}
ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype);
@ -4857,38 +4752,7 @@ update(ChromeDebuggerActor.prototype, {
* Override the eligibility check for scripts and sources to make sure every
* script and source with a URL is stored when debugging chrome.
*/
_allowSource: function(aSourceURL) !!aSourceURL,
/**
* An object that will be used by ThreadActors to tailor their behavior
* depending on the debugging context being required (chrome or content).
* The methods that this object provides must be bound to the ThreadActor
* before use.
*/
globalManager: {
findGlobals: function () {
// Add every global known to the debugger as debuggee.
this.dbg.addAllGlobalsAsDebuggees();
},
/**
* A function that the engine calls when a new global object has been
* created.
*
* @param aGlobal Debugger.Object
* The new global object that was created.
*/
onNewGlobal: function (aGlobal) {
this.addDebuggee(aGlobal);
// Notify the client.
this.conn.send({
from: this.actorID,
type: "newGlobal",
// TODO: after bug 801084 lands see if we need to JSONify this.
hostAnnotations: aGlobal.hostAnnotations
});
}
}
_allowSource: aSourceURL => !!aSourceURL
});
exports.ChromeDebuggerActor = ChromeDebuggerActor;
@ -4902,18 +4766,12 @@ exports.ChromeDebuggerActor = ChromeDebuggerActor;
* is associated. (Currently unused, but required to make this
* constructor usable with addGlobalActor.)
*
* @param aHooks object
* An object with preNest and postNest methods for calling
* when entering and exiting a nested event loops.
*
* @param aAddonID string
* ID of the add-on this actor will debug. It will be used to
* filter out globals marked for debugging.
* @param aParent object
* This actor's parent actor. See ThreadActor for a list of expected
* properties.
*/
function AddonThreadActor(aConnect, aHooks, aAddonID) {
this.addonID = aAddonID;
ThreadActor.call(this, aHooks);
function AddonThreadActor(aConnect, aParent) {
ThreadActor.call(this, aParent);
}
AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
@ -4935,7 +4793,7 @@ update(AddonThreadActor.prototype, {
return false;
}
// XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it
// XPIProvider.jsm evals some code in every add-on's bootstrap.js. Hide it.
if (aSourceURL == "resource://gre/modules/addons/XPIProvider.jsm") {
return false;
}
@ -4943,97 +4801,6 @@ update(AddonThreadActor.prototype, {
return true;
},
/**
* An object that will be used by ThreadActors to tailor their
* behaviour depending on the debugging context being required (chrome,
* addon or content). The methods that this object provides must
* be bound to the ThreadActor before use.
*/
globalManager: {
findGlobals: function ADA_findGlobals() {
for (let global of this.dbg.findAllGlobals()) {
if (this._checkGlobal(global)) {
this.dbg.addDebuggee(global);
}
}
},
/**
* A function that the engine calls when a new global object
* has been created.
*
* @param aGlobal Debugger.Object
* The new global object that was created.
*/
onNewGlobal: function ADA_onNewGlobal(aGlobal) {
if (this._checkGlobal(aGlobal)) {
this.addDebuggee(aGlobal);
// Notify the client.
this.conn.send({
from: this.actorID,
type: "newGlobal",
// TODO: after bug 801084 lands see if we need to JSONify this.
hostAnnotations: aGlobal.hostAnnotations
});
}
}
},
/**
* Checks if the provided global belongs to the debugged add-on.
*
* @param aGlobal Debugger.Object
*/
_checkGlobal: function ADA_checkGlobal(aGlobal) {
let obj = null;
try {
obj = aGlobal.unsafeDereference();
}
catch (e) {
// Because of bug 991399 we sometimes get bad objects here. If we can't
// dereference them then they won't be useful to us
return false;
}
try {
// This will fail for non-Sandbox objects, hence the try-catch block.
let metadata = Cu.getSandboxMetadata(obj);
if (metadata) {
return metadata.addonID === this.addonID;
}
} catch (e) {
}
if (obj instanceof Ci.nsIDOMWindow) {
let id = {};
if (mapURIToAddonID(obj.document.documentURIObject, id)) {
return id.value === this.addonID;
}
return false;
}
// Check the global for a __URI__ property and then try to map that to an
// add-on
let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
let uri;
try {
uri = Services.io.newURI(uridescriptor.value, null, null);
}
catch (e) {
DevToolsUtils.reportException("AddonThreadActor.prototype._checkGlobal",
new Error("Invalid URI: " + uridescriptor.value));
return false;
}
let id = {};
if (mapURIToAddonID(uri, id)) {
return id.value === this.addonID;
}
}
return false;
}
});
exports.AddonThreadActor = AddonThreadActor;

View File

@ -67,15 +67,20 @@ const TRACE_TYPES = new Set([
]);
/**
* Creates a TraceActor. TraceActor provides a stream of function
* Creates a TracerActor. TracerActor provides a stream of function
* call/return packets to a remote client gathering a full trace.
*/
function TraceActor(aConn, aParentActor)
function TracerActor(aConn, aParent)
{
this._dbg = null;
this._parent = aParent;
this._attached = false;
this._activeTraces = new MapStack();
this._totalTraces = 0;
this._startTime = 0;
this._sequence = 0;
this._bufferSendTimer = null;
this._buffer = [];
// Keep track of how many different trace requests have requested what kind of
// tracing info. This way we can minimize the amount of data we are collecting
@ -85,27 +90,25 @@ function TraceActor(aConn, aParentActor)
this._requestsForTraceType[type] = 0;
}
this._sequence = 0;
this._bufferSendTimer = null;
this._buffer = [];
this.onEnterFrame = this.onEnterFrame.bind(this);
this.onExitFrame = this.onExitFrame.bind(this);
// aParentActor.window might be an Xray for a window, but it might also be a
// double-wrapper for a Sandbox. We want to unwrap the latter but not the
// former.
this.global = aParentActor.window;
if (!Cu.isXrayWrapper(this.global)) {
this.global = this.global.wrappedJSObject;
}
}
TraceActor.prototype = {
TracerActor.prototype = {
actorPrefix: "trace",
get attached() { return this._attached; },
get idle() { return this._attached && this._activeTraces.size === 0; },
get tracing() { return this._attached && this._activeTraces.size > 0; },
get dbg() {
if (!this._dbg) {
this._dbg = this._parent.makeDebugger();
this._dbg.onEnterFrame = this.onEnterFrame;
}
return this._dbg;
},
/**
* Buffer traces and only send them every BUFFER_SEND_DELAY milliseconds.
*/
@ -123,74 +126,6 @@ TraceActor.prototype = {
}
},
/**
* Initializes a Debugger instance and adds listeners to it.
*/
_initDebugger: function() {
this.dbg = new Debugger();
this.dbg.onEnterFrame = this.onEnterFrame.bind(this);
this.dbg.onNewGlobalObject = this.globalManager.onNewGlobal.bind(this);
this.dbg.enabled = false;
},
/**
* Add a debuggee global to the Debugger object.
*/
_addDebuggee: function(aGlobal) {
try {
this.dbg.addDebuggee(aGlobal);
} catch (e) {
// Ignore attempts to add the debugger's compartment as a debuggee.
DevToolsUtils.reportException("TraceActor",
new Error("Ignoring request to add the debugger's "
+ "compartment as a debuggee"));
}
},
/**
* Add the provided window and all windows in its frame tree as debuggees.
*/
_addDebuggees: function(aWindow) {
this._addDebuggee(aWindow);
let frames = aWindow.frames;
if (frames) {
for (let i = 0; i < frames.length; i++) {
this._addDebuggees(frames[i]);
}
}
},
/**
* An object used by TraceActors to tailor their behavior depending
* on the debugging context required (chrome or content).
*/
globalManager: {
/**
* Adds all globals in the global object as debuggees.
*/
findGlobals: function() {
this._addDebuggees(this.global);
},
/**
* A function that the engine calls when a new global object has been
* created. Adds the global object as a debuggee if it is in the content
* window.
*
* @param aGlobal Debugger.Object
* The new global object that was created.
*/
onNewGlobal: function(aGlobal) {
// Content debugging only cares about new globals in the content
// window, like iframe children.
if (aGlobal.hostAnnotations &&
aGlobal.hostAnnotations.type == "document" &&
aGlobal.hostAnnotations.element === this.global) {
this._addDebuggee(aGlobal);
}
},
},
/**
* Handle a protocol request to attach to the trace actor.
*
@ -205,11 +140,7 @@ TraceActor.prototype = {
};
}
if (!this.dbg) {
this._initDebugger();
this.globalManager.findGlobals.call(this);
}
this.dbg.addDebuggees();
this._attached = true;
return {
@ -230,10 +161,12 @@ TraceActor.prototype = {
this.onStopTrace();
}
this.dbg = null;
this._dbg = null;
this._attached = false;
return { type: "detached" };
return {
type: "detached"
};
},
/**
@ -307,7 +240,11 @@ TraceActor.prototype = {
this.dbg.enabled = false;
}
return { type: "stoppedTrace", why: "requested", name: name };
return {
type: "stoppedTrace",
why: "requested",
name
};
},
// JS Debugger API hooks.
@ -443,19 +380,19 @@ TraceActor.prototype = {
/**
* The request types this actor can handle.
*/
TraceActor.prototype.requestTypes = {
"attach": TraceActor.prototype.onAttach,
"detach": TraceActor.prototype.onDetach,
"startTrace": TraceActor.prototype.onStartTrace,
"stopTrace": TraceActor.prototype.onStopTrace
TracerActor.prototype.requestTypes = {
"attach": TracerActor.prototype.onAttach,
"detach": TracerActor.prototype.onDetach,
"startTrace": TracerActor.prototype.onStartTrace,
"stopTrace": TracerActor.prototype.onStopTrace
};
exports.register = function(handle) {
handle.addTabActor(TraceActor, "traceActor");
handle.addTabActor(TracerActor, "traceActor");
};
exports.unregister = function(handle) {
handle.removeTabActor(TraceActor, "traceActor");
handle.removeTabActor(TracerActor, "traceActor");
};
@ -610,7 +547,7 @@ function createValueSnapshot(aValue, aDetailed=false) {
? detailedObjectSnapshot(aValue)
: objectSnapshot(aValue);
default:
DevToolsUtils.reportException("TraceActor",
DevToolsUtils.reportException("TracerActor",
new Error("Failed to provide a grip for: " + aValue));
return null;
}

View File

@ -0,0 +1,100 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 EventEmitter = require("devtools/toolkit/event-emitter");
const Debugger = require("Debugger");
const { reportException } = require("devtools/toolkit/DevToolsUtils");
/**
* Multiple actors that use a |Debugger| instance come in a few versions, each
* with a different set of debuggees. One version for content tabs (globals
* within a tab), one version for chrome debugging (all globals), and sometimes
* a third version for addon debugging (chrome globals the addon is loaded in
* and content globals the addon injects scripts into). The |makeDebugger|
* function helps us avoid repeating the logic for finding and maintaining the
* correct set of globals for a given |Debugger| instance across each version of
* all of our actors.
*
* The |makeDebugger| function expects a single object parameter with the
* following properties:
*
* @param Function findDebuggees
* Called with one argument: a |Debugger| instance. This function should
* return an iterable of globals to be added to the |Debugger|
* instance. The globals may be wrapped in a |Debugger.Object|, or
* unwrapped.
*
* @param Function shouldAddNewGlobalAsDebuggee
* Called with one argument: a |Debugger.Object| wrapping a global
* object. This function must return |true| if the global object should
* be added as debuggee, and |false| otherwise.
*
* @returns Debugger
* Returns a |Debugger| instance that can manage its set of debuggee
* globals itself and is decorated with the |EventEmitter| class.
*
* Events emitted by the returned |Debugger| instance:
*
* - "newGlobal": Emitted when a new global has been added as a
* debuggee. Passes the |Debugger.Object| wrapping the new
* debuggee global to listeners.
*
* Existing |Debugger| properties set on the returned |Debugger|
* instance:
*
* - onNewGlobalObject: The |Debugger| will automatically add new
* globals as debuggees if calling |shouldAddNewGlobalAsDebuggee|
* with the global returns true.
*
* - uncaughtExceptionHook: The |Debugger| already has an error
* reporter attached to |uncaughtExceptionHook|, so if any
* |Debugger| hooks fail, the error will be reported.
*
* New properties set on the returned |Debugger| instance:
*
* - addDebuggees: A function which takes no arguments. It adds all
* current globals that should be debuggees (as determined by
* |findDebuggees|) to the |Debugger| instance.
*/
module.exports = function makeDebugger({ findDebuggees, shouldAddNewGlobalAsDebuggee }) {
const dbg = new Debugger();
EventEmitter.decorate(dbg);
dbg.uncaughtExceptionHook = reportDebuggerHookException;
dbg.onNewGlobalObject = global => {
if (shouldAddNewGlobalAsDebuggee(global)) {
safeAddDebuggee(dbg, global);
}
};
dbg.addDebuggees = () => {
for (let global of findDebuggees(dbg)) {
safeAddDebuggee(dbg, global);
}
};
return dbg;
};
const reportDebuggerHookException = e => reportException("Debugger Hook", e);
/**
* Add |global| as a debuggee to |dbg|, handling error cases.
*/
function safeAddDebuggee(dbg, global) {
try {
let wrappedGlobal = dbg.addDebuggee(global);
if (wrappedGlobal) {
dbg.emit("newGlobal", wrappedGlobal);
}
} catch (e) {
// Ignoring attempt to add the debugger's compartment as a debuggee.
}
}

View File

@ -0,0 +1,47 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
const Services = require("Services");
const { Cc, Ci } = require("chrome");
Object.defineProperty(this, "addonManager", {
get: (function () {
let cached;
return () => cached
? cached
: (cached = Cc["@mozilla.org/addons/integration;1"]
.getService(Ci.amIAddonManager))
}())
});
const B2G_ID = "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}";
/**
* This is a wrapper around amIAddonManager.mapURIToAddonID which always returns
* false on B2G to avoid loading the add-on manager there and reports any
* exceptions rather than throwing so that the caller doesn't have to worry
* about them.
*/
module.exports = function mapURIToAddonID(uri, id) {
if (!Services.appinfo
|| Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT
|| Services.appinfo.ID == B2G_ID
|| !uri
|| !addonManager) {
return false;
}
try {
return addonManager.mapURIToAddonID(uri, id);
}
catch (e) {
DevToolsUtils.reportException("mapURIToAddonID", e);
return false;
}
};

View File

@ -14,6 +14,8 @@ let { AddonThreadActor, ThreadActor } = require("devtools/server/actors/script")
let { DebuggerServer } = require("devtools/server/main");
let DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
let { dbg_assert } = DevToolsUtils;
let makeDebugger = require("./utils/make-debugger");
let mapURIToAddonID = require("./utils/map-uri-to-addon-id");
let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -71,6 +73,29 @@ function sendShutdownEvent() {
exports.sendShutdownEvent = sendShutdownEvent;
/**
* Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
* become a dead object, return |undefined|.
*
* @param Debugger.Object wrappedGlobal
* The |Debugger.Object| which wraps a global.
*
* @returns {Object|undefined}
* Returns the unwrapped global object or |undefined| if unwrapping
* failed.
*/
const unwrapDebuggerObjectGlobal = wrappedGlobal => {
let global;
try {
global = wrappedGlobal.unsafeDereference();
}
catch (e) {
// Because of bug 991399 we sometimes get bad objects here. If we
// can't dereference them then they won't be useful to us.
}
return global;
};
/**
* Construct a root actor appropriate for use in a server running in a
* browser. The returned root actor:
@ -520,6 +545,13 @@ function TabActor(aConnection)
this._extraActors = {};
this._exited = false;
this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: () => this.windows,
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
});
this.traits = { reconfigure: true };
}
@ -715,6 +747,37 @@ TabActor.prototype = {
this._exited = true;
},
/**
* Return true if the given global is associated with this tab and should be
* added as a debuggee, false otherwise.
*/
_shouldAddNewGlobalAsDebuggee: function (wrappedGlobal) {
if (wrappedGlobal.hostAnnotations &&
wrappedGlobal.hostAnnotations.type == "document" &&
wrappedGlobal.hostAnnotations.element === this.window) {
return true;
}
let global = unwrapDebuggerObjectGlobal(wrappedGlobal);
if (!global) {
return false;
}
// Check if the global is a sdk page-mod sandbox.
let metadata = {};
let id = "";
try {
id = getInnerId(this.window);
metadata = Cu.getSandboxMetadata(global);
}
catch (e) {}
if (metadata["inner-window-id"] && metadata["inner-window-id"] == id) {
return true;
}
return false;
},
/* Support for DebuggerServer.addTabActor. */
_createExtraActors: createExtraActors,
_appendExtraActors: appendExtraActors,
@ -999,7 +1062,7 @@ TabActor.prototype = {
// Refresh the debuggee list when a new window object appears (top window or
// iframe).
if (threadActor.attached) {
threadActor.findGlobals();
threadActor.dbg.addDebuggees();
}
},
@ -1287,6 +1350,14 @@ function BrowserAddonActor(aConnection, aAddon) {
this.conn.addActorPool(this._contextPool);
this._threadActor = null;
this._global = null;
this._shouldAddNewGlobalAsDebuggee = this._shouldAddNewGlobalAsDebuggee.bind(this);
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: this._findDebuggees.bind(this),
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee
});
AddonManager.addAddonListener(this);
}
@ -1373,8 +1444,7 @@ BrowserAddonActor.prototype = {
}
if (!this.attached) {
this._threadActor = new AddonThreadActor(this.conn, this,
this._addon.id);
this._threadActor = new AddonThreadActor(this.conn, this);
this._contextPool.addActor(this._threadActor);
}
@ -1413,6 +1483,61 @@ BrowserAddonActor.prototype = {
windowUtils.resumeTimeouts();
windowUtils.suppressEventHandling(false);
}
},
/**
* Return true if the given global is associated with this addon and should be
* added as a debuggee, false otherwise.
*/
_shouldAddNewGlobalAsDebuggee: function (aGlobal) {
const global = unwrapDebuggerObjectGlobal(aGlobal);
try {
// This will fail for non-Sandbox objects, hence the try-catch block.
let metadata = Cu.getSandboxMetadata(global);
if (metadata) {
return metadata.addonID === this.id;
}
} catch (e) {}
if (global instanceof Ci.nsIDOMWindow) {
let id = {};
if (mapURIToAddonID(global.document.documentURIObject, id)) {
return id.value === this.id;
}
return false;
}
// Check the global for a __URI__ property and then try to map that to an
// add-on
let uridescriptor = aGlobal.getOwnPropertyDescriptor("__URI__");
if (uridescriptor && "value" in uridescriptor && uridescriptor.value) {
let uri;
try {
uri = Services.io.newURI(uridescriptor.value, null, null);
}
catch (e) {
DevToolsUtils.reportException(
"BrowserAddonActor.prototype._shouldAddNewGlobalAsDebuggee",
new Error("Invalid URI: " + uridescriptor.value)
);
return false;
}
let id = {};
if (mapURIToAddonID(uri, id)) {
return id.value === this.id;
}
}
return false;
},
/**
* Yield the current set of globals associated with this addon that should be
* added as debuggees.
*/
_findDebuggees: function (dbg) {
return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
}
};

View File

@ -10,7 +10,6 @@ const { Cc, Ci, Cu } = require("chrome");
const { DebuggerServer, ActorPool } = require("devtools/server/main");
const { EnvironmentActor, LongStringActor, ObjectActor, ThreadActor } = require("devtools/server/actors/script");
const { update } = require("devtools/toolkit/DevToolsUtils");
const Debugger = require("Debugger");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -67,7 +66,7 @@ function WebConsoleActor(aConnection, aParentActor)
this._prefs = {};
this.dbg = new Debugger();
this.dbg = this.parentActor.makeDebugger();
this._netEvents = new Map();
this._gripDepth = 0;
@ -1011,7 +1010,6 @@ WebConsoleActor.prototype =
// If we have an object to bind to |_self|, create a Debugger.Object
// referring to that object, belonging to dbg.
let bindSelf = null;
let dbgWindow = dbg.makeGlobalObjectReference(this.evalWindow);
if (aOptions.bindObjectActor) {
let objActor = this.getActorByID(aOptions.bindObjectActor);
if (objActor) {

View File

@ -99,7 +99,8 @@ let listener = {
}
// Make sure we exit all nested event loops so that the test can finish.
while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
while (DebuggerServer.xpcInspector
&& DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
DebuggerServer.xpcInspector.exitNestedEventLoop();
}

View File

@ -75,7 +75,9 @@ function test_enter_exit_frame()
do_check_eq(traceNames[4], "foo",
'Should have entered "foo" frame in fifth packet');
finishClient(gClient);
});
})
.then(null, e => DevToolsUtils.reportException("test_trace_actor-04.js",
e));
}
function start_trace()

View File

@ -6,6 +6,7 @@ const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/script");
const { DebuggerServer } = require("devtools/server/main");
const promise = require("promise");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
var gTestGlobals = [];
DebuggerServer.addTestGlobal = function(aGlobal) {
@ -66,6 +67,13 @@ function TestTabActor(aConnection, aGlobal)
this.conn.addActor(this._threadActor);
this._attached = false;
this._extraActors = {};
this.makeDebugger = makeDebugger.bind(null, {
findDebuggees: () => [this._global],
shouldAddNewGlobalAsDebuggee: g => g.hostAnnotations &&
g.hostAnnotations.type == "document" &&
g.hostAnnotations.element === this._global
});
}
TestTabActor.prototype = {