diff --git a/browser/devtools/performance/modules/logic/marker-utils.js b/browser/devtools/performance/modules/logic/marker-utils.js index 43608eaac9df..55a4edbe20cf 100644 --- a/browser/devtools/performance/modules/logic/marker-utils.js +++ b/browser/devtools/performance/modules/logic/marker-utils.js @@ -8,7 +8,7 @@ * and parsing out the blueprint to generate correct values for markers. */ -const { Ci } = require("chrome"); +const { Cu, Ci } = require("chrome"); loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); @@ -22,6 +22,18 @@ loader.lazyRequireGetter(this, "WebConsoleUtils", // String used to fill in platform data when it should be hidden. const GECKO_SYMBOL = "(Gecko)"; +/** + * Takes a marker, blueprint, and filter list and + * determines if this marker should be filtered or not. + */ +function isMarkerValid (marker, filter) { + let isUnknown = !(marker.name in TIMELINE_BLUEPRINT); + if (isUnknown) { + return filter.indexOf("UNKNOWN") === -1; + } + return filter.indexOf(marker.name) === -1; +} + /** * Returns the correct label to display for passed in marker, based * off of the blueprints. @@ -30,7 +42,7 @@ const GECKO_SYMBOL = "(Gecko)"; * @return {string} */ function getMarkerLabel (marker) { - let blueprint = TIMELINE_BLUEPRINT[marker.name]; + let blueprint = getBlueprintFor(marker); // Either use the label function in the blueprint, or use it directly // as a string. return typeof blueprint.label === "function" ? blueprint.label(marker) : blueprint.label; @@ -44,7 +56,7 @@ function getMarkerLabel (marker) { * @return {string} */ function getMarkerClassName (type) { - let blueprint = TIMELINE_BLUEPRINT[type]; + let blueprint = getBlueprintFor({ name: type }); // Either use the label function in the blueprint, or use it directly // as a string. let className = typeof blueprint.label === "function" ? blueprint.label() : blueprint.label; @@ -72,7 +84,7 @@ function getMarkerClassName (type) { * @return {Array} */ function getMarkerFields (marker) { - let blueprint = TIMELINE_BLUEPRINT[marker.name]; + let blueprint = getBlueprintFor(marker); // If blueprint.fields is a function, use that if (typeof blueprint.fields === "function") { @@ -111,7 +123,7 @@ const DOM = { * @return {Array} */ buildFields: function (doc, marker) { - let blueprint = TIMELINE_BLUEPRINT[marker.name]; + let blueprint = getBlueprintFor(marker); let fields = getMarkerFields(marker); return fields.map(({ label, value }) => DOM.buildNameValueLabel(doc, label, value)); @@ -125,7 +137,7 @@ const DOM = { * @return {Element} */ buildTitle: function (doc, marker) { - let blueprint = TIMELINE_BLUEPRINT[marker.name]; + let blueprint = getBlueprintFor(marker); let hbox = doc.createElement("hbox"); hbox.setAttribute("align", "center"); @@ -377,6 +389,14 @@ const JS_MARKER_MAP = { * A series of formatters used by the blueprint. */ const Formatters = { + /** + * Uses the marker name as the label for markers that do not have + * a blueprint entry. Uses "Other" in the marker filter menu. + */ + UnknownLabel: function (marker={}) { + return marker.name || L10N.getStr("timeline.label.unknown"); + }, + GCLabel: function (marker={}) { let label = L10N.getStr("timeline.label.garbageCollection"); // Only if a `nonincrementalReason` exists, do we want to label @@ -444,9 +464,22 @@ const Formatters = { }, }; +/** + * Takes a marker and returns the definition for that marker type, + * falling back to the UNKNOWN definition if undefined. + * + * @param {Marker} marker + * @return {object} + */ +function getBlueprintFor (marker) { + return TIMELINE_BLUEPRINT[marker.name] || TIMELINE_BLUEPRINT.UNKNOWN; +} + +exports.isMarkerValid = isMarkerValid; exports.getMarkerLabel = getMarkerLabel; exports.getMarkerClassName = getMarkerClassName; exports.getMarkerFields = getMarkerFields; exports.DOM = DOM; exports.CollapseFunctions = CollapseFunctions; exports.Formatters = Formatters; +exports.getBlueprintFor = getBlueprintFor; diff --git a/browser/devtools/performance/modules/logic/recording-utils.js b/browser/devtools/performance/modules/logic/recording-utils.js index af7f7fc3d563..5569d99b0750 100644 --- a/browser/devtools/performance/modules/logic/recording-utils.js +++ b/browser/devtools/performance/modules/logic/recording-utils.js @@ -196,55 +196,6 @@ function getProfileThreadFromAllocations(allocations) { return thread; } -/** - * Gets the current timeline blueprint without the hidden markers. - * - * @param blueprint - * The default timeline blueprint. - * @param array hiddenMarkers - * A list of hidden markers' names. - * @return object - * The filtered timeline blueprint. - */ -function getFilteredBlueprint({ blueprint, hiddenMarkers }) { - // Clone functions here just to prevent an error, as the blueprint - // contains functions (even though we do not use them). - let filteredBlueprint = Cu.cloneInto(blueprint, {}, { cloneFunctions: true }); - let maybeRemovedGroups = new Set(); - let removedGroups = new Set(); - - // 1. Remove hidden markers from the blueprint. - - for (let hiddenMarkerName of hiddenMarkers) { - maybeRemovedGroups.add(filteredBlueprint[hiddenMarkerName].group); - delete filteredBlueprint[hiddenMarkerName]; - } - - // 2. Get a list of all the groups that will be removed. - - for (let maybeRemovedGroup of maybeRemovedGroups) { - let markerNames = Object.keys(filteredBlueprint); - let isGroupRemoved = markerNames.every(e => filteredBlueprint[e].group != maybeRemovedGroup); - if (isGroupRemoved) { - removedGroups.add(maybeRemovedGroup); - } - } - - // 3. Offset groups so that their indices are consecutive. - - for (let removedGroup of removedGroups) { - let markerNames = Object.keys(filteredBlueprint); - for (let markerName of markerNames) { - let markerDetails = filteredBlueprint[markerName]; - if (markerDetails.group > removedGroup) { - markerDetails.group--; - } - } - } - - return filteredBlueprint; -}; - /** * Deduplicates a profile by deduplicating stacks, frames, and strings. * @@ -571,7 +522,6 @@ exports.offsetSampleTimes = offsetSampleTimes; exports.offsetMarkerTimes = offsetMarkerTimes; exports.offsetAndScaleTimestamps = offsetAndScaleTimestamps; exports.getProfileThreadFromAllocations = getProfileThreadFromAllocations; -exports.getFilteredBlueprint = getFilteredBlueprint; exports.deflateProfile = deflateProfile; exports.deflateThread = deflateThread; exports.UniqueStrings = UniqueStrings; diff --git a/browser/devtools/performance/modules/logic/waterfall-utils.js b/browser/devtools/performance/modules/logic/waterfall-utils.js index d2228fb43061..b1bf06f654dd 100644 --- a/browser/devtools/performance/modules/logic/waterfall-utils.js +++ b/browser/devtools/performance/modules/logic/waterfall-utils.js @@ -7,27 +7,25 @@ * Utility functions for collapsing markers into a waterfall. */ -loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", - "devtools/performance/markers", true); +loader.lazyRequireGetter(this, "getBlueprintFor", + "devtools/performance/marker-utils", true); /** * Collapses markers into a tree-like structure. * @param object markerNode * @param array markersList - * @param ?object blueprint */ -function collapseMarkersIntoNode({ markerNode, markersList, blueprint }) { +function collapseMarkersIntoNode({ markerNode, markersList }) { let { getCurrentParentNode, collapseMarker, addParentNode, popParentNode } = createParentNodeFactory(markerNode); - blueprint = blueprint || TIMELINE_BLUEPRINT; for (let i = 0, len = markersList.length; i < len; i++) { let curr = markersList[i]; let parentNode = getCurrentParentNode(); - let def = blueprint[curr.name]; - let collapse = def.collapseFunc || (() => null); + let blueprint = getBlueprintFor(curr); + + let collapse = blueprint.collapseFunc || (() => null); let peek = distance => markersList[i + distance]; - let foundParent = false; let collapseInfo = collapse(parentNode, curr, peek); if (collapseInfo) { diff --git a/browser/devtools/performance/modules/markers.js b/browser/devtools/performance/modules/markers.js index 984eb4aa79bc..177a4d4fb6f0 100644 --- a/browser/devtools/performance/modules/markers.js +++ b/browser/devtools/performance/modules/markers.js @@ -55,6 +55,16 @@ const { Formatters, CollapseFunctions: collapse } = require("devtools/performanc * updated as well. */ const TIMELINE_BLUEPRINT = { + /* Default definition used for markers that occur but + * are not defined here. Should ultimately be defined, but this gives + * us room to work on the front end separately from the platform. */ + "UNKNOWN": { + group: 2, + colorName: "graphs-grey", + collapseFunc: collapse.child, + label: Formatters.UnknownLabel + }, + /* Group 0 - Reflow and Rendering pipeline */ "Styles": { group: 0, @@ -131,7 +141,7 @@ const TIMELINE_BLUEPRINT = { /* Group 2 - User Controlled */ "ConsoleTime": { group: 2, - colorName: "graphs-grey", + colorName: "graphs-blue", label: sublabelForProperty(L10N.getStr("timeline.label.consoleTime"), "causeName"), fields: [{ property: "causeName", diff --git a/browser/devtools/performance/modules/widgets/graphs.js b/browser/devtools/performance/modules/widgets/graphs.js index 468ab8dd7e49..bb5b50b126fe 100644 --- a/browser/devtools/performance/modules/widgets/graphs.js +++ b/browser/devtools/performance/modules/widgets/graphs.js @@ -135,8 +135,8 @@ MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, { } }); -function TimelineGraph(parent, blueprint) { - MarkersOverview.call(this, parent, blueprint); +function TimelineGraph(parent, filter) { + MarkersOverview.call(this, parent, filter); } TimelineGraph.prototype = Heritage.extend(MarkersOverview.prototype, { @@ -163,7 +163,6 @@ const GRAPH_DEFINITIONS = { timeline: { constructor: TimelineGraph, selector: "#markers-overview", - needsBlueprints: true, primaryLink: true } }; @@ -174,15 +173,15 @@ const GRAPH_DEFINITIONS = { * * @param {object} definition * @param {DOMElement} root - * @param {function} getBlueprint + * @param {function} getFilter * @param {function} getTheme */ -function GraphsController ({ definition, root, getBlueprint, getTheme }) { +function GraphsController ({ definition, root, getFilter, getTheme }) { this._graphs = {}; this._enabled = new Set(); this._definition = definition || GRAPH_DEFINITIONS; this._root = root; - this._getBlueprint = getBlueprint; + this._getFilter = getFilter; this._getTheme = getTheme; this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0]; this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument); @@ -369,8 +368,8 @@ GraphsController.prototype = { _construct: Task.async(function *(graphName) { let def = this._definition[graphName]; let el = this.$(def.selector); - let blueprint = def.needsBlueprints ? this._getBlueprint() : void 0; - let graph = this._graphs[graphName] = new def.constructor(el, blueprint); + let filter = this._getFilter(); + let graph = this._graphs[graphName] = new def.constructor(el, filter); graph.graphName = graphName; yield graph.ready(); diff --git a/browser/devtools/performance/modules/widgets/marker-details.js b/browser/devtools/performance/modules/widgets/marker-details.js index 5772ace46882..12780ea2a346 100644 --- a/browser/devtools/performance/modules/widgets/marker-details.js +++ b/browser/devtools/performance/modules/widgets/marker-details.js @@ -13,8 +13,6 @@ loader.lazyRequireGetter(this, "EventEmitter", "devtools/toolkit/event-emitter"); loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); -loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", - "devtools/performance/markers", true); loader.lazyRequireGetter(this, "MarkerUtils", "devtools/performance/marker-utils"); diff --git a/browser/devtools/performance/modules/widgets/marker-view.js b/browser/devtools/performance/modules/widgets/marker-view.js index 7a33eecafd69..4f8d1058ce52 100644 --- a/browser/devtools/performance/modules/widgets/marker-view.js +++ b/browser/devtools/performance/modules/widgets/marker-view.js @@ -11,11 +11,10 @@ const { Cc, Ci, Cu, Cr } = require("chrome"); const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm"); const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm"); -const { TIMELINE_BLUEPRINT: ORIGINAL_BP } = require("devtools/performance/markers"); - loader.lazyRequireGetter(this, "MarkerUtils", "devtools/performance/marker-utils"); + const HTML_NS = "http://www.w3.org/1999/xhtml"; const LEVEL_INDENT = 10; // px @@ -60,15 +59,14 @@ MarkerView.prototype = Heritage.extend(AbstractTreeItem.prototype, { }, /** - * Sets a list of names and colors used to paint markers. - * @see TIMELINE_BLUEPRINT in timeline/widgets/global.js - * @param object blueprint + * Sets a list of marker types to be filtered out of this view. + * @param Array filter */ - set blueprint(blueprint) { - this.root._blueprint = blueprint; + set filter(filter) { + this.root._filter = filter; }, - get blueprint() { - return this.root._blueprint; + get filter() { + return this.root._filter; }, /** @@ -139,7 +137,6 @@ MarkerView.prototype = Heritage.extend(AbstractTreeItem.prototype, { if (!submarkers || !submarkers.length) { return; } - let blueprint = this.root._blueprint; let startTime = this.root._interval.startTime; let endTime = this.root._interval.endTime; let newLevel = this.level + 1; @@ -147,17 +144,15 @@ MarkerView.prototype = Heritage.extend(AbstractTreeItem.prototype, { for (let i = 0, len = submarkers.length; i < len; i++) { let marker = submarkers[i]; - // If this marker isn't in the global timeline blueprint, don't display - // it, but dump a warning message to the console. - if (!(marker.name in blueprint)) { - if (!(marker.name in ORIGINAL_BP)) { - console.warn(`Marker not found in timeline blueprint: ${marker.name}.`); - } + // Skip filtered markers + if (!MarkerUtils.isMarkerValid(marker, this.filter)) { continue; } + if (!isMarkerInRange(marker, startTime|0, endTime|0)) { continue; } + children.push(new MarkerView({ owner: this, marker: marker, @@ -175,15 +170,12 @@ MarkerView.prototype = Heritage.extend(AbstractTreeItem.prototype, { */ _buildMarkerCells: function(doc, targetNode, arrowNode) { let marker = this.marker; - let style = this.root._blueprint[marker.name]; + let blueprint = MarkerUtils.getBlueprintFor(marker); let startTime = this.root._interval.startTime; let endTime = this.root._interval.endTime; - let sidebarCell = this._buildMarkerSidebar( - doc, style, marker); - - let timebarCell = this._buildMarkerTimebar( - doc, style, marker, startTime, endTime, arrowNode); + let sidebarCell = this._buildMarkerSidebar(doc, blueprint, marker); + let timebarCell = this._buildMarkerTimebar(doc, blueprint, marker, startTime, endTime, arrowNode); targetNode.appendChild(sidebarCell); targetNode.appendChild(timebarCell); diff --git a/browser/devtools/performance/modules/widgets/markers-overview.js b/browser/devtools/performance/modules/widgets/markers-overview.js index 3d75a5569640..53bfbf2ad1b2 100644 --- a/browser/devtools/performance/modules/widgets/markers-overview.js +++ b/browser/devtools/performance/modules/widgets/markers-overview.js @@ -21,6 +21,10 @@ loader.lazyRequireGetter(this, "L10N", "devtools/performance/global", true); loader.lazyRequireGetter(this, "TickUtils", "devtools/performance/waterfall-ticks", true); +loader.lazyRequireGetter(this, "MarkerUtils", + "devtools/performance/marker-utils"); +loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT", + "devtools/performance/markers", true); const OVERVIEW_HEADER_HEIGHT = 14; // px const OVERVIEW_ROW_HEIGHT = 11; // px @@ -28,14 +32,12 @@ const OVERVIEW_ROW_HEIGHT = 11; // px const OVERVIEW_SELECTION_LINE_COLOR = "#666"; const OVERVIEW_CLIPHEAD_LINE_COLOR = "#555"; -const FIND_OPTIMAL_TICK_INTERVAL_MAX_ITERS = 100; const OVERVIEW_HEADER_TICKS_MULTIPLE = 100; // ms const OVERVIEW_HEADER_TICKS_SPACING_MIN = 75; // px const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif"; const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px const OVERVIEW_HEADER_TEXT_PADDING_TOP = 1; // px -const OVERVIEW_MARKERS_COLOR_STOPS = [0, 0.1, 0.75, 1]; const OVERVIEW_MARKER_WIDTH_MIN = 4; // px const OVERVIEW_GROUP_VERTICAL_PADDING = 5; // px @@ -44,13 +46,13 @@ const OVERVIEW_GROUP_VERTICAL_PADDING = 5; // px * * @param nsIDOMNode parent * The parent node holding the overview. - * @param Object blueprint - * List of names and colors defining markers. + * @param Array filter + * List of names of marker types that should not be shown. */ -function MarkersOverview(parent, blueprint, ...args) { +function MarkersOverview(parent, filter=[], ...args) { AbstractCanvasGraph.apply(this, [parent, "markers-overview", ...args]); this.setTheme(); - this.setBlueprint(blueprint); + this.setFilter(filter); } MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, { @@ -64,21 +66,36 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, { * Compute the height of the overview. */ get fixedHeight() { - return this.headerHeight + this.rowHeight * (this._lastGroup + 1); + return this.headerHeight + this.rowHeight * this._numberOfGroups; }, /** - * List of names and colors used to paint this overview. - * @see TIMELINE_BLUEPRINT in timeline/widgets/global.js + * List of marker types that should not be shown in the graph. */ - setBlueprint: function(blueprint) { + setFilter: function (filter) { this._paintBatches = new Map(); - this._lastGroup = 0; + this._filter = filter; + this._groupMap = Object.create(null); - for (let type in blueprint) { - this._paintBatches.set(type, { style: blueprint[type], batch: [] }); - this._lastGroup = Math.max(this._lastGroup, blueprint[type].group || 0); + let observedGroups = new Set(); + + for (let type in TIMELINE_BLUEPRINT) { + if (filter.indexOf(type) !== -1) { + continue; + } + this._paintBatches.set(type, { definition: TIMELINE_BLUEPRINT[type], batch: [] }); + observedGroups.add(TIMELINE_BLUEPRINT[type].group); } + + // Take our set of observed groups and order them and map + // the group numbers to fill in the holes via `_groupMap`. + // This normalizes our rows by removing rows that aren't used + // if filters are enabled. + let actualPosition = 0; + for (let groupNumber of Array.from(observedGroups).sort()) { + this._groupMap[groupNumber] = actualPosition++; + } + this._numberOfGroups = Object.keys(this._groupMap).length; }, /** @@ -103,17 +120,19 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, { // Group markers into separate paint batches. This is necessary to // draw all markers sharing the same style at once. - for (let marker of markers) { - let markerType = this._paintBatches.get(marker.name); - if (markerType) { - markerType.batch.push(marker); + // Again skip over markers that we're filtering -- we don't want them + // to be labeled as "Unknown" + if (!MarkerUtils.isMarkerValid(marker, this._filter)) { + continue; } + + let markerType = this._paintBatches.get(marker.name) || this._paintBatches.get("UNKNOWN"); + markerType.batch.push(marker); } // Calculate each row's height, and the time-based scaling. - let totalGroups = this._lastGroup + 1; let groupHeight = this.rowHeight * this._pixelRatio; let groupPadding = this.groupPadding * this._pixelRatio; let headerHeight = this.headerHeight * this._pixelRatio; @@ -132,7 +151,7 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, { ctx.fillStyle = this.alternatingBackgroundColor; ctx.beginPath(); - for (let i = 0; i < totalGroups; i += 2) { + for (let i = 0; i < this._numberOfGroups; i += 2) { let top = headerHeight + i * groupHeight; ctx.rect(0, top, canvasWidth, groupHeight); } @@ -172,11 +191,12 @@ MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, { // Draw the timeline markers. - for (let [, { style, batch }] of this._paintBatches) { - let top = headerHeight + style.group * groupHeight + groupPadding / 2; + for (let [, { definition, batch }] of this._paintBatches) { + let group = this._groupMap[definition.group]; + let top = headerHeight + group * groupHeight + groupPadding / 2; let height = groupHeight - groupPadding; - let color = getColor(style.colorName, this.theme); + let color = getColor(definition.colorName, this.theme); ctx.fillStyle = color; ctx.beginPath(); diff --git a/browser/devtools/performance/performance-controller.js b/browser/devtools/performance/performance-controller.js index f43f9dfa78df..4fbc1e758007 100644 --- a/browser/devtools/performance/performance-controller.js +++ b/browser/devtools/performance/performance-controller.js @@ -398,16 +398,6 @@ let PerformanceController = { return null; }, - /** - * Gets the current timeline blueprint without the hidden markers. - * @return object - */ - getTimelineBlueprint: function() { - let blueprint = TIMELINE_BLUEPRINT; - let hiddenMarkers = this.getPref("hidden-markers"); - return RecordingUtils.getFilteredBlueprint({ blueprint, hiddenMarkers }); - }, - /** * Fired from RecordingsView, we listen on the PerformanceController so we can * set it here and re-emit on the controller, where all views can listen. diff --git a/browser/devtools/performance/test/browser.ini b/browser/devtools/performance/test/browser.ini index b296a57dd350..825586de938e 100644 --- a/browser/devtools/performance/test/browser.ini +++ b/browser/devtools/performance/test/browser.ini @@ -13,7 +13,6 @@ support-files = # that need to be moved over to performance tool [browser_aaa-run-first-leaktest.js] -[browser_marker-utils.js] [browser_markers-cycle-collection.js] [browser_markers-gc.js] [browser_markers-parse-html.js] @@ -137,7 +136,6 @@ support-files = [browser_profiler_tree-view-08.js] [browser_profiler_tree-view-09.js] [browser_profiler_tree-view-10.js] -[browser_timeline-blueprint.js] [browser_timeline-filters.js] [browser_timeline-waterfall-background.js] [browser_timeline-waterfall-generic.js] diff --git a/browser/devtools/performance/test/browser_marker-utils.js b/browser/devtools/performance/test/browser_marker-utils.js deleted file mode 100644 index 86f7e5d89c05..000000000000 --- a/browser/devtools/performance/test/browser_marker-utils.js +++ /dev/null @@ -1,70 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/** - * Tests the marker utils methods. - */ - -function* spawnTest() { - let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); - let Utils = devtools.require("devtools/performance/marker-utils"); - - Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false); - - is(Utils.getMarkerLabel({ name: "DOMEvent" }), "DOM Event", - "getMarkerLabel() returns a simple label"); - is(Utils.getMarkerLabel({ name: "Javascript", causeName: "setTimeout handler" }), "setTimeout", - "getMarkerLabel() returns a label defined via function"); - - ok(Utils.getMarkerFields({ name: "Paint" }).length === 0, - "getMarkerFields() returns an empty array when no fields defined"); - - let fields = Utils.getMarkerFields({ name: "ConsoleTime", causeName: "snowstorm" }); - is(fields[0].label, "Timer Name:", "getMarkerFields() returns an array with proper label"); - is(fields[0].value, "snowstorm", "getMarkerFields() returns an array with proper value"); - - fields = Utils.getMarkerFields({ name: "DOMEvent", type: "mouseclick" }); - is(fields.length, 1, "getMarkerFields() ignores fields that are not found on marker"); - is(fields[0].label, "Event Type:", "getMarkerFields() returns an array with proper label"); - is(fields[0].value, "mouseclick", "getMarkerFields() returns an array with proper value"); - - fields = Utils.getMarkerFields({ name: "DOMEvent", eventPhase: Ci.nsIDOMEvent.AT_TARGET, type: "mouseclick" }); - is(fields.length, 2, "getMarkerFields() returns multiple fields when using a fields function"); - is(fields[0].label, "Event Type:", "getMarkerFields() correctly returns fields via function (1)"); - is(fields[0].value, "mouseclick", "getMarkerFields() correctly returns fields via function (2)"); - is(fields[1].label, "Phase:", "getMarkerFields() correctly returns fields via function (3)"); - is(fields[1].value, "Target", "getMarkerFields() correctly returns fields via function (4)"); - - is(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)", - "Correctly obfuscates JS markers when platform data is off."); - Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true); - is(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "Some Platform Field", - "Correctly deobfuscates JS markers when platform data is on."); - - is(Utils.getMarkerClassName("Javascript"), "Function Call", - "getMarkerClassName() returns correct string when defined via function"); - is(Utils.getMarkerClassName("GarbageCollection"), "GC Event", - "getMarkerClassName() returns correct string when defined via function"); - is(Utils.getMarkerClassName("Reflow"), "Layout", - "getMarkerClassName() returns correct string when defined via string"); - - TIMELINE_BLUEPRINT["fakemarker"] = { group: 0 }; - try { - Utils.getMarkerClassName("fakemarker"); - ok(false, "getMarkerClassName() should throw when no label on blueprint."); - } catch (e) { - ok(true, "getMarkerClassName() should throw when no label on blueprint."); - } - - TIMELINE_BLUEPRINT["fakemarker"] = { group: 0, label: () => void 0}; - try { - Utils.getMarkerClassName("fakemarker"); - ok(false, "getMarkerClassName() should throw when label function returnd undefined."); - } catch (e) { - ok(true, "getMarkerClassName() should throw when label function returnd undefined."); - } - - delete TIMELINE_BLUEPRINT["fakemarker"]; - - finish(); -} diff --git a/browser/devtools/performance/test/browser_timeline-blueprint.js b/browser/devtools/performance/test/browser_timeline-blueprint.js deleted file mode 100644 index 5336e6bdd256..000000000000 --- a/browser/devtools/performance/test/browser_timeline-blueprint.js +++ /dev/null @@ -1,34 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -/** - * Tests if the timeline blueprint has a correct structure. - */ - -function* spawnTest() { - let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); - - ok(TIMELINE_BLUEPRINT, - "A timeline blueprint should be available."); - - ok(Object.keys(TIMELINE_BLUEPRINT).length, - "The timeline blueprint has at least one entry."); - - for (let [key, value] of Iterator(TIMELINE_BLUEPRINT)) { - if (key.startsWith("meta::")) { - ok(!("group" in value), - "No meta entry in the timeline blueprint can contain a `group` key."); - ok("colorName" in value, - "Each meta entry in the timeline blueprint contains a `colorName` key."); - ok("label" in value, - "Each meta entry in the timeline blueprint contains a `label` key."); - } else { - ok("group" in value, - "Each entry in the timeline blueprint contains a `group` key."); - ok("colorName" in value, - "Each entry in the timeline blueprint contains a `colorName` key."); - ok("label" in value, - "Each entry in the timeline blueprint contains a `label` key."); - } - } -} diff --git a/browser/devtools/performance/test/browser_timeline-filters.js b/browser/devtools/performance/test/browser_timeline-filters.js index 7f61a968a408..e09efb164d02 100644 --- a/browser/devtools/performance/test/browser_timeline-filters.js +++ b/browser/devtools/performance/test/browser_timeline-filters.js @@ -5,6 +5,8 @@ * Tests markers filtering mechanism. */ +const EPSILON = 0.00000001; + function* spawnTest() { let { panel } = yield initPerformance(SIMPLE_URL); let { $, $$, EVENTS, PerformanceController, OverviewView, WaterfallView } = panel.panelWin; @@ -24,20 +26,34 @@ function* spawnTest() { yield stopRecording(panel); + // Push some fake markers of a type we do not have a blueprint for + let markers = PerformanceController.getCurrentRecording().getMarkers(); + let endTime = markers[markers.length - 1].end; + markers.push({ name: "CustomMarker", start: endTime + EPSILON, end: endTime + (EPSILON * 2) }); + markers.push({ name: "CustomMarker", start: endTime + (EPSILON * 3), end: endTime + (EPSILON * 4) }); + + // Invalidate marker cache + WaterfallView._cache.delete(markers); + // Select everything - OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE }) + let waterfallRendered = WaterfallView.once(EVENTS.WATERFALL_RENDERED); + OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE }); $("#filter-button").click(); let menuItem1 = $("menuitem[marker-type=Styles]"); let menuItem2 = $("menuitem[marker-type=Reflow]"); let menuItem3 = $("menuitem[marker-type=Paint]"); + let menuItem4 = $("menuitem[marker-type=UNKNOWN]"); let overview = OverviewView.graphs.get("timeline"); let originalHeight = overview.fixedHeight; + yield waterfallRendered; + ok($(".waterfall-marker-bar[type=Styles]"), "Found at least one 'Styles' marker (1)"); ok($(".waterfall-marker-bar[type=Reflow]"), "Found at least one 'Reflow' marker (1)"); ok($(".waterfall-marker-bar[type=Paint]"), "Found at least one 'Paint' marker (1)"); + ok($(".waterfall-marker-bar[type=CustomMarker]"), "Found at least one 'Unknown' marker (1)"); let heightBefore = overview.fixedHeight; EventUtils.synthesizeMouseAtCenter(menuItem1, {type: "mouseup"}, panel.panelWin); @@ -47,6 +63,7 @@ function* spawnTest() { ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (2)"); ok($(".waterfall-marker-bar[type=Reflow]"), "Found at least one 'Reflow' marker (2)"); ok($(".waterfall-marker-bar[type=Paint]"), "Found at least one 'Paint' marker (2)"); + ok($(".waterfall-marker-bar[type=CustomMarker]"), "Found at least one 'Unknown' marker (2)"); heightBefore = overview.fixedHeight; EventUtils.synthesizeMouseAtCenter(menuItem2, {type: "mouseup"}, panel.panelWin); @@ -56,6 +73,7 @@ function* spawnTest() { ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (3)"); ok(!$(".waterfall-marker-bar[type=Reflow]"), "No 'Reflow' marker (3)"); ok($(".waterfall-marker-bar[type=Paint]"), "Found at least one 'Paint' marker (3)"); + ok($(".waterfall-marker-bar[type=CustomMarker]"), "Found at least one 'Unknown' marker (3)"); heightBefore = overview.fixedHeight; EventUtils.synthesizeMouseAtCenter(menuItem3, {type: "mouseup"}, panel.panelWin); @@ -65,15 +83,25 @@ function* spawnTest() { ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (4)"); ok(!$(".waterfall-marker-bar[type=Reflow]"), "No 'Reflow' marker (4)"); ok(!$(".waterfall-marker-bar[type=Paint]"), "No 'Paint' marker (4)"); + ok($(".waterfall-marker-bar[type=CustomMarker]"), "Found at least one 'Unknown' marker (4)"); + + EventUtils.synthesizeMouseAtCenter(menuItem4, {type: "mouseup"}, panel.panelWin); + yield waitForOverviewAndCommand(overview, menuItem4); + + ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (5)"); + ok(!$(".waterfall-marker-bar[type=Reflow]"), "No 'Reflow' marker (5)"); + ok(!$(".waterfall-marker-bar[type=Paint]"), "No 'Paint' marker (5)"); + ok(!$(".waterfall-marker-bar[type=CustomMarker]"), "No 'Unknown' marker (5)"); for (let item of [menuItem1, menuItem2, menuItem3]) { EventUtils.synthesizeMouseAtCenter(item, {type: "mouseup"}, panel.panelWin); yield waitForOverviewAndCommand(overview, item); } - ok($(".waterfall-marker-bar[type=Styles]"), "Found at least one 'Styles' marker (5)"); - ok($(".waterfall-marker-bar[type=Reflow]"), "Found at least one 'Reflow' marker (5)"); - ok($(".waterfall-marker-bar[type=Paint]"), "Found at least one 'Paint' marker (5)"); + ok($(".waterfall-marker-bar[type=Styles]"), "Found at least one 'Styles' marker (6)"); + ok($(".waterfall-marker-bar[type=Reflow]"), "Found at least one 'Reflow' marker (6)"); + ok($(".waterfall-marker-bar[type=Paint]"), "Found at least one 'Paint' marker (6)"); + ok(!$(".waterfall-marker-bar[type=CustomMarker]"), "No 'Unknown' marker (6)"); is(overview.fixedHeight, originalHeight, "Overview restored"); diff --git a/browser/devtools/performance/test/head.js b/browser/devtools/performance/test/head.js index 9e39e58683e8..7f2eb057304c 100644 --- a/browser/devtools/performance/test/head.js +++ b/browser/devtools/performance/test/head.js @@ -61,6 +61,7 @@ let DEFAULT_PREFS = [ "devtools.performance.profiler.buffer-size", "devtools.performance.profiler.sample-frequency-khz", "devtools.performance.ui.experimental", + "devtools.performance.timeline.hidden-markers", ].reduce((prefs, pref) => { prefs[pref] = Preferences.get(pref); return prefs; diff --git a/browser/devtools/performance/test/unit/head.js b/browser/devtools/performance/test/unit/head.js index de1ac9b2daad..14d39eef80cd 100644 --- a/browser/devtools/performance/test/unit/head.js +++ b/browser/devtools/performance/test/unit/head.js @@ -5,8 +5,11 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); +let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); const RecordingUtils = devtools.require("devtools/performance/recording-utils"); +const PLATFORM_DATA_PREF = "devtools.performance.ui.show-platform-data"; + /** * Get a path in a FrameNode call tree. */ diff --git a/browser/devtools/performance/test/unit/test_marker-blueprint.js b/browser/devtools/performance/test/unit/test_marker-blueprint.js new file mode 100644 index 000000000000..bd93e3036de2 --- /dev/null +++ b/browser/devtools/performance/test/unit/test_marker-blueprint.js @@ -0,0 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the timeline blueprint has a correct structure. + */ + +function run_test() { + run_next_test(); +} + +add_task(function () { + let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); + + ok(TIMELINE_BLUEPRINT, + "A timeline blueprint should be available."); + + ok(Object.keys(TIMELINE_BLUEPRINT).length, + "The timeline blueprint has at least one entry."); + + for (let [key, value] of Iterator(TIMELINE_BLUEPRINT)) { + ok("group" in value, + "Each entry in the timeline blueprint contains a `group` key."); + ok("colorName" in value, + "Each entry in the timeline blueprint contains a `colorName` key."); + ok("label" in value, + "Each entry in the timeline blueprint contains a `label` key."); + } +}); diff --git a/browser/devtools/performance/test/unit/test_marker-utils.js b/browser/devtools/performance/test/unit/test_marker-utils.js new file mode 100644 index 000000000000..f9e3d1efbc33 --- /dev/null +++ b/browser/devtools/performance/test/unit/test_marker-utils.js @@ -0,0 +1,88 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the marker utils methods. + */ + +function run_test() { + run_next_test(); +} + +add_task(function () { + let { TIMELINE_BLUEPRINT } = devtools.require("devtools/performance/markers"); + let Utils = devtools.require("devtools/performance/marker-utils"); + + Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false); + + equal(Utils.getMarkerLabel({ name: "DOMEvent" }), "DOM Event", + "getMarkerLabel() returns a simple label"); + equal(Utils.getMarkerLabel({ name: "Javascript", causeName: "setTimeout handler" }), "setTimeout", + "getMarkerLabel() returns a label defined via function"); + + ok(Utils.getMarkerFields({ name: "Paint" }).length === 0, + "getMarkerFields() returns an empty array when no fields defined"); + + let fields = Utils.getMarkerFields({ name: "ConsoleTime", causeName: "snowstorm" }); + equal(fields[0].label, "Timer Name:", "getMarkerFields() returns an array with proper label"); + equal(fields[0].value, "snowstorm", "getMarkerFields() returns an array with proper value"); + + fields = Utils.getMarkerFields({ name: "DOMEvent", type: "mouseclick" }); + equal(fields.length, 1, "getMarkerFields() ignores fields that are not found on marker"); + equal(fields[0].label, "Event Type:", "getMarkerFields() returns an array with proper label"); + equal(fields[0].value, "mouseclick", "getMarkerFields() returns an array with proper value"); + + fields = Utils.getMarkerFields({ name: "DOMEvent", eventPhase: Ci.nsIDOMEvent.AT_TARGET, type: "mouseclick" }); + equal(fields.length, 2, "getMarkerFields() returns multiple fields when using a fields function"); + equal(fields[0].label, "Event Type:", "getMarkerFields() correctly returns fields via function (1)"); + equal(fields[0].value, "mouseclick", "getMarkerFields() correctly returns fields via function (2)"); + equal(fields[1].label, "Phase:", "getMarkerFields() correctly returns fields via function (3)"); + equal(fields[1].value, "Target", "getMarkerFields() correctly returns fields via function (4)"); + + equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)", + "Correctly obfuscates JS markers when platform data is off."); + Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true); + equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "Some Platform Field", + "Correctly deobfuscates JS markers when platform data is on."); + + equal(Utils.getMarkerClassName("Javascript"), "Function Call", + "getMarkerClassName() returns correct string when defined via function"); + equal(Utils.getMarkerClassName("GarbageCollection"), "GC Event", + "getMarkerClassName() returns correct string when defined via function"); + equal(Utils.getMarkerClassName("Reflow"), "Layout", + "getMarkerClassName() returns correct string when defined via string"); + + TIMELINE_BLUEPRINT["fakemarker"] = { group: 0 }; + try { + Utils.getMarkerClassName("fakemarker"); + ok(false, "getMarkerClassName() should throw when no label on blueprint."); + } catch (e) { + ok(true, "getMarkerClassName() should throw when no label on blueprint."); + } + + TIMELINE_BLUEPRINT["fakemarker"] = { group: 0, label: () => void 0}; + try { + Utils.getMarkerClassName("fakemarker"); + ok(false, "getMarkerClassName() should throw when label function returnd undefined."); + } catch (e) { + ok(true, "getMarkerClassName() should throw when label function returnd undefined."); + } + + delete TIMELINE_BLUEPRINT["fakemarker"]; + + let customBlueprint = { + UNKNOWN: { + group: 1, + label: "MyDefault" + }, + custom: { + group: 0, + label: "MyCustom" + } + }; + + equal(Utils.getBlueprintFor({ name: "Reflow" }).label, "Layout", + "Utils.getBlueprintFor() should return marker def for passed in marker."); + equal(Utils.getBlueprintFor({ name: "Not sure!" }).label(), "Unknown", + "Utils.getBlueprintFor() should return a default marker def if the marker is undefined."); +}); diff --git a/browser/devtools/performance/test/unit/xpcshell.ini b/browser/devtools/performance/test/unit/xpcshell.ini index b84740d0f699..acefb997ea97 100644 --- a/browser/devtools/performance/test/unit/xpcshell.ini +++ b/browser/devtools/performance/test/unit/xpcshell.ini @@ -5,9 +5,11 @@ tail = firefox-appdir = browser skip-if = toolkit == 'android' || toolkit == 'gonk' -[test_profiler-categories.js] [test_frame-utils-01.js] [test_frame-utils-02.js] +[test_marker-blueprint.js] +[test_marker-utils.js] +[test_profiler-categories.js] [test_tree-model-01.js] [test_tree-model-02.js] [test_tree-model-03.js] diff --git a/browser/devtools/performance/views/details-waterfall.js b/browser/devtools/performance/views/details-waterfall.js index eb26fc4affca..0896b7b111d4 100644 --- a/browser/devtools/performance/views/details-waterfall.js +++ b/browser/devtools/performance/views/details-waterfall.js @@ -32,6 +32,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, { this._onMarkerSelected = this._onMarkerSelected.bind(this); this._onResize = this._onResize.bind(this); this._onViewSource = this._onViewSource.bind(this); + this._hiddenMarkers = PerformanceController.getPref("hidden-markers"); this.headerContainer = $("#waterfall-header"); this.breakdownContainer = $("#waterfall-breakdown"); @@ -111,8 +112,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, { * Called whenever an observed pref is changed. */ _onObservedPrefChange: function(_, prefName) { - let blueprint = PerformanceController.getTimelineBlueprint(); - this._markersRoot.blueprint = blueprint; + this._hiddenMarkers = PerformanceController.getPref("hidden-markers"); }, /** @@ -136,7 +136,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, { WaterfallUtils.collapseMarkersIntoNode({ markerNode: rootMarkerNode, - markersList: markers + markersList: markers, }); this._cache.set(markers, rootMarkerNode); @@ -160,8 +160,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, { this._markersRoot = root; this._waterfallHeader = header; - let blueprint = PerformanceController.getTimelineBlueprint(); - root.blueprint = blueprint; + root.filter = this._hiddenMarkers; root.interval = interval; root.on("selected", this._onMarkerSelected); root.on("unselected", this._onMarkerSelected); diff --git a/browser/devtools/performance/views/overview.js b/browser/devtools/performance/views/overview.js index 1f05eaf9b487..5f017f7e008c 100644 --- a/browser/devtools/performance/views/overview.js +++ b/browser/devtools/performance/views/overview.js @@ -42,7 +42,7 @@ let OverviewView = { initialize: function () { this.graphs = new GraphsController({ root: $("#overview-pane"), - getBlueprint: () => PerformanceController.getTimelineBlueprint(), + getFilter: () => PerformanceController.getPref("hidden-markers"), getTheme: () => PerformanceController.getTheme(), }); @@ -331,8 +331,8 @@ let OverviewView = { case "hidden-markers": { let graph; if (graph = yield this.graphs.isAvailable("timeline")) { - let blueprint = PerformanceController.getTimelineBlueprint(); - graph.setBlueprint(blueprint); + let filter = PerformanceController.getPref("hidden-markers"); + graph.setFilter(filter); graph.refresh({ force: true }); } break; diff --git a/browser/locales/en-US/chrome/browser/devtools/timeline.properties b/browser/locales/en-US/chrome/browser/devtools/timeline.properties index 77b745b39c15..774be3add109 100644 --- a/browser/locales/en-US/chrome/browser/devtools/timeline.properties +++ b/browser/locales/en-US/chrome/browser/devtools/timeline.properties @@ -46,6 +46,7 @@ timeline.label.domevent=DOM Event timeline.label.consoleTime=Console timeline.label.garbageCollection=GC Event timeline.label.timestamp=Timestamp +timeline.label.unknown=Unknown # LOCALIZATION NOTE (graphs.memory): # This string is displayed in the memory graph of the Performance tool,