node-inspector-sans-ws
Version:
Web Inspector based nodeJS debugger
1,176 lines (1,047 loc) • 65.4 kB
JavaScript
/*
* Copyright (C) 2012 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")
};
return WebInspector.TimelinePresentationModel._categories;
};
/**
* @return {!Object.<string, {title: string, category}>}
*/
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.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("Rasterize"), 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.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
*/
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.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.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.ScheduleStyleRecalculation] = 1;
WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 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)
{
this._frames.push(frame);
},
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 = record.children;
else
records = [record];
var formattedRecords = [];
var recordsCount = records.length;
for (var i = 0; i < recordsCount; ++i)
formattedRecords.push(this._innerAddRecord(records[i], this._rootRecord));
return formattedRecords;
},
_innerAddRecord: function(record, parentRecord)
{
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;
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;
var childrenCount = children ? children.length : 0;
for (var i = 0; i < childrenCount; ++i)
this._innerAddRecord(children[i], formattedRecord);
formattedRecord.calculateAggregatedStats();
if (origin)
this._updateAncestorStats(formattedRecord);
if (parentRecord.coalesced && parentRecord.startTime > formattedRecord.startTime)
parentRecord._record.startTime = record.startTime;
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;
coalescedRecord.calculateAggregatedStats();
if (record.hasWarning || record.childHasWarning)
coalescedRecord.childHasWarning = true;
coalescedRecord.parent = parent;
parent._children[parent._children.indexOf(record)] = coalescedRecord;
return coalescedRecord;
},
_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"]];
case recordTypes.Time:
return this._rootRecord;
case recordTypes.TimeEnd:
return this._timeRecords[record.data["message"]];
}
},
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 += task.endTime - task.startTime;
}
var startTime = tasks[firstTaskIndex].startTime;
var endTime = tasks[lastTaskIndex].endTime;
var duration = endTime - startTime;
var offset = this._minimumRecordTime;
var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("CPU"));
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|undefined} 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["layerRootNode"])
this._relatedBackendNodeId = record.data["layerRootNode"];
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:
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;
if (!origin)
break;
var recordStack = presentationModel._timeRecordStack;
recordStack.splice(recordStack.indexOf(timeRecord), 1);
for (var index = recordStack.length; index; --index) {
var openRecord = recordStack[index - 1];
if (openRecord.startTime > timeRecord.startTime)
continue;
WebInspector.TimelinePresentationModel.adoptRecord(openRecord, timeRecord);
break;
}
}
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.setHasWarning();
presentationModel._layoutInvalidateStack[this.frameId] = null;
this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
this._relatedBackendNodeId = record.data["rootNode"];
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.<DebuggerAgent.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(Element)} 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 {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 {Element}
*/
_generatePopupContentSynchronously: function()
{
var contentHelper = new WebInspector.PopoverContentHelper(this.title);
var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, true),
Number.secondsToString(this._startTimeOffset));
contentHelper.appendTextRow(WebInspector.UIString("Duration"), text);
if (this._children.length) {
if (!this.coalesced)
contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime, true));
contentHelper.appendTextRow(WebInspector.UIString("CPU Time"), Number.secondsToString(this._cpuTime, true));
contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
WebInspector.TimelinePresentationModel._generateAggregatedInfo(this._aggregatedStats));
}
if (this.coalesced)
return contentHelper.contentTable();
const recordTypes = WebInspector.TimelineModel.RecordType;
// The messages may vary per record type;
var callSiteStackTraceLabel;
var callStackLabel;
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:
contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyScriptLocation());
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:
if (this._relatedNode)
contentHelper.appendElementRow(WebInspector.UIString("Layer root"), this._createNodeAnchor(this._relatedNode));
break;
case recordTypes.DecodeImage:
case recordTypes.ResizeImage:
if (this._relatedNode)
contentHelper.appendElementRow(WebInspector.UIString("Image element"), this._createNodeAnchor(this._relatedNode));
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");
if (this.stackTrace) {
callStackLabel = WebInspector.UIString("Layout forced");
contentHelper.appendTextRow(WebInspector.UIString("Note"), WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
}
if (this._relatedNode)
contentHelper.appendElementRow(WebInspector.UIString("Layout root"), this._createNodeAnchor(this._relatedNode));
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.scriptName && this.type !== recordTypes.FunctionCall)
contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyScriptLocation());
if (this.usedHeapSize) {
if (this.usedHeapSizeDelta) {
var sign =