Files
palemoon27/toolkit/devtools/shared/timeline/marker-details.js
T

302 lines
9.5 KiB
JavaScript

/* 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";
let { Ci } = require("chrome");
let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
/**
* This file contains the rendering code for the marker sidebar.
*/
loader.lazyRequireGetter(this, "L10N",
"devtools/shared/timeline/global", true);
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/shared/timeline/global", true);
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
/**
* 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._splitter.addEventListener("mouseup", () => this.emit("resize"));
}
MarkerDetails.prototype = {
/**
* Removes any node references from this view.
*/
destroy: function() {
this.empty();
this._parent = null;
this._splitter = null;
},
/**
* Clears the view.
*/
empty: function() {
this._parent.innerHTML = "";
},
/**
* Builds the label representing marker's type.
*
* @param string type
* Could be "Paint", "Reflow", "Styles", ...
* See TIMELINE_BLUEPRINT in widgets/global.js
*/
buildMarkerTypeLabel: function(type) {
let blueprint = TIMELINE_BLUEPRINT[type];
let hbox = this._document.createElement("hbox");
hbox.setAttribute("align", "center");
let bullet = this._document.createElement("hbox");
bullet.className = "marker-details-bullet";
bullet.style.backgroundColor = blueprint.fill;
bullet.style.borderColor = blueprint.stroke;
let label = this._document.createElement("label");
label.className = "marker-details-type";
label.setAttribute("value", blueprint.label);
hbox.appendChild(bullet);
hbox.appendChild(label);
return hbox;
},
/**
* Builds labels for name:value pairs. Like "Start: 100ms",
* "Duration: 200ms", ...
*
* @param string l10nName
* String identifier for label's name.
* @param string value
* Label's value.
*/
buildNameValueLabel: function(l10nName, value) {
let hbox = this._document.createElement("hbox");
let labelName = this._document.createElement("label");
let labelValue = this._document.createElement("label");
labelName.className = "plain marker-details-labelname";
labelValue.className = "plain marker-details-labelvalue";
labelName.setAttribute("value", L10N.getStr(l10nName));
labelValue.setAttribute("value", value);
hbox.appendChild(labelName);
hbox.appendChild(labelValue);
return hbox;
},
/**
* Populates view with marker's details.
*
* @param object params
* An options object holding:
* toolbox - The toolbox.
* marker - The marker to display.
* frames - Array of stack frame information; see stack.js.
*/
render: function({toolbox: toolbox, marker: marker, frames: frames}) {
this.empty();
// UI for any marker
let title = this.buildMarkerTypeLabel(marker.name);
let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
let start = this.buildNameValueLabel("timeline.markerDetail.start", toMs(marker.start));
let end = this.buildNameValueLabel("timeline.markerDetail.end", toMs(marker.end));
let duration = this.buildNameValueLabel("timeline.markerDetail.duration", toMs(marker.end - marker.start));
start.classList.add("marker-details-start");
end.classList.add("marker-details-end");
duration.classList.add("marker-details-duration");
this._parent.appendChild(title);
this._parent.appendChild(start);
this._parent.appendChild(end);
this._parent.appendChild(duration);
// UI for specific markers
switch (marker.name) {
case "ConsoleTime":
this.renderConsoleTimeMarker(this._parent, marker);
break;
case "DOMEvent":
this.renderDOMEventMarker(this._parent, marker);
break;
default:
}
if (marker.stack) {
let property = "timeline.markerDetail.stack";
if (marker.endStack) {
property = "timeline.markerDetail.startStack";
}
this.renderStackTrace({toolbox: toolbox, parent: this._parent, property: property,
frameIndex: marker.stack, frames: frames});
}
if (marker.endStack) {
this.renderStackTrace({toolbox: toolbox, parent: this._parent, property: "timeline.markerDetail.endStack",
frameIndex: marker.endStack, frames: frames});
}
},
/**
* Render a stack trace.
*
* @param object params
* An options object with the following members:
* object toolbox - The toolbox.
* nsIDOMNode parent - The parent node holding the view.
* string property - String identifier for label's name.
* integer frameIndex - The index of the topmost stack frame.
* array frames - Array of stack frames.
*/
renderStackTrace: function({toolbox: toolbox, parent: parent,
property: property, frameIndex: frameIndex,
frames: frames}) {
let labelName = this._document.createElement("label");
labelName.className = "plain marker-details-labelname";
labelName.setAttribute("value", L10N.getStr(property));
parent.appendChild(labelName);
while (frameIndex > 0) {
let frame = frames[frameIndex];
let url = frame.source;
let displayName = frame.functionDisplayName;
let line = frame.line;
let hbox = this._document.createElement("hbox");
if (displayName) {
let functionLabel = this._document.createElement("label");
functionLabel.setAttribute("value", displayName);
hbox.appendChild(functionLabel);
}
if (url) {
let aNode = this._document.createElement("a");
aNode.className = "waterfall-marker-location theme-link devtools-monospace";
aNode.href = url;
aNode.draggable = false;
aNode.setAttribute("title", url);
let text = WebConsoleUtils.abbreviateSourceURL(url) + ":" + line;
let label = this._document.createElement("label");
label.setAttribute("value", text);
aNode.appendChild(label);
hbox.appendChild(aNode);
aNode.addEventListener("click", (event) => {
event.preventDefault();
viewSourceInDebugger(toolbox, url, line);
});
}
if (!displayName && !url) {
let label = this._document.createElement("label");
label.setAttribute("value", L10N.getStr("timeline.markerDetail.unknownFrame"));
hbox.appendChild(label);
}
parent.appendChild(hbox);
frameIndex = frame.parent;
}
},
/**
* Render details of a console marker (console.time).
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderConsoleTimeMarker: function(parent, marker) {
if ("causeName" in marker) {
let timerName = this.buildNameValueLabel("timeline.markerDetail.consoleTimerName", marker.causeName);
this._parent.appendChild(timerName);
}
},
/**
* Render details of a DOM Event marker.
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderDOMEventMarker: function(parent, marker) {
if ("type" in marker) {
let type = this.buildNameValueLabel("timeline.markerDetail.DOMEventType", marker.type);
this._parent.appendChild(type);
}
if ("eventPhase" in marker) {
let phaseL10NProp;
if (marker.eventPhase == Ci.nsIDOMEvent.AT_TARGET) {
phaseL10NProp = "timeline.markerDetail.DOMEventTargetPhase";
}
if (marker.eventPhase == Ci.nsIDOMEvent.CAPTURING_PHASE) {
phaseL10NProp = "timeline.markerDetail.DOMEventCapturingPhase";
}
if (marker.eventPhase == Ci.nsIDOMEvent.BUBBLING_PHASE) {
phaseL10NProp = "timeline.markerDetail.DOMEventBubblingPhase";
}
let phase = this.buildNameValueLabel("timeline.markerDetail.DOMEventPhase", L10N.getStr(phaseL10NProp));
this._parent.appendChild(phase);
}
},
};
/**
* Opens/selects the debugger in this toolbox and jumps to the specified
* file name and line number.
* @param object toolbox
* The toolbox.
* @param string url
* @param number line
*/
let viewSourceInDebugger = Task.async(function *(toolbox, 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 = toolbox.getPanel("jsdebugger");
let { panelWin: dbg } = yield toolbox.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.");
});
exports.MarkerDetails = MarkerDetails;