UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

1,536 lines (1,368 loc) 68.5 kB
/* * 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