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