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