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