Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TimelineFlameChart.js
1 /*
2  * Copyright (C) 2014 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 /**
32  * @constructor
33  * @implements {WebInspector.FlameChartDataProvider}
34  * @param {!WebInspector.TracingTimelineModel} model
35  * @param {!WebInspector.TimelineFrameModelBase} frameModel
36  */
37 WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
38 {
39     WebInspector.FlameChartDataProvider.call(this);
40     this.reset();
41     this._model = model;
42     this._frameModel = frameModel;
43     this._font = "12px " + WebInspector.fontFamily();
44     this._linkifier = new WebInspector.Linkifier();
45     this._captureStacksSetting = WebInspector.settings.createSetting("timelineCaptureStacks", true);
46     this._filters = [];
47     this.addFilter(WebInspector.TracingTimelineUIUtils.hiddenEventsFilter());
48     this.addFilter(new WebInspector.TracingTimelineModel.ExclusiveEventNameFilter([WebInspector.TracingTimelineModel.RecordType.Program]));
49 }
50
51 WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.01;
52 WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs = 1.1;
53
54 /**
55  * @return {!WebInspector.FlameChart.ColorGenerator}
56  */
57 WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator = function()
58 {
59     if (!WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator) {
60         var hueSpace = { min: 30, max: 55, count: 5 };
61         var satSpace = { min: 70, max: 100, count: 6 };
62         var colorGenerator = new WebInspector.FlameChart.ColorGenerator(hueSpace, satSpace, 50, 0.7);
63         WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator = colorGenerator;
64     }
65     return WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator;
66 }
67
68 WebInspector.TimelineFlameChartDataProvider.prototype = {
69     /**
70      * @return {number}
71      */
72     barHeight: function()
73     {
74         return 20;
75     },
76
77     /**
78      * @return {number}
79      */
80     textBaseline: function()
81     {
82         return 6;
83     },
84
85     /**
86      * @return {number}
87      */
88     textPadding: function()
89     {
90         return 5;
91     },
92
93     /**
94      * @param {number} entryIndex
95      * @return {string}
96      */
97     entryFont: function(entryIndex)
98     {
99         return this._font;
100     },
101
102     /**
103      * @param {number} entryIndex
104      * @return {?string}
105      */
106     entryTitle: function(entryIndex)
107     {
108         var event = this._entryEvents[entryIndex];
109         if (event) {
110             if (event.phase === WebInspector.TracingModel.Phase.AsyncStepInto || event.phase === WebInspector.TracingModel.Phase.AsyncStepPast)
111                 return event.name + ":" + event.args["step"];
112
113             var name = WebInspector.TracingTimelineUIUtils.eventStyle(event).title;
114             // TODO(yurys): support event dividers
115             var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier);
116             if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame && details)
117                 return details.textContent;
118             return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
119         }
120         var title = this._entryIndexToTitle[entryIndex];
121         if (!title) {
122             title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
123             console.error(title);
124         }
125         return title;
126     },
127
128     /**
129      * @param {number} startTime
130      * @param {number} endTime
131      * @return {?Array.<number>}
132      */
133     dividerOffsets: function(startTime, endTime)
134     {
135         return null;
136     },
137
138     /**
139      * @override
140      * @param {number} index
141      * @return {string}
142      */
143     markerColor: function(index)
144     {
145         var event = this._markerEvents[index];
146         return WebInspector.TracingTimelineUIUtils.markerEventColor(event);
147     },
148
149     /**
150      * @override
151      * @param {number} index
152      * @return {string}
153      */
154     markerTitle: function(index)
155     {
156         var event = this._markerEvents[index];
157         return WebInspector.TracingTimelineUIUtils.eventTitle(event, this._model);
158     },
159
160     reset: function()
161     {
162         this._timelineData = null;
163         /** @type {!Array.<!WebInspector.TracingModel.Event>} */
164         this._entryEvents = [];
165         this._entryIndexToTitle = {};
166         this._markerEvents = [];
167         this._entryIndexToFrame = {};
168         this._asyncColorByCategory = {};
169     },
170
171     /**
172      * @return {!WebInspector.FlameChart.TimelineData}
173      */
174     timelineData: function()
175     {
176         if (this._timelineData)
177             return this._timelineData;
178
179         this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []);
180
181         this._minimumBoundary = this._model.minimumRecordTime();
182         this._timeSpan = this._model.isEmpty() ?  1000 : this._model.maximumRecordTime() - this._minimumBoundary;
183         this._currentLevel = 0;
184         this._appendFrameBars(this._frameModel.frames());
185         this._appendThreadTimelineData(WebInspector.UIString("Main Thread"), this._model.mainThreadEvents(), this._model.mainThreadAsyncEvents());
186         var threads = this._model.virtualThreads();
187         for (var i = 0; i < threads.length; i++)
188             this._appendThreadTimelineData(threads[i].name, threads[i].events, threads[i].asyncEvents);
189         return this._timelineData;
190     },
191
192     /**
193      * @param {string} threadTitle
194      * @param {!Array.<!WebInspector.TracingModel.Event>} syncEvents
195      * @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} asyncEvents
196      */
197     _appendThreadTimelineData: function(threadTitle, syncEvents, asyncEvents)
198     {
199         var levelCount = this._appendAsyncEvents(threadTitle, asyncEvents);
200         if (Runtime.experiments.isEnabled("timelineJSCPUProfile")) {
201             if (this._captureStacksSetting.get()) {
202                 var jsFrameEvents = this._generateJSFrameEvents(syncEvents);
203                 syncEvents = jsFrameEvents.mergeOrdered(syncEvents, WebInspector.TracingModel.Event.orderedCompareStartTime);
204             }
205         }
206         levelCount += this._appendSyncEvents(levelCount ? null : threadTitle, syncEvents);
207         if (levelCount)
208             ++this._currentLevel;
209     },
210
211     /**
212      * @param {?string} headerName
213      * @param {!Array.<!WebInspector.TracingModel.Event>} events
214      * @return {boolean}
215      */
216     _appendSyncEvents: function(headerName, events)
217     {
218         var openEvents = [];
219         var headerAppended = false;
220
221         var maxStackDepth = 0;
222         for (var i = 0; i < events.length; ++i) {
223             var e = events[i];
224             if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(e)) {
225                 this._markerEvents.push(e);
226                 this._timelineData.markerTimestamps.push(e.startTime);
227             }
228             if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant)
229                 continue;
230             if (!this._isVisible(e))
231                 continue;
232             while (openEvents.length && openEvents.peekLast().endTime <= e.startTime)
233                 openEvents.pop();
234             if (!headerAppended && headerName) {
235                 this._appendHeaderRecord(headerName, this._currentLevel++);
236                 headerAppended = true;
237             }
238             this._appendEvent(e, this._currentLevel + openEvents.length);
239             maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1);
240             if (e.endTime)
241                 openEvents.push(e);
242         }
243         this._currentLevel += maxStackDepth;
244         return !!maxStackDepth;
245     },
246
247     /**
248      * @param {string} header
249      * @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} eventSteps
250      */
251     _appendAsyncEvents: function(header, eventSteps)
252     {
253         var lastUsedTimeByLevel = [];
254         var headerAppended = false;
255
256         var maxStackDepth = 0;
257         for (var i = 0; i < eventSteps.length; ++i) {
258             var e = eventSteps[i][0];
259             if (!this._isVisible(e))
260                 continue;
261             if (!headerAppended && header) {
262                 this._appendHeaderRecord(header, this._currentLevel++);
263                 headerAppended = true;
264             }
265             var level;
266             for (level = 0; level < lastUsedTimeByLevel.length && lastUsedTimeByLevel[level] > e.startTime; ++level) {}
267             this._appendAsyncEventSteps(eventSteps[i], this._currentLevel + level);
268             var lastStep = eventSteps[i].peekLast();
269             lastUsedTimeByLevel[level] = lastStep.phase === WebInspector.TracingModel.Phase.AsyncEnd ? lastStep.startTime : Infinity;
270         }
271         this._currentLevel += lastUsedTimeByLevel.length;
272         return lastUsedTimeByLevel.length;
273     },
274
275     /**
276      * @param {!Array.<!WebInspector.TimelineFrame>} frames
277      */
278     _appendFrameBars: function(frames)
279     {
280         this._frameBarsLevel = this._currentLevel++;
281         for (var i = 0; i < frames.length; ++i)
282             this._appendFrame(frames[i]);
283     },
284
285     /**
286      * @param {!Array.<!WebInspector.TracingModel.Event>} events
287      * @return {!Array.<!WebInspector.TracingModel.Event>}
288      */
289     _generateJSFrameEvents: function(events)
290     {
291         function equalFrames(frame1, frame2)
292         {
293             return frame1.scriptId === frame2.scriptId && frame1.functionName === frame2.functionName;
294         }
295
296         function eventEndTime(e)
297         {
298             return e.endTime || e.startTime;
299         }
300
301         function isJSInvocationEvent(e)
302         {
303             switch (e.name) {
304             case WebInspector.TracingTimelineModel.RecordType.FunctionCall:
305             case WebInspector.TracingTimelineModel.RecordType.EvaluateScript:
306                 return true;
307             }
308             return false;
309         }
310
311         var jsFrameEvents = [];
312         var jsFramesStack = [];
313         var coalesceThresholdMs = WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs;
314
315         function onStartEvent(e)
316         {
317             extractStackTrace(e);
318         }
319
320         function onInstantEvent(e, top)
321         {
322             if (e.name === WebInspector.TracingTimelineModel.RecordType.JSSample && top && !isJSInvocationEvent(top))
323                 return;
324             extractStackTrace(e);
325         }
326
327         function onEndEvent(e)
328         {
329             if (isJSInvocationEvent(e))
330                 jsFramesStack.length = 0;
331         }
332
333         function extractStackTrace(e)
334         {
335             if (!e.stackTrace)
336                 return;
337             while (jsFramesStack.length && eventEndTime(jsFramesStack.peekLast()) + coalesceThresholdMs <= e.startTime)
338                 jsFramesStack.pop();
339             var endTime = eventEndTime(e);
340             var numFrames = e.stackTrace.length;
341             var minFrames = Math.min(numFrames, jsFramesStack.length);
342             var j;
343             for (j = 0; j < minFrames; ++j) {
344                 var newFrame = e.stackTrace[numFrames - 1 - j];
345                 var oldFrame = jsFramesStack[j].args["data"];
346                 if (!equalFrames(newFrame, oldFrame))
347                     break;
348                 jsFramesStack[j].setEndTime(Math.max(jsFramesStack[j].endTime, endTime));
349             }
350             jsFramesStack.length = j;
351             for (; j < numFrames; ++j) {
352                 var frame = e.stackTrace[numFrames - 1 - j];
353                 var jsFrameEvent = new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsMetadataEventCategory, WebInspector.TracingTimelineModel.RecordType.JSFrame,
354                     WebInspector.TracingModel.Phase.Complete, e.startTime, e.thread);
355                 jsFrameEvent.addArgs({ data: frame });
356                 jsFrameEvent.setEndTime(endTime);
357                 jsFramesStack.push(jsFrameEvent);
358                 jsFrameEvents.push(jsFrameEvent);
359             }
360         }
361
362         var stack = [];
363         for (var i = 0; i < events.length; ++i) {
364             var e = events[i];
365             var top = stack.peekLast();
366             if (top && top.endTime <= e.startTime)
367                 onEndEvent(stack.pop());
368             if (e.duration) {
369                 onStartEvent(e);
370                 stack.push(e);
371             } else {
372                 onInstantEvent(e, stack.peekLast());
373             }
374         }
375         while (stack.length)
376             onEndEvent(stack.pop());
377
378         return jsFrameEvents;
379     },
380
381     /**
382      * @param {!WebInspector.TracingTimelineModel.Filter} filter
383      */
384     addFilter: function(filter)
385     {
386         this._filters.push(filter);
387     },
388
389     /**
390      * @param {!WebInspector.TracingModel.Event} event
391      * @return {boolean}
392      */
393     _isVisible: function(event)
394     {
395         return this._filters.every(function (filter) { return filter.accept(event); });
396     },
397
398     /**
399      * @return {number}
400      */
401     minimumBoundary: function()
402     {
403         return this._minimumBoundary;
404     },
405
406     /**
407      * @return {number}
408      */
409     totalTime: function()
410     {
411         return this._timeSpan;
412     },
413
414     /**
415      * @return {number}
416      */
417     maxStackDepth: function()
418     {
419         return this._currentLevel;
420     },
421
422     /**
423      * @param {number} entryIndex
424      * @return {?Array.<!{title: string, text: string}>}
425      */
426     prepareHighlightedEntryInfo: function(entryIndex)
427     {
428         return null;
429     },
430
431     /**
432      * @param {number} entryIndex
433      * @return {boolean}
434      */
435     canJumpToEntry: function(entryIndex)
436     {
437         return false;
438     },
439
440     /**
441      * @param {number} entryIndex
442      * @return {string}
443      */
444     entryColor: function(entryIndex)
445     {
446         var event = this._entryEvents[entryIndex];
447         if (!event)
448             return this._entryIndexToFrame[entryIndex] ? "white" : "#555";
449         if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame)
450             return this._timelineData.entryLevels[entryIndex] % 2 ? "#efb320" : "#fcc02d";
451         var category = WebInspector.TracingTimelineUIUtils.eventStyle(event).category;
452         if (WebInspector.TracingModel.isAsyncPhase(event.phase)) {
453             if (event.category === WebInspector.TracingModel.ConsoleEventCategory)
454                 return WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator().colorForID(event.name);
455             var color = this._asyncColorByCategory[category.name];
456             if (color)
457                 return color;
458             var parsedColor = WebInspector.Color.parse(category.fillColorStop1);
459             color = parsedColor.setAlpha(0.7).toString(WebInspector.Color.Format.RGBA) || "";
460             this._asyncColorByCategory[category.name] = color;
461             return color;
462         }
463         return category.fillColorStop1;
464     },
465
466     /**
467      * @param {number} entryIndex
468      * @param {!CanvasRenderingContext2D} context
469      * @param {?string} text
470      * @param {number} barX
471      * @param {number} barY
472      * @param {number} barWidth
473      * @param {number} barHeight
474      * @param {function(number):number} offsetToPosition
475      * @return {boolean}
476      */
477     decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
478     {
479         var frame = this._entryIndexToFrame[entryIndex];
480         if (frame) {
481             context.save();
482
483             context.translate(0.5, 0.5);
484
485             context.beginPath();
486             context.moveTo(barX, barY);
487             context.lineTo(barX, context.canvas.height);
488             context.strokeStyle = "rgba(100, 100, 100, 0.4)";
489             context.setLineDash([5]);
490             context.stroke();
491             context.setLineDash([]);
492
493
494             var padding = 4 * window.devicePixelRatio;
495             barX += padding;
496             barWidth -= 2 * padding;
497             barY += padding;
498             barHeight -= 2 * padding;
499
500             var cornerRadis = 3;
501             var radiusY = cornerRadis;
502             var radiusX = Math.min(cornerRadis, barWidth / 2);
503
504             context.beginPath();
505             context.moveTo(barX + radiusX, barY);
506             context.lineTo(barX + barWidth - radiusX, barY);
507             context.quadraticCurveTo(barX + barWidth, barY, barX + barWidth, barY + radiusY);
508             context.lineTo(barX + barWidth, barY + barHeight - radiusY);
509             context.quadraticCurveTo(barX + barWidth, barY + barHeight, barX + barWidth - radiusX, barY + barHeight);
510             context.lineTo(barX + radiusX, barY + barHeight);
511             context.quadraticCurveTo(barX, barY + barHeight, barX, barY + barHeight - radiusY);
512             context.lineTo(barX, barY + radiusY);
513             context.quadraticCurveTo(barX, barY, barX + radiusX, barY);
514             context.closePath();
515
516             context.fillStyle = "rgba(200, 200, 200, 0.8)";
517             context.fill();
518             context.strokeStyle = "rgba(150, 150, 150, 0.8)";
519             context.stroke();
520
521             var frameDurationText = Number.millisToString(frame.duration, true);
522             var textWidth = context.measureText(frameDurationText).width;
523             if (barWidth > textWidth) {
524                 context.fillStyle = "#555";
525                 context.fillText(frameDurationText, barX + ((barWidth - textWidth) >> 1), barY + barHeight - 2);
526             }
527             context.restore();
528             return true;
529         }
530         if (barWidth < 5)
531             return false;
532
533         // Paint text using white color on dark background.
534         if (text) {
535             context.save();
536             context.fillStyle = "white";
537             context.shadowColor = "rgba(0, 0, 0, 0.1)";
538             context.shadowOffsetX = 1;
539             context.shadowOffsetY = 1;
540             context.font = this._font;
541             context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
542             context.restore();
543         }
544
545         var event = this._entryEvents[entryIndex];
546         if (event && event.warning) {
547             context.save();
548
549             context.rect(barX, barY, barWidth, this.barHeight());
550             context.clip();
551
552             context.beginPath();
553             context.fillStyle = "red";
554             context.moveTo(barX + barWidth - 15, barY + 1);
555             context.lineTo(barX + barWidth - 1, barY + 1);
556             context.lineTo(barX + barWidth - 1, barY + 15);
557             context.fill();
558
559             context.restore();
560         }
561
562         return true;
563     },
564
565     /**
566      * @param {number} entryIndex
567      * @return {boolean}
568      */
569     forceDecoration: function(entryIndex)
570     {
571         var event = this._entryEvents[entryIndex];
572         if (!event)
573             return !!this._entryIndexToFrame[entryIndex];
574         return !!event.warning;
575     },
576
577    /**
578      * @param {number} entryIndex
579      * @return {?{startTime: number, endTime: number}}
580      */
581     highlightTimeRange: function(entryIndex)
582     {
583         var startTime = this._timelineData.entryStartTimes[entryIndex];
584         if (!startTime)
585             return null;
586         return {
587             startTime: startTime,
588             endTime: startTime + this._timelineData.entryTotalTimes[entryIndex]
589         }
590     },
591
592     /**
593      * @return {number}
594      */
595     paddingLeft: function()
596     {
597         return 0;
598     },
599
600     /**
601      * @param {number} entryIndex
602      * @return {string}
603      */
604     textColor: function(entryIndex)
605     {
606         return "white";
607     },
608
609     /**
610      * @param {string} title
611      * @param {number} level
612      */
613     _appendHeaderRecord: function(title, level)
614     {
615         var index = this._entryEvents.length;
616         this._entryIndexToTitle[index] = title;
617         this._entryEvents.push(null);
618         this._timelineData.entryLevels[index] = level;
619         this._timelineData.entryTotalTimes[index] = this._timeSpan;
620         this._timelineData.entryStartTimes[index] = this._minimumBoundary;
621     },
622
623     /**
624      * @param {!WebInspector.TracingModel.Event} event
625      * @param {number} level
626      */
627     _appendEvent: function(event, level)
628     {
629         var index = this._entryEvents.length;
630         this._entryEvents.push(event);
631         this._timelineData.entryLevels[index] = level;
632         this._timelineData.entryTotalTimes[index] = event.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
633         this._timelineData.entryStartTimes[index] = event.startTime;
634     },
635
636     /**
637      * @param {!Array.<!WebInspector.TracingModel.Event>} steps
638      * @param {number} level
639      */
640     _appendAsyncEventSteps: function(steps, level)
641     {
642         // If we have past steps, put the end event for each range rather than start one.
643         var eventOffset = steps[1].phase === WebInspector.TracingModel.Phase.AsyncStepPast ? 1 : 0;
644         for (var i = 0; i < steps.length - 1; ++i) {
645             var index = this._entryEvents.length;
646             this._entryEvents.push(steps[i + eventOffset]);
647             var startTime = steps[i].startTime;
648             this._timelineData.entryLevels[index] = level;
649             this._timelineData.entryTotalTimes[index] = steps[i + 1].startTime - startTime;
650             this._timelineData.entryStartTimes[index] = startTime;
651         }
652     },
653
654     /**
655      * @param {!WebInspector.TimelineFrame} frame
656      */
657     _appendFrame: function(frame)
658     {
659         var index = this._entryEvents.length;
660         this._entryEvents.push(null);
661         this._entryIndexToFrame[index] = frame;
662         this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true);
663         this._timelineData.entryLevels[index] = this._frameBarsLevel;
664         this._timelineData.entryTotalTimes[index] = frame.duration;
665         this._timelineData.entryStartTimes[index] = frame.startTime;
666     },
667
668     /**
669      * @param {number} entryIndex
670      * @return {?WebInspector.TimelineSelection}
671      */
672     createSelection: function(entryIndex)
673     {
674         var event = this._entryEvents[entryIndex];
675         if (event) {
676             this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
677             return this._lastSelection.timelineSelection;
678         }
679         var frame = this._entryIndexToFrame[entryIndex];
680         if (frame) {
681             this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), entryIndex);
682             return this._lastSelection.timelineSelection;
683         }
684         return null;
685     },
686
687     /**
688      * @param {?WebInspector.TimelineSelection} selection
689      * @return {number}
690      */
691     entryIndexForSelection: function(selection)
692     {
693         if (!selection)
694             return -1;
695
696         if (this._lastSelection && this._lastSelection.timelineSelection.object() === selection.object())
697             return this._lastSelection.entryIndex;
698         switch  (selection.type()) {
699         case WebInspector.TimelineSelection.Type.TraceEvent:
700             var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
701             var entryEvents = this._entryEvents;
702             for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
703                 if (entryEvents[entryIndex] === event) {
704                     this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
705                     return entryIndex;
706                 }
707             }
708             break;
709         case WebInspector.TimelineSelection.Type.Frame:
710             var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object());
711             for (var frameIndex in this._entryIndexToFrame) {
712                 if (this._entryIndexToFrame[frameIndex] === frame) {
713                     this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), Number(frameIndex));
714                     return Number(frameIndex);
715                 }
716             }
717             break;
718         }
719         return -1;
720     }
721 }
722
723 /**
724  * @constructor
725  * @extends {WebInspector.VBox}
726  * @implements {WebInspector.TimelineModeView}
727  * @implements {WebInspector.FlameChartDelegate}
728  * @param {!WebInspector.TimelineModeViewDelegate} delegate
729  * @param {!WebInspector.TracingTimelineModel} tracingModel
730  * @param {!WebInspector.TimelineFrameModelBase} frameModel
731  */
732 WebInspector.TimelineFlameChart = function(delegate, tracingModel, frameModel)
733 {
734     WebInspector.VBox.call(this);
735     this.element.classList.add("timeline-flamechart");
736     this.registerRequiredCSS("flameChart.css");
737     this._delegate = delegate;
738     this._model = tracingModel;
739     this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(tracingModel, frameModel)
740     this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
741     this._mainView.show(this.element);
742     this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
743     this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
744 }
745
746 WebInspector.TimelineFlameChart.prototype = {
747     dispose: function()
748     {
749         this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
750         this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
751     },
752
753     /**
754      * @param {number} windowStartTime
755      * @param {number} windowEndTime
756      */
757     requestWindowTimes: function(windowStartTime, windowEndTime)
758     {
759         this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
760     },
761
762     /**
763      * @param {?RegExp} textFilter
764      */
765     refreshRecords: function(textFilter)
766     {
767         this._dataProvider.reset();
768         this._mainView.scheduleUpdate();
769     },
770
771     wasShown: function()
772     {
773         this._mainView.scheduleUpdate();
774     },
775
776     /**
777      * @return {!WebInspector.View}
778      */
779     view: function()
780     {
781         return this;
782     },
783
784     reset: function()
785     {
786         this._automaticallySizeWindow = true;
787         this._dataProvider.reset();
788         this._mainView.reset();
789         this._mainView.setWindowTimes(0, Infinity);
790     },
791
792     _onRecordingStarted: function()
793     {
794         this._automaticallySizeWindow = true;
795         this._mainView.reset();
796     },
797
798     /**
799      * @param {!WebInspector.TimelineModel.Record} record
800      */
801     addRecord: function(record)
802     {
803         this._dataProvider.reset();
804         if (this._automaticallySizeWindow) {
805             var minimumRecordTime = this._model.minimumRecordTime();
806             if (record.startTime() > (minimumRecordTime + 1000)) {
807                 this._automaticallySizeWindow = false;
808                 this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
809             }
810             this._mainView.scheduleUpdate();
811         } else {
812             if (!this._pendingUpdateTimer)
813                 this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
814         }
815     },
816
817     _updateOnAddRecord: function()
818     {
819         delete this._pendingUpdateTimer;
820         this._mainView.scheduleUpdate();
821     },
822
823     /**
824      * @param {number} startTime
825      * @param {number} endTime
826      */
827     setWindowTimes: function(startTime, endTime)
828     {
829         this._mainView.setWindowTimes(startTime, endTime);
830         this._delegate.select(null);
831     },
832
833     /**
834      * @param {number} width
835      */
836     setSidebarSize: function(width)
837     {
838     },
839
840     /**
841      * @param {?WebInspector.TimelineModel.Record} record
842      * @param {string=} regex
843      * @param {boolean=} selectRecord
844      */
845     highlightSearchResult: function(record, regex, selectRecord)
846     {
847     },
848
849     /**
850      * @param {?WebInspector.TimelineSelection} selection
851      */
852     setSelection: function(selection)
853     {
854         var index = this._dataProvider.entryIndexForSelection(selection);
855         this._mainView.setSelectedEntry(index);
856     },
857
858     /**
859      * @param {!WebInspector.Event} event
860      */
861     _onEntrySelected: function(event)
862     {
863         var entryIndex = /** @type{number} */ (event.data);
864         var timelineSelection = this._dataProvider.createSelection(entryIndex);
865         if (timelineSelection)
866             this._delegate.select(timelineSelection);
867     },
868
869     __proto__: WebInspector.VBox.prototype
870 }
871
872 /**
873   * @constructor
874   * @param {!WebInspector.TimelineSelection} selection
875   * @param {number} entryIndex
876   */
877 WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex)
878 {
879     this.timelineSelection = selection;
880     this.entryIndex = entryIndex;
881 }