monaca-lib
Version:
Monaca cloud API bindings for JavaScript
1,536 lines (1,368 loc) • 68.5 kB
JavaScript
/*
* 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
* @param {!WebInspector.TracingModel} tracingModel
* @param {!WebInspector.TimelineModel.Filter} recordFilter
* @extends {WebInspector.Object}
* @implements {WebInspector.TargetManager.Observer}
* @implements {WebInspector.TracingManagerClient}
*/
WebInspector.TimelineModel = function(tracingModel, recordFilter)
{
WebInspector.Object.call(this);
this._filters = [];
this._tracingModel = tracingModel;
this._recordFilter = recordFilter;
this._targets = [];
this.reset();
WebInspector.targetManager.observeTargets(this);
}
WebInspector.TimelineModel.RecordType = {
Task: "Task",
Program: "Program",
EventDispatch: "EventDispatch",
GPUTask: "GPUTask",
Animation: "Animation",
RequestMainThreadFrame: "RequestMainThreadFrame",
BeginFrame: "BeginFrame",
BeginMainThreadFrame: "BeginMainThreadFrame",
ActivateLayerTree: "ActivateLayerTree",
DrawFrame: "DrawFrame",
ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
RecalculateStyles: "RecalculateStyles",
InvalidateLayout: "InvalidateLayout",
Layout: "Layout",
UpdateLayer: "UpdateLayer",
UpdateLayerTree: "UpdateLayerTree",
PaintSetup: "PaintSetup",
Paint: "Paint",
PaintImage: "PaintImage",
Rasterize: "Rasterize",
RasterTask: "RasterTask",
ScrollLayer: "ScrollLayer",
CompositeLayers: "CompositeLayers",
ScheduleStyleInvalidationTracking: "ScheduleStyleInvalidationTracking",
StyleRecalcInvalidationTracking: "StyleRecalcInvalidationTracking",
StyleInvalidatorInvalidationTracking: "StyleInvalidatorInvalidationTracking",
LayoutInvalidationTracking: "LayoutInvalidationTracking",
LayerInvalidationTracking: "LayerInvalidationTracking",
PaintInvalidationTracking: "PaintInvalidationTracking",
ParseHTML: "ParseHTML",
ParseAuthorStyleSheet: "ParseAuthorStyleSheet",
TimerInstall: "TimerInstall",
TimerRemove: "TimerRemove",
TimerFire: "TimerFire",
XHRReadyStateChange: "XHRReadyStateChange",
XHRLoad: "XHRLoad",
EvaluateScript: "EvaluateScript",
CommitLoad: "CommitLoad",
MarkLoad: "MarkLoad",
MarkDOMContent: "MarkDOMContent",
MarkFirstPaint: "MarkFirstPaint",
TimeStamp: "TimeStamp",
ConsoleTime: "ConsoleTime",
ResourceSendRequest: "ResourceSendRequest",
ResourceReceiveResponse: "ResourceReceiveResponse",
ResourceReceivedData: "ResourceReceivedData",
ResourceFinish: "ResourceFinish",
FunctionCall: "FunctionCall",
GCEvent: "GCEvent",
JSFrame: "JSFrame",
JSSample: "JSSample",
UpdateCounters: "UpdateCounters",
RequestAnimationFrame: "RequestAnimationFrame",
CancelAnimationFrame: "CancelAnimationFrame",
FireAnimationFrame: "FireAnimationFrame",
WebSocketCreate : "WebSocketCreate",
WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
WebSocketDestroy : "WebSocketDestroy",
EmbedderCallback : "EmbedderCallback",
SetLayerTreeId: "SetLayerTreeId",
TracingStartedInPage: "TracingStartedInPage",
TracingSessionIdForWorker: "TracingSessionIdForWorker",
DecodeImage: "Decode Image",
ResizeImage: "Resize Image",
DrawLazyPixelRef: "Draw LazyPixelRef",
DecodeLazyPixelRef: "Decode LazyPixelRef",
LazyPixelRef: "LazyPixelRef",
LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl",
PictureSnapshot: "cc::Picture",
// CpuProfile is a virtual event created on frontend to support
// serialization of CPU Profiles within tracing timeline data.
CpuProfile: "CpuProfile"
}
WebInspector.TimelineModel.Events = {
RecordsCleared: "RecordsCleared",
RecordingStarted: "RecordingStarted",
RecordingStopped: "RecordingStopped",
RecordFilterChanged: "RecordFilterChanged",
BufferUsage: "BufferUsage",
RetrieveEventsProgress: "RetrieveEventsProgress"
}
WebInspector.TimelineModel.MainThreadName = "main";
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} events
* @param {function(!WebInspector.TracingModel.Event)} onStartEvent
* @param {function(!WebInspector.TracingModel.Event)} onEndEvent
* @param {function(!WebInspector.TracingModel.Event,?WebInspector.TracingModel.Event)=} onInstantEvent
*/
WebInspector.TimelineModel.forEachEvent = function(events, onStartEvent, onEndEvent, onInstantEvent)
{
var stack = [];
for (var i = 0; i < events.length; ++i) {
var e = events[i];
while (stack.length && stack.peekLast().endTime <= e.startTime)
onEndEvent(stack.pop());
if (e.duration) {
onStartEvent(e);
stack.push(e);
} else {
onInstantEvent && onInstantEvent(e, stack.peekLast() || null);
}
}
while (stack.length)
onEndEvent(stack.pop());
}
/**
* @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.TransferChunkLengthBytes = 5000000;
/**
* @constructor
* @param {string} name
*/
WebInspector.TimelineModel.VirtualThread = function(name)
{
this.name = name;
/** @type {!Array.<!WebInspector.TracingModel.Event>} */
this.events = [];
/** @type {!Array.<!Array.<!WebInspector.TracingModel.Event>>} */
this.asyncEvents = [];
}
/**
* @constructor
* @param {!WebInspector.TimelineModel} model
* @param {!WebInspector.TracingModel.Event} traceEvent
*/
WebInspector.TimelineModel.Record = function(model, traceEvent)
{
this._model = model;
this._event = traceEvent;
traceEvent._timelineRecord = this;
this._children = [];
}
/**
* @param {!WebInspector.TimelineModel.Record} a
* @param {!WebInspector.TimelineModel.Record} b
* @return {number}
*/
WebInspector.TimelineModel.Record._compareStartTime = function(a, b)
{
// Never return 0 as otherwise equal records would be merged.
return a.startTime() <= b.startTime() ? -1 : 1;
}
WebInspector.TimelineModel.Record.prototype = {
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
callSiteStackTrace: function()
{
var initiator = this._event.initiator;
return initiator ? initiator.stackTrace : null;
},
/**
* @return {?WebInspector.TimelineModel.Record}
*/
initiator: function()
{
var initiator = this._event.initiator;
return initiator ? initiator._timelineRecord : null;
},
/**
* @return {?WebInspector.Target}
*/
target: function()
{
return this._event.thread.target();
},
/**
* @return {number}
*/
selfTime: function()
{
return this._event.selfTime;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
children: function()
{
return this._children;
},
/**
* @return {number}
*/
startTime: function()
{
return this._event.startTime;
},
/**
* @return {string}
*/
thread: function()
{
if (this._event.thread.name() === "CrRendererMain")
return WebInspector.TimelineModel.MainThreadName;
return this._event.thread.name();
},
/**
* @return {number}
*/
endTime: function()
{
return this._endTime || this._event.endTime || this._event.startTime;
},
/**
* @param {number} endTime
*/
setEndTime: function(endTime)
{
this._endTime = endTime;
},
/**
* @return {!Object}
*/
data: function()
{
return this._event.args["data"];
},
/**
* @return {string}
*/
type: function()
{
if (this._event.category === WebInspector.TracingModel.ConsoleEventCategory)
return WebInspector.TimelineModel.RecordType.ConsoleTime;
return this._event.name;
},
/**
* @return {string}
*/
frameId: function()
{
switch (this._event.name) {
case WebInspector.TimelineModel.RecordType.RecalculateStyles:
case WebInspector.TimelineModel.RecordType.Layout:
return this._event.args["beginData"]["frame"];
default:
var data = this._event.args["data"];
return (data && data["frame"]) || "";
}
},
/**
* @return {?Array.<!ConsoleAgent.CallFrame>}
*/
stackTrace: function()
{
return this._event.stackTrace;
},
/**
* @param {string} key
* @return {?Object}
*/
getUserObject: function(key)
{
if (key === "TimelineUIUtils::preview-element")
return this._event.previewElement;
throw new Error("Unexpected key: " + key);
},
/**
* @param {string} key
* @param {?Object|undefined} value
*/
setUserObject: function(key, value)
{
if (key !== "TimelineUIUtils::preview-element")
throw new Error("Unexpected key: " + key);
this._event.previewElement = /** @type {?Element} */ (value);
},
/**
* @return {?Array.<string>}
*/
warnings: function()
{
if (this._event.warning)
return [this._event.warning];
return null;
},
/**
* @return {!WebInspector.TracingModel.Event}
*/
traceEvent: function()
{
return this._event;
},
/**
* @param {!WebInspector.TimelineModel.Record} child
*/
_addChild: function(child)
{
this._children.push(child);
child.parent = this;
},
/**
* @return {!WebInspector.TimelineModel}
*/
timelineModel: function()
{
return this._model;
}
}
WebInspector.TimelineModel.prototype = {
/**
* @param {boolean} captureCauses
* @param {boolean} enableJSSampling
* @param {boolean} captureMemory
* @param {boolean} capturePictures
*/
startRecording: function(captureCauses, enableJSSampling, captureMemory, capturePictures)
{
function disabledByDefault(category)
{
return "disabled-by-default-" + category;
}
var categoriesArray = [
"-*",
disabledByDefault("devtools.timeline"),
disabledByDefault("devtools.timeline.frame"),
WebInspector.TracingModel.ConsoleEventCategory
];
if (Runtime.experiments.isEnabled("timelineFlowEvents")) {
categoriesArray.push(disabledByDefault("toplevel.flow"),
disabledByDefault("ipc.flow"),
disabledByDefault("devtools.timeline.top-level-task"));
}
if (captureCauses || enableJSSampling)
categoriesArray.push(disabledByDefault("devtools.timeline.stack"));
if (captureCauses && Runtime.experiments.isEnabled("timelineInvalidationTracking"))
categoriesArray.push(disabledByDefault("devtools.timeline.invalidationTracking"));
if (capturePictures) {
categoriesArray = categoriesArray.concat([
disabledByDefault("devtools.timeline.layers"),
disabledByDefault("devtools.timeline.picture"),
disabledByDefault("blink.graphics_context_annotations")]);
}
var categories = categoriesArray.join(",");
this._startRecordingWithCategories(categories, enableJSSampling);
},
stopRecording: function()
{
this._allProfilesStoppedPromise = this._stopProfilingOnAllTargets();
if (this._targets[0])
this._targets[0].tracingManager.stop();
},
/**
* @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;
},
/**
* @return {?WebInspector.Target}
*/
target: function()
{
// FIXME: Consider returning null for loaded traces.
return this._targets[0];
},
/**
* @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
*/
setEventsForTest: function(events)
{
this._startCollectingTraceEvents(false);
this._tracingModel.addEvents(events);
this.tracingComplete();
},
/**
* @override
* @param {!WebInspector.Target} target
*/
targetAdded: function(target)
{
this._targets.push(target);
if (this._profiling)
this._startProfilingOnTarget(target);
},
/**
* @override
* @param {!WebInspector.Target} target
*/
targetRemoved: function(target)
{
this._targets.remove(target, true);
// FIXME: We'd like to stop profiling on the target and retrieve a profile
// but it's too late. Backend connection is closed.
},
/**
* @param {!WebInspector.Target} target
*/
_startProfilingOnTarget: function(target)
{
target.profilerAgent().start();
},
_startProfilingOnAllTargets: function()
{
var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000;
this._targets[0].profilerAgent().setSamplingInterval(intervalUs);
this._profiling = true;
for (var target of this._targets)
this._startProfilingOnTarget(target);
},
/**
* @param {!WebInspector.Target} target
* @return {!Promise}
*/
_stopProfilingOnTarget: function(target)
{
/**
* @param {?{profile: !ProfilerAgent.CPUProfile}} value
* @return {?ProfilerAgent.CPUProfile}
*/
function extractProfile(value)
{
return value && value.profile;
}
return target.profilerAgent().stop().then(extractProfile).then(this._addCpuProfile.bind(this, target.id()));
},
/**
* @return {!Promise}
*/
_stopProfilingOnAllTargets: function()
{
var targets = this._profiling ? this._targets : [];
this._profiling = false;
return Promise.all(targets.map(this._stopProfilingOnTarget, this));
},
/**
* @param {string} categories
* @param {boolean=} enableJSSampling
*/
_startRecordingWithCategories: function(categories, enableJSSampling)
{
if (!this._targets.length)
return;
if (enableJSSampling)
this._startProfilingOnAllTargets();
this._targets[0].tracingManager.start(this, categories, "");
},
/**
* @param {boolean} fromFile
*/
_startCollectingTraceEvents: function(fromFile)
{
this._tracingModel.reset();
this.reset();
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted, { fromFile: fromFile });
},
/**
* @override
*/
tracingStarted: function()
{
this._startCollectingTraceEvents(false);
},
/**
* @param {!Array.<!WebInspector.TracingManager.EventPayload>} events
* @override
*/
traceEventsCollected: function(events)
{
this._tracingModel.addEvents(events);
},
/**
* @override
*/
tracingComplete: function()
{
if (!this._allProfilesStoppedPromise) {
this._didStopRecordingTraceEvents();
return;
}
this._allProfilesStoppedPromise.then(this._didStopRecordingTraceEvents.bind(this));
this._allProfilesStoppedPromise = null;
},
/**
* @param {number} usage
* @override
*/
tracingBufferUsage: function(usage)
{
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.BufferUsage, usage);
},
/**
* @param {number} progress
* @override
*/
eventsRetrievalProgress: function(progress)
{
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RetrieveEventsProgress, progress);
},
/**
* @param {number} targetId
* @param {?ProfilerAgent.CPUProfile} cpuProfile
*/
_addCpuProfile: function(targetId, cpuProfile)
{
if (!cpuProfile)
return;
if (!this._cpuProfiles)
this._cpuProfiles = new Map();
this._cpuProfiles.set(targetId, cpuProfile);
},
_didStopRecordingTraceEvents: function()
{
this._injectCpuProfileEvents();
this._tracingModel.tracingComplete();
var metaEvents = this._tracingModel.devtoolsPageMetadataEvents();
var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents();
this._resetProcessingState();
for (var i = 0, length = metaEvents.length; i < length; i++) {
var metaEvent = metaEvents[i];
var process = metaEvent.thread.process();
var startTime = metaEvent.startTime;
var endTime = Infinity;
if (i + 1 < length)
endTime = metaEvents[i + 1].startTime;
this._currentPage = metaEvent.args["data"] && metaEvent.args["data"]["page"];
for (var thread of process.sortedThreads()) {
if (thread.name() === "WebCore: Worker" && !workerMetadataEvents.some(function(e) { return e.args["data"]["workerThreadId"] === thread.id(); }))
continue;
this._processThreadEvents(startTime, endTime, metaEvent.thread, thread);
}
}
this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
this._cpuProfiles = null;
this._buildTimelineRecords();
this._buildGPUTasks();
this._insertFirstPaintEvent();
this._resetProcessingState();
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
},
/**
* @param {number} pid
* @param {number} tid
* @param {?ProfilerAgent.CPUProfile} cpuProfile
*/
_injectCpuProfileEvent: function(pid, tid, cpuProfile)
{
if (!cpuProfile)
return;
var cpuProfileEvent = /** @type {!WebInspector.TracingManager.EventPayload} */ ({
cat: WebInspector.TracingModel.DevToolsMetadataEventCategory,
ph: WebInspector.TracingModel.Phase.Instant,
ts: this._tracingModel.maximumRecordTime() * 1000,
pid: pid,
tid: tid,
name: WebInspector.TimelineModel.RecordType.CpuProfile,
args: { data: { cpuProfile: cpuProfile } }
});
this._tracingModel.addEvents([cpuProfileEvent]);
},
_injectCpuProfileEvents: function()
{
if (!this._cpuProfiles)
return;
var mainMetaEvent = this._tracingModel.devtoolsPageMetadataEvents().peekLast();
var pid = mainMetaEvent.thread.process().id();
var mainTarget = this._targets[0];
var mainCpuProfile = this._cpuProfiles.get(mainTarget.id());
this._injectCpuProfileEvent(pid, mainMetaEvent.thread.id(), mainCpuProfile);
var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents();
for (var metaEvent of workerMetadataEvents) {
var workerId = metaEvent.args["data"]["workerId"];
var target = WebInspector.workerTargetManager.targetByWorkerId(workerId);
if (!target)
continue;
var cpuProfile = this._cpuProfiles.get(target.id());
this._injectCpuProfileEvent(pid, metaEvent.args["data"]["workerThreadId"], cpuProfile);
}
this._cpuProfiles = null;
},
_insertFirstPaintEvent: function()
{
if (!this._firstCompositeLayers)
return;
// First Paint is actually a DrawFrame that happened after first CompositeLayers following last CommitLoadEvent.
var recordTypes = WebInspector.TimelineModel.RecordType;
var i = insertionIndexForObjectInListSortedByFunction(this._firstCompositeLayers, this._inspectedTargetEvents, WebInspector.TracingModel.Event.compareStartTime);
for (; i < this._inspectedTargetEvents.length && this._inspectedTargetEvents[i].name !== recordTypes.DrawFrame; ++i) { }
if (i >= this._inspectedTargetEvents.length)
return;
var drawFrameEvent = this._inspectedTargetEvents[i];
var firstPaintEvent = new WebInspector.TracingModel.Event(drawFrameEvent.category, recordTypes.MarkFirstPaint, WebInspector.TracingModel.Phase.Instant, drawFrameEvent.startTime, drawFrameEvent.thread);
this._mainThreadEvents.splice(insertionIndexForObjectInListSortedByFunction(firstPaintEvent, this._mainThreadEvents, WebInspector.TracingModel.Event.compareStartTime), 0, firstPaintEvent);
var firstPaintRecord = new WebInspector.TimelineModel.Record(this, firstPaintEvent);
this._eventDividerRecords.splice(insertionIndexForObjectInListSortedByFunction(firstPaintRecord, this._eventDividerRecords, WebInspector.TimelineModel.Record._compareStartTime), 0, firstPaintRecord);
},
_buildTimelineRecords: function()
{
var topLevelRecords = this._buildTimelineRecordsForThread(this.mainThreadEvents());
/**
* @param {!WebInspector.TimelineModel.VirtualThread} virtualThread
* @this {!WebInspector.TimelineModel}
*/
function processVirtualThreadEvents(virtualThread)
{
var threadRecords = this._buildTimelineRecordsForThread(virtualThread.events);
topLevelRecords = topLevelRecords.mergeOrdered(threadRecords, WebInspector.TimelineModel.Record._compareStartTime);
}
this.virtualThreads().forEach(processVirtualThreadEvents.bind(this));
for (var i = 0; i < topLevelRecords.length; i++) {
var record = topLevelRecords[i];
if (record.type() === WebInspector.TimelineModel.RecordType.Program)
this._mainThreadTasks.push(record);
}
this._records = topLevelRecords;
},
_buildGPUTasks: function()
{
var gpuProcess = this._tracingModel.processByName("GPU Process");
if (!gpuProcess)
return;
var mainThread = gpuProcess.threadByName("CrGpuMain");
if (!mainThread)
return;
var events = mainThread.events();
var recordTypes = WebInspector.TimelineModel.RecordType;
for (var i = 0; i < events.length; ++i) {
if (events[i].name === recordTypes.GPUTask)
this._gpuTasks.push(new WebInspector.TimelineModel.Record(this, events[i]));
}
},
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} threadEvents
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
_buildTimelineRecordsForThread: function(threadEvents)
{
var recordStack = [];
var topLevelRecords = [];
for (var i = 0, size = threadEvents.length; i < size; ++i) {
var event = threadEvents[i];
for (var top = recordStack.peekLast(); top && top._event.endTime <= event.startTime; top = recordStack.peekLast()) {
recordStack.pop();
if (!recordStack.length)
topLevelRecords.push(top);
}
if (event.phase === WebInspector.TracingModel.Phase.AsyncEnd || event.phase === WebInspector.TracingModel.Phase.NestableAsyncEnd)
continue;
var parentRecord = recordStack.peekLast();
// Maintain the back-end logic of old timeline, skip console.time() / console.timeEnd() that are not properly nested.
if (WebInspector.TracingModel.isAsyncBeginPhase(event.phase) && parentRecord && event.endTime > parentRecord._event.endTime)
continue;
var record = new WebInspector.TimelineModel.Record(this, event);
if (WebInspector.TimelineUIUtils.isMarkerEvent(event))
this._eventDividerRecords.push(record);
if (!this._recordFilter.accept(record))
continue;
if (parentRecord)
parentRecord._addChild(record);
if (event.endTime)
recordStack.push(record);
}
if (recordStack.length)
topLevelRecords.push(recordStack[0]);
return topLevelRecords;
},
_resetProcessingState: function()
{
this._sendRequestEvents = {};
this._timerEvents = {};
this._requestAnimationFrameEvents = {};
this._invalidationTracker = new WebInspector.InvalidationTracker();
this._layoutInvalidate = {};
this._lastScheduleStyleRecalculation = {};
this._webSocketCreateEvents = {};
this._paintImageEventByPixelRefId = {};
this._lastPaintForLayer = {};
this._lastRecalculateStylesEvent = null;
this._currentScriptEvent = null;
this._eventStack = [];
this._hadCommitLoad = false;
this._firstCompositeLayers = null;
this._currentPage = null;
},
/**
* @param {number} startTime
* @param {?number} endTime
* @param {!WebInspector.TracingModel.Thread} mainThread
* @param {!WebInspector.TracingModel.Thread} thread
*/
_processThreadEvents: function(startTime, endTime, mainThread, thread)
{
var events = thread.events();
var asyncEvents = thread.asyncEvents();
var cpuProfileEvent = events.peekLast();
if (cpuProfileEvent && cpuProfileEvent.name === WebInspector.TimelineModel.RecordType.CpuProfile) {
var cpuProfile = cpuProfileEvent.args["data"]["cpuProfile"];
if (cpuProfile) {
var jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(cpuProfile, thread);
events = events.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime);
var jsFrameEvents = WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents(events);
events = jsFrameEvents.mergeOrdered(events, WebInspector.TracingModel.Event.orderedCompareStartTime);
}
}
var threadEvents;
var threadAsyncEvents;
if (thread === mainThread) {
threadEvents = this._mainThreadEvents;
threadAsyncEvents = this._mainThreadAsyncEvents;
} else {
var virtualThread = new WebInspector.TimelineModel.VirtualThread(thread.name());
this._virtualThreads.push(virtualThread);
threadEvents = virtualThread.events;
threadAsyncEvents = virtualThread.asyncEvents;
}
this._eventStack = [];
var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime });
var length = events.length;
for (; i < length; i++) {
var event = events[i];
if (endTime && event.startTime >= endTime)
break;
this._updateEventStack(event);
if (!this._processEvent(event))
continue;
threadEvents.push(event);
this._inspectedTargetEvents.push(event);
}
i = asyncEvents.lowerBound(startTime, function (time, event) { return time - event.startTime });
for (; i < asyncEvents.length; ++i) {
if (endTime && event.startTime >= endTime)
break;
var asyncEvent = asyncEvents[i];
if (this._processEvent(asyncEvent[0]))
threadAsyncEvents.push(asyncEvent);
}
},
/**
* @param {!WebInspector.TracingModel.Event} event
*/
_updateEventStack: function(event)
{
var eventStack = this._eventStack;
while (eventStack.length && eventStack.peekLast().endTime < event.startTime)
eventStack.pop();
var duration = event.duration;
if (duration) {
if (eventStack.length) {
var parent = eventStack.peekLast();
parent.selfTime -= duration;
}
event.selfTime = duration;
eventStack.push(event);
}
},
/**
* @param {!WebInspector.TracingModel.Event} event
* @return {boolean}
*/
_processEvent: function(event)
{
var recordTypes = WebInspector.TimelineModel.RecordType;
if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime)
this._currentScriptEvent = null;
var eventData = event.args["data"] || event.args["beginData"] || {};
if (eventData && eventData["stackTrace"])
event.stackTrace = eventData["stackTrace"];
switch (event.name) {
case recordTypes.ResourceSendRequest:
this._sendRequestEvents[event.args["data"]["requestId"]] = event;
event.url = event.args["data"]["url"];
break;
case recordTypes.ResourceReceiveResponse:
case recordTypes.ResourceReceivedData:
case recordTypes.ResourceFinish:
event.initiator = this._sendRequestEvents[event.args["data"]["requestId"]];
if (event.initiator)
event.url = event.initiator.url;
break;
case recordTypes.TimerInstall:
this._timerEvents[event.args["data"]["timerId"]] = event;
break;
case recordTypes.TimerFire:
event.initiator = this._timerEvents[event.args["data"]["timerId"]];
break;
case recordTypes.RequestAnimationFrame:
this._requestAnimationFrameEvents[event.args["data"]["id"]] = event;
break;
case recordTypes.FireAnimationFrame:
event.initiator = this._requestAnimationFrameEvents[event.args["data"]["id"]];
break;
case recordTypes.ScheduleStyleRecalculation:
this._lastScheduleStyleRecalculation[event.args["data"]["frame"]] = event;
break;
case recordTypes.RecalculateStyles:
this._invalidationTracker.didRecalcStyle(event);
if (event.args["beginData"])
event.initiator = this._lastScheduleStyleRecalculation[event.args["beginData"]["frame"]];
this._lastRecalculateStylesEvent = event;
break;
case recordTypes.ScheduleStyleInvalidationTracking:
case recordTypes.StyleRecalcInvalidationTracking:
case recordTypes.StyleInvalidatorInvalidationTracking:
case recordTypes.LayoutInvalidationTracking:
case recordTypes.LayerInvalidationTracking:
case recordTypes.PaintInvalidationTracking:
this._invalidationTracker.addInvalidation(new WebInspector.InvalidationTrackingEvent(event));
break;
case recordTypes.InvalidateLayout:
// Consider style recalculation as a reason for layout invalidation,
// but only if we had no earlier layout invalidation records.
var layoutInitator = event;
var frameId = event.args["data"]["frame"];
if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime > event.startTime)
layoutInitator = this._lastRecalculateStylesEvent.initiator;
this._layoutInvalidate[frameId] = layoutInitator;
break;
case recordTypes.Layout:
this._invalidationTracker.didLayout(event);
var frameId = event.args["beginData"]["frame"];
event.initiator = this._layoutInvalidate[frameId];
// In case we have no closing Layout event, endData is not available.
if (event.args["endData"]) {
event.backendNodeId = event.args["endData"]["rootNode"];
event.highlightQuad = event.args["endData"]["root"];
}
this._layoutInvalidate[frameId] = null;
if (this._currentScriptEvent)
event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.");
break;
case recordTypes.WebSocketCreate:
this._webSocketCreateEvents[event.args["data"]["identifier"]] = event;
break;
case recordTypes.WebSocketSendHandshakeRequest:
case recordTypes.WebSocketReceiveHandshakeResponse:
case recordTypes.WebSocketDestroy:
event.initiator = this._webSocketCreateEvents[event.args["data"]["identifier"]];
break;
case recordTypes.EvaluateScript:
case recordTypes.FunctionCall:
if (!this._currentScriptEvent)
this._currentScriptEvent = event;
break;
case recordTypes.SetLayerTreeId:
this._inspectedTargetLayerTreeId = event.args["layerTreeId"] || event.args["data"]["layerTreeId"];
break;
case recordTypes.Paint:
this._invalidationTracker.didPaint(event);
event.highlightQuad = event.args["data"]["clip"];
event.backendNodeId = event.args["data"]["nodeId"];
var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
break;
// Only keep layer paint events, skip paints for subframes that get painted to the same layer as parent.
if (!event.args["data"]["layerId"])
break;
this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event;
break;
case recordTypes.PictureSnapshot:
var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
break;
var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
if (paintEvent)
paintEvent.picture = event;
break;
case recordTypes.ScrollLayer:
event.backendNodeId = event.args["data"]["nodeId"];
break;
case recordTypes.PaintImage:
event.backendNodeId = event.args["data"]["nodeId"];
event.url = event.args["data"]["url"];
break;
case recordTypes.DecodeImage:
case recordTypes.ResizeImage:
var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
if (!paintImageEvent) {
var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
}
if (!paintImageEvent)
break;
event.backendNodeId = paintImageEvent.backendNodeId;
event.url = paintImageEvent.url;
break;
case recordTypes.DrawLazyPixelRef:
var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
if (!paintImageEvent)
break;
this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent;
event.backendNodeId = paintImageEvent.backendNodeId;
event.url = paintImageEvent.url;
break;
case recordTypes.MarkDOMContent:
case recordTypes.MarkLoad:
var page = eventData["page"];
if (page && page !== this._currentPage)
return false;
break;
case recordTypes.CommitLoad:
var page = eventData["page"];
if (page && page !== this._currentPage)
return false;
if (!eventData["isMainFrame"])
break;
this._hadCommitLoad = true;
this._firstCompositeLayers = null;
break;
case recordTypes.CompositeLayers:
if (!this._firstCompositeLayers && this._hadCommitLoad)
this._firstCompositeLayers = event;
break;
case recordTypes.Animation:
// FIXME: bring back Animation events as we figure out a way to show them while not cluttering the UI.
return false;
}
return true;
},
/**
* @param {string} name
* @return {?WebInspector.TracingModel.Event}
*/
_findAncestorEvent: function(name)
{
for (var i = this._eventStack.length - 1; i >= 0; --i) {
var event = this._eventStack[i];
if (event.name === name)
return event;
}
return null;
},
/**
* @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);
},
_createFileReader: function(file, delegate)
{
return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.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));
},
reset: function()
{
this._virtualThreads = [];
/** @type {!Array.<!WebInspector.TracingModel.Event>} */
this._mainThreadEvents = [];
/** @type {!Array.<!Array.<!WebInspector.TracingModel.Event>>} */
this._mainThreadAsyncEvents = [];
/** @type {!Array.<!WebInspector.TracingModel.Event>} */
this._inspectedTargetEvents = [];
this._records = [];
/** @type {!Array.<!WebInspector.TimelineModel.Record>} */
this._mainThreadTasks = [];
/** @type {!Array.<!WebInspector.TimelineModel.Record>} */
this._gpuTasks = [];
/** @type {!Array.<!WebInspector.TimelineModel.Record>} */
this._eventDividerRecords = [];
this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordsCleared);
},
/**
* @return {number}
*/
minimumRecordTime: function()
{
return this._tracingModel.minimumRecordTime();
},
/**
* @return {number}
*/
maximumRecordTime: function()
{
return this._tracingModel.maximumRecordTime();
},
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
inspectedTargetEvents: function()
{
return this._inspectedTargetEvents;
},
/**
* @return {!Array.<!WebInspector.TracingModel.Event>}
*/
mainThreadEvents: function()
{
return this._mainThreadEvents;
},
/**
* @param {!Array.<!WebInspector.TracingModel.Event>} events
*/
_setMainThreadEvents: function(events)
{
this._mainThreadEvents = events;
},
/**
* @return {!Array.<!Array.<!WebInspector.TracingModel.Event>>}
*/
mainThreadAsyncEvents: function()
{
return this._mainThreadAsyncEvents;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.VirtualThread>}
*/
virtualThreads: function()
{
return this._virtualThreads;
},
/**
* @param {!WebInspector.ChunkedFileReader} fileReader
* @param {!WebInspector.Progress} progress
* @return {!WebInspector.OutputStream}
*/
createLoader: function(fileReader, progress)
{
return new WebInspector.TracingModelLoader(this, fileReader, progress);
},
/**
* @param {!WebInspector.OutputStream} stream
*/
writeToStream: function(stream)
{
var saver = new WebInspector.TracingTimelineSaver(stream);
this._tracingModel.writeToStream(stream, saver);
},
/**
* @return {boolean}
*/
isEmpty: function()
{
return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
mainThreadTasks: function()
{
return this._mainThreadTasks;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
gpuTasks: function()
{
return this._gpuTasks;
},
/**
* @return {!Array.<!WebInspector.TimelineModel.Record>}
*/
eventDividerRecords: function()
{
return this._eventDividerRecords;
},
__proto__: WebInspector.Object.prototype
}
/**
* @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 = {
/**
* @override
* @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 = {
/**
* @override
* @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 = {
/**
* @override
* @param {!WebInspector.TimelineModel.Record} record
* @return {boolean}
*/
accept: function(record)
{
return !!this._recordTypes[record.type()];
},
__proto__: WebInspector.TimelineRecordTypeFilter.prototype
}
/**
* @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 = {
/**
* @override
*/
onTransferStarted: function()
{
this._progress.setTitle(WebInspector.UIString("Loading\u2026"));
},
/**
* @override
* @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());
}
},
/**
* @override
*/
onTransferFinished: function()
{
this._progress.done();
},
/**
* @override
* @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(WebInspecto