Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TimelineView.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  * @constructor
34  * @extends {WebInspector.HBox}
35  * @implements {WebInspector.TimelineModeView}
36  * @param {!WebInspector.TimelineModeViewDelegate} delegate
37  * @param {!WebInspector.TimelineModel} model
38  * @param {!WebInspector.TimelineUIUtils} uiUtils
39  */
40 WebInspector.TimelineView = function(delegate, model, uiUtils)
41 {
42     WebInspector.HBox.call(this);
43     this.element.classList.add("timeline-view");
44
45     this._delegate = delegate;
46     this._model = model;
47     this._uiUtils = uiUtils;
48     this._presentationModel = new WebInspector.TimelinePresentationModel(model, uiUtils);
49     this._calculator = new WebInspector.TimelineCalculator(model);
50     this._linkifier = new WebInspector.Linkifier();
51     this._frameStripByFrame = new Map();
52
53     this._boundariesAreValid = true;
54     this._scrollTop = 0;
55
56     this._recordsView = this._createRecordsView();
57     this._recordsView.addEventListener(WebInspector.SplitView.Events.SidebarSizeChanged, this._sidebarResized, this);
58     this._recordsView.show(this.element);
59     this._headerElement = this.element.createChild("div", "fill");
60     this._headerElement.id = "timeline-graph-records-header";
61
62     // Create gpu tasks containers.
63     this._cpuBarsElement = this._headerElement.createChild("div", "timeline-utilization-strip");
64     if (Runtime.experiments.isEnabled("gpuTimeline"))
65         this._gpuBarsElement = this._headerElement.createChild("div", "timeline-utilization-strip gpu");
66
67     this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
68
69     this.element.addEventListener("mousemove", this._mouseMove.bind(this), false);
70     this.element.addEventListener("mouseleave", this._mouseLeave.bind(this), false);
71     this.element.addEventListener("keydown", this._keyDown.bind(this), false);
72
73     this._expandOffset = 15;
74 }
75
76 WebInspector.TimelineView.prototype = {
77     /**
78      * @param {?WebInspector.TimelineFrameModelBase} frameModel
79      */
80     setFrameModel: function(frameModel)
81     {
82         this._frameModel = frameModel;
83     },
84
85     /**
86      * @return {!WebInspector.SplitView}
87      */
88     _createRecordsView: function()
89     {
90         var recordsView = new WebInspector.SplitView(true, false, "timelinePanelRecorsSplitViewState");
91         this._containerElement = recordsView.element;
92         this._containerElement.tabIndex = 0;
93         this._containerElement.id = "timeline-container";
94         this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
95
96         // Create records list in the records sidebar.
97         recordsView.sidebarElement().createChild("div", "timeline-records-title").textContent = WebInspector.UIString("RECORDS");
98         this._sidebarListElement = recordsView.sidebarElement().createChild("div", "timeline-records-list");
99
100         // Create grid in the records main area.
101         this._gridContainer = new WebInspector.VBoxWithResizeCallback(this._onViewportResize.bind(this));
102         this._gridContainer.element.id = "resources-container-content";
103         this._gridContainer.show(recordsView.mainElement());
104         this._timelineGrid = new WebInspector.TimelineGrid();
105         this._gridContainer.element.appendChild(this._timelineGrid.element);
106
107         this._itemsGraphsElement = this._gridContainer.element.createChild("div");
108         this._itemsGraphsElement.id = "timeline-graphs";
109
110         // Create gap elements
111         this._topGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
112         this._graphRowsElement = this._itemsGraphsElement.createChild("div");
113         this._bottomGapElement = this._itemsGraphsElement.createChild("div", "timeline-gap");
114         this._expandElements = this._itemsGraphsElement.createChild("div");
115         this._expandElements.id = "orphan-expand-elements";
116
117         return recordsView;
118     },
119
120     _rootRecord: function()
121     {
122         return this._presentationModel.rootRecord();
123     },
124
125     _updateEventDividers: function()
126     {
127         this._timelineGrid.removeEventDividers();
128         var clientWidth = this._graphRowsElementWidth;
129         var dividers = [];
130         var eventDividerRecords = this._model.eventDividerRecords();
131
132         for (var i = 0; i < eventDividerRecords.length; ++i) {
133             var record = eventDividerRecords[i];
134             var position = this._calculator.computePosition(record.startTime());
135             var dividerPosition = Math.round(position);
136             if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
137                 continue;
138             var title = this._uiUtils.titleForRecord(record);
139             var divider = this._uiUtils.createEventDivider(record.type(), title);
140             divider.style.left = dividerPosition + "px";
141             dividers[dividerPosition] = divider;
142         }
143         this._timelineGrid.addEventDividers(dividers);
144     },
145
146     _updateFrameBars: function(frames)
147     {
148         var clientWidth = this._graphRowsElementWidth;
149         if (this._frameContainer) {
150             this._frameContainer.removeChildren();
151         } else {
152             const frameContainerBorderWidth = 1;
153             this._frameContainer = createElementWithClass("div", "fill timeline-frame-container");
154             this._frameContainer.style.height = WebInspector.TimelinePanel.rowHeight + frameContainerBorderWidth + "px";
155             this._frameContainer.addEventListener("dblclick", this._onFrameDoubleClicked.bind(this), false);
156             this._frameContainer.addEventListener("click", this._onFrameClicked.bind(this), false);
157         }
158         this._frameStripByFrame.clear();
159
160         var dividers = [];
161
162         for (var i = 0; i < frames.length; ++i) {
163             var frame = frames[i];
164             var frameStart = this._calculator.computePosition(frame.startTime);
165             var frameEnd = this._calculator.computePosition(frame.endTime);
166
167             var frameStrip = createElementWithClass("div", "timeline-frame-strip");
168             var actualStart = Math.max(frameStart, 0);
169             var width = frameEnd - actualStart;
170             frameStrip.style.left = actualStart + "px";
171             frameStrip.style.width = width + "px";
172             frameStrip._frame = frame;
173             this._frameStripByFrame.set(frame, frameStrip);
174
175             const minWidthForFrameInfo = 60;
176             if (width > minWidthForFrameInfo)
177                 frameStrip.textContent = Number.millisToString(frame.endTime - frame.startTime, true);
178
179             this._frameContainer.appendChild(frameStrip);
180
181             if (actualStart > 0) {
182                 var frameMarker = this._uiUtils.createBeginFrameDivider();
183                 frameMarker.style.left = frameStart + "px";
184                 dividers.push(frameMarker);
185             }
186         }
187         this._timelineGrid.addEventDividers(dividers);
188         this._headerElement.appendChild(this._frameContainer);
189     },
190
191     _onFrameDoubleClicked: function(event)
192     {
193         var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
194         if (!frameBar)
195             return;
196         this._delegate.requestWindowTimes(frameBar._frame.startTime, frameBar._frame.endTime);
197     },
198
199     _onFrameClicked: function(event)
200     {
201         var frameBar = event.target.enclosingNodeOrSelfWithClass("timeline-frame-strip");
202         if (!frameBar)
203             return;
204         this._delegate.select(WebInspector.TimelineSelection.fromFrame(frameBar._frame));
205     },
206
207     /**
208      * @param {!WebInspector.TimelineModel.Record} record
209      */
210     addRecord: function(record)
211     {
212         this._presentationModel.addRecord(record);
213         this._invalidateAndScheduleRefresh(false, false);
214     },
215
216     /**
217      * @param {number} width
218      */
219     setSidebarSize: function(width)
220     {
221         this._recordsView.setSidebarSize(width);
222     },
223
224     /**
225      * @param {!WebInspector.Event} event
226      */
227     _sidebarResized: function(event)
228     {
229         this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, event.data);
230     },
231
232     _onViewportResize: function()
233     {
234         this._resize(this._recordsView.sidebarSize());
235     },
236
237     /**
238      * @param {number} sidebarWidth
239      */
240     _resize: function(sidebarWidth)
241     {
242         this._closeRecordDetails();
243         this._graphRowsElementWidth = this._graphRowsElement.offsetWidth;
244         this._headerElement.style.left = sidebarWidth + "px";
245         this._headerElement.style.width = this._itemsGraphsElement.offsetWidth + "px";
246         this._scheduleRefresh(false, true);
247     },
248
249     _resetView: function()
250     {
251         this._windowStartTime = 0;
252         this._windowEndTime = 0;
253         this._boundariesAreValid = false;
254         this._adjustScrollPosition(0);
255         this._linkifier.reset();
256         this._closeRecordDetails();
257         this._automaticallySizeWindow = true;
258         this._presentationModel.reset();
259     },
260
261
262     /**
263      * @return {!WebInspector.View}
264      */
265     view: function()
266     {
267         return this;
268     },
269
270     dispose: function()
271     {
272     },
273
274     reset: function()
275     {
276         this._resetView();
277         this._invalidateAndScheduleRefresh(true, true);
278     },
279
280     /**
281      * @return {!Array.<!Element>}
282      */
283     elementsToRestoreScrollPositionsFor: function()
284     {
285         return [this._containerElement];
286     },
287
288     /**
289      * @param {?RegExp} textFilter
290      */
291     refreshRecords: function(textFilter)
292     {
293         this._automaticallySizeWindow = false;
294         this._presentationModel.setTextFilter(textFilter);
295         this._invalidateAndScheduleRefresh(false, true);
296     },
297
298     willHide: function()
299     {
300         this._closeRecordDetails();
301         WebInspector.View.prototype.willHide.call(this);
302     },
303
304     wasShown: function()
305     {
306         this._presentationModel.refreshRecords();
307         WebInspector.HBox.prototype.wasShown.call(this);
308     },
309
310     _onScroll: function(event)
311     {
312         this._closeRecordDetails();
313         this._scrollTop = this._containerElement.scrollTop;
314         var dividersTop = Math.max(0, this._scrollTop);
315         this._timelineGrid.setScrollAndDividerTop(this._scrollTop, dividersTop);
316         this._scheduleRefresh(true, true);
317     },
318
319     /**
320      * @param {boolean} preserveBoundaries
321      * @param {boolean} userGesture
322      */
323     _invalidateAndScheduleRefresh: function(preserveBoundaries, userGesture)
324     {
325         this._presentationModel.invalidateFilteredRecords();
326         this._scheduleRefresh(preserveBoundaries, userGesture);
327     },
328
329     _clearSelection: function()
330     {
331         this._delegate.select(null);
332     },
333
334     /**
335      * @param {?WebInspector.TimelinePresentationModel.Record} presentationRecord
336      */
337     _selectRecord: function(presentationRecord)
338     {
339         if (presentationRecord.coalesced()) {
340             // Presentation record does not have model record to highlight.
341             this._innerSetSelectedRecord(presentationRecord);
342             var aggregatedStats = {};
343             var presentationChildren = presentationRecord.presentationChildren();
344             for (var i = 0; i < presentationChildren.length; ++i)
345                 this._uiUtils.aggregateTimeForRecord(aggregatedStats, presentationChildren[i].record());
346             var idle = presentationRecord.endTime() - presentationRecord.startTime();
347             for (var category in aggregatedStats)
348                 idle -= aggregatedStats[category];
349             aggregatedStats["idle"] = idle;
350
351             var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null, true);
352             var pieChart = WebInspector.TimelineUIUtils.generatePieChart(aggregatedStats);
353             var title = this._uiUtils.titleForRecord(presentationRecord.record());
354             contentHelper.appendTextRow(WebInspector.UIString("Type"), title);
355             contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), pieChart);
356             this._delegate.showInDetails(contentHelper.element);
357             return;
358         }
359         this._delegate.select(WebInspector.TimelineSelection.fromRecord(presentationRecord.record()));
360     },
361
362     /**
363      * @param {?WebInspector.TimelineSelection} selection
364      */
365     setSelection: function(selection)
366     {
367         if (!selection) {
368             this._innerSetSelectedRecord(null);
369             this._setSelectedFrame(null);
370             return;
371         }
372         if (selection.type() === WebInspector.TimelineSelection.Type.Record) {
373             var record = /** @type {!WebInspector.TimelineModel.Record} */ (selection.object());
374             this._innerSetSelectedRecord(this._presentationModel.toPresentationRecord(record));
375             this._setSelectedFrame(null);
376         } else if (selection.type() === WebInspector.TimelineSelection.Type.Frame) {
377             var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object());
378             this._innerSetSelectedRecord(null);
379             this._setSelectedFrame(frame);
380         }
381     },
382
383     /**
384      * @param {?WebInspector.TimelinePresentationModel.Record} presentationRecord
385      */
386     _innerSetSelectedRecord: function(presentationRecord)
387     {
388         if (presentationRecord === this._lastSelectedRecord)
389             return;
390
391         // Remove selection rendering.p
392         if (this._lastSelectedRecord) {
393             if (this._lastSelectedRecord.listRow())
394                 this._lastSelectedRecord.listRow().renderAsSelected(false);
395             if (this._lastSelectedRecord.graphRow())
396                 this._lastSelectedRecord.graphRow().renderAsSelected(false);
397         }
398
399         this._lastSelectedRecord = presentationRecord;
400         if (!presentationRecord)
401             return;
402
403         this._innerRevealRecord(presentationRecord);
404         if (presentationRecord.listRow())
405             presentationRecord.listRow().renderAsSelected(true);
406         if (presentationRecord.graphRow())
407             presentationRecord.graphRow().renderAsSelected(true);
408     },
409
410     /**
411      * @param {?WebInspector.TimelineFrame} frame
412      */
413     _setSelectedFrame: function(frame)
414     {
415         if (this._lastSelectedFrame === frame)
416             return;
417         var oldStripElement = this._lastSelectedFrame && this._frameStripByFrame.get(this._lastSelectedFrame);
418         if (oldStripElement)
419             oldStripElement.classList.remove("selected");
420         var newStripElement = frame && this._frameStripByFrame.get(frame);
421         if (newStripElement)
422             newStripElement.classList.add("selected");
423         this._lastSelectedFrame = frame;
424     },
425
426     /**
427      * @param {number} startTime
428      * @param {number} endTime
429      */
430     setWindowTimes: function(startTime, endTime)
431     {
432         this._windowStartTime = startTime;
433         this._windowEndTime = endTime;
434         this._presentationModel.setWindowTimes(startTime, endTime);
435         this._automaticallySizeWindow = false;
436         this._invalidateAndScheduleRefresh(false, true);
437         this._clearSelection();
438     },
439
440     /**
441      * @param {boolean} preserveBoundaries
442      * @param {boolean} userGesture
443      */
444     _scheduleRefresh: function(preserveBoundaries, userGesture)
445     {
446         this._closeRecordDetails();
447         this._boundariesAreValid &= preserveBoundaries;
448
449         if (!this.isShowing())
450             return;
451
452         if (preserveBoundaries || userGesture)
453             this._refresh();
454         else {
455             if (!this._refreshTimeout)
456                 this._refreshTimeout = setTimeout(this._refresh.bind(this), 300);
457         }
458     },
459
460     _refresh: function()
461     {
462         if (this._refreshTimeout) {
463             clearTimeout(this._refreshTimeout);
464             delete this._refreshTimeout;
465         }
466         var windowStartTime = this._windowStartTime || this._model.minimumRecordTime();
467         var windowEndTime = this._windowEndTime || this._model.maximumRecordTime();
468         this._timelinePaddingLeft = this._expandOffset;
469         this._calculator.setWindow(windowStartTime, windowEndTime);
470         this._calculator.setDisplayWindow(this._timelinePaddingLeft, this._graphRowsElementWidth);
471
472         this._refreshRecords();
473         if (!this._boundariesAreValid) {
474             this._updateEventDividers();
475             if (this._frameContainer)
476                 this._frameContainer.remove();
477             if (this._frameModel) {
478                 var frames = this._frameModel.filteredFrames(windowStartTime, windowEndTime);
479                 const maxFramesForFrameBars = 30;
480                 if  (frames.length && frames.length < maxFramesForFrameBars) {
481                     this._timelineGrid.removeDividers();
482                     this._updateFrameBars(frames);
483                 } else {
484                     this._timelineGrid.updateDividers(this._calculator);
485                 }
486             } else
487                 this._timelineGrid.updateDividers(this._calculator);
488             this._refreshAllUtilizationBars();
489         }
490         this._boundariesAreValid = true;
491     },
492
493     /**
494      * @param {!WebInspector.TimelinePresentationModel.Record} recordToReveal
495      */
496     _innerRevealRecord: function(recordToReveal)
497     {
498         var needRefresh = false;
499         // Expand all ancestors.
500         for (var parent = recordToReveal.presentationParent(); parent !== this._rootRecord(); parent = parent.presentationParent()) {
501             if (!parent.collapsed())
502                 continue;
503             this._presentationModel.invalidateFilteredRecords();
504             parent.setCollapsed(false);
505             needRefresh = true;
506         }
507         var recordsInWindow = this._presentationModel.filteredRecords();
508         var index = recordsInWindow.indexOf(recordToReveal);
509
510         var itemOffset = index * WebInspector.TimelinePanel.rowHeight;
511         var visibleTop = this._scrollTop - WebInspector.TimelinePanel.headerHeight;
512         var visibleBottom = visibleTop + this._containerElementHeight - WebInspector.TimelinePanel.rowHeight;
513         if (itemOffset < visibleTop)
514             this._containerElement.scrollTop = itemOffset;
515         else if (itemOffset > visibleBottom)
516             this._containerElement.scrollTop = itemOffset - this._containerElementHeight + WebInspector.TimelinePanel.headerHeight + WebInspector.TimelinePanel.rowHeight;
517         else if (needRefresh)
518             this._refreshRecords();
519     },
520
521     _refreshRecords: function()
522     {
523         this._containerElementHeight = this._containerElement.clientHeight;
524         var recordsInWindow = this._presentationModel.filteredRecords();
525
526         // Calculate the visible area.
527         var visibleTop = this._scrollTop;
528         var visibleBottom = visibleTop + this._containerElementHeight;
529
530         var rowHeight = WebInspector.TimelinePanel.rowHeight;
531         var headerHeight = WebInspector.TimelinePanel.headerHeight;
532
533         // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
534         var startIndex = Math.max(0, Math.min(Math.floor((visibleTop - headerHeight) / rowHeight), recordsInWindow.length - 1));
535         var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
536         var lastVisibleLine = Math.max(0, Math.floor((visibleBottom - headerHeight) / rowHeight));
537         if (this._automaticallySizeWindow && recordsInWindow.length > lastVisibleLine) {
538             this._automaticallySizeWindow = false;
539             this._clearSelection();
540             // If we're at the top, always use real timeline start as a left window bound so that expansion arrow padding logic works.
541             var windowStartTime = startIndex ? recordsInWindow[startIndex].startTime() : this._model.minimumRecordTime();
542             var windowEndTime = recordsInWindow[Math.max(0, lastVisibleLine - 1)].endTime();
543             this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
544             recordsInWindow = this._presentationModel.filteredRecords();
545             endIndex = Math.min(recordsInWindow.length, lastVisibleLine);
546         }
547
548         // Resize gaps first.
549         this._topGapElement.style.height = (startIndex * rowHeight) + "px";
550         this._recordsView.sidebarElement().firstElementChild.style.flexBasis = (startIndex * rowHeight + headerHeight) + "px";
551         this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
552         var rowsHeight = headerHeight + recordsInWindow.length * rowHeight;
553         var totalHeight = Math.max(this._containerElementHeight, rowsHeight);
554
555         this._recordsView.mainElement().style.height = totalHeight + "px";
556         this._recordsView.sidebarElement().style.height = totalHeight + "px";
557         this._recordsView.resizerElement().style.height = totalHeight + "px";
558
559         // Update visible rows.
560         var listRowElement = this._sidebarListElement.firstChild;
561         var width = this._graphRowsElementWidth;
562         this._itemsGraphsElement.removeChild(this._graphRowsElement);
563         var graphRowElement = this._graphRowsElement.firstChild;
564         var scheduleRefreshCallback = this._invalidateAndScheduleRefresh.bind(this, true, true);
565         var selectRecordCallback = this._selectRecord.bind(this);
566         this._itemsGraphsElement.removeChild(this._expandElements);
567         this._expandElements.removeChildren();
568
569         for (var i = 0; i < endIndex; ++i) {
570             var record = recordsInWindow[i];
571
572             if (i < startIndex) {
573                 var lastChildIndex = i + record.visibleChildrenCount();
574                 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
575                     var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
576                     var positions = this._calculator.computeBarGraphWindowPosition(record);
577                     expandElement._update(record, i, positions.left - this._expandOffset, positions.width);
578                 }
579             } else {
580                 if (!listRowElement) {
581                     listRowElement = new WebInspector.TimelineRecordListRow(this._linkifier, selectRecordCallback, scheduleRefreshCallback).element;
582                     this._sidebarListElement.appendChild(listRowElement);
583                 }
584                 if (!graphRowElement) {
585                     graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, selectRecordCallback, scheduleRefreshCallback).element;
586                     this._graphRowsElement.appendChild(graphRowElement);
587                 }
588
589                 listRowElement.row.update(record, visibleTop, this._uiUtils);
590                 graphRowElement.row.update(record, this._calculator, this._expandOffset, i, this._uiUtils);
591                 if (this._lastSelectedRecord === record) {
592                     listRowElement.row.renderAsSelected(true);
593                     graphRowElement.row.renderAsSelected(true);
594                 }
595
596                 listRowElement = listRowElement.nextSibling;
597                 graphRowElement = graphRowElement.nextSibling;
598             }
599         }
600
601         // Remove extra rows.
602         while (listRowElement) {
603             var nextElement = listRowElement.nextSibling;
604             listRowElement.row.dispose();
605             listRowElement = nextElement;
606         }
607         while (graphRowElement) {
608             var nextElement = graphRowElement.nextSibling;
609             graphRowElement.row.dispose();
610             graphRowElement = nextElement;
611         }
612
613         this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
614         this._itemsGraphsElement.appendChild(this._expandElements);
615         this._adjustScrollPosition(recordsInWindow.length * rowHeight + headerHeight);
616
617         return recordsInWindow.length;
618     },
619
620     _refreshAllUtilizationBars: function()
621     {
622         this._refreshUtilizationBars(WebInspector.UIString("CPU"), this._model.mainThreadTasks(), this._cpuBarsElement);
623         if (Runtime.experiments.isEnabled("gpuTimeline"))
624             this._refreshUtilizationBars(WebInspector.UIString("GPU"), this._model.gpuThreadTasks(), this._gpuBarsElement);
625     },
626
627     /**
628      * @param {string} name
629      * @param {!Array.<!WebInspector.TimelineModel.Record>} tasks
630      * @param {?Element} container
631      */
632     _refreshUtilizationBars: function(name, tasks, container)
633     {
634         if (!container)
635             return;
636
637         const barOffset = 3;
638         const minGap = 3;
639
640         var minWidth = WebInspector.TimelineCalculator._minWidth;
641         var widthAdjustment = minWidth / 2;
642
643         var width = this._graphRowsElementWidth;
644         var boundarySpan = this._windowEndTime - this._windowStartTime;
645         var scale = boundarySpan / (width - minWidth - this._timelinePaddingLeft);
646         var startTime = (this._windowStartTime - this._timelinePaddingLeft * scale);
647         var endTime = startTime + width * scale;
648
649         /**
650          * @param {number} value
651          * @param {!WebInspector.TimelineModel.Record} task
652          * @return {number}
653          */
654         function compareEndTime(value, task)
655         {
656             return value < task.endTime() ? -1 : 1;
657         }
658
659         var taskIndex = insertionIndexForObjectInListSortedByFunction(startTime, tasks, compareEndTime);
660
661         var foreignStyle = "gpu-task-foreign";
662         var element = /** @type {?Element} */ (container.firstChild);
663         var lastElement;
664         var lastLeft;
665         var lastRight;
666
667         for (; taskIndex < tasks.length; ++taskIndex) {
668             var task = tasks[taskIndex];
669             if (task.startTime() > endTime)
670                 break;
671
672             var left = Math.max(0, this._calculator.computePosition(task.startTime()) + barOffset - widthAdjustment);
673             var right = Math.min(width, this._calculator.computePosition(task.endTime() || 0) + barOffset + widthAdjustment);
674
675             if (lastElement) {
676                 var gap = Math.floor(left) - Math.ceil(lastRight);
677                 if (gap < minGap) {
678                     if (!task.data["foreign"])
679                         lastElement.classList.remove(foreignStyle);
680                     lastRight = right;
681                     lastElement._tasksInfo.lastTaskIndex = taskIndex;
682                     continue;
683                 }
684                 lastElement.style.width = (lastRight - lastLeft) + "px";
685             }
686
687             if (!element)
688                 element = container.createChild("div", "timeline-graph-bar");
689             element.style.left = left + "px";
690             element._tasksInfo = {name: name, tasks: tasks, firstTaskIndex: taskIndex, lastTaskIndex: taskIndex};
691             if (task.data["foreign"])
692                 element.classList.add(foreignStyle);
693             lastLeft = left;
694             lastRight = right;
695             lastElement = element;
696             element = /** @type {?Element} */ (element.nextSibling);
697         }
698
699         if (lastElement)
700             lastElement.style.width = (lastRight - lastLeft) + "px";
701
702         while (element) {
703             var nextElement = element.nextSibling;
704             element._tasksInfo = null;
705             container.removeChild(element);
706             element = nextElement;
707         }
708     },
709
710     _adjustScrollPosition: function(totalHeight)
711     {
712         // Prevent the container from being scrolled off the end.
713         if ((this._scrollTop + this._containerElementHeight) > totalHeight + 1)
714             this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
715     },
716
717     /**
718      * @param {!Element} element
719      * @param {!Event} event
720      * @return {!Element|!AnchorBox|undefined}
721      */
722     _getPopoverAnchor: function(element, event)
723     {
724         var anchor = element.enclosingNodeOrSelfWithClass("timeline-graph-bar");
725         if (anchor && anchor._tasksInfo)
726             return anchor;
727     },
728
729     _mouseLeave: function()
730     {
731         this._hideQuadHighlight();
732     },
733
734     /**
735      * @param {!Event} e
736      */
737     _mouseMove: function(e)
738     {
739         var rowElement = e.target.enclosingNodeOrSelfWithClass("timeline-tree-item");
740         if (!this._highlightQuad(rowElement))
741             this._hideQuadHighlight();
742
743         var taskBarElement = e.target.enclosingNodeOrSelfWithClass("timeline-graph-bar");
744         if (taskBarElement && taskBarElement._tasksInfo) {
745             var offset = taskBarElement.offsetLeft;
746             this._timelineGrid.showCurtains(offset >= 0 ? offset : 0, taskBarElement.offsetWidth);
747         } else
748             this._timelineGrid.hideCurtains();
749     },
750
751     /**
752      * @param {!Event} event
753      */
754     _keyDown: function(event)
755     {
756         if (!this._lastSelectedRecord || event.shiftKey || event.metaKey || event.ctrlKey)
757             return;
758
759         var record = this._lastSelectedRecord;
760         var recordsInWindow = this._presentationModel.filteredRecords();
761         var index = recordsInWindow.indexOf(record);
762         var recordsInPage = Math.floor(this._containerElementHeight / WebInspector.TimelinePanel.rowHeight);
763         var rowHeight = WebInspector.TimelinePanel.rowHeight;
764
765         if (index === -1)
766             index = 0;
767
768         switch (event.keyIdentifier) {
769         case "Left":
770             if (record.presentationParent()) {
771                 if ((!record.expandable() || record.collapsed()) && record.presentationParent() !== this._presentationModel.rootRecord()) {
772                     this._selectRecord(record.presentationParent());
773                 } else {
774                     record.setCollapsed(true);
775                     this._invalidateAndScheduleRefresh(true, true);
776                 }
777             }
778             event.consume(true);
779             break;
780         case "Up":
781             if (--index < 0)
782                 break;
783             this._selectRecord(recordsInWindow[index]);
784             event.consume(true);
785             break;
786         case "Right":
787             if (record.expandable() && record.collapsed()) {
788                 record.setCollapsed(false);
789                 this._invalidateAndScheduleRefresh(true, true);
790             } else {
791                 if (++index >= recordsInWindow.length)
792                     break;
793                 this._selectRecord(recordsInWindow[index]);
794             }
795             event.consume(true);
796             break;
797         case "Down":
798             if (++index >= recordsInWindow.length)
799                 break;
800             this._selectRecord(recordsInWindow[index]);
801             event.consume(true);
802             break;
803         case "PageUp":
804             index = Math.max(0, index - recordsInPage);
805             this._scrollTop = Math.max(0, this._scrollTop - recordsInPage * rowHeight);
806             this._containerElement.scrollTop = this._scrollTop;
807             this._selectRecord(recordsInWindow[index]);
808             event.consume(true);
809             break;
810         case "PageDown":
811             index = Math.min(recordsInWindow.length - 1, index + recordsInPage);
812             this._scrollTop = Math.min(this._containerElement.scrollHeight - this._containerElementHeight, this._scrollTop + recordsInPage * rowHeight);
813             this._containerElement.scrollTop = this._scrollTop;
814             this._selectRecord(recordsInWindow[index]);
815             event.consume(true);
816             break;
817         case "Home":
818             index = 0;
819             this._selectRecord(recordsInWindow[index]);
820             event.consume(true);
821             break;
822         case "End":
823             index = recordsInWindow.length - 1;
824             this._selectRecord(recordsInWindow[index]);
825             event.consume(true);
826             break;
827         }
828     },
829
830     /**
831      * @param {?Element} rowElement
832      * @return {boolean}
833      */
834     _highlightQuad: function(rowElement)
835     {
836         if (!rowElement || !rowElement.row)
837             return false;
838         var presentationRecord = rowElement.row._record;
839         if (presentationRecord.coalesced())
840             return false;
841         var record = presentationRecord.record();
842         if (this._highlightedQuadRecord === record)
843             return true;
844
845         var quad = this._uiUtils.highlightQuadForRecord(record);
846         var target = record.target();
847         if (!quad || !target)
848             return false;
849         this._highlightedQuadRecord = record;
850         target.domAgent().highlightQuad(quad, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
851         return true;
852     },
853
854     _hideQuadHighlight: function()
855     {
856         var target = this._highlightedQuadRecord ? this._highlightedQuadRecord.target() : null;
857         if (target)
858             target.domAgent().hideHighlight();
859
860         if (this._highlightedQuadRecord)
861             delete this._highlightedQuadRecord;
862     },
863
864     /**
865      * @param {!Element} anchor
866      * @param {!WebInspector.Popover} popover
867      */
868     _showPopover: function(anchor, popover)
869     {
870         if (!anchor._tasksInfo)
871             return;
872         popover.show(WebInspector.TimelineUIUtils.generateMainThreadBarPopupContent(this._model, anchor._tasksInfo), anchor, null, null, WebInspector.Popover.Orientation.Bottom);
873     },
874
875     _closeRecordDetails: function()
876     {
877         this._popoverHelper.hidePopover();
878     },
879
880     /**
881      * @param {?WebInspector.TimelineModel.Record} record
882      * @param {string=} regex
883      * @param {boolean=} selectRecord
884      */
885     highlightSearchResult: function(record, regex, selectRecord)
886     {
887        if (this._highlightDomChanges)
888             WebInspector.revertDomChanges(this._highlightDomChanges);
889         this._highlightDomChanges = [];
890
891         var presentationRecord = this._presentationModel.toPresentationRecord(record);
892         if (!presentationRecord)
893             return;
894
895         if (selectRecord)
896             this._selectRecord(presentationRecord);
897
898         for (var element = this._sidebarListElement.firstChild; element; element = element.nextSibling) {
899             if (element.row._record === presentationRecord) {
900                 element.row.highlight(regex, this._highlightDomChanges);
901                 break;
902             }
903         }
904     },
905
906     __proto__: WebInspector.HBox.prototype
907 }
908
909 /**
910  * @constructor
911  * @param {!WebInspector.TimelineModel} model
912  * @implements {WebInspector.TimelineGrid.Calculator}
913  */
914 WebInspector.TimelineCalculator = function(model)
915 {
916     this._model = model;
917 }
918
919 WebInspector.TimelineCalculator._minWidth = 5;
920
921 WebInspector.TimelineCalculator.prototype = {
922     /**
923      * @return {number}
924      */
925     paddingLeft: function()
926     {
927         return this._paddingLeft;
928     },
929
930     /**
931      * @param {number} time
932      * @return {number}
933      */
934     computePosition: function(time)
935     {
936         return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea + this._paddingLeft;
937     },
938
939     /**
940      * @param {!WebInspector.TimelinePresentationModel.Record} record
941      * @return {!{start: number, end: number, cpuWidth: number}}
942      */
943     computeBarGraphPercentages: function(record)
944     {
945         var start = (record.startTime() - this._minimumBoundary) / this.boundarySpan() * 100;
946         var end = (record.startTime() + record.selfTime() - this._minimumBoundary) / this.boundarySpan() * 100;
947         var cpuWidth = (record.endTime() - record.startTime()) / this.boundarySpan() * 100;
948         return {start: start, end: end, cpuWidth: cpuWidth};
949     },
950
951     /**
952      * @param {!WebInspector.TimelinePresentationModel.Record} record
953      * @return {!{left: number, width: number, cpuWidth: number}}
954      */
955     computeBarGraphWindowPosition: function(record)
956     {
957         var percentages = this.computeBarGraphPercentages(record);
958         var widthAdjustment = 0;
959
960         var left = this.computePosition(record.startTime());
961         var width = (percentages.end - percentages.start) / 100 * this._workingArea;
962         if (width < WebInspector.TimelineCalculator._minWidth) {
963             widthAdjustment = WebInspector.TimelineCalculator._minWidth - width;
964             width = WebInspector.TimelineCalculator._minWidth;
965         }
966         var cpuWidth = percentages.cpuWidth / 100 * this._workingArea + widthAdjustment;
967         return {left: left, width: width, cpuWidth: cpuWidth};
968     },
969
970     setWindow: function(minimumBoundary, maximumBoundary)
971     {
972         this._minimumBoundary = minimumBoundary;
973         this._maximumBoundary = maximumBoundary;
974     },
975
976     /**
977      * @param {number} paddingLeft
978      * @param {number} clientWidth
979      */
980     setDisplayWindow: function(paddingLeft, clientWidth)
981     {
982         this._workingArea = clientWidth - WebInspector.TimelineCalculator._minWidth - paddingLeft;
983         this._paddingLeft = paddingLeft;
984     },
985
986     /**
987      * @param {number} value
988      * @param {number=} precision
989      * @return {string}
990      */
991     formatTime: function(value, precision)
992     {
993         return Number.preciseMillisToString(value - this.zeroTime(), precision);
994     },
995
996     /**
997      * @return {number}
998      */
999     maximumBoundary: function()
1000     {
1001         return this._maximumBoundary;
1002     },
1003
1004     /**
1005      * @return {number}
1006      */
1007     minimumBoundary: function()
1008     {
1009         return this._minimumBoundary;
1010     },
1011
1012     /**
1013      * @return {number}
1014      */
1015     zeroTime: function()
1016     {
1017         return this._model.minimumRecordTime();
1018     },
1019
1020     /**
1021      * @return {number}
1022      */
1023     boundarySpan: function()
1024     {
1025         return this._maximumBoundary - this._minimumBoundary;
1026     }
1027 }
1028
1029 /**
1030  * @constructor
1031  * @param {!WebInspector.Linkifier} linkifier
1032  * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1033  * @param {function()} scheduleRefresh
1034  */
1035 WebInspector.TimelineRecordListRow = function(linkifier, selectRecord, scheduleRefresh)
1036 {
1037     this.element = createElement("div");
1038     this.element.row = this;
1039     this.element.style.cursor = "pointer";
1040     this.element.addEventListener("click", this._onClick.bind(this), false);
1041     this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1042     this.element.addEventListener("mouseleave", this._onMouseLeave.bind(this), false);
1043     this._linkifier = linkifier;
1044
1045     // Warning is float right block, it goes first.
1046     this._warningElement = this.element.createChild("div", "timeline-tree-item-warning hidden");
1047
1048     this._expandArrowElement = this.element.createChild("div", "timeline-tree-item-expand-arrow");
1049     this._expandArrowElement.addEventListener("click", this._onExpandClick.bind(this), false);
1050     var iconElement = this.element.createChild("span", "timeline-tree-icon");
1051     this._typeElement = this.element.createChild("span", "type");
1052
1053     this._dataElement = this.element.createChild("span", "data dimmed");
1054     this._scheduleRefresh = scheduleRefresh;
1055     this._selectRecord = selectRecord;
1056 }
1057
1058 WebInspector.TimelineRecordListRow.prototype = {
1059     /**
1060      * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
1061      * @param {number} offset
1062      * @param {!WebInspector.TimelineUIUtils} uiUtils
1063      */
1064     update: function(presentationRecord, offset, uiUtils)
1065     {
1066         this._record = presentationRecord;
1067         var record = presentationRecord.record();
1068         this._offset = offset;
1069
1070         this.element.className = "timeline-tree-item timeline-category-" + uiUtils.categoryForRecord(record).name;
1071         var paddingLeft = 5;
1072         var step = -3;
1073         for (var currentRecord = presentationRecord.presentationParent() ? presentationRecord.presentationParent().presentationParent() : null; currentRecord; currentRecord = currentRecord.presentationParent())
1074             paddingLeft += 12 / (Math.max(1, step++));
1075         this.element.style.paddingLeft = paddingLeft + "px";
1076         if (record.thread() !== WebInspector.TimelineModel.MainThreadName)
1077             this.element.classList.add("background");
1078
1079         this._typeElement.textContent = uiUtils.titleForRecord(record);
1080
1081         if (this._dataElement.firstChild)
1082             this._dataElement.removeChildren();
1083
1084         this._warningElement.classList.toggle("hidden", !presentationRecord.hasWarnings() && !presentationRecord.childHasWarnings());
1085         this._warningElement.classList.toggle("timeline-tree-item-child-warning", presentationRecord.childHasWarnings() && !presentationRecord.hasWarnings());
1086
1087         if (presentationRecord.coalesced()) {
1088             this._dataElement.createTextChild(WebInspector.UIString("× %d", presentationRecord.presentationChildren().length));
1089         } else {
1090             var detailsNode = uiUtils.buildDetailsNode(record, this._linkifier);
1091             if (detailsNode) {
1092                 this._dataElement.createTextChild("(");
1093                 this._dataElement.appendChild(detailsNode);
1094                 this._dataElement.createTextChild(")");
1095             }
1096         }
1097
1098         this._expandArrowElement.classList.toggle("parent", presentationRecord.expandable());
1099         this._expandArrowElement.classList.toggle("expanded", !!presentationRecord.visibleChildrenCount());
1100         this._record.setListRow(this);
1101     },
1102
1103     highlight: function(regExp, domChanges)
1104     {
1105         var matchInfo = this.element.textContent.match(regExp);
1106         if (matchInfo)
1107             WebInspector.highlightSearchResult(this.element, matchInfo.index, matchInfo[0].length, domChanges);
1108     },
1109
1110     dispose: function()
1111     {
1112         this.element.remove();
1113     },
1114
1115     /**
1116      * @param {!Event} event
1117      */
1118     _onExpandClick: function(event)
1119     {
1120         this._record.setCollapsed(!this._record.collapsed());
1121         this._scheduleRefresh();
1122         event.consume(true);
1123     },
1124
1125     /**
1126      * @param {!Event} event
1127      */
1128     _onClick: function(event)
1129     {
1130         this._selectRecord(this._record);
1131     },
1132
1133     /**
1134      * @param {boolean} selected
1135      */
1136     renderAsSelected: function(selected)
1137     {
1138         this.element.classList.toggle("selected", selected);
1139     },
1140
1141     /**
1142      * @param {!Event} event
1143      */
1144     _onMouseOver: function(event)
1145     {
1146         this.element.classList.add("hovered");
1147         if (this._record.graphRow())
1148             this._record.graphRow().element.classList.add("hovered");
1149     },
1150
1151     /**
1152      * @param {!Event} event
1153      */
1154     _onMouseLeave: function(event)
1155     {
1156         this.element.classList.remove("hovered");
1157         if (this._record.graphRow())
1158             this._record.graphRow().element.classList.remove("hovered");
1159     }
1160 }
1161
1162 /**
1163  * @constructor
1164  * @param {!Element} graphContainer
1165  * @param {function(!WebInspector.TimelinePresentationModel.Record)} selectRecord
1166  * @param {function()} scheduleRefresh
1167  */
1168 WebInspector.TimelineRecordGraphRow = function(graphContainer, selectRecord, scheduleRefresh)
1169 {
1170     this.element = createElement("div");
1171     this.element.row = this;
1172     this.element.addEventListener("mouseover", this._onMouseOver.bind(this), false);
1173     this.element.addEventListener("mouseleave", this._onMouseLeave.bind(this), false);
1174     this.element.addEventListener("click", this._onClick.bind(this), false);
1175
1176     this._barAreaElement = this.element.createChild("div", "timeline-graph-bar-area");
1177
1178     this._barCpuElement = this._barAreaElement.createChild("div", "timeline-graph-bar cpu");
1179     this._barCpuElement.row = this;
1180
1181     this._barElement = this._barAreaElement.createChild("div", "timeline-graph-bar");
1182     this._barElement.row = this;
1183
1184     this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
1185
1186     this._selectRecord = selectRecord;
1187     this._scheduleRefresh = scheduleRefresh;
1188 }
1189
1190 WebInspector.TimelineRecordGraphRow.prototype = {
1191     /**
1192      * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
1193      * @param {!WebInspector.TimelineCalculator} calculator
1194      * @param {number} expandOffset
1195      * @param {number} index
1196      * @param {!WebInspector.TimelineUIUtils} uiUtils
1197      */
1198     update: function(presentationRecord, calculator, expandOffset, index, uiUtils)
1199     {
1200         this._record = presentationRecord;
1201         var record = presentationRecord.record();
1202         this.element.className = "timeline-graph-side timeline-category-" + uiUtils.categoryForRecord(record).name;
1203         if (record.thread() !== WebInspector.TimelineModel.MainThreadName)
1204             this.element.classList.add("background");
1205
1206         var barPosition = calculator.computeBarGraphWindowPosition(presentationRecord);
1207         this._barElement.style.left = barPosition.left + "px";
1208         this._barElement.style.width = barPosition.width + "px";
1209         this._barCpuElement.style.left = barPosition.left + "px";
1210         this._barCpuElement.style.width = barPosition.cpuWidth + "px";
1211         this._expandElement._update(presentationRecord, index, barPosition.left - expandOffset, barPosition.width);
1212         this._record.setGraphRow(this);
1213     },
1214
1215     /**
1216      * @param {!Event} event
1217      */
1218     _onClick: function(event)
1219     {
1220         // check if we click arrow and expand if yes.
1221         if (this._expandElement._arrow.containsEventPoint(event))
1222             this._expand();
1223         this._selectRecord(this._record);
1224     },
1225
1226     /**
1227      * @param {boolean} selected
1228      */
1229     renderAsSelected: function(selected)
1230     {
1231         this.element.classList.toggle("selected", selected);
1232     },
1233
1234     _expand: function()
1235     {
1236         this._record.setCollapsed(!this._record.collapsed());
1237         this._scheduleRefresh();
1238     },
1239
1240     /**
1241      * @param {!Event} event
1242      */
1243     _onMouseOver: function(event)
1244     {
1245         this.element.classList.add("hovered");
1246         if (this._record.listRow())
1247             this._record.listRow().element.classList.add("hovered");
1248     },
1249
1250     /**
1251      * @param {!Event} event
1252      */
1253     _onMouseLeave: function(event)
1254     {
1255         this.element.classList.remove("hovered");
1256         if (this._record.listRow())
1257             this._record.listRow().element.classList.remove("hovered");
1258     },
1259
1260     dispose: function()
1261     {
1262         this.element.remove();
1263         this._expandElement._dispose();
1264     }
1265 }
1266
1267 /**
1268  * @constructor
1269  */
1270 WebInspector.TimelineExpandableElement = function(container)
1271 {
1272     this._element = container.createChild("div", "timeline-expandable");
1273     this._element.createChild("div", "timeline-expandable-left");
1274     this._arrow = this._element.createChild("div", "timeline-expandable-arrow");
1275 }
1276
1277 WebInspector.TimelineExpandableElement.prototype = {
1278     /**
1279      * @param {!WebInspector.TimelinePresentationModel.Record} record
1280      * @param {number} index
1281      * @param {number} left
1282      * @param {number} width
1283      */
1284     _update: function(record, index, left, width)
1285     {
1286         const rowHeight = WebInspector.TimelinePanel.rowHeight;
1287         if (record.visibleChildrenCount() || record.expandable()) {
1288             this._element.style.top = index * rowHeight + "px";
1289             this._element.style.left = left + "px";
1290             this._element.style.width = Math.max(12, width + 25) + "px";
1291             if (!record.collapsed()) {
1292                 this._element.style.height = (record.visibleChildrenCount() + 1) * rowHeight + "px";
1293                 this._element.classList.add("timeline-expandable-expanded");
1294                 this._element.classList.remove("timeline-expandable-collapsed");
1295             } else {
1296                 this._element.style.height = rowHeight + "px";
1297                 this._element.classList.add("timeline-expandable-collapsed");
1298                 this._element.classList.remove("timeline-expandable-expanded");
1299             }
1300             this._element.classList.remove("hidden");
1301         } else {
1302             this._element.classList.add("hidden");
1303         }
1304     },
1305
1306     _dispose: function()
1307     {
1308         this._element.remove();
1309     }
1310 }