blob: 638f700025e8a01c5afd309224c2c570f83a4d11 [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}
* @param {!WebInspector.TimelineModel} model
* @param {!WebInspector.TimelineUIUtils} uiUtils
*/
WebInspector.TimelinePresentationModel = function(model, uiUtils)
{
this._model = model;
this._uiUtils = uiUtils;
this._filters = [];
/**
* @type {!Map.<!WebInspector.TimelineModel.Record, !WebInspector.TimelinePresentationModel.Record>}
*/
this._recordToPresentationRecord = new Map();
this.reset();
}
WebInspector.TimelinePresentationModel.prototype = {
/**
* @param {number} startTime
* @param {number} endTime
*/
setWindowTimes: function(startTime, endTime)
{
this._windowStartTime = startTime;
this._windowEndTime = endTime;
},
/**
* @param {?WebInspector.TimelineModel.Record} record
* @return {?WebInspector.TimelinePresentationModel.Record}
*/
toPresentationRecord: function(record)
{
return record ? this._recordToPresentationRecord.get(record) || null : null;
},
/**
* @return {!WebInspector.TimelinePresentationModel.Record}
*/
rootRecord: function()
{
return this._rootRecord;
},
reset: function()
{
this._recordToPresentationRecord.clear();
this._rootRecord = new WebInspector.TimelinePresentationModel.RootRecord();
/** @type {!Object.<string, !WebInspector.TimelinePresentationModel.Record>} */
this._coalescingBuckets = {};
},
/**
* @param {!WebInspector.TimelineModel.Record} record
*/
addRecord: function(record)
{
if (this._uiUtils.isProgram(record)) {
var records = record.children();
for (var i = 0; i < records.length; ++i)
this._innerAddRecord(this._rootRecord, records[i]);
} else {
this._innerAddRecord(this._rootRecord, record);
}
},
/**
* @param {!WebInspector.TimelinePresentationModel.Record} parentRecord
* @param {!WebInspector.TimelineModel.Record} record
*/
_innerAddRecord: function(parentRecord, record)
{
var coalescingBucket;
// 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)
parentRecord = coalescedRecord;
var formattedRecord = new WebInspector.TimelinePresentationModel.ActualRecord(record, parentRecord);
this._recordToPresentationRecord.set(record, formattedRecord);
formattedRecord._collapsed = parentRecord === this._rootRecord;
if (coalescingBucket)
this._coalescingBuckets[coalescingBucket] = formattedRecord;
for (var i = 0; record.children() && i < record.children().length; ++i)
this._innerAddRecord(formattedRecord, record.children()[i]);
if (parentRecord.coalesced())
this._updateCoalescingParent(formattedRecord);
},
/**
* @param {!WebInspector.TimelineModel.Record} record
* @param {!WebInspector.TimelinePresentationModel.Record} newParent
* @param {string=} bucket
* @return {?WebInspector.TimelinePresentationModel.Record}
*/
_findCoalescedParent: function(record, newParent, bucket)
{
const coalescingThresholdMillis = 5;
var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent._presentationChildren.peekLast();
if (lastRecord && lastRecord.coalesced())
lastRecord = lastRecord._presentationChildren.peekLast();
var startTime = record.startTime();
var endTime = record.endTime();
if (!lastRecord)
return null;
if (lastRecord.record().type() !== record.type())
return null;
if (!this._uiUtils.isCoalescable(record.type()))
return null;
if (lastRecord.record().endTime() + coalescingThresholdMillis < startTime)
return null;
if (endTime + coalescingThresholdMillis < lastRecord.record().startTime())
return null;
if (lastRecord.presentationParent().coalesced())
return lastRecord.presentationParent();
return this._replaceWithCoalescedRecord(lastRecord);
},
/**
* @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
* @return {!WebInspector.TimelinePresentationModel.Record}
*/
_replaceWithCoalescedRecord: function(presentationRecord)
{
var record = presentationRecord.record();
var parent = presentationRecord._presentationParent;
var coalescedRecord = new WebInspector.TimelinePresentationModel.CoalescedRecord(record);
coalescedRecord._collapsed = true;
coalescedRecord._presentationChildren.push(presentationRecord);
presentationRecord._presentationParent = coalescedRecord;
if (presentationRecord.hasWarnings() || presentationRecord.childHasWarnings())
coalescedRecord._childHasWarnings = true;
coalescedRecord._presentationParent = parent;
parent._presentationChildren[parent._presentationChildren.indexOf(presentationRecord)] = coalescedRecord;
return coalescedRecord;
},
/**
* @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
*/
_updateCoalescingParent: function(presentationRecord)
{
var parentRecord = presentationRecord._presentationParent;
if (parentRecord.endTime() < presentationRecord.endTime())
parentRecord._endTime = presentationRecord.endTime();
},
/**
* @param {?RegExp} textFilter
*/
setTextFilter: function(textFilter)
{
var records = this._recordToPresentationRecord.values();
for (var i = 0; i < records.length; ++i)
records[i]._expandedOrCollapsedWhileFiltered = false;
this._textFilter = textFilter;
},
refreshRecords: function()
{
this.reset();
var modelRecords = this._model.records();
for (var i = 0; i < modelRecords.length; ++i)
this.addRecord(modelRecords[i]);
},
invalidateFilteredRecords: function()
{
delete this._filteredRecords;
},
/**
* @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
*/
filteredRecords: function()
{
if (this._filteredRecords)
return this._filteredRecords;
var recordsInWindow = [];
var stack = [{children: this._rootRecord._presentationChildren, 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._presentationParent._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 (record.startTime() < this._windowEndTime && record.endTime() > this._windowStartTime) {
if (this._model.isVisible(record.record())) {
record._presentationParent._expandable = true;
if (this._textFilter)
revealRecordsInStack();
if (!entry.parentIsCollapsed) {
recordsInWindow.push(record);
revealedDepth = stack.length;
entry.parentRecord._collapsed = false;
}
}
}
record._expandable = false;
stack.push({children: record._presentationChildren,
index: 0,
parentIsCollapsed: entry.parentIsCollapsed || (record._collapsed && (!this._textFilter || record._expandedOrCollapsedWhileFiltered)),
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;
},
__proto__: WebInspector.Object.prototype
}
/**
* @constructor
* @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
*/
WebInspector.TimelinePresentationModel.Record = function(parentRecord)
{
/**
* @type {!Array.<!WebInspector.TimelinePresentationModel.Record>}
*/
this._presentationChildren = [];
if (parentRecord) {
this._presentationParent = parentRecord;
parentRecord._presentationChildren.push(this);
}
}
WebInspector.TimelinePresentationModel.Record.prototype = {
/**
* @return {number}
*/
startTime: function()
{
throw new Error("Not implemented.");
},
/**
* @return {number}
*/
endTime: function()
{
throw new Error("Not implemented.");
},
/**
* @return {number}
*/
selfTime: function()
{
throw new Error("Not implemented.");
},
/**
* @return {!WebInspector.TimelineModel.Record}
*/
record: function()
{
throw new Error("Not implemented.");
},
/**
* @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
*/
presentationChildren: function()
{
return this._presentationChildren;
},
/**
* @return {boolean}
*/
coalesced: function()
{
return false;
},
/**
* @return {boolean}
*/
collapsed: function()
{
return this._collapsed;
},
/**
* @param {boolean} collapsed
*/
setCollapsed: function(collapsed)
{
this._collapsed = collapsed;
this._expandedOrCollapsedWhileFiltered = true;
},
/**
* @return {?WebInspector.TimelinePresentationModel.Record}
*/
presentationParent: function()
{
return this._presentationParent || null;
},
/**
* @return {number}
*/
visibleChildrenCount: function()
{
return this._visibleChildrenCount || 0;
},
/**
* @return {boolean}
*/
expandable: function()
{
return !!this._expandable;
},
/**
* @return {boolean}
*/
hasWarnings: function()
{
return false;
},
/**
* @return {boolean}
*/
childHasWarnings: function()
{
return this._childHasWarnings;
},
/**
* @return {?WebInspector.TimelineRecordListRow}
*/
listRow: function()
{
return this._listRow;
},
/**
* @param {!WebInspector.TimelineRecordListRow} listRow
*/
setListRow: function(listRow)
{
this._listRow = listRow;
},
/**
* @return {?WebInspector.TimelineRecordGraphRow}
*/
graphRow: function()
{
return this._graphRow;
},
/**
* @param {!WebInspector.TimelineRecordGraphRow} graphRow
*/
setGraphRow: function(graphRow)
{
this._graphRow = graphRow;
}
}
/**
* @constructor
* @extends {WebInspector.TimelinePresentationModel.Record}
* @param {!WebInspector.TimelineModel.Record} record
* @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
*/
WebInspector.TimelinePresentationModel.ActualRecord = function(record, parentRecord)
{
WebInspector.TimelinePresentationModel.Record.call(this, parentRecord);
this._record = record;
if (this.hasWarnings()) {
for (var parent = this._presentationParent; parent && !parent._childHasWarnings; parent = parent._presentationParent)
parent._childHasWarnings = true;
}
}
WebInspector.TimelinePresentationModel.ActualRecord.prototype = {
/**
* @return {number}
*/
startTime: function()
{
return this._record.startTime();
},
/**
* @return {number}
*/
endTime: function()
{
return this._record.endTime();
},
/**
* @return {number}
*/
selfTime: function()
{
return this._record.selfTime();
},
/**
* @return {!WebInspector.TimelineModel.Record}
*/
record: function()
{
return this._record;
},
/**
* @return {boolean}
*/
hasWarnings: function()
{
return !!this._record.warnings();
},
__proto__: WebInspector.TimelinePresentationModel.Record.prototype
}
/**
* @constructor
* @extends {WebInspector.TimelinePresentationModel.Record}
* @param {!WebInspector.TimelineModel.Record} record
*/
WebInspector.TimelinePresentationModel.CoalescedRecord = function(record)
{
WebInspector.TimelinePresentationModel.Record.call(this, null);
this._startTime = record.startTime();
this._endTime = record.endTime();
}
WebInspector.TimelinePresentationModel.CoalescedRecord.prototype = {
/**
* @return {number}
*/
startTime: function()
{
return this._startTime;
},
/**
* @return {number}
*/
endTime: function()
{
return this._endTime;
},
/**
* @return {number}
*/
selfTime: function()
{
return 0;
},
/**
* @return {!WebInspector.TimelineModel.Record}
*/
record: function()
{
return this._presentationChildren[0].record();
},
/**
* @return {boolean}
*/
coalesced: function()
{
return true;
},
/**
* @return {boolean}
*/
hasWarnings: function()
{
return false;
},
__proto__: WebInspector.TimelinePresentationModel.Record.prototype
}
/**
* @constructor
* @extends {WebInspector.TimelinePresentationModel.Record}
*/
WebInspector.TimelinePresentationModel.RootRecord = function()
{
WebInspector.TimelinePresentationModel.Record.call(this, null);
}
WebInspector.TimelinePresentationModel.RootRecord.prototype = {
/**
* @return {boolean}
*/
hasWarnings: function()
{
return false;
},
__proto__: WebInspector.TimelinePresentationModel.Record.prototype
}