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 27ac8abace
commit 77e22fc34d
35 changed files with 1822 additions and 178 deletions

View File

@ -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);

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.

View File

@ -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();

View File

@ -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,

View File

@ -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"

View File

@ -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);
});
},

View File

@ -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]

View 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;
});

View 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;
});

View 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;
});

View 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;
});

View 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) {
}
}

View 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>

View File

@ -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);
}

View 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
};

View File

@ -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();
},

View File

@ -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">

View File

@ -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)

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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)

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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)

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 709 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -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)

View File

@ -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

View File

@ -22,6 +22,7 @@ this.DevToolsUtils = {
safeErrorString: safeErrorString,
reportException: reportException,
makeInfallible: makeInfallible,
zip: zip,
yieldingEach: yieldingEach,
reportingDisabled: false , // Used by tests.
defineLazyPrototypeGetter: defineLazyPrototypeGetter,

View File

@ -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;
}

View File

@ -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");

View File

@ -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");
}