Files
tubestation/browser/devtools/performance/modules/global.js

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;