--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1116,16 +1116,17 @@ pref("devtools.debugger.chrome-enabled",
pref("devtools.debugger.chrome-debugging-host", "localhost");
pref("devtools.debugger.chrome-debugging-port", 6080);
pref("devtools.debugger.remote-host", "localhost");
pref("devtools.debugger.remote-timeout", 20000);
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);
pref("devtools.debugger.ui.panes-instruments-width", 300);
pref("devtools.debugger.ui.panes-visible-on-startup", false);
pref("devtools.debugger.ui.variables-sorting-enabled", true);
pref("devtools.debugger.ui.variables-only-enum-visible", false);
pref("devtools.debugger.ui.variables-searchbox-visible", false);
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -94,16 +94,17 @@ Cu.import("resource:///modules/devtools/
Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
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");
XPCOMUtils.defineLazyModuleGetter(this, "devtools",
"resource://gre/modules/devtools/Loader.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils",
@@ -187,16 +188,17 @@ let DebuggerController = {
window.removeEventListener("unload", this.shutdownDebugger, true);
}
return this._shutdown = DebuggerView.destroy().then(() => {
DebuggerView.destroy();
this.SourceScripts.disconnect();
this.StackFrames.disconnect();
this.ThreadState.disconnect();
+ this.Tracer.disconnect();
this.disconnect();
// Chrome debugging needs to close its parent process on shutdown.
if (window._isChromeDebugger) {
return this._quitApp();
} else {
return promise.resolve(null); // Done.
}
@@ -213,49 +215,54 @@ let DebuggerController = {
* @return object
* A promise that is resolved when the debugger finishes connecting.
*/
connect: function() {
if (this._connection) {
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;
},
/**
* Disconnects the debugger client and removes event handlers as necessary.
*/
disconnect: function() {
// Return early if the client didn't even have a chance to instantiate.
if (!this.client) {
@@ -326,31 +333,23 @@ let DebuggerController = {
if (aResponse.error == "wrongOrder") {
DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl);
}
},
/**
* 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;
}
this.activeThread = aThreadClient;
this.ThreadState.connect();
this.StackFrames.connect();
@@ -361,31 +360,23 @@ let DebuggerController = {
aCallback();
}
}, { useSourceMaps: Prefs.sourceMapsEnabled });
},
/**
* 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;
}
this.activeThread = aThreadClient;
this.ThreadState.connect();
this.StackFrames.connect();
@@ -394,16 +385,40 @@ let DebuggerController = {
if (aCallback) {
aCallback();
}
}, { 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.
*/
reconfigureThread: function(aUseSourceMaps) {
this.client.reconfigureThread({ useSourceMaps: aUseSourceMaps }, aResponse => {
if (aResponse.error) {
let msg = "Couldn't reconfigure thread: " + aResponse.message;
Cu.reportError(msg);
@@ -1407,16 +1422,228 @@ SourceScripts.prototype = {
}
}
return deferred.promise;
}
};
/**
+ * 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.
*/
function EventListeners() {
this._onEventListeners = this._onEventListeners.bind(this);
}
EventListeners.prototype = {
/**
@@ -1950,16 +2177,17 @@ let Prefs = new ViewHelpers.Prefs("devto
panesVisibleOnStartup: ["Bool", "debugger.ui.panes-visible-on-startup"],
variablesSortingEnabled: ["Bool", "debugger.ui.variables-sorting-enabled"],
variablesOnlyEnumVisible: ["Bool", "debugger.ui.variables-only-enum-visible"],
variablesSearchboxVisible: ["Bool", "debugger.ui.variables-searchbox-visible"],
pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
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"]
});
/**
* Returns true if this is a chrome debugger instance.
* @return boolean
*/
XPCOMUtils.defineLazyGetter(window, "_isChromeDebugger", function() {
@@ -1977,16 +2205,17 @@ EventEmitter.decorate(this);
*/
DebuggerController.initialize();
DebuggerController.Parser = new Parser();
DebuggerController.ThreadState = new ThreadState();
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.
*/
Object.defineProperties(window, {
"gTarget": {
get: function() DebuggerController._target
},
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -1052,16 +1052,386 @@ SourcesView.prototype = Heritage.extend(
_cmPopup: null,
_cbPanel: null,
_cbTextbox: null,
_selectedBreakpointItem: null,
_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.
*/
let SourceUtils = {
_labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
_groupsCache: new Map(),
/**
* Returns true if the specified url and/or content type are specific to
@@ -2784,11 +3154,12 @@ LineResults.size = function() {
return count;
};
/**
* Preliminary setup for the DebuggerView object.
*/
DebuggerView.Sources = new SourcesView();
DebuggerView.VariableBubble = new VariableBubbleView();
+DebuggerView.Tracer = new TracerView();
DebuggerView.WatchExpressions = new WatchExpressionsView();
DebuggerView.EventListeners = new EventListenersView();
DebuggerView.GlobalSearch = new GlobalSearchView();
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -55,16 +55,17 @@ let DebuggerView = {
this.Filtering.initialize();
this.FilteredSources.initialize();
this.FilteredFunctions.initialize();
this.ChromeGlobals.initialize();
this.StackFrames.initialize();
this.StackFramesClassicList.initialize();
this.Sources.initialize();
this.VariableBubble.initialize();
+ this.Tracer.initialize();
this.WatchExpressions.initialize();
this.EventListeners.initialize();
this.GlobalSearch.initialize();
this._initializeVariablesView();
this._initializeEditor(deferred.resolve);
document.title = L10N.getStr("DebuggerWindowTitle");
@@ -90,16 +91,17 @@ let DebuggerView = {
this.Filtering.destroy();
this.FilteredSources.destroy();
this.FilteredFunctions.destroy();
this.ChromeGlobals.destroy();
this.StackFrames.destroy();
this.StackFramesClassicList.destroy();
this.Sources.destroy();
this.VariableBubble.destroy();
+ this.Tracer.destroy();
this.WatchExpressions.destroy();
this.EventListeners.destroy();
this.GlobalSearch.destroy();
this._destroyPanes();
this._destroyEditor(deferred.resolve);
return deferred.promise;
},
@@ -164,17 +166,21 @@ let DebuggerView = {
searchEnabled: Prefs.variablesSearchboxVisible,
eval: DebuggerController.StackFrames.evaluate,
lazyEmpty: true
});
// 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.
this.Variables.on("fetched", (aEvent, aType) => {
switch (aType) {
case "variables":
window.emit(EVENTS.FETCHED_VARIABLES);
break;
@@ -632,16 +638,17 @@ let DebuggerView = {
Options: null,
Filtering: null,
FilteredSources: null,
FilteredFunctions: null,
GlobalSearch: null,
ChromeGlobals: null,
StackFrames: null,
Sources: null,
+ Tracer: null,
Variables: null,
VariableBubble: null,
WatchExpressions: null,
EventListeners: null,
editor: null,
_editorSource: {},
_loadingText: "",
_body: null,
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -81,16 +81,22 @@
<command id="toggleShowPanesOnStartup"
oncommand="DebuggerView.Options._toggleShowPanesOnStartup()"/>
<command id="toggleShowOnlyEnum"
oncommand="DebuggerView.Options._toggleShowVariablesOnlyEnum()"/>
<command id="toggleShowVariablesFilterBox"
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">
<menupopup id="sourceEditorContextMenu"
onpopupshowing="goUpdateGlobalEditMenuItems()">
<menuitem id="se-dbg-cMenu-addBreakpoint"
label="&debuggerUI.seMenuBreak;"
key="addBreakpointKey"
@@ -299,16 +305,23 @@
tabindex="0"/>
<toolbarbutton id="step-in"
class="devtools-toolbarbutton"
tabindex="0"/>
<toolbarbutton id="step-out"
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"/>
<vbox id="stackframes" flex="1"/>
<textbox id="searchbox"
class="devtools-searchinput" type="search"/>
<toolbarbutton id="instruments-pane-toggle"
class="devtools-toolbarbutton"
@@ -323,16 +336,17 @@
<scrollbox id="globalsearch" orient="vertical" hidden="true"/>
<splitter class="devtools-horizontal-splitter" hidden="true"/>
<hbox id="debugger-widgets" flex="1">
<tabbox id="sources-pane"
class="devtools-sidebar-tabs">
<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">
<vbox id="sources" flex="1"/>
<toolbar id="sources-toolbar" class="devtools-toolbar">
<hbox id="sources-controls">
<toolbarbutton id="black-box"
class="devtools-toolbarbutton"
@@ -349,16 +363,51 @@
class="devtools-toolbarbutton"
tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
command="toggleBreakpointsCommand"/>
</toolbar>
</tabpanel>
<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"
class="devtools-side-splitter"/>
<deck id="editor-deck" flex="4">
<vbox id="editor"/>
<vbox id="black-boxed-message" align="center" pack="center">
<label id="black-boxed-message-label">
--- a/browser/devtools/debugger/panel.js
+++ b/browser/devtools/debugger/panel.js
@@ -4,16 +4,18 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { 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;
this._destroyer = null;
this._view = this.panelWin.DebuggerView;
this._controller = this.panelWin.DebuggerController;
this._view._hostType = this._toolbox.hostType;
@@ -52,18 +54,17 @@ DebuggerPanel.prototype = {
this._toolbox.on("host-changed", this.handleHostChanged);
this.target.on("thread-paused", this.highlightWhenPaused);
this.target.on("thread-resumed", this.unhighlightWhenResumed);
this.isReady = true;
this.emit("ready");
return this;
})
.then(null, function onError(aReason) {
- Cu.reportError("DebuggerPanel open failed. " +
- aReason.error + ": " + aReason.message);
+ DevToolsUtils.reportException("DebuggerPane.prototype.open", aReason);
});
},
// DevToolPanel API
get target() this._toolbox.target,
destroy: function() {
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
code_location-changes.js
code_math.js
code_math.map
code_math.min.js
code_math_bogus_map.min.js
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
code_ugly-4.js
doc_binary_search.html
doc_blackboxing.html
doc_closures.html
doc_cmd-break.html
@@ -51,16 +52,17 @@ support-files =
doc_random-javascript.html
doc_recursion-stack.html
doc_scope-variable.html
doc_scope-variable-2.html
doc_scope-variable-3.html
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
sjs_random-javascript.sjs
testactors.js
[browser_dbg_aaa_run_first_leaktest.js]
[browser_dbg_bfcache.js]
@@ -183,16 +185,20 @@ support-files =
[browser_dbg_stack-03.js]
[browser_dbg_stack-04.js]
[browser_dbg_stack-05.js]
[browser_dbg_stack-06.js]
[browser_dbg_stack-07.js]
[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]
[browser_dbg_variables-view-04.js]
[browser_dbg_variables-view-05.js]
[browser_dbg_variables-view-accessibility.js]
[browser_dbg_variables-view-data.js]
[browser_dbg_variables-view-edit-getset-01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-01.js
@@ -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;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-02.js
@@ -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;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-03.js
@@ -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;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-04.js
@@ -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;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/code_tracing-01.js
@@ -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) {
+ }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_tracing-01.html
@@ -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>
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -370,16 +370,36 @@ function waitForThreadEvents(aPanel, aEv
thread.removeListener(aEventName, onEvent);
deferred.resolve.apply(deferred, aArgs);
}
});
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;
info("Thread is: '" + state + "'.");
if (state == aState) {
return promise.resolve(null);
@@ -593,8 +613,43 @@ function hideVarPopupByScrollingEditor(a
let popupHiding = once(tooltip, "popuphiding");
editor.setFirstVisibleLine(0);
return popupHiding.then(waitForTick);
}
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);
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/widgets/FastListWidget.js
@@ -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
+};
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -3428,17 +3428,17 @@ EditableValue.prototype = Heritage.exten
function EditableNameAndValue(aVariable, aOptions) {
EditableName.call(this, aVariable, aOptions);
}
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();
},
_next: function(e) {
// Override _next so as to set both key and value at the same time.
let key = this._input.value;
this.label.setAttribute("value", key);
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -40,16 +40,32 @@
<!-- LOCALIZATION NOTE (debuggerUI.sources.prettyPrint): This is the tooltip for the
- button that pretty prints the selected source. -->
<!ENTITY debuggerUI.sources.prettyPrint "Prettify Source">
<!-- LOCALIZATION NOTE (debuggerUI.sources.toggleBreakpoints): This is the tooltip for the
- 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">
<!ENTITY debuggerUI.pauseExceptions.key "E">
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
- checkbox that toggles ignoring caught exceptions. -->
<!ENTITY debuggerUI.ignoreCaughtExceptions "Ignore caught exceptions">
@@ -131,16 +147,17 @@
- appears in the source editor context menu for adding a conditional
- breakpoint. -->
<!ENTITY debuggerUI.seMenuCondBreak "Add conditional breakpoint">
<!ENTITY debuggerUI.seMenuCondBreak.key "B">
<!-- 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">
<!-- LOCALIZATION NOTE (debuggerUI.seMenuAddWatch): This is the text that
- appears in the source editor context menu for adding an expression. -->
<!ENTITY debuggerUI.seMenuAddWatch "Selection to watch expression">
<!ENTITY debuggerUI.seMenuAddWatch.key "E">
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -44,16 +44,24 @@ expandPanes=Expand panes
# LOCALIZATION NOTE (pauseLabel): The label that is displayed on the pause
# button when the debugger is in a running state.
pauseButtonTooltip=Click to pause (%S)
# LOCALIZATION NOTE (resumeLabel): The label that is displayed on the pause
# 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)
# LOCALIZATION NOTE (stepInTooltip): The label that is displayed on the
# button that steps into a function call.
stepInTooltip=Step In (%S)
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -88,16 +88,129 @@
font-size: 120%;
}
#black-boxed-message-button {
margin-top: 1em;
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 {
padding: 2px;
}
.theme-light .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
@@ -221,21 +334,16 @@
.devtools-tooltip-simple-text.token-other {
text-align: center;
color: #333 !important;
}
/* 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;
}
/* Watch expressions view */
#expressions {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8229dd7c78f37edcf897d773a9b84bc5be707322
GIT binary patch
literal 709
zc$@*n0y_PPP)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^0007vNkl<ZNDb{&
zO=uHA6rMMeADdDV4=MJb7*GCy6$-Vfr6?#Bu@|B72k~SJ7V6D|(q8l`>dBvr9u!)d
z=)sEzK|R@ndhjL)>Os<2v8|@Nv%6{L`DSU7-TX8Oc=W-t@8|p8e6#ar8T)T7et|!R
zb8$`D=Uar=zy{LUTlCyu48dFE-e5Cd2o?flr%kJH1rdXrVe8cZSO-1R1DWjX&PQLj
zlrXNERVfoe5F?aqEJ(3^q<)7cBEwJ8$X}1g<9QwT&wcOZ0ww^UAs>+qlYqS%Pxh9n
z;^;zZ`*J`>wD<|8@VNF{F<`Je(zSJnurD-T1o$MqCbZwkXmPI%A{Qx2+;;^;!oeq|
zX@8?m!i)0ysER|>V~@@OhgTh8@v>A0&Sx0n$0kQJM)*S;oX2^!^037By`{tiHS+5v
zgkd?8hG{>~oAwzWp(^Nq_M?}?)bNv;>^(Yvl}s<rcqzU!GYo4~p>t=Iv8Wg1`oOaa
z_|2|Jc&?_lk)Q=G3swAZaCWJ?8iR~|M&#!f#(L-`RLoUMJLaZY7-xVdb*bRU3$a*i
zr2=c>&7&9;x!crdN+Z{m-SaM$$TeZPWmkY9?Q1mJ$r6F4nLYw#8khpib%sOJgmW~6
zcK(zao$|n!h^#!Z&jrLU4AoJ(1=1ZtI<svwT4fOJBvuaUIGo0i(wgfEAm*+rSa+pc
zRYivY!a*lQo^)}vU}Vj5u5x4tblm|p1-AWL`}HgujT)uh>w4e(ax@55ny%V07q9sK
zbtZq-<LX>zoUOh&lCZR27D^(7l!Z(5lNo7y2B^{D*BG#VE#9Su<L%6V7U45lC=6>c
rzGNFgGLt*o_uzfozv?!Y__xr%u-f#VuXHO+00000NkvXXu0mjfRFYBc
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de0ded4c8d12508c0cd4846cd794517ebcf20cbf
GIT binary patch
literal 1323
zc$@(%1=RY9P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(000E?Nkl<ZXa((9
zTWB0r7(V}**`2+lNikkxHkL+giv&u&wIqeohhm{xvB5^`gD6Esyn-|@^@fOIEml!5
z6-4NZB~7Z;Uc?7|kS3|tDusv^t3tBdU`eXm?Cj3$&iVZ(b+#vy+1*JqyD#p6<(%{V
zmv7ED=bSlb8LOAN0(AxI3IwP?I*rZKS#!Ux8{Gk>^_{A-e=N~Ckh;)0klf)Xi2HGx
zkB<l&O@J6j#vajg=2M8M%qM=KzQEub%CVR+*wr<V`tY{BCqw0O1}=b1h5|S0xxy<X
zLR`jc)uxy5Xt=rc==x9pAVIuKRRx%#M6pUY@^4K~<8|JTP<3>JVQE9F`zPAHn7|F{
zIwnkRXl~qZpQDmcQ1rU}>23TY6!u!9rv2i!$^Etc$rZ>jDr56$3kEBI!+wvLzd(q4
zhkDwEWm{1Id#%!ReTkvCRdxutZ88<!h=;=`-Fer4gQ_uk?l9pS-L?SxSrTC@iEzZp
zSzH1c)9k47;BC$@v)Gdkxm=s)lk2Eo&B5DrQRKW&<}C`qEo+JlG*hcvfZ>D}13g%Z
zaN%VO6a#iv&R1LlHki%k*CDVi0<lqcnopY&stoZeuNdTT*3v?+HUZdT&nfin-Ua~A
zBeLc>M!x_+nD|>uxS!(r!sg@Kme^v?lvx5e;6m!ZbGaKs0^W7!;0ix6ky3ZJw?oct
z3$owUKfa1)(~s_)B<$zY8F=vYLoFHER$Kxy7<e1tP$aCrUhTn`dG0+4tEJmLc-vns
z6EtJWW^t*2{GBl^^f9TJujKEt3xz9vk%)TCh5PQV85mosFlBpFEYo{z(=x+%;3Y>3
znRt5SzP1la#g!ExlQHiQX!g?97|G?>SCr(>Qi<F+iKWva_)U8Ar`$Hb{muj9cQeL6
zp`E#5N{@U*ai{&pl3$v()cZ$z+P*9oQ&NCTu5b?%)^-Y>StbS;zLraqrX}g*cbJLL
z(Rdif%8{3|bsk7P$AIl7pP9RaBET2F5#?tz6W+5d)_0=k`k%{j*tR(W(1D2403M^k
z-&HBt%T90Jo~C=fY<{q<{iuxd3ED)_^Sn?g#}AuZb@F+plAmLrB>)XRN~b}Z@_4)B
zP|JGlq4SH9`3XA4;-*^h7C(dOboy#Vjl4|cOFhW}lK;J3Pjh8Zm<7Q<Ym9}Cd-<*z
z9B-q01RYUvrI($sU+I@EVXF)SWWloDrepAc>|7LJS~vI5DeWfN7<ij=i&^Ru@i?4w
z=cYd~z9+2OGp|5P`{v=E8-8>6rH!TJ>B;e3crAn?gjwQl4$Br!P%(qbfL|+n6$PM8
z_I)xGWMj~61U}Tz5ca$dqY#f1pBXf#oR6ma;P7bchzq;iH4N;?3v(wRTXY${)Utr@
z5@03WxZgf~9(CG7JTF2{j;_JfI=TlOr2d?{0R6l40B30L16Ni;;^NSjC9^I<cB0}Q
z;AGWxJ%L<(kqJyVm|f>NKD#&)`CZ1U(O!LUe7P$47j*bpF6XCv{4KPB{Ul>Sw2Mo?
zNfwL6l636d0i2IISS{=Cy4%haxacCzZ)?FjM*y3Yrs~D0roKRbEqz^!hj4R{2$N}X
zVFNwLKDRCCzNY{-35igMYN2;PQTEw_2wk5Hn!H@T(Vh--xVz<H+7ot?-xOVlu6X|`
h$5Xe}75JYk@GrW<rtuxF1FZl6002ovPDHLkV1ltumEiyY
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -216,16 +216,18 @@ browser.jar:
skin/classic/browser/devtools/debugger-pause.png (devtools/debugger-pause.png)
skin/classic/browser/devtools/debugger-play.png (devtools/debugger-play.png)
skin/classic/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
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)
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/browser/devtools/toggle-tools.png (devtools/toggle-tools.png)
skin/classic/browser/devtools/dock-bottom.png (devtools/dock-bottom.png)
skin/classic/browser/devtools/dock-side.png (devtools/dock-side.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -90,16 +90,129 @@
font-size: 120%;
}
#black-boxed-message-button {
margin-top: 1em;
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 {
padding: 2px;
}
.theme-light .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
@@ -223,21 +336,16 @@
.devtools-tooltip-simple-text.token-other {
text-align: center;
color: #333 !important;
}
/* 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;
}
/* Watch expressions view */
#expressions {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8229dd7c78f37edcf897d773a9b84bc5be707322
GIT binary patch
literal 709
zc$@*n0y_PPP)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^0007vNkl<ZNDb{&
zO=uHA6rMMeADdDV4=MJb7*GCy6$-Vfr6?#Bu@|B72k~SJ7V6D|(q8l`>dBvr9u!)d
z=)sEzK|R@ndhjL)>Os<2v8|@Nv%6{L`DSU7-TX8Oc=W-t@8|p8e6#ar8T)T7et|!R
zb8$`D=Uar=zy{LUTlCyu48dFE-e5Cd2o?flr%kJH1rdXrVe8cZSO-1R1DWjX&PQLj
zlrXNERVfoe5F?aqEJ(3^q<)7cBEwJ8$X}1g<9QwT&wcOZ0ww^UAs>+qlYqS%Pxh9n
z;^;zZ`*J`>wD<|8@VNF{F<`Je(zSJnurD-T1o$MqCbZwkXmPI%A{Qx2+;;^;!oeq|
zX@8?m!i)0ysER|>V~@@OhgTh8@v>A0&Sx0n$0kQJM)*S;oX2^!^037By`{tiHS+5v
zgkd?8hG{>~oAwzWp(^Nq_M?}?)bNv;>^(Yvl}s<rcqzU!GYo4~p>t=Iv8Wg1`oOaa
z_|2|Jc&?_lk)Q=G3swAZaCWJ?8iR~|M&#!f#(L-`RLoUMJLaZY7-xVdb*bRU3$a*i
zr2=c>&7&9;x!crdN+Z{m-SaM$$TeZPWmkY9?Q1mJ$r6F4nLYw#8khpib%sOJgmW~6
zcK(zao$|n!h^#!Z&jrLU4AoJ(1=1ZtI<svwT4fOJBvuaUIGo0i(wgfEAm*+rSa+pc
zRYivY!a*lQo^)}vU}Vj5u5x4tblm|p1-AWL`}HgujT)uh>w4e(ax@55ny%V07q9sK
zbtZq-<LX>zoUOh&lCZR27D^(7l!Z(5lNo7y2B^{D*BG#VE#9Su<L%6V7U45lC=6>c
rzGNFgGLt*o_uzfozv?!Y__xr%u-f#VuXHO+00000NkvXXu0mjfRFYBc
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de0ded4c8d12508c0cd4846cd794517ebcf20cbf
GIT binary patch
literal 1323
zc$@(%1=RY9P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(000E?Nkl<ZXa((9
zTWB0r7(V}**`2+lNikkxHkL+giv&u&wIqeohhm{xvB5^`gD6Esyn-|@^@fOIEml!5
z6-4NZB~7Z;Uc?7|kS3|tDusv^t3tBdU`eXm?Cj3$&iVZ(b+#vy+1*JqyD#p6<(%{V
zmv7ED=bSlb8LOAN0(AxI3IwP?I*rZKS#!Ux8{Gk>^_{A-e=N~Ckh;)0klf)Xi2HGx
zkB<l&O@J6j#vajg=2M8M%qM=KzQEub%CVR+*wr<V`tY{BCqw0O1}=b1h5|S0xxy<X
zLR`jc)uxy5Xt=rc==x9pAVIuKRRx%#M6pUY@^4K~<8|JTP<3>JVQE9F`zPAHn7|F{
zIwnkRXl~qZpQDmcQ1rU}>23TY6!u!9rv2i!$^Etc$rZ>jDr56$3kEBI!+wvLzd(q4
zhkDwEWm{1Id#%!ReTkvCRdxutZ88<!h=;=`-Fer4gQ_uk?l9pS-L?SxSrTC@iEzZp
zSzH1c)9k47;BC$@v)Gdkxm=s)lk2Eo&B5DrQRKW&<}C`qEo+JlG*hcvfZ>D}13g%Z
zaN%VO6a#iv&R1LlHki%k*CDVi0<lqcnopY&stoZeuNdTT*3v?+HUZdT&nfin-Ua~A
zBeLc>M!x_+nD|>uxS!(r!sg@Kme^v?lvx5e;6m!ZbGaKs0^W7!;0ix6ky3ZJw?oct
z3$owUKfa1)(~s_)B<$zY8F=vYLoFHER$Kxy7<e1tP$aCrUhTn`dG0+4tEJmLc-vns
z6EtJWW^t*2{GBl^^f9TJujKEt3xz9vk%)TCh5PQV85mosFlBpFEYo{z(=x+%;3Y>3
znRt5SzP1la#g!ExlQHiQX!g?97|G?>SCr(>Qi<F+iKWva_)U8Ar`$Hb{muj9cQeL6
zp`E#5N{@U*ai{&pl3$v()cZ$z+P*9oQ&NCTu5b?%)^-Y>StbS;zLraqrX}g*cbJLL
z(Rdif%8{3|bsk7P$AIl7pP9RaBET2F5#?tz6W+5d)_0=k`k%{j*tR(W(1D2403M^k
z-&HBt%T90Jo~C=fY<{q<{iuxd3ED)_^Sn?g#}AuZb@F+plAmLrB>)XRN~b}Z@_4)B
zP|JGlq4SH9`3XA4;-*^h7C(dOboy#Vjl4|cOFhW}lK;J3Pjh8Zm<7Q<Ym9}Cd-<*z
z9B-q01RYUvrI($sU+I@EVXF)SWWloDrepAc>|7LJS~vI5DeWfN7<ij=i&^Ru@i?4w
z=cYd~z9+2OGp|5P`{v=E8-8>6rH!TJ>B;e3crAn?gjwQl4$Br!P%(qbfL|+n6$PM8
z_I)xGWMj~61U}Tz5ca$dqY#f1pBXf#oR6ma;P7bchzq;iH4N;?3v(wRTXY${)Utr@
z5@03WxZgf~9(CG7JTF2{j;_JfI=TlOr2d?{0R6l40B30L16Ni;;^NSjC9^I<cB0}Q
z;AGWxJ%L<(kqJyVm|f>NKD#&)`CZ1U(O!LUe7P$47j*bpF6XCv{4KPB{Ul>Sw2Mo?
zNfwL6l636d0i2IISS{=Cy4%haxacCzZ)?FjM*y3Yrs~D0roKRbEqz^!hj4R{2$N}X
zVFNwLKDRCCzNY{-35igMYN2;PQTEw_2wk5Hn!H@T(Vh--xVz<H+7ot?-xOVlu6X|`
h$5Xe}75JYk@GrW<rtuxF1FZl6002ovPDHLkV1ltumEiyY
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -319,16 +319,18 @@ browser.jar:
skin/classic/browser/devtools/debugger-pause.png (devtools/debugger-pause.png)
skin/classic/browser/devtools/debugger-play.png (devtools/debugger-play.png)
skin/classic/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
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)
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)
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/browser/devtools/toggle-tools.png (devtools/toggle-tools.png)
skin/classic/browser/devtools/dock-bottom.png (devtools/dock-bottom.png)
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -88,16 +88,129 @@
font-size: 120%;
}
#black-boxed-message-button {
margin-top: 1em;
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 {
padding: 2px;
}
.theme-light .list-widget-item:not(.selected):not(.empty):hover {
background: linear-gradient(rgba(255,255,255,0.9), rgba(255,255,255,0.85)), Highlight;
@@ -221,21 +334,16 @@
.devtools-tooltip-simple-text.token-other {
text-align: center;
color: #333 !important;
}
/* 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;
}
/* Watch expressions view */
#expressions {
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8229dd7c78f37edcf897d773a9b84bc5be707322
GIT binary patch
literal 709
zc$@*n0y_PPP)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^0007vNkl<ZNDb{&
zO=uHA6rMMeADdDV4=MJb7*GCy6$-Vfr6?#Bu@|B72k~SJ7V6D|(q8l`>dBvr9u!)d
z=)sEzK|R@ndhjL)>Os<2v8|@Nv%6{L`DSU7-TX8Oc=W-t@8|p8e6#ar8T)T7et|!R
zb8$`D=Uar=zy{LUTlCyu48dFE-e5Cd2o?flr%kJH1rdXrVe8cZSO-1R1DWjX&PQLj
zlrXNERVfoe5F?aqEJ(3^q<)7cBEwJ8$X}1g<9QwT&wcOZ0ww^UAs>+qlYqS%Pxh9n
z;^;zZ`*J`>wD<|8@VNF{F<`Je(zSJnurD-T1o$MqCbZwkXmPI%A{Qx2+;;^;!oeq|
zX@8?m!i)0ysER|>V~@@OhgTh8@v>A0&Sx0n$0kQJM)*S;oX2^!^037By`{tiHS+5v
zgkd?8hG{>~oAwzWp(^Nq_M?}?)bNv;>^(Yvl}s<rcqzU!GYo4~p>t=Iv8Wg1`oOaa
z_|2|Jc&?_lk)Q=G3swAZaCWJ?8iR~|M&#!f#(L-`RLoUMJLaZY7-xVdb*bRU3$a*i
zr2=c>&7&9;x!crdN+Z{m-SaM$$TeZPWmkY9?Q1mJ$r6F4nLYw#8khpib%sOJgmW~6
zcK(zao$|n!h^#!Z&jrLU4AoJ(1=1ZtI<svwT4fOJBvuaUIGo0i(wgfEAm*+rSa+pc
zRYivY!a*lQo^)}vU}Vj5u5x4tblm|p1-AWL`}HgujT)uh>w4e(ax@55ny%V07q9sK
zbtZq-<LX>zoUOh&lCZR27D^(7l!Z(5lNo7y2B^{D*BG#VE#9Su<L%6V7U45lC=6>c
rzGNFgGLt*o_uzfozv?!Y__xr%u-f#VuXHO+00000NkvXXu0mjfRFYBc
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..de0ded4c8d12508c0cd4846cd794517ebcf20cbf
GIT binary patch
literal 1323
zc$@(%1=RY9P)<h;3K|Lk000e1NJLTq002M$001Be1^@s6qMd$(000E?Nkl<ZXa((9
zTWB0r7(V}**`2+lNikkxHkL+giv&u&wIqeohhm{xvB5^`gD6Esyn-|@^@fOIEml!5
z6-4NZB~7Z;Uc?7|kS3|tDusv^t3tBdU`eXm?Cj3$&iVZ(b+#vy+1*JqyD#p6<(%{V
zmv7ED=bSlb8LOAN0(AxI3IwP?I*rZKS#!Ux8{Gk>^_{A-e=N~Ckh;)0klf)Xi2HGx
zkB<l&O@J6j#vajg=2M8M%qM=KzQEub%CVR+*wr<V`tY{BCqw0O1}=b1h5|S0xxy<X
zLR`jc)uxy5Xt=rc==x9pAVIuKRRx%#M6pUY@^4K~<8|JTP<3>JVQE9F`zPAHn7|F{
zIwnkRXl~qZpQDmcQ1rU}>23TY6!u!9rv2i!$^Etc$rZ>jDr56$3kEBI!+wvLzd(q4
zhkDwEWm{1Id#%!ReTkvCRdxutZ88<!h=;=`-Fer4gQ_uk?l9pS-L?SxSrTC@iEzZp
zSzH1c)9k47;BC$@v)Gdkxm=s)lk2Eo&B5DrQRKW&<}C`qEo+JlG*hcvfZ>D}13g%Z
zaN%VO6a#iv&R1LlHki%k*CDVi0<lqcnopY&stoZeuNdTT*3v?+HUZdT&nfin-Ua~A
zBeLc>M!x_+nD|>uxS!(r!sg@Kme^v?lvx5e;6m!ZbGaKs0^W7!;0ix6ky3ZJw?oct
z3$owUKfa1)(~s_)B<$zY8F=vYLoFHER$Kxy7<e1tP$aCrUhTn`dG0+4tEJmLc-vns
z6EtJWW^t*2{GBl^^f9TJujKEt3xz9vk%)TCh5PQV85mosFlBpFEYo{z(=x+%;3Y>3
znRt5SzP1la#g!ExlQHiQX!g?97|G?>SCr(>Qi<F+iKWva_)U8Ar`$Hb{muj9cQeL6
zp`E#5N{@U*ai{&pl3$v()cZ$z+P*9oQ&NCTu5b?%)^-Y>StbS;zLraqrX}g*cbJLL
z(Rdif%8{3|bsk7P$AIl7pP9RaBET2F5#?tz6W+5d)_0=k`k%{j*tR(W(1D2403M^k
z-&HBt%T90Jo~C=fY<{q<{iuxd3ED)_^Sn?g#}AuZb@F+plAmLrB>)XRN~b}Z@_4)B
zP|JGlq4SH9`3XA4;-*^h7C(dOboy#Vjl4|cOFhW}lK;J3Pjh8Zm<7Q<Ym9}Cd-<*z
z9B-q01RYUvrI($sU+I@EVXF)SWWloDrepAc>|7LJS~vI5DeWfN7<ij=i&^Ru@i?4w
z=cYd~z9+2OGp|5P`{v=E8-8>6rH!TJ>B;e3crAn?gjwQl4$Br!P%(qbfL|+n6$PM8
z_I)xGWMj~61U}Tz5ca$dqY#f1pBXf#oR6ma;P7bchzq;iH4N;?3v(wRTXY${)Utr@
z5@03WxZgf~9(CG7JTF2{j;_JfI=TlOr2d?{0R6l40B30L16Ni;;^NSjC9^I<cB0}Q
z;AGWxJ%L<(kqJyVm|f>NKD#&)`CZ1U(O!LUe7P$47j*bpF6XCv{4KPB{Ul>Sw2Mo?
zNfwL6l636d0i2IISS{=Cy4%haxacCzZ)?FjM*y3Yrs~D0roKRbEqz^!hj4R{2$N}X
zVFNwLKDRCCzNY{-35igMYN2;PQTEw_2wk5Hn!H@T(Vh--xVz<H+7ot?-xOVlu6X|`
h$5Xe}75JYk@GrW<rtuxF1FZl6002ovPDHLkV1ltumEiyY
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -244,16 +244,18 @@ browser.jar:
skin/classic/browser/devtools/debugger-pause.png (devtools/debugger-pause.png)
skin/classic/browser/devtools/debugger-play.png (devtools/debugger-play.png)
skin/classic/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
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)
skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/browser/devtools/toggle-tools.png (devtools/toggle-tools.png)
skin/classic/browser/devtools/dock-bottom.png (devtools/dock-bottom.png)
skin/classic/browser/devtools/dock-side.png (devtools/dock-side.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
@@ -552,16 +554,18 @@ browser.jar:
skin/classic/aero/browser/devtools/debugger-pause.png (devtools/debugger-pause.png)
skin/classic/aero/browser/devtools/debugger-play.png (devtools/debugger-play.png)
skin/classic/aero/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/aero/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/aero/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
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)
skin/classic/aero/browser/devtools/responsive-background.png (devtools/responsive-background.png)
skin/classic/aero/browser/devtools/toggle-tools.png (devtools/toggle-tools.png)
skin/classic/aero/browser/devtools/dock-bottom.png (devtools/dock-bottom.png)
skin/classic/aero/browser/devtools/dock-side.png (devtools/dock-side.png)
skin/classic/aero/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -78,21 +78,47 @@ this.makeInfallible = function makeInfal
if (aName) {
who += " " + aName;
}
reportException(who, ex);
}
}
}
+/**
+ * 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
* very large arrays by yielding to the browser and continuing execution on the
* next tick.
*
* @param Array aArray
* The array being iterated over.
--- a/toolkit/devtools/DevToolsUtils.jsm
+++ b/toolkit/devtools/DevToolsUtils.jsm
@@ -17,14 +17,15 @@ this.EXPORTED_SYMBOLS = [ "DevToolsUtils
Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader)
.loadSubScript("resource://gre/modules/devtools/DevToolsUtils.js", this);
this.DevToolsUtils = {
safeErrorString: safeErrorString,
reportException: reportException,
makeInfallible: makeInfallible,
+ zip: zip,
yieldingEach: yieldingEach,
reportingDisabled: false , // Used by tests.
defineLazyPrototypeGetter: defineLazyPrototypeGetter,
getProperty: getProperty,
hasSafeGetter: hasSafeGetter,
};
--- a/toolkit/devtools/server/actors/tracer.js
+++ b/toolkit/devtools/server/actors/tracer.js
@@ -39,22 +39,22 @@ const { setTimeout } = require("sdk/time
* The number of milliseconds we should buffer frame enter/exit packets before
* sending.
*/
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.
*/
const TRACE_TYPES = new Set([
"time",
"return",
"throw",
@@ -310,17 +310,16 @@ TraceActor.prototype = {
/**
* Called by the engine when a frame is entered. Sends an unsolicited packet
* to the client carrying requested trace information.
*
* @param aFrame Debugger.frame
* The stack frame that was entered.
*/
onEnterFrame: function(aFrame) {
- let callee = aFrame.callee;
let packet = {
type: "enteredFrame",
sequence: this._sequence++
};
if (this._requestsForTraceType.name) {
packet.name = aFrame.callee
? aFrame.callee.displayName || "(anonymous function)"
@@ -360,17 +359,17 @@ TraceActor.prototype = {
if (this._requestsForTraceType.arguments && aFrame.arguments) {
packet.arguments = [];
let i = 0;
for (let arg of aFrame.arguments) {
if (i++ > MAX_ARGUMENTS) {
break;
}
- packet.arguments.push(createValueGrip(arg, true));
+ packet.arguments.push(createValueSnapshot(arg, true));
}
}
if (this._requestsForTraceType.depth) {
packet.depth = aFrame.depth;
}
const onExitFrame = this.onExitFrame;
@@ -410,26 +409,26 @@ TraceActor.prototype = {
packet.time = Date.now() - this._startTime;
}
if (this._requestsForTraceType.depth) {
packet.depth = aFrame.depth;
}
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);
}
}
this._send(packet);
}
};
/**
@@ -544,56 +543,36 @@ MapStack.prototype = {
}
return value;
}
};
// 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.
/**
* Create a grip for the given debuggee value.
*
* @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;
case "string":
if (aValue.length >= DebuggerServer.LONG_STRING_LENGTH) {
return {
type: "longString",
initial: aValue.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH),
@@ -613,145 +592,102 @@ function createValueGrip(aValue, aUseDes
}
return aValue;
case "undefined":
return { type: "undefined" };
case "object":
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));
return null;
}
}
/**
- * 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);
} catch (e) {
// Calling getOwnPropertyDescriptor on wrapped native prototypes is not
// allowed (bug 560072). Inform the user with a bogus, but hopefully
// explanatory, descriptor.
return {
configurable: false,
writable: false,
enumerable: false,
value: e.name
};
}
- // 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;
}
--- a/toolkit/devtools/server/tests/unit/test_trace_actor-05.js
+++ b/toolkit/devtools/server/tests/unit/test_trace_actor-05.js
@@ -84,21 +84,24 @@ function test_enter_exit_frame()
.then(stop_trace)
.then(function() {
let url = getFileUrl("tracerlocations.js");
check_location(traces[0].location, { url: url, line: 1, column: 0 });
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");
do_check_eq(traces[1].parameterNames.length, 1);
do_check_eq(traces[1].parameterNames[0], "x");
do_check_eq(typeof traces[1].arguments, "object");
do_check_true(Array.isArray(traces[1].arguments));
--- a/toolkit/devtools/server/tests/unit/test_trace_actor-06.js
+++ b/toolkit/devtools/server/tests/unit/test_trace_actor-06.js
@@ -62,49 +62,52 @@ function eval_code()
gDebuggee.eval("(" + function() {
function identity(x) {
return x;
}
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,
+ undef: undefined,
+ nil: null,
+ inf: Infinity
+ };
+ let obj3 = {
ninf: -Infinity,
nan: NaN,
- nzero: -0,
- obj: circular
+ nzero: -0
};
- let obj3 = {
+ let obj4 = {
+ obj: circular,
arr: [1,2,3,4,5]
};
identity();
identity(0);
identity("");
identity(false);
identity(undefined);
identity(null);
identity(Infinity);
identity(-Infinity);
identity(NaN);
identity(-0);
identity(obj);
identity(obj2);
identity(obj3);
+ identity(obj4);
} + ")()");
}
function stop_trace()
{
let deferred = defer();
gTraceClient.stopTrace(null, function() { deferred.resolve(); });
return deferred.promise;
@@ -165,16 +168,21 @@ function check_trace(aTrace)
break;
case 24:
case 25:
check_obj2(aTrace.type, value);
break;
case 26:
case 27:
check_obj3(aTrace.type, value);
+ break;
+ case 28:
+ case 29:
+ check_obj4(aTrace.type, value);
+ break;
}
}
function check_value(aActual, aExpectedType, aExpectedValue)
{
do_check_eq(typeof aActual, aExpectedType);
do_check_eq(aExpectedType === "object" ? aActual.type : aActual, aExpectedValue);
}
@@ -186,43 +194,43 @@ function check_obj(aType, aObj) {
do_check_eq(typeof aObj.ownProperties.num, "object");
do_check_eq(aObj.ownProperties.num.value, 0);
do_check_eq(typeof aObj.ownProperties.str, "object");
do_check_eq(aObj.ownProperties.str.value, "foo");
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");
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");
do_check_eq(typeof aObj.ownProperties.nan, "object");
do_check_eq(typeof aObj.ownProperties.nan.value, "object");
do_check_eq(aObj.ownProperties.nan.value.type, "NaN");
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");
-
- // Sub-objects aren't added.
- do_check_eq(typeof aObj.ownProperties.obj, "undefined");
}
-function check_obj3(aType, aObj) {
+function check_obj4(aType, aObj) {
// Sub-objects aren't added.
+ do_check_eq(typeof aObj.ownProperties.obj, "undefined");
do_check_eq(typeof aObj.ownProperties.arr, "undefined");
}