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