mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 929349 - Integrate a tracing debugger into our existing debugger; r=vporof,past
This commit is contained in:
parent
27ac8abace
commit
77e22fc34d
@ -1121,6 +1121,7 @@ pref("devtools.debugger.pause-on-exceptions", false);
|
||||
pref("devtools.debugger.ignore-caught-exceptions", true);
|
||||
pref("devtools.debugger.source-maps-enabled", true);
|
||||
pref("devtools.debugger.pretty-print-enabled", true);
|
||||
pref("devtools.debugger.tracer", false);
|
||||
|
||||
// The default Debugger UI settings
|
||||
pref("devtools.debugger.ui.panes-sources-width", 200);
|
||||
|
@ -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.
|
||||
|
@ -1056,6 +1056,376 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
_conditionalPopupVisible: false
|
||||
});
|
||||
|
||||
/**
|
||||
* Functions handling the traces UI.
|
||||
*/
|
||||
function TracerView() {
|
||||
this._selectedItem = null;
|
||||
this._matchingItems = null;
|
||||
this.widget = null;
|
||||
|
||||
this._highlightItem = this._highlightItem.bind(this);
|
||||
this._isNotSelectedItem = this._isNotSelectedItem.bind(this);
|
||||
|
||||
this._unhighlightMatchingItems =
|
||||
DevToolsUtils.makeInfallible(this._unhighlightMatchingItems.bind(this));
|
||||
this._onToggleTracing =
|
||||
DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
|
||||
this._onStartTracing =
|
||||
DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
|
||||
this._onClear = DevToolsUtils.makeInfallible(this._onClear.bind(this));
|
||||
this._onSelect = DevToolsUtils.makeInfallible(this._onSelect.bind(this));
|
||||
this._onMouseOver =
|
||||
DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
|
||||
this._onSearch = DevToolsUtils.makeInfallible(this._onSearch.bind(this));
|
||||
}
|
||||
|
||||
TracerView.MAX_TRACES = 200;
|
||||
|
||||
TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the debugger is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
dumpn("Initializing the TracerView");
|
||||
|
||||
this._traceButton = document.getElementById("trace");
|
||||
this._tracerTab = document.getElementById("tracer-tab");
|
||||
|
||||
// Remove tracer related elements from the dom and tear everything down if
|
||||
// the tracer isn't enabled.
|
||||
if (!Prefs.tracerEnabled) {
|
||||
this._traceButton.remove();
|
||||
this._traceButton = null;
|
||||
this._tracerTab.remove();
|
||||
this._tracerTab = null;
|
||||
document.getElementById("tracer-tabpanel").remove();
|
||||
this.widget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.widget = new FastListWidget(document.getElementById("tracer-traces"));
|
||||
|
||||
this._traceButton.removeAttribute("hidden");
|
||||
this._tracerTab.removeAttribute("hidden");
|
||||
|
||||
this._tracerDeck = document.getElementById("tracer-deck");
|
||||
this._search = document.getElementById("tracer-search");
|
||||
|
||||
this._template = document.getElementsByClassName("trace-item-template")[0];
|
||||
this._templateItem = this._template.getElementsByClassName("trace-item")[0];
|
||||
this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
|
||||
this._templateNameNode = this._template.getElementsByClassName("trace-name")[0];
|
||||
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this.widget.addEventListener("mouseover", this._onMouseOver, false);
|
||||
this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
|
||||
|
||||
this._search.addEventListener("input", this._onSearch, false);
|
||||
|
||||
this._startTooltip = L10N.getStr("startTracingTooltip");
|
||||
this._stopTooltip = L10N.getStr("stopTracingTooltip");
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
dumpn("Destroying the TracerView");
|
||||
|
||||
if (!this.widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this.widget.removeEventListener("mouseover", this._onMouseOver, false);
|
||||
this.widget.removeEventListener("mouseout", this._unhighlightMatchingItems, false);
|
||||
this._search.removeEventListener("input", this._onSearch, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by the "toggleTracing" command to switch the tracer state.
|
||||
*/
|
||||
_onToggleTracing: function() {
|
||||
if (DebuggerController.Tracer.tracing) {
|
||||
this._onStopTracing();
|
||||
} else {
|
||||
this._onStartTracing();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked either by the "startTracing" command or by
|
||||
* _onToggleTracing to start execution tracing in the backend.
|
||||
*/
|
||||
_onStartTracing: function() {
|
||||
this._tracerDeck.selectedIndex = 0;
|
||||
this._traceButton.setAttribute("checked", true);
|
||||
this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
|
||||
this.empty();
|
||||
DebuggerController.Tracer.startTracing();
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by _onToggleTracing to stop execution tracing in the
|
||||
* backend.
|
||||
*/
|
||||
_onStopTracing: function() {
|
||||
this._traceButton.removeAttribute("checked");
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
DebuggerController.Tracer.stopTracing();
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by the "clearTraces" command to empty the traces pane.
|
||||
*/
|
||||
_onClear: function() {
|
||||
this.empty();
|
||||
},
|
||||
|
||||
/**
|
||||
* Populate the given parent scope with the variable with the provided name
|
||||
* and value.
|
||||
*
|
||||
* @param String aName
|
||||
* The name of the variable.
|
||||
* @param Object aParent
|
||||
* The parent scope.
|
||||
* @param Object aValue
|
||||
* The value of the variable.
|
||||
*/
|
||||
_populateVariable: function(aName, aParent, aValue) {
|
||||
let item = aParent.addItem(aName, { value: aValue });
|
||||
if (aValue) {
|
||||
DebuggerView.Variables.controller.populate(
|
||||
item, new DebuggerController.Tracer.WrappedObject(aValue));
|
||||
item.expand();
|
||||
item.twisty = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the widget's "select" event. Displays parameters, exception, or
|
||||
* return value depending on whether the selected trace is a call, throw, or
|
||||
* return respectively.
|
||||
*
|
||||
* @param Object traceItem
|
||||
* The selected trace item.
|
||||
*/
|
||||
_onSelect: function _onSelect({ detail: traceItem }) {
|
||||
if (!traceItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = traceItem.attachment.trace;
|
||||
const { location: { url, line } } = data;
|
||||
DebuggerView.setEditorLocation(url, line, { noDebug: true });
|
||||
|
||||
DebuggerView.Variables.empty();
|
||||
const scope = DebuggerView.Variables.addScope();
|
||||
|
||||
if (data.type == "call") {
|
||||
const params = DevToolsUtils.zip(data.parameterNames, data.arguments);
|
||||
for (let [name, val] of params) {
|
||||
if (val === undefined) {
|
||||
scope.addItem(name, { value: "<value not available>" });
|
||||
} else {
|
||||
this._populateVariable(name, scope, val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const varName = "<" +
|
||||
(data.type == "throw" ? "exception" : data.type) +
|
||||
">";
|
||||
this._populateVariable(varName, scope, data.returnVal);
|
||||
}
|
||||
|
||||
scope.expand();
|
||||
DebuggerView.showInstrumentsPane();
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the hover frame enter/exit highlighting to a given item.
|
||||
*/
|
||||
_highlightItem: function(aItem) {
|
||||
aItem.target.querySelector(".trace-item")
|
||||
.classList.add("selected-matching");
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the hover frame enter/exit highlighting to a given item.
|
||||
*/
|
||||
_unhighlightItem: function(aItem) {
|
||||
if (!aItem || !aItem.target) {
|
||||
return;
|
||||
}
|
||||
const match = aItem.target.querySelector(".selected-matching");
|
||||
if (match) {
|
||||
match.classList.remove("selected-matching");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the frame enter/exit pair highlighting we do when hovering.
|
||||
*/
|
||||
_unhighlightMatchingItems: function() {
|
||||
if (this._matchingItems) {
|
||||
this._matchingItems.forEach(this._unhighlightItem);
|
||||
this._matchingItems = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns true if the given item is not the selected item.
|
||||
*/
|
||||
_isNotSelectedItem: function(aItem) {
|
||||
return aItem !== this.selectedItem;
|
||||
},
|
||||
|
||||
/**
|
||||
* Highlight the frame enter/exit pair of items for the given item.
|
||||
*/
|
||||
_highlightMatchingItems: function(aItem) {
|
||||
this._unhighlightMatchingItems();
|
||||
this._matchingItems = this.items.filter(t => t.value == aItem.value);
|
||||
this._matchingItems
|
||||
.filter(this._isNotSelectedItem)
|
||||
.forEach(this._highlightItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for the mouseover event.
|
||||
*/
|
||||
_onMouseOver: function({ target }) {
|
||||
const traceItem = this.getItemForElement(target);
|
||||
if (traceItem) {
|
||||
this._highlightMatchingItems(traceItem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener for typing in the search box.
|
||||
*/
|
||||
_onSearch: function() {
|
||||
const query = this._search.value.trim().toLowerCase();
|
||||
this.filterContents(item =>
|
||||
item.attachment.trace.name.toLowerCase().contains(query));
|
||||
},
|
||||
|
||||
/**
|
||||
* Select the traces tab in the sidebar.
|
||||
*/
|
||||
selectTab: function() {
|
||||
const tabs = this._tracerTab.parentElement;
|
||||
tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
|
||||
this._tracerDeck.selectedIndex = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Commit all staged items to the widget. Overridden so that we can call
|
||||
* |FastListWidget.prototype.flush|.
|
||||
*/
|
||||
commit: function() {
|
||||
WidgetMethods.commit.call(this);
|
||||
// TODO: Accessing non-standard widget properties. Figure out what's the
|
||||
// best way to expose such things. Bug 895514.
|
||||
this.widget.flush();
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds the trace record provided as an argument to the view.
|
||||
*
|
||||
* @param object aTrace
|
||||
* The trace record coming from the tracer actor.
|
||||
*/
|
||||
addTrace: function(aTrace) {
|
||||
const { type, frameId } = aTrace;
|
||||
|
||||
// Create the element node for the trace item.
|
||||
let view = this._createView(aTrace);
|
||||
|
||||
// Append a source item to this container.
|
||||
this.push([view, aTrace.frameId, ""], {
|
||||
staged: true,
|
||||
attachment: {
|
||||
trace: aTrace
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Customization function for creating an item's UI.
|
||||
*
|
||||
* @return nsIDOMNode
|
||||
* The network request view.
|
||||
*/
|
||||
_createView: function({ type, name, frameId, parameterNames, returnVal,
|
||||
location, depth, arguments: args }) {
|
||||
let fragment = document.createDocumentFragment();
|
||||
|
||||
this._templateItem.setAttribute("tooltiptext", SourceUtils.trimUrl(location.url));
|
||||
this._templateItem.style.MozPaddingStart = depth + "em";
|
||||
|
||||
const TYPES = ["call", "yield", "return", "throw"];
|
||||
for (let t of TYPES) {
|
||||
this._templateTypeIcon.classList.toggle("trace-" + t, t == type);
|
||||
}
|
||||
this._templateTypeIcon.setAttribute("value", {
|
||||
call: "\u2192",
|
||||
yield: "Y",
|
||||
return: "\u2190",
|
||||
throw: "E",
|
||||
terminated: "TERMINATED"
|
||||
}[type]);
|
||||
|
||||
this._templateNameNode.setAttribute("value", name);
|
||||
|
||||
// All extra syntax and parameter nodes added.
|
||||
const addedNodes = [];
|
||||
|
||||
if (parameterNames) {
|
||||
const syntax = (p) => {
|
||||
const el = document.createElement("label");
|
||||
el.setAttribute("value", p);
|
||||
el.classList.add("trace-syntax");
|
||||
el.classList.add("plain");
|
||||
addedNodes.push(el);
|
||||
return el;
|
||||
};
|
||||
|
||||
this._templateItem.appendChild(syntax("("));
|
||||
|
||||
for (let i = 0, n = parameterNames.length; i < n; i++) {
|
||||
let param = document.createElement("label");
|
||||
param.setAttribute("value", parameterNames[i]);
|
||||
param.classList.add("trace-param");
|
||||
param.classList.add("plain");
|
||||
addedNodes.push(param);
|
||||
this._templateItem.appendChild(param);
|
||||
|
||||
if (i + 1 !== n) {
|
||||
this._templateItem.appendChild(syntax(", "));
|
||||
}
|
||||
}
|
||||
|
||||
this._templateItem.appendChild(syntax(")"));
|
||||
}
|
||||
|
||||
// Flatten the DOM by removing one redundant box (the template container).
|
||||
for (let node of this._template.childNodes) {
|
||||
fragment.appendChild(node.cloneNode(true));
|
||||
}
|
||||
|
||||
// Remove any added nodes from the template.
|
||||
for (let node of addedNodes) {
|
||||
this._templateItem.removeChild(node);
|
||||
}
|
||||
|
||||
return fragment;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Utility functions for handling sources.
|
||||
*/
|
||||
@ -2789,6 +3159,7 @@ LineResults.size = function() {
|
||||
*/
|
||||
DebuggerView.Sources = new SourcesView();
|
||||
DebuggerView.VariableBubble = new VariableBubbleView();
|
||||
DebuggerView.Tracer = new TracerView();
|
||||
DebuggerView.WatchExpressions = new WatchExpressionsView();
|
||||
DebuggerView.EventListeners = new EventListenersView();
|
||||
DebuggerView.GlobalSearch = new GlobalSearchView();
|
||||
|
@ -60,6 +60,7 @@ let DebuggerView = {
|
||||
this.StackFramesClassicList.initialize();
|
||||
this.Sources.initialize();
|
||||
this.VariableBubble.initialize();
|
||||
this.Tracer.initialize();
|
||||
this.WatchExpressions.initialize();
|
||||
this.EventListeners.initialize();
|
||||
this.GlobalSearch.initialize();
|
||||
@ -95,6 +96,7 @@ let DebuggerView = {
|
||||
this.StackFramesClassicList.destroy();
|
||||
this.Sources.destroy();
|
||||
this.VariableBubble.destroy();
|
||||
this.Tracer.destroy();
|
||||
this.WatchExpressions.destroy();
|
||||
this.EventListeners.destroy();
|
||||
this.GlobalSearch.destroy();
|
||||
@ -169,7 +171,11 @@ let DebuggerView = {
|
||||
// Attach a controller that handles interfacing with the debugger protocol.
|
||||
VariablesViewController.attach(this.Variables, {
|
||||
getEnvironmentClient: aObject => gThreadClient.environment(aObject),
|
||||
getObjectClient: aObject => gThreadClient.pauseGrip(aObject)
|
||||
getObjectClient: aObject => {
|
||||
return aObject instanceof DebuggerController.Tracer.WrappedObject
|
||||
? DebuggerController.Tracer.syncGripClient(aObject.object)
|
||||
: gThreadClient.pauseGrip(aObject)
|
||||
}
|
||||
});
|
||||
|
||||
// Relay events from the VariablesView.
|
||||
@ -637,6 +643,7 @@ let DebuggerView = {
|
||||
ChromeGlobals: null,
|
||||
StackFrames: null,
|
||||
Sources: null,
|
||||
Tracer: null,
|
||||
Variables: null,
|
||||
VariableBubble: null,
|
||||
WatchExpressions: null,
|
||||
|
@ -86,6 +86,12 @@
|
||||
oncommand="DebuggerView.Options._toggleShowVariablesFilterBox()"/>
|
||||
<command id="toggleShowOriginalSource"
|
||||
oncommand="DebuggerView.Options._toggleShowOriginalSource()"/>
|
||||
<command id="toggleTracing"
|
||||
oncommand="DebuggerView.Tracer._onToggleTracing()"/>
|
||||
<command id="startTracing"
|
||||
oncommand="DebuggerView.Tracer._onStartTracing()"/>
|
||||
<command id="clearTraces"
|
||||
oncommand="DebuggerView.Tracer._onClear()"/>
|
||||
</commandset>
|
||||
|
||||
<popupset id="debuggerPopupset">
|
||||
@ -304,6 +310,13 @@
|
||||
class="devtools-toolbarbutton"
|
||||
tabindex="0"/>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<toolbarbutton id="trace"
|
||||
class="devtools-toolbarbutton"
|
||||
command="toggleTracing"
|
||||
tabindex="0"
|
||||
hidden="true"/>
|
||||
</hbox>
|
||||
<menulist id="chrome-globals"
|
||||
class="devtools-menulist"
|
||||
sizetopopup="none" hidden="true"/>
|
||||
@ -328,6 +341,7 @@
|
||||
<tabs>
|
||||
<tab id="sources-tab" label="&debuggerUI.tabs.sources;"/>
|
||||
<tab id="callstack-tab" label="&debuggerUI.tabs.callstack;"/>
|
||||
<tab id="tracer-tab" label="&debuggerUI.tabs.traces;" hidden="true"/>
|
||||
</tabs>
|
||||
<tabpanels flex="1">
|
||||
<tabpanel id="sources-tabpanel">
|
||||
@ -354,6 +368,41 @@
|
||||
<tabpanel id="callstack-tabpanel">
|
||||
<vbox id="callstack-list" flex="1"/>
|
||||
</tabpanel>
|
||||
<tabpanel id="tracer-tabpanel" flex="1">
|
||||
<deck id="tracer-deck" selectedIndex="1" flex="1">
|
||||
<vbox flex="1">
|
||||
<vbox id="tracer-traces" flex="1">
|
||||
<hbox class="trace-item-template" hidden="true">
|
||||
<hbox class="trace-item" align="center" flex="1" crop="end">
|
||||
<label class="trace-type plain"/>
|
||||
<label class="trace-name plain" crop="end"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<toolbar id="tracer-toolbar" class="devtools-toolbar">
|
||||
<toolbarbutton id="clear-tracer"
|
||||
label="&debuggerUI.clearButton;"
|
||||
tooltiptext="&debuggerUI.clearButton.tooltip;"
|
||||
command="clearTraces"
|
||||
class="devtools-toolbarbutton"/>
|
||||
<textbox id="tracer-search"
|
||||
class="devtools-searchinput"
|
||||
flex="1"
|
||||
type="search"/>
|
||||
</toolbar>
|
||||
</vbox>
|
||||
<vbox id="tracer-message"
|
||||
flex="1"
|
||||
align="center"
|
||||
pack="center">
|
||||
<description value="&debuggerUI.tracingNotStarted.label;" />
|
||||
<button id="start-tracing"
|
||||
class="devtools-toolbarbutton"
|
||||
command="startTracing"
|
||||
label="&debuggerUI.startTracing;"/>
|
||||
</vbox>
|
||||
</deck>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
</tabbox>
|
||||
<splitter id="sources-and-editor-splitter"
|
||||
|
@ -9,6 +9,8 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const promise = require("sdk/core/promise");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
|
||||
|
||||
function DebuggerPanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
this._toolbox = toolbox;
|
||||
@ -57,8 +59,7 @@ DebuggerPanel.prototype = {
|
||||
return this;
|
||||
})
|
||||
.then(null, function onError(aReason) {
|
||||
Cu.reportError("DebuggerPanel open failed. " +
|
||||
aReason.error + ": " + aReason.message);
|
||||
DevToolsUtils.reportException("DebuggerPane.prototype.open", aReason);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -20,6 +20,7 @@ support-files =
|
||||
code_script-switching-01.js
|
||||
code_script-switching-02.js
|
||||
code_test-editor-mode
|
||||
code_tracing-01.js
|
||||
code_ugly.js
|
||||
code_ugly-2.js
|
||||
code_ugly-3.js
|
||||
@ -56,6 +57,7 @@ support-files =
|
||||
doc_script-switching-01.html
|
||||
doc_script-switching-02.html
|
||||
doc_step-out.html
|
||||
doc_tracing-01.html
|
||||
doc_watch-expressions.html
|
||||
doc_with-frame.html
|
||||
head.js
|
||||
@ -188,6 +190,10 @@ support-files =
|
||||
[browser_dbg_step-out.js]
|
||||
[browser_dbg_tabactor-01.js]
|
||||
[browser_dbg_tabactor-02.js]
|
||||
[browser_dbg_tracing-01.js]
|
||||
[browser_dbg_tracing-02.js]
|
||||
[browser_dbg_tracing-03.js]
|
||||
[browser_dbg_tracing-04.js]
|
||||
[browser_dbg_variables-view-01.js]
|
||||
[browser_dbg_variables-view-02.js]
|
||||
[browser_dbg_variables-view-03.js]
|
||||
|
109
browser/devtools/debugger/test/browser_dbg_tracing-01.js
Normal file
109
browser/devtools/debugger/test/browser_dbg_tracing-01.js
Normal file
@ -0,0 +1,109 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we get the expected frame enter/exit logs in the tracer view.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
waitForSourceShown(gPanel, "code_tracing-01.js")
|
||||
.then(() => startTracing(gPanel))
|
||||
.then(clickButton)
|
||||
.then(() => waitForClientEvents(aPanel, "traces"))
|
||||
.then(testTraceLogs)
|
||||
.then(() => stopTracing(gPanel))
|
||||
.then(() => {
|
||||
const deferred = promise.defer();
|
||||
SpecialPowers.popPrefEnv(deferred.resolve);
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
function testTraceLogs() {
|
||||
const onclickLogs = filterTraces(gPanel,
|
||||
t => t.querySelector(".trace-name[value=onclick]"));
|
||||
is(onclickLogs.length, 2, "Should have two logs from 'onclick'");
|
||||
ok(onclickLogs[0].querySelector(".trace-call"),
|
||||
"The first 'onclick' log should be a call.");
|
||||
ok(onclickLogs[1].querySelector(".trace-return"),
|
||||
"The second 'onclick' log should be a return.");
|
||||
for (let t of onclickLogs) {
|
||||
ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
|
||||
.contains("doc_tracing-01.html"));
|
||||
}
|
||||
|
||||
const nonOnclickLogs = filterTraces(gPanel,
|
||||
t => !t.querySelector(".trace-name[value=onclick]"));
|
||||
for (let t of nonOnclickLogs) {
|
||||
ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
|
||||
.contains("code_tracing-01.js"));
|
||||
}
|
||||
|
||||
const mainLogs = filterTraces(gPanel,
|
||||
t => t.querySelector(".trace-name[value=main]"));
|
||||
is(mainLogs.length, 2, "Should have an enter and an exit for 'main'");
|
||||
ok(mainLogs[0].querySelector(".trace-call"),
|
||||
"The first 'main' log should be a call.");
|
||||
ok(mainLogs[1].querySelector(".trace-return"),
|
||||
"The second 'main' log should be a return.");
|
||||
|
||||
const factorialLogs = filterTraces(gPanel,
|
||||
t => t.querySelector(".trace-name[value=factorial]"));
|
||||
is(factorialLogs.length, 10, "Should have 5 enter, and 5 exit frames for 'factorial'");
|
||||
ok(factorialLogs.slice(0, 5).every(t => t.querySelector(".trace-call")),
|
||||
"The first five 'factorial' logs should be calls.");
|
||||
ok(factorialLogs.slice(5).every(t => t.querySelector(".trace-return")),
|
||||
"The second five 'factorial' logs should be returns.")
|
||||
|
||||
// Test that the depth affects padding so that calls are indented properly.
|
||||
let lastDepth = -Infinity;
|
||||
for (let t of factorialLogs.slice(0, 5)) {
|
||||
let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
|
||||
ok(depth > lastDepth, "The depth should be increasing");
|
||||
lastDepth = depth;
|
||||
}
|
||||
lastDepth = Infinity;
|
||||
for (let t of factorialLogs.slice(5)) {
|
||||
let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
|
||||
ok(depth < lastDepth, "The depth should be decreasing");
|
||||
lastDepth = depth;
|
||||
}
|
||||
|
||||
const throwerLogs = filterTraces(gPanel,
|
||||
t => t.querySelector(".trace-name[value=thrower]"));
|
||||
is(throwerLogs.length, 2, "Should have an enter and an exit for 'thrower'");
|
||||
ok(throwerLogs[0].querySelector(".trace-call"),
|
||||
"The first 'thrower' log should be a call.");
|
||||
ok(throwerLogs[1].querySelector(".trace-throw",
|
||||
"The second 'thrower' log should be a throw."));
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
78
browser/devtools/debugger/test/browser_dbg_tracing-02.js
Normal file
78
browser/devtools/debugger/test/browser_dbg_tracing-02.js
Normal file
@ -0,0 +1,78 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we highlight matching calls and returns on hover.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
waitForSourceShown(gPanel, "code_tracing-01.js")
|
||||
.then(() => startTracing(gPanel))
|
||||
.then(clickButton)
|
||||
.then(() => waitForClientEvents(aPanel, "traces"))
|
||||
.then(highlightCall)
|
||||
.then(testReturnHighlighted)
|
||||
.then(unhighlightCall)
|
||||
.then(testNoneHighlighted)
|
||||
.then(() => stopTracing(gPanel))
|
||||
.then(() => {
|
||||
const deferred = promise.defer();
|
||||
SpecialPowers.popPrefEnv(deferred.resolve);
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
function highlightCall() {
|
||||
const callTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0];
|
||||
EventUtils.sendMouseEvent({ type: "mouseover" },
|
||||
callTrace,
|
||||
gDebugger);
|
||||
}
|
||||
|
||||
function testReturnHighlighted() {
|
||||
const returnTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[1];
|
||||
ok(Array.indexOf(returnTrace.querySelector(".trace-item").classList, "selected-matching") >= 0,
|
||||
"The corresponding return log should be highlighted.");
|
||||
}
|
||||
|
||||
function unhighlightCall() {
|
||||
const callTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0];
|
||||
EventUtils.sendMouseEvent({ type: "mouseout" },
|
||||
callTrace,
|
||||
gDebugger);
|
||||
}
|
||||
|
||||
function testNoneHighlighted() {
|
||||
const highlightedTraces = filterTraces(gPanel, t => t.querySelector(".selected-matching"));
|
||||
is(highlightedTraces.length, 0, "Shouldn't have any highlighted traces");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
70
browser/devtools/debugger/test/browser_dbg_tracing-03.js
Normal file
70
browser/devtools/debugger/test/browser_dbg_tracing-03.js
Normal file
@ -0,0 +1,70 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that we can jump to function definitions by clicking on logs.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
|
||||
waitForSourceShown(gPanel, "code_tracing-01.js")
|
||||
.then(() => startTracing(gPanel))
|
||||
.then(clickButton)
|
||||
.then(() => waitForClientEvents(aPanel, "traces"))
|
||||
.then(() => {
|
||||
// Switch away from the JS file so we can make sure that clicking on a
|
||||
// log will switch us back to the correct JS file.
|
||||
aPanel.panelWin.DebuggerView.Sources.selectedValue = TAB_URL;
|
||||
return ensureSourceIs(aPanel, TAB_URL, true);
|
||||
})
|
||||
.then(() => {
|
||||
const finished = waitForSourceShown(gPanel, "code_tracing-01.js");
|
||||
clickTraceLog();
|
||||
return finished;
|
||||
})
|
||||
.then(testCorrectLine)
|
||||
.then(() => stopTracing(gPanel))
|
||||
.then(() => {
|
||||
const deferred = promise.defer();
|
||||
SpecialPowers.popPrefEnv(deferred.resolve);
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
function clickTraceLog() {
|
||||
filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0].click();
|
||||
}
|
||||
|
||||
function testCorrectLine() {
|
||||
is(gDebugger.DebuggerView.editor.getCursor().line, 19,
|
||||
"The editor should have the function definition site's line selected.");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
});
|
86
browser/devtools/debugger/test/browser_dbg_tracing-04.js
Normal file
86
browser/devtools/debugger/test/browser_dbg_tracing-04.js
Normal file
@ -0,0 +1,86 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that when we click on logs, we get the parameters/return value in the variables view.
|
||||
*/
|
||||
|
||||
const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
|
||||
|
||||
let gTab, gDebuggee, gPanel, gDebugger, gVariables;
|
||||
|
||||
function test() {
|
||||
SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
|
||||
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gVariables = gDebugger.DebuggerView.Variables;
|
||||
|
||||
waitForSourceShown(gPanel, "code_tracing-01.js")
|
||||
.then(() => startTracing(gPanel))
|
||||
.then(clickButton)
|
||||
.then(() => waitForClientEvents(aPanel, "traces"))
|
||||
.then(clickTraceCall)
|
||||
.then(testParams)
|
||||
.then(clickTraceReturn)
|
||||
.then(testReturn)
|
||||
.then(() => stopTracing(gPanel))
|
||||
.then(() => {
|
||||
const deferred = promise.defer();
|
||||
SpecialPowers.popPrefEnv(deferred.resolve);
|
||||
return deferred.promise;
|
||||
})
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
DevToolsUtils.reportException("browser_dbg_tracing-04.js", aError);
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function clickButton() {
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
gDebuggee.document.querySelector("button"),
|
||||
gDebuggee);
|
||||
}
|
||||
|
||||
function clickTraceCall() {
|
||||
filterTraces(gPanel, t => t.querySelector(".trace-name[value=factorial]"))[0]
|
||||
.click();
|
||||
}
|
||||
|
||||
function testParams() {
|
||||
const name = gDebugger.document.querySelector(".variables-view-variable .name");
|
||||
ok(name, "Should have a variable name");
|
||||
is(name.getAttribute("value"), "n", "The variable name should be n");
|
||||
|
||||
const value = gDebugger.document.querySelector(".variables-view-variable .value.token-number");
|
||||
ok(value, "Should have a variable value");
|
||||
is(value.getAttribute("value"), "5", "The variable value should be 5");
|
||||
}
|
||||
|
||||
function clickTraceReturn() {
|
||||
filterTraces(gPanel, t => t.querySelector(".trace-name[value=factorial]"))
|
||||
.pop().click();
|
||||
}
|
||||
|
||||
function testReturn() {
|
||||
const name = gDebugger.document.querySelector(".variables-view-variable .name");
|
||||
ok(name, "Should have a variable name");
|
||||
is(name.getAttribute("value"), "<return>", "The variable name should be <return>");
|
||||
|
||||
const value = gDebugger.document.querySelector(".variables-view-variable .value.token-number");
|
||||
ok(value, "Should have a variable value");
|
||||
is(value.getAttribute("value"), "120", "The variable value should be 120");
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
gTab = null;
|
||||
gDebuggee = null;
|
||||
gPanel = null;
|
||||
gDebugger = null;
|
||||
gVariables = null;
|
||||
});
|
29
browser/devtools/debugger/test/code_tracing-01.js
Normal file
29
browser/devtools/debugger/test/code_tracing-01.js
Normal file
@ -0,0 +1,29 @@
|
||||
function factorial(n) {
|
||||
if (n <= 1) {
|
||||
return 1;
|
||||
} else {
|
||||
return n * factorial(n - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function* yielder(n) {
|
||||
while (n-- >= 0) {
|
||||
yield { value: n, squared: n * n };
|
||||
}
|
||||
}
|
||||
|
||||
function thrower() {
|
||||
throw new Error("Curse your sudden but inevitable betrayal!");
|
||||
}
|
||||
|
||||
function main() {
|
||||
factorial(5);
|
||||
|
||||
// XXX bug 923729: Can't test yielding yet.
|
||||
// for (let x of yielder(5)) {}
|
||||
|
||||
try {
|
||||
thrower();
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
12
browser/devtools/debugger/test/doc_tracing-01.html
Normal file
12
browser/devtools/debugger/test/doc_tracing-01.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Debugger Tracer test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="code_tracing-01.js"></script>
|
||||
<button onclick="main()">Click me!</button>
|
||||
</body>
|
||||
</html>
|
@ -375,6 +375,26 @@ function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) {
|
||||
info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
|
||||
|
||||
let deferred = promise.defer();
|
||||
let client = aPanel.panelWin.gClient;
|
||||
let count = 0;
|
||||
|
||||
client.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
|
||||
info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
|
||||
|
||||
if (count == aEventRepeat) {
|
||||
ok(true, "Enough '" + aEventName + "' thread events have been fired.");
|
||||
client.removeListener(aEventName, onEvent);
|
||||
deferred.resolve.apply(deferred, aArgs);
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function ensureThreadClientState(aPanel, aState) {
|
||||
let thread = aPanel.panelWin.gThreadClient;
|
||||
let state = thread.state;
|
||||
@ -598,3 +618,38 @@ function hideVarPopupByScrollingEditor(aPanel) {
|
||||
function reopenVarPopup(...aArgs) {
|
||||
return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs));
|
||||
}
|
||||
|
||||
// Tracing helpers
|
||||
|
||||
function startTracing(aPanel) {
|
||||
const deferred = promise.defer();
|
||||
aPanel.panelWin.DebuggerController.Tracer.startTracing(aResponse => {
|
||||
if (aResponse.error) {
|
||||
deferred.reject(aResponse);
|
||||
} else {
|
||||
deferred.resolve(aResponse);
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function stopTracing(aPanel) {
|
||||
const deferred = promise.defer();
|
||||
aPanel.panelWin.DebuggerController.Tracer.stopTracing(aResponse => {
|
||||
if (aResponse.error) {
|
||||
deferred.reject(aResponse);
|
||||
} else {
|
||||
deferred.resolve(aResponse);
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function filterTraces(aPanel, f) {
|
||||
const traces = aPanel.panelWin.document
|
||||
.getElementById("tracer-traces")
|
||||
.querySelector("scrollbox")
|
||||
.children;
|
||||
return Array.filter(traces, f);
|
||||
}
|
||||
|
||||
|
210
browser/devtools/shared/widgets/FastListWidget.js
Normal file
210
browser/devtools/shared/widgets/FastListWidget.js
Normal file
@ -0,0 +1,210 @@
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
const { Cu, Ci } = require("chrome");
|
||||
const { ViewHelpers } = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
|
||||
|
||||
/**
|
||||
* A list menu widget that attempts to be very fast.
|
||||
*
|
||||
* Note: this widget should be used in tandem with the WidgetMethods in
|
||||
* ViewHelpers.jsm.
|
||||
*
|
||||
* Note: this widget also reuses SideMenuWidget CSS class names.
|
||||
*
|
||||
* @param nsIDOMNode aNode
|
||||
* The element associated with the widget.
|
||||
*/
|
||||
const FastListWidget = module.exports = function FastListWidget(aNode) {
|
||||
this.document = aNode.ownerDocument;
|
||||
this.window = this.document.defaultView;
|
||||
this._parent = aNode;
|
||||
this._fragment = this.document.createDocumentFragment();
|
||||
|
||||
// This is a prototype element that each item added to the list clones.
|
||||
this._templateElement = this.document.createElement("hbox");
|
||||
this._templateElement.className = "side-menu-widget-item side-menu-widget-item-contents";
|
||||
|
||||
// Create an internal scrollbox container.
|
||||
this._list = this.document.createElement("scrollbox");
|
||||
this._list.className = "side-menu-widget-container";
|
||||
this._list.setAttribute("flex", "1");
|
||||
this._list.setAttribute("orient", "vertical");
|
||||
this._list.setAttribute("theme", "dark");
|
||||
this._list.setAttribute("tabindex", "0");
|
||||
this._list.addEventListener("keypress", e => this.emit("keyPress", e), false);
|
||||
this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false);
|
||||
this._parent.appendChild(this._list);
|
||||
|
||||
this._orderedMenuElementsArray = [];
|
||||
this._itemsByElement = new Map();
|
||||
|
||||
// This widget emits events that can be handled in a MenuContainer.
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
// Delegate some of the associated node's methods to satisfy the interface
|
||||
// required by MenuContainer instances.
|
||||
ViewHelpers.delegateWidgetEventMethods(this, aNode);
|
||||
}
|
||||
|
||||
FastListWidget.prototype = {
|
||||
/**
|
||||
* Inserts an item in this container at the specified index, optionally
|
||||
* grouping by name.
|
||||
*
|
||||
* @param number aIndex
|
||||
* The position in the container intended for this item.
|
||||
* @param nsIDOMNode aContents
|
||||
* The node to be displayed in the container.
|
||||
* @param Object aAttachment [optional]
|
||||
* Extra data for the user.
|
||||
* @return nsIDOMNode
|
||||
* The element associated with the displayed item.
|
||||
*/
|
||||
insertItemAt: function(aIndex, aContents, aAttachment={}) {
|
||||
let element = this._templateElement.cloneNode();
|
||||
element.appendChild(aContents);
|
||||
|
||||
if (aIndex >= 0) {
|
||||
throw new Error("FastListWidget only supports appending items.");
|
||||
}
|
||||
|
||||
this._fragment.appendChild(element);
|
||||
this._orderedMenuElementsArray.push(element);
|
||||
this._itemsByElement.set(element, this);
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
/**
|
||||
* This is a non-standard widget implementation method. When appending items,
|
||||
* they are queued in a document fragment. This method appends the document
|
||||
* fragment to the dom.
|
||||
*/
|
||||
flush: function() {
|
||||
this._list.appendChild(this._fragment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes all of the child nodes from this container.
|
||||
*/
|
||||
removeAllItems: function() {
|
||||
let parent = this._parent;
|
||||
let list = this._list;
|
||||
|
||||
while (list.hasChildNodes()) {
|
||||
list.firstChild.remove();
|
||||
}
|
||||
|
||||
this._selectedItem = null;
|
||||
|
||||
this._orderedMenuElementsArray.length = 0;
|
||||
this._itemsByElement.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the given item.
|
||||
*/
|
||||
removeChild: function(child) {
|
||||
throw new Error("Not yet implemented");
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently selected child node in this container.
|
||||
* @return nsIDOMNode
|
||||
*/
|
||||
get selectedItem() this._selectedItem,
|
||||
|
||||
/**
|
||||
* Sets the currently selected child node in this container.
|
||||
* @param nsIDOMNode child
|
||||
*/
|
||||
set selectedItem(child) {
|
||||
let menuArray = this._orderedMenuElementsArray;
|
||||
|
||||
if (!child) {
|
||||
this._selectedItem = null;
|
||||
}
|
||||
for (let node of menuArray) {
|
||||
if (node == child) {
|
||||
node.classList.add("selected");
|
||||
node.parentNode.classList.add("selected");
|
||||
this._selectedItem = node;
|
||||
} else {
|
||||
node.classList.remove("selected");
|
||||
node.parentNode.classList.remove("selected");
|
||||
}
|
||||
}
|
||||
|
||||
this.ensureElementIsVisible(this.selectedItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the child node in this container situated at the specified index.
|
||||
*
|
||||
* @param number index
|
||||
* The position in the container intended for this item.
|
||||
* @return nsIDOMNode
|
||||
* The element associated with the displayed item.
|
||||
*/
|
||||
getItemAtIndex: function(index) {
|
||||
return this._orderedMenuElementsArray[index];
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the value of the named attribute on this container.
|
||||
*
|
||||
* @param string name
|
||||
* The name of the attribute.
|
||||
* @return string
|
||||
* The current attribute value.
|
||||
*/
|
||||
getAttribute: function(name) {
|
||||
return this._parent.getAttribute(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a new attribute or changes an existing attribute on this container.
|
||||
*
|
||||
* @param string name
|
||||
* The name of the attribute.
|
||||
* @param string value
|
||||
* The desired attribute value.
|
||||
*/
|
||||
setAttribute: function(name, value) {
|
||||
this._parent.setAttribute(name, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an attribute on this container.
|
||||
*
|
||||
* @param string name
|
||||
* The name of the attribute.
|
||||
*/
|
||||
removeAttribute: function(name) {
|
||||
this._parent.removeAttribute(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Ensures the specified element is visible.
|
||||
*
|
||||
* @param nsIDOMNode element
|
||||
* The element to make visible.
|
||||
*/
|
||||
ensureElementIsVisible: function(element) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the element is visible but not scrolled horizontally.
|
||||
let boxObject = this._list.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
|
||||
boxObject.ensureElementIsVisible(element);
|
||||
boxObject.scrollBy(-element.clientWidth, 0);
|
||||
},
|
||||
|
||||
window: null,
|
||||
document: null,
|
||||
_parent: null,
|
||||
_list: null,
|
||||
_selectedItem: null,
|
||||
_orderedMenuElementsArray: null,
|
||||
_itemsByElement: null
|
||||
};
|
@ -3433,7 +3433,7 @@ EditableNameAndValue.create = Editable.create;
|
||||
|
||||
EditableNameAndValue.prototype = Heritage.extend(EditableName.prototype, {
|
||||
_reset: function(e) {
|
||||
// Hide the Varible or Property if the user presses escape.
|
||||
// Hide the Variable or Property if the user presses escape.
|
||||
this._variable.remove();
|
||||
this.deactivate();
|
||||
},
|
||||
|
@ -45,6 +45,22 @@
|
||||
- button that toggles all breakpoints for all sources. -->
|
||||
<!ENTITY debuggerUI.sources.toggleBreakpoints "Enable/disable all breakpoints">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.tracingNotStarted.label): This is the text
|
||||
- displayed when tracing hasn't started in the debugger UI. -->
|
||||
<!ENTITY debuggerUI.tracingNotStarted.label "Tracing has not started.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.startTracing): This is the text displayed in
|
||||
- the button to start execution tracing. -->
|
||||
<!ENTITY debuggerUI.startTracing "Start Tracing">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.clearButton): This is the label for
|
||||
- the button that clears the collected tracing data in the tracing tab. -->
|
||||
<!ENTITY debuggerUI.clearButton "Clear">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.clearButton.tooltip): This is the tooltip for
|
||||
- the button that clears the collected tracing data in the tracing tab. -->
|
||||
<!ENTITY debuggerUI.clearButton.tooltip "Clear the collected traces">
|
||||
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
|
||||
- checkbox that toggles pausing on exceptions. -->
|
||||
<!ENTITY debuggerUI.pauseExceptions "Pause on exceptions">
|
||||
@ -136,6 +152,7 @@
|
||||
<!-- LOCALIZATION NOTE (debuggerUI.tabs.*): This is the text that
|
||||
- appears in the debugger's side pane tabs. -->
|
||||
<!ENTITY debuggerUI.tabs.sources "Sources">
|
||||
<!ENTITY debuggerUI.tabs.traces "Traces">
|
||||
<!ENTITY debuggerUI.tabs.callstack "Call Stack">
|
||||
<!ENTITY debuggerUI.tabs.variables "Variables">
|
||||
<!ENTITY debuggerUI.tabs.events "Events">
|
||||
|
@ -49,6 +49,14 @@ pauseButtonTooltip=Click to pause (%S)
|
||||
# button when the debugger is in a paused state.
|
||||
resumeButtonTooltip=Click to resume (%S)
|
||||
|
||||
# LOCALIZATION NOTE (startTracingTooltip): The label that is displayed on the trace
|
||||
# button when execution tracing is stopped.
|
||||
startTracingTooltip=Click to start tracing
|
||||
|
||||
# LOCALIZATION NOTE (stopTracingTooltip): The label that is displayed on the trace
|
||||
# button when execution tracing is started.
|
||||
stopTracingTooltip=Click to stop tracing
|
||||
|
||||
# LOCALIZATION NOTE (stepOverTooltip): The label that is displayed on the
|
||||
# button that steps over a function call.
|
||||
stepOverTooltip=Step Over (%S)
|
||||
|
@ -93,6 +93,119 @@
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
/* Tracer */
|
||||
|
||||
#trace {
|
||||
list-style-image: url(tracer-icon.png);
|
||||
-moz-image-region: rect(0px,16px,16px,0px);
|
||||
}
|
||||
|
||||
#trace[checked] {
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
#start-tracing {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#clear-tracer {
|
||||
min-width: 22px !important;
|
||||
}
|
||||
|
||||
#tracer-search {
|
||||
min-width: 72px !important;
|
||||
}
|
||||
|
||||
#tracer-message {
|
||||
/* Prevent the container deck from aquiring the height from this message. */
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.trace-name {
|
||||
-moz-padding-start: 4px !important;
|
||||
}
|
||||
|
||||
/* Tracer dark theme */
|
||||
|
||||
.theme-dark #tracer-message {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-dark #tracer-traces > scrollbox {
|
||||
background-color: #181d20 !important; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-dark .trace-item {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
|
||||
.trace-item.selected-matching {
|
||||
background-color: #1d4f73; /* Select highlight blue */
|
||||
}
|
||||
|
||||
.theme-dark .trace-call {
|
||||
color: #46afe3; /* highlight blue */
|
||||
}
|
||||
|
||||
.theme-dark .trace-return,
|
||||
.theme-dark .trace-yield {
|
||||
color: #70bf53; /* highlight green */
|
||||
}
|
||||
|
||||
.theme-dark .trace-throw {
|
||||
color: #eb5368; /* highlight red */
|
||||
}
|
||||
|
||||
.theme-dark .trace-param {
|
||||
color: #8fa1b2; /* Content text grey */
|
||||
}
|
||||
|
||||
.theme-dark .trace-syntax {
|
||||
color: #5e88b0; /* highlight blue-grey */
|
||||
}
|
||||
|
||||
/* Tracer light theme */
|
||||
|
||||
.theme-light #tracer-message {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-light #tracer-traces > scrollbox {
|
||||
background-color: #f7f7f7 !important; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-light .trace-item {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
||||
.trace-item.selected-matching {
|
||||
background-color: #4c9ed9; /* Select highlight blue */
|
||||
}
|
||||
|
||||
.theme-light .trace-call {
|
||||
color: #0088cc; /* highlight blue */
|
||||
}
|
||||
|
||||
.theme-light .trace-return,
|
||||
.theme-light .trace-yield {
|
||||
color: #2cbb0f; /* highlight green */
|
||||
}
|
||||
|
||||
.theme-light .trace-throw {
|
||||
color: #ed2655; /* highlight red */
|
||||
}
|
||||
|
||||
.theme-light .trace-param {
|
||||
color: #8fa1b2; /* Content text grey */
|
||||
}
|
||||
|
||||
.theme-light .trace-syntax {
|
||||
color: #5f88b0; /* highlight blue-grey */
|
||||
}
|
||||
|
||||
/* ListWidget items */
|
||||
|
||||
.list-widget-item {
|
||||
@ -226,11 +339,6 @@
|
||||
|
||||
/* Instruments pane (watch expressions, variables, event listeners...) */
|
||||
|
||||
#instruments-pane > tabs > tab {
|
||||
min-height: 25px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#instruments-pane .side-menu-widget-container,
|
||||
#instruments-pane .side-menu-widget-empty-notice-container {
|
||||
box-shadow: none !important;
|
||||
|
BIN
browser/themes/linux/devtools/tracer-icon.png
Normal file
BIN
browser/themes/linux/devtools/tracer-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 709 B |
BIN
browser/themes/linux/devtools/tracer-icon@2x.png
Normal file
BIN
browser/themes/linux/devtools/tracer-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -221,6 +221,8 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
|
||||
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
|
||||
skin/classic/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
|
||||
skin/classic/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
|
||||
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
|
||||
|
@ -95,6 +95,119 @@
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
/* Tracer */
|
||||
|
||||
#trace {
|
||||
list-style-image: url(tracer-icon.png);
|
||||
-moz-image-region: rect(0px,16px,16px,0px);
|
||||
}
|
||||
|
||||
#trace[checked] {
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
#start-tracing {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#clear-tracer {
|
||||
min-width: 22px !important;
|
||||
}
|
||||
|
||||
#tracer-search {
|
||||
min-width: 72px !important;
|
||||
}
|
||||
|
||||
#tracer-message {
|
||||
/* Prevent the container deck from aquiring the height from this message. */
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.trace-name {
|
||||
-moz-padding-start: 4px !important;
|
||||
}
|
||||
|
||||
/* Tracer dark theme */
|
||||
|
||||
.theme-dark #tracer-message {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-dark #tracer-traces > scrollbox {
|
||||
background-color: #181d20 !important; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-dark .trace-item {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
|
||||
.trace-item.selected-matching {
|
||||
background-color: #1d4f73; /* Select highlight blue */
|
||||
}
|
||||
|
||||
.theme-dark .trace-call {
|
||||
color: #46afe3; /* highlight blue */
|
||||
}
|
||||
|
||||
.theme-dark .trace-return,
|
||||
.theme-dark .trace-yield {
|
||||
color: #70bf53; /* highlight green */
|
||||
}
|
||||
|
||||
.theme-dark .trace-throw {
|
||||
color: #eb5368; /* highlight red */
|
||||
}
|
||||
|
||||
.theme-dark .trace-param {
|
||||
color: #8fa1b2; /* Content text grey */
|
||||
}
|
||||
|
||||
.theme-dark .trace-syntax {
|
||||
color: #5e88b0; /* highlight blue-grey */
|
||||
}
|
||||
|
||||
/* Tracer light theme */
|
||||
|
||||
.theme-light #tracer-message {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-light #tracer-traces > scrollbox {
|
||||
background-color: #f7f7f7 !important; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-light .trace-item {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
||||
.trace-item.selected-matching {
|
||||
background-color: #4c9ed9; /* Select highlight blue */
|
||||
}
|
||||
|
||||
.theme-light .trace-call {
|
||||
color: #0088cc; /* highlight blue */
|
||||
}
|
||||
|
||||
.theme-light .trace-return,
|
||||
.theme-light .trace-yield {
|
||||
color: #2cbb0f; /* highlight green */
|
||||
}
|
||||
|
||||
.theme-light .trace-throw {
|
||||
color: #ed2655; /* highlight red */
|
||||
}
|
||||
|
||||
.theme-light .trace-param {
|
||||
color: #8fa1b2; /* Content text grey */
|
||||
}
|
||||
|
||||
.theme-light .trace-syntax {
|
||||
color: #5f88b0; /* highlight blue-grey */
|
||||
}
|
||||
|
||||
/* ListWidget items */
|
||||
|
||||
.list-widget-item {
|
||||
@ -228,11 +341,6 @@
|
||||
|
||||
/* Instruments pane (watch expressions, variables, event listeners...) */
|
||||
|
||||
#instruments-pane > tabs > tab {
|
||||
min-height: 1em !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#instruments-pane .side-menu-widget-container,
|
||||
#instruments-pane .side-menu-widget-empty-notice-container {
|
||||
box-shadow: none !important;
|
||||
|
BIN
browser/themes/osx/devtools/tracer-icon.png
Normal file
BIN
browser/themes/osx/devtools/tracer-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 709 B |
BIN
browser/themes/osx/devtools/tracer-icon@2x.png
Normal file
BIN
browser/themes/osx/devtools/tracer-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -324,6 +324,8 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
|
||||
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
|
||||
skin/classic/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
|
||||
skin/classic/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
|
||||
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
|
||||
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
|
||||
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
|
||||
|
@ -93,6 +93,119 @@
|
||||
padding: .25em;
|
||||
}
|
||||
|
||||
/* Tracer */
|
||||
|
||||
#trace {
|
||||
list-style-image: url(tracer-icon.png);
|
||||
-moz-image-region: rect(0px,16px,16px,0px);
|
||||
}
|
||||
|
||||
#trace[checked] {
|
||||
-moz-image-region: rect(0px,32px,16px,16px);
|
||||
}
|
||||
|
||||
#start-tracing {
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
#clear-tracer {
|
||||
min-width: 22px !important;
|
||||
}
|
||||
|
||||
#tracer-search {
|
||||
min-width: 72px !important;
|
||||
}
|
||||
|
||||
#tracer-message {
|
||||
/* Prevent the container deck from aquiring the height from this message. */
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.trace-name {
|
||||
-moz-padding-start: 4px !important;
|
||||
}
|
||||
|
||||
/* Tracer dark theme */
|
||||
|
||||
.theme-dark #tracer-message {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
background: url(background-noise-toolbar.png) #181d20; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-dark #tracer-traces > scrollbox {
|
||||
background-color: #181d20 !important; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-dark .trace-item {
|
||||
color: #f5f7fa; /* Light foreground text */
|
||||
}
|
||||
|
||||
.trace-item.selected-matching {
|
||||
background-color: #1d4f73; /* Select highlight blue */
|
||||
}
|
||||
|
||||
.theme-dark .trace-call {
|
||||
color: #46afe3; /* highlight blue */
|
||||
}
|
||||
|
||||
.theme-dark .trace-return,
|
||||
.theme-dark .trace-yield {
|
||||
color: #70bf53; /* highlight green */
|
||||
}
|
||||
|
||||
.theme-dark .trace-throw {
|
||||
color: #eb5368; /* highlight red */
|
||||
}
|
||||
|
||||
.theme-dark .trace-param {
|
||||
color: #8fa1b2; /* Content text grey */
|
||||
}
|
||||
|
||||
.theme-dark .trace-syntax {
|
||||
color: #5e88b0; /* highlight blue-grey */
|
||||
}
|
||||
|
||||
/* Tracer light theme */
|
||||
|
||||
.theme-light #tracer-message {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
background: url(background-noise-toolbar.png) #f7f7f7; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-light #tracer-traces > scrollbox {
|
||||
background-color: #f7f7f7 !important; /* Content background sidebar */
|
||||
}
|
||||
|
||||
.theme-light .trace-item {
|
||||
color: #292e33; /* Dark foreground text */
|
||||
}
|
||||
|
||||
.trace-item.selected-matching {
|
||||
background-color: #4c9ed9; /* Select highlight blue */
|
||||
}
|
||||
|
||||
.theme-light .trace-call {
|
||||
color: #0088cc; /* highlight blue */
|
||||
}
|
||||
|
||||
.theme-light .trace-return,
|
||||
.theme-light .trace-yield {
|
||||
color: #2cbb0f; /* highlight green */
|
||||
}
|
||||
|
||||
.theme-light .trace-throw {
|
||||
color: #ed2655; /* highlight red */
|
||||
}
|
||||
|
||||
.theme-light .trace-param {
|
||||
color: #8fa1b2; /* Content text grey */
|
||||
}
|
||||
|
||||
.theme-light .trace-syntax {
|
||||
color: #5f88b0; /* highlight blue-grey */
|
||||
}
|
||||
|
||||
/* ListWidget items */
|
||||
|
||||
.list-widget-item {
|
||||
@ -226,11 +339,6 @@
|
||||
|
||||
/* Instruments pane (watch expressions, variables, event listeners...) */
|
||||
|
||||
#instruments-pane > tabs > tab {
|
||||
min-height: 25px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
#instruments-pane .side-menu-widget-container,
|
||||
#instruments-pane .side-menu-widget-empty-notice-container {
|
||||
box-shadow: none !important;
|
||||
|
BIN
browser/themes/windows/devtools/tracer-icon.png
Normal file
BIN
browser/themes/windows/devtools/tracer-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 709 B |
BIN
browser/themes/windows/devtools/tracer-icon@2x.png
Normal file
BIN
browser/themes/windows/devtools/tracer-icon@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -249,6 +249,8 @@ browser.jar:
|
||||
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
|
||||
skin/classic/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
|
||||
skin/classic/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
|
||||
skin/classic/browser/devtools/tracer-icon.png (devtools/tracer-icon.png)
|
||||
skin/classic/browser/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
|
||||
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
|
||||
@ -557,6 +559,8 @@ browser.jar:
|
||||
skin/classic/aero/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
|
||||
skin/classic/aero/browser/devtools/debugger-blackboxMessageEye.png (devtools/debugger-blackboxMessageEye.png)
|
||||
skin/classic/aero/browser/devtools/debugger-toggleBreakpoints.png (devtools/debugger-toggleBreakpoints.png)
|
||||
skin/classic/aero/devtools/tracer-icon.png (devtools/tracer-icon.png)
|
||||
skin/classic/aero/devtools/tracer-icon@2x.png (devtools/tracer-icon@2x.png)
|
||||
skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
|
||||
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
|
||||
skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
|
||||
|
@ -83,11 +83,37 @@ this.makeInfallible = function makeInfallible(aHandler, aName) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interleaves two arrays element by element, returning the combined array, like
|
||||
* a zip. In the case of arrays with different sizes, undefined values will be
|
||||
* interleaved at the end along with the extra values of the larger array.
|
||||
*
|
||||
* @param Array a
|
||||
* @param Array b
|
||||
* @returns Array
|
||||
* The combined array, in the form [a1, b1, a2, b2, ...]
|
||||
*/
|
||||
this.zip = function zip(a, b) {
|
||||
if (!b) {
|
||||
return a;
|
||||
}
|
||||
if (!a) {
|
||||
return b;
|
||||
}
|
||||
const pairs = [];
|
||||
for (let i = 0, aLength = a.length, bLength = b.length;
|
||||
i < aLength || i < bLength;
|
||||
i++) {
|
||||
pairs.push([a[i], b[i]]);
|
||||
}
|
||||
return pairs;
|
||||
};
|
||||
|
||||
const executeSoon = aFn => {
|
||||
Services.tm.mainThread.dispatch({
|
||||
run: this.makeInfallible(aFn)
|
||||
}, Components.interfaces.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
|
||||
|
@ -22,6 +22,7 @@ this.DevToolsUtils = {
|
||||
safeErrorString: safeErrorString,
|
||||
reportException: reportException,
|
||||
makeInfallible: makeInfallible,
|
||||
zip: zip,
|
||||
yieldingEach: yieldingEach,
|
||||
reportingDisabled: false , // Used by tests.
|
||||
defineLazyPrototypeGetter: defineLazyPrototypeGetter,
|
||||
|
@ -44,12 +44,12 @@ const BUFFER_SEND_DELAY = 50;
|
||||
/**
|
||||
* The maximum number of arguments we will send for any single function call.
|
||||
*/
|
||||
const MAX_ARGUMENTS = 5;
|
||||
const MAX_ARGUMENTS = 3;
|
||||
|
||||
/**
|
||||
* The maximum number of an object's properties we will serialize.
|
||||
*/
|
||||
const MAX_PROPERTIES = 5;
|
||||
const MAX_PROPERTIES = 3;
|
||||
|
||||
/**
|
||||
* The complete set of trace types supported.
|
||||
@ -315,7 +315,6 @@ TraceActor.prototype = {
|
||||
* The stack frame that was entered.
|
||||
*/
|
||||
onEnterFrame: function(aFrame) {
|
||||
let callee = aFrame.callee;
|
||||
let packet = {
|
||||
type: "enteredFrame",
|
||||
sequence: this._sequence++
|
||||
@ -365,7 +364,7 @@ TraceActor.prototype = {
|
||||
if (i++ > MAX_ARGUMENTS) {
|
||||
break;
|
||||
}
|
||||
packet.arguments.push(createValueGrip(arg, true));
|
||||
packet.arguments.push(createValueSnapshot(arg, true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -415,16 +414,16 @@ TraceActor.prototype = {
|
||||
}
|
||||
|
||||
if (aCompletion) {
|
||||
if (this._requestsForTraceType.return) {
|
||||
packet.return = createValueGrip(aCompletion.return, true);
|
||||
if (this._requestsForTraceType.return && "return" in aCompletion) {
|
||||
packet.return = createValueSnapshot(aCompletion.return, true);
|
||||
}
|
||||
|
||||
if (this._requestsForTraceType.throw) {
|
||||
packet.throw = createValueGrip(aCompletion.throw, true);
|
||||
else if (this._requestsForTraceType.throw && "throw" in aCompletion) {
|
||||
packet.throw = createValueSnapshot(aCompletion.throw, true);
|
||||
}
|
||||
|
||||
if (this._requestsForTraceType.yield) {
|
||||
packet.yield = createValueGrip(aCompletion.yield, true);
|
||||
else if (this._requestsForTraceType.yield && "yield" in aCompletion) {
|
||||
packet.yield = createValueSnapshot(aCompletion.yield, true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,30 +548,9 @@ MapStack.prototype = {
|
||||
// TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when
|
||||
// it is implemented.
|
||||
function getOffsetColumn(aOffset, aScript) {
|
||||
let bestOffsetMapping = null;
|
||||
for (let offsetMapping of aScript.getAllColumnOffsets()) {
|
||||
if (!bestOffsetMapping ||
|
||||
(offsetMapping.offset <= aOffset &&
|
||||
offsetMapping.offset > bestOffsetMapping.offset)) {
|
||||
bestOffsetMapping = offsetMapping;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bestOffsetMapping) {
|
||||
// XXX: Try not to completely break the experience of using the
|
||||
// tracer for the user by assuming column 0. Simultaneously,
|
||||
// report the error so that there is a paper trail if the
|
||||
// assumption is bad and the tracing experience becomes wonky.
|
||||
reportException("TraceActor",
|
||||
new Error("Could not find a column for offset " + aOffset +
|
||||
" in the script " + aScript));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return bestOffsetMapping.columnNumber;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Serialization helper functions. Largely copied from script.js and modified
|
||||
// for use in serialization rather than object actor requests.
|
||||
|
||||
@ -582,13 +560,14 @@ function getOffsetColumn(aOffset, aScript) {
|
||||
* @param aValue Debugger.Object|primitive
|
||||
* The value to describe with the created grip.
|
||||
*
|
||||
* @param aUseDescriptor boolean
|
||||
* If true, creates descriptors for objects rather than grips.
|
||||
* @param aDetailed boolean
|
||||
* If true, capture slightly more detailed information, like some
|
||||
* properties on an object.
|
||||
*
|
||||
* @return ValueGrip
|
||||
* A primitive value or a grip object.
|
||||
* @return Object
|
||||
* A primitive value or a snapshot of an object.
|
||||
*/
|
||||
function createValueGrip(aValue, aUseDescriptor) {
|
||||
function createValueSnapshot(aValue, aDetailed=false) {
|
||||
switch (typeof aValue) {
|
||||
case "boolean":
|
||||
return aValue;
|
||||
@ -618,7 +597,9 @@ function createValueGrip(aValue, aUseDescriptor) {
|
||||
if (aValue === null) {
|
||||
return { type: "null" };
|
||||
}
|
||||
return aUseDescriptor ? objectDescriptor(aValue) : objectGrip(aValue);
|
||||
return aDetailed
|
||||
? detailedObjectSnapshot(aValue)
|
||||
: objectSnapshot(aValue);
|
||||
default:
|
||||
reportException("TraceActor",
|
||||
new Error("Failed to provide a grip for: " + aValue));
|
||||
@ -627,96 +608,59 @@ function createValueGrip(aValue, aUseDescriptor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a grip for the given debuggee object.
|
||||
* Create a very minimal snapshot of the given debuggee object.
|
||||
*
|
||||
* @param aObject Debugger.Object
|
||||
* The object to describe with the created grip.
|
||||
*/
|
||||
function objectGrip(aObject) {
|
||||
let g = {
|
||||
function objectSnapshot(aObject) {
|
||||
return {
|
||||
"type": "object",
|
||||
"class": aObject.class,
|
||||
"extensible": aObject.isExtensible(),
|
||||
"frozen": aObject.isFrozen(),
|
||||
"sealed": aObject.isSealed()
|
||||
};
|
||||
|
||||
// Add additional properties for functions.
|
||||
if (aObject.class === "Function") {
|
||||
if (aObject.name) {
|
||||
g.name = aObject.name;
|
||||
}
|
||||
if (aObject.displayName) {
|
||||
g.displayName = aObject.displayName;
|
||||
}
|
||||
|
||||
// Check if the developer has added a de-facto standard displayName
|
||||
// property for us to use.
|
||||
let name = aObject.getOwnPropertyDescriptor("displayName");
|
||||
if (name && name.value && typeof name.value == "string") {
|
||||
g.userDisplayName = createValueGrip(name.value, aObject);
|
||||
}
|
||||
|
||||
// Add source location information.
|
||||
if (aObject.script) {
|
||||
g.url = aObject.script.url;
|
||||
g.line = aObject.script.startLine;
|
||||
}
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a descriptor for the given debuggee object. Descriptors are
|
||||
* identical to grips, with the addition of the prototype,
|
||||
* ownProperties, and safeGetterValues properties.
|
||||
* Create a (slightly more) detailed snapshot of the given debuggee object.
|
||||
*
|
||||
* @param aObject Debugger.Object
|
||||
* The object to describe with the created descriptor.
|
||||
*/
|
||||
function objectDescriptor(aObject) {
|
||||
let desc = objectGrip(aObject);
|
||||
let ownProperties = Object.create(null);
|
||||
function detailedObjectSnapshot(aObject) {
|
||||
let desc = objectSnapshot(aObject);
|
||||
let ownProperties = desc.ownProperties = Object.create(null);
|
||||
|
||||
if (Cu.isDeadWrapper(aObject)) {
|
||||
desc.prototype = createValueGrip(null);
|
||||
desc.ownProperties = ownProperties;
|
||||
desc.safeGetterValues = Object.create(null);
|
||||
if (aObject.class == "DeadObject") {
|
||||
return desc;
|
||||
}
|
||||
|
||||
const names = aObject.getOwnPropertyNames();
|
||||
let i = 0;
|
||||
for (let name of names) {
|
||||
for (let name of aObject.getOwnPropertyNames()) {
|
||||
if (i++ > MAX_PROPERTIES) {
|
||||
break;
|
||||
}
|
||||
let desc = propertyDescriptor(name, aObject);
|
||||
let desc = propertySnapshot(name, aObject);
|
||||
if (desc) {
|
||||
ownProperties[name] = desc;
|
||||
}
|
||||
}
|
||||
|
||||
desc.ownProperties = ownProperties;
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method that creates a property descriptor for the provided object,
|
||||
* properly formatted for sending in a protocol response.
|
||||
* A helper method that creates a snapshot of the object's |aName| property.
|
||||
*
|
||||
* @param aName string
|
||||
* The property that the descriptor is generated for.
|
||||
* The property of which the snapshot is taken.
|
||||
*
|
||||
* @param aObject Debugger.Object
|
||||
* The object whose property the descriptor is generated for.
|
||||
* The object whose property the snapshot is taken of.
|
||||
*
|
||||
* @return object
|
||||
* The property descriptor for the property |aName| in |aObject|.
|
||||
* @return Object
|
||||
* The snapshot of the property.
|
||||
*/
|
||||
function propertyDescriptor(aName, aObject) {
|
||||
function propertySnapshot(aName, aObject) {
|
||||
let desc;
|
||||
try {
|
||||
desc = aObject.getOwnPropertyDescriptor(aName);
|
||||
@ -732,26 +676,18 @@ function propertyDescriptor(aName, aObject) {
|
||||
};
|
||||
}
|
||||
|
||||
// Skip objects since we only support shallow objects anyways.
|
||||
if (!desc || typeof desc.value == "object" && desc.value !== null) {
|
||||
// Only create descriptors for simple values. We skip objects and properties
|
||||
// that have getters and setters; ain't nobody got time for that!
|
||||
if (!desc
|
||||
|| typeof desc.value == "object" && desc.value !== null
|
||||
|| !("value" in desc)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let retval = {
|
||||
return {
|
||||
configurable: desc.configurable,
|
||||
enumerable: desc.enumerable
|
||||
enumerable: desc.enumerable,
|
||||
writable: desc.writable,
|
||||
value: createValueSnapshot(desc.value)
|
||||
};
|
||||
|
||||
if ("value" in desc) {
|
||||
retval.writable = desc.writable;
|
||||
retval.value = createValueGrip(desc.value);
|
||||
} else {
|
||||
if ("get" in desc) {
|
||||
retval.get = createValueGrip(desc.get);
|
||||
}
|
||||
if ("set" in desc) {
|
||||
retval.set = createValueGrip(desc.set);
|
||||
}
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
@ -89,11 +89,14 @@ function test_enter_exit_frame()
|
||||
|
||||
do_check_eq(traces[1].name, "foo");
|
||||
|
||||
// foo's definition is at tracerlocations.js:3:0, but
|
||||
// Debugger.Script does not provide complete definition
|
||||
// locations (bug 901138). tracerlocations.js:4:2 is the first
|
||||
// statement in the function (used as an approximation).
|
||||
check_location(traces[1].location, { url: url, line: 4, column: 2 });
|
||||
// XXX: foo's definition is at tracerlocations.js:3:0, but Debugger.Script
|
||||
// does not provide complete definition locations (bug 901138). Therefore,
|
||||
// we use the first statement in the function (tracerlocations.js:4:2) as
|
||||
// an approximation.
|
||||
//
|
||||
// However, |column| will always be 0 until we can get bug 863089
|
||||
// fixed.
|
||||
check_location(traces[1].location, { url: url, line: 4, column: 0 });
|
||||
check_location(traces[1].callsite, { url: url, line: 8, column: 0 });
|
||||
|
||||
do_check_eq(typeof traces[1].parameterNames, "object");
|
||||
|
@ -67,23 +67,25 @@ function eval_code()
|
||||
let circular = {};
|
||||
circular.self = circular;
|
||||
|
||||
// Make sure there is only 5 properties per object because that is the value
|
||||
// Make sure there is only 3 properties per object because that is the value
|
||||
// of MAX_PROPERTIES in the server.
|
||||
let obj = {
|
||||
num: 0,
|
||||
str: "foo",
|
||||
bool: false,
|
||||
undef: undefined,
|
||||
nil: null
|
||||
};
|
||||
let obj2 = {
|
||||
inf: Infinity,
|
||||
ninf: -Infinity,
|
||||
nan: NaN,
|
||||
nzero: -0,
|
||||
obj: circular
|
||||
undef: undefined,
|
||||
nil: null,
|
||||
inf: Infinity
|
||||
};
|
||||
let obj3 = {
|
||||
ninf: -Infinity,
|
||||
nan: NaN,
|
||||
nzero: -0
|
||||
};
|
||||
let obj4 = {
|
||||
obj: circular,
|
||||
arr: [1,2,3,4,5]
|
||||
};
|
||||
|
||||
@ -100,6 +102,7 @@ function eval_code()
|
||||
identity(obj);
|
||||
identity(obj2);
|
||||
identity(obj3);
|
||||
identity(obj4);
|
||||
} + ")()");
|
||||
}
|
||||
|
||||
@ -170,6 +173,11 @@ function check_trace(aTrace)
|
||||
case 26:
|
||||
case 27:
|
||||
check_obj3(aTrace.type, value);
|
||||
break;
|
||||
case 28:
|
||||
case 29:
|
||||
check_obj4(aTrace.type, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +199,9 @@ function check_obj(aType, aObj) {
|
||||
|
||||
do_check_eq(typeof aObj.ownProperties.bool, "object");
|
||||
do_check_eq(aObj.ownProperties.bool.value, false);
|
||||
}
|
||||
|
||||
function check_obj2(aType, aObj) {
|
||||
do_check_eq(typeof aObj.ownProperties.undef, "object");
|
||||
do_check_eq(typeof aObj.ownProperties.undef.value, "object");
|
||||
do_check_eq(aObj.ownProperties.undef.value.type, "undefined");
|
||||
@ -199,13 +209,13 @@ function check_obj(aType, aObj) {
|
||||
do_check_eq(typeof aObj.ownProperties.nil, "object");
|
||||
do_check_eq(typeof aObj.ownProperties.nil.value, "object");
|
||||
do_check_eq(aObj.ownProperties.nil.value.type, "null");
|
||||
}
|
||||
|
||||
function check_obj2(aType, aObj) {
|
||||
do_check_eq(typeof aObj.ownProperties.inf, "object");
|
||||
do_check_eq(typeof aObj.ownProperties.inf.value, "object");
|
||||
do_check_eq(aObj.ownProperties.inf.value.type, "Infinity");
|
||||
}
|
||||
|
||||
function check_obj3(aType, aObj) {
|
||||
do_check_eq(typeof aObj.ownProperties.ninf, "object");
|
||||
do_check_eq(typeof aObj.ownProperties.ninf.value, "object");
|
||||
do_check_eq(aObj.ownProperties.ninf.value.type, "-Infinity");
|
||||
@ -217,12 +227,10 @@ function check_obj2(aType, aObj) {
|
||||
do_check_eq(typeof aObj.ownProperties.nzero, "object");
|
||||
do_check_eq(typeof aObj.ownProperties.nzero.value, "object");
|
||||
do_check_eq(aObj.ownProperties.nzero.value.type, "-0");
|
||||
}
|
||||
|
||||
function check_obj4(aType, aObj) {
|
||||
// Sub-objects aren't added.
|
||||
do_check_eq(typeof aObj.ownProperties.obj, "undefined");
|
||||
}
|
||||
|
||||
function check_obj3(aType, aObj) {
|
||||
// Sub-objects aren't added.
|
||||
do_check_eq(typeof aObj.ownProperties.arr, "undefined");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user