Home | History | Annotate | Download | only in timeline
      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  */
     12 WebInspector.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 
     25 WebInspector.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  */
    109 WebInspector.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 
    118 WebInspector.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  */
    673 WebInspector.TracingTimelineModel.Filter = function() { }
    674 
    675 WebInspector.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  */
    688 WebInspector.TracingTimelineModel.EventNameFilter = function(eventNames)
    689 {
    690     this._eventNames = eventNames.keySet();
    691 }
    692 
    693 WebInspector.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  */
    709 WebInspector.TracingTimelineModel.InclusiveEventNameFilter = function(includeNames)
    710 {
    711     WebInspector.TracingTimelineModel.EventNameFilter.call(this, includeNames)
    712 }
    713 
    714 WebInspector.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  */
    732 WebInspector.TracingTimelineModel.ExclusiveEventNameFilter = function(excludeNames)
    733 {
    734     WebInspector.TracingTimelineModel.EventNameFilter.call(this, excludeNames)
    735 }
    736 
    737 WebInspector.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  */
    756 WebInspector.TracingTimelineModel.TraceEventRecord = function(model, traceEvent)
    757 {
    758     this._model = model;
    759     this._event = traceEvent;
    760     traceEvent._timelineRecord = this;
    761     this._children = [];
    762 }
    763 
    764 WebInspector.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  */
    951 WebInspector.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 
    961 WebInspector.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  */
   1040 WebInspector.TracingTimelineSaver = function(stream)
   1041 {
   1042     this._stream = stream;
   1043 }
   1044 
   1045 WebInspector.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