Home | History | Annotate | Download | only in timeline
      1 /*
      2  * Copyright (C) 2013 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  */
     34 WebInspector.TimelineFrameModelBase = function()
     35 {
     36     this.reset();
     37 }
     38 
     39 WebInspector.TimelineFrameModelBase.prototype = {
     40     /**
     41      * @param {boolean} value
     42      */
     43     setMergeRecords: function(value)
     44     {
     45     },
     46 
     47     /**
     48      * @return {!Array.<!WebInspector.TimelineFrame>}
     49      */
     50     frames: function()
     51     {
     52         return this._frames;
     53     },
     54 
     55     /**
     56      * @param {number} startTime
     57      * @param {number} endTime
     58      * @return {!Array.<!WebInspector.TimelineFrame>}
     59      */
     60     filteredFrames: function(startTime, endTime)
     61     {
     62         /**
     63          * @param {number} value
     64          * @param {!WebInspector.TimelineFrame} object
     65          * @return {number}
     66          */
     67         function compareStartTime(value, object)
     68         {
     69             return value - object.startTime;
     70         }
     71         /**
     72          * @param {number} value
     73          * @param {!WebInspector.TimelineFrame} object
     74          * @return {number}
     75          */
     76         function compareEndTime(value, object)
     77         {
     78             return value - object.endTime;
     79         }
     80         var frames = this._frames;
     81         var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, frames, compareEndTime);
     82         var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, frames, compareStartTime);
     83         return frames.slice(firstFrame, lastFrame);
     84     },
     85 
     86     reset: function()
     87     {
     88         this._minimumRecordTime = Infinity;
     89         this._frames = [];
     90         this._lastFrame = null;
     91         this._lastLayerTree = null;
     92         this._hasThreadedCompositing = false;
     93         this._mainFrameCommitted = false;
     94         this._mainFrameRequested = false;
     95         this._framePendingCommit = null;
     96     },
     97 
     98     /**
     99      * @param {number} startTime
    100      */
    101     handleBeginFrame: function(startTime)
    102     {
    103         if (!this._lastFrame)
    104             this._startBackgroundFrame(startTime);
    105     },
    106 
    107     /**
    108      * @param {number} startTime
    109      */
    110     handleDrawFrame: function(startTime)
    111     {
    112         if (!this._lastFrame) {
    113             this._startBackgroundFrame(startTime);
    114             return;
    115         }
    116 
    117         // - if it wasn't drawn, it didn't happen!
    118         // - only show frames that either did not wait for the main thread frame or had one committed.
    119         if (this._mainFrameCommitted || !this._mainFrameRequested)
    120             this._startBackgroundFrame(startTime);
    121         this._mainFrameCommitted = false;
    122     },
    123 
    124     handleActivateLayerTree: function()
    125     {
    126         if (!this._lastFrame)
    127             return;
    128         this._mainFrameRequested = false;
    129         this._mainFrameCommitted = true;
    130         if (this._framePendingActivation) {
    131             this._lastFrame._addTimeForCategories(this._framePendingActivation.timeByCategory);
    132             this._lastFrame.paints = this._framePendingActivation.paints;
    133             this._framePendingActivation = null;
    134         }
    135     },
    136 
    137     handleRequestMainThreadFrame: function()
    138     {
    139         if (!this._lastFrame)
    140             return;
    141         this._mainFrameRequested = true;
    142     },
    143 
    144     handleCompositeLayers: function()
    145     {
    146         if (!this._hasThreadedCompositing || !this._framePendingCommit)
    147             return;
    148         this._framePendingActivation = this._framePendingCommit;
    149         this._framePendingCommit = null;
    150     },
    151 
    152     /**
    153      * @param {!WebInspector.DeferredLayerTree} layerTree
    154      */
    155     handleLayerTreeSnapshot: function(layerTree)
    156     {
    157         this._lastLayerTree = layerTree;
    158     },
    159 
    160     /**
    161      * @param {number} startTime
    162      */
    163     _startBackgroundFrame: function(startTime)
    164     {
    165         if (!this._hasThreadedCompositing) {
    166             this._lastFrame = null;
    167             this._hasThreadedCompositing = true;
    168         }
    169         if (this._lastFrame)
    170             this._flushFrame(this._lastFrame, startTime);
    171 
    172         this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime);
    173     },
    174 
    175     /**
    176      * @param {number} startTime
    177      */
    178     _startMainThreadFrame: function(startTime)
    179     {
    180         if (this._lastFrame)
    181             this._flushFrame(this._lastFrame, startTime);
    182         this._lastFrame = new WebInspector.TimelineFrame(startTime, startTime - this._minimumRecordTime);
    183     },
    184 
    185     /**
    186      * @param {!WebInspector.TimelineFrame} frame
    187      * @param {number} endTime
    188      */
    189     _flushFrame: function(frame, endTime)
    190     {
    191         frame._setLayerTree(this._lastLayerTree);
    192         frame._setEndTime(endTime);
    193         this._frames.push(frame);
    194     },
    195 
    196     /**
    197      * @param {!Array.<string>} types
    198      * @param {!WebInspector.TimelineModel.Record} record
    199      * @return {?WebInspector.TimelineModel.Record} record
    200      */
    201     _findRecordRecursively: function(types, record)
    202     {
    203         if (types.indexOf(record.type()) >= 0)
    204             return record;
    205         if (!record.children())
    206             return null;
    207         for (var i = 0; i < record.children().length; ++i) {
    208             var result = this._findRecordRecursively(types, record.children()[i]);
    209             if (result)
    210                 return result;
    211         }
    212         return null;
    213     }
    214 }
    215 
    216 /**
    217  * @constructor
    218  * @extends {WebInspector.TimelineFrameModelBase}
    219  */
    220 WebInspector.TimelineFrameModel = function()
    221 {
    222     WebInspector.TimelineFrameModelBase.call(this);
    223 }
    224 
    225 WebInspector.TimelineFrameModel._mainFrameMarkers = [
    226     WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation,
    227     WebInspector.TimelineModel.RecordType.InvalidateLayout,
    228     WebInspector.TimelineModel.RecordType.BeginFrame,
    229     WebInspector.TimelineModel.RecordType.ScrollLayer
    230 ];
    231 
    232 WebInspector.TimelineFrameModel.prototype = {
    233     reset: function()
    234     {
    235         this._mergeRecords = true;
    236         this._mergingBuffer = new WebInspector.TimelineMergingRecordBuffer();
    237         WebInspector.TimelineFrameModelBase.prototype.reset.call(this);
    238     },
    239 
    240     /**
    241      * @param {boolean} value
    242      */
    243     setMergeRecords: function(value)
    244     {
    245         this._mergeRecords = value;
    246     },
    247 
    248     /**
    249      * @param {!Array.<!WebInspector.TimelineModel.Record>} records
    250      */
    251     addRecords: function(records)
    252     {
    253         if (!records.length)
    254             return;
    255         if (records[0].startTime() < this._minimumRecordTime)
    256             this._minimumRecordTime = records[0].startTime();
    257         for (var i = 0; i < records.length; ++i)
    258             this.addRecord(records[i]);
    259     },
    260 
    261     /**
    262      * @param {!WebInspector.TimelineModel.Record} record
    263      */
    264     addRecord: function(record)
    265     {
    266         var recordTypes = WebInspector.TimelineModel.RecordType;
    267         var programRecord = record.type() === recordTypes.Program ? record : null;
    268 
    269         // Start collecting main frame
    270         if (programRecord) {
    271             if (!this._framePendingCommit && this._findRecordRecursively(WebInspector.TimelineFrameModel._mainFrameMarkers, programRecord))
    272                 this._framePendingCommit = new WebInspector.PendingFrame();
    273         }
    274         /** type {Array.<!WebInspector.TimelineModel.Record>} */
    275         var records = [];
    276         if (!this._mergeRecords)
    277             records = [record];
    278         else
    279             records = this._mergingBuffer.process(record.thread(), /** type {Array.<!WebInspector.TimelineModel.Record>} */(programRecord ? record.children() || [] : [record]));
    280         for (var i = 0; i < records.length; ++i) {
    281             if (records[i].thread() === WebInspector.TimelineModel.MainThreadName)
    282                 this._addMainThreadRecord(programRecord, records[i]);
    283             else
    284                 this._addBackgroundRecord(records[i]);
    285         }
    286     },
    287 
    288     /**
    289      * @param {!WebInspector.TimelineModel.Record} record
    290      */
    291     _addBackgroundRecord: function(record)
    292     {
    293         var recordTypes = WebInspector.TimelineModel.RecordType;
    294         if (record.type() === recordTypes.BeginFrame)
    295             this.handleBeginFrame(record.startTime());
    296         else if (record.type() === recordTypes.DrawFrame)
    297             this.handleDrawFrame(record.startTime());
    298         else if (record.type() === recordTypes.RequestMainThreadFrame)
    299             this.handleRequestMainThreadFrame();
    300         else if (record.type() === recordTypes.ActivateLayerTree)
    301             this.handleActivateLayerTree();
    302 
    303         if (this._lastFrame)
    304             this._lastFrame._addTimeFromRecord(record);
    305     },
    306 
    307     /**
    308      * @param {?WebInspector.TimelineModel.Record} programRecord
    309      * @param {!WebInspector.TimelineModel.Record} record
    310      */
    311     _addMainThreadRecord: function(programRecord, record)
    312     {
    313         var recordTypes = WebInspector.TimelineModel.RecordType;
    314         if (record.type() === recordTypes.UpdateLayerTree && record.data()["layerTree"])
    315             this.handleLayerTreeSnapshot(new WebInspector.DeferredAgentLayerTree(record.target(), record.data()["layerTree"]));
    316         if (!this._hasThreadedCompositing) {
    317             if (record.type() === recordTypes.BeginFrame)
    318                 this._startMainThreadFrame(record.startTime());
    319 
    320             if (!this._lastFrame)
    321                 return;
    322 
    323             this._lastFrame._addTimeFromRecord(record);
    324 
    325             // Account for "other" time at the same time as the first child.
    326             if (programRecord.children()[0] === record)
    327                 this._lastFrame._addTimeForCategory("other", this._deriveOtherTime(programRecord));
    328             return;
    329         }
    330 
    331         if (!this._framePendingCommit)
    332             return;
    333 
    334         WebInspector.TimelineUIUtilsImpl.aggregateTimeForRecord(this._framePendingCommit.timeByCategory, record);
    335         if (programRecord.children()[0] === record)
    336             this._framePendingCommit.timeByCategory["other"] = (this._framePendingCommit.timeByCategory["other"] || 0) + this._deriveOtherTime(programRecord);
    337 
    338         if (record.type() === recordTypes.CompositeLayers)
    339             this.handleCompositeLayers();
    340     },
    341 
    342     /**
    343      * @param {!WebInspector.TimelineModel.Record} programRecord
    344      * @return {number}
    345      */
    346     _deriveOtherTime: function(programRecord)
    347     {
    348         var accounted = 0;
    349         for (var i = 0; i < programRecord.children().length; ++i)
    350             accounted += programRecord.children()[i].endTime() - programRecord.children()[i].startTime();
    351         return programRecord.endTime() - programRecord.startTime() - accounted;
    352     },
    353 
    354     __proto__: WebInspector.TimelineFrameModelBase.prototype,
    355 };
    356 
    357 /**
    358  * @constructor
    359  * @extends {WebInspector.TimelineFrameModelBase}
    360  */
    361 WebInspector.TracingTimelineFrameModel = function()
    362 {
    363     WebInspector.TimelineFrameModelBase.call(this);
    364 }
    365 
    366 WebInspector.TracingTimelineFrameModel._mainFrameMarkers = [
    367     WebInspector.TracingTimelineModel.RecordType.ScheduleStyleRecalculation,
    368     WebInspector.TracingTimelineModel.RecordType.InvalidateLayout,
    369     WebInspector.TracingTimelineModel.RecordType.BeginMainThreadFrame,
    370     WebInspector.TracingTimelineModel.RecordType.ScrollLayer
    371 ];
    372 
    373 WebInspector.TracingTimelineFrameModel.prototype = {
    374     /**
    375      * @param {!Array.<!WebInspector.TracingModel.Event>} events
    376      * @param {string} sessionId
    377      */
    378     addTraceEvents: function(events, sessionId)
    379     {
    380         this._sessionId = sessionId;
    381         if (!events.length)
    382             return;
    383         if (events[0].startTime < this._minimumRecordTime)
    384             this._minimumRecordTime = events[0].startTime;
    385         for (var i = 0; i < events.length; ++i)
    386             this._addTraceEvent(events[i]);
    387     },
    388 
    389     /**
    390      * @param {!WebInspector.TracingModel.Event} event
    391      */
    392     _addTraceEvent: function(event)
    393     {
    394         var eventNames = WebInspector.TracingTimelineModel.RecordType;
    395 
    396         if (event.name === eventNames.SetLayerTreeId) {
    397             if (this._sessionId === event.args["sessionId"])
    398                 this._layerTreeId = event.args["layerTreeId"];
    399             return;
    400         }
    401         if (event.name === eventNames.TracingStartedInPage) {
    402             this._mainThread = event.thread;
    403             return;
    404         }
    405         if (event.thread === this._mainThread)
    406             this._addMainThreadTraceEvent(event);
    407         else
    408             this._addBackgroundTraceEvent(event);
    409     },
    410 
    411     /**
    412      * @param {!WebInspector.TracingModel.Event} event
    413      */
    414     _addBackgroundTraceEvent: function(event)
    415     {
    416         var eventNames = WebInspector.TracingTimelineModel.RecordType;
    417         if (event.phase === WebInspector.TracingModel.Phase.SnapshotObject && event.name === eventNames.LayerTreeHostImplSnapshot && parseInt(event.id, 0) === this._layerTreeId) {
    418             var snapshot = /** @type {!WebInspector.TracingModel.ObjectSnapshot} */ (event);
    419             this.handleLayerTreeSnapshot(new WebInspector.DeferredTracingLayerTree(snapshot));
    420             return;
    421         }
    422         if (this._lastFrame && event.selfTime)
    423             this._lastFrame._addTimeForCategory(WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name, event.selfTime);
    424 
    425         if (event.args["layerTreeId"] !== this._layerTreeId)
    426             return;
    427 
    428         var timestamp = event.startTime;
    429         if (event.name === eventNames.BeginFrame)
    430             this.handleBeginFrame(timestamp);
    431         else if (event.name === eventNames.DrawFrame)
    432             this.handleDrawFrame(timestamp);
    433         else if (event.name === eventNames.ActivateLayerTree)
    434             this.handleActivateLayerTree();
    435         else if (event.name === eventNames.RequestMainThreadFrame)
    436             this.handleRequestMainThreadFrame();
    437     },
    438 
    439     /**
    440      * @param {!WebInspector.TracingModel.Event} event
    441      */
    442     _addMainThreadTraceEvent: function(event)
    443     {
    444         var eventNames = WebInspector.TracingTimelineModel.RecordType;
    445         var timestamp = event.startTime;
    446         var selfTime = event.selfTime || 0;
    447 
    448         if (!this._hasThreadedCompositing) {
    449             if (event.name === eventNames.BeginMainThreadFrame)
    450                 this._startMainThreadFrame(timestamp);
    451             if (!this._lastFrame)
    452                 return;
    453             if (!selfTime)
    454                 return;
    455 
    456             var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name;
    457             this._lastFrame._addTimeForCategory(categoryName, selfTime);
    458             return;
    459         }
    460 
    461         if (!this._framePendingCommit && WebInspector.TracingTimelineFrameModel._mainFrameMarkers.indexOf(event.name) >= 0)
    462             this._framePendingCommit = new WebInspector.PendingFrame();
    463         if (!this._framePendingCommit)
    464             return;
    465         if (event.name === eventNames.Paint && event.args["data"]["layerId"] && event.picture)
    466             this._framePendingCommit.paints.push(new WebInspector.LayerPaintEvent(event));
    467 
    468         if (selfTime) {
    469             var categoryName = WebInspector.TracingTimelineUIUtils.eventStyle(event).category.name;
    470             this._framePendingCommit.timeByCategory[categoryName] = (this._framePendingCommit.timeByCategory[categoryName] || 0) + selfTime;
    471         }
    472         if (event.name === eventNames.CompositeLayers && event.args["layerTreeId"] === this._layerTreeId)
    473             this.handleCompositeLayers();
    474     },
    475 
    476     __proto__: WebInspector.TimelineFrameModelBase.prototype
    477 }
    478 
    479 /**
    480  * @constructor
    481  * @extends {WebInspector.DeferredLayerTree}
    482  * @param {!WebInspector.TracingModel.ObjectSnapshot} snapshot
    483  */
    484 WebInspector.DeferredTracingLayerTree = function(snapshot)
    485 {
    486     WebInspector.DeferredLayerTree.call(this, snapshot.thread.target());
    487     this._snapshot = snapshot;
    488 }
    489 
    490 WebInspector.DeferredTracingLayerTree.prototype = {
    491     /**
    492      * @param {function(!WebInspector.LayerTreeBase)} callback
    493      */
    494     resolve: function(callback)
    495     {
    496         this._snapshot.requestObject(onGotObject.bind(this));
    497         /**
    498          * @this {WebInspector.DeferredTracingLayerTree}
    499          * @param {?Object} result
    500          */
    501         function onGotObject(result)
    502         {
    503             if (!result)
    504                 return;
    505             var viewport = result["device_viewport_size"];
    506             var rootLayer = result["active_tree"]["root_layer"];
    507             var layerTree = new WebInspector.TracingLayerTree(this._target);
    508             layerTree.setViewportSize(viewport);
    509             layerTree.setLayers(rootLayer, callback.bind(null, layerTree));
    510         }
    511     },
    512 
    513     __proto__: WebInspector.DeferredLayerTree.prototype
    514 };
    515 
    516 
    517 /**
    518  * @constructor
    519  * @param {!Array.<!WebInspector.TimelineFrame>} frames
    520  */
    521 WebInspector.FrameStatistics = function(frames)
    522 {
    523     this.frameCount = frames.length;
    524     this.minDuration = Infinity;
    525     this.maxDuration = 0;
    526     this.timeByCategory = {};
    527     this.startOffset = frames[0].startTimeOffset;
    528     var lastFrame = frames[this.frameCount - 1];
    529     this.endOffset = lastFrame.startTimeOffset + lastFrame.duration;
    530 
    531     var totalDuration = 0;
    532     var sumOfSquares = 0;
    533     for (var i = 0; i < this.frameCount; ++i) {
    534         var duration = frames[i].duration;
    535         totalDuration += duration;
    536         sumOfSquares += duration * duration;
    537         this.minDuration = Math.min(this.minDuration, duration);
    538         this.maxDuration = Math.max(this.maxDuration, duration);
    539         WebInspector.FrameStatistics._aggregateTimeByCategory(this.timeByCategory, frames[i].timeByCategory);
    540     }
    541     this.average = totalDuration / this.frameCount;
    542     var variance = sumOfSquares / this.frameCount - this.average * this.average;
    543     this.stddev = Math.sqrt(variance);
    544 }
    545 
    546 /**
    547  * @param {!Object} total
    548  * @param {!Object} addend
    549  */
    550 WebInspector.FrameStatistics._aggregateTimeByCategory = function(total, addend)
    551 {
    552     for (var category in addend)
    553         total[category] = (total[category] || 0) + addend[category];
    554 }
    555 
    556 /**
    557  * @constructor
    558  * @param {number} startTime
    559  * @param {number} startTimeOffset
    560  */
    561 WebInspector.TimelineFrame = function(startTime, startTimeOffset)
    562 {
    563     this.startTime = startTime;
    564     this.startTimeOffset = startTimeOffset;
    565     this.endTime = this.startTime;
    566     this.duration = 0;
    567     this.timeByCategory = {};
    568     this.cpuTime = 0;
    569     /** @type {?WebInspector.DeferredLayerTree} */
    570     this.layerTree = null;
    571     this.paintTiles = null;
    572 }
    573 
    574 WebInspector.TimelineFrame.prototype = {
    575     /**
    576      * @param {number} endTime
    577      */
    578     _setEndTime: function(endTime)
    579     {
    580         this.endTime = endTime;
    581         this.duration = this.endTime - this.startTime;
    582     },
    583 
    584     /**
    585      * @param {?WebInspector.DeferredLayerTree} layerTree
    586      */
    587     _setLayerTree: function(layerTree)
    588     {
    589         this.layerTree = layerTree;
    590     },
    591 
    592     /**
    593      * @param {!WebInspector.TimelineModel.Record} record
    594      */
    595     _addTimeFromRecord: function(record)
    596     {
    597         if (!record.endTime())
    598             return;
    599         var timeByCategory = {};
    600         WebInspector.TimelineUIUtilsImpl.aggregateTimeForRecord(timeByCategory, record);
    601         this._addTimeForCategories(timeByCategory);
    602     },
    603 
    604     /**
    605      * @param {!Object} timeByCategory
    606      */
    607     _addTimeForCategories: function(timeByCategory)
    608     {
    609         for (var category in timeByCategory)
    610             this._addTimeForCategory(category, timeByCategory[category]);
    611     },
    612 
    613     /**
    614      * @param {string} category
    615      * @param {number} time
    616      */
    617     _addTimeForCategory: function(category, time)
    618     {
    619         this.timeByCategory[category] = (this.timeByCategory[category] || 0) + time;
    620         this.cpuTime += time;
    621     },
    622 }
    623 
    624 /**
    625  * @constructor
    626  * @param {!WebInspector.TracingModel.Event} event
    627  */
    628 WebInspector.LayerPaintEvent = function(event)
    629 {
    630     this._event = event;
    631 }
    632 
    633 WebInspector.LayerPaintEvent.prototype = {
    634     /**
    635      * @return {string}
    636      */
    637     layerId: function()
    638     {
    639         return this._event.args["data"]["layerId"];
    640     },
    641 
    642     /**
    643      * @return {!WebInspector.TracingModel.Event}
    644      */
    645     event: function()
    646     {
    647         return this._event;
    648     },
    649 
    650     /**
    651      * @param {function(?Array.<number>, ?WebInspector.PaintProfilerSnapshot)} callback
    652      */
    653     loadPicture: function(callback)
    654     {
    655         var target = this._event.thread.target();
    656         this._event.picture.requestObject(onGotObject);
    657         /**
    658          * @param {?Object} result
    659          */
    660         function onGotObject(result)
    661         {
    662             if (!result || !result["skp64"]) {
    663                 callback(null, null);
    664                 return;
    665             }
    666             var rect = result["params"] && result["params"]["layer_rect"];
    667             WebInspector.PaintProfilerSnapshot.load(target, result["skp64"], callback.bind(null, rect));
    668         }
    669     }
    670 };
    671 
    672 /**
    673  * @constructor
    674  */
    675 WebInspector.PendingFrame = function()
    676 {
    677     /** @type {!Object.<string, number>} */
    678     this.timeByCategory = {};
    679     /** @type {!Array.<!WebInspector.LayerPaintEvent>} */
    680     this.paints = [];
    681 }
    682