Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / HeapSnapshotView.js
1 /*
2  * Copyright (C) 2011 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  * @extends {WebInspector.View}
34  * @param {!WebInspector.HeapProfileHeader} profile
35  */
36 WebInspector.HeapSnapshotView = function(profile)
37 {
38     WebInspector.View.call(this);
39
40     this.element.classList.add("heap-snapshot-view");
41
42     profile.profileType().addEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceivSnapshot, this);
43     profile.profileType().addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this);
44
45     if (profile._profileType.id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) {
46         this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile);
47         this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this));
48         this._trackingOverviewGrid.show(this.element);
49     }
50
51     this.viewsContainer = new WebInspector.SplitView(false, true, "heapSnapshotRetainersViewSize", 200, 200);
52     this.viewsContainer.show(this.element);
53     this.viewsContainer.setMainElementConstraints(50, 50);
54     this.viewsContainer.setSidebarElementConstraints(70, 70);
55
56     this.containmentView = new WebInspector.View();
57     this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
58     this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
59     this.containmentDataGrid.show(this.containmentView.element);
60     this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
61
62     this.constructorsView = new WebInspector.View();
63
64     this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
65     this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
66     this.constructorsDataGrid.show(this.constructorsView.element);
67     this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
68
69     this.dataGrid = /** @type {!WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid);
70     this.currentView = this.constructorsView;
71     this.currentView.show(this.viewsContainer.mainElement());
72
73     this.diffView = new WebInspector.View();
74
75     this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
76     this.diffDataGrid.show(this.diffView.element);
77     this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
78
79     this.dominatorView = new WebInspector.View();
80     this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
81     this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
82     this.dominatorDataGrid.show(this.dominatorView.element);
83     this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
84
85     if (WebInspector.experimentsSettings.allocationProfiler.isEnabled() && profile.profileType() === WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType) {
86         this.allocationView = new WebInspector.View();
87         this.allocationDataGrid = new WebInspector.AllocationDataGrid();
88         this.allocationDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
89         this.allocationDataGrid.show(this.allocationView.element);
90         this.allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
91     }
92
93     this.retainmentViewHeader = document.createElementWithClass("div", "retainers-view-header");
94     var retainingPathsTitleDiv = this.retainmentViewHeader.createChild("div", "title");
95     var retainingPathsTitle = retainingPathsTitleDiv.createChild("span");
96     retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree");
97     this.viewsContainer.hideDefaultResizer();
98     this.viewsContainer.installResizer(this.retainmentViewHeader);
99
100     this.retainmentView = new WebInspector.View();
101     this.retainmentView.element.classList.add("retaining-paths-view");
102     this.retainmentView.element.appendChild(this.retainmentViewHeader);
103     this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid();
104     this.retainmentDataGrid.show(this.retainmentView.element);
105     this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
106     this.retainmentView.show(this.viewsContainer.sidebarElement());
107     this.retainmentDataGrid.reset();
108
109     this.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this));
110
111     this.views = [{title: WebInspector.UIString("Summary"), view: this.constructorsView, grid: this.constructorsDataGrid}];
112
113     if (profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType)
114         this.views.push({title: WebInspector.UIString("Comparison"), view: this.diffView, grid: this.diffDataGrid});
115     this.views.push({title: WebInspector.UIString("Containment"), view: this.containmentView, grid: this.containmentDataGrid});
116     if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get())
117         this.views.push({title: WebInspector.UIString("Dominators"), view: this.dominatorView, grid: this.dominatorDataGrid});
118     if (this.allocationView)
119         this.views.push({title: WebInspector.UIString("Allocation"), view: this.allocationView, grid: this.allocationDataGrid});
120     this.views.current = 0;
121     for (var i = 0; i < this.views.length; ++i)
122         this.viewSelect.createOption(WebInspector.UIString(this.views[i].title));
123
124     this._profile = profile;
125
126     this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this));
127     this._updateBaseOptions();
128
129     this._filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this));
130     this._updateFilterOptions();
131
132     this._classNameFilter = new WebInspector.StatusBarInput("Class filter");
133     this._classNameFilter.setOnChangeHandler(this._onClassFilterChanged.bind(this));
134
135     this.selectedSizeText = new WebInspector.StatusBarText("");
136
137     this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
138
139     this._updateSelectorsVisibility();
140     this._refreshView();
141 }
142
143 WebInspector.HeapSnapshotView.prototype = {
144     _refreshView: function()
145     {
146         this._profile.load(profileCallback.bind(this));
147
148         /**
149          * @this {WebInspector.HeapSnapshotView}
150          */
151         function profileCallback(heapSnapshotProxy)
152         {
153             var list = this._profiles();
154             var profileIndex = list.indexOf(this._profile);
155             this.baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1));
156             this.dataGrid.setDataSource(heapSnapshotProxy);
157             if (this._trackingOverviewGrid)
158                 this._trackingOverviewGrid._updateGrid();
159         }
160     },
161
162     _onIdsRangeChanged: function(event)
163     {
164         var minId = event.data.minId;
165         var maxId = event.data.maxId;
166         this.selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size)));
167         if (this.constructorsDataGrid.snapshot)
168             this.constructorsDataGrid.setSelectionRange(minId, maxId);
169     },
170
171     get statusBarItems()
172     {
173         var result = [this.viewSelect.element, this._classNameFilter.element];
174         if (this._profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType)
175             result.push(this.baseSelect.element, this._filterSelect.element);
176         result.push(this.selectedSizeText.element);
177         return result;
178     },
179
180     wasShown: function()
181     {
182         // FIXME: load base and current snapshots in parallel
183         this._profile.load(profileCallback.bind(this));
184
185         /**
186          * @this {WebInspector.HeapSnapshotView}
187          */
188         function profileCallback() {
189             this._profile._wasShown();
190             if (this._baseProfile)
191                 this._baseProfile.load(function() { });
192         }
193     },
194
195     willHide: function()
196     {
197         this._currentSearchResultIndex = -1;
198         this._popoverHelper.hidePopover();
199         if (this.helpPopover && this.helpPopover.isShowing())
200             this.helpPopover.hide();
201     },
202
203     searchCanceled: function()
204     {
205         if (this._searchResults) {
206             for (var i = 0; i < this._searchResults.length; ++i) {
207                 var node = this._searchResults[i].node;
208                 delete node._searchMatched;
209                 node.refresh();
210             }
211         }
212
213         delete this._searchFinishedCallback;
214         this._currentSearchResultIndex = -1;
215         this._searchResults = [];
216     },
217
218     /**
219      * @param {string} query
220      * @param {function(!WebInspector.View, number)} finishedCallback
221      */
222     performSearch: function(query, finishedCallback)
223     {
224         // Call searchCanceled since it will reset everything we need before doing a new search.
225         this.searchCanceled();
226
227         query = query.trim();
228
229         if (!query)
230             return;
231         if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
232             return;
233
234         /**
235          * @param {boolean} found
236          * @this {WebInspector.HeapSnapshotView}
237          */
238         function didHighlight(found)
239         {
240             finishedCallback(this, found ? 1 : 0);
241         }
242
243         if (query.charAt(0) === "@") {
244             var snapshotNodeId = parseInt(query.substring(1), 10);
245             if (!isNaN(snapshotNodeId))
246                 this.dataGrid.highlightObjectByHeapSnapshotId(String(snapshotNodeId), didHighlight.bind(this));
247             else
248                 finishedCallback(this, 0);
249             return;
250         }
251
252         this._searchFinishedCallback = finishedCallback;
253         var nameRegExp = createPlainTextSearchRegex(query, "i");
254
255         function matchesByName(gridNode) {
256             return ("_name" in gridNode) && nameRegExp.test(gridNode._name);
257         }
258
259         function matchesQuery(gridNode)
260         {
261             delete gridNode._searchMatched;
262             if (matchesByName(gridNode)) {
263                 gridNode._searchMatched = true;
264                 gridNode.refresh();
265                 return true;
266             }
267             return false;
268         }
269
270         var current = this.dataGrid.rootNode().children[0];
271         var depth = 0;
272         var info = {};
273
274         // Restrict to type nodes and instances.
275         const maxDepth = 1;
276
277         while (current) {
278             if (matchesQuery(current))
279                 this._searchResults.push({ node: current });
280             current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
281             depth += info.depthChange;
282         }
283
284         finishedCallback(this, this._searchResults.length);
285     },
286
287     jumpToFirstSearchResult: function()
288     {
289         if (!this._searchResults || !this._searchResults.length)
290             return;
291         this._currentSearchResultIndex = 0;
292         this._jumpToSearchResult(this._currentSearchResultIndex);
293     },
294
295     jumpToLastSearchResult: function()
296     {
297         if (!this._searchResults || !this._searchResults.length)
298             return;
299         this._currentSearchResultIndex = (this._searchResults.length - 1);
300         this._jumpToSearchResult(this._currentSearchResultIndex);
301     },
302
303     jumpToNextSearchResult: function()
304     {
305         if (!this._searchResults || !this._searchResults.length)
306             return;
307         if (++this._currentSearchResultIndex >= this._searchResults.length)
308             this._currentSearchResultIndex = 0;
309         this._jumpToSearchResult(this._currentSearchResultIndex);
310     },
311
312     jumpToPreviousSearchResult: function()
313     {
314         if (!this._searchResults || !this._searchResults.length)
315             return;
316         if (--this._currentSearchResultIndex < 0)
317             this._currentSearchResultIndex = (this._searchResults.length - 1);
318         this._jumpToSearchResult(this._currentSearchResultIndex);
319     },
320
321     /**
322      * @return {boolean}
323      */
324     showingFirstSearchResult: function()
325     {
326         return (this._currentSearchResultIndex === 0);
327     },
328
329     /**
330      * @return {boolean}
331      */
332     showingLastSearchResult: function()
333     {
334         return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
335     },
336
337     /**
338      * @return {number}
339      */
340     currentSearchResultIndex: function() {
341         return this._currentSearchResultIndex;
342     },
343
344     _jumpToSearchResult: function(index)
345     {
346         var searchResult = this._searchResults[index];
347         if (!searchResult)
348             return;
349
350         var node = searchResult.node;
351         node.revealAndSelect();
352     },
353
354     refreshVisibleData: function()
355     {
356         var child = this.dataGrid.rootNode().children[0];
357         while (child) {
358             child.refresh();
359             child = child.traverseNextNode(false, null, true);
360         }
361     },
362
363     _changeBase: function()
364     {
365         if (this._baseProfile=== this._profiles()[this.baseSelect.selectedIndex()])
366             return;
367
368         this._baseProfile = this._profiles()[this.baseSelect.selectedIndex()];
369         var dataGrid = /** @type {!WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid);
370         // Change set base data source only if main data source is already set.
371         if (dataGrid.snapshot)
372             this._baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
373
374         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
375             return;
376
377         // The current search needs to be performed again. First negate out previous match
378         // count by calling the search finished callback with a negative number of matches.
379         // Then perform the search again with the same query and callback.
380         this._searchFinishedCallback(this, -this._searchResults.length);
381         this.performSearch(this.currentQuery, this._searchFinishedCallback);
382     },
383
384     _changeFilter: function()
385     {
386         var profileIndex = this._filterSelect.selectedIndex() - 1;
387         this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
388
389         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
390             action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
391             label: this._filterSelect.selectedOption().label
392         });
393
394         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
395             return;
396
397         // The current search needs to be performed again. First negate out previous match
398         // count by calling the search finished callback with a negative number of matches.
399         // Then perform the search again with the same query and callback.
400         this._searchFinishedCallback(this, -this._searchResults.length);
401         this.performSearch(this.currentQuery, this._searchFinishedCallback);
402     },
403
404     /**
405      * @param {string} value
406      */
407     _onClassFilterChanged: function(value)
408     {
409         this.dataGrid.changeNameFilter(value);
410     },
411
412     /**
413      * @return {!Array.<!WebInspector.ProfileHeader>}
414      */
415     _profiles: function()
416     {
417         return this._profile.profileType().getProfiles();
418     },
419
420     /**
421      * @param {!WebInspector.ContextMenu} contextMenu
422      * @param {?Event} event
423      */
424     populateContextMenu: function(contextMenu, event)
425     {
426         this.dataGrid.populateContextMenu(contextMenu, event);
427     },
428
429     _selectionChanged: function(event)
430     {
431         var selectedNode = event.target.selectedNode;
432         this._setRetainmentDataGridSource(selectedNode);
433         this._inspectedObjectChanged(event);
434     },
435
436     _inspectedObjectChanged: function(event)
437     {
438         var selectedNode = event.target.selectedNode;
439         if (!this._profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
440             ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId);
441     },
442
443     _setRetainmentDataGridSource: function(nodeItem)
444     {
445         if (nodeItem && nodeItem.snapshotNodeIndex)
446             this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex);
447         else
448             this.retainmentDataGrid.reset();
449     },
450
451     _mouseDownInContentsGrid: function(event)
452     {
453         if (event.detail < 2)
454             return;
455
456         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
457         if (!cell || (!cell.classList.contains("count-column") && !cell.classList.contains("shallowSize-column") && !cell.classList.contains("retainedSize-column")))
458             return;
459
460         event.consume(true);
461     },
462
463     changeView: function(viewTitle, callback)
464     {
465         var viewIndex = null;
466         for (var i = 0; i < this.views.length; ++i) {
467             if (this.views[i].title === viewTitle) {
468                 viewIndex = i;
469                 break;
470             }
471         }
472         if (this.views.current === viewIndex || viewIndex == null) {
473             setTimeout(callback, 0);
474             return;
475         }
476
477         /**
478          * @this {WebInspector.HeapSnapshotView}
479          */
480         function dataGridContentShown(event)
481         {
482             var dataGrid = event.data;
483             dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
484             if (dataGrid === this.dataGrid)
485                 callback();
486         }
487         this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
488
489         this.viewSelect.setSelectedIndex(viewIndex);
490         this._changeView(viewIndex);
491     },
492
493     _updateDataSourceAndView: function()
494     {
495         var dataGrid = this.dataGrid;
496         if (dataGrid.snapshot)
497             return;
498
499         this._profile.load(didLoadSnapshot.bind(this));
500
501         /**
502          * @this {WebInspector.HeapSnapshotView}
503          */
504         function didLoadSnapshot(snapshotProxy)
505         {
506             if (this.dataGrid !== dataGrid)
507                 return;
508             if (dataGrid.snapshot !== snapshotProxy)
509                 dataGrid.setDataSource(snapshotProxy);
510             if (dataGrid === this.diffDataGrid) {
511                 if (!this._baseProfile)
512                     this._baseProfile = this._profiles()[this.baseSelect.selectedIndex()];
513                 this._baseProfile.load(didLoadBaseSnaphot.bind(this));
514             }
515         }
516
517         /**
518          * @this {WebInspector.HeapSnapshotView}
519          */
520         function didLoadBaseSnaphot(baseSnapshotProxy)
521         {
522             if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy)
523                 this.diffDataGrid.setBaseDataSource(baseSnapshotProxy);
524         }
525     },
526
527     _onSelectedViewChanged: function(event)
528     {
529         this._changeView(event.target.selectedIndex);
530     },
531
532     _updateSelectorsVisibility: function()
533     {
534         this.baseSelect.visible = (this.currentView === this.diffView);
535         this._filterSelect.visible = (this.currentView === this.constructorsView);
536         this._classNameFilter.visible = (this.currentView === this.constructorsView || this.currentView === this.diffView);
537
538         if (this._trackingOverviewGrid) {
539             this._trackingOverviewGrid.element.classList.toggle("hidden", this.currentView !== this.constructorsView);
540             if (this.currentView === this.constructorsView)
541                 this._trackingOverviewGrid.update();
542         }
543     },
544
545     _changeView: function(selectedIndex)
546     {
547         if (selectedIndex === this.views.current)
548             return;
549
550         this.views.current = selectedIndex;
551         this.currentView.detach();
552         var view = this.views[this.views.current];
553         this.currentView = view.view;
554         this.dataGrid = view.grid;
555         this.currentView.show(this.viewsContainer.mainElement());
556         this.refreshVisibleData();
557         this.dataGrid.updateWidths();
558
559         this._updateSelectorsVisibility();
560
561         this._updateDataSourceAndView();
562
563         if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
564             return;
565
566         // The current search needs to be performed again. First negate out previous match
567         // count by calling the search finished callback with a negative number of matches.
568         // Then perform the search again the with same query and callback.
569         this._searchFinishedCallback(this, -this._searchResults.length);
570         this.performSearch(this.currentQuery, this._searchFinishedCallback);
571     },
572
573     _getHoverAnchor: function(target)
574     {
575         var span = target.enclosingNodeOrSelfWithNodeName("span");
576         if (!span)
577             return;
578         var row = target.enclosingNodeOrSelfWithNodeName("tr");
579         if (!row)
580             return;
581         span.node = row._dataGridNode;
582         return span;
583     },
584
585     _resolveObjectForPopover: function(element, showCallback, objectGroupName)
586     {
587         if (this._profile.fromFile())
588             return;
589         element.node.queryObjectContent(showCallback, objectGroupName);
590     },
591
592     _updateBaseOptions: function()
593     {
594         var list = this._profiles();
595         // We're assuming that snapshots can only be added.
596         if (this.baseSelect.size() === list.length)
597             return;
598
599         for (var i = this.baseSelect.size(), n = list.length; i < n; ++i) {
600             var title = list[i].title;
601             this.baseSelect.createOption(title);
602         }
603     },
604
605     _updateFilterOptions: function()
606     {
607         var list = this._profiles();
608         // We're assuming that snapshots can only be added.
609         if (this._filterSelect.size() - 1 === list.length)
610             return;
611
612         if (!this._filterSelect.size())
613             this._filterSelect.createOption(WebInspector.UIString("All objects"));
614
615         for (var i = this._filterSelect.size() - 1, n = list.length; i < n; ++i) {
616             var title = list[i].title;
617             if (!i)
618                 title = WebInspector.UIString("Objects allocated before %s", title);
619             else
620                 title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title);
621             this._filterSelect.createOption(title);
622         }
623     },
624
625     _updateControls: function()
626     {
627         this._updateBaseOptions();
628         this._updateFilterOptions();
629     },
630
631     /**
632      * @param {!WebInspector.Event} event
633      */
634     _onReceivSnapshot: function(event)
635     {
636         this._updateControls();
637     },
638
639     /**
640      * @param {!WebInspector.Event} event
641      */
642     _onProfileHeaderRemoved: function(event)
643     {
644         var profile = event.data;
645         if (this._profile === profile) {
646             this.detach();
647             this._profile.profileType().removeEventListener(WebInspector.ProfileType.Events.AddProfileHeader, this._onReceivSnapshot, this);
648             this._profile.profileType().removeEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this);
649         } else {
650             this._updateControls();
651         }
652     },
653
654     __proto__: WebInspector.View.prototype
655 }
656
657 /**
658  * @constructor
659  * @implements {HeapProfilerAgent.Dispatcher}
660  */
661 WebInspector.HeapProfilerDispatcher = function()
662 {
663     this._dispatchers = [];
664     InspectorBackend.registerHeapProfilerDispatcher(this);
665 }
666
667 WebInspector.HeapProfilerDispatcher.prototype = {
668     /**
669      * @param {!HeapProfilerAgent.Dispatcher} dispatcher
670      */
671     register: function(dispatcher)
672     {
673         this._dispatchers.push(dispatcher);
674     },
675
676     _genericCaller: function(eventName)
677     {
678         var args = Array.prototype.slice.call(arguments.callee.caller.arguments);
679         for (var i = 0; i < this._dispatchers.length; ++i)
680             this._dispatchers[i][eventName].apply(this._dispatchers[i], args);
681     },
682
683     /**
684      * @override
685      * @param {!Array.<number>} samples
686      */
687     heapStatsUpdate: function(samples)
688     {
689         this._genericCaller("heapStatsUpdate");
690     },
691
692     /**
693      * @override
694      * @param {number} lastSeenObjectId
695      * @param {number} timestamp
696      */
697     lastSeenObjectId: function(lastSeenObjectId, timestamp)
698     {
699         this._genericCaller("lastSeenObjectId");
700     },
701
702     /**
703      * @override
704      * @param {string} chunk
705      */
706     addHeapSnapshotChunk: function(chunk)
707     {
708         this._genericCaller("addHeapSnapshotChunk");
709     },
710
711     /**
712      * @override
713      * @param {number} done
714      * @param {number} total
715      * @param {boolean=} finished
716      */
717     reportHeapSnapshotProgress: function(done, total, finished)
718     {
719         this._genericCaller("reportHeapSnapshotProgress");
720     },
721
722     /**
723      * @override
724      */
725     resetProfiles: function()
726     {
727         this._genericCaller("resetProfiles");
728     }
729 }
730
731 WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher();
732
733 /**
734  * @constructor
735  * @extends {WebInspector.ProfileType}
736  * @implements {HeapProfilerAgent.Dispatcher}
737  * @param {string=} id
738  * @param {string=} title
739  */
740 WebInspector.HeapSnapshotProfileType = function(id, title)
741 {
742     WebInspector.ProfileType.call(this, id || WebInspector.HeapSnapshotProfileType.TypeId, title || WebInspector.UIString("Take Heap Snapshot"));
743     WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
744 }
745
746 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
747 WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived";
748
749 WebInspector.HeapSnapshotProfileType.prototype = {
750     /**
751      * @override
752      * @return {string}
753      */
754     fileExtension: function()
755     {
756         return ".heapsnapshot";
757     },
758
759     get buttonTooltip()
760     {
761         return WebInspector.UIString("Take heap snapshot.");
762     },
763
764     /**
765      * @override
766      * @return {boolean}
767      */
768     isInstantProfile: function()
769     {
770         return true;
771     },
772
773     /**
774      * @override
775      * @return {boolean}
776      */
777     buttonClicked: function()
778     {
779         this._takeHeapSnapshot(function() {});
780         WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
781         return false;
782     },
783
784     /**
785      * @override
786      * @param {!Array.<number>} samples
787      */
788     heapStatsUpdate: function(samples)
789     {
790     },
791
792     /**
793      * @override
794      * @param {number} lastSeenObjectId
795      * @param {number} timestamp
796      */
797     lastSeenObjectId: function(lastSeenObjectId, timestamp)
798     {
799     },
800
801     get treeItemTitle()
802     {
803         return WebInspector.UIString("HEAP SNAPSHOTS");
804     },
805
806     get description()
807     {
808         return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes.");
809     },
810
811     /**
812      * @override
813      * @param {!string} title
814      * @return {!WebInspector.ProfileHeader}
815      */
816     createProfileLoadedFromFile: function(title)
817     {
818         return new WebInspector.HeapProfileHeader(this, title);
819     },
820
821     _takeHeapSnapshot: function(callback)
822     {
823         if (this.profileBeingRecorded())
824             return;
825         this._profileBeingRecorded = new WebInspector.HeapProfileHeader(this);
826         this.addProfile(this._profileBeingRecorded);
827         this._profileBeingRecorded.updateStatus(WebInspector.UIString("Snapshotting\u2026"));
828
829         /**
830          * @param {?string} error
831          * @this {WebInspector.HeapSnapshotProfileType}
832          */
833         function didTakeHeapSnapshot(error)
834         {
835             var profile = this._profileBeingRecorded;
836             profile.title = WebInspector.UIString("Snapshot %d", profile.uid);
837             profile._finishLoad();
838             this._profileBeingRecorded = null;
839             WebInspector.panels.profiles.showProfile(profile);
840             callback();
841         }
842         HeapProfilerAgent.takeHeapSnapshot(true, didTakeHeapSnapshot.bind(this));
843     },
844
845     /**
846      * @override
847      * @param {string} chunk
848      */
849     addHeapSnapshotChunk: function(chunk)
850     {
851         if (!this.profileBeingRecorded())
852             return;
853         this.profileBeingRecorded().transferChunk(chunk);
854     },
855
856     /**
857      * @override
858      * @param {number} done
859      * @param {number} total
860      * @param {boolean=} finished
861      */
862     reportHeapSnapshotProgress: function(done, total, finished)
863     {
864         var profile = this.profileBeingRecorded();
865         if (!profile)
866             return;
867         profile.updateStatus(WebInspector.UIString("%.0f%", (done / total) * 100), true);
868         if (finished)
869             profile._prepareToLoad();
870     },
871
872     /**
873      * @override
874      */
875     resetProfiles: function()
876     {
877         this._reset();
878     },
879
880     _snapshotReceived: function(profile)
881     {
882         if (this._profileBeingRecorded === profile)
883             this._profileBeingRecorded = null;
884         this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile);
885     },
886
887     __proto__: WebInspector.ProfileType.prototype
888 }
889
890
891 /**
892  * @constructor
893  * @extends {WebInspector.HeapSnapshotProfileType}
894  */
895 WebInspector.TrackingHeapSnapshotProfileType = function()
896 {
897     WebInspector.HeapSnapshotProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations"));
898 }
899
900 WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD";
901
902 WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate";
903 WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted";
904 WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped";
905
906 WebInspector.TrackingHeapSnapshotProfileType.prototype = {
907
908     /**
909      * @override
910      * @param {!Array.<number>} samples
911      */
912     heapStatsUpdate: function(samples)
913     {
914         if (!this._profileSamples)
915             return;
916         var index;
917         for (var i = 0; i < samples.length; i += 3) {
918             index = samples[i];
919             var count = samples[i+1];
920             var size  = samples[i+2];
921             this._profileSamples.sizes[index] = size;
922             if (!this._profileSamples.max[index] || size > this._profileSamples.max[index])
923                 this._profileSamples.max[index] = size;
924         }
925         this._lastUpdatedIndex = index;
926     },
927
928     /**
929      * @override
930      * @param {number} lastSeenObjectId
931      * @param {number} timestamp
932      */
933     lastSeenObjectId: function(lastSeenObjectId, timestamp)
934     {
935         var profileSamples = this._profileSamples;
936         if (!profileSamples)
937             return;
938         var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
939         profileSamples.ids[currentIndex] = lastSeenObjectId;
940         if (!profileSamples.max[currentIndex]) {
941             profileSamples.max[currentIndex] = 0;
942             profileSamples.sizes[currentIndex] = 0;
943         }
944         profileSamples.timestamps[currentIndex] = timestamp;
945         if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0])
946             profileSamples.totalTime *= 2;
947         this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
948         this._profileBeingRecorded.updateStatus(null, true);
949     },
950
951     /**
952      * @override
953      * @return {boolean}
954      */
955     hasTemporaryView: function()
956     {
957         return true;
958     },
959
960     get buttonTooltip()
961     {
962         return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile.");
963     },
964
965     /**
966      * @override
967      * @return {boolean}
968      */
969     isInstantProfile: function()
970     {
971         return false;
972     },
973
974     /**
975      * @override
976      * @return {boolean}
977      */
978     buttonClicked: function()
979     {
980         return this._toggleRecording();
981     },
982
983     _startRecordingProfile: function()
984     {
985         if (this.profileBeingRecorded())
986             return;
987         this._profileBeingRecorded = new WebInspector.HeapProfileHeader(this);
988         this._lastSeenIndex = -1;
989         this._profileSamples = {
990             'sizes': [],
991             'ids': [],
992             'timestamps': [],
993             'max': [],
994             'totalTime': 30000
995         };
996         this._profileBeingRecorded._profileSamples = this._profileSamples;
997         this._recording = true;
998         this.addProfile(this._profileBeingRecorded);
999         this._profileBeingRecorded.updateStatus(WebInspector.UIString("Recording\u2026"));
1000         HeapProfilerAgent.startTrackingHeapObjects(WebInspector.experimentsSettings.allocationProfiler.isEnabled());
1001         this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted);
1002     },
1003
1004     _stopRecordingProfile: function()
1005     {
1006         this._profileBeingRecorded.updateStatus(WebInspector.UIString("Snapshotting\u2026"));
1007         /**
1008          * @param {?string} error
1009          * @this {WebInspector.HeapSnapshotProfileType}
1010          */
1011         function didTakeHeapSnapshot(error)
1012         {
1013             var profile = this._profileBeingRecorded;
1014             if (!profile)
1015                 return;
1016             profile._finishLoad();
1017             this._profileSamples = null;
1018             this._profileBeingRecorded = null;
1019             WebInspector.panels.profiles.showProfile(profile);
1020         }
1021
1022         HeapProfilerAgent.stopTrackingHeapObjects(true, didTakeHeapSnapshot.bind(this));
1023         this._recording = false;
1024         this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped);
1025     },
1026
1027     _toggleRecording: function()
1028     {
1029         if (this._recording)
1030             this._stopRecordingProfile();
1031         else
1032             this._startRecordingProfile();
1033         return this._recording;
1034     },
1035
1036     get treeItemTitle()
1037     {
1038         return WebInspector.UIString("HEAP TIMELINES");
1039     },
1040
1041     get description()
1042     {
1043         return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks.");
1044     },
1045
1046     _reset: function()
1047     {
1048         WebInspector.HeapSnapshotProfileType.prototype._reset.call(this);
1049         if (this._recording)
1050             this._stopRecordingProfile();
1051         this._profileSamples = null;
1052         this._lastSeenIndex = -1;
1053     },
1054
1055     /**
1056      * @override
1057      */
1058     profileBeingRecordedRemoved: function()
1059     {
1060         this._stopRecordingProfile();
1061         this._profileSamples = null;
1062     },
1063
1064     __proto__: WebInspector.HeapSnapshotProfileType.prototype
1065 }
1066
1067 /**
1068  * @constructor
1069  * @extends {WebInspector.ProfileHeader}
1070  * @param {!WebInspector.HeapSnapshotProfileType} type
1071  * @param {string=} title
1072  */
1073 WebInspector.HeapProfileHeader = function(type, title)
1074 {
1075     WebInspector.ProfileHeader.call(this, type, title || WebInspector.UIString("Snapshot %d", type._nextProfileUid));
1076     this.maxJSObjectId = -1;
1077     /**
1078      * @type {?WebInspector.HeapSnapshotWorkerProxy}
1079      */
1080     this._workerProxy = null;
1081     /**
1082      * @type {?WebInspector.OutputStream}
1083      */
1084     this._receiver = null;
1085     /**
1086      * @type {?WebInspector.HeapSnapshotProxy}
1087      */
1088     this._snapshotProxy = null;
1089     /**
1090      * @type {?Array.<function(!WebInspector.HeapSnapshotProxy)>}
1091      */
1092     this._loadCallbacks = [];
1093     this._totalNumberOfChunks = 0;
1094     this._bufferedWriter = null;
1095 }
1096
1097 WebInspector.HeapProfileHeader.prototype = {
1098     /**
1099      * @override
1100      * @return {!WebInspector.ProfileSidebarTreeElement}
1101      */
1102     createSidebarTreeElement: function()
1103     {
1104         return new WebInspector.ProfileSidebarTreeElement(this, "heap-snapshot-sidebar-tree-item");
1105     },
1106
1107     /**
1108      * @override
1109      * @return {!WebInspector.HeapSnapshotView}
1110      */
1111     createView: function()
1112     {
1113         return new WebInspector.HeapSnapshotView(this);
1114     },
1115
1116     /**
1117      * @override
1118      * @param {function(!WebInspector.HeapSnapshotProxy):void} callback
1119      */
1120     load: function(callback)
1121     {
1122         if (this.uid === -1)
1123             return;
1124         if (this._snapshotProxy) {
1125             callback(this._snapshotProxy);
1126             return;
1127         }
1128         this._loadCallbacks.push(callback);
1129     },
1130
1131     _prepareToLoad: function()
1132     {
1133         console.assert(!this._receiver, "Already loading");
1134         this._setupWorker();
1135         this.updateStatus(WebInspector.UIString("Loading\u2026"), true);
1136     },
1137
1138     _finishLoad: function()
1139     {
1140         this._receiver.close(function() {});
1141         if (this._bufferedWriter) {
1142             this._bufferedWriter.close(this._didWriteToTempFile.bind(this));
1143             this._bufferedWriter = null;
1144         }
1145     },
1146
1147     _didWriteToTempFile: function(tempFile)
1148     {
1149         this._tempFile = tempFile;
1150         if (!tempFile)
1151             this._failedToCreateTempFile = true;
1152         if (this._onTempFileReady) {
1153             this._onTempFileReady();
1154             this._onTempFileReady = null;
1155         }
1156     },
1157
1158     _setupWorker: function()
1159     {
1160         /**
1161          * @this {WebInspector.HeapProfileHeader}
1162          */
1163         function setProfileWait(event)
1164         {
1165             this.updateStatus(null, event.data);
1166         }
1167         console.assert(!this._workerProxy, "HeapSnapshotWorkerProxy already exists");
1168         this._workerProxy = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this));
1169         this._workerProxy.addEventListener("wait", setProfileWait, this);
1170         this._receiver = this._workerProxy.createLoader(this.uid, this._snapshotReceived.bind(this));
1171     },
1172
1173     /**
1174      * @param {string} eventName
1175      * @param {*} data
1176      */
1177     _handleWorkerEvent: function(eventName, data)
1178     {
1179         if (WebInspector.HeapSnapshotProgressEvent.Update !== eventName)
1180             return;
1181         var subtitle = /** @type {string} */ (data);
1182         this.updateStatus(subtitle);
1183     },
1184
1185     /**
1186      * @override
1187      */
1188     dispose: function()
1189     {
1190         if (this._workerProxy)
1191             this._workerProxy.dispose();
1192         this.removeTempFile();
1193         this._wasDisposed = true;
1194     },
1195
1196     _didCompleteSnapshotTransfer: function()
1197     {
1198         if (!this._snapshotProxy)
1199             return;
1200         this.updateStatus(Number.bytesToString(this._snapshotProxy.totalSize), false);
1201     },
1202
1203     /**
1204      * @param {string} chunk
1205      */
1206     transferChunk: function(chunk)
1207     {
1208         if (!this._bufferedWriter)
1209             this._bufferedWriter = new WebInspector.BufferedTempFileWriter("heap-profiler", this.uid);
1210         this._bufferedWriter.write(chunk);
1211
1212         ++this._totalNumberOfChunks;
1213         this._receiver.write(chunk, function() {});
1214     },
1215
1216     _snapshotReceived: function(snapshotProxy)
1217     {
1218         if (this._wasDisposed)
1219             return;
1220         this._receiver = null;
1221         this._snapshotProxy = snapshotProxy;
1222         this.maxJSObjectId = snapshotProxy.maxJSObjectId();
1223         this._didCompleteSnapshotTransfer();
1224         this._workerProxy.startCheckingForLongRunningCalls();
1225         this.notifySnapshotReceived();
1226     },
1227
1228     notifySnapshotReceived: function()
1229     {
1230         for (var i = 0; i < this._loadCallbacks.length; i++)
1231             this._loadCallbacks[i](this._snapshotProxy);
1232         this._loadCallbacks = null;
1233         this._profileType._snapshotReceived(this);
1234         if (this.canSaveToFile())
1235             this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.ProfileReceived);
1236     },
1237
1238     // Hook point for tests.
1239     _wasShown: function()
1240     {
1241     },
1242
1243     /**
1244      * @override
1245      * @return {boolean}
1246      */
1247     canSaveToFile: function()
1248     {
1249         return !this.fromFile() && this._snapshotProxy;
1250     },
1251
1252     /**
1253      * @override
1254      */
1255     saveToFile: function()
1256     {
1257         var fileOutputStream = new WebInspector.FileOutputStream();
1258
1259         /**
1260          * @param {boolean} accepted
1261          * @this {WebInspector.HeapProfileHeader}
1262          */
1263         function onOpen(accepted)
1264         {
1265             if (!accepted)
1266                 return;
1267             if (this._failedToCreateTempFile) {
1268                 WebInspector.log("Failed to open temp file with heap snapshot",
1269                                  WebInspector.ConsoleMessage.MessageLevel.Error);
1270                 fileOutputStream.close();
1271             } else if (this._tempFile) {
1272                 var delegate = new WebInspector.SaveSnapshotOutputStreamDelegate(this);
1273                 this._tempFile.writeToOutputSteam(fileOutputStream, delegate);
1274             } else {
1275                 this._onTempFileReady = onOpen.bind(this, accepted);
1276                 this._updateSaveProgress(0, 1);
1277             }
1278         }
1279         this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
1280         fileOutputStream.open(this._fileName, onOpen.bind(this));
1281     },
1282
1283     _updateSaveProgress: function(value, total)
1284     {
1285         var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0);
1286         this.updateStatus(WebInspector.UIString("Saving\u2026 %d\%", percentValue));
1287     },
1288
1289     /**
1290      * @override
1291      * @param {!File} file
1292      */
1293     loadFromFile: function(file)
1294     {
1295         this.updateStatus(WebInspector.UIString("Loading\u2026"), true);
1296         this._setupWorker();
1297         var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
1298         var fileReader = this._createFileReader(file, delegate);
1299         fileReader.start(this._receiver);
1300     },
1301
1302     _createFileReader: function(file, delegate)
1303     {
1304         return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
1305     },
1306
1307     __proto__: WebInspector.ProfileHeader.prototype
1308 }
1309
1310 /**
1311  * @constructor
1312  * @implements {WebInspector.OutputStreamDelegate}
1313  */
1314 WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
1315 {
1316     this._snapshotHeader = snapshotHeader;
1317 }
1318
1319 WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
1320     onTransferStarted: function()
1321     {
1322     },
1323
1324     /**
1325      * @param {!WebInspector.ChunkedReader} reader
1326      */
1327     onChunkTransferred: function(reader)
1328     {
1329     },
1330
1331     onTransferFinished: function()
1332     {
1333     },
1334
1335     /**
1336      * @param {!WebInspector.ChunkedReader} reader
1337      */
1338     onError: function (reader, e)
1339     {
1340         var subtitle;
1341         switch(e.target.error.code) {
1342         case e.target.error.NOT_FOUND_ERR:
1343             subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
1344             break;
1345         case e.target.error.NOT_READABLE_ERR:
1346             subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
1347             break;
1348         case e.target.error.ABORT_ERR:
1349             return;
1350         default:
1351             subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
1352         }
1353         this._snapshotHeader.updateStatus(subtitle);
1354     }
1355 }
1356
1357 /**
1358  * @constructor
1359  * @implements {WebInspector.OutputStreamDelegate}
1360  * @param {!WebInspector.HeapProfileHeader} profileHeader
1361  */
1362 WebInspector.SaveSnapshotOutputStreamDelegate = function(profileHeader)
1363 {
1364     this._profileHeader = profileHeader;
1365 }
1366
1367 WebInspector.SaveSnapshotOutputStreamDelegate.prototype = {
1368     onTransferStarted: function()
1369     {
1370         this._profileHeader._updateSaveProgress(0, 1);
1371     },
1372
1373     onTransferFinished: function()
1374     {
1375         this._profileHeader._didCompleteSnapshotTransfer();
1376     },
1377
1378     /**
1379      * @param {!WebInspector.ChunkedReader} reader
1380      */
1381     onChunkTransferred: function(reader)
1382     {
1383         this._profileHeader._updateSaveProgress(reader.loadedSize(), reader.fileSize());
1384     },
1385
1386     /**
1387      * @param {!WebInspector.ChunkedReader} reader
1388      */
1389     onError: function(reader, event)
1390     {
1391         WebInspector.log("Failed to read heap snapshot from temp file: " + event.message,
1392                          WebInspector.ConsoleMessage.MessageLevel.Error);
1393         this.onTransferFinished();
1394     }
1395 }
1396
1397 /**
1398  * @constructor
1399  * @extends {WebInspector.View}
1400  * @param {!WebInspector.HeapProfileHeader} heapProfileHeader
1401  */
1402 WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader)
1403 {
1404     WebInspector.View.call(this);
1405     this.registerRequiredCSS("flameChart.css");
1406     this.element.id = "heap-recording-view";
1407     this.element.classList.add("heap-tracking-overview");
1408
1409     this._overviewContainer = this.element.createChild("div", "overview-container");
1410     this._overviewGrid = new WebInspector.OverviewGrid("heap-recording");
1411     this._overviewGrid.element.classList.add("fill");
1412
1413     this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas");
1414     this._overviewContainer.appendChild(this._overviewGrid.element);
1415     this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator();
1416     this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
1417
1418     this._profileSamples = heapProfileHeader._profileSamples;
1419     if (heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) {
1420         this._profileType = heapProfileHeader._profileType;
1421         this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
1422         this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
1423     }
1424     var timestamps = this._profileSamples.timestamps;
1425     var totalTime = this._profileSamples.totalTime;
1426     this._windowLeft = 0.0;
1427     this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0;
1428     this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
1429     this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
1430     this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
1431 }
1432
1433 WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged";
1434
1435 WebInspector.HeapTrackingOverviewGrid.prototype = {
1436     _onStopTracking: function(event)
1437     {
1438         this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
1439         this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
1440     },
1441
1442     _onHeapStatsUpdate: function(event)
1443     {
1444         this._profileSamples = event.data;
1445         this._scheduleUpdate();
1446     },
1447
1448      /**
1449       * @param {number} width
1450       * @param {number} height
1451       */
1452     _drawOverviewCanvas: function(width, height)
1453     {
1454         if (!this._profileSamples)
1455             return;
1456         var profileSamples = this._profileSamples;
1457         var sizes = profileSamples.sizes;
1458         var topSizes = profileSamples.max;
1459         var timestamps = profileSamples.timestamps;
1460         var startTime = timestamps[0];
1461         var endTime = timestamps[timestamps.length - 1];
1462
1463         var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
1464         var maxSize = 0;
1465         /**
1466           * @param {!Array.<number>} sizes
1467           * @param {function(number, number):void} callback
1468           */
1469         function aggregateAndCall(sizes, callback)
1470         {
1471             var size = 0;
1472             var currentX = 0;
1473             for (var i = 1; i < timestamps.length; ++i) {
1474                 var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
1475                 if (x !== currentX) {
1476                     if (size)
1477                         callback(currentX, size);
1478                     size = 0;
1479                     currentX = x;
1480                 }
1481                 size += sizes[i];
1482             }
1483             callback(currentX, size);
1484         }
1485
1486         /**
1487           * @param {number} x
1488           * @param {number} size
1489           */
1490         function maxSizeCallback(x, size)
1491         {
1492             maxSize = Math.max(maxSize, size);
1493         }
1494
1495         aggregateAndCall(sizes, maxSizeCallback);
1496
1497         var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);
1498
1499         this._overviewCanvas.width = width * window.devicePixelRatio;
1500         this._overviewCanvas.height = height * window.devicePixelRatio;
1501         this._overviewCanvas.style.width = width + "px";
1502         this._overviewCanvas.style.height = height + "px";
1503
1504         var context = this._overviewCanvas.getContext("2d");
1505         context.scale(window.devicePixelRatio, window.devicePixelRatio);
1506
1507         context.beginPath();
1508         context.lineWidth = 2;
1509         context.strokeStyle = "rgba(192, 192, 192, 0.6)";
1510         var currentX = (endTime - startTime) * scaleFactor;
1511         context.moveTo(currentX, height - 1);
1512         context.lineTo(currentX, 0);
1513         context.stroke();
1514         context.closePath();
1515
1516         var gridY;
1517         var gridValue;
1518         var gridLabelHeight = 14;
1519         if (yScaleFactor) {
1520             const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
1521             // The round value calculation is a bit tricky, because
1522             // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
1523             // e.g. a round value 10KB is 10240 bytes.
1524             gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
1525             gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10));
1526             if (gridValue * 5 <= maxGridValue)
1527                 gridValue *= 5;
1528             gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
1529             context.beginPath();
1530             context.lineWidth = 1;
1531             context.strokeStyle = "rgba(0, 0, 0, 0.2)";
1532             context.moveTo(0, gridY);
1533             context.lineTo(width, gridY);
1534             context.stroke();
1535             context.closePath();
1536         }
1537
1538         /**
1539           * @param {number} x
1540           * @param {number} size
1541           */
1542         function drawBarCallback(x, size)
1543         {
1544             context.moveTo(x, height - 1);
1545             context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
1546         }
1547
1548         context.beginPath();
1549         context.lineWidth = 2;
1550         context.strokeStyle = "rgba(192, 192, 192, 0.6)";
1551         aggregateAndCall(topSizes, drawBarCallback);
1552         context.stroke();
1553         context.closePath();
1554
1555         context.beginPath();
1556         context.lineWidth = 2;
1557         context.strokeStyle = "rgba(0, 0, 192, 0.8)";
1558         aggregateAndCall(sizes, drawBarCallback);
1559         context.stroke();
1560         context.closePath();
1561
1562         if (gridValue) {
1563             var label = Number.bytesToString(gridValue);
1564             var labelPadding = 4;
1565             var labelX = 0;
1566             var labelY = gridY - 0.5;
1567             var labelWidth = 2 * labelPadding + context.measureText(label).width;
1568             context.beginPath();
1569             context.textBaseline = "bottom";
1570             context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
1571             context.fillStyle = "rgba(255, 255, 255, 0.75)";
1572             context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
1573             context.fillStyle = "rgb(64, 64, 64)";
1574             context.fillText(label, labelX + labelPadding, labelY);
1575             context.fill();
1576             context.closePath();
1577         }
1578     },
1579
1580     onResize: function()
1581     {
1582         this._updateOverviewCanvas = true;
1583         this._scheduleUpdate();
1584     },
1585
1586     _onWindowChanged: function()
1587     {
1588         if (!this._updateGridTimerId)
1589             this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
1590     },
1591
1592     _scheduleUpdate: function()
1593     {
1594         if (this._updateTimerId)
1595             return;
1596         this._updateTimerId = setTimeout(this.update.bind(this), 10);
1597     },
1598
1599     _updateBoundaries: function()
1600     {
1601         this._windowLeft = this._overviewGrid.windowLeft();
1602         this._windowRight = this._overviewGrid.windowRight();
1603         this._windowWidth = this._windowRight - this._windowLeft;
1604     },
1605
1606     update: function()
1607     {
1608         this._updateTimerId = null;
1609         if (!this.isShowing())
1610             return;
1611         this._updateBoundaries();
1612         this._overviewCalculator._updateBoundaries(this);
1613         this._overviewGrid.updateDividers(this._overviewCalculator);
1614         this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
1615     },
1616
1617     _updateGrid: function()
1618     {
1619         this._updateGridTimerId = 0;
1620         this._updateBoundaries();
1621         var ids = this._profileSamples.ids;
1622         var timestamps = this._profileSamples.timestamps;
1623         var sizes = this._profileSamples.sizes;
1624         var startTime = timestamps[0];
1625         var totalTime = this._profileSamples.totalTime;
1626         var timeLeft = startTime + totalTime * this._windowLeft;
1627         var timeRight = startTime + totalTime * this._windowRight;
1628         var minId = 0;
1629         var maxId = ids[ids.length - 1] + 1;
1630         var size = 0;
1631         for (var i = 0; i < timestamps.length; ++i) {
1632             if (!timestamps[i])
1633                 continue;
1634             if (timestamps[i] > timeRight)
1635                 break;
1636             maxId = ids[i];
1637             if (timestamps[i] < timeLeft) {
1638                 minId = ids[i];
1639                 continue;
1640             }
1641             size += sizes[i];
1642         }
1643
1644         this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size});
1645     },
1646
1647     __proto__: WebInspector.View.prototype
1648 }
1649
1650
1651 /**
1652  * @constructor
1653  */
1654 WebInspector.HeapTrackingOverviewGrid.SmoothScale = function()
1655 {
1656     this._lastUpdate = 0;
1657     this._currentScale = 0.0;
1658 }
1659
1660 WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = {
1661     /**
1662      * @param {number} target
1663      * @return {number}
1664      */
1665     nextScale: function(target) {
1666         target = target || this._currentScale;
1667         if (this._currentScale) {
1668             var now = Date.now();
1669             var timeDeltaMs = now - this._lastUpdate;
1670             this._lastUpdate = now;
1671             var maxChangePerSec = 20;
1672             var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
1673             var scaleChange = target / this._currentScale;
1674             this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
1675         } else
1676             this._currentScale = target;
1677         return this._currentScale;
1678     }
1679 }
1680
1681
1682 /**
1683  * @constructor
1684  * @implements {WebInspector.TimelineGrid.Calculator}
1685  */
1686 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function()
1687 {
1688 }
1689
1690 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = {
1691     /**
1692      * @param {!WebInspector.HeapTrackingOverviewGrid} chart
1693      */
1694     _updateBoundaries: function(chart)
1695     {
1696         this._minimumBoundaries = 0;
1697         this._maximumBoundaries = chart._profileSamples.totalTime;
1698         this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
1699     },
1700
1701     /**
1702      * @param {number} time
1703      * @return {number}
1704      */
1705     computePosition: function(time)
1706     {
1707         return (time - this._minimumBoundaries) * this._xScaleFactor;
1708     },
1709
1710     /**
1711      * @param {number} value
1712      * @param {boolean=} hires
1713      * @return {string}
1714      */
1715     formatTime: function(value, hires)
1716     {
1717         return Number.secondsToString((value + this._minimumBoundaries) / 1000, hires);
1718     },
1719
1720     /**
1721      * @return {number}
1722      */
1723     maximumBoundary: function()
1724     {
1725         return this._maximumBoundaries;
1726     },
1727
1728     /**
1729      * @return {number}
1730      */
1731     minimumBoundary: function()
1732     {
1733         return this._minimumBoundaries;
1734     },
1735
1736     /**
1737      * @return {number}
1738      */
1739     zeroTime: function()
1740     {
1741         return this._minimumBoundaries;
1742     },
1743
1744     /**
1745      * @return {number}
1746      */
1747     boundarySpan: function()
1748     {
1749         return this._maximumBoundaries - this._minimumBoundaries;
1750     }
1751 }