Index: Source/devtools/front_end/timeline/TimelineModel.js |
diff --git a/Source/devtools/front_end/timeline/TimelineModel.js b/Source/devtools/front_end/timeline/TimelineModel.js |
index 579555069137dd262a28c3e4c90ee55ab9c3a7b9..43262572d90ce396e26d9f5c22137af0739f4745 100644 |
--- a/Source/devtools/front_end/timeline/TimelineModel.js |
+++ b/Source/devtools/front_end/timeline/TimelineModel.js |
@@ -30,16 +30,25 @@ |
/** |
* @constructor |
+ * @param {!WebInspector.TracingManager} tracingManager |
+ * @param {!WebInspector.TracingModel} tracingModel |
+ * @param {!WebInspector.TimelineModel.Filter} recordFilter |
* @extends {WebInspector.Object} |
*/ |
-WebInspector.TimelineModel = function() |
+WebInspector.TimelineModel = function(tracingManager, tracingModel, recordFilter) |
{ |
WebInspector.Object.call(this); |
this._filters = []; |
+ this._tracingManager = tracingManager; |
+ this._tracingModel = tracingModel; |
+ this._recordFilter = recordFilter; |
+ this._tracingManager.addEventListener(WebInspector.TracingManager.Events.TracingStarted, this._onTracingStarted, this); |
+ this._tracingManager.addEventListener(WebInspector.TracingManager.Events.EventsCollected, this._onEventsCollected, this); |
+ this._tracingManager.addEventListener(WebInspector.TracingManager.Events.TracingComplete, this._onTracingComplete, this); |
+ this.reset(); |
} |
WebInspector.TimelineModel.RecordType = { |
- Root: "Root", |
Program: "Program", |
EventDispatch: "EventDispatch", |
@@ -47,21 +56,28 @@ WebInspector.TimelineModel.RecordType = { |
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", |
- DecodeImage: "DecodeImage", |
- ResizeImage: "ResizeImage", |
CompositeLayers: "CompositeLayers", |
+ StyleRecalcInvalidationTracking: "StyleRecalcInvalidationTracking", |
+ LayoutInvalidationTracking: "LayoutInvalidationTracking", |
+ LayerInvalidationTracking: "LayerInvalidationTracking", |
+ PaintInvalidationTracking: "PaintInvalidationTracking", |
+ |
ParseHTML: "ParseHTML", |
TimerInstall: "TimerInstall", |
@@ -86,6 +102,8 @@ WebInspector.TimelineModel.RecordType = { |
FunctionCall: "FunctionCall", |
GCEvent: "GCEvent", |
+ JSFrame: "JSFrame", |
+ JSSample: "JSSample", |
UpdateCounters: "UpdateCounters", |
@@ -99,6 +117,24 @@ WebInspector.TimelineModel.RecordType = { |
WebSocketDestroy : "WebSocketDestroy", |
EmbedderCallback : "EmbedderCallback", |
+ |
+ CallStack: "CallStack", |
+ 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 = { |
@@ -141,18 +177,258 @@ WebInspector.TimelineModel.forAllRecords = function(recordsArray, preOrderCallba |
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 = []; |
+} |
+ |
+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.ScheduleStyleRecalculation: |
+ case WebInspector.TimelineModel.RecordType.RecalculateStyles: |
+ case WebInspector.TimelineModel.RecordType.InvalidateLayout: |
+ return this._event.args["frameId"]; |
+ case WebInspector.TimelineModel.RecordType.Layout: |
+ return this._event.args["beginData"]["frameId"]; |
+ 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, captureMemory, 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 (captureCauses || enableJSSampling) |
+ categoriesArray.push(disabledByDefault("devtools.timeline.stack")); |
+ if (enableJSSampling) { |
+ this._jsProfilerStarted = true; |
+ this._currentTarget = WebInspector.context.flavor(WebInspector.Target); |
+ this._configureCpuProfilerSamplingInterval(); |
+ this._currentTarget.profilerAgent().start(); |
+ } |
+ 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); |
}, |
stopRecording: function() |
{ |
+ if (this._jsProfilerStarted) { |
+ this._stopCallbackBarrier = new CallbackBarrier(); |
+ this._currentTarget.profilerAgent().stop(this._stopCallbackBarrier.createCallback(this._didStopRecordingJSSamples.bind(this))); |
+ this._jsProfilerStarted = false; |
+ } |
+ this._tracingManager.stop(); |
}, |
/** |
@@ -230,218 +506,640 @@ WebInspector.TimelineModel.prototype = { |
}, |
/** |
- * @param {!Blob} file |
- * @param {!WebInspector.Progress} progress |
+ * @param {!Array.<!WebInspector.TracingManager.EventPayload>} events |
*/ |
- loadFromFile: function(file, progress) |
+ setEventsForTest: function(events) |
{ |
- var delegate = new WebInspector.TimelineModelLoadFromFileDelegate(this, progress); |
- var fileReader = this._createFileReader(file, delegate); |
- var loader = this.createLoader(fileReader, progress); |
- fileReader.start(loader); |
+ this._startCollectingTraceEvents(false); |
+ this._tracingModel.addEvents(events); |
+ this._onTracingComplete(); |
}, |
- /** |
- * @param {!WebInspector.ChunkedFileReader} fileReader |
- * @param {!WebInspector.Progress} progress |
- * @return {!WebInspector.OutputStream} |
- */ |
- createLoader: function(fileReader, progress) |
+ _configureCpuProfilerSamplingInterval: function() |
{ |
- throw new Error("Not implemented."); |
+ var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000; |
+ this._currentTarget.profilerAgent().setSamplingInterval(intervalUs, didChangeInterval); |
+ |
+ function didChangeInterval(error) |
+ { |
+ if (error) |
+ WebInspector.console.error(error); |
+ } |
}, |
- _createFileReader: function(file, delegate) |
+ /** |
+ * @param {string} categories |
+ */ |
+ _startRecordingWithCategories: function(categories) |
{ |
- return new WebInspector.ChunkedFileReader(file, WebInspector.TimelineModel.TransferChunkLengthBytes, delegate); |
+ this._tracingManager.start(categories, ""); |
}, |
- _createFileWriter: function() |
+ _onTracingStarted: function() |
{ |
- return new WebInspector.FileOutputStream(); |
+ this._startCollectingTraceEvents(false); |
}, |
- saveToFile: function() |
+ /** |
+ * @param {boolean} fromFile |
+ */ |
+ _startCollectingTraceEvents: function(fromFile) |
{ |
- 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)); |
+ this.reset(); |
+ this._tracingModel.reset(); |
+ this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted, { fromFile: fromFile }); |
}, |
/** |
- * @param {!WebInspector.OutputStream} stream |
+ * @param {!WebInspector.Event} event |
*/ |
- writeToStream: function(stream) |
+ _onEventsCollected: function(event) |
{ |
- throw new Error("Not implemented."); |
+ var traceEvents = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */ (event.data); |
+ this._tracingModel.addEvents(traceEvents); |
}, |
- reset: function() |
+ _onTracingComplete: 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); |
+ if (this._stopCallbackBarrier) |
+ this._stopCallbackBarrier.callWhenDone(this._didStopRecordingTraceEvents.bind(this)); |
+ else |
+ this._didStopRecordingTraceEvents(); |
}, |
/** |
- * @return {number} |
+ * @param {?Protocol.Error} error |
+ * @param {?ProfilerAgent.CPUProfile} cpuProfile |
*/ |
- minimumRecordTime: function() |
+ _didStopRecordingJSSamples: function(error, cpuProfile) |
{ |
- throw new Error("Not implemented."); |
+ if (error) |
+ WebInspector.console.error(error); |
+ this._recordedCpuProfile = cpuProfile; |
}, |
- /** |
- * @return {number} |
- */ |
- maximumRecordTime: function() |
+ _didStopRecordingTraceEvents: function() |
{ |
- throw new Error("Not implemented."); |
+ this._stopCallbackBarrier = null; |
+ |
+ if (this._recordedCpuProfile) { |
+ this._injectCpuProfileEvent(this._recordedCpuProfile); |
+ this._recordedCpuProfile = null; |
+ } |
+ this._tracingModel.tracingComplete(); |
+ |
+ var events = this._tracingModel.devtoolsPageMetadataEvents(); |
+ var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents(); |
+ |
+ this._resetProcessingState(); |
+ for (var i = 0, length = events.length; i < length; i++) { |
+ var event = events[i]; |
+ var process = event.thread.process(); |
+ var startTime = event.startTime; |
+ |
+ var endTime = Infinity; |
+ if (i + 1 < length) |
+ endTime = events[i + 1].startTime; |
+ |
+ var threads = process.sortedThreads(); |
+ for (var j = 0; j < threads.length; j++) { |
+ var thread = threads[j]; |
+ if (thread.name() === "WebCore: Worker" && !workerMetadataEvents.some(function(e) { return e.args["data"]["workerThreadId"] === thread.id(); })) |
+ continue; |
+ this._processThreadEvents(startTime, endTime, event.thread, thread); |
+ } |
+ } |
+ this._resetProcessingState(); |
+ |
+ this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime); |
+ |
+ if (this._cpuProfile) { |
+ this._processCpuProfile(this._cpuProfile); |
+ this._cpuProfile = null; |
+ } |
+ this._buildTimelineRecords(); |
+ this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped); |
}, |
/** |
- * @return {boolean} |
+ * @param {!ProfilerAgent.CPUProfile} cpuProfile |
*/ |
- isEmpty: function() |
+ _injectCpuProfileEvent: function(cpuProfile) |
{ |
- return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0; |
+ var metaEvent = this._tracingModel.devtoolsPageMetadataEvents().peekLast(); |
+ if (!metaEvent) |
+ return; |
+ var cpuProfileEvent = /** @type {!WebInspector.TracingManager.EventPayload} */ ({ |
+ cat: WebInspector.TracingModel.DevToolsMetadataEventCategory, |
+ ph: WebInspector.TracingModel.Phase.Instant, |
+ ts: this._tracingModel.maximumRecordTime() * 1000, |
+ pid: metaEvent.thread.process().id(), |
+ tid: metaEvent.thread.id(), |
+ name: WebInspector.TimelineModel.RecordType.CpuProfile, |
+ args: { data: { cpuProfile: cpuProfile } } |
+ }); |
+ this._tracingModel.addEvents([cpuProfileEvent]); |
}, |
/** |
- * @return {!Array.<!WebInspector.TimelineModel.Record>} |
+ * @param {!ProfilerAgent.CPUProfile} cpuProfile |
*/ |
- mainThreadTasks: function() |
+ _processCpuProfile: function(cpuProfile) |
{ |
- return this._mainThreadTasks; |
+ var jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(this, cpuProfile); |
+ this._inspectedTargetEvents = this._inspectedTargetEvents.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime); |
+ this._setMainThreadEvents(this.mainThreadEvents().mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime)); |
+ var jsFrameEvents = WebInspector.TimelineJSProfileProcessor.generateJSFrameEvents(this.mainThreadEvents()); |
+ this._setMainThreadEvents(jsFrameEvents.mergeOrdered(this.mainThreadEvents(), WebInspector.TracingModel.Event.orderedCompareStartTime)); |
+ this._inspectedTargetEvents = jsFrameEvents.mergeOrdered(this._inspectedTargetEvents, WebInspector.TracingModel.Event.orderedCompareStartTime); |
}, |
- /** |
- * @return {!Array.<!WebInspector.TimelineModel.Record>} |
- */ |
- gpuThreadTasks: function() |
+ _buildTimelineRecords: function() |
{ |
- return this._gpuThreadTasks; |
+ var topLevelRecords = this._buildTimelineRecordsForThread(this.mainThreadEvents()); |
+ |
+ /** |
+ * @param {!WebInspector.TimelineModel.Record} a |
+ * @param {!WebInspector.TimelineModel.Record} b |
+ * @return {number} |
+ */ |
+ function compareRecordStartTime(a, b) |
+ { |
+ // Never return 0 as otherwise equal records would be merged. |
+ return (a.startTime() <= b.startTime()) ? -1 : +1; |
+ } |
+ |
+ /** |
+ * @param {!WebInspector.TimelineModel.VirtualThread} virtualThread |
+ * @this {!WebInspector.TimelineModel} |
+ */ |
+ function processVirtualThreadEvents(virtualThread) |
+ { |
+ var threadRecords = this._buildTimelineRecordsForThread(virtualThread.events); |
+ topLevelRecords = topLevelRecords.mergeOrdered(threadRecords, compareRecordStartTime); |
+ } |
+ 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); |
+ if (record.type() === WebInspector.TimelineModel.RecordType.GPUTask) |
+ this._gpuThreadTasks.push(record); |
+ } |
+ this._records = topLevelRecords; |
}, |
/** |
+ * @param {!Array.<!WebInspector.TracingModel.Event>} threadEvents |
* @return {!Array.<!WebInspector.TimelineModel.Record>} |
*/ |
- eventDividerRecords: function() |
+ _buildTimelineRecordsForThread: function(threadEvents) |
{ |
- return this._eventDividerRecords; |
- }, |
+ 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); |
+ } |
- __proto__: WebInspector.Object.prototype |
-} |
+ if (recordStack.length) |
+ topLevelRecords.push(recordStack[0]); |
-/** |
- * @interface |
- */ |
-WebInspector.TimelineModel.Record = function() |
-{ |
-} |
+ return topLevelRecords; |
+ }, |
-WebInspector.TimelineModel.Record.prototype = { |
- /** |
- * @return {?Array.<!ConsoleAgent.CallFrame>} |
- */ |
- callSiteStackTrace: function() { }, |
+ _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 = []; |
+ }, |
/** |
- * @return {?WebInspector.TimelineModel.Record} |
+ * @param {number} startTime |
+ * @param {?number} endTime |
+ * @param {!WebInspector.TracingModel.Thread} mainThread |
+ * @param {!WebInspector.TracingModel.Thread} thread |
*/ |
- initiator: function() { }, |
+ _processThreadEvents: function(startTime, endTime, mainThread, thread) |
+ { |
+ var events = thread.events(); |
+ var length = events.length; |
+ var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime }); |
+ |
+ var threadEvents; |
+ if (thread === mainThread) { |
+ threadEvents = this._mainThreadEvents; |
+ this._mainThreadAsyncEvents = this._mainThreadAsyncEvents.concat(thread.asyncEvents()); |
+ } else { |
+ var virtualThread = new WebInspector.TimelineModel.VirtualThread(thread.name()); |
+ threadEvents = virtualThread.events; |
+ virtualThread.asyncEvents = virtualThread.asyncEvents.concat(thread.asyncEvents()); |
+ this._virtualThreads.push(virtualThread); |
+ } |
+ |
+ this._eventStack = []; |
+ for (; i < length; i++) { |
+ var event = events[i]; |
+ if (endTime && event.startTime >= endTime) |
+ break; |
+ this._processEvent(event); |
+ threadEvents.push(event); |
+ this._inspectedTargetEvents.push(event); |
+ } |
+ }, |
/** |
- * @return {?WebInspector.Target} |
+ * @param {!WebInspector.TracingModel.Event} event |
*/ |
- target: function() { }, |
+ _processEvent: function(event) |
+ { |
+ var recordTypes = WebInspector.TimelineModel.RecordType; |
+ |
+ 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); |
+ } |
+ |
+ if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime) |
+ this._currentScriptEvent = null; |
+ |
+ switch (event.name) { |
+ case recordTypes.CallStack: |
+ var lastMainThreadEvent = this.mainThreadEvents().peekLast(); |
+ if (lastMainThreadEvent && event.args["stack"] && event.args["stack"].length) |
+ lastMainThreadEvent.stackTrace = event.args["stack"]; |
+ break; |
+ |
+ case recordTypes.CpuProfile: |
+ this._cpuProfile = event.args["data"]["cpuProfile"]; |
+ break; |
+ |
+ case recordTypes.ResourceSendRequest: |
+ this._sendRequestEvents[event.args["data"]["requestId"]] = event; |
+ event.imageURL = 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.imageURL = event.initiator.imageURL; |
+ 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["frame"]] = event; |
+ break; |
+ |
+ case recordTypes.RecalculateStyles: |
+ this._invalidationTracker.didRecalcStyle(event); |
+ event.initiator = this._lastScheduleStyleRecalculation[event.args["frame"]]; |
+ this._lastRecalculateStylesEvent = event; |
+ break; |
+ |
+ case recordTypes.StyleRecalcInvalidationTracking: |
+ case recordTypes.LayoutInvalidationTracking: |
+ case recordTypes.LayerInvalidationTracking: |
+ case recordTypes.PaintInvalidationTracking: |
+ this._invalidationTracker.addInvalidation(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["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"]; |
+ 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.imageURL = 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.imageURL = paintImageEvent.imageURL; |
+ break; |
+ |
+ case recordTypes.DrawLazyPixelRef: |
+ var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage); |
+ if (!paintImageEvent) |
+ break; |
+ this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent; |
+ event.backendNodeId = paintImageEvent.backendNodeId; |
+ event.imageURL = paintImageEvent.imageURL; |
+ break; |
+ } |
+ }, |
/** |
- * @return {number} |
+ * @param {string} name |
+ * @return {?WebInspector.TracingModel.Event} |
*/ |
- selfTime: function() { }, |
+ _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; |
+ }, |
/** |
- * @return {!Array.<!WebInspector.TimelineModel.Record>} |
+ * @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 = []; |
+ this._mainThreadEvents = []; |
+ this._mainThreadAsyncEvents = []; |
+ this._inspectedTargetEvents = []; |
+ |
+ this._records = []; |
+ /** @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} |
*/ |
- children: function() { }, |
+ minimumRecordTime: function() |
+ { |
+ return this._tracingModel.minimumRecordTime(); |
+ }, |
/** |
* @return {number} |
*/ |
- startTime: function() { }, |
+ maximumRecordTime: function() |
+ { |
+ return this._tracingModel.maximumRecordTime(); |
+ }, |
/** |
- * @return {string} |
+ * @return {!Array.<!WebInspector.TracingModel.Event>} |
*/ |
- thread: function() { }, |
+ inspectedTargetEvents: function() |
+ { |
+ return this._inspectedTargetEvents; |
+ }, |
/** |
- * @return {number} |
+ * @return {!Array.<!WebInspector.TracingModel.Event>} |
*/ |
- endTime: function() { }, |
+ mainThreadEvents: function() |
+ { |
+ return this._mainThreadEvents; |
+ }, |
/** |
- * @param {number} endTime |
+ * @param {!Array.<!WebInspector.TracingModel.Event>} events |
*/ |
- setEndTime: function(endTime) { }, |
+ _setMainThreadEvents: function(events) |
+ { |
+ this._mainThreadEvents = events; |
+ }, |
/** |
- * @return {!Object} |
+ * @return {!Array.<!Array.<!WebInspector.TracingModel.Event>>} |
*/ |
- data: function() { }, |
+ mainThreadAsyncEvents: function() |
+ { |
+ return this._mainThreadAsyncEvents; |
+ }, |
/** |
- * @return {string} |
+ * @return {!Array.<!WebInspector.TimelineModel.VirtualThread>} |
*/ |
- type: function() { }, |
+ virtualThreads: function() |
+ { |
+ return this._virtualThreads; |
+ }, |
/** |
- * @return {string} |
+ * @param {!WebInspector.ChunkedFileReader} fileReader |
+ * @param {!WebInspector.Progress} progress |
+ * @return {!WebInspector.OutputStream} |
*/ |
- frameId: function() { }, |
+ createLoader: function(fileReader, progress) |
+ { |
+ return new WebInspector.TracingModelLoader(this, fileReader, progress); |
+ }, |
/** |
- * @return {?Array.<!ConsoleAgent.CallFrame>} |
+ * @param {!WebInspector.OutputStream} stream |
*/ |
- stackTrace: function() { }, |
+ writeToStream: function(stream) |
+ { |
+ var saver = new WebInspector.TracingTimelineSaver(stream); |
+ this._tracingModel.writeToStream(stream, saver); |
+ }, |
/** |
- * @param {string} key |
- * @return {?Object} |
+ * @return {boolean} |
*/ |
- getUserObject: function(key) { }, |
+ isEmpty: function() |
+ { |
+ return this.minimumRecordTime() === 0 && this.maximumRecordTime() === 0; |
+ }, |
/** |
- * @param {string} key |
- * @param {?Object|undefined} value |
+ * @return {!Array.<!WebInspector.TimelineModel.Record>} |
*/ |
- setUserObject: function(key, value) { }, |
+ mainThreadTasks: function() |
+ { |
+ return this._mainThreadTasks; |
+ }, |
/** |
- * @return {?Array.<string>} |
+ * @return {!Array.<!WebInspector.TimelineModel.Record>} |
+ */ |
+ gpuThreadTasks: function() |
+ { |
+ return this._gpuThreadTasks; |
+ }, |
+ |
+ /** |
+ * @return {!Array.<!WebInspector.TimelineModel.Record>} |
*/ |
- warnings: function() { } |
+ eventDividerRecords: function() |
+ { |
+ return this._eventDividerRecords; |
+ }, |
+ |
+ |
+ __proto__: WebInspector.Object.prototype |
} |
/** |
@@ -617,3 +1315,365 @@ WebInspector.TimelineModelLoadFromFileDelegate.prototype = { |
} |
} |
} |
+ |
+ |
+/** |
+ * @interface |
+ */ |
+WebInspector.TraceEventFilter = function() { } |
+ |
+WebInspector.TraceEventFilter.prototype = { |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} event |
+ * @return {boolean} |
+ */ |
+ accept: function(event) { } |
+} |
+ |
+/** |
+ * @constructor |
+ * @implements {WebInspector.TraceEventFilter} |
+ * @param {!Array.<string>} eventNames |
+ */ |
+WebInspector.TraceEventNameFilter = function(eventNames) |
+{ |
+ this._eventNames = eventNames.keySet(); |
+} |
+ |
+WebInspector.TraceEventNameFilter.prototype = { |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} event |
+ * @return {boolean} |
+ */ |
+ accept: function(event) |
+ { |
+ throw new Error("Not implemented."); |
+ } |
+} |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.TraceEventNameFilter} |
+ * @param {!Array.<string>} includeNames |
+ */ |
+WebInspector.InclusiveTraceEventNameFilter = function(includeNames) |
+{ |
+ WebInspector.TraceEventNameFilter.call(this, includeNames) |
+} |
+ |
+WebInspector.InclusiveTraceEventNameFilter.prototype = { |
+ /** |
+ * @override |
+ * @param {!WebInspector.TracingModel.Event} event |
+ * @return {boolean} |
+ */ |
+ accept: function(event) |
+ { |
+ return event.category === WebInspector.TracingModel.ConsoleEventCategory || !!this._eventNames[event.name]; |
+ }, |
+ __proto__: WebInspector.TraceEventNameFilter.prototype |
+} |
+ |
+/** |
+ * @constructor |
+ * @extends {WebInspector.TraceEventNameFilter} |
+ * @param {!Array.<string>} excludeNames |
+ */ |
+WebInspector.ExclusiveTraceEventNameFilter = function(excludeNames) |
+{ |
+ WebInspector.TraceEventNameFilter.call(this, excludeNames) |
+} |
+ |
+WebInspector.ExclusiveTraceEventNameFilter.prototype = { |
+ /** |
+ * @override |
+ * @param {!WebInspector.TracingModel.Event} event |
+ * @return {boolean} |
+ */ |
+ accept: function(event) |
+ { |
+ return !this._eventNames[event.name]; |
+ }, |
+ __proto__: WebInspector.TraceEventNameFilter.prototype |
+} |
+ |
+/** |
+ * @constructor |
+ * @implements {WebInspector.OutputStream} |
+ * @param {!WebInspector.TimelineModel} model |
+ * @param {!{cancel: function()}} reader |
+ * @param {!WebInspector.Progress} progress |
+ */ |
+WebInspector.TracingModelLoader = function(model, reader, progress) |
+{ |
+ this._model = model; |
+ this._reader = reader; |
+ this._progress = progress; |
+ this._buffer = ""; |
+ this._firstChunk = true; |
+ this._loader = new WebInspector.TracingModel.Loader(model._tracingModel); |
+} |
+ |
+WebInspector.TracingModelLoader.prototype = { |
+ /** |
+ * @param {string} chunk |
+ */ |
+ write: function(chunk) |
+ { |
+ var data = this._buffer + chunk; |
+ var lastIndex = 0; |
+ var index; |
+ do { |
+ index = lastIndex; |
+ lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index); |
+ } while (lastIndex !== -1) |
+ |
+ var json = data.slice(0, index) + "]"; |
+ this._buffer = data.slice(index); |
+ |
+ if (!index) |
+ return; |
+ |
+ if (this._firstChunk) { |
+ this._model._startCollectingTraceEvents(true); |
+ } else { |
+ var commaIndex = json.indexOf(","); |
+ if (commaIndex !== -1) |
+ json = json.slice(commaIndex + 1); |
+ json = "[" + json; |
+ } |
+ |
+ var items; |
+ try { |
+ items = /** @type {!Array.<!WebInspector.TracingManager.EventPayload>} */ (JSON.parse(json)); |
+ } catch (e) { |
+ this._reportErrorAndCancelLoading("Malformed timeline data: " + e); |
+ return; |
+ } |
+ |
+ if (this._firstChunk) { |
+ this._firstChunk = false; |
+ if (this._looksLikeAppVersion(items[0])) { |
+ this._reportErrorAndCancelLoading("Old Timeline format is not supported."); |
+ return; |
+ } |
+ } |
+ |
+ try { |
+ this._loader.loadNextChunk(items); |
+ } catch(e) { |
+ this._reportErrorAndCancelLoading("Malformed timeline data: " + e); |
+ return; |
+ } |
+ }, |
+ |
+ _reportErrorAndCancelLoading: function(messsage) |
+ { |
+ WebInspector.console.error(messsage); |
+ this._model._onTracingComplete(); |
+ this._model.reset(); |
+ this._reader.cancel(); |
+ this._progress.done(); |
+ }, |
+ |
+ _looksLikeAppVersion: function(item) |
+ { |
+ return typeof item === "string" && item.indexOf("Chrome") !== -1; |
+ }, |
+ |
+ close: function() |
+ { |
+ this._loader.finish(); |
+ this._model._onTracingComplete(); |
+ } |
+} |
+ |
+/** |
+ * @constructor |
+ * @param {!WebInspector.OutputStream} stream |
+ * @implements {WebInspector.OutputStreamDelegate} |
+ */ |
+WebInspector.TracingTimelineSaver = function(stream) |
+{ |
+ this._stream = stream; |
+} |
+ |
+WebInspector.TracingTimelineSaver.prototype = { |
+ onTransferStarted: function() |
+ { |
+ this._stream.write("["); |
+ }, |
+ |
+ onTransferFinished: function() |
+ { |
+ this._stream.write("]"); |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.ChunkedReader} reader |
+ */ |
+ onChunkTransferred: function(reader) { }, |
+ |
+ /** |
+ * @param {!WebInspector.ChunkedReader} reader |
+ * @param {!Event} event |
+ */ |
+ onError: function(reader, event) { }, |
+} |
+ |
+/** |
+ * @constructor |
+ * @param {!WebInspector.TracingModel.Event} event |
+ */ |
+WebInspector.InvalidationTrackingEvent = function(event) |
+{ |
+ this.type = event.name; |
+ this.frameId = event.args["data"]["frame"]; |
+ this.nodeId = event.args["data"]["nodeId"]; |
+ this.nodeName = event.args["data"]["nodeName"]; |
+ this.paintId = event.args["data"]["paintId"]; |
+ this.reason = event.args["data"]["reason"]; |
+ this.stackTrace = event.args["data"]["stackTrace"]; |
+} |
+ |
+/** |
+ * @constructor |
+ */ |
+WebInspector.InvalidationTracker = function() |
+{ |
+ this._initializePerFrameState(); |
+} |
+ |
+WebInspector.InvalidationTracker.prototype = { |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} event |
+ */ |
+ addInvalidation: function(event) |
+ { |
+ var invalidation = new WebInspector.InvalidationTrackingEvent(event); |
+ |
+ this._startNewFrameIfNeeded(); |
+ if (!invalidation.nodeId && !invalidation.paintId) { |
+ console.error("Invalidation lacks node information."); |
+ console.error(invalidation); |
+ } |
+ |
+ // Record the paintIds for style recalc or layout invalidations. |
+ // FIXME: This O(n^2) loop could be optimized with a map. |
+ var recordTypes = WebInspector.TimelineModel.RecordType; |
+ if (invalidation.type == recordTypes.PaintInvalidationTracking) |
+ this._invalidationEvents.forEach(updatePaintId); |
+ else |
+ this._invalidationEvents.push(invalidation); |
+ |
+ function updatePaintId(invalidationToUpdate) |
+ { |
+ if (invalidationToUpdate.nodeId !== invalidation.nodeId) |
+ return; |
+ if (invalidationToUpdate.type === recordTypes.StyleRecalcInvalidationTracking |
+ || invalidationToUpdate.type === recordTypes.LayoutInvalidationTracking) { |
+ invalidationToUpdate.paintId = invalidation.paintId; |
+ } |
+ } |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} styleRecalcEvent |
+ */ |
+ didRecalcStyle: function(styleRecalcEvent) |
+ { |
+ var recalcFrameId = styleRecalcEvent.args["frame"]; |
+ var index = this._lastStyleRecalcEventIndex; |
+ var invalidationCount = this._invalidationEvents.length; |
+ for (; index < invalidationCount; index++) { |
+ var invalidation = this._invalidationEvents[index]; |
+ if (invalidation.type !== WebInspector.TimelineModel.RecordType.StyleRecalcInvalidationTracking) |
+ continue; |
+ if (invalidation.frameId === recalcFrameId) |
+ this._addInvalidationTrackingEvent(styleRecalcEvent, invalidation); |
+ } |
+ |
+ this._lastStyleRecalcEventIndex = invalidationCount; |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} layoutEvent |
+ */ |
+ didLayout: function(layoutEvent) |
+ { |
+ var layoutFrameId = layoutEvent.args["beginData"]["frame"]; |
+ var index = this._lastLayoutEventIndex; |
+ var invalidationCount = this._invalidationEvents.length; |
+ for (; index < invalidationCount; index++) { |
+ var invalidation = this._invalidationEvents[index]; |
+ if (invalidation.type !== WebInspector.TimelineModel.RecordType.LayoutInvalidationTracking) |
+ continue; |
+ if (invalidation.frameId === layoutFrameId) |
+ this._addInvalidationTrackingEvent(layoutEvent, invalidation); |
+ } |
+ |
+ this._lastLayoutEventIndex = invalidationCount; |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} paintEvent |
+ */ |
+ didPaint: function(paintEvent) |
+ { |
+ this._didPaint = true; |
+ |
+ // If a paint doesn't have a corresponding graphics layer id, it paints |
+ // into its parent so add an effectivePaintId to these events. |
+ var layerId = paintEvent.args["data"]["layerId"]; |
+ if (layerId) |
+ this._lastPaintWithLayer = paintEvent; |
+ if (!this._lastPaintWithLayer) { |
+ console.error("Failed to find the paint container for a paint event."); |
+ return; |
+ } |
+ |
+ var effectivePaintId = this._lastPaintWithLayer.args["data"]["nodeId"]; |
+ var frameId = paintEvent.args["data"]["frame"]; |
+ this._invalidationEvents.forEach(recordInvalidationForPaint.bind(this)); |
+ |
+ /** |
+ * @param {!WebInspector.InvalidationTrackingEvent} invalidation |
+ * @this {WebInspector.InvalidationTracker} |
+ */ |
+ function recordInvalidationForPaint(invalidation) |
+ { |
+ if (invalidation.paintId === effectivePaintId && invalidation.frameId === frameId) |
+ this._addInvalidationTrackingEvent(paintEvent, invalidation); |
+ } |
+ }, |
+ |
+ /** |
+ * @param {!WebInspector.TracingModel.Event} event |
+ * @param {!WebInspector.InvalidationTrackingEvent} invalidation |
+ */ |
+ _addInvalidationTrackingEvent: function(event, invalidation) |
+ { |
+ if (!event.invalidationTrackingEvents) |
+ event.invalidationTrackingEvents = [ invalidation ]; |
+ else |
+ event.invalidationTrackingEvents.push(invalidation); |
+ }, |
+ |
+ _startNewFrameIfNeeded: function() |
+ { |
+ if (!this._didPaint) |
+ return; |
+ |
+ this._initializePerFrameState(); |
+ }, |
+ |
+ _initializePerFrameState: function() |
+ { |
+ /** @type {!Array.<!WebInspector.InvalidationTrackingEvent>} */ |
+ this._invalidationEvents = []; |
+ this._lastStyleRecalcEventIndex = 0; |
+ this._lastLayoutEventIndex = 0; |
+ this._lastPaintWithLayer = undefined; |
+ this._didPaint = false; |
+ } |
+} |