Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / TimelineUIUtils.js
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  */
34 WebInspector.TimelineUIUtils = function() { }
35
36 WebInspector.TimelineUIUtils.categories = function()
37 {
38     if (WebInspector.TimelineUIUtils._categories)
39         return WebInspector.TimelineUIUtils._categories;
40     WebInspector.TimelineUIUtils._categories = {
41         loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "hsl(214, 53%, 58%)", "hsl(214, 67%, 90%)", "hsl(214, 67%, 74%)", "hsl(214, 67%, 66%)"),
42         scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "hsl(43, 68%, 53%)", "hsl(43, 83%, 90%)", "hsl(43, 83%, 72%)", "hsl(43, 83%, 64%) "),
43         rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "hsl(256, 50%, 60%)", "hsl(256, 67%, 90%)", "hsl(256, 67%, 76%)", "hsl(256, 67%, 70%)"),
44         painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "hsl(109, 33%, 47%)", "hsl(109, 33%, 90%)", "hsl(109, 33%, 64%)", "hsl(109, 33%, 55%)"),
45         other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "hsl(0, 0%, 73%)", "hsl(0, 0%, 90%)", "hsl(0, 0%, 87%)", "hsl(0, 0%, 79%)"),
46         idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), -1, "hsl(0, 0%, 87%)", "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)", "hsl(0, 100%, 100%)")
47     };
48     return WebInspector.TimelineUIUtils._categories;
49 };
50
51 /**
52  * @return {!Object.<string, !{title: string, category: !WebInspector.TimelineCategory}>}
53  */
54 WebInspector.TimelineUIUtils._initRecordStyles = function()
55 {
56     if (WebInspector.TimelineUIUtils._recordStylesMap)
57         return WebInspector.TimelineUIUtils._recordStylesMap;
58
59     var recordTypes = WebInspector.TimelineModel.RecordType;
60     var categories = WebInspector.TimelineUIUtils.categories();
61
62     var recordStyles = {};
63     recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] };
64     recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] };
65     recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] };
66     recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] };
67     recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] };
68     recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] };
69     recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] };
70     recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] };
71     recordStyles[recordTypes.AutosizeText] = { title: WebInspector.UIString("Autosize Text"), category: categories["rendering"] };
72     recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] };
73     recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
74     recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Paint"), category: categories["painting"] };
75     recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] };
76     recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] };
77     recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] };
78     recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] };
79     recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] };
80     recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] };
81     recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] };
82     recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] };
83     recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] };
84     recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] };
85     recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] };
86     recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] };
87     recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] };
88     recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] };
89     recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] };
90     recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] };
91     recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] };
92     recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] };
93     recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] };
94     recordStyles[recordTypes.MarkFirstPaint] = { title: WebInspector.UIString("First paint"), category: categories["painting"] };
95     recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] };
96     recordStyles[recordTypes.ConsoleTime] = { title: WebInspector.UIString("Console Time"), category: categories["scripting"] };
97     recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] };
98     recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] };
99     recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] };
100     recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] };
101     recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] };
102     recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] };
103     recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] };
104     recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] };
105     recordStyles[recordTypes.EmbedderCallback] = { title: WebInspector.UIString("Embedder Callback"), category: categories["scripting"] };
106
107     WebInspector.TimelineUIUtils._recordStylesMap = recordStyles;
108     return recordStyles;
109 }
110
111 /**
112  * @param {!WebInspector.TimelineModel.Record} record
113  * @return {!{title: string, category: !WebInspector.TimelineCategory}}
114  */
115 WebInspector.TimelineUIUtils.recordStyle = function(record)
116 {
117     var recordStyles = WebInspector.TimelineUIUtils._initRecordStyles();
118     var result = recordStyles[record.type];
119     if (!result) {
120         result = {
121             title: WebInspector.UIString("Unknown: %s", record.type),
122             category: WebInspector.TimelineUIUtils.categories()["other"]
123         };
124         recordStyles[record.type] = result;
125     }
126     return result;
127 }
128
129 /**
130  * @param {!WebInspector.TimelineModel.Record} record
131  * @return {!WebInspector.TimelineCategory}
132  */
133 WebInspector.TimelineUIUtils.categoryForRecord = function(record)
134 {
135     return WebInspector.TimelineUIUtils.recordStyle(record).category;
136 }
137
138 /**
139  * @param {!WebInspector.TimelineModel.Record} record
140  */
141 WebInspector.TimelineUIUtils.isEventDivider = function(record)
142 {
143     var recordTypes = WebInspector.TimelineModel.RecordType;
144     if (record.type === recordTypes.TimeStamp)
145         return true;
146     if (record.type === recordTypes.MarkFirstPaint)
147         return true;
148     if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
149         if (record.data && ((typeof record.data.isMainFrame) === "boolean"))
150             return record.data.isMainFrame;
151     }
152     return false;
153 }
154
155 /**
156  * @param {string=} recordType
157  * @return {boolean}
158  */
159 WebInspector.TimelineUIUtils.needsPreviewElement = function(recordType)
160 {
161     if (!recordType)
162         return false;
163     const recordTypes = WebInspector.TimelineModel.RecordType;
164     switch (recordType) {
165     case recordTypes.ScheduleResourceRequest:
166     case recordTypes.ResourceSendRequest:
167     case recordTypes.ResourceReceiveResponse:
168     case recordTypes.ResourceReceivedData:
169     case recordTypes.ResourceFinish:
170         return true;
171     default:
172         return false;
173     }
174 }
175
176 /**
177  * @param {string} recordType
178  * @param {string=} title
179  */
180 WebInspector.TimelineUIUtils.createEventDivider = function(recordType, title)
181 {
182     var eventDivider = document.createElement("div");
183     eventDivider.className = "resources-event-divider";
184     var recordTypes = WebInspector.TimelineModel.RecordType;
185
186     if (recordType === recordTypes.MarkDOMContent)
187         eventDivider.className += " resources-blue-divider";
188     else if (recordType === recordTypes.MarkLoad)
189         eventDivider.className += " resources-red-divider";
190     else if (recordType === recordTypes.MarkFirstPaint)
191         eventDivider.className += " resources-green-divider";
192     else if (recordType === recordTypes.TimeStamp)
193         eventDivider.className += " resources-orange-divider";
194     else if (recordType === recordTypes.BeginFrame)
195         eventDivider.className += " timeline-frame-divider";
196
197     if (title)
198         eventDivider.title = title;
199
200     return eventDivider;
201 }
202
203
204 /**
205  * @param {!WebInspector.TimelineModel} model
206  * @param {!{name: string, tasks: !Array.<!{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info
207  * @return {!Element}
208  */
209 WebInspector.TimelineUIUtils.generateMainThreadBarPopupContent = function(model, info)
210 {
211     var firstTaskIndex = info.firstTaskIndex;
212     var lastTaskIndex = info.lastTaskIndex;
213     var tasks = info.tasks;
214     var messageCount = lastTaskIndex - firstTaskIndex + 1;
215     var cpuTime = 0;
216
217     for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) {
218         var task = tasks[i];
219         cpuTime += task.endTime - task.startTime;
220     }
221     var startTime = tasks[firstTaskIndex].startTime;
222     var endTime = tasks[lastTaskIndex].endTime;
223     var duration = endTime - startTime;
224
225     var contentHelper = new WebInspector.TimelinePopupContentHelper(info.name);
226     var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(duration, true),
227         Number.millisToString(startTime - model.minimumRecordTime(), true));
228     contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
229     contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(cpuTime, true));
230     contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount);
231     return contentHelper.contentTable();
232 }
233
234 /**
235  * @param {!WebInspector.TimelineModel.Record} record
236  * @return {string}
237  */
238 WebInspector.TimelineUIUtils.recordTitle = function(record)
239 {
240     if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp)
241         return record.data["message"];
242     if (WebInspector.TimelineUIUtils.isEventDivider(record)) {
243         var startTime = Number.millisToString(record.startTimeOffset);
244         return WebInspector.UIString("%s at %s", WebInspector.TimelineUIUtils.recordStyle(record).title, startTime, true);
245     }
246     return WebInspector.TimelineUIUtils.recordStyle(record).title;
247 }
248
249 /**
250  * @param {!Object} total
251  * @param {!Object} addend
252  */
253 WebInspector.TimelineUIUtils.aggregateTimeByCategory = function(total, addend)
254 {
255     for (var category in addend)
256         total[category] = (total[category] || 0) + addend[category];
257 }
258
259 /**
260  * @param {!Object} total
261  * @param {!WebInspector.TimelineModel.Record} record
262  */
263 WebInspector.TimelineUIUtils.aggregateTimeForRecord = function(total, record)
264 {
265     var childrenTime = 0;
266     var children = record.children;
267     for (var i = 0; i < children.length; ++i) {
268         WebInspector.TimelineUIUtils.aggregateTimeForRecord(total, children[i]);
269         childrenTime += children[i].endTime - children[i].startTime;
270     }
271     var categoryName = WebInspector.TimelineUIUtils.recordStyle(record).category.name;
272     var ownTime = record.endTime - record.startTime - childrenTime;
273     total[categoryName] = (total[categoryName] || 0) + ownTime;
274 }
275
276 /**
277  * @param {!Object} aggregatedStats
278  */
279 WebInspector.TimelineUIUtils._generateAggregatedInfo = function(aggregatedStats)
280 {
281     var cell = document.createElement("span");
282     cell.className = "timeline-aggregated-info";
283     for (var index in aggregatedStats) {
284         var label = document.createElement("div");
285         label.className = "timeline-aggregated-category timeline-" + index;
286         cell.appendChild(label);
287         var text = document.createElement("span");
288         text.textContent = Number.millisToString(aggregatedStats[index], true);
289         cell.appendChild(text);
290     }
291     return cell;
292 }
293
294 /**
295  * @param {!Object} aggregatedStats
296  * @param {!WebInspector.TimelineCategory=} selfCategory
297  * @param {number=} selfTime
298  * @return {!Element}
299  */
300 WebInspector.TimelineUIUtils.generatePieChart = function(aggregatedStats, selfCategory, selfTime)
301 {
302     var element = document.createElement("div");
303     element.className = "timeline-aggregated-info";
304
305     var total = 0;
306     for (var categoryName in aggregatedStats)
307         total += aggregatedStats[categoryName];
308
309     function formatter(value)
310     {
311         return Number.millisToString(value, true);
312     }
313     var pieChart = new WebInspector.PieChart(total, formatter);
314     element.appendChild(pieChart.element);
315     var footerElement = element.createChild("div", "timeline-aggregated-info-legend");
316
317     // In case of self time, first add self, then children of the same category.
318     if (selfCategory && selfTime) {
319         // Self.
320         pieChart.addSlice(selfTime, selfCategory.fillColorStop1);
321         var rowElement = footerElement.createChild("div");
322         rowElement.createChild("div", "timeline-aggregated-category timeline-" + selfCategory.name);
323         rowElement.createTextChild(WebInspector.UIString("%s %s (Self)", formatter(selfTime), selfCategory.title));
324
325         // Children of the same category.
326         var categoryTime = aggregatedStats[selfCategory.name];
327         var value = categoryTime - selfTime;
328         if (value > 0) {
329             pieChart.addSlice(value, selfCategory.fillColorStop0);
330             rowElement = footerElement.createChild("div");
331             rowElement.createChild("div", "timeline-aggregated-category timeline-" + selfCategory.name);
332             rowElement.createTextChild(WebInspector.UIString("%s %s (Children)", formatter(value), selfCategory.title));
333         }
334     }
335
336     // Add other categories.
337     for (var categoryName in WebInspector.TimelineUIUtils.categories()) {
338         var category = WebInspector.TimelineUIUtils.categories()[categoryName];
339          if (category === selfCategory)
340              continue;
341          var value = aggregatedStats[category.name];
342          if (!value)
343              continue;
344          pieChart.addSlice(value, category.fillColorStop0);
345          var rowElement = footerElement.createChild("div");
346          rowElement.createChild("div", "timeline-aggregated-category timeline-" + category.name);
347          rowElement.createTextChild(WebInspector.UIString("%s %s", formatter(value), category.title));
348     }
349     return element;
350 }
351
352 WebInspector.TimelineUIUtils.generatePopupContentForFrame = function(frame)
353 {
354     var contentHelper = new WebInspector.TimelinePopupContentHelper(WebInspector.UIString("Frame"));
355     var durationInMillis = frame.endTime - frame.startTime;
356     var durationText = WebInspector.UIString("%s (at %s)", Number.millisToString(frame.endTime - frame.startTime, true),
357         Number.millisToString(frame.startTimeOffset, true));
358     contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText);
359     contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1000 / durationInMillis));
360     contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.millisToString(frame.cpuTime, true));
361     contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"),
362         WebInspector.TimelineUIUtils._generateAggregatedInfo(frame.timeByCategory));
363     if (WebInspector.experimentsSettings.layersPanel.isEnabled() && frame.layerTree) {
364         var layerTreeSnapshot = new WebInspector.LayerTreeSnapshot(frame.layerTree);
365         contentHelper.appendElementRow(WebInspector.UIString("Layer tree"),
366                                        WebInspector.Linkifier.linkifyUsingRevealer(layerTreeSnapshot, WebInspector.UIString("show")));
367     }
368     return contentHelper.contentTable();
369 }
370
371 /**
372  * @param {!WebInspector.FrameStatistics} statistics
373  */
374 WebInspector.TimelineUIUtils.generatePopupContentForFrameStatistics = function(statistics)
375 {
376     /**
377      * @param {number} time
378      */
379     function formatTimeAndFPS(time)
380     {
381         return WebInspector.UIString("%s (%.0f FPS)", Number.millisToString(time, true), 1 / time);
382     }
383
384     var contentHelper = new WebInspector.TimelineDetailsContentHelper(new WebInspector.Linkifier(), false);
385     contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration));
386     contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average));
387     contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration));
388     contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.millisToString(statistics.stddev, true));
389
390     return contentHelper.element;
391 }
392
393 /**
394  * @param {!CanvasRenderingContext2D} context
395  * @param {number} width
396  * @param {number} height
397  * @param {string} color0
398  * @param {string} color1
399  * @param {string} color2
400  */
401 WebInspector.TimelineUIUtils.createFillStyle = function(context, width, height, color0, color1, color2)
402 {
403     var gradient = context.createLinearGradient(0, 0, width, height);
404     gradient.addColorStop(0, color0);
405     gradient.addColorStop(0.25, color1);
406     gradient.addColorStop(0.75, color1);
407     gradient.addColorStop(1, color2);
408     return gradient;
409 }
410
411 /**
412  * @param {!CanvasRenderingContext2D} context
413  * @param {number} width
414  * @param {number} height
415  * @param {!WebInspector.TimelineCategory} category
416  */
417 WebInspector.TimelineUIUtils.createFillStyleForCategory = function(context, width, height, category)
418 {
419     return WebInspector.TimelineUIUtils.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor);
420 }
421
422 /**
423  * @param {!WebInspector.TimelineCategory} category
424  */
425 WebInspector.TimelineUIUtils.createStyleRuleForCategory = function(category)
426 {
427     var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " +
428         ".panel.timeline .timeline-filters-header .filter-checkbox-filter.filter-checkbox-filter-" + category.name + " .checkbox-filter-checkbox, " +
429         ".popover .timeline-" + category.name + ", " +
430         ".timeline-details-view .timeline-" + category.name + ", " +
431         ".timeline-category-" + category.name + " .timeline-tree-icon"
432
433     return selector + " { background-image: linear-gradient(" +
434        category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" +
435        " border-color: " + category.borderColor +
436        "}";
437 }
438
439 /**
440  * @param {!WebInspector.TimelineModel.Record} record
441  * @param {!WebInspector.Linkifier} linkifier
442  * @param {function(!DocumentFragment)} callback
443  */
444 WebInspector.TimelineUIUtils.generatePopupContent = function(record, linkifier, callback)
445 {
446     var imageElement = /** @type {?Element} */ (record.getUserObject("TimelineUIUtils::preview-element") || null);
447     var relatedNode = null;
448
449     var barrier = new CallbackBarrier();
450     if (!imageElement && WebInspector.TimelineUIUtils.needsPreviewElement(record.type))
451         WebInspector.DOMPresentationUtils.buildImagePreviewContents(record.url, false, barrier.createCallback(saveImage));
452     if (record.relatedBackendNodeId())
453         WebInspector.domModel.pushNodesByBackendIdsToFrontend([record.relatedBackendNodeId()], barrier.createCallback(setRelatedNode));
454     barrier.callWhenDone(callbackWrapper);
455
456     /**
457      * @param {!Element=} element
458      */
459     function saveImage(element)
460     {
461         imageElement = element || null;
462         record.setUserObject("TimelineUIUtils::preview-element", element);
463     }
464
465     /**
466      * @param {?Array.<!DOMAgent.NodeId>} nodeIds
467      */
468     function setRelatedNode(nodeIds)
469     {
470         if (nodeIds)
471             relatedNode = WebInspector.domModel.nodeForId(nodeIds[0]);
472     }
473
474     function callbackWrapper()
475     {
476         callback(WebInspector.TimelineUIUtils._generatePopupContentSynchronously(record, linkifier, imageElement, relatedNode));
477     }
478 }
479
480 /**
481  * @param {!WebInspector.TimelineModel.Record} record
482  * @param {!WebInspector.Linkifier} linkifier
483  * @param {?Element} imagePreviewElement
484  * @param {?WebInspector.DOMNode} relatedNode
485  * @return {!DocumentFragment}
486  */
487 WebInspector.TimelineUIUtils._generatePopupContentSynchronously = function(record, linkifier, imagePreviewElement, relatedNode)
488 {
489     var fragment = document.createDocumentFragment();
490     if (record.children.length)
491         fragment.appendChild(WebInspector.TimelineUIUtils.generatePieChart(record.aggregatedStats, record.category, record.selfTime));
492     else
493         fragment.appendChild(WebInspector.TimelineUIUtils.generatePieChart(record.aggregatedStats));
494
495     const recordTypes = WebInspector.TimelineModel.RecordType;
496
497     // The messages may vary per record type;
498     var callSiteStackTraceLabel;
499     var callStackLabel;
500     var relatedNodeLabel;
501
502     var contentHelper = new WebInspector.TimelineDetailsContentHelper(linkifier, true);
503     contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.millisToString(record.selfTime, true));
504     contentHelper.appendTextRow(WebInspector.UIString("Start Time"), Number.millisToString(record.startTimeOffset));
505
506     switch (record.type) {
507         case recordTypes.GCEvent:
508             contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(record.data["usedHeapSizeDelta"]));
509             break;
510         case recordTypes.TimerFire:
511             callSiteStackTraceLabel = WebInspector.UIString("Timer installed");
512             // Fall-through intended.
513
514         case recordTypes.TimerInstall:
515         case recordTypes.TimerRemove:
516             contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), record.data["timerId"]);
517             if (typeof record.timeout === "number") {
518                 contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.millisToString(record.timeout));
519                 contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !record.singleShot);
520             }
521             break;
522         case recordTypes.FireAnimationFrame:
523             callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested");
524             contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), record.data["id"]);
525             break;
526         case recordTypes.FunctionCall:
527             if (record.scriptName)
528                 contentHelper.appendLocationRow(WebInspector.UIString("Location"), record.scriptName, record.scriptLine);
529             break;
530         case recordTypes.ScheduleResourceRequest:
531         case recordTypes.ResourceSendRequest:
532         case recordTypes.ResourceReceiveResponse:
533         case recordTypes.ResourceReceivedData:
534         case recordTypes.ResourceFinish:
535             contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(record.url));
536             if (imagePreviewElement)
537                 contentHelper.appendElementRow(WebInspector.UIString("Preview"), imagePreviewElement);
538             if (record.data["requestMethod"])
539                 contentHelper.appendTextRow(WebInspector.UIString("Request Method"), record.data["requestMethod"]);
540             if (typeof record.data["statusCode"] === "number")
541                 contentHelper.appendTextRow(WebInspector.UIString("Status Code"), record.data["statusCode"]);
542             if (record.data["mimeType"])
543                 contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), record.data["mimeType"]);
544             if (record.data["encodedDataLength"])
545                 contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", record.data["encodedDataLength"]));
546             break;
547         case recordTypes.EvaluateScript:
548             if (record.data && record.url)
549                 contentHelper.appendLocationRow(WebInspector.UIString("Script"), record.url, record.data["lineNumber"]);
550             break;
551         case recordTypes.Paint:
552             var clip = record.data["clip"];
553             if (clip) {
554                 contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1]));
555                 var clipWidth = WebInspector.TimelineUIUtils._quadWidth(clip);
556                 var clipHeight = WebInspector.TimelineUIUtils._quadHeight(clip);
557                 contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d Ã— %d", clipWidth, clipHeight));
558             } else {
559                 // Backward compatibility: older version used x, y, width, height fields directly in data.
560                 if (typeof record.data["x"] !== "undefined" && typeof record.data["y"] !== "undefined")
561                     contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", record.data["x"], record.data["y"]));
562                 if (typeof record.data["width"] !== "undefined" && typeof record.data["height"] !== "undefined")
563                     contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", record.data["width"], record.data["height"]));
564             }
565             // Fall-through intended.
566
567         case recordTypes.PaintSetup:
568         case recordTypes.Rasterize:
569         case recordTypes.ScrollLayer:
570             relatedNodeLabel = WebInspector.UIString("Layer root");
571             break;
572         case recordTypes.AutosizeText:
573             relatedNodeLabel = WebInspector.UIString("Root node");
574             break;
575         case recordTypes.DecodeImage:
576         case recordTypes.ResizeImage:
577             relatedNodeLabel = WebInspector.UIString("Image element");
578             if (record.url)
579                 contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(record.url));
580             break;
581         case recordTypes.RecalculateStyles: // We don't want to see default details.
582             if (record.data["elementCount"])
583                 contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), record.data["elementCount"]);
584             callStackLabel = WebInspector.UIString("Styles recalculation forced");
585             break;
586         case recordTypes.Layout:
587             if (record.data["dirtyObjects"])
588                 contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), record.data["dirtyObjects"]);
589             if (record.data["totalObjects"])
590                 contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), record.data["totalObjects"]);
591             if (typeof record.data["partialLayout"] === "boolean") {
592                 contentHelper.appendTextRow(WebInspector.UIString("Layout scope"),
593                    record.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document"));
594             }
595             callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated");
596             callStackLabel = WebInspector.UIString("Layout forced");
597             relatedNodeLabel = WebInspector.UIString("Layout root");
598             break;
599         case recordTypes.ConsoleTime:
600             contentHelper.appendTextRow(WebInspector.UIString("Message"), record.data["message"]);
601             break;
602         case recordTypes.WebSocketCreate:
603         case recordTypes.WebSocketSendHandshakeRequest:
604         case recordTypes.WebSocketReceiveHandshakeResponse:
605         case recordTypes.WebSocketDestroy:
606             if (typeof record.webSocketURL !== "undefined")
607                 contentHelper.appendTextRow(WebInspector.UIString("URL"), record.webSocketURL);
608             if (typeof record.webSocketProtocol !== "undefined")
609                 contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), record.webSocketProtocol);
610             if (typeof record.data["message"] !== "undefined")
611                 contentHelper.appendTextRow(WebInspector.UIString("Message"), record.data["message"]);
612             break;
613         case recordTypes.EmbedderCallback:
614             contentHelper.appendTextRow(WebInspector.UIString("Callback Function"), record.embedderCallbackName);
615             break;
616         default:
617             var detailsNode = WebInspector.TimelineUIUtils.buildDetailsNode(record, linkifier);
618             if (detailsNode)
619                 contentHelper.appendElementRow(WebInspector.UIString("Details"), detailsNode);
620             break;
621     }
622
623     if (relatedNode)
624         contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related node"), WebInspector.DOMPresentationUtils.linkifyNodeReference(relatedNode));
625
626     if (record.scriptName && record.type !== recordTypes.FunctionCall)
627         contentHelper.appendLocationRow(WebInspector.UIString("Function Call"), record.scriptName, record.scriptLine);
628
629     if (record.jsHeapSizeUsed) {
630         if (record.usedHeapSizeDelta) {
631             var sign = record.usedHeapSizeDelta > 0 ? "+" : "-";
632             contentHelper.appendTextRow(WebInspector.UIString("Used JavaScript Heap Size"),
633                 WebInspector.UIString("%s (%s%s)", Number.bytesToString(record.jsHeapSizeUsed), sign, Number.bytesToString(Math.abs(record.usedHeapSizeDelta))));
634         } else if (record.category === WebInspector.TimelineUIUtils.categories().scripting)
635             contentHelper.appendTextRow(WebInspector.UIString("Used JavaScript Heap Size"), Number.bytesToString(record.jsHeapSizeUsed));
636     }
637
638     if (record.callSiteStackTrace)
639         contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), record.callSiteStackTrace);
640
641     if (record.stackTrace)
642         contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), record.stackTrace);
643
644     if (record.warnings()) {
645         var ul = document.createElement("ul");
646         for (var i = 0; i < record.warnings().length; ++i)
647             ul.createChild("li").textContent = record.warnings()[i];
648         contentHelper.appendElementRow(WebInspector.UIString("Warning"), ul);
649     }
650     fragment.appendChild(contentHelper.element);
651     return fragment;
652 }
653
654 /**
655  * @param {!Array.<number>} quad
656  * @return {number}
657  */
658 WebInspector.TimelineUIUtils._quadWidth = function(quad)
659 {
660     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2)));
661 }
662
663 /**
664  * @param {!Array.<number>} quad
665  * @return {number}
666  */
667 WebInspector.TimelineUIUtils._quadHeight = function(quad)
668 {
669     return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2)));
670 }
671
672 /**
673  * @param {!WebInspector.TimelineModel.Record} record
674  * @param {!WebInspector.Linkifier} linkifier
675  * @return {?Node}
676  */
677 WebInspector.TimelineUIUtils.buildDetailsNode = function(record, linkifier)
678 {
679     var details;
680     var detailsText;
681
682     switch (record.type) {
683     case WebInspector.TimelineModel.RecordType.GCEvent:
684         detailsText = WebInspector.UIString("%s collected", Number.bytesToString(record.data["usedHeapSizeDelta"]));
685         break;
686     case WebInspector.TimelineModel.RecordType.TimerFire:
687         detailsText = record.data["timerId"];
688         break;
689     case WebInspector.TimelineModel.RecordType.FunctionCall:
690         if (record.scriptName)
691             details = linkifyLocation(record.scriptName, record.scriptLine, 0);
692         break;
693     case WebInspector.TimelineModel.RecordType.FireAnimationFrame:
694         detailsText = record.data["id"];
695         break;
696     case WebInspector.TimelineModel.RecordType.EventDispatch:
697         detailsText = record.data ? record.data["type"] : null;
698         break;
699     case WebInspector.TimelineModel.RecordType.Paint:
700         var width = record.data.clip ? WebInspector.TimelineUIUtils._quadWidth(record.data.clip) : record.data.width;
701         var height = record.data.clip ? WebInspector.TimelineUIUtils._quadHeight(record.data.clip) : record.data.height;
702         if (width && height)
703             detailsText = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height);
704         break;
705     case WebInspector.TimelineModel.RecordType.TimerInstall:
706     case WebInspector.TimelineModel.RecordType.TimerRemove:
707         details = linkifyTopCallFrame();
708         detailsText = record.data["timerId"];
709         break;
710     case WebInspector.TimelineModel.RecordType.RequestAnimationFrame:
711     case WebInspector.TimelineModel.RecordType.CancelAnimationFrame:
712         details = linkifyTopCallFrame();
713         detailsText = record.data["id"];
714         break;
715     case WebInspector.TimelineModel.RecordType.ParseHTML:
716     case WebInspector.TimelineModel.RecordType.RecalculateStyles:
717         details = linkifyTopCallFrame();
718         break;
719     case WebInspector.TimelineModel.RecordType.EvaluateScript:
720         details = record.url ? linkifyLocation(record.url, record.data["lineNumber"], 0) : null;
721         break;
722     case WebInspector.TimelineModel.RecordType.XHRReadyStateChange:
723     case WebInspector.TimelineModel.RecordType.XHRLoad:
724     case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest:
725     case WebInspector.TimelineModel.RecordType.ResourceSendRequest:
726     case WebInspector.TimelineModel.RecordType.ResourceReceivedData:
727     case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse:
728     case WebInspector.TimelineModel.RecordType.ResourceFinish:
729     case WebInspector.TimelineModel.RecordType.DecodeImage:
730     case WebInspector.TimelineModel.RecordType.ResizeImage:
731         detailsText = WebInspector.displayNameForURL(record.url);
732         break;
733     case WebInspector.TimelineModel.RecordType.ConsoleTime:
734         detailsText = record.data["message"];
735         break;
736     case WebInspector.TimelineModel.RecordType.EmbedderCallback:
737         detailsText = record.data["callbackName"];
738         break;
739     default:
740         details = record.scriptName ? linkifyLocation(record.scriptName, record.scriptLine, 0) : linkifyTopCallFrame();
741         break;
742     }
743
744     if (!details && detailsText)
745         details = document.createTextNode(detailsText);
746     return details;
747
748     /**
749      * @param {string} url
750      * @param {number} lineNumber
751      * @param {number=} columnNumber
752      */
753     function linkifyLocation(url, lineNumber, columnNumber)
754     {
755         // FIXME(62725): stack trace line/column numbers are one-based.
756         columnNumber = columnNumber ? columnNumber - 1 : 0;
757         return linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details");
758     }
759
760     /**
761      * @param {!ConsoleAgent.CallFrame} callFrame
762      */
763     function linkifyCallFrame(callFrame)
764     {
765         return linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
766     }
767
768     /**
769      * @return {?Element}
770      */
771     function linkifyTopCallFrame()
772     {
773         if (record.stackTrace)
774             return linkifyCallFrame(record.stackTrace[0]);
775         if (record.callSiteStackTrace)
776             return linkifyCallFrame(record.callSiteStackTrace[0]);
777         return null;
778     }
779
780     /**
781      * @return {?Element}
782      */
783     function linkifyScriptLocation()
784     {
785         return record.scriptName ? linkifyLocation(record.scriptName, record.scriptLine, 0) : null;
786     }
787 }
788
789 /**
790  * @constructor
791  * @extends {WebInspector.Object}
792  * @param {string} name
793  * @param {string} title
794  * @param {number} overviewStripGroupIndex
795  * @param {string} borderColor
796  * @param {string} backgroundColor
797  * @param {string} fillColorStop0
798  * @param {string} fillColorStop1
799  */
800 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, backgroundColor, fillColorStop0, fillColorStop1)
801 {
802     this.name = name;
803     this.title = title;
804     this.overviewStripGroupIndex = overviewStripGroupIndex;
805     this.borderColor = borderColor;
806     this.backgroundColor = backgroundColor;
807     this.fillColorStop0 = fillColorStop0;
808     this.fillColorStop1 = fillColorStop1;
809     this.hidden = false;
810 }
811
812 WebInspector.TimelineCategory.Events = {
813     VisibilityChanged: "VisibilityChanged"
814 };
815
816 WebInspector.TimelineCategory.prototype = {
817     /**
818      * @return {boolean}
819      */
820     get hidden()
821     {
822         return this._hidden;
823     },
824
825     set hidden(hidden)
826     {
827         this._hidden = hidden;
828         this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this);
829     },
830
831     __proto__: WebInspector.Object.prototype
832 }
833
834 /**
835  * @constructor
836  * @param {string} title
837  */
838 WebInspector.TimelinePopupContentHelper = function(title)
839 {
840     this._contentTable = document.createElement("table");
841     var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
842     titleCell.colSpan = 2;
843     var titleRow = document.createElement("tr");
844     titleRow.appendChild(titleCell);
845     this._contentTable.appendChild(titleRow);
846 }
847
848 WebInspector.TimelinePopupContentHelper.prototype = {
849     /**
850      * @return {!Element}
851      */
852     contentTable: function()
853     {
854         return this._contentTable;
855     },
856
857     /**
858      * @param {string=} styleName
859      */
860     _createCell: function(content, styleName)
861     {
862         var text = document.createElement("label");
863         text.appendChild(document.createTextNode(content));
864         var cell = document.createElement("td");
865         cell.className = "timeline-details";
866         if (styleName)
867             cell.className += " " + styleName;
868         cell.textContent = content;
869         return cell;
870     },
871
872     /**
873      * @param {string} title
874      * @param {string|number|boolean} content
875      */
876     appendTextRow: function(title, content)
877     {
878         var row = document.createElement("tr");
879         row.appendChild(this._createCell(title, "timeline-details-row-title"));
880         row.appendChild(this._createCell(content, "timeline-details-row-data"));
881         this._contentTable.appendChild(row);
882     },
883
884     /**
885      * @param {string} title
886      * @param {!Node|string} content
887      */
888     appendElementRow: function(title, content)
889     {
890         var row = document.createElement("tr");
891         var titleCell = this._createCell(title, "timeline-details-row-title");
892         row.appendChild(titleCell);
893         var cell = document.createElement("td");
894         cell.className = "details";
895         if (content instanceof Node)
896             cell.appendChild(content);
897         else
898             cell.createTextChild(content || "");
899         row.appendChild(cell);
900         this._contentTable.appendChild(row);
901     }
902 }
903
904 /**
905  * @constructor
906  * @param {!WebInspector.Linkifier} linkifier
907  * @param {boolean} monospaceValues
908  */
909 WebInspector.TimelineDetailsContentHelper = function(linkifier, monospaceValues)
910 {
911     this._linkifier = linkifier;
912     this.element = document.createElement("div");
913     this.element.className = "timeline-details-view-block";
914     this._monospaceValues = monospaceValues;
915 }
916
917 WebInspector.TimelineDetailsContentHelper.prototype = {
918     /**
919      * @param {string} title
920      * @param {string|number|boolean} value
921      */
922     appendTextRow: function(title, value)
923     {
924         var rowElement = this.element.createChild("div", "timeline-details-view-row");
925         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
926         rowElement.createChild("span", "timeline-details-view-row-value" + (this._monospaceValues ? " monospace" : "")).textContent = value;
927     },
928
929     /**
930      * @param {string} title
931      * @param {!Node|string} content
932      */
933     appendElementRow: function(title, content)
934     {
935         var rowElement = this.element.createChild("div", "timeline-details-view-row");
936         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
937         var valueElement = rowElement.createChild("span", "timeline-details-view-row-details" + (this._monospaceValues ? " monospace" : ""));
938         if (content instanceof Node)
939             valueElement.appendChild(content);
940         else
941             valueElement.createTextChild(content || "");
942     },
943
944     /**
945      * @param {string} title
946      * @param {string} url
947      * @param {number} line
948      */
949     appendLocationRow: function(title, url, line)
950     {
951         this.appendElementRow(title, this._linkifier.linkifyLocation(url, line - 1) || "");
952     },
953
954     /**
955      * @param {string} title
956      * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace
957      */
958     appendStackTrace: function(title, stackTrace)
959     {
960         var rowElement = this.element.createChild("div", "timeline-details-view-row");
961         rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title);
962         var stackTraceElement = rowElement.createChild("div", "timeline-details-view-row-stack-trace monospace");
963
964         for (var i = 0; i < stackTrace.length; ++i) {
965             var stackFrame = stackTrace[i];
966             var row = stackTraceElement.createChild("div");
967             row.createTextChild(stackFrame.functionName || WebInspector.UIString("(anonymous function)"));
968             row.createTextChild(" @ ");
969             var urlElement = this._linkifier.linkifyLocation(stackFrame.url, stackFrame.lineNumber - 1);
970             row.appendChild(urlElement);
971         }
972     }
973 }