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 */
36WebInspector.TimelinePresentationModel = function()
37{
38    this._linkifier = new WebInspector.Linkifier();
39    this._glueRecords = false;
40    this._filters = [];
41    this.reset();
42}
43
44WebInspector.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 */
61WebInspector.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 */
119WebInspector.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
133WebInspector.TimelinePresentationModel.categoryForRecord = function(record)
134{
135    return WebInspector.TimelinePresentationModel.recordStyle(record).category;
136}
137
138WebInspector.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 */
155WebInspector.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 */
184WebInspector.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 */
205WebInspector.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
226WebInspector.TimelinePresentationModel._hiddenRecords = { }
227WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1;
228WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1;
229WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1;
230WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1;
231
232WebInspector.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 */
610WebInspector.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
792WebInspector.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
799WebInspector.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
809WebInspector.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 */
1361WebInspector.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
1376WebInspector.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 */
1394WebInspector.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 */
1426WebInspector.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 */
1442WebInspector.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 */
1450WebInspector.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 */
1468WebInspector.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 */
1483WebInspector.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 */
1492WebInspector.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 */
1501WebInspector.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 */
1515WebInspector.TimelinePresentationModel.Filter = function()
1516{
1517}
1518
1519WebInspector.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 */
1537WebInspector.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
1548WebInspector.TimelineCategory.Events = {
1549    VisibilityChanged: "VisibilityChanged"
1550};
1551
1552WebInspector.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