Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2013 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         idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), -1, "#DDDDDD", "#FFFFFF", "#FFFFFF")
     55     };
     56     return WebInspector.TimelinePresentationModel._categories;
     57 };
     58 
     59 /**
     60  * @return {!Object.<string, {title: string, category: !WebInspector.TimelineCategory}>}
     61  */
     62 WebInspector.TimelinePresentationModel._initRecordStyles = function()
     63 {
     64     if (WebInspector.TimelinePresentationModel._recordStylesMap)
     65         return WebInspector.TimelinePresentationModel._recordStylesMap;
     66 
     67     var recordTypes = WebInspector.TimelineModel.RecordType;
     68     var categories = WebInspector.TimelinePresentationModel.categories();
     69 
     70     var recordStyles = {};
     71     recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] };
     72     recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] };
     73     recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] };
     74     recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] };
     75     recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] };
     76     recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] };
     77     recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] };
     78     recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] };
     79     recordStyles[recordTypes.AutosizeText] = { title: WebInspector.UIString("Autosize Text"), category: categories["rendering"] };
     80     recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] };
     81     recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
     82     recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
     83     recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] };
     84     recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] };
     85     recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] };
     86     recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] };
     87     recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] };
     88     recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] };
     89     recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] };
     90     recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] };
     91     recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] };
     92     recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] };
     93     recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] };
     94     recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] };
     95     recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] };
     96     recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] };
     97     recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] };
     98     recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] };
     99     recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] };
    100     recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] };
    101     recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] };
    102     recordStyles[recordTypes.MarkFirstPaint] = { title: WebInspector.UIString("First paint"), category: categories["painting"] };
    103     recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] };
    104     recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] };
    105     recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] };
    106     recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] };
    107     recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] };
    108     recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] };
    109     recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] };
    110     recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] };
    111     recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] };
    112     recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] };
    113     recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] };
    114 
    115     WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles;
    116     return recordStyles;
    117 }
    118 
    119 /**
    120  * @param {!Object} record
    121  * @return {{title: string, category: !WebInspector.TimelineCategory}}
    122  */
    123 WebInspector.TimelinePresentationModel.recordStyle = function(record)
    124 {
    125     var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles();
    126     var result = recordStyles[record.type];
    127     if (!result) {
    128         result = {
    129             title: WebInspector.UIString("Unknown: %s", record.type),
    130             category: WebInspector.TimelinePresentationModel.categories()["other"]
    131         };
    132         recordStyles[record.type] = result;
    133     }
    134     return result;
    135 }
    136 
    137 WebInspector.TimelinePresentationModel.categoryForRecord = function(record)
    138 {
    139     return WebInspector.TimelinePresentationModel.recordStyle(record).category;
    140 }
    141 
    142 WebInspector.TimelinePresentationModel.isEventDivider = function(record)
    143 {
    144     var recordTypes = WebInspector.TimelineModel.RecordType;
    145     if (record.type === recordTypes.TimeStamp)
    146         return true;
    147     if (record.type === recordTypes.MarkFirstPaint)
    148         return true;
    149     if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
    150         if (record.data && ((typeof record.data.isMainFrame) === "boolean"))
    151             return record.data.isMainFrame;
    152     }
    153     return false;
    154 }
    155 
    156 /**
    157  * @param {!Array.<*>} recordsArray
    158  * @param {?function(*)} preOrderCallback
    159  * @param {function(*)=} postOrderCallback
    160  */
    161 WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback)
    162 {
    163     if (!recordsArray)
    164         return;
    165     var stack = [{array: recordsArray, index: 0}];
    166     while (stack.length) {
    167         var entry = stack[stack.length - 1];
    168         var records = entry.array;
    169         if (entry.index < records.length) {
    170              var record = records[entry.index];
    171              if (preOrderCallback && preOrderCallback(record))
    172                  return;
    173              if (record.children)
    174                  stack.push({array: record.children, index: 0, record: record});
    175              else if (postOrderCallback && postOrderCallback(record))
    176                 return;
    177              ++entry.index;
    178         } else {
    179             if (entry.record && postOrderCallback && postOrderCallback(entry.record))
    180                 return;
    181             stack.pop();
    182         }
    183     }
    184 }
    185 
    186 /**
    187  * @param {string=} recordType
    188  * @return {boolean}
    189  */
    190 WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType)
    191 {
    192     if (!recordType)
    193         return false;
    194     const recordTypes = WebInspector.TimelineModel.RecordType;
    195     switch (recordType) {
    196     case recordTypes.ScheduleResourceRequest:
    197     case recordTypes.ResourceSendRequest:
    198     case recordTypes.ResourceReceiveResponse:
    199     case recordTypes.ResourceReceivedData:
    200     case recordTypes.ResourceFinish:
    201         return true;
    202     default:
    203         return false;
    204     }
    205 }
    206 
    207 /**
    208  * @param {string} recordType
    209  * @param {string=} title
    210  */
    211 WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title)
    212 {
    213     var eventDivider = document.createElement("div");
    214     eventDivider.className = "resources-event-divider";
    215     var recordTypes = WebInspector.TimelineModel.RecordType;
    216 
    217     if (recordType === recordTypes.MarkDOMContent)
    218         eventDivider.className += " resources-blue-divider";
    219     else if (recordType === recordTypes.MarkLoad)
    220         eventDivider.className += " resources-red-divider";
    221     else if (recordType === recordTypes.MarkFirstPaint)
    222         eventDivider.className += " resources-green-divider";
    223     else if (recordType === recordTypes.TimeStamp)
    224         eventDivider.className += " resources-orange-divider";
    225     else if (recordType === recordTypes.BeginFrame)
    226         eventDivider.className += " timeline-frame-divider";
    227 
    228     if (title)
    229         eventDivider.title = title;
    230 
    231     return eventDivider;
    232 }
    233 
    234 WebInspector.TimelinePresentationModel._hiddenRecords = { }
    235 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1;
    236 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1;
    237 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkFirstPaint] = 1;
    238 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1;
    239 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1;
    240 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.GPUTask] = 1;
    241 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ActivateLayerTree] = 1;
    242 
    243 WebInspector.TimelinePresentationModel.prototype = {
    244     /**
    245      * @param {!WebInspector.TimelinePresentationModel.Filter} filter
    246      */
    247     addFilter: function(filter)
    248     {
    249         this._filters.push(filter);
    250     },
    251 
    252     /**
    253      * @param {?WebInspector.TimelinePresentationModel.Filter} filter
    254      */
    255     setSearchFilter: function(filter)
    256     {
    257         this._searchFilter = filter;
    258     },
    259 
    260     rootRecord: function()
    261     {
    262         return this._rootRecord;
    263     },
    264 
    265     frames: function()
    266     {
    267         return this._frames;
    268     },
    269 
    270     reset: function()
    271     {
    272         this._linkifier.reset();
    273         this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false);
    274         this._sendRequestRecords = {};
    275         this._scheduledResourceRequests = {};
    276         this._timerRecords = {};
    277         this._requestAnimationFrameRecords = {};
    278         this._eventDividerRecords = [];
    279         this._timeRecords = {};
    280         this._timeRecordStack = [];
    281         this._frames = [];
    282         this._minimumRecordTime = -1;
    283         this._layoutInvalidateStack = {};
    284         this._lastScheduleStyleRecalculation = {};
    285         this._webSocketCreateRecords = {};
    286         this._coalescingBuckets = {};
    287     },
    288 
    289     addFrame: function(frame)
    290     {
    291         if (!frame.isBackground)
    292             this._frames.push(frame);
    293     },
    294 
    295     /**
    296      * @param {!TimelineAgent.TimelineEvent} record
    297      * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    298      */
    299     addRecord: function(record)
    300     {
    301         if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime)
    302             this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record);
    303 
    304         var records;
    305         if (record.type === WebInspector.TimelineModel.RecordType.Program)
    306             records = this._foldSyncTimeRecords(record.children || []);
    307         else
    308             records = [record];
    309         var result = Array(records.length);
    310         for (var i = 0; i < records.length; ++i)
    311             result[i] = this._innerAddRecord(this._rootRecord, records[i]);
    312         return result;
    313     },
    314 
    315     /**
    316      * @param {!WebInspector.TimelinePresentationModel.Record} parentRecord
    317      * @param {!TimelineAgent.TimelineEvent} record
    318      * @return {!WebInspector.TimelinePresentationModel.Record}
    319      */
    320     _innerAddRecord: function(parentRecord, record)
    321     {
    322         const recordTypes = WebInspector.TimelineModel.RecordType;
    323         var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords;
    324         var origin;
    325         var coalescingBucket;
    326 
    327         if (!isHiddenRecord) {
    328             var newParentRecord = this._findParentRecord(record);
    329             if (newParentRecord) {
    330                 origin = parentRecord;
    331                 parentRecord = newParentRecord;
    332             }
    333             // On main thread, only coalesce if the last event is of same type.
    334             if (parentRecord === this._rootRecord)
    335                 coalescingBucket = record.thread ? record.type : "mainThread";
    336             var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket);
    337             if (coalescedRecord) {
    338                 if (!origin)
    339                     origin = parentRecord;
    340                 parentRecord = coalescedRecord;
    341             }
    342         }
    343 
    344         var children = record.children;
    345         var scriptDetails = null;
    346         if (record.data && record.data["scriptName"]) {
    347             scriptDetails = {
    348                 scriptName: record.data["scriptName"],
    349                 scriptLine: record.data["scriptLine"]
    350             }
    351         };
    352 
    353         if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) {
    354             var childRecord = children[0];
    355             if (childRecord.type === recordTypes.FunctionCall) {
    356                 scriptDetails = {
    357                     scriptName: childRecord.data["scriptName"],
    358                     scriptLine: childRecord.data["scriptLine"]
    359                 };
    360                 children = childRecord.children.concat(children.slice(1));
    361             }
    362         }
    363 
    364         var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord);
    365 
    366         if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord))
    367             this._eventDividerRecords.push(formattedRecord);
    368 
    369         if (isHiddenRecord)
    370             return formattedRecord;
    371 
    372         formattedRecord.collapsed = parentRecord === this._rootRecord;
    373         if (coalescingBucket)
    374             this._coalescingBuckets[coalescingBucket] = formattedRecord;
    375 
    376         if (children) {
    377             children = this._foldSyncTimeRecords(children);
    378             for (var i = 0; i < children.length; ++i)
    379                 this._innerAddRecord(formattedRecord, children[i]);
    380         }
    381 
    382         formattedRecord.calculateAggregatedStats();
    383         if (parentRecord.coalesced)
    384             this._updateCoalescingParent(formattedRecord);
    385         else if (origin)
    386             this._updateAncestorStats(formattedRecord);
    387 
    388         origin = formattedRecord.origin();
    389         if (!origin.isRoot() && !origin.coalesced)
    390             origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime;
    391         return formattedRecord;
    392     },
    393 
    394     /**
    395      * @param {!WebInspector.TimelinePresentationModel.Record} record
    396      */
    397     _updateAncestorStats: function(record)
    398     {
    399         var lastChildEndTime = record.lastChildEndTime;
    400         var aggregatedStats = record.aggregatedStats;
    401         for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) {
    402             currentRecord._cpuTime += record._cpuTime;
    403             if (currentRecord.lastChildEndTime < lastChildEndTime)
    404                 currentRecord.lastChildEndTime = lastChildEndTime;
    405             for (var category in aggregatedStats)
    406                 currentRecord.aggregatedStats[category] += aggregatedStats[category];
    407         }
    408     },
    409 
    410     /**
    411      * @param {!Object} record
    412      * @param {!Object} newParent
    413      * @param {string=} bucket
    414      * @return {?WebInspector.TimelinePresentationModel.Record}
    415      */
    416     _findCoalescedParent: function(record, newParent, bucket)
    417     {
    418         const coalescingThresholdSeconds = 0.005;
    419 
    420         var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent.children.peekLast();
    421         if (lastRecord && lastRecord.coalesced)
    422             lastRecord = lastRecord.children.peekLast();
    423         var startTime = WebInspector.TimelineModel.startTimeInSeconds(record);
    424         var endTime = WebInspector.TimelineModel.endTimeInSeconds(record);
    425         if (!lastRecord)
    426             return null;
    427         if (lastRecord.type !== record.type)
    428             return null;
    429         if (lastRecord.endTime + coalescingThresholdSeconds < startTime)
    430             return null;
    431         if (endTime + coalescingThresholdSeconds < lastRecord.startTime)
    432             return null;
    433         if (WebInspector.TimelinePresentationModel.coalescingKeyForRecord(record) !== WebInspector.TimelinePresentationModel.coalescingKeyForRecord(lastRecord._record))
    434             return null;
    435         if (lastRecord.parent.coalesced)
    436             return lastRecord.parent;
    437         return this._replaceWithCoalescedRecord(lastRecord);
    438     },
    439 
    440     /**
    441      * @param {!WebInspector.TimelinePresentationModel.Record} record
    442      * @return {!WebInspector.TimelinePresentationModel.Record}
    443      */
    444     _replaceWithCoalescedRecord: function(record)
    445     {
    446         var rawRecord = {
    447             type: record._record.type,
    448             startTime: record._record.startTime,
    449             endTime: record._record.endTime,
    450             data: { }
    451         };
    452         if (record._record.thread)
    453             rawRecord.thread = "aggregated";
    454         if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp)
    455             rawRecord.data.message = record.data.message;
    456 
    457         var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false);
    458         var parent = record.parent;
    459 
    460         coalescedRecord.coalesced = true;
    461         coalescedRecord.collapsed = true;
    462         coalescedRecord._children.push(record);
    463         record.parent = coalescedRecord;
    464         if (record.hasWarnings() || record.childHasWarnings())
    465             coalescedRecord._childHasWarnings = true;
    466 
    467         coalescedRecord.parent = parent;
    468         parent._children[parent._children.indexOf(record)] = coalescedRecord;
    469         WebInspector.TimelineModel.aggregateTimeByCategory(coalescedRecord._aggregatedStats, record._aggregatedStats);
    470 
    471         return coalescedRecord;
    472     },
    473 
    474     /**
    475      * @param {!WebInspector.TimelinePresentationModel.Record} record
    476      */
    477     _updateCoalescingParent: function(record)
    478     {
    479         var parentRecord = record.parent;
    480         WebInspector.TimelineModel.aggregateTimeByCategory(parentRecord._aggregatedStats, record._aggregatedStats);
    481         if (parentRecord.startTime > record._record.startTime)
    482             parentRecord._record.startTime = record._record.startTime;
    483         if (parentRecord.endTime < record._record.endTime) {
    484             parentRecord._record.endTime = record._record.endTime;
    485             parentRecord.lastChildEndTime = parentRecord.endTime;
    486         }
    487     },
    488 
    489     /**
    490      * @param {!Array.<!TimelineAgent.TimelineEvent>} records
    491      */
    492     _foldSyncTimeRecords: function(records)
    493     {
    494         var recordTypes = WebInspector.TimelineModel.RecordType;
    495         // Fast case -- if there are no Time records, return input as is.
    496         for (var i = 0; i < records.length && records[i].type !== recordTypes.Time; ++i) {}
    497         if (i === records.length)
    498             return records;
    499 
    500         var result = [];
    501         var stack = [];
    502         for (var i = 0; i < records.length; ++i) {
    503             result.push(records[i]);
    504             if (records[i].type === recordTypes.Time) {
    505                 stack.push(result.length - 1);
    506                 continue;
    507             }
    508             if (records[i].type !== recordTypes.TimeEnd)
    509                 continue;
    510             while (stack.length) {
    511                 var begin = stack.pop();
    512                 if (result[begin].data.message !== records[i].data.message)
    513                     continue;
    514                 var timeEndRecord = /** @type {!TimelineAgent.TimelineEvent} */ (result.pop());
    515                 var children = result.splice(begin + 1, result.length - begin);
    516                 result[begin] = this._createSynchronousTimeRecord(result[begin], timeEndRecord, children);
    517                 break;
    518             }
    519         }
    520         return result;
    521     },
    522 
    523     /**
    524      * @param {!TimelineAgent.TimelineEvent} beginRecord
    525      * @param {!TimelineAgent.TimelineEvent} endRecord
    526      * @param {!Array.<!TimelineAgent.TimelineEvent>} children
    527      * @return {!TimelineAgent.TimelineEvent}
    528      */
    529     _createSynchronousTimeRecord: function(beginRecord, endRecord, children)
    530     {
    531         return {
    532             type: beginRecord.type,
    533             startTime: beginRecord.startTime,
    534             endTime: endRecord.startTime,
    535             stackTrace: beginRecord.stackTrace,
    536             children: children,
    537             data: {
    538                 message: beginRecord.data.message,
    539                 isSynchronous: true
    540            },
    541         };
    542     },
    543 
    544     _findParentRecord: function(record)
    545     {
    546         if (!this._glueRecords)
    547             return null;
    548         var recordTypes = WebInspector.TimelineModel.RecordType;
    549 
    550         switch (record.type) {
    551         case recordTypes.ResourceReceiveResponse:
    552         case recordTypes.ResourceFinish:
    553         case recordTypes.ResourceReceivedData:
    554             return this._sendRequestRecords[record.data["requestId"]];
    555 
    556         case recordTypes.ResourceSendRequest:
    557             return this._rootRecord;
    558 
    559         case recordTypes.TimerFire:
    560             return this._timerRecords[record.data["timerId"]];
    561 
    562         case recordTypes.ResourceSendRequest:
    563             return this._scheduledResourceRequests[record.data["url"]];
    564 
    565         case recordTypes.FireAnimationFrame:
    566             return this._requestAnimationFrameRecords[record.data["id"]];
    567         }
    568     },
    569 
    570     setGlueRecords: function(glue)
    571     {
    572         this._glueRecords = glue;
    573     },
    574 
    575     invalidateFilteredRecords: function()
    576     {
    577         delete this._filteredRecords;
    578     },
    579 
    580     filteredRecords: function()
    581     {
    582         if (this._filteredRecords)
    583             return this._filteredRecords;
    584 
    585         var recordsInWindow = [];
    586         var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false, parentRecord: {}}];
    587         var revealedDepth = 0;
    588 
    589         function revealRecordsInStack() {
    590             for (var depth = revealedDepth + 1; depth < stack.length; ++depth) {
    591                 if (stack[depth - 1].parentIsCollapsed) {
    592                     stack[depth].parentRecord.parent._expandable = true;
    593                     return;
    594                 }
    595                 stack[depth - 1].parentRecord.collapsed = false;
    596                 recordsInWindow.push(stack[depth].parentRecord);
    597                 stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length;
    598                 stack[depth].parentIsRevealed = true;
    599                 revealedDepth = depth;
    600             }
    601         }
    602 
    603         while (stack.length) {
    604             var entry = stack[stack.length - 1];
    605             var records = entry.children;
    606             if (records && entry.index < records.length) {
    607                 var record = records[entry.index];
    608                 ++entry.index;
    609 
    610                 if (this.isVisible(record)) {
    611                     record.parent._expandable = true;
    612                     if (this._searchFilter)
    613                         revealRecordsInStack();
    614                     if (!entry.parentIsCollapsed) {
    615                         recordsInWindow.push(record);
    616                         revealedDepth = stack.length;
    617                         entry.parentRecord.collapsed = false;
    618                     }
    619                 }
    620 
    621                 record._expandable = false;
    622 
    623                 stack.push({children: record.children,
    624                             index: 0,
    625                             parentIsCollapsed: (entry.parentIsCollapsed || (record.collapsed && (!this._searchFilter || record.clicked))),
    626                             parentRecord: record,
    627                             windowLengthBeforeChildrenTraversal: recordsInWindow.length});
    628             } else {
    629                 stack.pop();
    630                 revealedDepth = Math.min(revealedDepth, stack.length - 1);
    631                 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
    632             }
    633         }
    634 
    635         this._filteredRecords = recordsInWindow;
    636         return recordsInWindow;
    637     },
    638 
    639     filteredFrames: function(startTime, endTime)
    640     {
    641         function compareStartTime(value, object)
    642         {
    643             return value - object.startTime;
    644         }
    645         function compareEndTime(value, object)
    646         {
    647             return value - object.endTime;
    648         }
    649         var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime);
    650         var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime);
    651         while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime)
    652             ++lastFrame;
    653         return this._frames.slice(firstFrame, lastFrame);
    654     },
    655 
    656     eventDividerRecords: function()
    657     {
    658         return this._eventDividerRecords;
    659     },
    660 
    661     isVisible: function(record)
    662     {
    663         for (var i = 0; i < this._filters.length; ++i) {
    664             if (!this._filters[i].accept(record))
    665                 return false;
    666         }
    667         return !this._searchFilter || this._searchFilter.accept(record);
    668     },
    669 
    670     /**
    671      * @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info
    672      * @return {!Element}
    673      */
    674     generateMainThreadBarPopupContent: function(info)
    675     {
    676         var firstTaskIndex = info.firstTaskIndex;
    677         var lastTaskIndex = info.lastTaskIndex;
    678         var tasks = info.tasks;
    679         var messageCount = lastTaskIndex - firstTaskIndex + 1;
    680         var cpuTime = 0;
    681 
    682         for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
    683             var task = tasks[i];
    684             cpuTime += WebInspector.TimelineModel.endTimeInSeconds(task) - WebInspector.TimelineModel.startTimeInSeconds(task);
    685         }
    686         var startTime = WebInspector.TimelineModel.startTimeInSeconds(tasks[firstTaskIndex]);
    687         var endTime = WebInspector.TimelineModel.endTimeInSeconds(tasks[lastTaskIndex]);
    688         var duration = endTime - startTime;
    689         var offset = this._minimumRecordTime;
    690 
    691         var contentHelper = new WebInspector.TimelinePopupContentHelper(info.name);
    692         var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true),
    693             Number.secondsToString(startTime - offset, true));
    694         contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
    695         contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true));
    696         contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount);
    697         return contentHelper.contentTable();
    698     },
    699 
    700     __proto__: WebInspector.Object.prototype
    701 }
    702 
    703 /**
    704  * @constructor
    705  * @param {!WebInspector.TimelinePresentationModel} presentationModel
    706  * @param {!Object} record
    707  * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
    708  * @param {?WebInspector.TimelinePresentationModel.Record} origin
    709  * @param {?Object} scriptDetails
    710  * @param {boolean} hidden
    711  */
    712 WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden)
    713 {
    714     this._linkifier = presentationModel._linkifier;
    715     this._aggregatedStats = {};
    716     this._record = record;
    717     this._children = [];
    718     if (!hidden && parentRecord) {
    719         this.parent = parentRecord;
    720         if (this.isBackground)
    721             WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this);
    722         else
    723             parentRecord.children.push(this);
    724     }
    725     if (origin)
    726         this._origin = origin;
    727 
    728     this._selfTime = this.endTime - this.startTime;
    729     this._lastChildEndTime = this.endTime;
    730     this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime;
    731 
    732     if (record.data) {
    733         if (record.data["url"])
    734             this.url = record.data["url"];
    735         if (record.data["rootNode"])
    736             this._relatedBackendNodeId = record.data["rootNode"];
    737         else if (record.data["elementId"])
    738             this._relatedBackendNodeId = record.data["elementId"];
    739     }
    740     if (scriptDetails) {
    741         this.scriptName = scriptDetails.scriptName;
    742         this.scriptLine = scriptDetails.scriptLine;
    743     }
    744     if (parentRecord && parentRecord.callSiteStackTrace)
    745         this.callSiteStackTrace = parentRecord.callSiteStackTrace;
    746 
    747     var recordTypes = WebInspector.TimelineModel.RecordType;
    748     switch (record.type) {
    749     case recordTypes.ResourceSendRequest:
    750         // Make resource receive record last since request was sent; make finish record last since response received.
    751         presentationModel._sendRequestRecords[record.data["requestId"]] = this;
    752         break;
    753 
    754     case recordTypes.ScheduleResourceRequest:
    755         presentationModel._scheduledResourceRequests[record.data["url"]] = this;
    756         break;
    757 
    758     case recordTypes.ResourceReceiveResponse:
    759         var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
    760         if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
    761             this.url = sendRequestRecord.url;
    762             // Now that we have resource in the collection, recalculate details in order to display short url.
    763             sendRequestRecord._refreshDetails();
    764             if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
    765                 sendRequestRecord.parent._refreshDetails();
    766         }
    767         break;
    768 
    769     case recordTypes.ResourceReceivedData:
    770     case recordTypes.ResourceFinish:
    771         var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
    772         if (sendRequestRecord) // False for main resource.
    773             this.url = sendRequestRecord.url;
    774         break;
    775 
    776     case recordTypes.TimerInstall:
    777         this.timeout = record.data["timeout"];
    778         this.singleShot = record.data["singleShot"];
    779         presentationModel._timerRecords[record.data["timerId"]] = this;
    780         break;
    781 
    782     case recordTypes.TimerFire:
    783         var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]];
    784         if (timerInstalledRecord) {
    785             this.callSiteStackTrace = timerInstalledRecord.stackTrace;
    786             this.timeout = timerInstalledRecord.timeout;
    787             this.singleShot = timerInstalledRecord.singleShot;
    788         }
    789         break;
    790 
    791     case recordTypes.RequestAnimationFrame:
    792         presentationModel._requestAnimationFrameRecords[record.data["id"]] = this;
    793         break;
    794 
    795     case recordTypes.FireAnimationFrame:
    796         var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]];
    797         if (requestAnimationRecord)
    798             this.callSiteStackTrace = requestAnimationRecord.stackTrace;
    799         break;
    800 
    801     case recordTypes.Time:
    802         if (record.data.isSynchronous)
    803             break;
    804         var message = record.data["message"];
    805         var oldReference = presentationModel._timeRecords[message];
    806         if (oldReference)
    807             break;
    808         presentationModel._timeRecords[message] = this;
    809         if (origin)
    810             presentationModel._timeRecordStack.push(this);
    811         break;
    812 
    813     case recordTypes.TimeEnd:
    814         var message = record.data["message"];
    815         var timeRecord = presentationModel._timeRecords[message];
    816         delete presentationModel._timeRecords[message];
    817         if (timeRecord) {
    818             this.timeRecord = timeRecord;
    819             timeRecord.timeEndRecord = this;
    820             var intervalDuration = this.startTime - timeRecord.startTime;
    821             this.intervalDuration = intervalDuration;
    822             timeRecord.intervalDuration = intervalDuration;
    823         }
    824         break;
    825 
    826     case recordTypes.ScheduleStyleRecalculation:
    827         presentationModel._lastScheduleStyleRecalculation[this.frameId] = this;
    828         break;
    829 
    830     case recordTypes.RecalculateStyles:
    831         var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId];
    832         if (!scheduleStyleRecalculationRecord)
    833             break;
    834         this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
    835         break;
    836 
    837     case recordTypes.InvalidateLayout:
    838         // Consider style recalculation as a reason for layout invalidation,
    839         // but only if we had no earlier layout invalidation records.
    840         var styleRecalcStack;
    841         if (!presentationModel._layoutInvalidateStack[this.frameId]) {
    842             for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) {
    843                 if (outerRecord.type === recordTypes.RecalculateStyles) {
    844                     styleRecalcStack = outerRecord.callSiteStackTrace;
    845                     break;
    846                 }
    847             }
    848         }
    849         presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
    850         break;
    851 
    852     case recordTypes.Layout:
    853         var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId];
    854         if (layoutInvalidateStack)
    855             this.callSiteStackTrace = layoutInvalidateStack;
    856         if (this.stackTrace)
    857             this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
    858 
    859         presentationModel._layoutInvalidateStack[this.frameId] = null;
    860         this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
    861         this._relatedBackendNodeId = record.data["rootNode"];
    862         break;
    863 
    864     case recordTypes.AutosizeText:
    865         if (record.data.needsRelayout && parentRecord.type === recordTypes.Layout)
    866             parentRecord.addWarning(WebInspector.UIString("Layout required two passes due to text autosizing, consider setting viewport."));
    867         break;
    868 
    869     case recordTypes.Paint:
    870         this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data);
    871         break;
    872 
    873     case recordTypes.WebSocketCreate:
    874         this.webSocketURL = record.data["url"];
    875         if (typeof record.data["webSocketProtocol"] !== "undefined")
    876             this.webSocketProtocol = record.data["webSocketProtocol"];
    877         presentationModel._webSocketCreateRecords[record.data["identifier"]] = this;
    878         break;
    879 
    880     case recordTypes.WebSocketSendHandshakeRequest:
    881     case recordTypes.WebSocketReceiveHandshakeResponse:
    882     case recordTypes.WebSocketDestroy:
    883         var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]];
    884         if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
    885             this.webSocketURL = webSocketCreateRecord.webSocketURL;
    886             if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
    887                 this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
    888         }
    889         break;
    890     }
    891 }
    892 
    893 WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record)
    894 {
    895     record.parent.children.splice(record.parent.children.indexOf(record));
    896     WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record);
    897     record.parent = newParent;
    898 }
    899 
    900 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record)
    901 {
    902     function compareStartTime(value, record)
    903     {
    904         return value < record.startTime ? -1 : 1;
    905     }
    906 
    907     parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record);
    908 }
    909 
    910 WebInspector.TimelinePresentationModel.Record.prototype = {
    911     get lastChildEndTime()
    912     {
    913         return this._lastChildEndTime;
    914     },
    915 
    916     set lastChildEndTime(time)
    917     {
    918         this._lastChildEndTime = time;
    919     },
    920 
    921     get selfTime()
    922     {
    923         return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime;
    924     },
    925 
    926     set selfTime(time)
    927     {
    928         this._selfTime = time;
    929     },
    930 
    931     get cpuTime()
    932     {
    933         return this._cpuTime;
    934     },
    935 
    936     /**
    937      * @return {boolean}
    938      */
    939     isRoot: function()
    940     {
    941         return this.type === WebInspector.TimelineModel.RecordType.Root;
    942     },
    943 
    944     /**
    945      * @return {!WebInspector.TimelinePresentationModel.Record}
    946      */
    947     origin: function()
    948     {
    949         return this._origin || this.parent;
    950     },
    951 
    952     /**
    953      * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
    954      */
    955     get children()
    956     {
    957         return this._children;
    958     },
    959 
    960     /**
    961      * @return {number}
    962      */
    963     get visibleChildrenCount()
    964     {
    965         return this._visibleChildrenCount || 0;
    966     },
    967 
    968     /**
    969      * @return {boolean}
    970      */
    971     get expandable()
    972     {
    973         return !!this._expandable;
    974     },
    975 
    976     /**
    977      * @return {!WebInspector.TimelineCategory}
    978      */
    979     get category()
    980     {
    981         return WebInspector.TimelinePresentationModel.recordStyle(this._record).category
    982     },
    983 
    984     /**
    985      * @return {string}
    986      */
    987     get title()
    988     {
    989         return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] :
    990             WebInspector.TimelinePresentationModel.recordStyle(this._record).title;
    991     },
    992 
    993     /**
    994      * @return {number}
    995      */
    996     get startTime()
    997     {
    998         return WebInspector.TimelineModel.startTimeInSeconds(this._record);
    999     },
   1000 
   1001     /**
   1002      * @return {number}
   1003      */
   1004     get endTime()
   1005     {
   1006         return WebInspector.TimelineModel.endTimeInSeconds(this._record);
   1007     },
   1008 
   1009     /**
   1010      * @return {boolean}
   1011      */
   1012     get isBackground()
   1013     {
   1014         return !!this._record.thread;
   1015     },
   1016 
   1017     /**
   1018      * @return {!Object}
   1019      */
   1020     get data()
   1021     {
   1022         return this._record.data;
   1023     },
   1024 
   1025     /**
   1026      * @return {string}
   1027      */
   1028     get type()
   1029     {
   1030         return this._record.type;
   1031     },
   1032 
   1033     /**
   1034      * @return {string}
   1035      */
   1036     get frameId()
   1037     {
   1038         return this._record.frameId;
   1039     },
   1040 
   1041     /**
   1042      * @return {number}
   1043      */
   1044     get usedHeapSizeDelta()
   1045     {
   1046         return this._record.usedHeapSizeDelta || 0;
   1047     },
   1048 
   1049     /**
   1050      * @return {number}
   1051      */
   1052     get usedHeapSize()
   1053     {
   1054         return this._record.usedHeapSize;
   1055     },
   1056 
   1057     /**
   1058      * @return {?Array.<!ConsoleAgent.CallFrame>}
   1059      */
   1060     get stackTrace()
   1061     {
   1062         if (this._record.stackTrace && this._record.stackTrace.length)
   1063             return this._record.stackTrace;
   1064         return null;
   1065     },
   1066 
   1067     containsTime: function(time)
   1068     {
   1069         return this.startTime <= time && time <= this.endTime;
   1070     },
   1071 
   1072     /**
   1073      * @param {function(!DocumentFragment)} callback
   1074      */
   1075     generatePopupContent: function(callback)
   1076     {
   1077         var barrier = new CallbackBarrier();
   1078         if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type) && !this._imagePreviewElement)
   1079             WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, barrier.createCallback(this._setImagePreviewElement.bind(this)));
   1080         if (this._relatedBackendNodeId && !this._relatedNode)
   1081             WebInspector.domAgent.pushNodeByBackendIdToFrontend(this._relatedBackendNodeId, barrier.createCallback(this._setRelatedNode.bind(this)));
   1082 
   1083         barrier.callWhenDone(callbackWrapper.bind(this));
   1084 
   1085         /**
   1086          * @this {WebInspector.TimelinePresentationModel.Record}
   1087          */
   1088         function callbackWrapper()
   1089         {
   1090             callback(this._generatePopupContentSynchronously());
   1091         }
   1092     },
   1093 
   1094     /**
   1095      * @param {string} key
   1096      * @return {?Object}
   1097      */
   1098     getUserObject: function(key)
   1099     {
   1100         if (!this._userObjects)
   1101             return null;
   1102         return this._userObjects.get(key);
   1103     },
   1104 
   1105     /**
   1106      * @param {string} key
   1107      * @param {!Object} value
   1108      */
   1109     setUserObject: function(key, value)
   1110     {
   1111         if (!this._userObjects)
   1112             this._userObjects = new StringMap();
   1113         this._userObjects.put(key, value);
   1114     },
   1115 
   1116     /**
   1117      * @param {!Element} element
   1118      */
   1119     _setImagePreviewElement: function(element)
   1120     {
   1121         this._imagePreviewElement = element;
   1122     },
   1123 
   1124     /**
   1125      * @param {?DOMAgent.NodeId} nodeId
   1126      */
   1127     _setRelatedNode: function(nodeId)
   1128     {
   1129         if (typeof nodeId === "number")
   1130             this._relatedNode = WebInspector.domAgent.nodeForId(nodeId);
   1131     },
   1132 
   1133     /**
   1134      * @return {!DocumentFragment}
   1135      */
   1136     _generatePopupContentSynchronously: function()
   1137     {
   1138         var fragment = document.createDocumentFragment();
   1139         var pie = WebInspector.TimelinePresentationModel.generatePieChart(this._aggregatedStats, this.category.name);
   1140         // Insert self time.
   1141         if (!this.coalesced && this._children.length) {
   1142             pie.pieChart.addSlice(this._selfTime, this.category.fillColorStop1);
   1143             var rowElement = document.createElement("div");
   1144             pie.footerElement.insertBefore(rowElement, pie.footerElement.firstChild);
   1145             rowElement.createChild("div", "timeline-aggregated-category timeline-" + this.category.name);
   1146             rowElement.createTextChild(WebInspector.UIString("%s %s (Self)", Number.secondsToString(this._selfTime, true), this.category.title));
   1147         }
   1148         fragment.appendChild(pie.element);
   1149 
   1150         var contentHelper = new WebInspector.TimelineDetailsContentHelper(true);
   1151 
   1152         if (this.coalesced)
   1153             return fragment;
   1154 
   1155         const recordTypes = WebInspector.TimelineModel.RecordType;
   1156 
   1157         // The messages may vary per record type;
   1158         var callSiteStackTraceLabel;
   1159         var callStackLabel;
   1160         var relatedNodeLabel;
   1161 
   1162         switch (this.type) {
   1163             case recordTypes.GCEvent:
   1164                 contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"]));
   1165                 break;
   1166             case recordTypes.TimerFire:
   1167                 callSiteStackTraceLabel = WebInspector.UIString("Timer installed");
   1168                 // Fall-through intended.
   1169 
   1170             case recordTypes.TimerInstall:
   1171             case recordTypes.TimerRemove:
   1172                 contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]);
   1173                 if (typeof this.timeout === "number") {
   1174                     contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000));
   1175                     contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
   1176                 }
   1177                 break;
   1178             case recordTypes.FireAnimationFrame:
   1179                 callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested");
   1180                 contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]);
   1181                 break;
   1182             case recordTypes.FunctionCall:
   1183                 if (this.scriptName)
   1184                     contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyLocation(this.scriptName, this.scriptLine, 0));
   1185                 break;
   1186             case recordTypes.ScheduleResourceRequest:
   1187             case recordTypes.ResourceSendRequest:
   1188             case recordTypes.ResourceReceiveResponse:
   1189             case recordTypes.ResourceReceivedData:
   1190             case recordTypes.ResourceFinish:
   1191                 contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url));
   1192                 if (this._imagePreviewElement)
   1193                     contentHelper.appendElementRow(WebInspector.UIString("Preview"), this._imagePreviewElement);
   1194                 if (this.data["requestMethod"])
   1195                     contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]);
   1196                 if (typeof this.data["statusCode"] === "number")
   1197                     contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]);
   1198                 if (this.data["mimeType"])
   1199                     contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]);
   1200                 if (this.data["encodedDataLength"])
   1201                     contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"]));
   1202                 break;
   1203             case recordTypes.EvaluateScript:
   1204                 if (this.data && this.url)
   1205                     contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"]));
   1206                 break;
   1207             case recordTypes.Paint:
   1208                 var clip = this.data["clip"];
   1209                 if (clip) {
   1210                     contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
   1211                     var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip);
   1212                     var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip);
   1213                     contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d  %d", clipWidth, clipHeight));
   1214                 } else {
   1215                     // Backward compatibility: older version used x, y, width, height fields directly in data.
   1216                     if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined")
   1217                         contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"]));
   1218                     if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined")
   1219                         contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"]));
   1220                 }
   1221                 // Fall-through intended.
   1222 
   1223             case recordTypes.PaintSetup:
   1224             case recordTypes.Rasterize:
   1225             case recordTypes.ScrollLayer:
   1226                 relatedNodeLabel = WebInspector.UIString("Layer root");
   1227                 break;
   1228             case recordTypes.AutosizeText:
   1229                 relatedNodeLabel = WebInspector.UIString("Root node");
   1230                 break;
   1231             case recordTypes.DecodeImage:
   1232             case recordTypes.ResizeImage:
   1233                 relatedNodeLabel = WebInspector.UIString("Image element");
   1234                 if (this.url)
   1235                     contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(this.url));
   1236                 break;
   1237             case recordTypes.RecalculateStyles: // We don't want to see default details.
   1238                 if (this.data["elementCount"])
   1239                     contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]);
   1240                 callStackLabel = WebInspector.UIString("Styles recalculation forced");
   1241                 break;
   1242             case recordTypes.Layout:
   1243                 if (this.data["dirtyObjects"])
   1244                     contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]);
   1245                 if (this.data["totalObjects"])
   1246                     contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]);
   1247                 if (typeof this.data["partialLayout"] === "boolean") {
   1248                     contentHelper.appendTextRow(WebInspector.UIString("Layout scope"),
   1249                        this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document"));
   1250                 }
   1251                 callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
   1252                 callStackLabel = WebInspector.UIString("Layout forced");
   1253                 relatedNodeLabel = WebInspector.UIString("Layout root");
   1254                 break;
   1255             case recordTypes.Time:
   1256             case recordTypes.TimeEnd:
   1257                 contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
   1258                 if (typeof this.intervalDuration === "number")
   1259                     contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true));
   1260                 break;
   1261             case recordTypes.WebSocketCreate:
   1262             case recordTypes.WebSocketSendHandshakeRequest:
   1263             case recordTypes.WebSocketReceiveHandshakeResponse:
   1264             case recordTypes.WebSocketDestroy:
   1265                 if (typeof this.webSocketURL !== "undefined")
   1266                     contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL);
   1267                 if (typeof this.webSocketProtocol !== "undefined")
   1268                     contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol);
   1269                 if (typeof this.data["message"] !== "undefined")
   1270                     contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
   1271                 break;
   1272             default:
   1273                 if (this.detailsNode())
   1274                     contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode());
   1275                 break;
   1276         }
   1277 
   1278         if (this._relatedNode)
   1279             contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related node"), this._createNodeAnchor(this._relatedNode));
   1280 
   1281         if (this.scriptName && this.type !== recordTypes.FunctionCall)
   1282             contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyLocation(this.scriptName, this.scriptLine, 0));
   1283 
   1284         if (this.usedHeapSize) {
   1285             if (this.usedHeapSizeDelta) {
   1286                 var sign = this.usedHeapSizeDelta > 0 ? "+" : "-";
   1287                 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"),
   1288                     WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(Math.abs(this.usedHeapSizeDelta))));
   1289             } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting)
   1290                 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize));
   1291         }
   1292 
   1293         if (this.callSiteStackTrace)
   1294             contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this));
   1295 
   1296         if (this.stackTrace)
   1297             contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this));
   1298 
   1299         if (this._warnings) {
   1300             var ul = document.createElement("ul");
   1301             for (var i = 0; i < this._warnings.length; ++i)
   1302                 ul.createChild("li").textContent = this._warnings[i];
   1303             contentHelper.appendElementRow(WebInspector.UIString("Warning"), ul);
   1304         }
   1305         fragment.appendChild(contentHelper.element);
   1306         return fragment;
   1307     },
   1308 
   1309     /**
   1310      * @param {!WebInspector.DOMAgent} node
   1311      */
   1312     _createNodeAnchor: function(node)
   1313     {
   1314         var span = document.createElement("span");
   1315         span.classList.add("node-link");
   1316         span.addEventListener("click", onClick, false);
   1317         WebInspector.DOMPresentationUtils.decorateNodeLabel(node, span);
   1318         function onClick()
   1319         {
   1320             WebInspector.showPanel("elements").revealAndSelectNode(node.id);
   1321         }
   1322         return span;
   1323     },
   1324 
   1325     _refreshDetails: function()
   1326     {
   1327         delete this._detailsNode;
   1328     },
   1329 
   1330     /**
   1331      * @return {?Node}
   1332      */
   1333     detailsNode: function()
   1334     {
   1335         if (typeof this._detailsNode === "undefined") {
   1336             this._detailsNode = this._getRecordDetails();
   1337 
   1338             if (this._detailsNode && !this.coalesced) {
   1339                 this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild);
   1340                 this._detailsNode.appendChild(document.createTextNode(")"));
   1341             }
   1342         }
   1343         return this._detailsNode;
   1344     },
   1345 
   1346     _createSpanWithText: function(textContent)
   1347     {
   1348         var node = document.createElement("span");
   1349         node.textContent = textContent;
   1350         return node;
   1351     },
   1352 
   1353     /**
   1354      * @return {?Node}
   1355      */
   1356     _getRecordDetails: function()
   1357     {
   1358         var details;
   1359         if (this.coalesced)
   1360             return this._createSpanWithText(WebInspector.UIString(" %d", this.children.length));
   1361 
   1362         switch (this.type) {
   1363         case WebInspector.TimelineModel.RecordType.GCEvent:
   1364             details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"]));
   1365             break;
   1366         case WebInspector.TimelineModel.RecordType.TimerFire:
   1367             details = this._linkifyScriptLocation(this.data["timerId"]);
   1368             break;
   1369         case WebInspector.TimelineModel.RecordType.FunctionCall:
   1370             if (this.scriptName)
   1371                 details = this._linkifyLocation(this.scriptName, this.scriptLine, 0);
   1372             break;
   1373         case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
   1374             details = this._linkifyScriptLocation(this.data["id"]);
   1375             break;
   1376         case WebInspector.TimelineModel.RecordType.EventDispatch:
   1377             details = this.data ? this.data["type"] : null;
   1378             break;
   1379         case WebInspector.TimelineModel.RecordType.Paint:
   1380             var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width;
   1381             var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height;
   1382             if (width && height)
   1383                 details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
   1384             break;
   1385         case WebInspector.TimelineModel.RecordType.TimerInstall:
   1386         case WebInspector.TimelineModel.RecordType.TimerRemove:
   1387             details = this._linkifyTopCallFrame(this.data["timerId"]);
   1388             break;
   1389         case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
   1390         case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
   1391             details = this._linkifyTopCallFrame(this.data["id"]);
   1392             break;
   1393         case WebInspector.TimelineModel.RecordType.ParseHTML:
   1394         case WebInspector.TimelineModel.RecordType.RecalculateStyles:
   1395             details = this._linkifyTopCallFrame();
   1396             break;
   1397         case WebInspector.TimelineModel.RecordType.EvaluateScript:
   1398             details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null;
   1399             break;
   1400         case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
   1401         case WebInspector.TimelineModel.RecordType.XHRLoad:
   1402         case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest:
   1403         case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
   1404         case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
   1405         case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
   1406         case WebInspector.TimelineModel.RecordType.ResourceFinish:
   1407         case WebInspector.TimelineModel.RecordType.DecodeImage:
   1408         case WebInspector.TimelineModel.RecordType.ResizeImage:
   1409             details = WebInspector.displayNameForURL(this.url);
   1410             break;
   1411         case WebInspector.TimelineModel.RecordType.Time:
   1412         case WebInspector.TimelineModel.RecordType.TimeEnd:
   1413             details = this.data["message"];
   1414             break;
   1415         default:
   1416             details = this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : (this._linkifyTopCallFrame() || null);
   1417             break;
   1418         }
   1419 
   1420         if (details) {
   1421             if (details instanceof Node)
   1422                 details.tabIndex = -1;
   1423             else
   1424                 return this._createSpanWithText("" + details);
   1425         }
   1426 
   1427         return details || null;
   1428     },
   1429 
   1430     /**
   1431      * @param {string} url
   1432      * @param {number} lineNumber
   1433      * @param {number=} columnNumber
   1434      */
   1435     _linkifyLocation: function(url, lineNumber, columnNumber)
   1436     {
   1437         // FIXME(62725): stack trace line/column numbers are one-based.
   1438         columnNumber = columnNumber ? columnNumber - 1 : 0;
   1439         return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
   1440     },
   1441 
   1442     /**
   1443      * @param {!ConsoleAgent.CallFrame} callFrame
   1444      */
   1445     _linkifyCallFrame: function(callFrame)
   1446     {
   1447         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
   1448     },
   1449 
   1450     /**
   1451      * @param {string=} defaultValue
   1452      */
   1453     _linkifyTopCallFrame: function(defaultValue)
   1454     {
   1455         if (this.stackTrace)
   1456             return this._linkifyCallFrame(this.stackTrace[0]);
   1457         if (this.callSiteStackTrace)
   1458             return this._linkifyCallFrame(this.callSiteStackTrace[0]);
   1459         return defaultValue;
   1460     },
   1461 
   1462     /**
   1463      * @param {*} defaultValue
   1464      * @return {!Element|string}
   1465      */
   1466     _linkifyScriptLocation: function(defaultValue)
   1467     {
   1468         return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : "" + defaultValue;
   1469     },
   1470 
   1471     calculateAggregatedStats: function()
   1472     {
   1473         this._aggregatedStats = {};
   1474         this._cpuTime = this._selfTime;
   1475 
   1476         for (var index = this._children.length; index; --index) {
   1477             var child = this._children[index - 1];
   1478             for (var category in child._aggregatedStats)
   1479                 this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category];
   1480         }
   1481         for (var category in this._aggregatedStats)
   1482             this._cpuTime += this._aggregatedStats[category];
   1483         this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime;
   1484     },
   1485 
   1486     get aggregatedStats()
   1487     {
   1488         return this._aggregatedStats;
   1489     },
   1490 
   1491     /**
   1492      * @param {string} message
   1493      */
   1494     addWarning: function(message)
   1495     {
   1496         if (this._warnings)
   1497             this._warnings.push(message);
   1498         else
   1499             this._warnings = [message];
   1500         for (var parent = this.parent; parent && !parent._childHasWarnings; parent = parent.parent)
   1501             parent._childHasWarnings = true;
   1502     },
   1503 
   1504     /**
   1505      * @return {boolean}
   1506      */
   1507     hasWarnings: function()
   1508     {
   1509         return !!this._warnings;
   1510     },
   1511 
   1512     /**
   1513      * @return {boolean}
   1514      */
   1515     childHasWarnings: function()
   1516     {
   1517         return this._childHasWarnings;
   1518     }
   1519 }
   1520 
   1521 /**
   1522  * @param {!Object} aggregatedStats
   1523  */
   1524 WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats)
   1525 {
   1526     var cell = document.createElement("span");
   1527     cell.className = "timeline-aggregated-info";
   1528     for (var index in aggregatedStats) {
   1529         var label = document.createElement("div");
   1530         label.className = "timeline-aggregated-category timeline-" + index;
   1531         cell.appendChild(label);
   1532         var text = document.createElement("span");
   1533         text.textContent = Number.secondsToString(aggregatedStats[index], true);
   1534         cell.appendChild(text);
   1535     }
   1536     return cell;
   1537 }
   1538 
   1539 /**
   1540  * @param {!Object} aggregatedStats
   1541  * @param {string=} firstCategoryName
   1542  * @return {{pieChart: !WebInspector.PieChart, element: !Element, footerElement: !Element}}
   1543  */
   1544 WebInspector.TimelinePresentationModel.generatePieChart = function(aggregatedStats, firstCategoryName)
   1545 {
   1546     var element = document.createElement("div");
   1547     element.className = "timeline-aggregated-info";
   1548 
   1549     var total = 0;
   1550     var categoryNames = [];
   1551     if (firstCategoryName)
   1552         categoryNames.push(firstCategoryName);
   1553     for (var categoryName in WebInspector.TimelinePresentationModel.categories()) {
   1554         if (aggregatedStats[categoryName]) {
   1555             total += aggregatedStats[categoryName];
   1556             if (firstCategoryName !== categoryName)
   1557                 categoryNames.push(categoryName);
   1558         }
   1559     }
   1560 
   1561     var pieChart = new WebInspector.PieChart(total);
   1562     element.appendChild(pieChart.element);
   1563     var footerElement = element.createChild("div", "timeline-aggregated-info-legend");
   1564 
   1565     for (var i = 0; i < categoryNames.length; ++i) {
   1566         var category = WebInspector.TimelinePresentationModel.categories()[categoryNames[i]];
   1567         pieChart.addSlice(aggregatedStats[category.name], category.fillColorStop0);
   1568         var rowElement = footerElement.createChild("div");
   1569         rowElement.createChild("div", "timeline-aggregated-category timeline-" + category.name);
   1570         rowElement.createTextChild(WebInspector.UIString("%s %s", Number.secondsToString(aggregatedStats[category.name], true), category.title));
   1571     }
   1572     return { pieChart: pieChart, element: element, footerElement: footerElement };
   1573 }
   1574 
   1575 WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame)
   1576 {
   1577     var contentHelper = new WebInspector.TimelinePopupContentHelper(WebInspector.UIString("Frame"));
   1578     var durationInSeconds = frame.endTime - frame.startTime;
   1579     var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true),
   1580         Number.secondsToString(frame.startTimeOffset, true));
   1581     contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
   1582     contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds));
   1583     contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true));
   1584     contentHelper.appendTextRow(WebInspector.UIString("Thread"), frame.isBackground ? WebInspector.UIString("background") : WebInspector.UIString("main"));
   1585     contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
   1586         WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory));
   1587     return contentHelper.contentTable();
   1588 }
   1589 
   1590 /**
   1591  * @param {!WebInspector.FrameStatistics} statistics
   1592  */
   1593 WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics)
   1594 {
   1595     /**
   1596      * @param {number} time
   1597      */
   1598     function formatTimeAndFPS(time)
   1599     {
   1600         return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time);
   1601     }
   1602 
   1603     var contentHelper = new WebInspector.TimelineDetailsContentHelper(false);
   1604     contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration));
   1605     contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average));
   1606     contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration));
   1607     contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true));
   1608 
   1609     return contentHelper.element;
   1610 }
   1611 
   1612 /**
   1613  * @param {!CanvasRenderingContext2D} context
   1614  * @param {number} width
   1615  * @param {number} height
   1616  * @param {string} color0
   1617  * @param {string} color1
   1618  * @param {string} color2
   1619  */
   1620 WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2)
   1621 {
   1622     var gradient = context.createLinearGradient(0, 0, width, height);
   1623     gradient.addColorStop(0, color0);
   1624     gradient.addColorStop(0.25, color1);
   1625     gradient.addColorStop(0.75, color1);
   1626     gradient.addColorStop(1, color2);
   1627     return gradient;
   1628 }
   1629 
   1630 /**
   1631  * @param {!CanvasRenderingContext2D} context
   1632  * @param {number} width
   1633  * @param {number} height
   1634  * @param {!WebInspector.TimelineCategory} category
   1635  */
   1636 WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category)
   1637 {
   1638     return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
   1639 }
   1640 
   1641 /**
   1642  * @param {!WebInspector.TimelineCategory} category
   1643  */
   1644 WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category)
   1645 {
   1646     var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
   1647         ".panel.timeline .timeline-filters-header .filter-checkbox-filter.filter-checkbox-filter-" + category.name + " .checkbox-filter-checkbox, " +
   1648         ".popover .timeline-" + category.name + ", " +
   1649         ".timeline-details-view .timeline-" + category.name + ", " +
   1650         ".timeline-category-" + category.name + " .timeline-tree-icon"
   1651 
   1652     return selector + " { background-image: -webkit-linear-gradient(" +
   1653        category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" +
   1654        " border-color: " + category.borderColor +
   1655        "}";
   1656 }
   1657 
   1658 
   1659 /**
   1660  * @param {!Object} rawRecord
   1661  * @return {?string}
   1662  */
   1663 WebInspector.TimelinePresentationModel.coalescingKeyForRecord = function(rawRecord)
   1664 {
   1665     var recordTypes = WebInspector.TimelineModel.RecordType;
   1666     switch (rawRecord.type)
   1667     {
   1668     case recordTypes.EventDispatch: return rawRecord.data["type"];
   1669     case recordTypes.Time: return rawRecord.data["message"];
   1670     case recordTypes.TimeStamp: return rawRecord.data["message"];
   1671     default: return null;
   1672     }
   1673 }
   1674 
   1675 /**
   1676  * @param {!Array.<number>} quad
   1677  * @return {number}
   1678  */
   1679 WebInspector.TimelinePresentationModel.quadWidth = function(quad)
   1680 {
   1681     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
   1682 }
   1683 
   1684 /**
   1685  * @param {!Array.<number>} quad
   1686  * @return {number}
   1687  */
   1688 WebInspector.TimelinePresentationModel.quadHeight = function(quad)
   1689 {
   1690     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
   1691 }
   1692 
   1693 /**
   1694  * @param {!Object} data
   1695  * @return {?Array.<number>}
   1696  */
   1697 WebInspector.TimelinePresentationModel.quadFromRectData = function(data)
   1698 {
   1699     if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined")
   1700         return null;
   1701     var x0 = data["x"];
   1702     var x1 = data["x"] + data["width"];
   1703     var y0 = data["y"];
   1704     var y1 = data["y"] + data["height"];
   1705     return [x0, y0, x1, y0, x1, y1, x0, y1];
   1706 }
   1707 
   1708 /**
   1709  * @interface
   1710  */
   1711 WebInspector.TimelinePresentationModel.Filter = function()
   1712 {
   1713 }
   1714 
   1715 WebInspector.TimelinePresentationModel.Filter.prototype = {
   1716     /**
   1717      * @param {!WebInspector.TimelinePresentationModel.Record} record
   1718      * @return {boolean}
   1719      */
   1720     accept: function(record) { return false; }
   1721 }
   1722 
   1723 /**
   1724  * @constructor
   1725  * @extends {WebInspector.Object}
   1726  * @param {string} name
   1727  * @param {string} title
   1728  * @param {number} overviewStripGroupIndex
   1729  * @param {string} borderColor
   1730  * @param {string} fillColorStop0
   1731  * @param {string} fillColorStop1
   1732  */
   1733 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1)
   1734 {
   1735     this.name = name;
   1736     this.title = title;
   1737     this.overviewStripGroupIndex = overviewStripGroupIndex;
   1738     this.borderColor = borderColor;
   1739     this.fillColorStop0 = fillColorStop0;
   1740     this.fillColorStop1 = fillColorStop1;
   1741     this.hidden = false;
   1742 }
   1743 
   1744 WebInspector.TimelineCategory.Events = {
   1745     VisibilityChanged: "VisibilityChanged"
   1746 };
   1747 
   1748 WebInspector.TimelineCategory.prototype = {
   1749     /**
   1750      * @return {boolean}
   1751      */
   1752     get hidden()
   1753     {
   1754         return this._hidden;
   1755     },
   1756 
   1757     set hidden(hidden)
   1758     {
   1759         this._hidden = hidden;
   1760         this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
   1761     },
   1762 
   1763     __proto__: WebInspector.Object.prototype
   1764 }
   1765 
   1766 /**
   1767  * @constructor
   1768  * @param {string} title
   1769  */
   1770 WebInspector.TimelinePopupContentHelper = function(title)
   1771 {
   1772     this._contentTable = document.createElement("table");
   1773     var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
   1774     titleCell.colSpan = 2;
   1775     var titleRow = document.createElement("tr");
   1776     titleRow.appendChild(titleCell);
   1777     this._contentTable.appendChild(titleRow);
   1778 }
   1779 
   1780 WebInspector.TimelinePopupContentHelper.prototype = {
   1781     contentTable: function()
   1782     {
   1783         return this._contentTable;
   1784     },
   1785 
   1786     /**
   1787      * @param {string=} styleName
   1788      */
   1789     _createCell: function(content, styleName)
   1790     {
   1791         var text = document.createElement("label");
   1792         text.appendChild(document.createTextNode(content));
   1793         var cell = document.createElement("td");
   1794         cell.className = "timeline-details";
   1795         if (styleName)
   1796             cell.className += " " + styleName;
   1797         cell.textContent = content;
   1798         return cell;
   1799     },
   1800 
   1801     /**
   1802      * @param {string} title
   1803      * @param {string|number|boolean} content
   1804      */
   1805     appendTextRow: function(title, content)
   1806     {
   1807         var row = document.createElement("tr");
   1808         row.appendChild(this._createCell(title, "timeline-details-row-title"));
   1809         row.appendChild(this._createCell(content, "timeline-details-row-data"));
   1810         this._contentTable.appendChild(row);
   1811     },
   1812 
   1813     /**
   1814      * @param {string} title
   1815      * @param {!Element|string} content
   1816      */
   1817     appendElementRow: function(title, content)
   1818     {
   1819         var row = document.createElement("tr");
   1820         var titleCell = this._createCell(title, "timeline-details-row-title");
   1821         row.appendChild(titleCell);
   1822         var cell = document.createElement("td");
   1823         cell.className = "details";
   1824         if (content instanceof Node)
   1825             cell.appendChild(content);
   1826         else
   1827             cell.createTextChild(content || "");
   1828         row.appendChild(cell);
   1829         this._contentTable.appendChild(row);
   1830     }
   1831 }
   1832 
   1833 /**
   1834  * @constructor
   1835  * @param {boolean} monospaceValues
   1836  */
   1837 WebInspector.TimelineDetailsContentHelper = function(monospaceValues)
   1838 {
   1839     this.element = document.createElement("div");
   1840     this.element.className = "timeline-details-view-block";
   1841     this._monospaceValues = monospaceValues;
   1842 }
   1843 
   1844 WebInspector.TimelineDetailsContentHelper.prototype = {
   1845     /**
   1846      * @param {string} title
   1847      * @param {string|number|boolean} value
   1848      */
   1849     appendTextRow: function(title, value)
   1850     {
   1851         var rowElement = this.element.createChild("div", "timeline-details-view-row");
   1852         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
   1853         rowElement.createChild("span", "timeline-details-view-row-value" + (this._monospaceValues ? " monospace" : "")).textContent = value;
   1854     },
   1855 
   1856     /**
   1857      * @param {string} title
   1858      * @param {!Element|string} content
   1859      */
   1860     appendElementRow: function(title, content)
   1861     {
   1862         var rowElement = this.element.createChild("div", "timeline-details-view-row");
   1863         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
   1864         var valueElement = rowElement.createChild("span", "timeline-details-view-row-details" + (this._monospaceValues ? " monospace" : ""));
   1865         if (content instanceof Node)
   1866             valueElement.appendChild(content);
   1867         else
   1868             valueElement.createTextChild(content || "");
   1869     },
   1870 
   1871     /**
   1872      * @param {string} title
   1873      * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace
   1874      * @param {function(!ConsoleAgent.CallFrame)} callFrameLinkifier
   1875      */
   1876     appendStackTrace: function(title, stackTrace, callFrameLinkifier)
   1877     {
   1878         var rowElement = this.element.createChild("div", "timeline-details-view-row");
   1879         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
   1880         var stackTraceElement = rowElement.createChild("div", "timeline-details-view-row-stack-trace monospace");
   1881 
   1882         for (var i = 0; i < stackTrace.length; ++i) {
   1883             var stackFrame = stackTrace[i];
   1884             var row = stackTraceElement.createChild("div");
   1885             row.createTextChild(stackFrame.functionName || WebInspector.UIString("(anonymous function)"));
   1886             row.createTextChild(" @ ");
   1887             var urlElement = callFrameLinkifier(stackFrame);
   1888             row.appendChild(urlElement);
   1889         }
   1890     }
   1891 }
   1892