Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2012 Google Inc. All rights reserved.
      3  * Copyright (C) 2012 Intel Inc. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  *     * Redistributions of source code must retain the above copyright
     10  * notice, this list of conditions and the following disclaimer.
     11  *     * Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *     * Neither the name of Google Inc. nor the names of its
     16  * contributors may be used to endorse or promote products derived from
     17  * this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /**
     33  * @constructor
     34  * @extends {WebInspector.Object}
     35  */
     36 WebInspector.TimelinePresentationModel = function()
     37 {
     38     this._linkifier = new WebInspector.Linkifier();
     39     this._glueRecords = false;
     40     this._filters = [];
     41     this.reset();
     42 }
     43 
     44 WebInspector.TimelinePresentationModel.categories = function()
     45 {
     46     if (WebInspector.TimelinePresentationModel._categories)
     47         return WebInspector.TimelinePresentationModel._categories;
     48     WebInspector.TimelinePresentationModel._categories = {
     49         loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "#5A8BCC", "#8EB6E9", "#70A2E3"),
     50         scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "#D8AA34", "#F3D07A", "#F1C453"),
     51         rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "#8266CC", "#AF9AEB", "#9A7EE6"),
     52         painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "#5FA050", "#8DC286", "#71B363"),
     53         other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "#BBBBBB", "#DDDDDD", "#DDDDDD")
     54     };
     55     return WebInspector.TimelinePresentationModel._categories;
     56 };
     57 
     58 /**
     59  * @return {!Object.<string, {title: string, category}>}
     60  */
     61 WebInspector.TimelinePresentationModel._initRecordStyles = function()
     62 {
     63     if (WebInspector.TimelinePresentationModel._recordStylesMap)
     64         return WebInspector.TimelinePresentationModel._recordStylesMap;
     65 
     66     var recordTypes = WebInspector.TimelineModel.RecordType;
     67     var categories = WebInspector.TimelinePresentationModel.categories();
     68 
     69     var recordStyles = {};
     70     recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] };
     71     recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] };
     72     recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] };
     73     recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] };
     74     recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] };
     75     recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] };
     76     recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] };
     77     recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] };
     78     recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] };
     79     recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
     80     recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Rasterize"), category: categories["painting"] };
     81     recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] };
     82     recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] };
     83     recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] };
     84     recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] };
     85     recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] };
     86     recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] };
     87     recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] };
     88     recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] };
     89     recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] };
     90     recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] };
     91     recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] };
     92     recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] };
     93     recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] };
     94     recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] };
     95     recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] };
     96     recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] };
     97     recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] };
     98     recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] };
     99     recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] };
    100     recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] };
    101     recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] };
    102     recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] };
    103     recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] };
    104     recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] };
    105     recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] };
    106     recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] };
    107     recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] };
    108     recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] };
    109     recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] };
    110     recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] };
    111 
    112     WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles;
    113     return recordStyles;
    114 }
    115 
    116 /**
    117  * @param {Object} record
    118  */
    119 WebInspector.TimelinePresentationModel.recordStyle = function(record)
    120 {
    121     var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles();
    122     var result = recordStyles[record.type];
    123     if (!result) {
    124         result = {
    125             title: WebInspector.UIString("Unknown: %s", record.type),
    126             category: WebInspector.TimelinePresentationModel.categories()["other"]
    127         };
    128         recordStyles[record.type] = result;
    129     }
    130     return result;
    131 }
    132 
    133 WebInspector.TimelinePresentationModel.categoryForRecord = function(record)
    134 {
    135     return WebInspector.TimelinePresentationModel.recordStyle(record).category;
    136 }
    137 
    138 WebInspector.TimelinePresentationModel.isEventDivider = function(record)
    139 {
    140     var recordTypes = WebInspector.TimelineModel.RecordType;
    141     if (record.type === recordTypes.TimeStamp)
    142         return true;
    143     if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
    144         if (record.data && ((typeof record.data.isMainFrame) === "boolean"))
    145             return record.data.isMainFrame;
    146     }
    147     return false;
    148 }
    149 
    150 /**
    151  * @param {Array} recordsArray
    152  * @param {?function(*)} preOrderCallback
    153  * @param {function(*)=} postOrderCallback
    154  */
    155 WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
    156 {
    157     if (!recordsArray)
    158         return;
    159     var stack = [{array: recordsArray, index: 0}];
    160     while (stack.length) {
    161         var entry = stack[stack.length - 1];
    162         var records = entry.array;
    163         if (entry.index < records.length) {
    164              var record = records[entry.index];
    165              if (preOrderCallback && preOrderCallback(record))
    166                  return;
    167              if (record.children)
    168                  stack.push({array: record.children, index: 0, record: record});
    169              else if (postOrderCallback && postOrderCallback(record))
    170                 return;
    171              ++entry.index;
    172         } else {
    173             if (entry.record && postOrderCallback && postOrderCallback(entry.record))
    174                 return;
    175             stack.pop();
    176         }
    177     }
    178 }
    179 
    180 /**
    181  * @param {string=} recordType
    182  * @return {boolean}
    183  */
    184 WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType)
    185 {
    186     if (!recordType)
    187         return false;
    188     const recordTypes = WebInspector.TimelineModel.RecordType;
    189     switch (recordType) {
    190     case recordTypes.ScheduleResourceRequest:
    191     case recordTypes.ResourceSendRequest:
    192     case recordTypes.ResourceReceiveResponse:
    193     case recordTypes.ResourceReceivedData:
    194     case recordTypes.ResourceFinish:
    195         return true;
    196     default:
    197         return false;
    198     }
    199 }
    200 
    201 /**
    202  * @param {string} recordType
    203  * @param {string=} title
    204  */
    205 WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title)
    206 {
    207     var eventDivider = document.createElement("div");
    208     eventDivider.className = "resources-event-divider";
    209     var recordTypes = WebInspector.TimelineModel.RecordType;
    210 
    211     if (recordType === recordTypes.MarkDOMContent)
    212         eventDivider.className += " resources-blue-divider";
    213     else if (recordType === recordTypes.MarkLoad)
    214         eventDivider.className += " resources-red-divider";
    215     else if (recordType === recordTypes.TimeStamp)
    216         eventDivider.className += " resources-orange-divider";
    217     else if (recordType === recordTypes.BeginFrame)
    218         eventDivider.className += " timeline-frame-divider";
    219 
    220     if (title)
    221         eventDivider.title = title;
    222 
    223     return eventDivider;
    224 }
    225 
    226 WebInspector.TimelinePresentationModel._hiddenRecords = { }
    227 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1;
    228 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1;
    229 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1;
    230 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1;
    231 
    232 WebInspector.TimelinePresentationModel.prototype = {
    233     /**
    234      * @param {!WebInspector.TimelinePresentationModel.Filter} filter
    235      */
    236     addFilter: function(filter)
    237     {
    238         this._filters.push(filter);
    239     },
    240 
    241     /**
    242      * @param {!WebInspector.TimelinePresentationModel.Filter} filter
    243      */
    244     removeFilter: function(filter)
    245     {
    246         var index = this._filters.indexOf(filter);
    247         if (index !== -1)
    248             this._filters.splice(index, 1);
    249     },
    250 
    251     rootRecord: function()
    252     {
    253         return this._rootRecord;
    254     },
    255 
    256     frames: function()
    257     {
    258         return this._frames;
    259     },
    260 
    261     reset: function()
    262     {
    263         this._linkifier.reset();
    264         this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false);
    265         this._sendRequestRecords = {};
    266         this._scheduledResourceRequests = {};
    267         this._timerRecords = {};
    268         this._requestAnimationFrameRecords = {};
    269         this._eventDividerRecords = [];
    270         this._timeRecords = {};
    271         this._timeRecordStack = [];
    272         this._frames = [];
    273         this._minimumRecordTime = -1;
    274         this._layoutInvalidateStack = {};
    275         this._lastScheduleStyleRecalculation = {};
    276         this._webSocketCreateRecords = {};
    277         this._coalescingBuckets = {};
    278     },
    279 
    280     addFrame: function(frame)
    281     {
    282         this._frames.push(frame);
    283     },
    284 
    285     addRecord: function(record)
    286     {
    287         if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime)
    288             this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record);
    289 
    290         var records;
    291         if (record.type === WebInspector.TimelineModel.RecordType.Program)
    292             records = record.children;
    293         else
    294             records = [record];
    295 
    296         var formattedRecords = [];
    297         var recordsCount = records.length;
    298         for (var i = 0; i < recordsCount; ++i)
    299             formattedRecords.push(this._innerAddRecord(records[i], this._rootRecord));
    300         return formattedRecords;
    301     },
    302 
    303     _innerAddRecord: function(record, parentRecord)
    304     {
    305         const recordTypes = WebInspector.TimelineModel.RecordType;
    306         var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords;
    307         var origin;
    308         var coalescingBucket;
    309 
    310         if (!isHiddenRecord) {
    311             var newParentRecord = this._findParentRecord(record);
    312             if (newParentRecord) {
    313                 origin = parentRecord;
    314                 parentRecord = newParentRecord;
    315             }
    316             // On main thread, only coalesce if the last event is of same type.
    317             if (parentRecord === this._rootRecord)
    318                 coalescingBucket = record.thread ? record.type : "mainThread";
    319             var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket);
    320             if (coalescedRecord) {
    321                 if (!origin)
    322                     origin = parentRecord;
    323                 parentRecord = coalescedRecord;
    324             }
    325         }
    326 
    327         var children = record.children;
    328         var scriptDetails;
    329         if (record.data && record.data["scriptName"]) {
    330             scriptDetails = {
    331                 scriptName: record.data["scriptName"],
    332                 scriptLine: record.data["scriptLine"]
    333             }
    334         };
    335 
    336         if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) {
    337             var childRecord = children[0];
    338             if (childRecord.type === recordTypes.FunctionCall) {
    339                 scriptDetails = {
    340                     scriptName: childRecord.data["scriptName"],
    341                     scriptLine: childRecord.data["scriptLine"]
    342                 };
    343                 children = childRecord.children.concat(children.slice(1));
    344             }
    345         }
    346 
    347         var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord);
    348 
    349         if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord))
    350             this._eventDividerRecords.push(formattedRecord);
    351 
    352         if (isHiddenRecord)
    353             return formattedRecord;
    354 
    355         formattedRecord.collapsed = parentRecord === this._rootRecord;
    356         if (coalescingBucket)
    357             this._coalescingBuckets[coalescingBucket] = formattedRecord;
    358 
    359         var childrenCount = children ? children.length : 0;
    360         for (var i = 0; i < childrenCount; ++i)
    361             this._innerAddRecord(children[i], formattedRecord);
    362 
    363         formattedRecord.calculateAggregatedStats();
    364 
    365         if (origin)
    366             this._updateAncestorStats(formattedRecord);
    367 
    368         if (parentRecord.coalesced && parentRecord.startTime > formattedRecord.startTime)
    369             parentRecord._record.startTime = record.startTime;
    370 
    371         origin = formattedRecord.origin();
    372         if (!origin.isRoot() && !origin.coalesced)
    373             origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime;
    374         return formattedRecord;
    375     },
    376 
    377     /**
    378      * @param {WebInspector.TimelinePresentationModel.Record} record
    379      */
    380     _updateAncestorStats: function(record)
    381     {
    382         var lastChildEndTime = record.lastChildEndTime;
    383         var aggregatedStats = record.aggregatedStats;
    384         for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) {
    385             currentRecord._cpuTime += record._cpuTime;
    386             if (currentRecord.lastChildEndTime < lastChildEndTime)
    387                 currentRecord.lastChildEndTime = lastChildEndTime;
    388             for (var category in aggregatedStats)
    389                 currentRecord.aggregatedStats[category] += aggregatedStats[category];
    390         }
    391     },
    392 
    393     /**
    394      * @param {Object} record
    395      * @param {Object} newParent
    396      * @param {String} bucket
    397      * @return {WebInspector.TimelinePresentationModel.Record?}
    398      */
    399     _findCoalescedParent: function(record, newParent, bucket)
    400     {
    401         const coalescingThresholdSeconds = 0.005;
    402 
    403         var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent.children.peekLast();
    404         if (lastRecord && lastRecord.coalesced)
    405             lastRecord = lastRecord.children.peekLast();
    406         var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
    407         var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
    408         if (!lastRecord)
    409             return null;
    410         if (lastRecord.type !== record.type)
    411             return null;
    412         if (lastRecord.endTime + coalescingThresholdSeconds < startTime)
    413             return null;
    414         if (endTime + coalescingThresholdSeconds < lastRecord.startTime)
    415             return null;
    416         if (WebInspector.TimelinePresentationModel.coalescingKeyForRecord(record) !== WebInspector.TimelinePresentationModel.coalescingKeyForRecord(lastRecord._record))
    417             return null;
    418         if (lastRecord.parent.coalesced)
    419             return lastRecord.parent;
    420         return this._replaceWithCoalescedRecord(lastRecord);
    421     },
    422 
    423     /**
    424      * @param {WebInspector.TimelinePresentationModel.Record} record
    425      * @return {WebInspector.TimelinePresentationModel.Record}
    426      */
    427     _replaceWithCoalescedRecord: function(record)
    428     {
    429         var rawRecord = {
    430             type: record._record.type,
    431             startTime: record._record.startTime,
    432             endTime: record._record.endTime,
    433             data: { }
    434         };
    435         if (record._record.thread)
    436             rawRecord.thread = "aggregated";
    437         if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp)
    438             rawRecord.data.message = record.data.message;
    439 
    440         var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false);
    441         var parent = record.parent;
    442 
    443         coalescedRecord.coalesced = true;
    444         coalescedRecord.collapsed = true;
    445         coalescedRecord._children.push(record);
    446         record.parent = coalescedRecord;
    447         coalescedRecord.calculateAggregatedStats();
    448         if (record.hasWarning || record.childHasWarning)
    449             coalescedRecord.childHasWarning = true;
    450 
    451         coalescedRecord.parent = parent;
    452         parent._children[parent._children.indexOf(record)] = coalescedRecord;
    453         return coalescedRecord;
    454     },
    455 
    456     _findParentRecord: function(record)
    457     {
    458         if (!this._glueRecords)
    459             return null;
    460         var recordTypes = WebInspector.TimelineModel.RecordType;
    461 
    462         switch (record.type) {
    463         case recordTypes.ResourceReceiveResponse:
    464         case recordTypes.ResourceFinish:
    465         case recordTypes.ResourceReceivedData:
    466             return this._sendRequestRecords[record.data["requestId"]];
    467 
    468         case recordTypes.ResourceSendRequest:
    469             return this._rootRecord;
    470 
    471         case recordTypes.TimerFire:
    472             return this._timerRecords[record.data["timerId"]];
    473 
    474         case recordTypes.ResourceSendRequest:
    475             return this._scheduledResourceRequests[record.data["url"]];
    476 
    477         case recordTypes.FireAnimationFrame:
    478             return this._requestAnimationFrameRecords[record.data["id"]];
    479 
    480         case recordTypes.Time:
    481             return this._rootRecord;
    482 
    483         case recordTypes.TimeEnd:
    484             return this._timeRecords[record.data["message"]];
    485         }
    486     },
    487 
    488     setGlueRecords: function(glue)
    489     {
    490         this._glueRecords = glue;
    491     },
    492 
    493     invalidateFilteredRecords: function()
    494     {
    495         delete this._filteredRecords;
    496     },
    497 
    498     filteredRecords: function()
    499     {
    500         if (this._filteredRecords)
    501             return this._filteredRecords;
    502 
    503         var recordsInWindow = [];
    504 
    505         var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false}];
    506         while (stack.length) {
    507             var entry = stack[stack.length - 1];
    508             var records = entry.children;
    509             if (records && entry.index < records.length) {
    510                  var record = records[entry.index];
    511                  ++entry.index;
    512 
    513                  if (this.isVisible(record)) {
    514                      ++record.parent._invisibleChildrenCount;
    515                      if (!entry.parentIsCollapsed)
    516                          recordsInWindow.push(record);
    517                  }
    518 
    519                  record._invisibleChildrenCount = 0;
    520 
    521                  stack.push({children: record.children,
    522                              index: 0,
    523                              parentIsCollapsed: (entry.parentIsCollapsed || record.collapsed),
    524                              parentRecord: record,
    525                              windowLengthBeforeChildrenTraversal: recordsInWindow.length});
    526             } else {
    527                 stack.pop();
    528                 if (entry.parentRecord)
    529                     entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
    530             }
    531         }
    532 
    533         this._filteredRecords = recordsInWindow;
    534         return recordsInWindow;
    535     },
    536 
    537     filteredFrames: function(startTime, endTime)
    538     {
    539         function compareStartTime(value, object)
    540         {
    541             return value - object.startTime;
    542         }
    543         function compareEndTime(value, object)
    544         {
    545             return value - object.endTime;
    546         }
    547         var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime);
    548         var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime);
    549         while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime)
    550             ++lastFrame;
    551         return this._frames.slice(firstFrame, lastFrame);
    552     },
    553 
    554     eventDividerRecords: function()
    555     {
    556         return this._eventDividerRecords;
    557     },
    558 
    559     isVisible: function(record)
    560     {
    561         for (var i = 0; i < this._filters.length; ++i) {
    562             if (!this._filters[i].accept(record))
    563                 return false;
    564         }
    565         return true;
    566     },
    567 
    568     /**
    569      * @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info
    570      * @return {!Element}
    571      */
    572     generateMainThreadBarPopupContent: function(info)
    573     {
    574         var firstTaskIndex = info.firstTaskIndex;
    575         var lastTaskIndex = info.lastTaskIndex;
    576         var tasks = info.tasks;
    577         var messageCount = lastTaskIndex - firstTaskIndex + 1;
    578         var cpuTime = 0;
    579 
    580         for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
    581             var task = tasks[i];
    582             cpuTime += task.endTime - task.startTime;
    583         }
    584         var startTime = tasks[firstTaskIndex].startTime;
    585         var endTime = tasks[lastTaskIndex].endTime;
    586         var duration = endTime - startTime;
    587         var offset = this._minimumRecordTime;
    588 
    589         var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("CPU"));
    590         var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true),
    591             Number.secondsToString(startTime - offset, true));
    592         contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
    593         contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true));
    594         contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount);
    595         return contentHelper.contentTable();
    596     },
    597 
    598     __proto__: WebInspector.Object.prototype
    599 }
    600 
    601 /**
    602  * @constructor
    603  * @param {WebInspector.TimelinePresentationModel} presentationModel
    604  * @param {Object} record
    605  * @param {WebInspector.TimelinePresentationModel.Record} parentRecord
    606  * @param {WebInspector.TimelinePresentationModel.Record} origin
    607  * @param {Object|undefined} scriptDetails
    608  * @param {boolean} hidden
    609  */
    610 WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden)
    611 {
    612     this._linkifier = presentationModel._linkifier;
    613     this._aggregatedStats = {};
    614     this._record = record;
    615     this._children = [];
    616     if (!hidden && parentRecord) {
    617         this.parent = parentRecord;
    618         if (this.isBackground)
    619             WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this);
    620         else
    621             parentRecord.children.push(this);
    622     }
    623     if (origin)
    624         this._origin = origin;
    625 
    626     this._selfTime = this.endTime - this.startTime;
    627     this._lastChildEndTime = this.endTime;
    628     this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime;
    629 
    630     if (record.data) {
    631         if (record.data["url"])
    632             this.url = record.data["url"];
    633         if (record.data["layerRootNode"])
    634             this._relatedBackendNodeId = record.data["layerRootNode"];
    635     }
    636     if (scriptDetails) {
    637         this.scriptName = scriptDetails.scriptName;
    638         this.scriptLine = scriptDetails.scriptLine;
    639     }
    640     if (parentRecord && parentRecord.callSiteStackTrace)
    641         this.callSiteStackTrace = parentRecord.callSiteStackTrace;
    642 
    643     var recordTypes = WebInspector.TimelineModel.RecordType;
    644     switch (record.type) {
    645     case recordTypes.ResourceSendRequest:
    646         // Make resource receive record last since request was sent; make finish record last since response received.
    647         presentationModel._sendRequestRecords[record.data["requestId"]] = this;
    648         break;
    649 
    650     case recordTypes.ScheduleResourceRequest:
    651         presentationModel._scheduledResourceRequests[record.data["url"]] = this;
    652         break;
    653 
    654     case recordTypes.ResourceReceiveResponse:
    655         var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
    656         if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
    657             this.url = sendRequestRecord.url;
    658             // Now that we have resource in the collection, recalculate details in order to display short url.
    659             sendRequestRecord._refreshDetails();
    660             if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
    661                 sendRequestRecord.parent._refreshDetails();
    662         }
    663         break;
    664 
    665     case recordTypes.ResourceReceivedData:
    666     case recordTypes.ResourceFinish:
    667         var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
    668         if (sendRequestRecord) // False for main resource.
    669             this.url = sendRequestRecord.url;
    670         break;
    671 
    672     case recordTypes.TimerInstall:
    673         this.timeout = record.data["timeout"];
    674         this.singleShot = record.data["singleShot"];
    675         presentationModel._timerRecords[record.data["timerId"]] = this;
    676         break;
    677 
    678     case recordTypes.TimerFire:
    679         var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]];
    680         if (timerInstalledRecord) {
    681             this.callSiteStackTrace = timerInstalledRecord.stackTrace;
    682             this.timeout = timerInstalledRecord.timeout;
    683             this.singleShot = timerInstalledRecord.singleShot;
    684         }
    685         break;
    686 
    687     case recordTypes.RequestAnimationFrame:
    688         presentationModel._requestAnimationFrameRecords[record.data["id"]] = this;
    689         break;
    690 
    691     case recordTypes.FireAnimationFrame:
    692         var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]];
    693         if (requestAnimationRecord)
    694             this.callSiteStackTrace = requestAnimationRecord.stackTrace;
    695         break;
    696 
    697     case recordTypes.Time:
    698         var message = record.data["message"];
    699         var oldReference = presentationModel._timeRecords[message];
    700         if (oldReference)
    701             break;
    702         presentationModel._timeRecords[message] = this;
    703         if (origin)
    704             presentationModel._timeRecordStack.push(this);
    705         break;
    706 
    707     case recordTypes.TimeEnd:
    708         var message = record.data["message"];
    709         var timeRecord = presentationModel._timeRecords[message];
    710         delete presentationModel._timeRecords[message];
    711         if (timeRecord) {
    712             this.timeRecord = timeRecord;
    713             timeRecord.timeEndRecord = this;
    714             var intervalDuration = this.startTime - timeRecord.startTime;
    715             this.intervalDuration = intervalDuration;
    716             timeRecord.intervalDuration = intervalDuration;
    717             if (!origin)
    718                 break;
    719             var recordStack = presentationModel._timeRecordStack;
    720             recordStack.splice(recordStack.indexOf(timeRecord), 1);
    721             for (var index = recordStack.length; index; --index) {
    722                 var openRecord = recordStack[index - 1];
    723                 if (openRecord.startTime > timeRecord.startTime)
    724                     continue;
    725                 WebInspector.TimelinePresentationModel.adoptRecord(openRecord, timeRecord);
    726                 break;
    727             }
    728         }
    729         break;
    730 
    731     case recordTypes.ScheduleStyleRecalculation:
    732         presentationModel._lastScheduleStyleRecalculation[this.frameId] = this;
    733         break;
    734 
    735     case recordTypes.RecalculateStyles:
    736         var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId];
    737         if (!scheduleStyleRecalculationRecord)
    738             break;
    739         this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
    740         break;
    741 
    742     case recordTypes.InvalidateLayout:
    743         // Consider style recalculation as a reason for layout invalidation,
    744         // but only if we had no earlier layout invalidation records.
    745         var styleRecalcStack;
    746         if (!presentationModel._layoutInvalidateStack[this.frameId]) {
    747             for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) {
    748                 if (outerRecord.type === recordTypes.RecalculateStyles) {
    749                     styleRecalcStack = outerRecord.callSiteStackTrace;
    750                     break;
    751                 }
    752             }
    753         }
    754         presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
    755         break;
    756 
    757     case recordTypes.Layout:
    758         var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId];
    759         if (layoutInvalidateStack)
    760             this.callSiteStackTrace = layoutInvalidateStack;
    761         if (this.stackTrace)
    762             this.setHasWarning();
    763         presentationModel._layoutInvalidateStack[this.frameId] = null;
    764         this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
    765         this._relatedBackendNodeId = record.data["rootNode"];
    766         break;
    767 
    768     case recordTypes.Paint:
    769         this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
    770         break;
    771 
    772     case recordTypes.WebSocketCreate:
    773         this.webSocketURL = record.data["url"];
    774         if (typeof record.data["webSocketProtocol"] !== "undefined")
    775             this.webSocketProtocol = record.data["webSocketProtocol"];
    776         presentationModel._webSocketCreateRecords[record.data["identifier"]] = this;
    777         break;
    778 
    779     case recordTypes.WebSocketSendHandshakeRequest:
    780     case recordTypes.WebSocketReceiveHandshakeResponse:
    781     case recordTypes.WebSocketDestroy:
    782         var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]];
    783         if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
    784             this.webSocketURL = webSocketCreateRecord.webSocketURL;
    785             if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
    786                 this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
    787         }
    788         break;
    789     }
    790 }
    791 
    792 WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record)
    793 {
    794     record.parent.children.splice(record.parent.children.indexOf(record));
    795     WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record);
    796     record.parent = newParent;
    797 }
    798 
    799 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record)
    800 {
    801     function compareStartTime(value, record)
    802     {
    803         return value < record.startTime ? -1 : 1;
    804     }
    805 
    806     parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record);
    807 }
    808 
    809 WebInspector.TimelinePresentationModel.Record.prototype = {
    810     get lastChildEndTime()
    811     {
    812         return this._lastChildEndTime;
    813     },
    814 
    815     set lastChildEndTime(time)
    816     {
    817         this._lastChildEndTime = time;
    818     },
    819 
    820     get selfTime()
    821     {
    822         return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime;
    823     },
    824 
    825     set selfTime(time)
    826     {
    827         this._selfTime = time;
    828     },
    829 
    830     get cpuTime()
    831     {
    832         return this._cpuTime;
    833     },
    834 
    835     /**
    836      * @return {boolean}
    837      */
    838     isRoot: function()
    839     {
    840         return this.type === WebInspector.TimelineModel.RecordType.Root;
    841     },
    842 
    843     /**
    844      * @return {WebInspector.TimelinePresentationModel.Record}
    845      */
    846     origin: function()
    847     {
    848         return this._origin || this.parent;
    849     },
    850 
    851     /**
    852      * @return {Array.<WebInspector.TimelinePresentationModel.Record>}
    853      */
    854     get children()
    855     {
    856         return this._children;
    857     },
    858 
    859     /**
    860      * @return {number}
    861      */
    862     get visibleChildrenCount()
    863     {
    864         return this._visibleChildrenCount || 0;
    865     },
    866 
    867     /**
    868      * @return {number}
    869      */
    870     get invisibleChildrenCount()
    871     {
    872         return this._invisibleChildrenCount || 0;
    873     },
    874 
    875     /**
    876      * @return {WebInspector.TimelineCategory}
    877      */
    878     get category()
    879     {
    880         return WebInspector.TimelinePresentationModel.recordStyle(this._record).category
    881     },
    882 
    883     /**
    884      * @return {string}
    885      */
    886     get title()
    887     {
    888         return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] :
    889             WebInspector.TimelinePresentationModel.recordStyle(this._record).title;
    890     },
    891 
    892     /**
    893      * @return {number}
    894      */
    895     get startTime()
    896     {
    897         return WebInspector.TimelineModel.startTimeInSeconds(this._record);
    898     },
    899 
    900     /**
    901      * @return {number}
    902      */
    903     get endTime()
    904     {
    905         return WebInspector.TimelineModel.endTimeInSeconds(this._record);
    906     },
    907 
    908     /**
    909      * @return {boolean}
    910      */
    911     get isBackground()
    912     {
    913         return !!this._record.thread;
    914     },
    915 
    916     /**
    917      * @return {Object}
    918      */
    919     get data()
    920     {
    921         return this._record.data;
    922     },
    923 
    924     /**
    925      * @return {string}
    926      */
    927     get type()
    928     {
    929         return this._record.type;
    930     },
    931 
    932     /**
    933      * @return {string}
    934      */
    935     get frameId()
    936     {
    937         return this._record.frameId;
    938     },
    939 
    940     /**
    941      * @return {number}
    942      */
    943     get usedHeapSizeDelta()
    944     {
    945         return this._record.usedHeapSizeDelta || 0;
    946     },
    947 
    948     /**
    949      * @return {number}
    950      */
    951     get usedHeapSize()
    952     {
    953         return this._record.usedHeapSize;
    954     },
    955 
    956     /**
    957      * @return {Array.<DebuggerAgent.CallFrame>?}
    958      */
    959     get stackTrace()
    960     {
    961         if (this._record.stackTrace && this._record.stackTrace.length)
    962             return this._record.stackTrace;
    963         return null;
    964     },
    965 
    966     containsTime: function(time)
    967     {
    968         return this.startTime <= time && time <= this.endTime;
    969     },
    970 
    971     /**
    972      * @param {function(Element)} callback
    973      */
    974     generatePopupContent: function(callback)
    975     {
    976         var barrier = new CallbackBarrier();
    977         if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type) && !this._imagePreviewElement)
    978             WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, barrier.createCallback(this._setImagePreviewElement.bind(this)));
    979         if (this._relatedBackendNodeId && !this._relatedNode)
    980             WebInspector.domAgent.pushNodeByBackendIdToFrontend(this._relatedBackendNodeId, barrier.createCallback(this._setRelatedNode.bind(this)));
    981 
    982         barrier.callWhenDone(callbackWrapper.bind(this));
    983         function callbackWrapper()
    984         {
    985             callback(this._generatePopupContentSynchronously());
    986         }
    987     },
    988 
    989     /**
    990      * @param {Element} element
    991      */
    992     _setImagePreviewElement: function(element)
    993     {
    994         this._imagePreviewElement = element;
    995     },
    996 
    997     /**
    998      * @param {?DOMAgent.NodeId} nodeId
    999      */
   1000     _setRelatedNode: function(nodeId)
   1001     {
   1002         if (typeof nodeId === "number")
   1003             this._relatedNode = WebInspector.domAgent.nodeForId(nodeId);
   1004     },
   1005 
   1006     /**
   1007      * @return {Element}
   1008      */
   1009     _generatePopupContentSynchronously: function()
   1010     {
   1011         var contentHelper = new WebInspector.PopoverContentHelper(this.title);
   1012         var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, true),
   1013             Number.secondsToString(this._startTimeOffset));
   1014         contentHelper.appendTextRow(WebInspector.UIString("Duration"), text);
   1015 
   1016         if (this._children.length) {
   1017             if (!this.coalesced)
   1018                 contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime, true));
   1019             contentHelper.appendTextRow(WebInspector.UIString("CPU Time"), Number.secondsToString(this._cpuTime, true));
   1020             contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
   1021                 WebInspector.TimelinePresentationModel._generateAggregatedInfo(this._aggregatedStats));
   1022         }
   1023 
   1024         if (this.coalesced)
   1025             return contentHelper.contentTable();
   1026 
   1027         const recordTypes = WebInspector.TimelineModel.RecordType;
   1028 
   1029         // The messages may vary per record type;
   1030         var callSiteStackTraceLabel;
   1031         var callStackLabel;
   1032 
   1033         switch (this.type) {
   1034             case recordTypes.GCEvent:
   1035                 contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"]));
   1036                 break;
   1037             case recordTypes.TimerFire:
   1038                 callSiteStackTraceLabel = WebInspector.UIString("Timer installed");
   1039                 // Fall-through intended.
   1040 
   1041             case recordTypes.TimerInstall:
   1042             case recordTypes.TimerRemove:
   1043                 contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]);
   1044                 if (typeof this.timeout === "number") {
   1045                     contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000));
   1046                     contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
   1047                 }
   1048                 break;
   1049             case recordTypes.FireAnimationFrame:
   1050                 callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested");
   1051                 contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]);
   1052                 break;
   1053             case recordTypes.FunctionCall:
   1054                 contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyScriptLocation());
   1055                 break;
   1056             case recordTypes.ScheduleResourceRequest:
   1057             case recordTypes.ResourceSendRequest:
   1058             case recordTypes.ResourceReceiveResponse:
   1059             case recordTypes.ResourceReceivedData:
   1060             case recordTypes.ResourceFinish:
   1061                 contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url));
   1062                 if (this._imagePreviewElement)
   1063                     contentHelper.appendElementRow(WebInspector.UIString("Preview"), this._imagePreviewElement);
   1064                 if (this.data["requestMethod"])
   1065                     contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]);
   1066                 if (typeof this.data["statusCode"] === "number")
   1067                     contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]);
   1068                 if (this.data["mimeType"])
   1069                     contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]);
   1070                 if (this.data["encodedDataLength"])
   1071                     contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"]));
   1072                 break;
   1073             case recordTypes.EvaluateScript:
   1074                 if (this.data && this.url)
   1075                     contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"]));
   1076                 break;
   1077             case recordTypes.Paint:
   1078                 var clip = this.data["clip"];
   1079                 if (clip) {
   1080                     contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
   1081                     var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip);
   1082                     var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip);
   1083                     contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d  %d", clipWidth, clipHeight));
   1084                 } else {
   1085                     // Backward compatibility: older version used x, y, width, height fields directly in data.
   1086                     if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined")
   1087                         contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"]));
   1088                     if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined")
   1089                         contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"]));
   1090                 }
   1091                 // Fall-through intended.
   1092 
   1093             case recordTypes.PaintSetup:
   1094             case recordTypes.Rasterize:
   1095             case recordTypes.ScrollLayer:
   1096                 if (this._relatedNode)
   1097                     contentHelper.appendElementRow(WebInspector.UIString("Layer root"), this._createNodeAnchor(this._relatedNode));
   1098                 break;
   1099             case recordTypes.RecalculateStyles: // We don't want to see default details.
   1100                 if (this.data["elementCount"])
   1101                     contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]);
   1102                 callStackLabel = WebInspector.UIString("Styles recalculation forced");
   1103                 break;
   1104             case recordTypes.Layout:
   1105                 if (this.data["dirtyObjects"])
   1106                     contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]);
   1107                 if (this.data["totalObjects"])
   1108                     contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]);
   1109                 if (typeof this.data["partialLayout"] === "boolean") {
   1110                     contentHelper.appendTextRow(WebInspector.UIString("Layout scope"),
   1111                        this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document"));
   1112                 }
   1113                 callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
   1114                 if (this.stackTrace) {
   1115                     callStackLabel = WebInspector.UIString("Layout forced");
   1116                     contentHelper.appendTextRow(WebInspector.UIString("Note"), WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
   1117                 }
   1118                 if (this._relatedNode)
   1119                     contentHelper.appendElementRow(WebInspector.UIString("Layout root"), this._createNodeAnchor(this._relatedNode));
   1120                 break;
   1121             case recordTypes.Time:
   1122             case recordTypes.TimeEnd:
   1123                 contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
   1124                 if (typeof this.intervalDuration === "number")
   1125                     contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true));
   1126                 break;
   1127             case recordTypes.WebSocketCreate:
   1128             case recordTypes.WebSocketSendHandshakeRequest:
   1129             case recordTypes.WebSocketReceiveHandshakeResponse:
   1130             case recordTypes.WebSocketDestroy:
   1131                 if (typeof this.webSocketURL !== "undefined")
   1132                     contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL);
   1133                 if (typeof this.webSocketProtocol !== "undefined")
   1134                     contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol);
   1135                 if (typeof this.data["message"] !== "undefined")
   1136                     contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"])
   1137                     break;
   1138             default:
   1139                 if (this.detailsNode())
   1140                     contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode());
   1141                 break;
   1142         }
   1143 
   1144         if (this.scriptName && this.type !== recordTypes.FunctionCall)
   1145             contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyScriptLocation());
   1146 
   1147         if (this.usedHeapSize) {
   1148             if (this.usedHeapSizeDelta) {
   1149                 var sign = this.usedHeapSizeDelta > 0 ? "+" : "-";
   1150                 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"),
   1151                     WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(Math.abs(this.usedHeapSizeDelta))));
   1152             } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting)
   1153                 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize));
   1154         }
   1155 
   1156         if (this.callSiteStackTrace)
   1157             contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this));
   1158 
   1159         if (this.stackTrace)
   1160             contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this));
   1161 
   1162         return contentHelper.contentTable();
   1163     },
   1164 
   1165     /**
   1166      * @param {WebInspector.DOMAgent} node
   1167      */
   1168     _createNodeAnchor: function(node)
   1169     {
   1170         var span = document.createElement("span");
   1171         span.classList.add("node-link");
   1172         span.addEventListener("click", onClick, false);
   1173         WebInspector.DOMPresentationUtils.decorateNodeLabel(node, span);
   1174         function onClick()
   1175         {
   1176             WebInspector.showPanel("elements").revealAndSelectNode(node.id);
   1177         }
   1178         return span;
   1179     },
   1180 
   1181     _refreshDetails: function()
   1182     {
   1183         delete this._detailsNode;
   1184     },
   1185 
   1186     /**
   1187      * @return {?Node}
   1188      */
   1189     detailsNode: function()
   1190     {
   1191         if (typeof this._detailsNode === "undefined") {
   1192             this._detailsNode = this._getRecordDetails();
   1193 
   1194             if (this._detailsNode && !this.coalesced) {
   1195                 this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild);
   1196                 this._detailsNode.appendChild(document.createTextNode(")"));
   1197             }
   1198         }
   1199         return this._detailsNode;
   1200     },
   1201 
   1202     _createSpanWithText: function(textContent)
   1203     {
   1204         var node = document.createElement("span");
   1205         node.textContent = textContent;
   1206         return node;
   1207     },
   1208 
   1209     /**
   1210      * @return {?Node}
   1211      */
   1212     _getRecordDetails: function()
   1213     {
   1214         var details;
   1215         if (this.coalesced)
   1216             return this._createSpanWithText(WebInspector.UIString(" %d", this.children.length));
   1217 
   1218         switch (this.type) {
   1219         case WebInspector.TimelineModel.RecordType.GCEvent:
   1220             details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"]));
   1221             break;
   1222         case WebInspector.TimelineModel.RecordType.TimerFire:
   1223             details = this._linkifyScriptLocation(this.data["timerId"]);
   1224             break;
   1225         case WebInspector.TimelineModel.RecordType.FunctionCall:
   1226             details = this._linkifyScriptLocation();
   1227             break;
   1228         case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
   1229             details = this._linkifyScriptLocation(this.data["id"]);
   1230             break;
   1231         case WebInspector.TimelineModel.RecordType.EventDispatch:
   1232             details = this.data ? this.data["type"] : null;
   1233             break;
   1234         case WebInspector.TimelineModel.RecordType.Paint:
   1235             var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width;
   1236             var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height;
   1237             if (width && height)
   1238                 details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
   1239             break;
   1240         case WebInspector.TimelineModel.RecordType.DecodeImage:
   1241             details = this.data["imageType"];
   1242             break;
   1243         case WebInspector.TimelineModel.RecordType.ResizeImage:
   1244             details = this.data["cached"] ? WebInspector.UIString("cached") : WebInspector.UIString("non-cached");
   1245             break;
   1246         case WebInspector.TimelineModel.RecordType.TimerInstall:
   1247         case WebInspector.TimelineModel.RecordType.TimerRemove:
   1248             details = this._linkifyTopCallFrame(this.data["timerId"]);
   1249             break;
   1250         case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
   1251         case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
   1252             details = this._linkifyTopCallFrame(this.data["id"]);
   1253             break;
   1254         case WebInspector.TimelineModel.RecordType.ParseHTML:
   1255         case WebInspector.TimelineModel.RecordType.RecalculateStyles:
   1256             details = this._linkifyTopCallFrame();
   1257             break;
   1258         case WebInspector.TimelineModel.RecordType.EvaluateScript:
   1259             details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null;
   1260             break;
   1261         case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
   1262         case WebInspector.TimelineModel.RecordType.XHRLoad:
   1263         case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest:
   1264         case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
   1265         case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
   1266         case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
   1267         case WebInspector.TimelineModel.RecordType.ResourceFinish:
   1268             details = WebInspector.displayNameForURL(this.url);
   1269             break;
   1270         case WebInspector.TimelineModel.RecordType.Time:
   1271         case WebInspector.TimelineModel.RecordType.TimeEnd:
   1272             details = this.data["message"];
   1273             break;
   1274         default:
   1275             details = this._linkifyScriptLocation() || this._linkifyTopCallFrame() || null;
   1276             break;
   1277         }
   1278 
   1279         if (details) {
   1280             if (details instanceof Node)
   1281                 details.tabIndex = -1;
   1282             else
   1283                 return this._createSpanWithText("" + details);
   1284         }
   1285 
   1286         return details || null;
   1287     },
   1288 
   1289     /**
   1290      * @param {string} url
   1291      * @param {number} lineNumber
   1292      * @param {number=} columnNumber
   1293      */
   1294     _linkifyLocation: function(url, lineNumber, columnNumber)
   1295     {
   1296         // FIXME(62725): stack trace line/column numbers are one-based.
   1297         columnNumber = columnNumber ? columnNumber - 1 : 0;
   1298         return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
   1299     },
   1300 
   1301     _linkifyCallFrame: function(callFrame)
   1302     {
   1303         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
   1304     },
   1305 
   1306     /**
   1307      * @param {string=} defaultValue
   1308      */
   1309     _linkifyTopCallFrame: function(defaultValue)
   1310     {
   1311         if (this.stackTrace)
   1312             return this._linkifyCallFrame(this.stackTrace[0]);
   1313         if (this.callSiteStackTrace)
   1314             return this._linkifyCallFrame(this.callSiteStackTrace[0]);
   1315         return defaultValue;
   1316     },
   1317 
   1318     /**
   1319      * @param {*=} defaultValue
   1320      * @return {Element|string}
   1321      */
   1322     _linkifyScriptLocation: function(defaultValue)
   1323     {
   1324         if (this.scriptName)
   1325             return this._linkifyLocation(this.scriptName, this.scriptLine, 0);
   1326         else
   1327             return defaultValue ? "" + defaultValue : null;
   1328     },
   1329 
   1330     calculateAggregatedStats: function()
   1331     {
   1332         this._aggregatedStats = {};
   1333         this._cpuTime = this._selfTime;
   1334 
   1335         for (var index = this._children.length; index; --index) {
   1336             var child = this._children[index - 1];
   1337             for (var category in child._aggregatedStats)
   1338                 this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category];
   1339         }
   1340         for (var category in this._aggregatedStats)
   1341             this._cpuTime += this._aggregatedStats[category];
   1342         this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime;
   1343     },
   1344 
   1345     get aggregatedStats()
   1346     {
   1347         return this._aggregatedStats;
   1348     },
   1349 
   1350     setHasWarning: function()
   1351     {
   1352         this.hasWarning = true;
   1353         for (var parent = this.parent; parent && !parent.childHasWarning; parent = parent.parent)
   1354             parent.childHasWarning = true;
   1355     }
   1356 }
   1357 
   1358 /**
   1359  * @param {Object} aggregatedStats
   1360  */
   1361 WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats)
   1362 {
   1363     var cell = document.createElement("span");
   1364     cell.className = "timeline-aggregated-info";
   1365     for (var index in aggregatedStats) {
   1366         var label = document.createElement("div");
   1367         label.className = "timeline-aggregated-category timeline-" + index;
   1368         cell.appendChild(label);
   1369         var text = document.createElement("span");
   1370         text.textContent = Number.secondsToString(aggregatedStats[index], true);
   1371         cell.appendChild(text);
   1372     }
   1373     return cell;
   1374 }
   1375 
   1376 WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame)
   1377 {
   1378     var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Frame"));
   1379     var durationInSeconds = frame.endTime - frame.startTime;
   1380     var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true),
   1381         Number.secondsToString(frame.startTimeOffset, true));
   1382     contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
   1383     contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds));
   1384     contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true));
   1385     contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
   1386         WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory));
   1387 
   1388     return contentHelper.contentTable();
   1389 }
   1390 
   1391 /**
   1392  * @param {WebInspector.FrameStatistics} statistics
   1393  */
   1394 WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics)
   1395 {
   1396     /**
   1397      * @param {number} time
   1398      */
   1399     function formatTimeAndFPS(time)
   1400     {
   1401         return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time);
   1402     }
   1403 
   1404     var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Selected Range"));
   1405 
   1406     contentHelper.appendTextRow(WebInspector.UIString("Selected range"), WebInspector.UIString("%s\u2013%s (%d frames)",
   1407         Number.secondsToString(statistics.startOffset, true), Number.secondsToString(statistics.endOffset, true), statistics.frameCount));
   1408     contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration));
   1409     contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average));
   1410     contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration));
   1411     contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true));
   1412     contentHelper.appendElementRow(WebInspector.UIString("Time by category"),
   1413         WebInspector.TimelinePresentationModel._generateAggregatedInfo(statistics.timeByCategory));
   1414 
   1415     return contentHelper.contentTable();
   1416 }
   1417 
   1418 /**
   1419  * @param {CanvasRenderingContext2D} context
   1420  * @param {number} width
   1421  * @param {number} height
   1422  * @param {string} color0
   1423  * @param {string} color1
   1424  * @param {string} color2
   1425  */
   1426 WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2)
   1427 {
   1428     var gradient = context.createLinearGradient(0, 0, width, height);
   1429     gradient.addColorStop(0, color0);
   1430     gradient.addColorStop(0.25, color1);
   1431     gradient.addColorStop(0.75, color1);
   1432     gradient.addColorStop(1, color2);
   1433     return gradient;
   1434 }
   1435 
   1436 /**
   1437  * @param {CanvasRenderingContext2D} context
   1438  * @param {number} width
   1439  * @param {number} height
   1440  * @param {WebInspector.TimelineCategory} category
   1441  */
   1442 WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category)
   1443 {
   1444     return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
   1445 }
   1446 
   1447 /**
   1448  * @param {WebInspector.TimelineCategory} category
   1449  */
   1450 WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category)
   1451 {
   1452     var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
   1453         ".timeline-category-statusbar-item.timeline-category-" + category.name + " .timeline-category-checkbox, " +
   1454         ".popover .timeline-" + category.name + ", " +
   1455         ".timeline-category-" + category.name + " .timeline-tree-icon"
   1456 
   1457     return selector + " { background-image: -webkit-linear-gradient(" +
   1458        category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" +
   1459        " border-color: " + category.borderColor +
   1460        "}";
   1461 }
   1462 
   1463 
   1464 /**
   1465  * @param {Object} rawRecord
   1466  * @return {string?}
   1467  */
   1468 WebInspector.TimelinePresentationModel.coalescingKeyForRecord = function(rawRecord)
   1469 {
   1470     var recordTypes = WebInspector.TimelineModel.RecordType;
   1471     switch (rawRecord.type)
   1472     {
   1473     case recordTypes.EventDispatch: return rawRecord.data["type"];
   1474     case recordTypes.TimeStamp: return rawRecord.data["message"];
   1475     default: return null;
   1476     }
   1477 }
   1478 
   1479 /**
   1480  * @param {Array.<number>} quad
   1481  * @return {number}
   1482  */
   1483 WebInspector.TimelinePresentationModel.quadWidth = function(quad)
   1484 {
   1485     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
   1486 }
   1487 
   1488 /**
   1489  * @param {Array.<number>} quad
   1490  * @return {number}
   1491  */
   1492 WebInspector.TimelinePresentationModel.quadHeight = function(quad)
   1493 {
   1494     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
   1495 }
   1496 
   1497 /**
   1498  * @param {Object} data
   1499  * @return {Array.<number>?}
   1500  */
   1501 WebInspector.TimelinePresentationModel.quadFromRectData = function(data)
   1502 {
   1503     if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined")
   1504         return null;
   1505     var x0 = data["x"];
   1506     var x1 = data["x"] + data["width"];
   1507     var y0 = data["y"];
   1508     var y1 = data["y"] + data["height"];
   1509     return [x0, y0, x1, y0, x1, y1, x0, y1];
   1510 }
   1511 
   1512 /**
   1513  * @interface
   1514  */
   1515 WebInspector.TimelinePresentationModel.Filter = function()
   1516 {
   1517 }
   1518 
   1519 WebInspector.TimelinePresentationModel.Filter.prototype = {
   1520     /**
   1521      * @param {!WebInspector.TimelinePresentationModel.Record} record
   1522      * @return {boolean}
   1523      */
   1524     accept: function(record) { return false; }
   1525 }
   1526 
   1527 /**
   1528  * @constructor
   1529  * @extends {WebInspector.Object}
   1530  * @param {string} name
   1531  * @param {string} title
   1532  * @param {number} overviewStripGroupIndex
   1533  * @param {string} borderColor
   1534  * @param {string} fillColorStop0
   1535  * @param {string} fillColorStop1
   1536  */
   1537 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1)
   1538 {
   1539     this.name = name;
   1540     this.title = title;
   1541     this.overviewStripGroupIndex = overviewStripGroupIndex;
   1542     this.borderColor = borderColor;
   1543     this.fillColorStop0 = fillColorStop0;
   1544     this.fillColorStop1 = fillColorStop1;
   1545     this.hidden = false;
   1546 }
   1547 
   1548 WebInspector.TimelineCategory.Events = {
   1549     VisibilityChanged: "VisibilityChanged"
   1550 };
   1551 
   1552 WebInspector.TimelineCategory.prototype = {
   1553     /**
   1554      * @return {boolean}
   1555      */
   1556     get hidden()
   1557     {
   1558         return this._hidden;
   1559     },
   1560 
   1561     set hidden(hidden)
   1562     {
   1563         this._hidden = hidden;
   1564         this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
   1565     },
   1566 
   1567     __proto__: WebInspector.Object.prototype
   1568 }
   1569