Source/WebCore: [Web Inspector]Add WebSocket networking events in Timeline panel.
[WebKit-https.git] / Source / WebCore / inspector / front-end / TimelinePresentationModel.js
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  * Copyright (C) 2012 Intel Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 /**
33  * @constructor
34  * @extends {WebInspector.Object}
35  */
36 WebInspector.TimelinePresentationModel = function()
37 {
38     this._linkifier = new WebInspector.Linkifier();
39     this._glueRecords = false;
40     this._filters = [];
41     this.reset();
42 }
43
44 WebInspector.TimelinePresentationModel.categories = function()
45 {
46     if (WebInspector.TimelinePresentationModel._categories)
47         return WebInspector.TimelinePresentationModel._categories;
48     WebInspector.TimelinePresentationModel._categories = {
49         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  */
61 WebInspector.TimelinePresentationModel._initRecordStyles = function()
62 {
63     if (WebInspector.TimelinePresentationModel._recordStylesMap)
64         return WebInspector.TimelinePresentationModel._recordStylesMap;
65
66     var recordTypes = WebInspector.TimelineModel.RecordType;
67     var categories = WebInspector.TimelinePresentationModel.categories();
68
69     var recordStyles = {};
70     recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] };
71     recordStyles[recordTypes.Program] = { title: WebInspector.UIString("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.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["painting"] };
80     recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] };
81     recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] };
82     recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] };
83     recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] };
84     recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] };
85     recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] };
86     recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] };
87     recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] };
88     recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] };
89     recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] };
90     recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] };
91     recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] };
92     recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] };
93     recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] };
94     recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] };
95     recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] };
96     recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] };
97     recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] };
98     recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] };
99     recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] };
100     recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] };
101     recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] };
102     recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] };
103     recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] };
104     recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] };
105     recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] };
106     recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] };
107     recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] };
108     recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] };
109
110     WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles;
111     return recordStyles;
112 }
113
114 /**
115  * @param {Object} record
116  */
117 WebInspector.TimelinePresentationModel.recordStyle = function(record)
118 {
119     var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles();
120     var result = recordStyles[record.type];
121     if (!result) {
122         result = {
123             title: WebInspector.UIString("Unknown: %s", record.type),
124             category: WebInspector.TimelinePresentationModel.categories()["program"]
125         };
126         recordStyles[record.type] = result;
127     }
128     return result;
129 }
130
131 WebInspector.TimelinePresentationModel.categoryForRecord = function(record)
132 {
133     return WebInspector.TimelinePresentationModel.recordStyle(record).category;
134 }
135
136 WebInspector.TimelinePresentationModel.isEventDivider = function(record)
137 {
138     var recordTypes = WebInspector.TimelineModel.RecordType;
139     if (record.type === recordTypes.TimeStamp)
140         return true;
141     if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
142         var mainFrame = WebInspector.resourceTreeModel.mainFrame;
143         if (mainFrame && mainFrame.id === record.frameId)
144             return true;
145     }
146     return false;
147 }
148
149 /**
150  * @param {Array} recordsArray
151  * @param {?function(*)} preOrderCallback
152  * @param {function(*)=} postOrderCallback
153  */
154 WebInspector.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  */
183 WebInspector.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  */
204 WebInspector.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
225 WebInspector.TimelinePresentationModel._hiddenRecords = { }
226 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1;
227 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1;
228 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1;
229 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1;
230
231 WebInspector.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._timeRecords = {};
269         this._timeRecordStack = [];
270         this._frames = [];
271         this._minimumRecordTime = -1;
272         this._layoutInvalidateStack = {};
273         this._lastScheduleStyleRecalculation = {};
274         this._webSocketCreateRecords = {};
275     },
276
277     addFrame: function(frame)
278     {
279         this._frames.push(frame);
280     },
281
282     addRecord: function(record)
283     {
284         if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime)
285             this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record);
286
287         var records;
288         if (record.type === WebInspector.TimelineModel.RecordType.Program)
289             records = record.children;
290         else
291             records = [record];
292
293         var formattedRecords = [];
294         var recordsCount = records.length;
295         for (var i = 0; i < recordsCount; ++i)
296             formattedRecords.push(this._innerAddRecord(records[i], this._rootRecord));
297         return formattedRecords;
298     },
299
300     _innerAddRecord: function(record, parentRecord)
301     {
302         const recordTypes = WebInspector.TimelineModel.RecordType;
303         var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords;
304         var origin;
305         if (!isHiddenRecord) {
306             var newParentRecord = this._findParentRecord(record);
307             if (newParentRecord) {
308                 origin = parentRecord;
309                 parentRecord = newParentRecord;
310             }
311         }
312
313         var children = record.children;
314         var scriptDetails;
315         if (record.data && record.data["scriptName"]) {
316             scriptDetails = {
317                 scriptName: record.data["scriptName"],
318                 scriptLine: record.data["scriptLine"]
319             }
320         };
321
322         if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) {
323             var childRecord = children[0];
324             if (childRecord.type === recordTypes.FunctionCall) {
325                 scriptDetails = {
326                     scriptName: childRecord.data["scriptName"],
327                     scriptLine: childRecord.data["scriptLine"]
328                 };
329                 children = childRecord.children.concat(children.slice(1));
330             }
331         }
332
333         var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord);
334
335         if (isHiddenRecord)
336             return formattedRecord;
337
338         formattedRecord.collapsed = (parentRecord === this._rootRecord);
339
340         var childrenCount = children ? children.length : 0;
341         for (var i = 0; i < childrenCount; ++i)
342             this._innerAddRecord(children[i], formattedRecord);
343
344         formattedRecord.calculateAggregatedStats(WebInspector.TimelinePresentationModel.categories());
345
346         if (origin) {
347             var lastChildEndTime = formattedRecord.lastChildEndTime;
348             var aggregatedStats = formattedRecord.aggregatedStats;
349             for (var currentRecord = formattedRecord.parent; !currentRecord.isRoot(); currentRecord = currentRecord.parent) {
350                 currentRecord._cpuTime += formattedRecord._cpuTime;
351                 if (currentRecord.lastChildEndTime < lastChildEndTime)
352                     currentRecord.lastChildEndTime = lastChildEndTime;
353                 for (var category in aggregatedStats)
354                     currentRecord.aggregatedStats[category] += aggregatedStats[category];
355             }
356         }
357         origin = formattedRecord.origin();
358         if (!origin.isRoot()) {
359             origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime;
360         }
361         return formattedRecord;
362     },
363
364     _findParentRecord: function(record)
365     {
366         if (!this._glueRecords)
367             return null;
368         var recordTypes = WebInspector.TimelineModel.RecordType;
369
370         switch (record.type) {
371         case recordTypes.ResourceReceiveResponse:
372         case recordTypes.ResourceFinish:
373         case recordTypes.ResourceReceivedData:
374             return this._sendRequestRecords[record.data["requestId"]];
375
376         case recordTypes.ResourceSendRequest:
377             return this._rootRecord;
378
379         case recordTypes.TimerFire:
380             return this._timerRecords[record.data["timerId"]];
381
382         case recordTypes.ResourceSendRequest:
383             return this._scheduledResourceRequests[record.data["url"]];
384
385         case recordTypes.FireAnimationFrame:
386             return this._requestAnimationFrameRecords[record.data["id"]];
387
388         case recordTypes.Time:
389             return this._rootRecord;
390
391         case recordTypes.TimeEnd:
392             return this._timeRecords[record.data["message"]];
393         }
394     },
395
396     setGlueRecords: function(glue)
397     {
398         this._glueRecords = glue;
399     },
400
401     invalidateFilteredRecords: function()
402     {
403         delete this._filteredRecords;
404     },
405
406     filteredRecords: function()
407     {
408         if (this._filteredRecords)
409             return this._filteredRecords;
410
411         var recordsInWindow = [];
412
413         var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false}];
414         while (stack.length) {
415             var entry = stack[stack.length - 1];
416             var records = entry.children;
417             if (records && entry.index < records.length) {
418                  var record = records[entry.index];
419                  ++entry.index;
420
421                  if (this.isVisible(record)) {
422                      ++record.parent._invisibleChildrenCount;
423                      if (!entry.parentIsCollapsed)
424                          recordsInWindow.push(record);
425                  }
426
427                  record._invisibleChildrenCount = 0;
428
429                  stack.push({children: record.children,
430                              index: 0,
431                              parentIsCollapsed: (entry.parentIsCollapsed || record.collapsed),
432                              parentRecord: record,
433                              windowLengthBeforeChildrenTraversal: recordsInWindow.length});
434             } else {
435                 stack.pop();
436                 if (entry.parentRecord)
437                     entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
438             }
439         }
440
441         this._filteredRecords = recordsInWindow;
442         return recordsInWindow;
443     },
444
445     filteredFrames: function(startTime, endTime)
446     {
447         function compareStartTime(value, object)
448         {
449             return value - object.startTime;
450         }
451         function compareEndTime(value, object)
452         {
453             return value - object.endTime;
454         }
455         var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime);
456         var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime);
457         while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime)
458             ++lastFrame;
459         return this._frames.slice(firstFrame, lastFrame);
460     },
461
462     isVisible: function(record)
463     {
464         for (var i = 0; i < this._filters.length; ++i) {
465             if (!this._filters[i].accept(record))
466                 return false;
467         }
468         return true;
469     },
470
471     /**
472      * @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info
473      * @return {!Element}
474      */
475     generateMainThreadBarPopupContent: function(info)
476     {
477         var firstTaskIndex = info.firstTaskIndex;
478         var lastTaskIndex = info.lastTaskIndex;
479         var tasks = info.tasks;
480         var messageCount = lastTaskIndex - firstTaskIndex + 1;
481         var cpuTime = 0;
482
483         for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
484             var task = tasks[i];
485             cpuTime += task.endTime - task.startTime;
486         }
487         var startTime = tasks[firstTaskIndex].startTime;
488         var endTime = tasks[lastTaskIndex].endTime;
489         var duration = endTime - startTime;
490         var offset = this._minimumRecordTime;
491
492         var contentHelper = new WebInspector.TimelinePresentationModel.PopupContentHelper(WebInspector.UIString("CPU"));
493         var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true),
494             Number.secondsToString(startTime - offset, true));
495         contentHelper._appendTextRow(WebInspector.UIString("Duration"), durationText);
496         contentHelper._appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true));
497         contentHelper._appendTextRow(WebInspector.UIString("Message Count"), messageCount);
498         return contentHelper._contentTable;
499     },
500
501     __proto__: WebInspector.Object.prototype
502 }
503
504 /**
505  * @constructor
506  * @param {WebInspector.TimelinePresentationModel} presentationModel
507  * @param {Object} record
508  * @param {WebInspector.TimelinePresentationModel.Record} parentRecord
509  * @param {WebInspector.TimelinePresentationModel.Record} origin
510  * @param {Object|undefined} scriptDetails
511  * @param {boolean} hidden
512  */
513 WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden)
514 {
515     this._linkifier = presentationModel._linkifier;
516     this._aggregatedStats = [];
517     this._record = record;
518     this._children = [];
519     if (!hidden && parentRecord) {
520         this.parent = parentRecord;
521         parentRecord.children.push(this);
522     }
523     if (origin)
524         this._origin = origin;
525
526     this._selfTime = this.endTime - this.startTime;
527     this._lastChildEndTime = this.endTime;
528     this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime;
529
530     if (record.data && record.data["url"])
531         this.url = record.data["url"];
532     if (scriptDetails) {
533         this.scriptName = scriptDetails.scriptName;
534         this.scriptLine = scriptDetails.scriptLine;
535     }
536     if (parentRecord && parentRecord.callSiteStackTrace)
537         this.callSiteStackTrace = parentRecord.callSiteStackTrace;
538
539     var recordTypes = WebInspector.TimelineModel.RecordType;
540     switch (record.type) {
541     case recordTypes.ResourceSendRequest:
542         // Make resource receive record last since request was sent; make finish record last since response received.
543         presentationModel._sendRequestRecords[record.data["requestId"]] = this;
544         break;
545
546     case recordTypes.ScheduleResourceRequest:
547         presentationModel._scheduledResourceRequests[record.data["url"]] = this;
548         break;
549
550     case recordTypes.ResourceReceiveResponse:
551         var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
552         if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
553             this.url = sendRequestRecord.url;
554             // Now that we have resource in the collection, recalculate details in order to display short url.
555             sendRequestRecord._refreshDetails();
556             if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
557                 sendRequestRecord.parent._refreshDetails();
558         }
559         break;
560
561     case recordTypes.ResourceReceivedData:
562     case recordTypes.ResourceFinish:
563         var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]];
564         if (sendRequestRecord) // False for main resource.
565             this.url = sendRequestRecord.url;
566         break;
567
568     case recordTypes.TimerInstall:
569         this.timeout = record.data["timeout"];
570         this.singleShot = record.data["singleShot"];
571         presentationModel._timerRecords[record.data["timerId"]] = this;
572         break;
573
574     case recordTypes.TimerFire:
575         var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]];
576         if (timerInstalledRecord) {
577             this.callSiteStackTrace = timerInstalledRecord.stackTrace;
578             this.timeout = timerInstalledRecord.timeout;
579             this.singleShot = timerInstalledRecord.singleShot;
580         }
581         break;
582
583     case recordTypes.RequestAnimationFrame:
584         presentationModel._requestAnimationFrameRecords[record.data["id"]] = this;
585         break;
586
587     case recordTypes.FireAnimationFrame:
588         var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]];
589         if (requestAnimationRecord)
590             this.callSiteStackTrace = requestAnimationRecord.stackTrace;
591         break;
592
593     case recordTypes.Time:
594         var message = record.data["message"];
595         var oldReference = presentationModel._timeRecords[message];
596         if (oldReference)
597             break;
598         presentationModel._timeRecords[message] = this;
599         if (origin)
600             presentationModel._timeRecordStack.push(this);
601         break;
602
603     case recordTypes.TimeEnd:
604         var message = record.data["message"];
605         var timeRecord = presentationModel._timeRecords[message];
606         delete presentationModel._timeRecords[message];
607         if (timeRecord) {
608             this.timeRecord = timeRecord;
609             timeRecord.timeEndRecord = this;
610             var intervalDuration = this.startTime - timeRecord.startTime;
611             this.intervalDuration = intervalDuration;
612             timeRecord.intervalDuration = intervalDuration;
613             if (!origin)
614                 break;
615             var recordStack = presentationModel._timeRecordStack;
616             recordStack.splice(recordStack.indexOf(timeRecord), 1);
617             for (var index = recordStack.length; index; --index) {
618                 var openRecord = recordStack[index - 1];
619                 if (openRecord.startTime > timeRecord.startTime)
620                     continue;
621                 function compareStartTime(value, record)
622                 {
623                     return value < record.startTime ? -1 : 1;
624                 }
625                 timeRecord.parent.children.splice(timeRecord.parent.children.indexOf(timeRecord));
626                 openRecord.children.splice(insertionIndexForObjectInListSortedByFunction(timeRecord.startTime, openRecord.children, compareStartTime), 0, timeRecord);
627                 break;
628             }
629         }
630         break;
631
632     case recordTypes.ScheduleStyleRecalculation:
633         presentationModel._lastScheduleStyleRecalculation[this.frameId] = this;
634         break;
635
636     case recordTypes.RecalculateStyles:
637         var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId];
638         if (!scheduleStyleRecalculationRecord)
639             break;
640         this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace;
641         break;
642
643     case recordTypes.InvalidateLayout:
644         // Consider style recalculation as a reason for layout invalidation,
645         // but only if we had no earlier layout invalidation records.
646         var styleRecalcStack;
647         if (!presentationModel._layoutInvalidateStack[this.frameId]) {
648             for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) {
649                 if (outerRecord.type === recordTypes.RecalculateStyles) {
650                     styleRecalcStack = outerRecord.callSiteStackTrace;
651                     break;
652                 }
653             }
654         }
655         presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace;
656         break;
657
658     case recordTypes.Layout:
659         var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId];
660         if (layoutInvalidateStack)
661             this.callSiteStackTrace = layoutInvalidateStack;
662         if (this.stackTrace)
663             this.setHasWarning();
664         presentationModel._layoutInvalidateStack[this.frameId] = null;
665         break;
666
667     case recordTypes.WebSocketCreate:
668         this.webSocketURL = record.data["url"];
669         if (typeof record.data["webSocketProtocol"] !== "undefined")
670             this.webSocketProtocol = record.data["webSocketProtocol"];
671         presentationModel._webSocketCreateRecords[record.data["identifier"]] = this;
672         break;
673    
674     case recordTypes.WebSocketSendHandshakeRequest:
675     case recordTypes.WebSocketReceiveHandshakeResponse:
676     case recordTypes.WebSocketDestroy:
677         var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]];
678         if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request.
679             this.webSocketURL = webSocketCreateRecord.webSocketURL;
680             if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined")
681                 this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol;
682         }
683         break;
684     }
685 }
686
687 WebInspector.TimelinePresentationModel.Record.prototype = {
688     get lastChildEndTime()
689     {
690         return this._lastChildEndTime;
691     },
692
693     set lastChildEndTime(time)
694     {
695         this._lastChildEndTime = time;
696     },
697
698     get selfTime()
699     {
700         return this._selfTime;
701     },
702
703     set selfTime(time)
704     {
705         this._selfTime = time;
706     },
707
708     get cpuTime()
709     {
710         return this._cpuTime;
711     },
712
713     /**
714      * @return {boolean}
715      */
716     isRoot: function()
717     {
718         return this.type === WebInspector.TimelineModel.RecordType.Root;
719     },
720
721     /**
722      * @return {WebInspector.TimelinePresentationModel.Record}
723      */
724     origin: function()
725     {
726         return this._origin || this.parent;
727     },
728
729     /**
730      * @return {Array.<WebInspector.TimelinePresentationModel.Record>}
731      */
732     get children()
733     {
734         return this._children;
735     },
736
737     /**
738      * @return {number}
739      */
740     get visibleChildrenCount()
741     {
742         return this._visibleChildrenCount || 0;
743     },
744
745     /**
746      * @return {number}
747      */
748     get invisibleChildrenCount()
749     {
750         return this._invisibleChildrenCount || 0;
751     },
752
753     /**
754      * @return {WebInspector.TimelineCategory}
755      */
756     get category()
757     {
758         return WebInspector.TimelinePresentationModel.recordStyle(this._record).category
759     },
760
761     /**
762      * @return {string}
763      */
764     get title()
765     {
766         return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] :
767             WebInspector.TimelinePresentationModel.recordStyle(this._record).title;
768     },
769
770     /**
771      * @return {number}
772      */
773     get startTime()
774     {
775         return WebInspector.TimelineModel.startTimeInSeconds(this._record);
776     },
777
778     /**
779      * @return {number}
780      */
781     get endTime()
782     {
783         return WebInspector.TimelineModel.endTimeInSeconds(this._record);
784     },
785
786     /**
787      * @return {Object}
788      */
789     get data()
790     {
791         return this._record.data;
792     },
793
794     /**
795      * @return {string}
796      */
797     get type()
798     {
799         return this._record.type;
800     },
801
802     /**
803      * @return {string}
804      */
805     get frameId()
806     {
807         return this._record.frameId;
808     },
809
810     /**
811      * @return {number}
812      */
813     get usedHeapSizeDelta()
814     {
815         return this._record.usedHeapSizeDelta || 0;
816     },
817
818     /**
819      * @return {number}
820      */
821     get usedHeapSize()
822     {
823         return this._record.usedHeapSize;
824     },
825
826     /**
827      * @return {Array.<DebuggerAgent.CallFrame>?}
828      */
829     get stackTrace()
830     {
831         if (this._record.stackTrace && this._record.stackTrace.length)
832             return this._record.stackTrace;
833         return null;
834     },
835
836     containsTime: function(time)
837     {
838         return this.startTime <= time && time <= this.endTime;
839     },
840
841     /**
842      * @param {function(Element)} callback
843      */
844     generatePopupContent: function(callback)
845     {
846         if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type))
847             WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, this._generatePopupContentWithImagePreview.bind(this, callback));
848         else
849             this._generatePopupContentWithImagePreview(callback);
850     },
851
852     /**
853      * @param {function(Element)} callback
854      * @param {Element=} previewElement
855      */
856     _generatePopupContentWithImagePreview: function(callback, previewElement)
857     {
858         var contentHelper = new WebInspector.TimelinePresentationModel.PopupContentHelper(this.title);
859         var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, true),
860             Number.secondsToString(this._startTimeOffset));
861         contentHelper._appendTextRow(WebInspector.UIString("Duration"), text);
862
863         if (this._children.length) {
864             contentHelper._appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime, true));
865             contentHelper._appendTextRow(WebInspector.UIString("CPU Time"), Number.secondsToString(this._cpuTime, true));
866             contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"),
867                 WebInspector.TimelinePresentationModel._generateAggregatedInfo(this._aggregatedStats));
868         }
869         const recordTypes = WebInspector.TimelineModel.RecordType;
870
871         // The messages may vary per record type;
872         var callSiteStackTraceLabel;
873         var callStackLabel;
874
875         switch (this.type) {
876             case recordTypes.GCEvent:
877                 contentHelper._appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"]));
878                 break;
879             case recordTypes.TimerInstall:
880             case recordTypes.TimerFire:
881             case recordTypes.TimerRemove:
882                 contentHelper._appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]);
883                 if (typeof this.timeout === "number") {
884                     contentHelper._appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000));
885                     contentHelper._appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
886                 }
887                 break;
888             case recordTypes.FireAnimationFrame:
889                 contentHelper._appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]);
890                 break;
891             case recordTypes.FunctionCall:
892                 contentHelper._appendElementRow(WebInspector.UIString("Location"), this._linkifyScriptLocation());
893                 break;
894             case recordTypes.ScheduleResourceRequest:
895             case recordTypes.ResourceSendRequest:
896             case recordTypes.ResourceReceiveResponse:
897             case recordTypes.ResourceReceivedData:
898             case recordTypes.ResourceFinish:
899                 contentHelper._appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url));
900                 if (previewElement)
901                     contentHelper._appendElementRow(WebInspector.UIString("Preview"), previewElement);
902                 if (this.data["requestMethod"])
903                     contentHelper._appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]);
904                 if (typeof this.data["statusCode"] === "number")
905                     contentHelper._appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]);
906                 if (this.data["mimeType"])
907                     contentHelper._appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]);
908                 if (this.data["encodedDataLength"])
909                     contentHelper._appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"]));
910                 break;
911             case recordTypes.EvaluateScript:
912                 if (this.data && this.url)
913                     contentHelper._appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"]));
914                 break;
915             case recordTypes.Paint:
916                 contentHelper._appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"]));
917                 contentHelper._appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", this.data["width"], this.data["height"]));
918                 break;
919             case recordTypes.RecalculateStyles: // We don't want to see default details.
920                 callSiteStackTraceLabel = WebInspector.UIString("Styles invalidated");
921                 callStackLabel = WebInspector.UIString("Styles recalculation forced");
922                 break;
923             case recordTypes.Layout:
924                 callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
925                 if (this.stackTrace) {
926                     callStackLabel = WebInspector.UIString("Layout forced");
927                     contentHelper._appendTextRow(WebInspector.UIString("Note"), WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck."));
928                 }
929                 break;
930             case recordTypes.Time:
931             case recordTypes.TimeEnd:
932                 contentHelper._appendTextRow(WebInspector.UIString("Message"), this.data["message"]);
933                 if (typeof this.intervalDuration === "number")
934                     contentHelper._appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true));
935                 break;
936             case recordTypes.WebSocketCreate:
937             case recordTypes.WebSocketSendHandshakeRequest:
938             case recordTypes.WebSocketReceiveHandshakeResponse:
939             case recordTypes.WebSocketDestroy:
940                 if (typeof this.webSocketURL !== "undefined")
941                     contentHelper._appendTextRow(WebInspector.UIString("URL"), this.webSocketURL);
942                 if (typeof this.webSocketProtocol !== "undefined")
943                     contentHelper._appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol);
944                 if (typeof this.data["message"] !== "undefined")
945                     contentHelper._appendTextRow(WebInspector.UIString("Message"), this.data["message"])
946                     break;
947             default:
948                 if (this.detailsNode())
949                     contentHelper._appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode());
950                 break;
951         }
952
953         if (this.scriptName && this.type !== recordTypes.FunctionCall)
954             contentHelper._appendElementRow(WebInspector.UIString("Function Call"), this._linkifyScriptLocation());
955
956         if (this.usedHeapSize) {
957             if (this.usedHeapSizeDelta) {
958                 var sign = this.usedHeapSizeDelta > 0 ? "+" : "-";
959                 contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"),
960                     WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(this.usedHeapSizeDelta)));
961             } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting)
962                 contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize));
963         }
964
965         if (this.callSiteStackTrace)
966             contentHelper._appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this));
967
968         if (this.stackTrace)
969             contentHelper._appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this));
970
971         callback(contentHelper._contentTable);
972     },
973
974     _refreshDetails: function()
975     {
976         delete this._detailsNode;
977     },
978
979     /**
980      * @return {?Node}
981      */
982     detailsNode: function()
983     {
984         if (typeof this._detailsNode === "undefined") {
985             this._detailsNode = this._getRecordDetails();
986
987             if (this._detailsNode) {
988                 this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild);
989                 this._detailsNode.appendChild(document.createTextNode(")"));
990             }
991         }
992         return this._detailsNode;
993     },
994
995     _createSpanWithText: function(textContent)
996     {
997         var node = document.createElement("span");
998         node.textContent = textContent;
999         return node;
1000     },
1001
1002     /**
1003      * @return {?Node}
1004      */
1005     _getRecordDetails: function()
1006     {
1007         var details;
1008         switch (this.type) {
1009         case WebInspector.TimelineModel.RecordType.GCEvent:
1010             details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"]));
1011             break;
1012         case WebInspector.TimelineModel.RecordType.TimerFire:
1013             details = this._linkifyScriptLocation(this.data["timerId"]);
1014             break;
1015         case WebInspector.TimelineModel.RecordType.FunctionCall:
1016             details = this._linkifyScriptLocation();
1017             break;
1018         case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
1019             details = this._linkifyScriptLocation(this.data["id"]);
1020             break;
1021         case WebInspector.TimelineModel.RecordType.EventDispatch:
1022             details = this.data ? this.data["type"] : null;
1023             break;
1024         case WebInspector.TimelineModel.RecordType.Paint:
1025             details = this.data["width"] + "\u2009\u00d7\u2009" + this.data["height"];
1026             break;
1027         case WebInspector.TimelineModel.RecordType.DecodeImage:
1028             details = this.data["imageType"];
1029             break;
1030         case WebInspector.TimelineModel.RecordType.ResizeImage:
1031             details = this.data["cached"] ? WebInspector.UIString("cached") : WebInspector.UIString("non-cached");
1032             break;
1033         case WebInspector.TimelineModel.RecordType.TimerInstall:
1034         case WebInspector.TimelineModel.RecordType.TimerRemove:
1035             details = this._linkifyTopCallFrame(this.data["timerId"]);
1036             break;
1037         case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
1038         case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
1039             details = this._linkifyTopCallFrame(this.data["id"]);
1040             break;
1041         case WebInspector.TimelineModel.RecordType.ParseHTML:
1042         case WebInspector.TimelineModel.RecordType.RecalculateStyles:
1043             details = this._linkifyTopCallFrame();
1044             break;
1045         case WebInspector.TimelineModel.RecordType.EvaluateScript:
1046             details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null;
1047             break;
1048         case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
1049         case WebInspector.TimelineModel.RecordType.XHRLoad:
1050         case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest:
1051         case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
1052         case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
1053         case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
1054         case WebInspector.TimelineModel.RecordType.ResourceFinish:
1055             details = WebInspector.displayNameForURL(this.url);
1056             break;
1057         case WebInspector.TimelineModel.RecordType.Time:
1058         case WebInspector.TimelineModel.RecordType.TimeEnd:
1059         case WebInspector.TimelineModel.RecordType.TimeStamp:
1060             details = this.data["message"];
1061             break;
1062         default:
1063             details = this._linkifyScriptLocation() || this._linkifyTopCallFrame() || null;
1064             break;
1065         }
1066
1067         if (typeof details === "string")
1068             return this._createSpanWithText(details);
1069
1070         return details ? details : null;
1071     },
1072
1073     /**
1074      * @param {string} url
1075      * @param {number} lineNumber
1076      * @param {number=} columnNumber
1077      */
1078     _linkifyLocation: function(url, lineNumber, columnNumber)
1079     {
1080         // FIXME(62725): stack trace line/column numbers are one-based.
1081         columnNumber = columnNumber ? columnNumber - 1 : 0;
1082         return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
1083     },
1084
1085     _linkifyCallFrame: function(callFrame)
1086     {
1087         return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
1088     },
1089
1090     /**
1091      * @param {string=} defaultValue
1092      */
1093     _linkifyTopCallFrame: function(defaultValue)
1094     {
1095         if (this.stackTrace)
1096             return this._linkifyCallFrame(this.stackTrace[0]);
1097         if (this.callSiteStackTrace)
1098             return this._linkifyCallFrame(this.callSiteStackTrace[0]);
1099         return defaultValue;
1100     },
1101
1102     /**
1103      * @param {string=} defaultValue
1104      */
1105     _linkifyScriptLocation: function(defaultValue)
1106     {
1107         return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : defaultValue;
1108     },
1109
1110     calculateAggregatedStats: function(categories)
1111     {
1112         this._aggregatedStats = {};
1113         for (var category in categories)
1114             this._aggregatedStats[category] = 0;
1115         this._cpuTime = this._selfTime;
1116
1117         for (var index = this._children.length; index; --index) {
1118             var child = this._children[index - 1];
1119             for (var category in categories)
1120                 this._aggregatedStats[category] += child._aggregatedStats[category];
1121         }
1122         for (var category in this._aggregatedStats)
1123             this._cpuTime += this._aggregatedStats[category];
1124         this._aggregatedStats[this.category.name] += this._selfTime;
1125     },
1126
1127     get aggregatedStats()
1128     {
1129         return this._aggregatedStats;
1130     },
1131
1132     setHasWarning: function()
1133     {
1134         this.hasWarning = true;
1135         for (var parent = this.parent; parent && !parent.childHasWarning; parent = parent.parent)
1136             parent.childHasWarning = true;
1137     }
1138 }
1139
1140 /**
1141  * @param {Object} aggregatedStats
1142  */
1143 WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats)
1144 {
1145     var cell = document.createElement("span");
1146     cell.className = "timeline-aggregated-info";
1147     for (var index in aggregatedStats) {
1148         var label = document.createElement("div");
1149         label.className = "timeline-aggregated-category timeline-" + index;
1150         cell.appendChild(label);
1151         var text = document.createElement("span");
1152         text.textContent = Number.secondsToString(aggregatedStats[index], true);
1153         cell.appendChild(text);
1154     }
1155     return cell;
1156 }
1157
1158 /**
1159  * @constructor
1160  * @param {string} title
1161  */
1162 WebInspector.TimelinePresentationModel.PopupContentHelper = function(title)
1163 {
1164     this._contentTable = document.createElement("table");
1165     var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
1166     titleCell.colSpan = 2;
1167     var titleRow = document.createElement("tr");
1168     titleRow.appendChild(titleCell);
1169     this._contentTable.appendChild(titleRow);
1170 }
1171
1172 WebInspector.TimelinePresentationModel.PopupContentHelper.prototype = {
1173     /**
1174      * @param {string=} styleName
1175      */
1176     _createCell: function(content, styleName)
1177     {
1178         var text = document.createElement("label");
1179         text.appendChild(document.createTextNode(content));
1180         var cell = document.createElement("td");
1181         cell.className = "timeline-details";
1182         if (styleName)
1183             cell.className += " " + styleName;
1184         cell.textContent = content;
1185         return cell;
1186     },
1187
1188     _appendTextRow: function(title, content)
1189     {
1190         var row = document.createElement("tr");
1191         row.appendChild(this._createCell(title, "timeline-details-row-title"));
1192         row.appendChild(this._createCell(content, "timeline-details-row-data"));
1193         this._contentTable.appendChild(row);
1194     },
1195
1196     /**
1197      * @param {string=} titleStyle
1198      */
1199     _appendElementRow: function(title, content, titleStyle)
1200     {
1201         var row = document.createElement("tr");
1202         var titleCell = this._createCell(title, "timeline-details-row-title");
1203         if (titleStyle)
1204             titleCell.addStyleClass(titleStyle);
1205         row.appendChild(titleCell);
1206         var cell = document.createElement("td");
1207         cell.className = "timeline-details";
1208         cell.appendChild(content);
1209         row.appendChild(cell);
1210         this._contentTable.appendChild(row);
1211     },
1212
1213     _appendStackTrace: function(title, stackTrace, callFrameLinkifier)
1214     {
1215         this._appendTextRow("", "");
1216         var framesTable = document.createElement("table");
1217         for (var i = 0; i < stackTrace.length; ++i) {
1218             var stackFrame = stackTrace[i];
1219             var row = document.createElement("tr");
1220             row.className = "timeline-details";
1221             row.appendChild(this._createCell(stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"), "timeline-function-name"));
1222             row.appendChild(this._createCell(" @ "));
1223             var linkCell = document.createElement("td");
1224             var urlElement = callFrameLinkifier(stackFrame);
1225             linkCell.appendChild(urlElement);
1226             row.appendChild(linkCell);
1227             framesTable.appendChild(row);
1228         }
1229         this._appendElementRow(title, framesTable, "timeline-stacktrace-title");
1230     }
1231 }
1232
1233 WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame)
1234 {
1235     var contentHelper = new WebInspector.TimelinePresentationModel.PopupContentHelper(WebInspector.UIString("Frame"));
1236     var durationInSeconds = frame.endTime - frame.startTime;
1237     var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true),
1238         Number.secondsToString(frame.startTimeOffset, true));
1239     contentHelper._appendTextRow(WebInspector.UIString("Duration"), durationText);
1240     contentHelper._appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds));
1241     contentHelper._appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true));
1242     contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"),
1243         WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory));
1244
1245     return contentHelper._contentTable;
1246 }
1247
1248 /**
1249  * @param {WebInspector.FrameStatistics} statistics
1250  */
1251 WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics)
1252 {
1253     /**
1254      * @param {number} time
1255      */
1256     function formatTimeAndFPS(time)
1257     {
1258         return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time);
1259     }
1260
1261     var contentHelper = new WebInspector.TimelinePresentationModel.PopupContentHelper(WebInspector.UIString("Selected Range"));
1262
1263     contentHelper._appendTextRow(WebInspector.UIString("Selected range"), WebInspector.UIString("%s\u2013%s (%d frames)",
1264         Number.secondsToString(statistics.startOffset, true), Number.secondsToString(statistics.endOffset, true), statistics.frameCount));
1265     contentHelper._appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration));
1266     contentHelper._appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average));
1267     contentHelper._appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration));
1268     contentHelper._appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true));
1269     contentHelper._appendElementRow(WebInspector.UIString("Time by category"),
1270         WebInspector.TimelinePresentationModel._generateAggregatedInfo(statistics.timeByCategory));
1271
1272     return contentHelper._contentTable;
1273 }
1274
1275 /**
1276  * @param {CanvasRenderingContext2D} context
1277  * @param {number} width
1278  * @param {number} height
1279  * @param {string} color0
1280  * @param {string} color1
1281  * @param {string} color2
1282  */
1283 WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2)
1284 {
1285     var gradient = context.createLinearGradient(0, 0, width, height);
1286     gradient.addColorStop(0, color0);
1287     gradient.addColorStop(0.25, color1);
1288     gradient.addColorStop(0.75, color1);
1289     gradient.addColorStop(1, color2);
1290     return gradient;
1291 }
1292
1293 /**
1294  * @param {CanvasRenderingContext2D} context
1295  * @param {number} width
1296  * @param {number} height
1297  * @param {WebInspector.TimelineCategory} category
1298  */
1299 WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category)
1300 {
1301     return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
1302 }
1303
1304 /**
1305  * @param {WebInspector.TimelineCategory} category
1306  */
1307 WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category)
1308 {
1309     var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
1310         ".timeline-category-statusbar-item.timeline-category-" + category.name + " .timeline-category-checkbox, " +
1311         ".popover .timeline-" + category.name + ", " +
1312         ".timeline-category-" + category.name + " .timeline-tree-icon"
1313
1314     return selector + " { background-image: -webkit-linear-gradient(" +
1315        category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 75%, " + category.borderColor + ");" +
1316        " border-color: " + category.borderColor +
1317        "}";
1318 }
1319
1320 /**
1321  * @interface
1322  */
1323 WebInspector.TimelinePresentationModel.Filter = function()
1324 {
1325 }
1326
1327 WebInspector.TimelinePresentationModel.Filter.prototype = {
1328     /**
1329      * @param {!WebInspector.TimelinePresentationModel.Record} record
1330      * @return {boolean}
1331      */
1332     accept: function(record) { return false; }
1333 }
1334
1335 /**
1336  * @constructor
1337  * @extends {WebInspector.Object}
1338  * @param {string} name
1339  * @param {string} title
1340  * @param {number} overviewStripGroupIndex
1341  * @param {string} borderColor
1342  * @param {string} fillColorStop0
1343  * @param {string} fillColorStop1
1344  */
1345 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1)
1346 {
1347     this.name = name;
1348     this.title = title;
1349     this.overviewStripGroupIndex = overviewStripGroupIndex;
1350     this.borderColor = borderColor;
1351     this.fillColorStop0 = fillColorStop0;
1352     this.fillColorStop1 = fillColorStop1;
1353     this.hidden = false;
1354 }
1355
1356 WebInspector.TimelineCategory.Events = {
1357     VisibilityChanged: "VisibilityChanged"
1358 };
1359
1360 WebInspector.TimelineCategory.prototype = {
1361     /**
1362      * @return {boolean}
1363      */
1364     get hidden()
1365     {
1366         return this._hidden;
1367     },
1368
1369     set hidden(hidden)
1370     {
1371         this._hidden = hidden;
1372         this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
1373     },
1374
1375     __proto__: WebInspector.Object.prototype
1376 }