diff options
Diffstat (limited to 'toolkit/devtools/performance/views/details-js-call-tree.js')
-rw-r--r-- | toolkit/devtools/performance/views/details-js-call-tree.js | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/toolkit/devtools/performance/views/details-js-call-tree.js b/toolkit/devtools/performance/views/details-js-call-tree.js new file mode 100644 index 000000000..4e87a82a4 --- /dev/null +++ b/toolkit/devtools/performance/views/details-js-call-tree.js @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +/** + * CallTree view containing profiler call tree, controlled by DetailsView. + */ +let JsCallTreeView = Heritage.extend(DetailsSubview, { + + rerenderPrefs: [ + "invert-call-tree", + "show-platform-data" + ], + + rangeChangeDebounceTime: 50, // ms + + /** + * Sets up the view with event binding. + */ + initialize: function () { + DetailsSubview.initialize.call(this); + + this._onPrefChanged = this._onPrefChanged.bind(this); + this._onLink = this._onLink.bind(this); + }, + + /** + * Unbinds events. + */ + destroy: function () { + DetailsSubview.destroy.call(this); + }, + + /** + * Method for handling all the set up for rendering a new call tree. + * + * @param object interval [optional] + * The { startTime, endTime }, in milliseconds. + * @param object options [optional] + * Additional options for new the call tree. + */ + render: function (interval={}, options={}) { + let recording = PerformanceController.getCurrentRecording(); + let profile = recording.getProfile(); + let threadNode = this._prepareCallTree(profile, interval, options); + this._populateCallTree(threadNode, options); + this.emit(EVENTS.JS_CALL_TREE_RENDERED); + }, + + /** + * Fired on the "link" event for the call tree in this container. + */ + _onLink: function (_, treeItem) { + let { url, line } = treeItem.frame.getInfo(); + viewSourceInDebugger(url, line).then( + () => this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER), + () => this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER)); + }, + + /** + * Called when the recording is stopped and prepares data to + * populate the call tree. + */ + _prepareCallTree: function (profile, { startTime, endTime }, options) { + let threadSamples = profile.threads[0].samples; + let contentOnly = !PerformanceController.getPref("show-platform-data"); + let invertTree = PerformanceController.getPref("invert-call-tree"); + + let threadNode = new ThreadNode(threadSamples, + { startTime, endTime, contentOnly, invertTree }); + + // If we have an empty profile (no samples), then don't invert the tree, as + // it would hide the root node and a completely blank call tree space can be + // mis-interpreted as an error. + options.inverted = invertTree && threadNode.samples > 0; + + return threadNode; + }, + + /** + * Renders the call tree. + */ + _populateCallTree: function (frameNode, options={}) { + let root = new CallView({ + frame: frameNode, + inverted: options.inverted, + // Root nodes are hidden in inverted call trees. + hidden: options.inverted, + // Call trees should only auto-expand when not inverted. Passing undefined + // will default to the CALL_TREE_AUTO_EXPAND depth. + autoExpandDepth: options.inverted ? 0 : undefined, + }); + + // Bind events. + root.on("link", this._onLink); + + // Pipe "focus" events to the view, mostly for tests + root.on("focus", () => this.emit("focus")); + + // Clear out other call trees. + let container = $("#js-calltree-view > .call-tree-cells-container"); + container.innerHTML = ""; + root.attachTo(container); + + // Profiler data does not contain memory allocations information. + root.toggleAllocations(false); + + // When platform data isn't shown, hide the cateogry labels, since they're + // only available for C++ frames. + let contentOnly = !PerformanceController.getPref("show-platform-data"); + root.toggleCategories(!contentOnly); + }, + + toString: () => "[object JsCallTreeView]" +}); + +/** + * Opens/selects the debugger in this toolbox and jumps to the specified + * file name and line number. + * @param string url + * @param number line + */ +let viewSourceInDebugger = Task.async(function *(url, line) { + // If the Debugger was already open, switch to it and try to show the + // source immediately. Otherwise, initialize it and wait for the sources + // to be added first. + let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger"); + let { panelWin: dbg } = yield gToolbox.selectTool("jsdebugger"); + + if (!debuggerAlreadyOpen) { + yield dbg.once(dbg.EVENTS.SOURCES_ADDED); + } + + let { DebuggerView } = dbg; + let { Sources } = DebuggerView; + + let item = Sources.getItemForAttachment(a => a.source.url === url); + if (item) { + return DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true }); + } + + return Promise.reject("Couldn't find the specified source in the debugger."); +}); |