blob: c1361657def37c9b92af464042ca5add7e867f43 [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.Object}
*/
WebInspector.TimelineModel = function()
{
WebInspector.Object.call(this);
this._filters = [];
}
WebInspector.TimelineModel.RecordType = {
Root: "Root",
Program: "Program",
EventDispatch: "EventDispatch",
GPUTask: "GPUTask",
RequestMainThreadFrame: "RequestMainThreadFrame",
BeginFrame: "BeginFrame",
ActivateLayerTree: "ActivateLayerTree",
DrawFrame: "DrawFrame",
ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
RecalculateStyles: "RecalculateStyles",
InvalidateLayout: "InvalidateLayout",
Layout: "Layout",
UpdateLayerTree: "UpdateLayerTree",
PaintSetup: "PaintSetup",
Paint: "Paint",
Rasterize: "Rasterize",
ScrollLayer: "ScrollLayer",
DecodeImage: "DecodeImage",
ResizeImage: "ResizeImage",
CompositeLayers: "CompositeLayers",
ParseHTML: "ParseHTML",
TimerInstall: "TimerInstall",
TimerRemove: "TimerRemove",
TimerFire: "TimerFire",
XHRReadyStateChange: "XHRReadyStateChange",
XHRLoad: "XHRLoad",
EvaluateScript: "EvaluateScript",
MarkLoad: "MarkLoad",
MarkDOMContent: "MarkDOMContent",
MarkFirstPaint: "MarkFirstPaint",
TimeStamp: "TimeStamp",
ConsoleTime: "ConsoleTime",
ResourceSendRequest: "ResourceSendRequest",
ResourceReceiveResponse: "ResourceReceiveResponse",
ResourceReceivedData: "ResourceReceivedData",
ResourceFinish: "ResourceFinish",
FunctionCall: "FunctionCall",
GCEvent: "GCEvent",
JSFrame: "JSFrame",
UpdateCounters: "UpdateCounters",
RequestAnimationFrame: "RequestAnimationFrame",
CancelAnimationFrame: "CancelAnimationFrame",
FireAnimationFrame: "FireAnimationFrame",
WebSocketCreate : "WebSocketCreate",
WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
WebSocketDestroy : "WebSocketDestroy",
EmbedderCallback : "EmbedderCallback",
}
WebInspector.TimelineModel.Events = {
RecordAdded: "RecordAdded",
RecordsCleared: "RecordsCleared",
RecordingStarted: "RecordingStarted",
RecordingStopped: "RecordingStopped",
RecordingProgress: "RecordingProgress",
RecordFilterChanged: "RecordFilterChanged"
}
WebInspector.TimelineModel.MainThreadName = "main";
/**
* @param {!Array.<!WebInspector.TimelineModel.Record>} recordsArray
* @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
* @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
* @return {boolean}
*/
WebInspector.TimelineModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
{
/**
* @param {!Array.<!WebInspector.TimelineModel.Record>} records
* @param {number} depth
* @return {boolean}
*/
function processRecords(records, depth)
{
for (var i = 0; i < records.length; ++i) {
var record = records[i];
if (preOrderCallback && preOrderCallback(record, depth))
return true;
if (processRecords(record.children(), depth + 1))
return true;
if (postOrderCallback && postOrderCallback(record, depth))
return true;
}
return false;
}
return processRecords(recordsArray, 0);
}
WebInspector.TimelineModel.prototype = {
/**
* @param {boolean} captureStacks
* @param {boolean} captureMemory
* @param {boolean} capturePictures
*/
startRecording: function(captureStacks, captureMemory, capturePictures)
{
},
stopRecording: function()
{
},
/**
* @param {?function(!WebInspector.TimelineModel.Record)|?function(!WebInspector.TimelineModel.Record,number)} preOrderCallback
* @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)=} postOrderCallback
*/
forAllRecords: function(preOrderCallback, postOrderCallback)
{
WebInspector.TimelineModel.forAllRecords(this._records, preOrderCallback, postOrderCallback);
},
/**
* @param {!WebInspector.TimelineModel.Filter} filter
*/
addFilter: function(filter)
{
this._filters.push(filter);
filter._model = this;
},
/**
* @param {function(!WebInspector.TimelineModel.Record)|function(!WebInspector.TimelineModel.Record,number)} callback
*/
forAllFilteredRecords: function(callback)
{
/**
* @param {!WebInspector.TimelineModel.Record} record
* @param {number} depth
* @this {WebInspector.TimelineModel}
* @return {boolean}
*/
function processRecord(record, depth)
{
var visible = this.isVisible(record);
if (visible) {
if (callback(record, depth))
return true;
}
for (var i = 0; i < record.children().length; ++i) {
if (processRecord.call(this, record.children()[i], visible ? depth + 1 : depth))
return true;
}
return false;
}
for (var i = 0; i < this._records.length; ++i)
processRecord.call(this, this._records[i], 0);
},
/**
* @param {!WebInspector.TimelineModel.Record} record
* @return {boolean}
*/
isVisible: function(record)
{
for (var i = 0; i < this._filters.length; ++i) {
if (!this._filters[i].accept(record))
return false;
}
return true;
},
_filterChanged: function()
{
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordFilterChanged);
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
records: function()
{
return this._records;
},
/**
* @param {!Blob} file
* @param {!WebInspector.Progress} progress
*/
loadFromFile: function(file, progress)
{
var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress);
var fileReader = this._createFileReader(file, delegate);
var loader = this.createLoader(fileReader, progress);
fileReader.start(loader);
},
/**
* @param {!WebInspector.ChunkedFileReader} fileReader
* @param {!WebInspector.Progress} progress
* @return {!WebInspector.OutputStream}
*/
createLoader: function(fileReader, progress)
{
throw new Error("Not implemented.");
},
_createFileReader: function(file, delegate)
{
return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModelImpl.TransferChunkLengthBytes, delegate);
},
_createFileWriter: function()
{
return new WebInspector.FileOutputStream();
},
saveToFile: function()
{
var now = new Date();
var fileName = "TimelineRawData-" + now.toISO8601Compact() + ".json";
var stream = this._createFileWriter();
/**
* @param {boolean} accepted
* @this {WebInspector.TimelineModel}
*/
function callback(accepted)
{
if (!accepted)
return;
this.writeToStream(stream);
}
stream.open(fileName, callback.bind(this));
},
/**
* @param {!WebInspector.OutputStream} stream
*/
writeToStream: function(stream)
{
throw new Error("Not implemented.");
},
reset: function()
{
this._records = [];
this._minimumRecordTime = 0;
this._maximumRecordTime = 0;
/** @type {!Array.<!WebInspector.TimelineModel.Record>} */
this._mainThreadTasks = [];
/** @type {!Array.<!WebInspector.TimelineModel.Record>} */
this._gpuThreadTasks = [];
/** @type {!Array.<!WebInspector.TimelineModel.Record>} */
this._eventDividerRecords = [];
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
},
/**
* @return {number}
*/
minimumRecordTime: function()
{
return this._minimumRecordTime;
},
/**
* @return {number}
*/
maximumRecordTime: function()
{
return this._maximumRecordTime;
},
/**
* @return {boolean}
*/
isEmpty: function()
{
return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0;
},
/**
* @param {!WebInspector.TimelineModel.Record} record
*/
_updateBoundaries: function(record)
{
var startTime = record.startTime();
var endTime = record.endTime();
if (!this._minimumRecordTime || startTime < this._minimumRecordTime)
this._minimumRecordTime = startTime;
if (endTime > this._maximumRecordTime)
this._maximumRecordTime = endTime;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
mainThreadTasks: function()
{
return this._mainThreadTasks;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
gpuThreadTasks: function()
{
return this._gpuThreadTasks;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
eventDividerRecords: function()
{
return this._eventDividerRecords;
},
__proto__: WebInspector.Object.prototype
}
/**
* @interface
*/
WebInspector.TimelineModel.Record = function()
{
}
WebInspector.TimelineModel.Record.prototype = {
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
callSiteStackTrace: function() { },
/**
* @return {?WebInspector.TimelineModel.Record}
*/
initiator: function() { },
/**
* @return {?WebInspector.Target}
*/
target: function() { },
/**
* @return {number}
*/
selfTime: function() { },
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
children: function() { },
/**
* @return {number}
*/
startTime: function() { },
/**
* @return {string}
*/
thread: function() { },
/**
* @return {number}
*/
endTime: function() { },
/**
* @param {number} endTime
*/
setEndTime: function(endTime) { },
/**
* @return {!Object}
*/
data: function() { },
/**
* @return {string}
*/
type: function() { },
/**
* @return {string}
*/
frameId: function() { },
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
stackTrace: function() { },
/**
* @param {string} key
* @return {?Object}
*/
getUserObject: function(key) { },
/**
* @param {string} key
* @param {?Object|undefined} value
*/
setUserObject: function(key, value) { },
/**
* @return {?Array.<string>}
*/
warnings: function() { }
}
/**
* @constructor
*/
WebInspector.TimelineModel.Filter = function()
{
/** @type {!WebInspector.TimelineModel} */
this._model;
}
WebInspector.TimelineModel.Filter.prototype = {
/**
* @param {!WebInspector.TimelineModel.Record} record
* @return {boolean}
*/
accept: function(record)
{
return true;
},
notifyFilterChanged: function()
{
this._model._filterChanged();
}
}
/**
* @constructor
* @extends {WebInspector.TimelineModel.Filter}
* @param {!Array.<string>} recordTypes
*/
WebInspector.TimelineRecordTypeFilter = function(recordTypes)
{
WebInspector.TimelineModel.Filter.call(this);
this._recordTypes = recordTypes.keySet();
}
WebInspector.TimelineRecordTypeFilter.prototype = {
__proto__: WebInspector.TimelineModel.Filter.prototype
}
/**
* @constructor
* @extends {WebInspector.TimelineRecordTypeFilter}
* @param {!Array.<string>} recordTypes
*/
WebInspector.TimelineRecordHiddenEmptyTypeFilter = function(recordTypes)
{
WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
}
WebInspector.TimelineRecordHiddenEmptyTypeFilter.prototype = {
/**
* @param {!WebInspector.TimelineModel.Record} record
* @return {boolean}
*/
accept: function(record)
{
return record.children().length !== 0 || !this._recordTypes[record.type()];
},
__proto__: WebInspector.TimelineRecordTypeFilter.prototype
}
/**
* @constructor
* @extends {WebInspector.TimelineRecordTypeFilter}
* @param {!Array.<string>} recordTypes
*/
WebInspector.TimelineRecordHiddenTypeFilter = function(recordTypes)
{
WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
}
WebInspector.TimelineRecordHiddenTypeFilter.prototype = {
/**
* @param {!WebInspector.TimelineModel.Record} record
* @return {boolean}
*/
accept: function(record)
{
return !this._recordTypes[record.type()];
},
__proto__: WebInspector.TimelineRecordTypeFilter.prototype
}
/**
* @constructor
* @extends {WebInspector.TimelineRecordTypeFilter}
* @param {!Array.<string>} recordTypes
*/
WebInspector.TimelineRecordVisibleTypeFilter = function(recordTypes)
{
WebInspector.TimelineRecordTypeFilter.call(this, recordTypes);
}
WebInspector.TimelineRecordVisibleTypeFilter.prototype = {
/**
* @param {!WebInspector.TimelineModel.Record} record
* @return {boolean}
*/
accept: function(record)
{
return !!this._recordTypes[record.type()];
},
__proto__: WebInspector.TimelineRecordTypeFilter.prototype
}
/**
* @constructor
*/
WebInspector.TimelineMergingRecordBuffer = function()
{
this._backgroundRecordsBuffer = [];
}
/**
* @constructor
*/
WebInspector.TimelineMergingRecordBuffer.prototype = {
/**
* @param {string} thread
* @param {!Array.<!WebInspector.TimelineModel.Record>} records
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
process: function(thread, records)
{
if (thread !== WebInspector.TimelineModel.MainThreadName) {
this._backgroundRecordsBuffer = this._backgroundRecordsBuffer.concat(records);
return [];
}
/**
* @param {!WebInspector.TimelineModel.Record} a
* @param {!WebInspector.TimelineModel.Record} b
*/
function recordTimestampComparator(a, b)
{
// Never return 0, as the merge function will squash identical entries.
return a.startTime() < b.startTime() ? -1 : 1;
}
var result = this._backgroundRecordsBuffer.mergeOrdered(records, recordTimestampComparator);
this._backgroundRecordsBuffer = [];
return result;
}
}
/**
* @constructor
* @implements {WebInspector.OutputStreamDelegate}
* @param {!WebInspector.TimelineModel} model
* @param {!WebInspector.Progress} progress
*/
WebInspector.TimelineModelLoadFromFileDelegate = function(model, progress)
{
this._model = model;
this._progress = progress;
}
WebInspector.TimelineModelLoadFromFileDelegate.prototype = {
onTransferStarted: function()
{
this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
},
/**
* @param {!WebInspector.ChunkedReader} reader
*/
onChunkTransferred: function(reader)
{
if (this._progress.isCanceled()) {
reader.cancel();
this._progress.done();
this._model.reset();
return;
}
var totalSize = reader.fileSize();
if (totalSize) {
this._progress.setTotalWork(totalSize);
this._progress.setWorked(reader.loadedSize());
}
},
onTransferFinished: function()
{
this._progress.done();
},
/**
* @param {!WebInspector.ChunkedReader} reader
* @param {!Event} event
*/
onError: function(reader, event)
{
this._progress.done();
this._model.reset();
switch (event.target.error.code) {
case FileError.NOT_FOUND_ERR:
WebInspector.console.error(WebInspector.UIString("File \"%s\" not found.", reader.fileName()));
break;
case FileError.NOT_READABLE_ERR:
WebInspector.console.error(WebInspector.UIString("File \"%s\" is not readable", reader.fileName()));
break;
case FileError.ABORT_ERR:
break;
default:
WebInspector.console.error(WebInspector.UIString("An error occurred while reading the file \"%s\"", reader.fileName()));
}
}
}