monaca-lib
Version:
Monaca cloud API bindings for JavaScript
1,351 lines (1,236 loc) • 43.2 kB
JavaScript
/*
* Copyright (C) 2014 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @implements {WebInspector.FlameChartDataProvider}
* @param {!WebInspector.TimelineModel} model
*/
WebInspector.TimelineFlameChartDataProviderBase = function(model)
{
WebInspector.FlameChartDataProvider.call(this);
this.reset();
this._model = model;
/** @type {?WebInspector.FlameChart.TimelineData} */
this._timelineData;
this._font = "11px " + WebInspector.fontFamily();
this._filters = [];
this.addFilter(WebInspector.TimelineUIUtils.hiddenEventsFilter());
this.addFilter(new WebInspector.ExclusiveTraceEventNameFilter([WebInspector.TimelineModel.RecordType.Program]));
this._jsFramesColorGenerator = new WebInspector.FlameChart.ColorGenerator(
{ min: 180, max: 310, count: 7 },
{ min: 50, max: 80, count: 5 },
85);
}
WebInspector.TimelineFlameChartDataProviderBase.prototype = {
/**
* @override
* @return {number}
*/
barHeight: function()
{
return 17;
},
/**
* @override
* @return {number}
*/
textBaseline: function()
{
return 5;
},
/**
* @override
* @return {number}
*/
textPadding: function()
{
return 4;
},
/**
* @override
* @param {number} entryIndex
* @return {string}
*/
entryFont: function(entryIndex)
{
return this._font;
},
/**
* @override
* @param {number} entryIndex
* @return {?string}
*/
entryTitle: function(entryIndex)
{
return null;
},
reset: function()
{
this._timelineData = null;
},
/**
* @param {!WebInspector.TraceEventFilter} filter
*/
addFilter: function(filter)
{
this._filters.push(filter);
},
/**
* @override
* @return {number}
*/
minimumBoundary: function()
{
return this._minimumBoundary;
},
/**
* @override
* @return {number}
*/
totalTime: function()
{
return this._timeSpan;
},
/**
* @override
* @return {number}
*/
maxStackDepth: function()
{
return this._currentLevel;
},
/**
* @override
* @param {number} entryIndex
* @return {?Array.<!{title: string, text: string}>}
*/
prepareHighlightedEntryInfo: function(entryIndex)
{
return null;
},
/**
* @override
* @param {number} entryIndex
* @return {boolean}
*/
canJumpToEntry: function(entryIndex)
{
return false;
},
/**
* @override
* @param {number} entryIndex
* @return {string}
*/
entryColor: function(entryIndex)
{
return "red";
},
/**
* @override
* @param {number} index
* @return {boolean}
*/
forceDecoration: function(index)
{
return false;
},
/**
* @override
* @param {number} entryIndex
* @param {!CanvasRenderingContext2D} context
* @param {?string} text
* @param {number} barX
* @param {number} barY
* @param {number} barWidth
* @param {number} barHeight
* @return {boolean}
*/
decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight)
{
return false;
},
/**
* @override
* @param {number} startTime
* @param {number} endTime
* @return {?Array.<number>}
*/
dividerOffsets: function(startTime, endTime)
{
return null;
},
/**
* @override
* @return {number}
*/
paddingLeft: function()
{
return 0;
},
/**
* @override
* @param {number} entryIndex
* @return {string}
*/
textColor: function(entryIndex)
{
return "#333";
},
/**
* @override
* @param {number} entryIndex
* @return {?{startTime: number, endTime: number}}
*/
highlightTimeRange: function(entryIndex)
{
var startTime = this._timelineData.entryStartTimes[entryIndex];
return {
startTime: startTime,
endTime: startTime + this._timelineData.entryTotalTimes[entryIndex]
};
},
/**
* @param {number} entryIndex
* @return {?WebInspector.TimelineSelection}
*/
createSelection: function(entryIndex)
{
return null;
},
/**
* @override
* @return {!WebInspector.FlameChart.TimelineData}
*/
timelineData: function()
{
throw new Error("Not implemented");
},
/**
* @param {!WebInspector.TracingModel.Event} event
* @return {boolean}
*/
_isVisible: function(event)
{
return this._filters.every(function (filter) { return filter.accept(event); });
}
}
/**
* @constructor
* @extends {WebInspector.TimelineFlameChartDataProviderBase}
* @param {!WebInspector.TimelineModel} model
* @param {?WebInspector.TimelineFrameModelBase} frameModel
*/
WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
{
WebInspector.TimelineFlameChartDataProviderBase.call(this, model);
this._frameModel = frameModel;
this._consoleColorGenerator = new WebInspector.FlameChart.ColorGenerator(
{ min: 30, max: 55, count: 5 },
{ min: 70, max: 100, count: 6 },
50, 0.7);
}
WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.001;
WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs = 1.1;
WebInspector.TimelineFlameChartDataProvider.prototype = {
/**
* @override
* @param {number} entryIndex
* @return {?string}
*/
entryTitle: function(entryIndex)
{
var event = this._entryEvents[entryIndex];
if (event) {
if (event.phase === WebInspector.TracingModel.Phase.AsyncStepInto || event.phase === WebInspector.TracingModel.Phase.AsyncStepPast)
return event.name + ":" + event.args["step"];
var name = WebInspector.TimelineUIUtils.eventStyle(event).title;
// TODO(yurys): support event dividers
var detailsText = WebInspector.TimelineUIUtils.buildDetailsTextForTraceEvent(event, this._model.target());
if (event.name === WebInspector.TimelineModel.RecordType.JSFrame && detailsText)
return detailsText;
return detailsText ? WebInspector.UIString("%s (%s)", name, detailsText) : name;
}
var title = this._entryIndexToTitle[entryIndex];
if (!title) {
title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
console.error(title);
}
return title;
},
/**
* @override
*/
reset: function()
{
WebInspector.TimelineFlameChartDataProviderBase.prototype.reset.call(this);
/** @type {!Array.<!WebInspector.TracingModel.Event>} */
this._entryEvents = [];
this._entryIndexToTitle = {};
/** @type {!Array.<!WebInspector.TimelineFlameChartMarker>} */
this._markers = [];
this._entryIndexToFrame = {};
this._asyncColorByCategory = {};
},
/**
* @override
* @return {!WebInspector.FlameChart.TimelineData}
*/
timelineData: function()
{
if (this._timelineData)
return this._timelineData;
this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []);
this._flowEventIndexById = {};
this._minimumBoundary = this._model.minimumRecordTime();
this._timeSpan = this._model.isEmpty() ? 1000 : this._model.maximumRecordTime() - this._minimumBoundary;
this._currentLevel = 0;
this._appendFrameBars(this._frameModel.frames());
this._appendThreadTimelineData(WebInspector.UIString("Main Thread"), this._model.mainThreadEvents(), this._model.mainThreadAsyncEvents());
if (Runtime.experiments.isEnabled("gpuTimeline"))
this._appendGPUEvents();
var threads = this._model.virtualThreads();
for (var i = 0; i < threads.length; i++)
this._appendThreadTimelineData(threads[i].name, threads[i].events, threads[i].asyncEvents);
/**
* @param {!WebInspector.TimelineFlameChartMarker} a
* @param {!WebInspector.TimelineFlameChartMarker} b
*/
function compareStartTime(a, b)
{
return a.startTime() - b.startTime();
}
this._markers.sort(compareStartTime);
this._timelineData.markers = this._markers;
this._flowEventIndexById = {};
return this._timelineData;
},
/**
* @param {string} threadTitle
* @param {!Array.<!WebInspector.TracingModel.Event>} syncEvents
* @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} asyncEvents
*/
_appendThreadTimelineData: function(threadTitle, syncEvents, asyncEvents)
{
var levelCount = this._appendAsyncEvents(threadTitle, asyncEvents);
levelCount += this._appendSyncEvents(levelCount ? null : threadTitle, syncEvents);
if (levelCount)
++this._currentLevel;
},
/**
* @param {?string} headerName
* @param {!Array.<!WebInspector.TracingModel.Event>} events
* @return {boolean}
*/
_appendSyncEvents: function(headerName, events)
{
var openEvents = [];
var headerAppended = false;
var flowEventsEnabled = Runtime.experiments.isEnabled("timelineFlowEvents");
function isFlowEvent(event)
{
return e.phase === WebInspector.TracingModel.Phase.FlowBegin ||
e.phase === WebInspector.TracingModel.Phase.FlowStep ||
e.phase === WebInspector.TracingModel.Phase.FlowEnd;
}
var maxStackDepth = 0;
for (var i = 0; i < events.length; ++i) {
var e = events[i];
if (WebInspector.TimelineUIUtils.isMarkerEvent(e))
this._markers.push(new WebInspector.TimelineFlameChartMarker(e.startTime, e.startTime - this._model.minimumRecordTime(), WebInspector.TimelineUIUtils.markerStyleForEvent(e)));
if (!isFlowEvent(e)) {
if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant)
continue;
if (WebInspector.TracingModel.isAsyncPhase(e.phase))
continue;
if (!this._isVisible(e))
continue;
}
while (openEvents.length && openEvents.peekLast().endTime <= e.startTime)
openEvents.pop();
if (!headerAppended && headerName) {
this._appendHeaderRecord(headerName, this._currentLevel++);
headerAppended = true;
}
var level = this._currentLevel + openEvents.length;
this._appendEvent(e, level);
if (flowEventsEnabled)
this._appendFlowEvent(e, level);
maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1);
if (e.endTime)
openEvents.push(e);
}
this._currentLevel += maxStackDepth;
return !!maxStackDepth;
},
/**
* @param {string} header
* @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} eventSteps
*/
_appendAsyncEvents: function(header, eventSteps)
{
var lastUsedTimeByLevel = [];
var headerAppended = false;
for (var i = 0; i < eventSteps.length; ++i) {
var e = eventSteps[i][0];
if (!this._isVisible(e))
continue;
if (!headerAppended && header) {
this._appendHeaderRecord(header, this._currentLevel++);
headerAppended = true;
}
var level;
for (level = 0; level < lastUsedTimeByLevel.length && lastUsedTimeByLevel[level] > e.startTime; ++level) {}
if (WebInspector.TracingModel.isNestableAsyncPhase(e.phase))
this._appendEvent(e, this._currentLevel + level);
else
this._appendAsyncEventSteps(eventSteps[i], this._currentLevel + level);
var lastStep = eventSteps[i].peekLast();
if (e.phase === WebInspector.TracingModel.Phase.AsyncBegin || e.phase === WebInspector.TracingModel.Phase.NestableAsyncInstant)
lastUsedTimeByLevel[level] = lastStep.startTime;
else if (e.phase === WebInspector.TracingModel.Phase.NestableAsyncBegin && e.endTime)
lastUsedTimeByLevel[level] = e.endTime;
else
lastUsedTimeByLevel[level] = Infinity;
}
this._currentLevel += lastUsedTimeByLevel.length;
return lastUsedTimeByLevel.length;
},
_appendGPUEvents: function()
{
function recordToEvent(record)
{
return record.traceEvent();
}
if (this._appendSyncEvents(WebInspector.UIString("GPU"), this._model.gpuTasks().map(recordToEvent)))
++this._currentLevel;
},
/**
* @param {!Array.<!WebInspector.TimelineFrame>} frames
*/
_appendFrameBars: function(frames)
{
var style = WebInspector.TimelineUIUtils.markerStyleForFrame();
this._frameBarsLevel = this._currentLevel++;
for (var i = 0; i < frames.length; ++i) {
this._markers.push(new WebInspector.TimelineFlameChartMarker(frames[i].startTime, frames[i].startTime - this._model.minimumRecordTime(), style));
this._appendFrame(frames[i]);
}
},
/**
* @override
* @param {number} entryIndex
* @return {string}
*/
entryColor: function(entryIndex)
{
var event = this._entryEvents[entryIndex];
if (!event)
return this._entryIndexToFrame[entryIndex] ? "white" : "#aaa";
if (event.name === WebInspector.TimelineModel.RecordType.JSFrame) {
var colorId = event.args["data"]["url"];
return this._jsFramesColorGenerator.colorForID(colorId);
}
var category = WebInspector.TimelineUIUtils.eventStyle(event).category;
if (WebInspector.TracingModel.isAsyncPhase(event.phase)) {
if (event.category === WebInspector.TracingModel.ConsoleEventCategory)
return this._consoleColorGenerator.colorForID(event.name);
var color = this._asyncColorByCategory[category.name];
if (color)
return color;
var parsedColor = WebInspector.Color.parse(category.fillColorStop1);
color = parsedColor.setAlpha(0.7).asString(WebInspector.Color.Format.RGBA) || "";
this._asyncColorByCategory[category.name] = color;
return color;
}
return category.fillColorStop1;
},
/**
* @override
* @param {number} entryIndex
* @param {!CanvasRenderingContext2D} context
* @param {?string} text
* @param {number} barX
* @param {number} barY
* @param {number} barWidth
* @param {number} barHeight
* @return {boolean}
*/
decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight)
{
var frame = this._entryIndexToFrame[entryIndex];
if (frame) {
var /** @const */ vPadding = 1;
var /** @const */ hPadding = 2;
barX += hPadding;
barWidth -= 2 * hPadding;
barY += vPadding;
barHeight -= 2 * vPadding + 1;
context.fillStyle = "#ccc";
context.fillRect(barX, barY, barWidth, barHeight);
// Draw frame perforation.
context.fillStyle = "white";
for (var i = 1; i < barWidth; i += 4) {
context.fillRect(barX + i, barY + 1, 2, 2);
context.fillRect(barX + i, barY + barHeight - 3, 2, 2);
}
var frameDurationText = Number.preciseMillisToString(frame.duration, 1);
var textWidth = context.measureText(frameDurationText).width;
if (barWidth > textWidth) {
context.fillStyle = this.textColor(entryIndex);
context.fillText(frameDurationText, barX + ((barWidth - textWidth) >> 1), barY + barHeight - 3);
}
return true;
}
if (barWidth < 5)
return false;
if (text) {
context.save();
context.fillStyle = this.textColor(entryIndex);
context.font = this._font;
context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
context.restore();
}
var event = this._entryEvents[entryIndex];
if (event && event.warning) {
context.save();
context.rect(barX, barY, barWidth, this.barHeight());
context.clip();
context.beginPath();
var /** @const */ triangleSize = 10;
context.fillStyle = "red";
context.moveTo(barX + barWidth - triangleSize, barY + 1);
context.lineTo(barX + barWidth - 1, barY + 1);
context.lineTo(barX + barWidth - 1, barY + triangleSize);
context.fill();
context.restore();
}
return true;
},
/**
* @override
* @param {number} entryIndex
* @return {boolean}
*/
forceDecoration: function(entryIndex)
{
var event = this._entryEvents[entryIndex];
if (!event)
return !!this._entryIndexToFrame[entryIndex];
return !!event.warning;
},
/**
* @param {string} title
* @param {number} level
*/
_appendHeaderRecord: function(title, level)
{
var index = this._entryEvents.length;
this._entryIndexToTitle[index] = title;
this._entryEvents.push(null);
this._timelineData.entryLevels[index] = level;
this._timelineData.entryTotalTimes[index] = this._timeSpan;
this._timelineData.entryStartTimes[index] = this._minimumBoundary;
},
/**
* @param {!WebInspector.TracingModel.Event} event
* @param {number} level
*/
_appendEvent: function(event, level)
{
var index = this._entryEvents.length;
this._entryEvents.push(event);
this._timelineData.entryLevels[index] = level;
this._timelineData.entryTotalTimes[index] = event.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
this._timelineData.entryStartTimes[index] = event.startTime;
},
/**
* @param {!WebInspector.TracingModel.Event} event
* @param {number} level
*/
_appendFlowEvent: function(event, level)
{
var timelineData = this._timelineData;
/**
* @param {!WebInspector.TracingModel.Event} event
* @return {number}
*/
function pushStartFlow(event)
{
var flowIndex = timelineData.flowStartTimes.length;
timelineData.flowStartTimes.push(event.startTime);
timelineData.flowStartLevels.push(level);
return flowIndex;
}
/**
* @param {!WebInspector.TracingModel.Event} event
* @param {number} flowIndex
*/
function pushEndFlow(event, flowIndex)
{
timelineData.flowEndTimes[flowIndex] = event.startTime;
timelineData.flowEndLevels[flowIndex] = level;
}
switch(event.phase) {
case WebInspector.TracingModel.Phase.FlowBegin:
this._flowEventIndexById[event.id] = pushStartFlow(event);
break;
case WebInspector.TracingModel.Phase.FlowStep:
pushEndFlow(event, this._flowEventIndexById[event.id]);
this._flowEventIndexById[event.id] = pushStartFlow(event);
break;
case WebInspector.TracingModel.Phase.FlowEnd:
pushEndFlow(event, this._flowEventIndexById[event.id]);
delete this._flowEventIndexById[event.id];
break;
}
},
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} steps
* @param {number} level
*/
_appendAsyncEventSteps: function(steps, level)
{
// If we have past steps, put the end event for each range rather than start one.
var eventOffset = steps[1].phase === WebInspector.TracingModel.Phase.AsyncStepPast ? 1 : 0;
for (var i = 0; i < steps.length - 1; ++i) {
var index = this._entryEvents.length;
this._entryEvents.push(steps[i + eventOffset]);
var startTime = steps[i].startTime;
this._timelineData.entryLevels[index] = level;
this._timelineData.entryTotalTimes[index] = steps[i + 1].startTime - startTime;
this._timelineData.entryStartTimes[index] = startTime;
}
},
/**
* @param {!WebInspector.TimelineFrame} frame
*/
_appendFrame: function(frame)
{
var index = this._entryEvents.length;
this._entryEvents.push(null);
this._entryIndexToFrame[index] = frame;
this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true);
this._timelineData.entryLevels[index] = this._frameBarsLevel;
this._timelineData.entryTotalTimes[index] = frame.duration;
this._timelineData.entryStartTimes[index] = frame.startTime;
},
/**
* @override
* @param {number} entryIndex
* @return {?WebInspector.TimelineSelection}
*/
createSelection: function(entryIndex)
{
var event = this._entryEvents[entryIndex];
if (event) {
this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
return this._lastSelection.timelineSelection;
}
var frame = this._entryIndexToFrame[entryIndex];
if (frame) {
this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), entryIndex);
return this._lastSelection.timelineSelection;
}
return null;
},
/**
* @param {?WebInspector.TimelineSelection} selection
* @return {number}
*/
entryIndexForSelection: function(selection)
{
if (!selection)
return -1;
if (this._lastSelection && this._lastSelection.timelineSelection.object() === selection.object())
return this._lastSelection.entryIndex;
switch (selection.type()) {
case WebInspector.TimelineSelection.Type.TraceEvent:
var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
var entryEvents = this._entryEvents;
for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
if (entryEvents[entryIndex] === event) {
this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
return entryIndex;
}
}
break;
case WebInspector.TimelineSelection.Type.Frame:
var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object());
for (var frameIndex in this._entryIndexToFrame) {
if (this._entryIndexToFrame[frameIndex] === frame) {
this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), Number(frameIndex));
return Number(frameIndex);
}
}
break;
}
return -1;
},
__proto__: WebInspector.TimelineFlameChartDataProviderBase.prototype
}
/**
* @constructor
* @extends {WebInspector.TimelineFlameChartDataProviderBase}
* @param {!WebInspector.TimelineModel} model
*/
WebInspector.TimelineFlameChartBottomUpDataProvider = function(model)
{
WebInspector.TimelineFlameChartDataProviderBase.call(this, model);
}
/**
* @constructor
*/
WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode = function()
{
/** @type {number} */
this.totalTime;
/** @type {number} */
this.selfTime;
/** @type {string} */
this.name;
/** @type {string} */
this.color;
/** @type {string} */
this.id;
/** @type {!WebInspector.TracingModel.Event} */
this.event;
/** @type {?Object.<string,!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode>} */
this.children;
/** @type {?WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} */
this.parent;
}
WebInspector.TimelineFlameChartBottomUpDataProvider.prototype = {
/**
* @override
* @return {!WebInspector.FlameChart.TimelineData}
*/
timelineData: function()
{
if (this._timelineData)
return this._timelineData;
this._entries = [];
this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []);
this._appendTimelineData(this._model.mainThreadEvents());
return this._timelineData;
},
/**
* @override
*/
reset: function()
{
WebInspector.TimelineFlameChartDataProviderBase.prototype.reset.call(this);
this._entries = [];
},
/**
* @param {number} startTime
* @param {number} endTime
*/
setWindowTimes: function(startTime, endTime)
{
this._startTime = startTime;
this._endTime = endTime;
this.reset();
},
/**
* @override
* @param {number} index
* @return {string}
*/
entryTitle: function(index)
{
return this._entries[index].name;
},
/**
* @override
* @param {number} entryIndex
* @return {string}
*/
entryColor: function(entryIndex)
{
var entry = this._entries[entryIndex];
if (entry.color)
return entry.color;
var event = entry.event;
if (!event)
return "#aaa";
if (event.name === WebInspector.TimelineModel.RecordType.JSFrame) {
var colorId = event.args["data"]["url"];
return this._jsFramesColorGenerator.colorForID(colorId);
}
return WebInspector.TimelineUIUtils.eventStyle(event).category.fillColorStop1;
},
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} events
*/
_appendTimelineData: function(events)
{
var topDownTree = this._buildTopDownTree(events);
var bottomUpTree = this._buildBottomUpTree(topDownTree);
this._flowEventIndexById = {};
this._minimumBoundary = 0;
this._currentLevel = 0;
this._timeSpan = appendTree.call(this, 0, 0, bottomUpTree);
/**
* @param {number} level
* @param {number} position
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} node
* @this {!WebInspector.TimelineFlameChartBottomUpDataProvider}
*/
function appendTree(level, position, node)
{
/**
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} a
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} b
* @return {number}
*/
function sortFunction(a, b)
{
return b.totalTime - a.totalTime;
}
this._currentLevel = Math.max(this._currentLevel, level);
this._appendNode(node, level, position);
if (node.children)
Object.values(node.children).sort(sortFunction).reduce(appendTree.bind(this, level + 1), position);
return position + node.totalTime;
}
},
/**
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} node
* @param {number} level
* @param {number} position
*/
_appendNode: function(node, level, position)
{
var index = this._entries.length;
this._entries.push(node);
this._timelineData.entryLevels[index] = level;
this._timelineData.entryTotalTimes[index] = node.totalTime;
this._timelineData.entryStartTimes[index] = position;
},
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} events
* @return {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode}
*/
_buildTopDownTree: function(events)
{
// Use a big enough value that exceeds the max recording time.
var /** @const */ initialTime = 1e7;
var root = new WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode();
root.totalTime = initialTime;
root.selfTime = initialTime;
root.name = WebInspector.UIString("Top-Down Chart");
var parent = root;
/**
* @param {!WebInspector.TracingModel.Event} e
* @return {boolean}
* @this {!WebInspector.TimelineFlameChartBottomUpDataProvider}
*/
function filter(e)
{
if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant)
return false;
if (WebInspector.TracingModel.isAsyncPhase(e.phase))
return false;
if (!this._isVisible(e))
return false;
if (e.endTime <= this._startTime)
return false;
if (e.startTime >= this._endTime)
return false;
return true;
}
var boundFilter = filter.bind(this);
/**
* @param {!WebInspector.TracingModel.Event} e
* @this {!WebInspector.TimelineFlameChartBottomUpDataProvider}
*/
function onStartEvent(e)
{
if (!boundFilter(e))
return;
var time = Math.min(this._endTime, e.endTime) - Math.max(this._startTime, e.startTime);
var id = eventId(e);
if (!parent.children)
parent.children = {};
var node = parent.children[id];
if (node) {
node.selfTime += time;
node.totalTime += time;
} else {
node = new WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode();
node.totalTime = time;
node.selfTime = time;
node.parent = parent;
node.name = eventName(e);
node.id = id;
node.event = e;
parent.children[id] = node;
}
parent.selfTime -= time;
if (parent.selfTime < 0) {
console.log("Error: Negative self of " + parent.selfTime, e);
parent.selfTime = 0;
}
parent = node;
}
/**
* @param {!WebInspector.TracingModel.Event} e
*/
function onEndEvent(e)
{
if (!boundFilter(e))
return;
parent = parent.parent;
}
/**
* @param {!WebInspector.TracingModel.Event} e
* @return {string}
*/
function eventId(e)
{
if (e.name === "JSFrame")
return "f:" + e.args.data.callUID;
return e.name;
}
/**
* @param {!WebInspector.TracingModel.Event} e
* @return {string}
*/
function eventName(e)
{
if (e.name === "JSFrame")
return WebInspector.beautifyFunctionName(e.args.data.functionName);
if (e.name === "EventDispatch")
return WebInspector.UIString("Event%s", e.args.data ? " (" + e.args.data.type + ")" : "");
return e.name;
}
WebInspector.TimelineModel.forEachEvent(events, onStartEvent.bind(this), onEndEvent);
root.totalTime -= root.selfTime;
root.selfTime = 0;
return root;
},
/**
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} topDownTree
* @return {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode}
*/
_buildBottomUpTree: function(topDownTree)
{
var buRoot = new WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode();
buRoot.totalTime = 0;
buRoot.name = WebInspector.UIString("Bottom-Up Chart");
buRoot.children = {};
var categories = WebInspector.TimelineUIUtils.categories();
for (var categoryName in categories) {
var category = categories[categoryName];
var node = new WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode();
node.totalTime = 0;
node.name = category.title;
node.color = category.fillColorStop1;
buRoot.children[categoryName] = node;
}
for (var id in topDownTree.children)
processNode(topDownTree.children[id]);
for (var id in buRoot.children) {
var buNode = buRoot.children[id];
buRoot.totalTime += buNode.totalTime;
}
/**
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} tdNode
*/
function processNode(tdNode)
{
if (tdNode.selfTime > 0) {
var category = WebInspector.TimelineUIUtils.eventStyle(tdNode.event).category;
var buNode = buRoot.children[category.name] || buRoot;
var time = tdNode.selfTime;
buNode.totalTime += time;
appendNode(tdNode, buNode, time);
}
for (var id in tdNode.children)
processNode(tdNode.children[id]);
}
/**
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} tdNode
* @param {!WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode} buParent
* @param {number} time
*/
function appendNode(tdNode, buParent, time)
{
// FIXME: it is possible to optimize this loop out.
while (tdNode.parent) {
if (!buParent.children)
buParent.children = {};
var id = tdNode.id;
var buNode = buParent.children[id];
if (!buNode) {
buNode = new WebInspector.TimelineFlameChartBottomUpDataProvider.TreeNode();
buNode.totalTime = time;
buNode.name = tdNode.name;
buNode.event = tdNode.event;
buNode.id = id;
buParent.children[id] = buNode;
} else {
buNode.totalTime += time;
}
tdNode = tdNode.parent;
buParent = buNode;
}
}
return buRoot;
},
__proto__: WebInspector.TimelineFlameChartDataProviderBase.prototype
}
/**
* @constructor
* @implements {WebInspector.FlameChartMarker}
* @param {number} startTime
* @param {number} startOffset
* @param {!WebInspector.TimelineMarkerStyle} style
*/
WebInspector.TimelineFlameChartMarker = function(startTime, startOffset, style)
{
this._startTime = startTime;
this._startOffset = startOffset;
this._style = style;
}
WebInspector.TimelineFlameChartMarker.prototype = {
/**
* @override
* @return {number}
*/
startTime: function()
{
return this._startTime;
},
/**
* @override
* @return {string}
*/
color: function()
{
return this._style.color;
},
/**
* @override
* @return {string}
*/
title: function()
{
var startTime = Number.millisToString(this._startOffset);
return WebInspector.UIString("%s at %s", this._style.title, startTime);
},
/**
* @override
* @param {!CanvasRenderingContext2D} context
* @param {number} x
* @param {number} height
* @param {number} pixelsPerMillisecond
*/
draw: function(context, x, height, pixelsPerMillisecond)
{
var lowPriorityVisibilityThresholdInPixelsPerMs = 4;
if (this._style.lowPriority && pixelsPerMillisecond < lowPriorityVisibilityThresholdInPixelsPerMs)
return;
context.save();
if (!this._style.lowPriority) {
context.strokeStyle = this._style.color;
context.lineWidth = 2;
context.beginPath();
context.moveTo(x, 0);
context.lineTo(x, height);
context.stroke();
}
if (this._style.tall) {
context.strokeStyle = this._style.color;
context.lineWidth = this._style.lineWidth;
context.translate(this._style.lineWidth < 1 || (this._style.lineWidth & 1) ? 0.5 : 0, 0.5);
context.beginPath();
context.moveTo(x, height);
context.setLineDash(this._style.dashStyle);
context.lineTo(x, context.canvas.height);
context.stroke();
}
context.restore();
}
}
/**
* @constructor
* @extends {WebInspector.VBox}
* @implements {WebInspector.TimelineModeView}
* @implements {WebInspector.FlameChartDelegate}
* @param {!WebInspector.TimelineModeViewDelegate} delegate
* @param {!WebInspector.TimelineModel} timelineModel
* @param {!WebInspector.TimelineFlameChartDataProviderBase} dataProvider
*/
WebInspector.TimelineFlameChart = function(delegate, timelineModel, dataProvider)
{
WebInspector.VBox.call(this);
this.element.classList.add("timeline-flamechart");
this._delegate = delegate;
this._model = timelineModel;
this._dataProvider = dataProvider;
this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
this._mainView.show(this.element);
this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
}
WebInspector.TimelineFlameChart.prototype = {
/**
* @override
*/
dispose: function()
{
this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
},
/**
* @override
* @param {number} windowStartTime
* @param {number} windowEndTime
*/
requestWindowTimes: function(windowStartTime, windowEndTime)
{
this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
},
/**
* @override
* @param {number} startTime
* @param {number} endTime
*/
updateBoxSelection: function(startTime, endTime)
{
this._delegate.select(WebInspector.TimelineSelection.fromRange(startTime, endTime));
},
/**
* @override
* @param {?RegExp} textFilter
*/
refreshRecords: function(textFilter)
{
this._dataProvider.reset();
this._mainView.scheduleUpdate();
},
/**
* @override
*/
wasShown: function()
{
this._mainView.scheduleUpdate();
},
/**
* @override
* @return {!WebInspector.View}
*/
view: function()
{
return this;
},
/**
* @override
*/
reset: function()
{
this._automaticallySizeWindow = true;
this._dataProvider.reset();
this._mainView.reset();
this._mainView.setWindowTimes(0, Infinity);
},
_onRecordingStarted: function()
{
this._automaticallySizeWindow = true;
this._mainView.reset();
},
/**
* @override
* @param {number} startTime
* @param {number} endTime
*/
setWindowTimes: function(startTime, endTime)
{
this._mainView.setWindowTimes(startTime, endTime);
this._delegate.select(null);
},
/**
* @override
* @param {number} width
*/
setSidebarSize: function(width)
{
},
/**
* @override
* @param {?WebInspector.TimelineModel.Record} record
* @param {string=} regex
* @param {boolean=} selectRecord
*/
highlightSearchResult: function(record, regex, selectRecord)
{
if (!record) {
this._delegate.select(null);
return;
}
var traceEvent = record.traceEvent();
var entryIndex = this._dataProvider._entryEvents.indexOf(traceEvent);
var timelineSelection = this._dataProvider.createSelection(entryIndex);
if (timelineSelection)
this._delegate.select(timelineSelection);
},
/**
* @override
* @param {?WebInspector.TimelineSelection} selection
*/
setSelection: function(selection)
{
var index = this._dataProvider.entryIndexForSelection(selection);
this._mainView.setSelectedEntry(index);
},
/**
* @param {!WebInspector.Event} event
*/
_onEntrySelected: function(event)
{
var entryIndex = /** @type{number} */ (event.data);
var timelineSelection = this._dataProvider.createSelection(entryIndex);
if (timelineSelection)
this._delegate.select(timelineSelection);
},
__proto__: WebInspector.VBox.prototype
}
/**
* @constructor
* @param {!WebInspector.TimelineSelection} selection
* @param {number} entryIndex
*/
WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex)
{
this.timelineSelection = selection;
this.entryIndex = entryIndex;
}