Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TracingTimelineModel.js
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 /**
6  * @constructor
7  * @param {!WebInspector.TracingModel} tracingModel
8  * @param {!WebInspector.TimelineModel.Filter} recordFilter
9  * @extends {WebInspector.TimelineModel}
10  */
11 WebInspector.TracingTimelineModel = function(tracingModel, recordFilter)
12 {
13     WebInspector.TimelineModel.call(this);
14     this._tracingModel = tracingModel;
15     this._inspectedTargetEvents = [];
16     this._recordFilter = recordFilter;
17     this._tracingModel.addEventListener(WebInspector.TracingModel.Events.TracingStarted, this._onTracingStarted, this);
18     this._tracingModel.addEventListener(WebInspector.TracingModel.Events.TracingComplete, this._onTracingComplete, this);
19     this.reset();
20 }
21
22 WebInspector.TracingTimelineModel.RecordType = {
23     Program: "Program",
24     EventDispatch: "EventDispatch",
25
26     GPUTask: "GPUTask",
27
28     RequestMainThreadFrame: "RequestMainThreadFrame",
29     BeginFrame: "BeginFrame",
30     BeginMainThreadFrame: "BeginMainThreadFrame",
31     ActivateLayerTree: "ActivateLayerTree",
32     DrawFrame: "DrawFrame",
33     ScheduleStyleRecalculation: "ScheduleStyleRecalculation",
34     RecalculateStyles: "RecalculateStyles",
35     InvalidateLayout: "InvalidateLayout",
36     Layout: "Layout",
37     UpdateLayer: "UpdateLayer",
38     UpdateLayerTree: "UpdateLayerTree",
39     PaintSetup: "PaintSetup",
40     Paint: "Paint",
41     PaintImage: "PaintImage",
42     Rasterize: "Rasterize",
43     RasterTask: "RasterTask",
44     ScrollLayer: "ScrollLayer",
45     CompositeLayers: "CompositeLayers",
46
47     ParseHTML: "ParseHTML",
48
49     TimerInstall: "TimerInstall",
50     TimerRemove: "TimerRemove",
51     TimerFire: "TimerFire",
52
53     XHRReadyStateChange: "XHRReadyStateChange",
54     XHRLoad: "XHRLoad",
55     EvaluateScript: "EvaluateScript",
56
57     MarkLoad: "MarkLoad",
58     MarkDOMContent: "MarkDOMContent",
59     MarkFirstPaint: "MarkFirstPaint",
60
61     TimeStamp: "TimeStamp",
62     ConsoleTime: "ConsoleTime",
63
64     ResourceSendRequest: "ResourceSendRequest",
65     ResourceReceiveResponse: "ResourceReceiveResponse",
66     ResourceReceivedData: "ResourceReceivedData",
67     ResourceFinish: "ResourceFinish",
68
69     FunctionCall: "FunctionCall",
70     GCEvent: "GCEvent",
71     JSFrame: "JSFrame",
72     JSSample: "JSSample",
73
74     UpdateCounters: "UpdateCounters",
75
76     RequestAnimationFrame: "RequestAnimationFrame",
77     CancelAnimationFrame: "CancelAnimationFrame",
78     FireAnimationFrame: "FireAnimationFrame",
79
80     WebSocketCreate : "WebSocketCreate",
81     WebSocketSendHandshakeRequest : "WebSocketSendHandshakeRequest",
82     WebSocketReceiveHandshakeResponse : "WebSocketReceiveHandshakeResponse",
83     WebSocketDestroy : "WebSocketDestroy",
84
85     EmbedderCallback : "EmbedderCallback",
86
87     CallStack: "CallStack",
88     SetLayerTreeId: "SetLayerTreeId",
89     TracingStartedInPage: "TracingStartedInPage",
90     TracingStartedInWorker: "TracingStartedInWorker",
91
92     DecodeImage: "Decode Image",
93     ResizeImage: "Resize Image",
94     DrawLazyPixelRef: "Draw LazyPixelRef",
95     DecodeLazyPixelRef: "Decode LazyPixelRef",
96
97     LazyPixelRef: "LazyPixelRef",
98     LayerTreeHostImplSnapshot: "cc::LayerTreeHostImpl",
99     PictureSnapshot: "cc::Picture"
100 };
101
102 /**
103  * @constructor
104  * @param {string} name
105  */
106 WebInspector.TracingTimelineModel.VirtualThread = function(name)
107 {
108     this.name = name;
109     /** @type {!Array.<!WebInspector.TracingModel.Event>} */
110     this.events = [];
111 }
112
113 WebInspector.TracingTimelineModel.prototype = {
114     /**
115      * @param {boolean} captureStacks
116      * @param {boolean} captureMemory
117      * @param {boolean} capturePictures
118      */
119     startRecording: function(captureStacks, captureMemory, capturePictures)
120     {
121         function disabledByDefault(category)
122         {
123             return "disabled-by-default-" + category;
124         }
125         var categoriesArray = ["-*", disabledByDefault("devtools.timeline"), disabledByDefault("devtools.timeline.frame")];
126         if (captureStacks) {
127             categoriesArray.push(disabledByDefault("devtools.timeline.stack"));
128             if (WebInspector.experimentsSettings.timelineJSCPUProfile.isEnabled()) {
129                 this._jsProfilerStarted = true;
130                 this._currentTarget = WebInspector.context.flavor(WebInspector.Target);
131                 this._configureCpuProfilerSamplingInterval();
132                 this._currentTarget.profilerAgent().start();
133             }
134         }
135         if (capturePictures) {
136             categoriesArray = categoriesArray.concat([
137                 disabledByDefault("devtools.timeline.layers"),
138                 disabledByDefault("devtools.timeline.picture"),
139                 disabledByDefault("blink.graphics_context_annotations")]);
140         }
141         var categories = categoriesArray.join(",");
142         this._startRecordingWithCategories(categories);
143     },
144
145     stopRecording: function()
146     {
147         this._stopCallbackBarrier = new CallbackBarrier();
148         if (this._jsProfilerStarted) {
149             this._currentTarget.profilerAgent().stop(this._stopCallbackBarrier.createCallback(this._didStopRecordingJSSamples.bind(this)));
150             this._jsProfilerStarted = false;
151         }
152         this._tracingModel.stop();
153     },
154
155     /**
156      * @param {string} sessionId
157      * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events
158      */
159     setEventsForTest: function(sessionId, events)
160     {
161         this._onTracingStarted();
162         this._tracingModel.setEventsForTest(sessionId, events);
163         this._onTracingComplete();
164     },
165
166     _configureCpuProfilerSamplingInterval: function()
167     {
168         var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000;
169         this._currentTarget.profilerAgent().setSamplingInterval(intervalUs, didChangeInterval);
170
171         function didChangeInterval(error)
172         {
173             if (error)
174                 WebInspector.console.error(error);
175         }
176     },
177
178     /**
179      * @param {string} categories
180      */
181     _startRecordingWithCategories: function(categories)
182     {
183         this.reset();
184         this._tracingModel.start(categories, "");
185     },
186
187     _onTracingStarted: function()
188     {
189         this.reset();
190         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStarted);
191     },
192
193     _onTracingComplete: function()
194     {
195         if (this._stopCallbackBarrier)
196             this._stopCallbackBarrier.callWhenDone(this._didStopRecordingTraceEvents.bind(this));
197         else
198             this._didStopRecordingTraceEvents();
199     },
200
201     /**
202      * @param {?Protocol.Error} error
203      * @param {?ProfilerAgent.CPUProfile} cpuProfile
204      */
205     _didStopRecordingJSSamples: function(error, cpuProfile)
206     {
207         if (error)
208             WebInspector.console.error(error);
209         this._cpuProfile = cpuProfile;
210     },
211
212     _didStopRecordingTraceEvents: function()
213     {
214         this._stopCallbackBarrier = null;
215         var events = this._tracingModel.devtoolsPageMetadataEvents();
216         var workerMetadataEvents = this._tracingModel.devtoolsWorkerMetadataEvents();
217         events.sort(WebInspector.TracingModel.Event.compareStartTime);
218
219         this._resetProcessingState();
220         for (var i = 0, length = events.length; i < length; i++) {
221             var event = events[i];
222             var process = event.thread.process();
223             var startTime = event.startTime;
224
225             var endTime = Infinity;
226             if (i + 1 < length)
227                 endTime = events[i + 1].startTime;
228
229             var threads = process.sortedThreads();
230             for (var j = 0; j < threads.length; j++) {
231                 var thread = threads[j];
232                 if (thread.name() === "WebCore: Worker" && !workerMetadataEvents.some(function(e) { return e.thread === thread; }))
233                     continue;
234                 this._processThreadEvents(startTime, endTime, event.thread, thread);
235             }
236         }
237         this._resetProcessingState();
238
239         this._inspectedTargetEvents.sort(WebInspector.TracingModel.Event.compareStartTime);
240
241         if (this._cpuProfile) {
242             var jsSamples = WebInspector.TimelineJSProfileProcessor.generateTracingEventsFromCpuProfile(this, this._cpuProfile);
243             this._inspectedTargetEvents = this._inspectedTargetEvents.mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime);
244             this._setMainThreadEvents(this.mainThreadEvents().mergeOrdered(jsSamples, WebInspector.TracingModel.Event.orderedCompareStartTime));
245             this._cpuProfile = null;
246         }
247
248         this._buildTimelineRecords();
249         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordingStopped);
250     },
251
252     /**
253      * @return {number}
254      */
255     minimumRecordTime: function()
256     {
257         return this._tracingModel.minimumRecordTime();
258     },
259
260     /**
261      * @return {number}
262      */
263     maximumRecordTime: function()
264     {
265         return this._tracingModel.maximumRecordTime();
266     },
267
268     /**
269      * @return {!Array.<!WebInspector.TracingModel.Event>}
270      */
271     inspectedTargetEvents: function()
272     {
273         return this._inspectedTargetEvents;
274     },
275
276     /**
277      * @return {!Array.<!WebInspector.TracingModel.Event>}
278      */
279     mainThreadEvents: function()
280     {
281         return this._mainThreadEvents;
282     },
283
284     /**
285      * @param {!Array.<!WebInspector.TracingModel.Event>} events
286      */
287     _setMainThreadEvents: function(events)
288     {
289         this._mainThreadEvents = events;
290     },
291
292     /**
293      * @return {!Array.<!WebInspector.TracingTimelineModel.VirtualThread>}
294      */
295     virtualThreads: function()
296     {
297         return this._virtualThreads;
298     },
299
300     /**
301      * @param {!WebInspector.ChunkedFileReader} fileReader
302      * @param {!WebInspector.Progress} progress
303      * @return {!WebInspector.OutputStream}
304      */
305     createLoader: function(fileReader, progress)
306     {
307         return new WebInspector.TracingModelLoader(this, fileReader, progress);
308     },
309
310     /**
311      * @param {!WebInspector.OutputStream} stream
312      */
313     writeToStream: function(stream)
314     {
315         var saver = new WebInspector.TracingTimelineSaver(stream);
316         var events = this._tracingModel.rawEvents();
317         saver.save(events);
318     },
319
320     reset: function()
321     {
322         this._virtualThreads = [];
323         this._mainThreadEvents = [];
324         this._inspectedTargetEvents = [];
325         WebInspector.TimelineModel.prototype.reset.call(this);
326     },
327
328     _buildTimelineRecords: function()
329     {
330         var recordStack = [];
331         var mainThreadEvents = this.mainThreadEvents();
332         for (var i = 0, size = mainThreadEvents.length; i < size; ++i) {
333             var event = mainThreadEvents[i];
334             while (recordStack.length) {
335                 var top = recordStack.peekLast();
336                 if (top._event.endTime >= event.startTime)
337                     break;
338                 recordStack.pop();
339                 if (!recordStack.length)
340                     this._addTopLevelRecord(top);
341             }
342             var record = new WebInspector.TracingTimelineModel.TraceEventRecord(this, event);
343             if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(event))
344                 this._eventDividerRecords.push(record);
345             if (!this._recordFilter.accept(record))
346                 continue;
347             var parentRecord = recordStack.peekLast();
348             if (parentRecord)
349                 parentRecord._addChild(record);
350             if (event.endTime)
351                 recordStack.push(record);
352         }
353         if (recordStack.length)
354             this._addTopLevelRecord(recordStack[0]);
355     },
356
357     /**
358      * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} record
359      */
360     _addTopLevelRecord: function(record)
361     {
362         this._updateBoundaries(record);
363         this._records.push(record);
364         if (record.type() === WebInspector.TracingTimelineModel.RecordType.Program)
365             this._mainThreadTasks.push(record);
366         if (record.type() === WebInspector.TracingTimelineModel.RecordType.GPUTask)
367             this._gpuThreadTasks.push(record);
368         this.dispatchEventToListeners(WebInspector.TimelineModel.Events.RecordAdded, record);
369     },
370
371     _resetProcessingState: function()
372     {
373         this._sendRequestEvents = {};
374         this._timerEvents = {};
375         this._requestAnimationFrameEvents = {};
376         this._layoutInvalidate = {};
377         this._lastScheduleStyleRecalculation = {};
378         this._webSocketCreateEvents = {};
379         this._paintImageEventByPixelRefId = {};
380         this._lastPaintForLayer = {};
381         this._lastRecalculateStylesEvent = null;
382         this._currentScriptEvent = null;
383         this._eventStack = [];
384     },
385
386     /**
387      * @param {number} startTime
388      * @param {?number} endTime
389      * @param {!WebInspector.TracingModel.Thread} mainThread
390      * @param {!WebInspector.TracingModel.Thread} thread
391      */
392     _processThreadEvents: function(startTime, endTime, mainThread, thread)
393     {
394         var events = thread.events();
395         var length = events.length;
396         var i = events.lowerBound(startTime, function (time, event) { return time - event.startTime });
397
398         var threadEvents;
399         if (thread === mainThread) {
400             threadEvents = this._mainThreadEvents;
401         } else {
402             var virtualThread = new WebInspector.TracingTimelineModel.VirtualThread(thread.name());
403             threadEvents = virtualThread.events;
404             this._virtualThreads.push(virtualThread);
405         }
406
407         this._eventStack = [];
408         for (; i < length; i++) {
409             var event = events[i];
410             if (endTime && event.startTime >= endTime)
411                 break;
412             this._processEvent(event);
413             threadEvents.push(event);
414             this._inspectedTargetEvents.push(event);
415         }
416     },
417
418     /**
419      * @param {!WebInspector.TracingModel.Event} event
420      */
421     _processEvent: function(event)
422     {
423         var recordTypes = WebInspector.TracingTimelineModel.RecordType;
424
425         var eventStack = this._eventStack;
426         while (eventStack.length && eventStack.peekLast().endTime < event.startTime)
427             eventStack.pop();
428         var duration = event.duration;
429         if (duration) {
430             if (eventStack.length) {
431                 var parent = eventStack.peekLast();
432                 parent.selfTime -= duration;
433             }
434             event.selfTime = duration;
435             eventStack.push(event);
436         }
437
438         if (this._currentScriptEvent && event.startTime > this._currentScriptEvent.endTime)
439             this._currentScriptEvent = null;
440
441         switch (event.name) {
442         case recordTypes.CallStack:
443             var lastMainThreadEvent = this.mainThreadEvents().peekLast();
444             if (lastMainThreadEvent && event.args["stack"] && event.args["stack"].length)
445                 lastMainThreadEvent.stackTrace = event.args["stack"];
446             break;
447
448         case recordTypes.ResourceSendRequest:
449             this._sendRequestEvents[event.args["data"]["requestId"]] = event;
450             event.imageURL = event.args["data"]["url"];
451             break;
452
453         case recordTypes.ResourceReceiveResponse:
454         case recordTypes.ResourceReceivedData:
455         case recordTypes.ResourceFinish:
456             event.initiator = this._sendRequestEvents[event.args["data"]["requestId"]];
457             if (event.initiator)
458                 event.imageURL = event.initiator.imageURL;
459             break;
460
461         case recordTypes.TimerInstall:
462             this._timerEvents[event.args["data"]["timerId"]] = event;
463             break;
464
465         case recordTypes.TimerFire:
466             event.initiator = this._timerEvents[event.args["data"]["timerId"]];
467             break;
468
469         case recordTypes.RequestAnimationFrame:
470             this._requestAnimationFrameEvents[event.args["data"]["id"]] = event;
471             break;
472
473         case recordTypes.FireAnimationFrame:
474             event.initiator = this._requestAnimationFrameEvents[event.args["data"]["id"]];
475             break;
476
477         case recordTypes.ScheduleStyleRecalculation:
478             this._lastScheduleStyleRecalculation[event.args["frame"]] = event;
479             break;
480
481         case recordTypes.RecalculateStyles:
482             event.initiator = this._lastScheduleStyleRecalculation[event.args["frame"]];
483             this._lastRecalculateStylesEvent = event;
484             break;
485
486         case recordTypes.InvalidateLayout:
487             // Consider style recalculation as a reason for layout invalidation,
488             // but only if we had no earlier layout invalidation records.
489             var layoutInitator = event;
490             var frameId = event.args["frame"];
491             if (!this._layoutInvalidate[frameId] && this._lastRecalculateStylesEvent && this._lastRecalculateStylesEvent.endTime >  event.startTime)
492                 layoutInitator = this._lastRecalculateStylesEvent.initiator;
493             this._layoutInvalidate[frameId] = layoutInitator;
494             break;
495
496         case recordTypes.Layout:
497             var frameId = event.args["beginData"]["frame"];
498             event.initiator = this._layoutInvalidate[frameId];
499             event.backendNodeId = event.args["endData"]["rootNode"];
500             event.highlightQuad =  event.args["endData"]["root"];
501             this._layoutInvalidate[frameId] = null;
502             if (this._currentScriptEvent)
503                 event.warning = WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.");
504             break;
505
506         case recordTypes.WebSocketCreate:
507             this._webSocketCreateEvents[event.args["data"]["identifier"]] = event;
508             break;
509
510         case recordTypes.WebSocketSendHandshakeRequest:
511         case recordTypes.WebSocketReceiveHandshakeResponse:
512         case recordTypes.WebSocketDestroy:
513             event.initiator = this._webSocketCreateEvents[event.args["data"]["identifier"]];
514             break;
515
516         case recordTypes.EvaluateScript:
517         case recordTypes.FunctionCall:
518             if (!this._currentScriptEvent)
519                 this._currentScriptEvent = event;
520             break;
521
522         case recordTypes.SetLayerTreeId:
523             this._inspectedTargetLayerTreeId = event.args["layerTreeId"];
524             break;
525
526         case recordTypes.Paint:
527             event.highlightQuad = event.args["data"]["clip"];
528             event.backendNodeId = event.args["data"]["nodeId"];
529             var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
530             if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
531                 break;
532             // Only keep layer paint events, skip paints for subframes that get painted to the same layer as parent.
533             if (!event.args["data"]["layerId"])
534                 break;
535             this._lastPaintForLayer[layerUpdateEvent.args["layerId"]] = event;
536             break;
537
538         case recordTypes.PictureSnapshot:
539             var layerUpdateEvent = this._findAncestorEvent(recordTypes.UpdateLayer);
540             if (!layerUpdateEvent || layerUpdateEvent.args["layerTreeId"] !== this._inspectedTargetLayerTreeId)
541                 break;
542             var paintEvent = this._lastPaintForLayer[layerUpdateEvent.args["layerId"]];
543             if (!paintEvent || !event.args["snapshot"] || !event.args["snapshot"]["params"])
544                 break;
545             paintEvent.picture = event.args["snapshot"]["skp64"];
546             paintEvent.layerRect = event.args["snapshot"]["params"]["layer_rect"];
547             break;
548
549         case recordTypes.ScrollLayer:
550             event.backendNodeId = event.args["data"]["nodeId"];
551             break;
552
553         case recordTypes.PaintImage:
554             event.backendNodeId = event.args["data"]["nodeId"];
555             event.imageURL = event.args["data"]["url"];
556             break;
557
558         case recordTypes.DecodeImage:
559         case recordTypes.ResizeImage:
560             var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
561             if (!paintImageEvent) {
562                 var decodeLazyPixelRefEvent = this._findAncestorEvent(recordTypes.DecodeLazyPixelRef);
563                 paintImageEvent = decodeLazyPixelRefEvent && this._paintImageEventByPixelRefId[decodeLazyPixelRefEvent.args["LazyPixelRef"]];
564             }
565             if (!paintImageEvent)
566                 break;
567             event.backendNodeId = paintImageEvent.backendNodeId;
568             event.imageURL = paintImageEvent.imageURL;
569             break;
570
571         case recordTypes.DrawLazyPixelRef:
572             var paintImageEvent = this._findAncestorEvent(recordTypes.PaintImage);
573             if (!paintImageEvent)
574                 break;
575             this._paintImageEventByPixelRefId[event.args["LazyPixelRef"]] = paintImageEvent;
576             event.backendNodeId = paintImageEvent.backendNodeId;
577             event.imageURL = paintImageEvent.imageURL;
578             break;
579         }
580     },
581
582     /**
583      * @param {string} name
584      * @return {?WebInspector.TracingModel.Event}
585      */
586     _findAncestorEvent: function(name)
587     {
588         for (var i = this._eventStack.length - 1; i >= 0; --i) {
589             var event = this._eventStack[i];
590             if (event.name === name)
591                 return event;
592         }
593         return null;
594     },
595
596     __proto__: WebInspector.TimelineModel.prototype
597 }
598
599 /**
600  * @interface
601  */
602 WebInspector.TracingTimelineModel.Filter = function() { }
603
604 WebInspector.TracingTimelineModel.Filter.prototype = {
605     /**
606      * @param {!WebInspector.TracingModel.Event} event
607      * @return {boolean}
608      */
609     accept: function(event) { }
610 }
611
612 /**
613  * @constructor
614  * @implements {WebInspector.TracingTimelineModel.Filter}
615  * @param {!Array.<string>} eventNames
616  */
617 WebInspector.TracingTimelineModel.EventNameFilter = function(eventNames)
618 {
619     this._eventNames = eventNames.keySet();
620 }
621
622 WebInspector.TracingTimelineModel.EventNameFilter.prototype = {
623     /**
624      * @param {!WebInspector.TracingModel.Event} event
625      * @return {boolean}
626      */
627     accept: function(event)
628     {
629         throw new Error("Not implemented.");
630     }
631 }
632
633 /**
634  * @constructor
635  * @extends {WebInspector.TracingTimelineModel.EventNameFilter}
636  * @param {!Array.<string>} includeNames
637  */
638 WebInspector.TracingTimelineModel.InclusiveEventNameFilter = function(includeNames)
639 {
640     WebInspector.TracingTimelineModel.EventNameFilter.call(this, includeNames)
641 }
642
643 WebInspector.TracingTimelineModel.InclusiveEventNameFilter.prototype = {
644     /**
645      * @override
646      * @param {!WebInspector.TracingModel.Event} event
647      * @return {boolean}
648      */
649     accept: function(event)
650     {
651         return !!this._eventNames[event.name];
652     },
653     __proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype
654 }
655
656 /**
657  * @constructor
658  * @extends {WebInspector.TracingTimelineModel.EventNameFilter}
659  * @param {!Array.<string>} excludeNames
660  */
661 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter = function(excludeNames)
662 {
663     WebInspector.TracingTimelineModel.EventNameFilter.call(this, excludeNames)
664 }
665
666 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter.prototype = {
667     /**
668      * @override
669      * @param {!WebInspector.TracingModel.Event} event
670      * @return {boolean}
671      */
672     accept: function(event)
673     {
674         return !this._eventNames[event.name];
675     },
676     __proto__: WebInspector.TracingTimelineModel.EventNameFilter.prototype
677 }
678
679 /**
680  * @constructor
681  * @implements {WebInspector.TimelineModel.Record}
682  * @param {!WebInspector.TimelineModel} model
683  * @param {!WebInspector.TracingModel.Event} traceEvent
684  */
685 WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent)
686 {
687     this._model = model;
688     this._event = traceEvent;
689     traceEvent._timelineRecord = this;
690     this._children = [];
691 }
692
693 WebInspector.TracingTimelineModel.TraceEventRecord.prototype = {
694     /**
695      * @return {?Array.<!ConsoleAgent.CallFrame>}
696      */
697     callSiteStackTrace: function()
698     {
699         var initiator = this._event.initiator;
700         return initiator ? initiator.stackTrace : null;
701     },
702
703     /**
704      * @return {?WebInspector.TimelineModel.Record}
705      */
706     initiator: function()
707     {
708         var initiator = this._event.initiator;
709         return initiator ? initiator._timelineRecord : null;
710     },
711
712     /**
713      * @return {?WebInspector.Target}
714      */
715     target: function()
716     {
717         return this._event.thread.target();
718     },
719
720     /**
721      * @return {number}
722      */
723     selfTime: function()
724     {
725         return this._event.selfTime;
726     },
727
728     /**
729      * @return {!Array.<!WebInspector.TimelineModel.Record>}
730      */
731     children: function()
732     {
733         return this._children;
734     },
735
736     /**
737      * @return {number}
738      */
739     startTime: function()
740     {
741         return this._event.startTime;
742     },
743
744     /**
745      * @return {string}
746      */
747     thread: function()
748     {
749         // FIXME: Should return the actual thread name.
750         return WebInspector.TimelineModel.MainThreadName;
751     },
752
753     /**
754      * @return {number}
755      */
756     endTime: function()
757     {
758         return this._event.endTime || this._event.startTime;
759     },
760
761     /**
762      * @param {number} endTime
763      */
764     setEndTime: function(endTime)
765     {
766         throw new Error("Unsupported operation setEndTime");
767     },
768
769     /**
770      * @return {!Object}
771      */
772     data: function()
773     {
774         return this._event.args["data"];
775     },
776
777     /**
778      * @return {string}
779      */
780     type: function()
781     {
782         return this._event.name;
783     },
784
785     /**
786      * @return {string}
787      */
788     frameId: function()
789     {
790         switch (this._event.name) {
791         case WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation:
792         case WebInspector.TracingTimelineModel.RecordType.RecalculateStyles:
793         case WebInspector.TracingTimelineModel.RecordType.InvalidateLayout:
794             return this._event.args["frameId"];
795         case WebInspector.TracingTimelineModel.RecordType.Layout:
796             return this._event.args["beginData"]["frameId"];
797         default:
798             var data = this._event.args["data"];
799             return (data && data["frame"]) || "";
800         }
801     },
802
803     /**
804      * @return {?Array.<!ConsoleAgent.CallFrame>}
805      */
806     stackTrace: function()
807     {
808         return this._event.stackTrace;
809     },
810
811     /**
812      * @param {string} key
813      * @return {?Object}
814      */
815     getUserObject: function(key)
816     {
817         if (key === "TimelineUIUtils::preview-element")
818             return this._event.previewElement;
819         throw new Error("Unexpected key: " + key);
820     },
821
822     /**
823      * @param {string} key
824      * @param {?Object|undefined} value
825      */
826     setUserObject: function(key, value)
827     {
828         if (key !== "TimelineUIUtils::preview-element")
829             throw new Error("Unexpected key: " + key);
830         this._event.previewElement = /** @type {?Element} */ (value);
831     },
832
833     /**
834      * @return {?Array.<string>}
835      */
836     warnings: function()
837     {
838         if (this._event.warning)
839             return [this._event.warning];
840         return null;
841     },
842
843     /**
844      * @return {!WebInspector.TracingModel.Event}
845      */
846     traceEvent: function()
847     {
848         return this._event;
849     },
850
851     /**
852      * @param {!WebInspector.TracingTimelineModel.TraceEventRecord} child
853      */
854     _addChild: function(child)
855     {
856         this._children.push(child);
857         child.parent = this;
858     },
859
860     /**
861      * @return {!WebInspector.TimelineModel}
862      */
863     timelineModel: function()
864     {
865         return this._model;
866     }
867 }
868
869
870
871 /**
872  * @constructor
873  * @implements {WebInspector.OutputStream}
874  * @param {!WebInspector.TracingTimelineModel} model
875  * @param {!{cancel: function()}} reader
876  * @param {!WebInspector.Progress} progress
877  */
878 WebInspector.TracingModelLoader = function(model, reader, progress)
879 {
880     this._model = model;
881     this._reader = reader;
882     this._progress = progress;
883     this._buffer = "";
884     this._firstChunk = true;
885     this._loader = new WebInspector.TracingModel.Loader(model._tracingModel);
886 }
887
888 WebInspector.TracingModelLoader.prototype = {
889     /**
890      * @param {string} chunk
891      */
892     write: function(chunk)
893     {
894         var data = this._buffer + chunk;
895         var lastIndex = 0;
896         var index;
897         do {
898             index = lastIndex;
899             lastIndex = WebInspector.TextUtils.findBalancedCurlyBrackets(data, index);
900         } while (lastIndex !== -1)
901
902         var json = data.slice(0, index) + "]";
903         this._buffer = data.slice(index);
904
905         if (!index)
906             return;
907
908         if (this._firstChunk) {
909             this._model.reset();
910         } else {
911             var commaIndex = json.indexOf(",");
912             if (commaIndex !== -1)
913                 json = json.slice(commaIndex + 1);
914             json = "[" + json;
915         }
916
917         var items;
918         try {
919             items = /** @type {!Array.<!WebInspector.TracingModel.EventPayload>} */ (JSON.parse(json));
920         } catch (e) {
921             this._reportErrorAndCancelLoading("Malformed timeline data: " + e);
922             return;
923         }
924
925         if (this._firstChunk) {
926             this._firstChunk = false;
927             if (this._looksLikeAppVersion(items[0])) {
928                 this._reportErrorAndCancelLoading("Old Timeline format is not supported.");
929                 return;
930             }
931         }
932
933         try {
934             this._loader.loadNextChunk(items);
935         } catch(e) {
936             this._reportErrorAndCancelLoading("Malformed timeline data: " + e);
937             return;
938         }
939     },
940
941     _reportErrorAndCancelLoading: function(messsage)
942     {
943         WebInspector.console.error(messsage);
944         this._model.reset();
945         this._reader.cancel();
946         this._progress.done();
947     },
948
949     _looksLikeAppVersion: function(item)
950     {
951         return typeof item === "string" && item.indexOf("Chrome") !== -1;
952     },
953
954     close: function()
955     {
956         this._loader.finish();
957     }
958 }
959
960 /**
961  * @constructor
962  * @param {!WebInspector.OutputStream} stream
963  */
964 WebInspector.TracingTimelineSaver = function(stream)
965 {
966     this._stream = stream;
967 }
968
969 WebInspector.TracingTimelineSaver.prototype = {
970     /**
971      * @param {!Array.<*>} payloads
972      */
973     save: function(payloads)
974     {
975         this._payloads = payloads;
976         this._recordIndex = 0;
977         this._writeNextChunk(this._stream);
978     },
979
980     _writeNextChunk: function(stream)
981     {
982         const separator = ",\n";
983         var data = [];
984         var length = 0;
985
986         if (this._recordIndex === 0)
987             data.push("[");
988         while (this._recordIndex < this._payloads.length) {
989             var item = JSON.stringify(this._payloads[this._recordIndex]);
990             var itemLength = item.length + separator.length;
991             if (length + itemLength > WebInspector.TimelineModelImpl.TransferChunkLengthBytes)
992                 break;
993             length += itemLength;
994             if (this._recordIndex > 0)
995                 data.push(separator);
996             data.push(item);
997             ++this._recordIndex;
998         }
999         if (this._recordIndex === this._payloads.length)
1000             data.push("]");
1001         stream.write(data.join(""), this._didWriteNextChunk.bind(this, stream));
1002     },
1003
1004     _didWriteNextChunk: function(stream)
1005     {
1006         if (this._recordIndex === this._payloads.length)
1007             stream.close();
1008         else
1009             this._writeNextChunk(stream);
1010     }
1011 }