blob: 3ebd2f1b56646711586acddac83c6df72281c038 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
* Copyright (C) 2012 Intel 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
* @extends {WebInspector.Object}
*/
WebInspector.TimelinePresentationModel = function()
{
this._linkifier = new WebInspector.Linkifier();
this._glueRecords = false;
this._filters = [];
this.reset();
}
WebInspector.TimelinePresentationModel.categories = function()
{
if (WebInspector.TimelinePresentationModel._categories)
return WebInspector.TimelinePresentationModel._categories;
WebInspector.TimelinePresentationModel._categories = {
loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "#5A8BCC", "#8EB6E9", "#70A2E3"),
scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "#D8AA34", "#F3D07A", "#F1C453"),
rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "#8266CC", "#AF9AEB", "#9A7EE6"),
painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "#5FA050", "#8DC286", "#71B363"),
other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "#BBBBBB", "#DDDDDD", "#DDDDDD"),
idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), -1, "#DDDDDD", "#FFFFFF", "#FFFFFF")
};
return WebInspector.TimelinePresentationModel._categories;
};
/**
* @return {!Object.<string, {title: string, category: !WebInspector.TimelineCategory}>}
*/
WebInspector.TimelinePresentationModel._initRecordStyles = function()
{
if (WebInspector.TimelinePresentationModel._recordStylesMap)
return WebInspector.TimelinePresentationModel._recordStylesMap;
var recordTypes = WebInspector.TimelineModel.RecordType;
var categories = WebInspector.TimelinePresentationModel.categories();
var recordStyles = {};
recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] };
recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] };
recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] };
recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] };
recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] };
recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] };
recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] };
recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] };
recordStyles[recordTypes.AutosizeText] = { title: WebInspector.UIString("Autosize Text"), category: categories["rendering"] };
recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] };
recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] };
recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] };
recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] };
recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] };
recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] };
recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] };
recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] };
recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] };
recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] };
recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] };
recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] };
recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] };
recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] };
recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] };
recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] };
recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] };
recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] };
recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] };
recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] };
recordStyles[recordTypes.MarkFirstPaint] = { title: WebInspector.UIString("First paint"), category: categories["painting"] };
recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] };
recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] };
recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] };
recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] };
recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] };
recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] };
recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] };
recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] };
recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] };
recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] };
recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] };
WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles;
return recordStyles;
}
/**
* @param {!Object} record
* @return {{title: string, category: !WebInspector.TimelineCategory}}
*/
WebInspector.TimelinePresentationModel.recordStyle = function(record)
{
var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles();
var result = recordStyles[record.type];
if (!result) {
result = {
title: WebInspector.UIString("Unknown: %s", record.type),
category: WebInspector.TimelinePresentationModel.categories()["other"]
};
recordStyles[record.type] = result;
}
return result;
}
WebInspector.TimelinePresentationModel.categoryForRecord = function(record)
{
return WebInspector.TimelinePresentationModel.recordStyle(record).category;
}
WebInspector.TimelinePresentationModel.isEventDivider = function(record)
{
var recordTypes = WebInspector.TimelineModel.RecordType;
if (record.type === recordTypes.TimeStamp)
return true;
if (record.type === recordTypes.MarkFirstPaint)
return true;
if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
if (record.data && ((typeof record.data.isMainFrame) === "boolean"))
return record.data.isMainFrame;
}
return false;
}
/**
* @param {!Array.<*>} recordsArray
* @param {?function(*)} preOrderCallback
* @param {function(*)=} postOrderCallback
*/
WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
{
if (!recordsArray)
return;
var stack = [{array: recordsArray, index: 0}];
while (stack.length) {
var entry = stack[stack.length - 1];
var records = entry.array;
if (entry.index < records.length) {
var record = records[entry.index];
if (preOrderCallback && preOrderCallback(record))
return;
if (record.children)
stack.push({array: record.children, index: 0, record: record});
else if (postOrderCallback && postOrderCallback(record))
return;
++entry.index;
} else {
if (entry.record && postOrderCallback && postOrderCallback(entry.record))
return;
stack.pop();
}
}
}
/**
* @param {string=} recordType
* @return {boolean}
*/
WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType)
{
if (!recordType)
return false;
const recordTypes = WebInspector.TimelineModel.RecordType;
switch (recordType) {
case recordTypes.ScheduleResourceRequest:
case recordTypes.ResourceSendRequest:
case recordTypes.ResourceReceiveResponse:
case recordTypes.ResourceReceivedData:
case recordTypes.ResourceFinish:
return true;
default:
return false;
}
}
/**
* @param {string} recordType
* @param {string=} title
*/
WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title)
{
var eventDivider = document.createElement("div");
eventDivider.className = "resources-event-divider";
var recordTypes = WebInspector.TimelineModel.RecordType;
if (recordType === recordTypes.MarkDOMContent)
eventDivider.className += " resources-blue-divider";
else if (recordType === recordTypes.MarkLoad)
eventDivider.className += " resources-red-divider";
else if (recordType === recordTypes.MarkFirstPaint)
eventDivider.className += " resources-green-divider";
else if (recordType === recordTypes.TimeStamp)
eventDivider.className += " resources-orange-divider";
else if (recordType === recordTypes.BeginFrame)
eventDivider.className += " timeline-frame-divider";
if (title)
eventDivider.title = title;
return eventDivider;
}
WebInspector.TimelinePresentationModel._hiddenRecords = { }
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkFirstPaint] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.GPUTask] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ActivateLayerTree] = 1;
WebInspector.TimelinePresentationModel.prototype = {
/**
* @param {!WebInspector.TimelinePresentationModel.Filter} filter
*/
addFilter: function(filter)
{
this._filters.push(filter);
},
/**
* @param {?WebInspector.TimelinePresentationModel.Filter} filter
*/
setSearchFilter: function(filter)
{
this._searchFilter = filter;
},
rootRecord: function()
{
return this._rootRecord;
},
frames: function()
{
return this._frames;
},
reset: function()
{
this._linkifier.reset();
this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false);
this._sendRequestRecords = {};
this._scheduledResourceRequests = {};
this._timerRecords = {};
this._requestAnimationFrameRecords = {};
this._eventDividerRecords = [];
this._timeRecords = {};
this._timeRecordStack = [];
this._frames = [];
this._minimumRecordTime = -1;
this._layoutInvalidateStack = {};
this._lastScheduleStyleRecalculation = {};
this._webSocketCreateRecords = {};
this._coalescingBuckets = {};
},
addFrame: function(frame)
{
if (!frame.isBackground)
this._frames.push(frame);
},
/**
* @param {!TimelineAgent.TimelineEvent} record
* @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
*/
addRecord: function(record)
{
if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime)
this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record);
var records;
if (record.type === WebInspector.TimelineModel.RecordType.Program)
records = this._foldSyncTimeRecords(record.children || []);
else
records = [record];
var result = Array(records.length);
for (var i = 0; i < records.length; ++i)
result[i] = this._innerAddRecord(this._rootRecord, records[i]);
return result;
},
/**
* @param {!WebInspector.TimelinePresentationModel.Record} parentRecord
* @param {!TimelineAgent.TimelineEvent} record
* @return {!WebInspector.TimelinePresentationModel.Record}
*/
_innerAddRecord: function(parentRecord, record)
{
const recordTypes = WebInspector.TimelineModel.RecordType;
var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords;
var origin;
var coalescingBucket;
if (!isHiddenRecord) {
var newParentRecord = this._findParentRecord(record);
if (newParentRecord) {
origin = parentRecord;
parentRecord = newParentRecord;
}
// On main thread, only coalesce if the last event is of same type.
if (parentRecord === this._rootRecord)
coalescingBucket = record.thread ? record.type : "mainThread";
var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket);
if (coalescedRecord) {
if (!origin)
origin = parentRecord;
parentRecord = coalescedRecord;
}
}
var children = record.children;
var scriptDetails = null;
if (record.data && record.data["scriptName"]) {
scriptDetails = {
scriptName: record.data["scriptName"],
scriptLine: record.data["scriptLine"]
}
};
if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) {
var childRecord = children[0];
if (childRecord.type === recordTypes.FunctionCall) {
scriptDetails = {
scriptName: childRecord.data["scriptName"],
scriptLine: childRecord.data["scriptLine"]
};
children = childRecord.children.concat(children.slice(1));
}
}
var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord);
if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord))
this._eventDividerRecords.push(formattedRecord);
if (isHiddenRecord)
return formattedRecord;
formattedRecord.collapsed = parentRecord === this._rootRecord;
if (coalescingBucket)
this._coalescingBuckets[coalescingBucket] = formattedRecord;
if (children) {
children = this._foldSyncTimeRecords(children);
for (var i = 0; i < children.length; ++i)
this._innerAddRecord(formattedRecord, children[i]);
}
formattedRecord.calculateAggregatedStats();
if (parentRecord.coalesced) {
WebInspector.TimelineModel.aggregateTimeByCategory(parentRecord._aggregatedStats, formattedRecord._aggregatedStats);
if (parentRecord.startTime > formattedRecord.startTime)
parentRecord._record.startTime = record.startTime;
} else if (origin)
this._updateAncestorStats(formattedRecord);
origin = formattedRecord.origin();
if (!origin.isRoot() && !origin.coalesced)
origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime;
return formattedRecord;
},
/**
* @param {!WebInspector.TimelinePresentationModel.Record} record
*/
_updateAncestorStats: function(record)
{
var lastChildEndTime = record.lastChildEndTime;
var aggregatedStats = record.aggregatedStats;
for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) {
currentRecord._cpuTime += record._cpuTime;
if (currentRecord.lastChildEndTime < lastChildEndTime)
currentRecord.lastChildEndTime = lastChildEndTime;
for (var category in aggregatedStats)
currentRecord.aggregatedStats[category] += aggregatedStats[category];
}
},
/**
* @param {!Object} record
* @param {!Object} newParent
* @param {string=} bucket
* @return {?WebInspector.TimelinePresentationModel.Record}
*/
_findCoalescedParent: function(record, newParent, bucket)
{
const coalescingThresholdSeconds = 0.005;
var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent.children.peekLast();
if (lastRecord && lastRecord.coalesced)
lastRecord = lastRecord.children.peekLast();
var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
if (!lastRecord)
return null;
if (lastRecord.type !== record.type)
return null;
if (lastRecord.endTime + coalescingThresholdSeconds < startTime)
return null;
if (endTime + coalescingThresholdSeconds < lastRecord.startTime)
return null;
if (WebInspector.TimelinePresentationModel.coalescingKeyForRecord(record) !== WebInspector.TimelinePresentationModel.coalescingKeyForRecord(lastRecord._record))
return null;
if (lastRecord.parent.coalesced)
return lastRecord.parent;
return this._replaceWithCoalescedRecord(lastRecord);
},
/**
* @param {!WebInspector.TimelinePresentationModel.Record} record
* @return {!WebInspector.TimelinePresentationModel.Record}
*/
_replaceWithCoalescedRecord: function(record)
{
var rawRecord = {
type: record._record.type,
startTime: record._record.startTime,
endTime: record._record.endTime,
data: { }
};
if (record._record.thread)
rawRecord.thread = "aggregated";
if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp)
rawRecord.data.message = record.data.message;
var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false);
var parent = record.parent;
coalescedRecord.coalesced = true;
coalescedRecord.collapsed = true;
coalescedRecord._children.push(record);
record.parent = coalescedRecord;
if (record.hasWarnings() || record.childHasWarnings())
coalescedRecord._childHasWarnings = true;
coalescedRecord.parent = parent;
parent._children[parent._children.indexOf(record)] = coalescedRecord;
WebInspector.TimelineModel.aggregateTimeByCategory(coalescedRecord._aggregatedStats, record._aggregatedStats);
return coalescedRecord;
},
/**
* @param {!Array.<!TimelineAgent.TimelineEvent>} records
*/
_foldSyncTimeRecords: function(records)
{
var recordTypes = WebInspector.TimelineModel.RecordType;
// Fast case -- if there are no Time records, return input as is.
for (var i = 0; i < records.length && records[i].type !== recordTypes.Time; ++i) {}
if (i === records.length)
return records;
var result = [];
var stack = [];
for (var i = 0; i < records.length; ++i) {
result.push(records[i]);
if (records[i].type === recordTypes.Time) {
stack.push(result.length - 1);
continue;
}
if (records[i].type !== recordTypes.TimeEnd)
continue;
while (stack.length) {
var begin = stack.pop();
if (result[begin].data.message !== records[i].data.message)
continue;
var timeEndRecord = /** @type {!TimelineAgent.TimelineEvent} */ (result.pop());
var children = result.splice(begin + 1, result.length - begin);
result[begin] = this._createSynchronousTimeRecord(result[begin], timeEndRecord, children);
break;
}
}
return result;
},
/**
* @param {!TimelineAgent.TimelineEvent} beginRecord
* @param {!TimelineAgent.TimelineEvent} endRecord
* @param {!Array.<!TimelineAgent.TimelineEvent>} children
* @return {!TimelineAgent.TimelineEvent}
*/
_createSynchronousTimeRecord: function(beginRecord, endRecord, children)
{
return {
type: beginRecord.type,
startTime: beginRecord.startTime,
endTime: endRecord.startTime,
stackTrace: beginRecord.stackTrace,
children: children,
data: {
message: beginRecord.data.message,
isSynchronous: true
},
};
},
_findParentRecord: function(record)
{
if (!this._glueRecords)
return null;
var recordTypes = WebInspector.TimelineModel.RecordType;
switch (record.type) {
case recordTypes.ResourceReceiveResponse:
case recordTypes.ResourceFinish:
case recordTypes.ResourceReceivedData:
return this._sendRequestRecords[record.data["requestId"]];
case recordTypes.ResourceSendRequest:
return this._rootRecord;
case recordTypes.TimerFire:
return this._timerRecords[record.data["timerId"]];
case recordTypes.ResourceSendRequest:
return this._scheduledResourceRequests[record.data["url"]];
case recordTypes.FireAnimationFrame:
return this._requestAnimationFrameRecords[record.data["id"]];
}
},
setGlueRecords: function(glue)
{
this._glueRecords = glue;
},
invalidateFilteredRecords: function()
{
delete this._filteredRecords;
},
filteredRecords: function()
{
if (this._filteredRecords)
return this._filteredRecords;
var recordsInWindow = [];
var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false, parentRecord: {}}];
var revealedDepth = 0;
function revealRecordsInStack() {
for (var depth = revealedDepth + 1; depth < stack.length; ++depth) {
if (stack[depth - 1].parentIsCollapsed) {
stack[depth].parentRecord.parent._expandable = true;
return;
}
stack[depth - 1].parentRecord.collapsed = false;
recordsInWindow.push(stack[depth].parentRecord);
stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length;
stack[depth].parentIsRevealed = true;
revealedDepth = depth;
}
}
while (stack.length) {
var entry = stack[stack.length - 1];
var records = entry.children;
if (records && entry.index < records.length) {
var record = records[entry.index];
++entry.index;
if (this.isVisible(record)) {
record.parent._expandable = true;
if (this._searchFilter)
revealRecordsInStack();
if (!entry.parentIsCollapsed) {
recordsInWindow.push(record);
revealedDepth = stack.length;
entry.parentRecord.collapsed = false;
}
}
record._expandable = false;
stack.push({children: record.children,
index: 0,
parentIsCollapsed: (entry.parentIsCollapsed || (record.collapsed && (!this._searchFilter || record.clicked))),
parentRecord: record,
windowLengthBeforeChildrenTraversal: recordsInWindow.length});
} else {
stack.pop();
revealedDepth = Math.min(revealedDepth, stack.length - 1);
entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
}
}
this._filteredRecords = recordsInWindow;
return recordsInWindow;
},
filteredFrames: function(startTime, endTime)
{
function compareStartTime(value, object)
{
return value - object.startTime;
}
function compareEndTime(value, object)
{
return value - object.endTime;
}
var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime);
var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime);
while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime)
++lastFrame;
return this._frames.slice(firstFrame, lastFrame);
},
eventDividerRecords: function()
{
return this._eventDividerRecords;
},
isVisible: function(record)
{
for (var i = 0; i < this._filters.length; ++i) {
if (!this._filters[i].accept(record))
return false;
}
return !this._searchFilter || this._searchFilter.accept(record);
},
/**
* @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info
* @return {!Element}
*/
generateMainThreadBarPopupContent: function(info)
{
var firstTaskIndex = info.firstTaskIndex;
var lastTaskIndex = info.lastTaskIndex;
var tasks = info.tasks;
var messageCount = lastTaskIndex - firstTaskIndex + 1;
var cpuTime = 0;
for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
var task = tasks[i];
cpuTime += WebInspector.TimelineModel.endTimeInSeconds(task) - WebInspector.TimelineModel.startTimeInSeconds(task);
}
var startTime = WebInspector.TimelineModel.startTimeInSeconds(tasks[firstTaskIndex]);
var endTime = WebInspector.TimelineModel.endTimeInSeconds(tasks[lastTaskIndex]);
var duration = endTime - startTime;
var offset = this._minimumRecordTime;
var contentHelper = new WebInspector.TimelinePopupContentHelper(info.name);
var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true),
Number.secondsToString(startTime - offset, true));
contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true));
contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount);
return contentHelper.contentTable();
},
__proto__: WebInspector.Object.prototype
}
/**
* @constructor
* @param {!WebInspector.TimelinePresentationModel} presentationModel
* @param {!Object} record
* @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
* @param {?WebInspector.TimelinePresentationModel.Record} origin
* @param {?Object} scriptDetails
* @param {boolean} hidden
*/
WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden)
{
this._linkifier = presentationModel._linkifier;
this._aggregatedStats = {};
this._record = record;
this._children = [];
if (!hidden && parentRecord) {
this.parent = parentRecord;
if (this.isBackground)
WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this);
else
parentRecord.children.push(this);
}
if (origin)
this._origin = origin;
this._selfTime = this.endTime - this.startTime;
this._lastChildEndTime = this.endTime;
this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime;
if (record.data) {
if (record.data["url"])
this.url = record.data["url"];
if (record.data["rootNode"])
this._relatedBackendNodeId = record.data["rootNode"];
else if (record.data["elementId"])
this._relatedBackendNodeId = record.data["elementId"];
}
if (scriptDetails) {
this.scriptName = scriptDetails.scriptName;
this.scriptLine = scriptDetails.scriptLine;
}
if (parentRecord && parentRecord.callSiteStackTrace)
this.callSiteStackTrace = parentRecord.callSiteStackTrace;
var recordTypes = WebInspector.TimelineModel.RecordType;
switch (record.type) {
case recordTypes.ResourceSendRequest:
// Make resource receive record last since request was sent; make finish record last since response received.
presentationModel._sendRequestRecords[record.data["requestId"]] = this;
break;
case recordTypes.ScheduleResourceRequest:
presentationModel._scheduledResourceRequests[record.data["url"]] = this;
break;
case recordTypes.ResourceReceiveResponse:
var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
this.url = sendRequestRecord.url;
// Now that we have resource in the collection, recalculate details in order to display short url.
sendRequestRecord._refreshDetails();
if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
sendRequestRecord.parent._refreshDetails();
}
break;
case recordTypes.ResourceReceivedData:
case recordTypes.ResourceFinish:
var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
if (sendRequestRecord) // False for main resource.
this.url = sendRequestRecord.url;
break;
case recordTypes.TimerInstall:
this.timeout = record.data["timeout"];
this.singleShot = record.data["singleShot"];
presentationModel._timerRecords[record.data["timerId"]] = this;
break;
case recordTypes.TimerFire:
var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]];
if (timerInstalledRecord) {
this.callSiteStackTrace = timerInstalledRecord.stackTrace;
this.timeout = timerInstalledRecord.timeout;
this.singleShot = timerInstalledRecord.singleShot;
}
break;
case recordTypes.RequestAnimationFrame:
presentationModel._requestAnimationFrameRecords[record.data["id"]] = this;
break;
case recordTypes.FireAnimationFrame:
var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]];
if (requestAnimationRecord)
this.callSiteStackTrace = requestAnimationRecord.stackTrace;
break;
case recordTypes.Time:
if (record.data.isSynchronous)
break;
var message = record.data["message"];
var oldReference = presentationModel._timeRecords[message];
if (oldReference)
break;
presentationModel._timeRecords[message] = this;
if (origin)
presentationModel._timeRecordStack.push(this);
break;
case recordTypes.TimeEnd:
var message = record.data["message"];
var timeRecord = presentationModel._timeRecords[message];
delete presentationModel._timeRecords[message];
if (timeRecord) {
this.timeRecord = timeRecord;
timeRecord.timeEndRecord = this;
var intervalDuration = this.startTime - timeRecord.startTime;
this.intervalDuration = intervalDuration;
timeRecord.intervalDuration = intervalDuration;
}
break;
case recordTypes.ScheduleStyleRecalculation:
presentationModel._lastScheduleStyleRecalculation[this.frameId] = this;
break;
case recordTypes.RecalculateStyles:
var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId];
if (!scheduleStyleRecalculationRecord)
break;
this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
break;
case recordTypes.InvalidateLayout:
// Consider style recalculation as a reason for layout invalidation,
// but only if we had no earlier layout invalidation records.
var styleRecalcStack;
if (!presentationModel._layoutInvalidateStack[this.frameId]) {
for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) {
if (outerRecord.type === recordTypes.RecalculateStyles) {
styleRecalcStack = outerRecord.callSiteStackTrace;
break;
}
}
}
presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
break;
case recordTypes.Layout:
var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId];
if (layoutInvalidateStack)
this.callSiteStackTrace = layoutInvalidateStack;
if (this.stackTrace)
this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
presentationModel._layoutInvalidateStack[this.frameId] = null;
this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
this._relatedBackendNodeId = record.data["rootNode"];
break;
case recordTypes.AutosizeText:
if (record.data.needsRelayout && parentRecord.type === recordTypes.Layout)
parentRecord.addWarning(WebInspector.UIString("Layout required two passes due to text autosizing, consider setting viewport."));
break;
case recordTypes.Paint:
this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
break;
case recordTypes.WebSocketCreate:
this.webSocketURL = record.data["url"];
if (typeof record.data["webSocketProtocol"] !== "undefined")
this.webSocketProtocol = record.data["webSocketProtocol"];
presentationModel._webSocketCreateRecords[record.data["identifier"]] = this;
break;
case recordTypes.WebSocketSendHandshakeRequest:
case recordTypes.WebSocketReceiveHandshakeResponse:
case recordTypes.WebSocketDestroy:
var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]];
if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
this.webSocketURL = webSocketCreateRecord.webSocketURL;
if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
}
break;
}
}
WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record)
{
record.parent.children.splice(record.parent.children.indexOf(record));
WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record);
record.parent = newParent;
}
WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record)
{
function compareStartTime(value, record)
{
return value < record.startTime ? -1 : 1;
}
parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record);
}
WebInspector.TimelinePresentationModel.Record.prototype = {
get lastChildEndTime()
{
return this._lastChildEndTime;
},
set lastChildEndTime(time)
{
this._lastChildEndTime = time;
},
get selfTime()
{
return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime;
},
set selfTime(time)
{
this._selfTime = time;
},
get cpuTime()
{
return this._cpuTime;
},
/**
* @return {boolean}
*/
isRoot: function()
{
return this.type === WebInspector.TimelineModel.RecordType.Root;
},
/**
* @return {!WebInspector.TimelinePresentationModel.Record}
*/
origin: function()
{
return this._origin || this.parent;
},
/**
* @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
*/
get children()
{
return this._children;
},
/**
* @return {number}
*/
get visibleChildrenCount()
{
return this._visibleChildrenCount || 0;
},
/**
* @return {boolean}
*/
get expandable()
{
return !!this._expandable;
},
/**
* @return {!WebInspector.TimelineCategory}
*/
get category()
{
return WebInspector.TimelinePresentationModel.recordStyle(this._record).category
},
/**
* @return {string}
*/
get title()
{
return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] :
WebInspector.TimelinePresentationModel.recordStyle(this._record).title;
},
/**
* @return {number}
*/
get startTime()
{
return WebInspector.TimelineModel.startTimeInSeconds(this._record);
},
/**
* @return {number}
*/
get endTime()
{
return WebInspector.TimelineModel.endTimeInSeconds(this._record);
},
/**
* @return {boolean}
*/
get isBackground()
{
return !!this._record.thread;
},
/**
* @return {!Object}
*/
get data()
{
return this._record.data;
},
/**
* @return {string}
*/
get type()
{
return this._record.type;
},
/**
* @return {string}
*/
get frameId()
{
return this._record.frameId;
},
/**
* @return {number}
*/
get usedHeapSizeDelta()
{
return this._record.usedHeapSizeDelta || 0;
},
/**
* @return {number}
*/
get usedHeapSize()
{
return this._record.usedHeapSize;
},
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
get stackTrace()
{
if (this._record.stackTrace && this._record.stackTrace.length)
return this._record.stackTrace;
return null;
},
containsTime: function(time)
{
return this.startTime <= time && time <= this.endTime;
},
/**
* @param {function(!DocumentFragment)} callback
*/
generatePopupContent: function(callback)
{
var barrier = new CallbackBarrier();
if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type) && !this._imagePreviewElement)
WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, barrier.createCallback(this._setImagePreviewElement.bind(this)));
if (this._relatedBackendNodeId && !this._relatedNode)
WebInspector.domAgent.pushNodeByBackendIdToFrontend(this._relatedBackendNodeId, barrier.createCallback(this._setRelatedNode.bind(this)));
barrier.callWhenDone(callbackWrapper.bind(this));
function callbackWrapper()
{
callback(this._generatePopupContentSynchronously());
}
},
/**
* @param {string} key
* @return {?Object}
*/
getUserObject: function(key)
{
if (!this._userObjects)
return null;
return this._userObjects.get(key);
},
/**
* @param {string} key
* @param {!Object} value
*/
setUserObject: function(key, value)
{
if (!this._userObjects)
this._userObjects = new StringMap();
this._userObjects.put(key, value);
},
/**
* @param {!Element} element
*/
_setImagePreviewElement: function(element)
{
this._imagePreviewElement = element;
},
/**
* @param {?DOMAgent.NodeId} nodeId
*/
_setRelatedNode: function(nodeId)
{
if (typeof nodeId === "number")
this._relatedNode = WebInspector.domAgent.nodeForId(nodeId);
},
/**
* @return {!DocumentFragment}
*/
_generatePopupContentSynchronously: function()
{
var fragment = document.createDocumentFragment();
var pie = WebInspector.TimelinePresentationModel.generatePieChart(this._aggregatedStats, this.category.name);
// Insert self time.
if (!this.coalesced && this._children.length) {
pie.pieChart.addSlice(this._selfTime, this.category.fillColorStop1);
var rowElement = document.createElement("div");
pie.footerElement.insertBefore(rowElement, pie.footerElement.firstChild);
rowElement.createChild("div", "timeline-aggregated-category timeline-" + this.category.name);
rowElement.createTextChild(WebInspector.UIString("%s %s (Self)", Number.secondsToString(this._selfTime, true), this.category.title));
}
fragment.appendChild(pie.element);
var contentHelper = new WebInspector.TimelineDetailsContentHelper(true);
if (this.coalesced)
return fragment;
const recordTypes = WebInspector.TimelineModel.RecordType;
// The messages may vary per record type;
var callSiteStackTraceLabel;
var callStackLabel;
var relatedNodeLabel;
switch (this.type) {
case recordTypes.GCEvent:
contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"]));
break;
case recordTypes.TimerFire:
callSiteStackTraceLabel = WebInspector.UIString("Timer installed");
// Fall-through intended.
case recordTypes.TimerInstall:
case recordTypes.TimerRemove:
contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]);
if (typeof this.timeout === "number") {
contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000));
contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
}
break;
case recordTypes.FireAnimationFrame:
callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested");
contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]);
break;
case recordTypes.FunctionCall:
if (this.scriptName)
contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyLocation(this.scriptName, this.scriptLine, 0));
break;
case recordTypes.ScheduleResourceRequest:
case recordTypes.ResourceSendRequest:
case recordTypes.ResourceReceiveResponse:
case recordTypes.ResourceReceivedData:
case recordTypes.ResourceFinish:
contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url));
if (this._imagePreviewElement)
contentHelper.appendElementRow(WebInspector.UIString("Preview"), this._imagePreviewElement);
if (this.data["requestMethod"])
contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]);
if (typeof this.data["statusCode"] === "number")
contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]);
if (this.data["mimeType"])
contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]);
if (this.data["encodedDataLength"])
contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"]));
break;
case recordTypes.EvaluateScript:
if (this.data && this.url)
contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"]));
break;
case recordTypes.Paint:
var clip = this.data["clip"];
if (clip) {
contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip);
var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip);
contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", clipWidth, clipHeight));
} else {
// Backward compatibility: older version used x, y, width, height fields directly in data.
if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined")
contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"]));
if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined")
contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"]));
}
// Fall-through intended.
case recordTypes.PaintSetup:
case recordTypes.Rasterize:
case recordTypes.ScrollLayer:
relatedNodeLabel = WebInspector.UIString("Layer root");
break;
case recordTypes.AutosizeText:
relatedNodeLabel = WebInspector.UIString("Root node");
break;
case recordTypes.DecodeImage:
case recordTypes.ResizeImage:
relatedNodeLabel = WebInspector.UIString("Image element");
if (this.url)
contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(this.url));
break;
case recordTypes.RecalculateStyles: // We don't want to see default details.
if (this.data["elementCount"])
contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]);
callStackLabel = WebInspector.UIString("Styles recalculation forced");
break;
case recordTypes.Layout:
if (this.data["dirtyObjects"])
contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]);
if (this.data["totalObjects"])
contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]);
if (typeof this.data["partialLayout"] === "boolean") {
contentHelper.appendTextRow(WebInspector.UIString("Layout scope"),
this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document"));
}
callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
callStackLabel = WebInspector.UIString("Layout forced");
relatedNodeLabel = WebInspector.UIString("Layout root");
break;
case recordTypes.Time:
case recordTypes.TimeEnd:
contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
if (typeof this.intervalDuration === "number")
contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true));
break;
case recordTypes.WebSocketCreate:
case recordTypes.WebSocketSendHandshakeRequest:
case recordTypes.WebSocketReceiveHandshakeResponse:
case recordTypes.WebSocketDestroy:
if (typeof this.webSocketURL !== "undefined")
contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL);
if (typeof this.webSocketProtocol !== "undefined")
contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol);
if (typeof this.data["message"] !== "undefined")
contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
break;
default:
if (this.detailsNode())
contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode());
break;
}
if (this._relatedNode)
contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related node"), this._createNodeAnchor(this._relatedNode));
if (this.scriptName && this.type !== recordTypes.FunctionCall)
contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyLocation(this.scriptName, this.scriptLine, 0));
if (this.usedHeapSize) {
if (this.usedHeapSizeDelta) {
var sign = this.usedHeapSizeDelta > 0 ? "+" : "-";
contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"),
WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(Math.abs(this.usedHeapSizeDelta))));
} else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting)
contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize));
}
if (this.callSiteStackTrace)
contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this));
if (this.stackTrace)
contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this));
if (this._warnings) {
var ul = document.createElement("ul");
for (var i = 0; i < this._warnings.length; ++i)
ul.createChild("li").textContent = this._warnings[i];
contentHelper.appendElementRow(WebInspector.UIString("Warning"), ul);
}
fragment.appendChild(contentHelper.element);
return fragment;
},
/**
* @param {!WebInspector.DOMAgent} node
*/
_createNodeAnchor: function(node)
{
var span = document.createElement("span");
span.classList.add("node-link");
span.addEventListener("click", onClick, false);
WebInspector.DOMPresentationUtils.decorateNodeLabel(node, span);
function onClick()
{
WebInspector.showPanel("elements").revealAndSelectNode(node.id);
}
return span;
},
_refreshDetails: function()
{
delete this._detailsNode;
},
/**
* @return {?Node}
*/
detailsNode: function()
{
if (typeof this._detailsNode === "undefined") {
this._detailsNode = this._getRecordDetails();
if (this._detailsNode && !this.coalesced) {
this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild);
this._detailsNode.appendChild(document.createTextNode(")"));
}
}
return this._detailsNode;
},
_createSpanWithText: function(textContent)
{
var node = document.createElement("span");
node.textContent = textContent;
return node;
},
/**
* @return {?Node}
*/
_getRecordDetails: function()
{
var details;
if (this.coalesced)
return this._createSpanWithText(WebInspector.UIString("× %d", this.children.length));
switch (this.type) {
case WebInspector.TimelineModel.RecordType.GCEvent:
details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"]));
break;
case WebInspector.TimelineModel.RecordType.TimerFire:
details = this._linkifyScriptLocation(this.data["timerId"]);
break;
case WebInspector.TimelineModel.RecordType.FunctionCall:
if (this.scriptName)
details = this._linkifyLocation(this.scriptName, this.scriptLine, 0);
break;
case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
details = this._linkifyScriptLocation(this.data["id"]);
break;
case WebInspector.TimelineModel.RecordType.EventDispatch:
details = this.data ? this.data["type"] : null;
break;
case WebInspector.TimelineModel.RecordType.Paint:
var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width;
var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height;
if (width && height)
details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
break;
case WebInspector.TimelineModel.RecordType.TimerInstall:
case WebInspector.TimelineModel.RecordType.TimerRemove:
details = this._linkifyTopCallFrame(this.data["timerId"]);
break;
case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
details = this._linkifyTopCallFrame(this.data["id"]);
break;
case WebInspector.TimelineModel.RecordType.ParseHTML:
case WebInspector.TimelineModel.RecordType.RecalculateStyles:
details = this._linkifyTopCallFrame();
break;
case WebInspector.TimelineModel.RecordType.EvaluateScript:
details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null;
break;
case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
case WebInspector.TimelineModel.RecordType.XHRLoad:
case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest:
case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
case WebInspector.TimelineModel.RecordType.ResourceFinish:
case WebInspector.TimelineModel.RecordType.DecodeImage:
case WebInspector.TimelineModel.RecordType.ResizeImage:
details = WebInspector.displayNameForURL(this.url);
break;
case WebInspector.TimelineModel.RecordType.Time:
case WebInspector.TimelineModel.RecordType.TimeEnd:
details = this.data["message"];
break;
default:
details = this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : (this._linkifyTopCallFrame() || null);
break;
}
if (details) {
if (details instanceof Node)
details.tabIndex = -1;
else
return this._createSpanWithText("" + details);
}
return details || null;
},
/**
* @param {string} url
* @param {number} lineNumber
* @param {number=} columnNumber
*/
_linkifyLocation: function(url, lineNumber, columnNumber)
{
// FIXME(62725): stack trace line/column numbers are one-based.
columnNumber = columnNumber ? columnNumber - 1 : 0;
return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
},
/**
* @param {!ConsoleAgent.CallFrame} callFrame
*/
_linkifyCallFrame: function(callFrame)
{
return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
},
/**
* @param {string=} defaultValue
*/
_linkifyTopCallFrame: function(defaultValue)
{
if (this.stackTrace)
return this._linkifyCallFrame(this.stackTrace[0]);
if (this.callSiteStackTrace)
return this._linkifyCallFrame(this.callSiteStackTrace[0]);
return defaultValue;
},
/**
* @param {*} defaultValue
* @return {!Element|string}
*/
_linkifyScriptLocation: function(defaultValue)
{
return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : "" + defaultValue;
},
calculateAggregatedStats: function()
{
this._aggregatedStats = {};
this._cpuTime = this._selfTime;
for (var index = this._children.length; index; --index) {
var child = this._children[index - 1];
for (var category in child._aggregatedStats)
this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category];
}
for (var category in this._aggregatedStats)
this._cpuTime += this._aggregatedStats[category];
this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime;
},
get aggregatedStats()
{
return this._aggregatedStats;
},
/**
* @param {string} message
*/
addWarning: function(message)
{
if (this._warnings)
this._warnings.push(message);
else
this._warnings = [message];
for (var parent = this.parent; parent && !parent._childHasWarnings; parent = parent.parent)
parent._childHasWarnings = true;
},
/**
* @return {boolean}
*/
hasWarnings: function()
{
return !!this._warnings;
},
/**
* @return {boolean}
*/
childHasWarnings: function()
{
return this._childHasWarnings;
}
}
/**
* @param {!Object} aggregatedStats
*/
WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats)
{
var cell = document.createElement("span");
cell.className = "timeline-aggregated-info";
for (var index in aggregatedStats) {
var label = document.createElement("div");
label.className = "timeline-aggregated-category timeline-" + index;
cell.appendChild(label);
var text = document.createElement("span");
text.textContent = Number.secondsToString(aggregatedStats[index], true);
cell.appendChild(text);
}
return cell;
}
/**
* @param {!Object} aggregatedStats
* @param {string=} firstCategoryName
* @return {{pieChart: !WebInspector.PieChart, element: !Element, footerElement: !Element}}
*/
WebInspector.TimelinePresentationModel.generatePieChart = function(aggregatedStats, firstCategoryName)
{
var element = document.createElement("div");
element.className = "timeline-aggregated-info";
var total = 0;
var categoryNames = [];
if (firstCategoryName)
categoryNames.push(firstCategoryName);
for (var categoryName in WebInspector.TimelinePresentationModel.categories()) {
if (aggregatedStats[categoryName]) {
total += aggregatedStats[categoryName];
if (firstCategoryName !== categoryName)
categoryNames.push(categoryName);
}
}
var pieChart = new WebInspector.PieChart(total);
element.appendChild(pieChart.element);
var footerElement = element.createChild("div", "timeline-aggregated-info-legend");
for (var i = 0; i < categoryNames.length; ++i) {
var category = WebInspector.TimelinePresentationModel.categories()[categoryNames[i]];
pieChart.addSlice(aggregatedStats[category.name], category.fillColorStop0);
var rowElement = footerElement.createChild("div");
rowElement.createChild("div", "timeline-aggregated-category timeline-" + category.name);
rowElement.createTextChild(WebInspector.UIString("%s %s", Number.secondsToString(aggregatedStats[category.name], true), category.title));
}
return { pieChart: pieChart, element: element, footerElement: footerElement };
}
WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame)
{
var contentHelper = new WebInspector.TimelinePopupContentHelper(WebInspector.UIString("Frame"));
var durationInSeconds = frame.endTime - frame.startTime;
var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true),
Number.secondsToString(frame.startTimeOffset, true));
contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds));
contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true));
contentHelper.appendTextRow(WebInspector.UIString("Thread"), frame.isBackground ? WebInspector.UIString("background") : WebInspector.UIString("main"));
contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory));
return contentHelper.contentTable();
}
/**
* @param {!WebInspector.FrameStatistics} statistics
*/
WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics)
{
/**
* @param {number} time
*/
function formatTimeAndFPS(time)
{
return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time);
}
var contentHelper = new WebInspector.TimelineDetailsContentHelper(false);
contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration));
contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average));
contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration));
contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true));
return contentHelper.element;
}
/**
* @param {!CanvasRenderingContext2D} context
* @param {number} width
* @param {number} height
* @param {string} color0
* @param {string} color1
* @param {string} color2
*/
WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2)
{
var gradient = context.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, color0);
gradient.addColorStop(0.25, color1);
gradient.addColorStop(0.75, color1);
gradient.addColorStop(1, color2);
return gradient;
}
/**
* @param {!CanvasRenderingContext2D} context
* @param {number} width
* @param {number} height
* @param {!WebInspector.TimelineCategory} category
*/
WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category)
{
return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
}
/**
* @param {!WebInspector.TimelineCategory} category
*/
WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category)
{
var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
".panel.timeline .timeline-filters-header .filter-checkbox-filter.filter-checkbox-filter-" + category.name + " .checkbox-filter-checkbox, " +
".popover .timeline-" + category.name + ", " +
".timeline-details-view .timeline-" + category.name + ", " +
".timeline-category-" + category.name + " .timeline-tree-icon"
return selector + " { background-image: -webkit-linear-gradient(" +
category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" +
" border-color: " + category.borderColor +
"}";
}
/**
* @param {!Object} rawRecord
* @return {?string}
*/
WebInspector.TimelinePresentationModel.coalescingKeyForRecord = function(rawRecord)
{
var recordTypes = WebInspector.TimelineModel.RecordType;
switch (rawRecord.type)
{
case recordTypes.EventDispatch: return rawRecord.data["type"];
case recordTypes.Time: return rawRecord.data["message"];
case recordTypes.TimeStamp: return rawRecord.data["message"];
default: return null;
}
}
/**
* @param {!Array.<number>} quad
* @return {number}
*/
WebInspector.TimelinePresentationModel.quadWidth = function(quad)
{
return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
}
/**
* @param {!Array.<number>} quad
* @return {number}
*/
WebInspector.TimelinePresentationModel.quadHeight = function(quad)
{
return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
}
/**
* @param {!Object} data
* @return {?Array.<number>}
*/
WebInspector.TimelinePresentationModel.quadFromRectData = function(data)
{
if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined")
return null;
var x0 = data["x"];
var x1 = data["x"] + data["width"];
var y0 = data["y"];
var y1 = data["y"] + data["height"];
return [x0, y0, x1, y0, x1, y1, x0, y1];
}
/**
* @interface
*/
WebInspector.TimelinePresentationModel.Filter = function()
{
}
WebInspector.TimelinePresentationModel.Filter.prototype = {
/**
* @param {!WebInspector.TimelinePresentationModel.Record} record
* @return {boolean}
*/
accept: function(record) { return false; }
}
/**
* @constructor
* @extends {WebInspector.Object}
* @param {string} name
* @param {string} title
* @param {number} overviewStripGroupIndex
* @param {string} borderColor
* @param {string} fillColorStop0
* @param {string} fillColorStop1
*/
WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1)
{
this.name = name;
this.title = title;
this.overviewStripGroupIndex = overviewStripGroupIndex;
this.borderColor = borderColor;
this.fillColorStop0 = fillColorStop0;
this.fillColorStop1 = fillColorStop1;
this.hidden = false;
}
WebInspector.TimelineCategory.Events = {
VisibilityChanged: "VisibilityChanged"
};
WebInspector.TimelineCategory.prototype = {
/**
* @return {boolean}
*/
get hidden()
{
return this._hidden;
},
set hidden(hidden)
{
this._hidden = hidden;
this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
},
__proto__: WebInspector.Object.prototype
}
/**
* @constructor
* @param {string} title
*/
WebInspector.TimelinePopupContentHelper = function(title)
{
this._contentTable = document.createElement("table");
var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
titleCell.colSpan = 2;
var titleRow = document.createElement("tr");
titleRow.appendChild(titleCell);
this._contentTable.appendChild(titleRow);
}
WebInspector.TimelinePopupContentHelper.prototype = {
contentTable: function()
{
return this._contentTable;
},
/**
* @param {string=} styleName
*/
_createCell: function(content, styleName)
{
var text = document.createElement("label");
text.appendChild(document.createTextNode(content));
var cell = document.createElement("td");
cell.className = "timeline-details";
if (styleName)
cell.className += " " + styleName;
cell.textContent = content;
return cell;
},
/**
* @param {string} title
* @param {string|number|boolean} content
*/
appendTextRow: function(title, content)
{
var row = document.createElement("tr");
row.appendChild(this._createCell(title, "timeline-details-row-title"));
row.appendChild(this._createCell(content, "timeline-details-row-data"));
this._contentTable.appendChild(row);
},
/**
* @param {string} title
* @param {!Element|string} content
*/
appendElementRow: function(title, content)
{
var row = document.createElement("tr");
var titleCell = this._createCell(title, "timeline-details-row-title");
row.appendChild(titleCell);
var cell = document.createElement("td");
cell.className = "details";
if (content instanceof Element)
cell.appendChild(content);
else
cell.createTextChild(content || "");
row.appendChild(cell);
this._contentTable.appendChild(row);
}
}
/**
* @constructor
* @param {boolean} monospaceValues
*/
WebInspector.TimelineDetailsContentHelper = function(monospaceValues)
{
this.element = document.createElement("div");
this.element.className = "timeline-details-view-block";
this._monospaceValues = monospaceValues;
}
WebInspector.TimelineDetailsContentHelper.prototype = {
/**
* @param {string} title
* @param {string|number|boolean} value
*/
appendTextRow: function(title, value)
{
var rowElement = this.element.createChild("div", "timeline-details-view-row");
rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
rowElement.createChild("span", "timeline-details-view-row-value" + (this._monospaceValues ? " monospace" : "")).textContent = value;
},
/**
* @param {string} title
* @param {!Element|string} content
*/
appendElementRow: function(title, content)
{
var rowElement = this.element.createChild("div", "timeline-details-view-row");
rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
var valueElement = rowElement.createChild("span", "timeline-details-view-row-details" + (this._monospaceValues ? " monospace" : ""));
if (content instanceof Element)
valueElement.appendChild(content);
else
valueElement.createTextChild(content || "");
},
/**
* @param {string} title
* @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace
* @param {function(!ConsoleAgent.CallFrame)} callFrameLinkifier
*/
appendStackTrace: function(title, stackTrace, callFrameLinkifier)
{
var rowElement = this.element.createChild("div", "timeline-details-view-row");
rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
var stackTraceElement = rowElement.createChild("div", "timeline-details-view-row-stack-trace monospace");
for (var i = 0; i < stackTrace.length; ++i) {
var stackFrame = stackTrace[i];
var row = stackTraceElement.createChild("div");
row.createTextChild(stackFrame.functionName || WebInspector.UIString("(anonymous function)"));
row.createTextChild(" @ ");
var urlElement = callFrameLinkifier(stackFrame);
row.appendChild(urlElement);
}
}
}