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