348 lines
12 KiB
JavaScript
348 lines
12 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";
|
|
|
|
const { Cc, Ci, Cu, Cr } = require("chrome");
|
|
const { ViewHelpers } = require("resource:///modules/devtools/ViewHelpers.jsm");
|
|
|
|
// String used to fill in platform data when it should be hidden.
|
|
const GECKO_SYMBOL = "(Gecko)";
|
|
|
|
/**
|
|
* Localization convenience methods.
|
|
+ TODO: merge these into a single file: Bug 1082695.
|
|
*/
|
|
const L10N = new ViewHelpers.MultiL10N([
|
|
"chrome://browser/locale/devtools/timeline.properties",
|
|
"chrome://browser/locale/devtools/profiler.properties"
|
|
]);
|
|
|
|
/**
|
|
* A list of preferences for this tool. The values automatically update
|
|
* if somebody edits edits about:config or the prefs change somewhere else.
|
|
*/
|
|
const Prefs = new ViewHelpers.Prefs("devtools.performance.ui", {
|
|
showPlatformData: ["Bool", "show-platform-data"]
|
|
}, {
|
|
monitorChanges: true
|
|
});
|
|
|
|
/**
|
|
* Details about each profile entry cateogry.
|
|
* @see CATEGORY_MAPPINGS.
|
|
*/
|
|
const CATEGORIES = [{
|
|
color: "#5e88b0",
|
|
abbrev: "other",
|
|
label: L10N.getStr("category.other")
|
|
}, {
|
|
color: "#46afe3",
|
|
abbrev: "css",
|
|
label: L10N.getStr("category.css")
|
|
}, {
|
|
color: "#d96629",
|
|
abbrev: "js",
|
|
label: L10N.getStr("category.js")
|
|
}, {
|
|
color: "#eb5368",
|
|
abbrev: "gc",
|
|
label: L10N.getStr("category.gc")
|
|
}, {
|
|
color: "#df80ff",
|
|
abbrev: "network",
|
|
label: L10N.getStr("category.network")
|
|
}, {
|
|
color: "#70bf53",
|
|
abbrev: "graphics",
|
|
label: L10N.getStr("category.graphics")
|
|
}, {
|
|
color: "#8fa1b2",
|
|
abbrev: "storage",
|
|
label: L10N.getStr("category.storage")
|
|
}, {
|
|
color: "#d99b28",
|
|
abbrev: "events",
|
|
label: L10N.getStr("category.events")
|
|
}];
|
|
|
|
/**
|
|
* Mapping from category bitmasks in the profiler data to additional details.
|
|
* To be kept in sync with the js::ProfileEntry::Category in ProfilingStack.h
|
|
*/
|
|
const CATEGORY_MAPPINGS = {
|
|
"16": CATEGORIES[0], // js::ProfileEntry::Category::OTHER
|
|
"32": CATEGORIES[1], // js::ProfileEntry::Category::CSS
|
|
"64": CATEGORIES[2], // js::ProfileEntry::Category::JS
|
|
"128": CATEGORIES[3], // js::ProfileEntry::Category::GC
|
|
"256": CATEGORIES[3], // js::ProfileEntry::Category::CC
|
|
"512": CATEGORIES[4], // js::ProfileEntry::Category::NETWORK
|
|
"1024": CATEGORIES[5], // js::ProfileEntry::Category::GRAPHICS
|
|
"2048": CATEGORIES[6], // js::ProfileEntry::Category::STORAGE
|
|
"4096": CATEGORIES[7], // js::ProfileEntry::Category::EVENTS
|
|
};
|
|
|
|
/**
|
|
* A simple schema for mapping markers to the timeline UI. The keys correspond
|
|
* to marker names, while the values are objects with the following format:
|
|
*
|
|
* - group: The row index in the timeline overview graph; multiple markers
|
|
* can be added on the same row. @see <overview.js/buildGraphImage>
|
|
* - label: The label used in the waterfall to identify the marker. Can be a
|
|
* string or just a function that accepts the marker and returns a
|
|
* string, if you want to use a dynamic property for the main label.
|
|
* If you use a function for a label, it *must* handle the case where
|
|
* no marker is provided for a main label to describe all markers of
|
|
* this type.
|
|
* - colorName: The label of the DevTools color used for this marker. If
|
|
* adding a new color, be sure to check that there's an entry
|
|
* for `.marker-details-bullet.{COLORNAME}` for the equivilent
|
|
* entry in ./browser/themes/shared/devtools/performance.inc.css
|
|
* https://developer.mozilla.org/en-US/docs/Tools/DevToolsColors
|
|
* - fields: An optional array of marker properties you wish to display in the
|
|
* marker details view. For example, a field in the array such as
|
|
* { property: "aCauseName", label: "Cause" } would render a string
|
|
* like `Cause: ${marker.aCauseName}` in the marker details view.
|
|
* Each `field` item may take the following properties:
|
|
* - property: The property that must exist on the marker to render,
|
|
* and the value of the property will be displayed.
|
|
* - label: The name of the property that should be displayed.
|
|
* - formatter: If a formatter is provided, instead of directly using
|
|
* the `property` property on the marker, the marker is
|
|
* passed into the formatter function to determine the
|
|
* displayed value.
|
|
* Can also be a function that returns an object. Each key in the object
|
|
* will be rendered as a field, with its value rendering as the value.
|
|
*
|
|
* Whenever this is changed, browser_timeline_waterfall-styles.js *must* be
|
|
* updated as well.
|
|
*/
|
|
const TIMELINE_BLUEPRINT = {
|
|
/* Group 0 - Reflow and Rendering pipeline */
|
|
"Styles": {
|
|
group: 0,
|
|
colorName: "graphs-purple",
|
|
label: L10N.getStr("timeline.label.styles2"),
|
|
fields: getStylesFields,
|
|
},
|
|
"Reflow": {
|
|
group: 0,
|
|
colorName: "graphs-purple",
|
|
label: L10N.getStr("timeline.label.reflow2")
|
|
},
|
|
"Paint": {
|
|
group: 0,
|
|
colorName: "graphs-green",
|
|
label: L10N.getStr("timeline.label.paint")
|
|
},
|
|
|
|
/* Group 1 - JS */
|
|
"DOMEvent": {
|
|
group: 1,
|
|
colorName: "graphs-yellow",
|
|
label: L10N.getStr("timeline.label.domevent"),
|
|
fields: getDOMEventFields,
|
|
},
|
|
"Javascript": {
|
|
group: 1,
|
|
colorName: "graphs-yellow",
|
|
label: getJSLabel,
|
|
fields: getJSFields,
|
|
},
|
|
"Parse HTML": {
|
|
group: 1,
|
|
colorName: "graphs-yellow",
|
|
label: L10N.getStr("timeline.label.parseHTML")
|
|
},
|
|
"Parse XML": {
|
|
group: 1,
|
|
colorName: "graphs-yellow",
|
|
label: L10N.getStr("timeline.label.parseXML")
|
|
},
|
|
"GarbageCollection": {
|
|
group: 1,
|
|
colorName: "graphs-red",
|
|
label: getGCLabel,
|
|
fields: [
|
|
{ property: "causeName", label: "Reason:" },
|
|
{ property: "nonincrementalReason", label: "Non-incremental Reason:" }
|
|
]
|
|
},
|
|
|
|
/* Group 2 - User Controlled */
|
|
"ConsoleTime": {
|
|
group: 2,
|
|
colorName: "graphs-grey",
|
|
label: sublabelForProperty(L10N.getStr("timeline.label.consoleTime"), "causeName"),
|
|
fields: [{
|
|
property: "causeName",
|
|
label: L10N.getStr("timeline.markerDetail.consoleTimerName")
|
|
}]
|
|
},
|
|
"TimeStamp": {
|
|
group: 2,
|
|
colorName: "graphs-blue",
|
|
label: sublabelForProperty(L10N.getStr("timeline.label.timestamp"), "causeName"),
|
|
fields: [{
|
|
property: "causeName",
|
|
label: "Label:"
|
|
}]
|
|
},
|
|
};
|
|
|
|
/**
|
|
* A series of formatters used by the blueprint.
|
|
*/
|
|
|
|
function getGCLabel (marker={}) {
|
|
let label = L10N.getStr("timeline.label.garbageCollection");
|
|
// Only if a `nonincrementalReason` exists, do we want to label
|
|
// this as a non incremental GC event.
|
|
if ("nonincrementalReason" in marker) {
|
|
label = `${label} (Non-incremental)`;
|
|
}
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* Mapping of JS marker causes to a friendlier form. Only
|
|
* markers that are considered "from content" should be labeled here.
|
|
*/
|
|
const JS_MARKER_MAP = {
|
|
"<script> element": "Script Tag",
|
|
"setInterval handler": "setInterval",
|
|
"setTimeout handler": "setTimeout",
|
|
"FrameRequestCallback": "requestAnimationFrame",
|
|
"promise callback": "Promise Callback",
|
|
"promise initializer": "Promise Init",
|
|
"Worker runnable": "Worker",
|
|
"javascript: URI": "JavaScript URI",
|
|
// The difference between these two event handler markers are differences
|
|
// in their WebIDL implementation, so distinguishing them is not necessary.
|
|
"EventHandlerNonNull": "Event Handler",
|
|
"EventListener.handleEvent": "Event Handler",
|
|
};
|
|
|
|
function getJSLabel (marker={}) {
|
|
let generic = L10N.getStr("timeline.label.javascript2");
|
|
if ("causeName" in marker) {
|
|
return JS_MARKER_MAP[marker.causeName] || generic;
|
|
}
|
|
return generic;
|
|
}
|
|
|
|
/**
|
|
* Returns a hash for computing a fields object for a JS marker. If the cause
|
|
* is considered content (so an entry exists in the JS_MARKER_MAP), do not display it
|
|
* since it's redundant with the label. Otherwise for Gecko code, either display
|
|
* the cause, or "(Gecko)", depending on if "show-platform-data" is set.
|
|
*/
|
|
function getJSFields (marker) {
|
|
if ("causeName" in marker && !JS_MARKER_MAP[marker.causeName]) {
|
|
return { Reason: Prefs.showPlatformData ? marker.causeName : GECKO_SYMBOL };
|
|
}
|
|
}
|
|
|
|
function getDOMEventFields (marker) {
|
|
let fields = Object.create(null);
|
|
if ("type" in marker) {
|
|
fields[L10N.getStr("timeline.markerDetail.DOMEventType")] = marker.type;
|
|
}
|
|
if ("eventPhase" in marker) {
|
|
let phase;
|
|
if (marker.eventPhase === Ci.nsIDOMEvent.AT_TARGET) {
|
|
phase = L10N.getStr("timeline.markerDetail.DOMEventTargetPhase");
|
|
} else if (marker.eventPhase === Ci.nsIDOMEvent.CAPTURING_PHASE) {
|
|
phase = L10N.getStr("timeline.markerDetail.DOMEventCapturingPhase");
|
|
} else if (marker.eventPhase === Ci.nsIDOMEvent.BUBBLING_PHASE) {
|
|
phase = L10N.getStr("timeline.markerDetail.DOMEventBubblingPhase");
|
|
}
|
|
fields[L10N.getStr("timeline.markerDetail.DOMEventPhase")] = phase;
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
function getStylesFields (marker) {
|
|
if ("restyleHint" in marker) {
|
|
return { "Restyle Hint": marker.restyleHint.replace(/eRestyle_/g, "") };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Takes a main label (like "Timestamp") and a property,
|
|
* and returns a marker that will print out the property
|
|
* value for a marker if it exists ("Timestamp (rendering)"),
|
|
* or just the main label if it does not.
|
|
*/
|
|
function sublabelForProperty (mainLabel, prop) {
|
|
return (marker={}) => marker[prop] ? `${mainLabel} (${marker[prop]})` : mainLabel;
|
|
}
|
|
|
|
/**
|
|
* Get the numeric bitmask (or set of masks) for the given category
|
|
* abbreviation. See CATEGORIES and CATEGORY_MAPPINGS above.
|
|
*
|
|
* CATEGORY_MASK can be called with just a name if it is expected that the
|
|
* category is mapped to by exactly one bitmask. If the category is mapped
|
|
* to by multiple masks, CATEGORY_MASK for that name must be called with
|
|
* an additional argument specifying the desired id (in ascending order).
|
|
*/
|
|
const [CATEGORY_MASK, CATEGORY_MASK_LIST] = (function () {
|
|
let bitmasksForCategory = {};
|
|
let all = Object.keys(CATEGORY_MAPPINGS);
|
|
|
|
for (let category of CATEGORIES) {
|
|
bitmasksForCategory[category.abbrev] = all
|
|
.filter(mask => CATEGORY_MAPPINGS[mask] == category)
|
|
.map(mask => +mask)
|
|
.sort();
|
|
}
|
|
|
|
return [
|
|
function (name, index) {
|
|
if (!(name in bitmasksForCategory)) {
|
|
throw new Error(`Category abbreviation '${name}' does not exist.`);
|
|
}
|
|
if (arguments.length == 1) {
|
|
if (bitmasksForCategory[name].length != 1) {
|
|
throw new Error(`Expected exactly one category number for '${name}'.`);
|
|
} else {
|
|
return bitmasksForCategory[name][0];
|
|
}
|
|
} else {
|
|
if (index > bitmasksForCategory[name].length) {
|
|
throw new Error(`Index '${index}' too high for category '${name}'.`);
|
|
} else {
|
|
return bitmasksForCategory[name][index - 1];
|
|
}
|
|
}
|
|
},
|
|
|
|
function (name) {
|
|
if (!(name in bitmasksForCategory)) {
|
|
throw new Error(`Category abbreviation '${name}' does not exist.`);
|
|
}
|
|
return bitmasksForCategory[name];
|
|
}
|
|
];
|
|
})();
|
|
|
|
// Human-readable "other" category bitmask. Older Geckos don't have all the
|
|
// necessary instrumentation in the sampling profiler backend for creating
|
|
// a categories graph, in which case we default to the "other" category.
|
|
const CATEGORY_OTHER = CATEGORY_MASK('other');
|
|
|
|
// Human-readable JIT category bitmask. Certain pseudo-frames in a sample,
|
|
// like "EnterJIT", don't have any associated `cateogry` information.
|
|
const CATEGORY_JIT = CATEGORY_MASK('js');
|
|
|
|
// Exported symbols.
|
|
exports.L10N = L10N;
|
|
exports.TIMELINE_BLUEPRINT = TIMELINE_BLUEPRINT;
|
|
exports.CATEGORIES = CATEGORIES;
|
|
exports.CATEGORY_MAPPINGS = CATEGORY_MAPPINGS;
|
|
exports.CATEGORY_MASK = CATEGORY_MASK;
|
|
exports.CATEGORY_MASK_LIST = CATEGORY_MASK_LIST;
|
|
exports.CATEGORY_OTHER = CATEGORY_OTHER;
|
|
exports.CATEGORY_JIT = CATEGORY_JIT;
|