Bug 929349 - Integrate a tracing debugger into our existing debugger; r=vporof,past

This commit is contained in:
Nick Fitzgerald
2013-12-18 14:17:27 -08:00
parent bac639d84a
commit 3930bddb92
35 changed files with 1822 additions and 178 deletions

View File

@@ -99,6 +99,7 @@ const promise = require("sdk/core/promise");
const Editor = require("devtools/sourceeditor/editor");
const DebuggerEditor = require("devtools/sourceeditor/debugger.js");
const {Tooltip} = require("devtools/shared/widgets/Tooltip");
const FastListWidget = require("devtools/shared/widgets/FastListWidget");
XPCOMUtils.defineLazyModuleGetter(this, "Parser",
"resource:///modules/devtools/Parser.jsm");
@@ -192,6 +193,7 @@ let DebuggerController = {
this.SourceScripts.disconnect();
this.StackFrames.disconnect();
this.ThreadState.disconnect();
this.Tracer.disconnect();
this.disconnect();
// Chrome debugging needs to close its parent process on shutdown.
@@ -218,39 +220,44 @@ let DebuggerController = {
return this._connection;
}
let deferred = promise.defer();
this._connection = deferred.promise;
let startedDebugging = promise.defer();
this._connection = startedDebugging.promise;
if (!window._isChromeDebugger) {
let target = this._target;
let { client, form: { chromeDebugger }, threadActor } = target;
let { client, form: { chromeDebugger, traceActor }, threadActor } = target;
target.on("close", this._onTabDetached);
target.on("navigate", this._onTabNavigated);
target.on("will-navigate", this._onTabNavigated);
this.client = client;
if (target.chrome) {
this._startChromeDebugging(client, chromeDebugger, deferred.resolve);
this._startChromeDebugging(chromeDebugger, startedDebugging.resolve);
} else {
this._startDebuggingTab(client, threadActor, deferred.resolve);
this._startDebuggingTab(threadActor, startedDebugging.resolve);
const startedTracing = promise.defer();
this._startTracingTab(traceActor, startedTracing.resolve);
return promise.all([startedDebugging.promise, startedTracing.promise]);
}
return deferred.promise;
return startedDebugging.promise;
}
// Chrome debugging needs to make its own connection to the debuggee.
let transport = debuggerSocketConnect(
Prefs.chromeDebuggingHost, Prefs.chromeDebuggingPort);
let client = new DebuggerClient(transport);
let client = this.client = new DebuggerClient(transport);
client.addListener("tabNavigated", this._onTabNavigated);
client.addListener("tabDetached", this._onTabDetached);
client.connect(() => {
client.listTabs(aResponse => {
this._startChromeDebugging(client, aResponse.chromeDebugger, deferred.resolve);
this._startChromeDebugging(aResponse.chromeDebugger, startedDebugging.resolve);
});
});
return deferred.promise;
return startedDebugging.promise;
},
/**
@@ -331,21 +338,13 @@ let DebuggerController = {
/**
* Sets up a debugging session.
*
* @param DebuggerClient aClient
* The debugger client.
* @param string aThreadActor
* The remote protocol grip of the tab.
* @param function aCallback
* A function to invoke once the client attached to the active thread.
* A function to invoke once the client attaches to the active thread.
*/
_startDebuggingTab: function(aClient, aThreadActor, aCallback) {
if (!aClient) {
Cu.reportError("No client found!");
return;
}
this.client = aClient;
aClient.attachThread(aThreadActor, (aResponse, aThreadClient) => {
_startDebuggingTab: function(aThreadActor, aCallback) {
this.client.attachThread(aThreadActor, (aResponse, aThreadClient) => {
if (!aThreadClient) {
Cu.reportError("Couldn't attach to thread: " + aResponse.error);
return;
@@ -366,21 +365,13 @@ let DebuggerController = {
/**
* Sets up a chrome debugging session.
*
* @param DebuggerClient aClient
* The debugger client.
* @param object aChromeDebugger
* The remote protocol grip of the chrome debugger.
* @param function aCallback
* A function to invoke once the client attached to the active thread.
* A function to invoke once the client attaches to the active thread.
*/
_startChromeDebugging: function(aClient, aChromeDebugger, aCallback) {
if (!aClient) {
Cu.reportError("No client found!");
return;
}
this.client = aClient;
aClient.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
_startChromeDebugging: function(aChromeDebugger, aCallback) {
this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
if (!aThreadClient) {
Cu.reportError("Couldn't attach to thread: " + aResponse.error);
return;
@@ -398,6 +389,30 @@ let DebuggerController = {
}, { useSourceMaps: Prefs.sourceMapsEnabled });
},
/**
* Sets up an execution tracing session.
*
* @param object aTraceActor
* The remote protocol grip of the trace actor.
* @param function aCallback
* A function to invoke once the client attaches to the tracer.
*/
_startTracingTab: function(aTraceActor, aCallback) {
this.client.attachTracer(aTraceActor, (response, traceClient) => {
if (!traceClient) {
DevToolsUtils.reportError(new Error("Failed to attach to tracing actor."));
return;
}
this.traceClient = traceClient;
this.Tracer.connect();
if (aCallback) {
aCallback();
}
});
},
/**
* Detach and reattach to the thread actor with useSourceMaps true, blow
* away old sources and get them again.
@@ -1411,6 +1426,218 @@ SourceScripts.prototype = {
}
};
/**
* Tracer update the UI according to the messages exchanged with the tracer
* actor.
*/
function Tracer() {
this._trace = null;
this._idCounter = 0;
this.onTraces = this.onTraces.bind(this);
}
Tracer.prototype = {
get client() {
return DebuggerController.client;
},
get traceClient() {
return DebuggerController.traceClient;
},
get tracing() {
return !!this._trace;
},
/**
* Hooks up the debugger controller with the tracer client.
*/
connect: function() {
this._stack = [];
this.client.addListener("traces", this.onTraces);
},
/**
* Disconnects the debugger controller from the tracer client. Any further
* communcation with the tracer actor will not have any effect on the UI.
*/
disconnect: function() {
this._stack = null;
this.client.removeListener("traces", this.onTraces);
},
/**
* Instructs the tracer actor to start tracing.
*/
startTracing: function(aCallback = () => {}) {
DebuggerView.Tracer.selectTab();
if (this.tracing) {
return;
}
this._trace = "dbg.trace" + Math.random();
this.traceClient.startTrace([
"name",
"location",
"parameterNames",
"depth",
"arguments",
"return",
"throw",
"yield"
], this._trace, (aResponse) => {
const { error } = aResponse;
if (error) {
DevToolsUtils.reportException(error);
this._trace = null;
}
aCallback(aResponse);
});
},
/**
* Instructs the tracer actor to stop tracing.
*/
stopTracing: function(aCallback = () => {}) {
if (!this.tracing) {
return;
}
this.traceClient.stopTrace(this._trace, aResponse => {
const { error } = aResponse;
if (error) {
DevToolsUtils.reportException(error);
}
this._trace = null;
aCallback(aResponse);
});
},
onTraces: function (aEvent, { traces }) {
const tracesLength = traces.length;
let tracesToShow;
if (tracesLength > TracerView.MAX_TRACES) {
tracesToShow = traces.slice(tracesLength - TracerView.MAX_TRACES,
tracesLength);
DebuggerView.Tracer.empty();
this._stack.splice(0, this._stack.length);
} else {
tracesToShow = traces;
}
for (let t of tracesToShow) {
if (t.type == "enteredFrame") {
this._onCall(t);
} else {
this._onReturn(t);
}
}
DebuggerView.Tracer.commit();
},
/**
* Callback for handling a new call frame.
*/
_onCall: function({ name, location, parameterNames, depth, arguments: args }) {
const item = {
name: name,
location: location,
id: this._idCounter++
};
this._stack.push(item);
DebuggerView.Tracer.addTrace({
type: "call",
name: name,
location: location,
depth: depth,
parameterNames: parameterNames,
arguments: args,
frameId: item.id
});
},
/**
* Callback for handling an exited frame.
*/
_onReturn: function(aPacket) {
if (!this._stack.length) {
return;
}
const { name, id, location } = this._stack.pop();
DebuggerView.Tracer.addTrace({
type: aPacket.why,
name: name,
location: location,
depth: aPacket.depth,
frameId: id,
returnVal: aPacket.return || aPacket.throw || aPacket.yield
});
},
/**
* Create an object which has the same interface as a normal object client,
* but since we already have all the information for an object that we will
* ever get (the server doesn't create actors when tracing, just firehoses
* data and forgets about it) just return the data immdiately.
*
* @param Object aObject
* The tracer object "grip" (more like a limited snapshot).
* @returns Object
* The synchronous client object.
*/
syncGripClient: function(aObject) {
return {
get isFrozen() { return aObject.frozen; },
get isSealed() { return aObject.sealed; },
get isExtensible() { return aObject.extensible; },
get ownProperties() { return aObject.ownProperties; },
get prototype() { return null; },
getParameterNames: callback => callback(aObject),
getPrototypeAndProperties: callback => callback(aObject),
getPrototype: callback => callback(aObject),
getOwnPropertyNames: (callback) => {
callback({
ownPropertyNames: aObject.ownProperties
? Object.keys(aObject.ownProperties)
: []
});
},
getProperty: (property, callback) => {
callback({
descriptor: aObject.ownProperties
? aObject.ownProperties[property]
: null
});
},
getDisplayString: callback => callback("[object " + aObject.class + "]"),
getScope: callback => callback({
error: "scopeNotAvailable",
message: "Cannot get scopes for traced objects"
})
};
},
/**
* Wraps object snapshots received from the tracer server so that we can
* differentiate them from long living object grips from the debugger server
* in the variables view.
*
* @param Object aObject
* The object snapshot from the tracer actor.
*/
WrappedObject: function(aObject) {
this.object = aObject;
}
};
/**
* Handles breaking on event listeners in the currently debugged target.
*/
@@ -1955,6 +2182,7 @@ let Prefs = new ViewHelpers.Prefs("devtools", {
ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"],
tracerEnabled: ["Bool", "debugger.tracer"],
editorTabSize: ["Int", "editor.tabsize"]
});
@@ -1982,6 +2210,7 @@ DebuggerController.StackFrames = new StackFrames();
DebuggerController.SourceScripts = new SourceScripts();
DebuggerController.Breakpoints = new Breakpoints();
DebuggerController.Breakpoints.DOM = new EventListeners();
DebuggerController.Tracer = new Tracer();
/**
* Export some properties to the global scope for easier access.