/* 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"; /** * This file contains the rendering code for the marker sidebar. */ const EventEmitter = require("devtools/shared/event-emitter"); const { MarkerDOMUtils } = require("devtools/client/performance/modules/marker-dom-utils"); /** * A detailed view for one single marker. * * @param nsIDOMNode parent * The parent node holding the view. * @param nsIDOMNode splitter * The splitter node that the resize event is bound to. */ function MarkerDetails(parent, splitter) { EventEmitter.decorate(this); this._document = parent.ownerDocument; this._parent = parent; this._splitter = splitter; this._onClick = this._onClick.bind(this); this._onSplitterMouseUp = this._onSplitterMouseUp.bind(this); this._parent.addEventListener("click", this._onClick); this._splitter.addEventListener("mouseup", this._onSplitterMouseUp); this.hidden = true; } MarkerDetails.prototype = { /** * Sets this view's width. * @param number */ set width(value) { this._parent.setAttribute("width", value); }, /** * Sets this view's width. * @return number */ get width() { return +this._parent.getAttribute("width"); }, /** * Sets this view's visibility. * @param boolean */ set hidden(value) { if (this._parent.hidden != value) { this._parent.hidden = value; this.emit("resize"); } }, /** * Gets this view's visibility. * @param boolean */ get hidden() { return this._parent.hidden; }, /** * Clears the marker details from this view. */ empty: function () { this._parent.innerHTML = ""; }, /** * Populates view with marker's details. * * @param object params * An options object holding: * - marker: The marker to display. * - frames: Array of stack frame information; see stack.js. * - allocations: Whether or not allocations were enabled for this * recording. [optional] */ render: function (options) { let { marker, frames } = options; this.empty(); let elements = []; elements.push(MarkerDOMUtils.buildTitle(this._document, marker)); elements.push(MarkerDOMUtils.buildDuration(this._document, marker)); MarkerDOMUtils.buildFields(this._document, marker).forEach(f => elements.push(f)); MarkerDOMUtils.buildCustom(this._document, marker, options) .forEach(f => elements.push(f)); // Build a stack element -- and use the "startStack" label if // we have both a startStack and endStack. if (marker.stack) { let type = marker.endStack ? "startStack" : "stack"; elements.push(MarkerDOMUtils.buildStackTrace(this._document, { frameIndex: marker.stack, frames, type })); } if (marker.endStack) { let type = "endStack"; elements.push(MarkerDOMUtils.buildStackTrace(this._document, { frameIndex: marker.endStack, frames, type })); } elements.forEach(el => this._parent.appendChild(el)); }, /** * Handles click in the marker details view. Based on the target, * can handle different actions -- only supporting view source links * for the moment. */ _onClick: function (e) { let data = findActionFromEvent(e.target, this._parent); if (!data) { return; } this.emit(data.action, data); }, /** * Handles the "mouseup" event on the marker details view splitter. */ _onSplitterMouseUp: function () { this.emit("resize"); } }; /** * Take an element from an event `target`, and ascend through * the DOM, looking for an element with a `data-action` attribute. Return * the parsed `data-action` value found, or null if none found before * reaching the parent `container`. * * @param {Element} target * @param {Element} container * @return {?object} */ function findActionFromEvent(target, container) { let el = target; let action; while (el !== container) { action = el.getAttribute("data-action"); if (action) { return JSON.parse(action); } el = el.parentNode; } return null; } exports.MarkerDetails = MarkerDetails;