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 * @implements {WebInspector.ProfileType.DataDisplayDelegate}
34 * @extends {WebInspector.VBox}
35 * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
36 * @param {!WebInspector.HeapProfileHeader} profile
37 */
38WebInspector.HeapSnapshotView = function(dataDisplayDelegate, profile)
39{
40    WebInspector.VBox.call(this);
41
42    this.element.classList.add("heap-snapshot-view");
43
44    profile.profileType().addEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this);
45    profile.profileType().addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this);
46
47    if (profile.profileType().id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) {
48        this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile);
49        this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this));
50    }
51
52    this._parentDataDisplayDelegate = dataDisplayDelegate;
53
54    this._splitView = new WebInspector.SplitView(false, true, "heapSnapshotSplitViewState", 200, 200);
55    this._splitView.show(this.element);
56
57    this._containmentView = new WebInspector.VBox();
58    this._containmentView.setMinimumSize(50, 25);
59    this._containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(this);
60    this._containmentDataGrid.show(this._containmentView.element);
61    this._containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
62
63    this._statisticsView = new WebInspector.HeapSnapshotStatisticsView();
64
65    this._constructorsView = new WebInspector.VBox();
66    this._constructorsView.setMinimumSize(50, 25);
67
68    this._constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(this);
69    this._constructorsDataGrid.show(this._constructorsView.element);
70    this._constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
71
72    this._diffView = new WebInspector.VBox();
73    this._diffView.setMinimumSize(50, 25);
74
75    this._diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(this);
76    this._diffDataGrid.show(this._diffView.element);
77    this._diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
78
79    if (profile._hasAllocationStacks) {
80        this._allocationView = new WebInspector.VBox();
81        this._allocationView.setMinimumSize(50, 25);
82        this._allocationDataGrid = new WebInspector.AllocationDataGrid(profile.target() , this);
83        this._allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._onSelectAllocationNode, this);
84        this._allocationDataGrid.show(this._allocationView.element);
85
86        this._allocationStackView = new WebInspector.HeapAllocationStackView(profile.target());
87        this._allocationStackView.setMinimumSize(50, 25);
88
89        this._tabbedPane = new WebInspector.TabbedPane();
90        this._tabbedPane.closeableTabs = false;
91        this._tabbedPane.headerElement().classList.add("heap-object-details-header");
92    }
93
94    this._retainmentView = new WebInspector.VBox();
95    this._retainmentView.setMinimumSize(50, 21);
96    this._retainmentView.element.classList.add("retaining-paths-view");
97
98    var splitViewResizer;
99    if (this._allocationStackView) {
100        this._tabbedPane = new WebInspector.TabbedPane();
101        this._tabbedPane.closeableTabs = false;
102        this._tabbedPane.headerElement().classList.add("heap-object-details-header");
103
104        this._tabbedPane.appendTab("retainers", WebInspector.UIString("Retainers"), this._retainmentView);
105        this._tabbedPane.appendTab("allocation-stack", WebInspector.UIString("Allocation stack"), this._allocationStackView);
106
107        splitViewResizer = this._tabbedPane.headerElement();
108        this._objectDetailsView = this._tabbedPane;
109    } else {
110        var retainmentViewHeader = document.createElementWithClass("div", "heap-snapshot-view-resizer");
111        var retainingPathsTitleDiv = retainmentViewHeader.createChild("div", "title");
112        var retainingPathsTitle = retainingPathsTitleDiv.createChild("span");
113        retainingPathsTitle.textContent = WebInspector.UIString("Retainers");
114        this._retainmentView.element.appendChild(retainmentViewHeader);
115
116        splitViewResizer = retainmentViewHeader;
117        this._objectDetailsView = this._retainmentView;
118    }
119    this._splitView.hideDefaultResizer();
120    this._splitView.installResizer(splitViewResizer);
121
122    this._retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid(this);
123    this._retainmentDataGrid.show(this._retainmentView.element);
124    this._retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
125    this._retainmentDataGrid.reset();
126
127    this._perspectives = [];
128    this._perspectives.push(new WebInspector.HeapSnapshotView.SummaryPerspective());
129    if (profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType)
130        this._perspectives.push(new WebInspector.HeapSnapshotView.ComparisonPerspective());
131    this._perspectives.push(new WebInspector.HeapSnapshotView.ContainmentPerspective());
132    if (this._allocationView)
133        this._perspectives.push(new WebInspector.HeapSnapshotView.AllocationPerspective());
134    this._perspectives.push(new WebInspector.HeapSnapshotView.StatisticsPerspective());
135
136    this._perspectiveSelect = new WebInspector.StatusBarComboBox(this._onSelectedPerspectiveChanged.bind(this));
137    for (var i = 0; i < this._perspectives.length; ++i)
138        this._perspectiveSelect.createOption(this._perspectives[i].title());
139
140    this._profile = profile;
141
142    this._baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this));
143    this._baseSelect.visible = false;
144    this._updateBaseOptions();
145
146    this._filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this));
147    this._filterSelect.visible = false;
148    this._updateFilterOptions();
149
150    this._classNameFilter = new WebInspector.StatusBarInput("Class filter");
151    this._classNameFilter.visible = false;
152    this._constructorsDataGrid.setNameFilter(this._classNameFilter);
153    this._diffDataGrid.setNameFilter(this._classNameFilter);
154
155    this._selectedSizeText = new WebInspector.StatusBarText("");
156
157    this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
158
159    this._currentPerspectiveIndex = 0;
160    this._currentPerspective = this._perspectives[0];
161    this._currentPerspective.activate(this);
162    this._dataGrid = this._currentPerspective.masterGrid(this);
163
164    this._refreshView();
165}
166
167/**
168 * @constructor
169 * @param {string} title
170 */
171WebInspector.HeapSnapshotView.Perspective = function(title)
172{
173    this._title = title;
174}
175
176WebInspector.HeapSnapshotView.Perspective.prototype = {
177    /**
178     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
179     */
180    activate: function(heapSnapshotView) { },
181
182    /**
183     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
184     */
185    deactivate: function(heapSnapshotView)
186    {
187        heapSnapshotView._baseSelect.visible = false;
188        heapSnapshotView._filterSelect.visible = false;
189        heapSnapshotView._classNameFilter.visible = false;
190        if (heapSnapshotView._trackingOverviewGrid)
191            heapSnapshotView._trackingOverviewGrid.detach();
192        if (heapSnapshotView._allocationView)
193            heapSnapshotView._allocationView.detach();
194        if (heapSnapshotView._statisticsView)
195            heapSnapshotView._statisticsView.detach();
196
197        heapSnapshotView._splitView.detach();
198        heapSnapshotView._splitView.detachChildViews();
199    },
200
201    /**
202     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
203     * @return {?WebInspector.DataGrid}
204     */
205    masterGrid: function(heapSnapshotView)
206    {
207        return null;
208    },
209
210    /**
211     * @return {string}
212     */
213    title: function()
214    {
215        return this._title;
216    },
217
218    /**
219     * @return {boolean}
220     */
221    supportsSearch: function()
222    {
223        return false;
224    }
225}
226
227/**
228 * @constructor
229 * @extends {WebInspector.HeapSnapshotView.Perspective}
230 */
231WebInspector.HeapSnapshotView.SummaryPerspective = function()
232{
233    WebInspector.HeapSnapshotView.Perspective.call(this,  WebInspector.UIString("Summary"));
234}
235
236WebInspector.HeapSnapshotView.SummaryPerspective.prototype = {
237    /**
238     * @override
239     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
240     */
241    activate: function(heapSnapshotView)
242    {
243        heapSnapshotView._constructorsView.show(heapSnapshotView._splitView.mainElement());
244        heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement());
245        heapSnapshotView._splitView.show(heapSnapshotView.element);
246        heapSnapshotView._filterSelect.visible = true;
247        heapSnapshotView._classNameFilter.visible = true;
248        if (heapSnapshotView._trackingOverviewGrid) {
249            heapSnapshotView._trackingOverviewGrid.show(heapSnapshotView.element, heapSnapshotView._splitView.element);
250            heapSnapshotView._trackingOverviewGrid.update();
251            heapSnapshotView._trackingOverviewGrid._updateGrid();
252        }
253    },
254
255    /**
256     * @override
257     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
258     * @return {?WebInspector.DataGrid}
259     */
260    masterGrid: function(heapSnapshotView)
261    {
262        return heapSnapshotView._constructorsDataGrid;
263    },
264
265    /**
266     * @override
267     * @return {boolean}
268     */
269    supportsSearch: function()
270    {
271        return true;
272    },
273
274   __proto__: WebInspector.HeapSnapshotView.Perspective.prototype
275}
276
277/**
278 * @constructor
279 * @extends {WebInspector.HeapSnapshotView.Perspective}
280 */
281WebInspector.HeapSnapshotView.ComparisonPerspective = function()
282{
283    WebInspector.HeapSnapshotView.Perspective.call(this,  WebInspector.UIString("Comparison"));
284}
285
286WebInspector.HeapSnapshotView.ComparisonPerspective.prototype = {
287    /**
288     * @override
289     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
290     */
291    activate: function(heapSnapshotView)
292    {
293        heapSnapshotView._diffView.show(heapSnapshotView._splitView.mainElement());
294        heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement());
295        heapSnapshotView._splitView.show(heapSnapshotView.element);
296        heapSnapshotView._baseSelect.visible = true;
297        heapSnapshotView._classNameFilter.visible = true;
298    },
299
300    /**
301     * @override
302     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
303     * @return {?WebInspector.DataGrid}
304     */
305    masterGrid: function(heapSnapshotView)
306    {
307        return heapSnapshotView._diffDataGrid;
308    },
309
310    /**
311     * @override
312     * @return {boolean}
313     */
314    supportsSearch: function()
315    {
316        return true;
317    },
318
319   __proto__: WebInspector.HeapSnapshotView.Perspective.prototype
320}
321
322/**
323 * @constructor
324 * @extends {WebInspector.HeapSnapshotView.Perspective}
325 */
326WebInspector.HeapSnapshotView.ContainmentPerspective = function()
327{
328    WebInspector.HeapSnapshotView.Perspective.call(this,  WebInspector.UIString("Containment"));
329}
330
331WebInspector.HeapSnapshotView.ContainmentPerspective.prototype = {
332    /**
333     * @override
334     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
335     */
336    activate: function(heapSnapshotView)
337    {
338        heapSnapshotView._containmentView.show(heapSnapshotView._splitView.mainElement());
339        heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement());
340        heapSnapshotView._splitView.show(heapSnapshotView.element);
341    },
342
343    /**
344     * @override
345     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
346     * @return {?WebInspector.DataGrid}
347     */
348    masterGrid: function(heapSnapshotView)
349    {
350        return heapSnapshotView._containmentDataGrid;
351    },
352   __proto__: WebInspector.HeapSnapshotView.Perspective.prototype
353}
354
355/**
356 * @constructor
357 * @extends {WebInspector.HeapSnapshotView.Perspective}
358 */
359WebInspector.HeapSnapshotView.AllocationPerspective = function()
360{
361    WebInspector.HeapSnapshotView.Perspective.call(this,  WebInspector.UIString("Allocation"));
362    this._allocationSplitView = new WebInspector.SplitView(false, true, "heapSnapshotAllocationSplitViewState", 200, 200);
363
364    var resizer = document.createElementWithClass("div", "heap-snapshot-view-resizer");
365    var title = resizer.createChild("div", "title").createChild("span");
366    title.textContent = WebInspector.UIString("Live objects");
367    this._allocationSplitView.hideDefaultResizer();
368    this._allocationSplitView.installResizer(resizer);
369
370    this._allocationSplitView.sidebarElement().appendChild(resizer);
371}
372
373WebInspector.HeapSnapshotView.AllocationPerspective.prototype = {
374    /**
375     * @override
376     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
377     */
378    activate: function(heapSnapshotView)
379    {
380        heapSnapshotView._allocationView.show(this._allocationSplitView.mainElement());
381        heapSnapshotView._constructorsView.show(heapSnapshotView._splitView.mainElement());
382        heapSnapshotView._objectDetailsView.show(heapSnapshotView._splitView.sidebarElement());
383        heapSnapshotView._splitView.show(this._allocationSplitView.sidebarElement());
384        this._allocationSplitView.show(heapSnapshotView.element);
385
386        heapSnapshotView._constructorsDataGrid.clear();
387        var selectedNode = heapSnapshotView._allocationDataGrid.selectedNode;
388        if (selectedNode)
389            heapSnapshotView._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId());
390    },
391
392    /**
393     * @override
394     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
395     */
396    deactivate: function(heapSnapshotView)
397    {
398        this._allocationSplitView.detach();
399        WebInspector.HeapSnapshotView.Perspective.prototype.deactivate.call(this, heapSnapshotView);
400    },
401
402    /**
403     * @override
404     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
405     * @return {?WebInspector.DataGrid}
406     */
407    masterGrid: function(heapSnapshotView)
408    {
409        return heapSnapshotView._allocationDataGrid;
410    },
411
412   __proto__: WebInspector.HeapSnapshotView.Perspective.prototype
413}
414
415/**
416 * @constructor
417 * @extends {WebInspector.HeapSnapshotView.Perspective}
418 */
419WebInspector.HeapSnapshotView.StatisticsPerspective = function()
420{
421    WebInspector.HeapSnapshotView.Perspective.call(this,  WebInspector.UIString("Statistics"));
422}
423
424WebInspector.HeapSnapshotView.StatisticsPerspective.prototype = {
425    /**
426     * @override
427     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
428     */
429    activate: function(heapSnapshotView)
430    {
431        heapSnapshotView._statisticsView.show(heapSnapshotView.element);
432    },
433
434    /**
435     * @override
436     * @param {!WebInspector.HeapSnapshotView} heapSnapshotView
437     * @return {?WebInspector.DataGrid}
438     */
439    masterGrid: function(heapSnapshotView)
440    {
441        return null;
442    },
443
444   __proto__: WebInspector.HeapSnapshotView.Perspective.prototype
445}
446
447
448WebInspector.HeapSnapshotView.prototype = {
449    /**
450     * @override
451     * @param {?WebInspector.ProfileHeader} profile
452     * @return {?WebInspector.View}
453     */
454    showProfile: function(profile)
455    {
456        return this._parentDataDisplayDelegate.showProfile(profile);
457    },
458
459    /**
460     * @override
461     * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId
462     * @param {string} perspectiveName
463     */
464    showObject: function(snapshotObjectId, perspectiveName)
465    {
466        if (snapshotObjectId <= this._profile.maxJSObjectId)
467            this.highlightLiveObject(perspectiveName, snapshotObjectId);
468        else
469            this._parentDataDisplayDelegate.showObject(snapshotObjectId, perspectiveName);
470    },
471
472    _refreshView: function()
473    {
474        this._profile.load(profileCallback.bind(this));
475
476        /**
477         * @param {!WebInspector.HeapSnapshotProxy} heapSnapshotProxy
478         * @this {WebInspector.HeapSnapshotView}
479         */
480        function profileCallback(heapSnapshotProxy)
481        {
482            heapSnapshotProxy.getStatistics(this._gotStatistics.bind(this));
483            var list = this._profiles();
484            var profileIndex = list.indexOf(this._profile);
485            this._baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1));
486            this._dataGrid.setDataSource(heapSnapshotProxy);
487            if (this._trackingOverviewGrid)
488                this._trackingOverviewGrid._updateGrid();
489        }
490    },
491
492    /**
493     * @param {!WebInspector.HeapSnapshotCommon.Statistics} statistics
494     */
495    _gotStatistics: function(statistics)
496    {
497        this._statisticsView.setTotal(statistics.total);
498        this._statisticsView.addRecord(statistics.code, WebInspector.UIString("Code"), "#f77");
499        this._statisticsView.addRecord(statistics.strings, WebInspector.UIString("Strings"), "#5e5");
500        this._statisticsView.addRecord(statistics.jsArrays, WebInspector.UIString("JS Arrays"), "#7af");
501        this._statisticsView.addRecord(statistics.native, WebInspector.UIString("Typed Arrays"), "#fc5");
502        this._statisticsView.addRecord(statistics.total, WebInspector.UIString("Total"));
503    },
504
505    _onIdsRangeChanged: function(event)
506    {
507        var minId = event.data.minId;
508        var maxId = event.data.maxId;
509        this._selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size)));
510        if (this._constructorsDataGrid.snapshot)
511            this._constructorsDataGrid.setSelectionRange(minId, maxId);
512    },
513
514    get statusBarItems()
515    {
516        var result = [this._perspectiveSelect.element, this._classNameFilter.element];
517        if (this._profile.profileType() !== WebInspector.ProfileTypeRegistry.instance.trackingHeapSnapshotProfileType)
518            result.push(this._baseSelect.element, this._filterSelect.element);
519        result.push(this._selectedSizeText.element);
520        return result;
521    },
522
523    wasShown: function()
524    {
525        // FIXME: load base and current snapshots in parallel
526        this._profile.load(profileCallback.bind(this));
527
528        /**
529         * @this {WebInspector.HeapSnapshotView}
530         */
531        function profileCallback() {
532            this._profile._wasShown();
533            if (this._baseProfile)
534                this._baseProfile.load(function() { });
535        }
536    },
537
538    willHide: function()
539    {
540        this._currentSearchResultIndex = -1;
541        this._popoverHelper.hidePopover();
542        if (this.helpPopover && this.helpPopover.isShowing())
543            this.helpPopover.hide();
544    },
545
546    searchCanceled: function()
547    {
548        if (this._searchResults) {
549            for (var i = 0; i < this._searchResults.length; ++i) {
550                var node = this._searchResults[i].node;
551                delete node._searchMatched;
552                node.refresh();
553            }
554        }
555
556        delete this._searchFinishedCallback;
557        this._currentSearchResultIndex = -1;
558        this._searchResults = [];
559    },
560
561    /**
562     * @param {string} query
563     * @param {function(!WebInspector.View, number)} finishedCallback
564     */
565    performSearch: function(query, finishedCallback)
566    {
567        // Call searchCanceled since it will reset everything we need before doing a new search.
568        this.searchCanceled();
569
570        query = query.trim();
571
572        if (!query)
573            return;
574        if (!this._currentPerspective.supportsSearch())
575            return;
576
577        /**
578         * @param {boolean} found
579         * @this {WebInspector.HeapSnapshotView}
580         */
581        function didHighlight(found)
582        {
583            finishedCallback(this, found ? 1 : 0);
584        }
585
586        if (query.charAt(0) === "@") {
587            var snapshotNodeId = parseInt(query.substring(1), 10);
588            if (!isNaN(snapshotNodeId))
589                this._dataGrid.highlightObjectByHeapSnapshotId(String(snapshotNodeId), didHighlight.bind(this));
590            else
591                finishedCallback(this, 0);
592            return;
593        }
594
595        this._searchFinishedCallback = finishedCallback;
596        var nameRegExp = createPlainTextSearchRegex(query, "i");
597
598        function matchesByName(gridNode) {
599            return ("_name" in gridNode) && nameRegExp.test(gridNode._name);
600        }
601
602        function matchesQuery(gridNode)
603        {
604            delete gridNode._searchMatched;
605            if (matchesByName(gridNode)) {
606                gridNode._searchMatched = true;
607                gridNode.refresh();
608                return true;
609            }
610            return false;
611        }
612
613        var current = this._dataGrid.rootNode().children[0];
614        var depth = 0;
615        var info = {};
616
617        // Restrict to type nodes and instances.
618        const maxDepth = 1;
619
620        while (current) {
621            if (matchesQuery(current))
622                this._searchResults.push({ node: current });
623            current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
624            depth += info.depthChange;
625        }
626
627        finishedCallback(this, this._searchResults.length);
628    },
629
630    jumpToFirstSearchResult: function()
631    {
632        if (!this._searchResults || !this._searchResults.length)
633            return;
634        this._currentSearchResultIndex = 0;
635        this._jumpToSearchResult(this._currentSearchResultIndex);
636    },
637
638    jumpToLastSearchResult: function()
639    {
640        if (!this._searchResults || !this._searchResults.length)
641            return;
642        this._currentSearchResultIndex = (this._searchResults.length - 1);
643        this._jumpToSearchResult(this._currentSearchResultIndex);
644    },
645
646    jumpToNextSearchResult: function()
647    {
648        if (!this._searchResults || !this._searchResults.length)
649            return;
650        if (++this._currentSearchResultIndex >= this._searchResults.length)
651            this._currentSearchResultIndex = 0;
652        this._jumpToSearchResult(this._currentSearchResultIndex);
653    },
654
655    jumpToPreviousSearchResult: function()
656    {
657        if (!this._searchResults || !this._searchResults.length)
658            return;
659        if (--this._currentSearchResultIndex < 0)
660            this._currentSearchResultIndex = (this._searchResults.length - 1);
661        this._jumpToSearchResult(this._currentSearchResultIndex);
662    },
663
664    /**
665     * @return {boolean}
666     */
667    showingFirstSearchResult: function()
668    {
669        return (this._currentSearchResultIndex === 0);
670    },
671
672    /**
673     * @return {boolean}
674     */
675    showingLastSearchResult: function()
676    {
677        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
678    },
679
680    /**
681     * @return {number}
682     */
683    currentSearchResultIndex: function() {
684        return this._currentSearchResultIndex;
685    },
686
687    _jumpToSearchResult: function(index)
688    {
689        var searchResult = this._searchResults[index];
690        if (!searchResult)
691            return;
692
693        var node = searchResult.node;
694        node.revealAndSelect();
695    },
696
697    refreshVisibleData: function()
698    {
699        if (!this._dataGrid)
700            return;
701        var child = this._dataGrid.rootNode().children[0];
702        while (child) {
703            child.refresh();
704            child = child.traverseNextNode(false, null, true);
705        }
706    },
707
708    _changeBase: function()
709    {
710        if (this._baseProfile === this._profiles()[this._baseSelect.selectedIndex()])
711            return;
712
713        this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()];
714        var dataGrid = /** @type {!WebInspector.HeapSnapshotDiffDataGrid} */ (this._dataGrid);
715        // Change set base data source only if main data source is already set.
716        if (dataGrid.snapshot)
717            this._baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
718
719        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
720            return;
721
722        // The current search needs to be performed again. First negate out previous match
723        // count by calling the search finished callback with a negative number of matches.
724        // Then perform the search again with the same query and callback.
725        this._searchFinishedCallback(this, -this._searchResults.length);
726        this.performSearch(this.currentQuery, this._searchFinishedCallback);
727    },
728
729    _changeFilter: function()
730    {
731        var profileIndex = this._filterSelect.selectedIndex() - 1;
732        this._dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
733
734        WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
735            action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
736            label: this._filterSelect.selectedOption().label
737        });
738
739        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
740            return;
741
742        // The current search needs to be performed again. First negate out previous match
743        // count by calling the search finished callback with a negative number of matches.
744        // Then perform the search again with the same query and callback.
745        this._searchFinishedCallback(this, -this._searchResults.length);
746        this.performSearch(this.currentQuery, this._searchFinishedCallback);
747    },
748
749    /**
750     * @return {!Array.<!WebInspector.ProfileHeader>}
751     */
752    _profiles: function()
753    {
754        return this._profile.profileType().getProfiles();
755    },
756
757    /**
758     * @param {!WebInspector.ContextMenu} contextMenu
759     * @param {!Event} event
760     */
761    populateContextMenu: function(contextMenu, event)
762    {
763        if (this._dataGrid)
764            this._dataGrid.populateContextMenu(contextMenu, event);
765    },
766
767    _selectionChanged: function(event)
768    {
769        var selectedNode = event.target.selectedNode;
770        this._setSelectedNodeForDetailsView(selectedNode);
771        this._inspectedObjectChanged(event);
772    },
773
774    _onSelectAllocationNode: function(event)
775    {
776        var selectedNode = event.target.selectedNode;
777        this._constructorsDataGrid.setAllocationNodeId(selectedNode.allocationNodeId());
778        this._setSelectedNodeForDetailsView(null);
779    },
780
781    _inspectedObjectChanged: function(event)
782    {
783        var selectedNode = event.target.selectedNode;
784        var target = this._profile.target();
785        if (target && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
786            target.consoleAgent().addInspectedHeapObject(selectedNode.snapshotNodeId);
787    },
788
789    /**
790     * @param {?WebInspector.HeapSnapshotGridNode} nodeItem
791     */
792    _setSelectedNodeForDetailsView: function(nodeItem)
793    {
794        var dataSource = nodeItem && nodeItem.retainersDataSource();
795        if (dataSource) {
796            this._retainmentDataGrid.setDataSource(dataSource.snapshot, dataSource.snapshotNodeIndex);
797            if (this._allocationStackView)
798                this._allocationStackView.setAllocatedObject(dataSource.snapshot, dataSource.snapshotNodeIndex)
799        } else {
800            if (this._allocationStackView)
801                this._allocationStackView.clear();
802            this._retainmentDataGrid.reset();
803        }
804    },
805
806    /**
807     * @param {string} perspectiveTitle
808     * @param {function()} callback
809     */
810    _changePerspectiveAndWait: function(perspectiveTitle, callback)
811    {
812        var perspectiveIndex = null;
813        for (var i = 0; i < this._perspectives.length; ++i) {
814            if (this._perspectives[i].title() === perspectiveTitle) {
815                perspectiveIndex = i;
816                break;
817            }
818        }
819        if (this._currentPerspectiveIndex === perspectiveIndex || perspectiveIndex === null) {
820            setTimeout(callback, 0);
821            return;
822        }
823
824        /**
825         * @this {WebInspector.HeapSnapshotView}
826         */
827        function dataGridContentShown(event)
828        {
829            var dataGrid = event.data;
830            dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
831            if (dataGrid === this._dataGrid)
832                callback();
833        }
834        this._perspectives[perspectiveIndex].masterGrid(this).addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
835
836        this._perspectiveSelect.setSelectedIndex(perspectiveIndex);
837        this._changePerspective(perspectiveIndex);
838    },
839
840    _updateDataSourceAndView: function()
841    {
842        var dataGrid = this._dataGrid;
843        if (!dataGrid || dataGrid.snapshot)
844            return;
845
846        this._profile.load(didLoadSnapshot.bind(this));
847
848        /**
849         * @this {WebInspector.HeapSnapshotView}
850         */
851        function didLoadSnapshot(snapshotProxy)
852        {
853            if (this._dataGrid !== dataGrid)
854                return;
855            if (dataGrid.snapshot !== snapshotProxy)
856                dataGrid.setDataSource(snapshotProxy);
857            if (dataGrid === this._diffDataGrid) {
858                if (!this._baseProfile)
859                    this._baseProfile = this._profiles()[this._baseSelect.selectedIndex()];
860                this._baseProfile.load(didLoadBaseSnaphot.bind(this));
861            }
862        }
863
864        /**
865         * @this {WebInspector.HeapSnapshotView}
866         */
867        function didLoadBaseSnaphot(baseSnapshotProxy)
868        {
869            if (this._diffDataGrid.baseSnapshot !== baseSnapshotProxy)
870                this._diffDataGrid.setBaseDataSource(baseSnapshotProxy);
871        }
872    },
873
874    _onSelectedPerspectiveChanged: function(event)
875    {
876        this._changePerspective(event.target.selectedIndex);
877        // FIXME: This is needed by CodeSchool extension.
878        this._onSelectedViewChanged(event);
879    },
880
881    _onSelectedViewChanged: function(event)
882    {
883    },
884
885    /**
886     * @param {number} selectedIndex
887     */
888    _changePerspective: function(selectedIndex)
889    {
890        if (selectedIndex === this._currentPerspectiveIndex)
891            return;
892
893        this._currentPerspectiveIndex = selectedIndex;
894
895        this._currentPerspective.deactivate(this);
896        var perspective = this._perspectives[selectedIndex];
897        this._currentPerspective = perspective;
898        this._dataGrid = perspective.masterGrid(this);
899        perspective.activate(this);
900
901        this.refreshVisibleData();
902        if (this._dataGrid)
903            this._dataGrid.updateWidths();
904
905        this._updateDataSourceAndView();
906
907        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
908            return;
909
910        // The current search needs to be performed again. First negate out previous match
911        // count by calling the search finished callback with a negative number of matches.
912        // Then perform the search again the with same query and callback.
913        this._searchFinishedCallback(this, -this._searchResults.length);
914        this.performSearch(this.currentQuery, this._searchFinishedCallback);
915    },
916
917    /**
918     * @param {string} perspectiveName
919     * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId
920     */
921    highlightLiveObject: function(perspectiveName, snapshotObjectId)
922    {
923        this._changePerspectiveAndWait(perspectiveName, didChangePerspective.bind(this));
924
925        /**
926         * @this {WebInspector.HeapSnapshotView}
927         */
928        function didChangePerspective()
929        {
930            this._dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId, didHighlightObject);
931        }
932
933        function didHighlightObject(found)
934        {
935            if (!found)
936                WebInspector.console.error("Cannot find corresponding heap snapshot node");
937        }
938    },
939
940    _getHoverAnchor: function(target)
941    {
942        var span = target.enclosingNodeOrSelfWithNodeName("span");
943        if (!span)
944            return;
945        var row = target.enclosingNodeOrSelfWithNodeName("tr");
946        if (!row)
947            return;
948        span.node = row._dataGridNode;
949        return span;
950    },
951
952    _resolveObjectForPopover: function(element, showCallback, objectGroupName)
953    {
954        if (!this._profile.target())
955            return;
956        element.node.queryObjectContent(this._profile.target(), showCallback, objectGroupName);
957    },
958
959    _updateBaseOptions: function()
960    {
961        var list = this._profiles();
962        // We're assuming that snapshots can only be added.
963        if (this._baseSelect.size() === list.length)
964            return;
965
966        for (var i = this._baseSelect.size(), n = list.length; i < n; ++i) {
967            var title = list[i].title;
968            this._baseSelect.createOption(title);
969        }
970    },
971
972    _updateFilterOptions: function()
973    {
974        var list = this._profiles();
975        // We're assuming that snapshots can only be added.
976        if (this._filterSelect.size() - 1 === list.length)
977            return;
978
979        if (!this._filterSelect.size())
980            this._filterSelect.createOption(WebInspector.UIString("All objects"));
981
982        for (var i = this._filterSelect.size() - 1, n = list.length; i < n; ++i) {
983            var title = list[i].title;
984            if (!i)
985                title = WebInspector.UIString("Objects allocated before %s", title);
986            else
987                title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title);
988            this._filterSelect.createOption(title);
989        }
990    },
991
992    _updateControls: function()
993    {
994        this._updateBaseOptions();
995        this._updateFilterOptions();
996    },
997
998    /**
999     * @param {!WebInspector.Event} event
1000     */
1001    _onReceiveSnapshot: function(event)
1002    {
1003        this._updateControls();
1004    },
1005
1006    /**
1007     * @param {!WebInspector.Event} event
1008     */
1009    _onProfileHeaderRemoved: function(event)
1010    {
1011        var profile = event.data;
1012        if (this._profile === profile) {
1013            this.detach();
1014            this._profile.profileType().removeEventListener(WebInspector.HeapSnapshotProfileType.SnapshotReceived, this._onReceiveSnapshot, this);
1015            this._profile.profileType().removeEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, this._onProfileHeaderRemoved, this);
1016            this.dispose();
1017        } else {
1018            this._updateControls();
1019        }
1020    },
1021
1022    dispose: function()
1023    {
1024        if (this._allocationStackView) {
1025            this._allocationStackView.clear();
1026            this._allocationDataGrid.dispose();
1027        }
1028    },
1029
1030    __proto__: WebInspector.VBox.prototype
1031}
1032
1033/**
1034 * @constructor
1035 * @extends {WebInspector.ProfileType}
1036 * @implements {WebInspector.TargetManager.Observer}
1037 * @param {string=} id
1038 * @param {string=} title
1039 */
1040WebInspector.HeapSnapshotProfileType = function(id, title)
1041{
1042    WebInspector.ProfileType.call(this, id || WebInspector.HeapSnapshotProfileType.TypeId, title || WebInspector.UIString("Take Heap Snapshot"));
1043    WebInspector.targetManager.observeTargets(this);
1044    WebInspector.targetManager.addModelListener(WebInspector.HeapProfilerModel, WebInspector.HeapProfilerModel.Events.ResetProfiles, this._resetProfiles, this);
1045    WebInspector.targetManager.addModelListener(WebInspector.HeapProfilerModel, WebInspector.HeapProfilerModel.Events.AddHeapSnapshotChunk, this._addHeapSnapshotChunk, this);
1046    WebInspector.targetManager.addModelListener(WebInspector.HeapProfilerModel, WebInspector.HeapProfilerModel.Events.ReportHeapSnapshotProgress, this._reportHeapSnapshotProgress, this);
1047}
1048
1049WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
1050WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived";
1051
1052WebInspector.HeapSnapshotProfileType.prototype = {
1053    /**
1054     * @param {!WebInspector.Target} target
1055     */
1056    targetAdded: function(target)
1057    {
1058        target.heapProfilerModel.enable();
1059    },
1060
1061    /**
1062     * @param {!WebInspector.Target} target
1063     */
1064    targetRemoved: function(target)
1065    {
1066    },
1067
1068    /**
1069     * @override
1070     * @return {string}
1071     */
1072    fileExtension: function()
1073    {
1074        return ".heapsnapshot";
1075    },
1076
1077    get buttonTooltip()
1078    {
1079        return WebInspector.UIString("Take heap snapshot.");
1080    },
1081
1082    /**
1083     * @override
1084     * @return {boolean}
1085     */
1086    isInstantProfile: function()
1087    {
1088        return true;
1089    },
1090
1091    /**
1092     * @override
1093     * @return {boolean}
1094     */
1095    buttonClicked: function()
1096    {
1097        this._takeHeapSnapshot(function() {});
1098        WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
1099        return false;
1100    },
1101
1102    get treeItemTitle()
1103    {
1104        return WebInspector.UIString("HEAP SNAPSHOTS");
1105    },
1106
1107    get description()
1108    {
1109        return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes.");
1110    },
1111
1112    /**
1113     * @override
1114     * @param {string} title
1115     * @return {!WebInspector.ProfileHeader}
1116     */
1117    createProfileLoadedFromFile: function(title)
1118    {
1119        return new WebInspector.HeapProfileHeader(null, this, title);
1120    },
1121
1122    _takeHeapSnapshot: function(callback)
1123    {
1124        if (this.profileBeingRecorded())
1125            return;
1126        var target = /** @type {!WebInspector.Target} */ (WebInspector.context.flavor(WebInspector.Target));
1127        var profile = new WebInspector.HeapProfileHeader(target, this);
1128        this.setProfileBeingRecorded(profile);
1129        this.addProfile(profile);
1130        profile.updateStatus(WebInspector.UIString("Snapshotting\u2026"));
1131
1132        /**
1133         * @param {?string} error
1134         * @this {WebInspector.HeapSnapshotProfileType}
1135         */
1136        function didTakeHeapSnapshot(error)
1137        {
1138            var profile = this._profileBeingRecorded;
1139            profile.title = WebInspector.UIString("Snapshot %d", profile.uid);
1140            profile._finishLoad();
1141            this.setProfileBeingRecorded(null);
1142            this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, profile);
1143            callback();
1144        }
1145        target.heapProfilerAgent().takeHeapSnapshot(true, didTakeHeapSnapshot.bind(this));
1146    },
1147
1148    /**
1149     * @param {!WebInspector.Event} event
1150     */
1151    _addHeapSnapshotChunk: function(event)
1152    {
1153        if (!this.profileBeingRecorded())
1154            return;
1155        var chunk = /** @type {string} */(event.data);
1156        this.profileBeingRecorded().transferChunk(chunk);
1157    },
1158
1159    /**
1160     * @param {!WebInspector.Event} event
1161     */
1162    _reportHeapSnapshotProgress: function(event)
1163    {
1164        var profile = this.profileBeingRecorded();
1165        if (!profile)
1166            return;
1167        var data = /** @type {{done: number, total: number, finished: boolean}} */ (event.data);
1168        profile.updateStatus(WebInspector.UIString("%.0f%", (data.done / data.total) * 100), true);
1169        if (data.finished)
1170            profile._prepareToLoad();
1171    },
1172
1173    _resetProfiles: function()
1174    {
1175        this._reset();
1176    },
1177
1178    _snapshotReceived: function(profile)
1179    {
1180        if (this._profileBeingRecorded === profile)
1181            this.setProfileBeingRecorded(null);
1182        this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile);
1183    },
1184
1185    __proto__: WebInspector.ProfileType.prototype
1186}
1187
1188
1189/**
1190 * @constructor
1191 * @extends {WebInspector.HeapSnapshotProfileType}
1192 */
1193WebInspector.TrackingHeapSnapshotProfileType = function()
1194{
1195    WebInspector.HeapSnapshotProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations"));
1196}
1197
1198WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD";
1199
1200WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate";
1201WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted";
1202WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped";
1203
1204WebInspector.TrackingHeapSnapshotProfileType.prototype = {
1205
1206    /**
1207     * @param {!WebInspector.Target} target
1208     */
1209    targetAdded: function(target)
1210    {
1211        WebInspector.HeapSnapshotProfileType.prototype.targetAdded.call(this, target);
1212        target.heapProfilerModel.addEventListener(WebInspector.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this);
1213        target.heapProfilerModel.addEventListener(WebInspector.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this);
1214    },
1215
1216    /**
1217     * @param {!WebInspector.Target} target
1218     */
1219    targetRemoved: function(target)
1220    {
1221        WebInspector.HeapSnapshotProfileType.prototype.targetRemoved.call(this, target);
1222        target.heapProfilerModel.removeEventListener(WebInspector.HeapProfilerModel.Events.HeapStatsUpdate, this._heapStatsUpdate, this);
1223        target.heapProfilerModel.removeEventListener(WebInspector.HeapProfilerModel.Events.LastSeenObjectId, this._lastSeenObjectId, this);
1224    },
1225
1226    /**
1227     * @param {!WebInspector.Event} event
1228     */
1229    _heapStatsUpdate: function(event)
1230    {
1231        if (!this._profileSamples)
1232            return;
1233        var samples = /** @type {!Array.<number>} */ (event.data);
1234        var index;
1235        for (var i = 0; i < samples.length; i += 3) {
1236            index = samples[i];
1237            var count = samples[i+1];
1238            var size  = samples[i+2];
1239            this._profileSamples.sizes[index] = size;
1240            if (!this._profileSamples.max[index])
1241                this._profileSamples.max[index] = size;
1242        }
1243    },
1244
1245    /**
1246     * @param {!WebInspector.Event} event
1247     */
1248    _lastSeenObjectId: function(event)
1249    {
1250        var profileSamples = this._profileSamples;
1251        if (!profileSamples)
1252            return;
1253        var data = /** @type {{lastSeenObjectId: number, timestamp: number}} */ (event.data);
1254        var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
1255        profileSamples.ids[currentIndex] = data.lastSeenObjectId;
1256        if (!profileSamples.max[currentIndex]) {
1257            profileSamples.max[currentIndex] = 0;
1258            profileSamples.sizes[currentIndex] = 0;
1259        }
1260        profileSamples.timestamps[currentIndex] = data.timestamp;
1261        if (profileSamples.totalTime < data.timestamp - profileSamples.timestamps[0])
1262            profileSamples.totalTime *= 2;
1263        this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
1264        this._profileBeingRecorded.updateStatus(null, true);
1265    },
1266
1267    /**
1268     * @override
1269     * @return {boolean}
1270     */
1271    hasTemporaryView: function()
1272    {
1273        return true;
1274    },
1275
1276    get buttonTooltip()
1277    {
1278        return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile.");
1279    },
1280
1281    /**
1282     * @override
1283     * @return {boolean}
1284     */
1285    isInstantProfile: function()
1286    {
1287        return false;
1288    },
1289
1290    /**
1291     * @override
1292     * @return {boolean}
1293     */
1294    buttonClicked: function()
1295    {
1296        return this._toggleRecording();
1297    },
1298
1299    _startRecordingProfile: function()
1300    {
1301        if (this.profileBeingRecorded())
1302            return;
1303        var recordAllocationStacks = WebInspector.settings.recordAllocationStacks.get();
1304        this._addNewProfile(recordAllocationStacks);
1305        this.profileBeingRecorded().target().heapProfilerAgent().startTrackingHeapObjects(recordAllocationStacks);
1306    },
1307
1308    /**
1309     * @param {boolean} withAllocationStacks
1310     */
1311    _addNewProfile: function(withAllocationStacks)
1312    {
1313        var target =  WebInspector.context.flavor(WebInspector.Target);
1314        this.setProfileBeingRecorded(new WebInspector.HeapProfileHeader(target, this, undefined, withAllocationStacks));
1315        this._lastSeenIndex = -1;
1316        this._profileSamples = {
1317            'sizes': [],
1318            'ids': [],
1319            'timestamps': [],
1320            'max': [],
1321            'totalTime': 30000
1322        };
1323        this._profileBeingRecorded._profileSamples = this._profileSamples;
1324        this._recording = true;
1325        this.addProfile(this._profileBeingRecorded);
1326        this._profileBeingRecorded.updateStatus(WebInspector.UIString("Recording\u2026"));
1327        this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted);
1328    },
1329
1330    _stopRecordingProfile: function()
1331    {
1332        this._profileBeingRecorded.updateStatus(WebInspector.UIString("Snapshotting\u2026"));
1333        /**
1334         * @param {?string} error
1335         * @this {WebInspector.HeapSnapshotProfileType}
1336         */
1337        function didTakeHeapSnapshot(error)
1338        {
1339            var profile = this.profileBeingRecorded();
1340            if (!profile)
1341                return;
1342            profile._finishLoad();
1343            this._profileSamples = null;
1344            this.setProfileBeingRecorded(null);
1345            this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProfileComplete, profile);
1346        }
1347
1348        this._profileBeingRecorded.target().heapProfilerAgent().stopTrackingHeapObjects(true, didTakeHeapSnapshot.bind(this));
1349        this._recording = false;
1350        this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped);
1351    },
1352
1353    _toggleRecording: function()
1354    {
1355        if (this._recording)
1356            this._stopRecordingProfile();
1357        else
1358            this._startRecordingProfile();
1359        return this._recording;
1360    },
1361
1362    get treeItemTitle()
1363    {
1364        return WebInspector.UIString("HEAP TIMELINES");
1365    },
1366
1367    get description()
1368    {
1369        return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks.");
1370    },
1371
1372    /**
1373     * @override
1374     */
1375    resetProfiles: function()
1376    {
1377        var wasRecording = this._recording;
1378        var recordingAllocationStacks = wasRecording && this.profileBeingRecorded()._hasAllocationStacks;
1379        // Clear current profile to avoid stopping backend.
1380        this.setProfileBeingRecorded(null);
1381        WebInspector.HeapSnapshotProfileType.prototype.resetProfiles.call(this);
1382        this._profileSamples = null;
1383        this._lastSeenIndex = -1;
1384        if (wasRecording)
1385            this._addNewProfile(recordingAllocationStacks);
1386    },
1387
1388    /**
1389     * @override
1390     */
1391    profileBeingRecordedRemoved: function()
1392    {
1393        this._stopRecordingProfile();
1394        this._profileSamples = null;
1395    },
1396
1397    __proto__: WebInspector.HeapSnapshotProfileType.prototype
1398}
1399
1400/**
1401 * @constructor
1402 * @extends {WebInspector.ProfileHeader}
1403 * @param {?WebInspector.Target} target
1404 * @param {!WebInspector.HeapSnapshotProfileType} type
1405 * @param {string=} title
1406 * @param {boolean=} hasAllocationStacks
1407 */
1408WebInspector.HeapProfileHeader = function(target, type, title, hasAllocationStacks)
1409{
1410    WebInspector.ProfileHeader.call(this, target, type, title || WebInspector.UIString("Snapshot %d", type.nextProfileUid()));
1411    this._hasAllocationStacks = !!hasAllocationStacks;
1412    this.maxJSObjectId = -1;
1413    /**
1414     * @type {?WebInspector.HeapSnapshotWorkerProxy}
1415     */
1416    this._workerProxy = null;
1417    /**
1418     * @type {?WebInspector.OutputStream}
1419     */
1420    this._receiver = null;
1421    /**
1422     * @type {?WebInspector.HeapSnapshotProxy}
1423     */
1424    this._snapshotProxy = null;
1425    /**
1426     * @type {?Array.<function(!WebInspector.HeapSnapshotProxy)>}
1427     */
1428    this._loadCallbacks = [];
1429    this._totalNumberOfChunks = 0;
1430    this._bufferedWriter = null;
1431}
1432
1433WebInspector.HeapProfileHeader.prototype = {
1434    /**
1435     * @override
1436     * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
1437     * @return {!WebInspector.ProfileSidebarTreeElement}
1438     */
1439    createSidebarTreeElement: function(dataDisplayDelegate)
1440    {
1441        return new WebInspector.ProfileSidebarTreeElement(dataDisplayDelegate, this, "heap-snapshot-sidebar-tree-item");
1442    },
1443
1444    /**
1445     * @override
1446     * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
1447     * @return {!WebInspector.HeapSnapshotView}
1448     */
1449    createView: function(dataDisplayDelegate)
1450    {
1451        return new WebInspector.HeapSnapshotView(dataDisplayDelegate, this);
1452    },
1453
1454    /**
1455     * @override
1456     * @param {function(!WebInspector.HeapSnapshotProxy):void} callback
1457     */
1458    load: function(callback)
1459    {
1460        if (this.uid === -1)
1461            return;
1462        if (this._snapshotProxy) {
1463            callback(this._snapshotProxy);
1464            return;
1465        }
1466        this._loadCallbacks.push(callback);
1467    },
1468
1469    _prepareToLoad: function()
1470    {
1471        console.assert(!this._receiver, "Already loading");
1472        this._setupWorker();
1473        this.updateStatus(WebInspector.UIString("Loading\u2026"), true);
1474    },
1475
1476    _finishLoad: function()
1477    {
1478        if (!this._wasDisposed)
1479            this._receiver.close();
1480        if (this._bufferedWriter) {
1481            this._bufferedWriter.finishWriting(this._didWriteToTempFile.bind(this));
1482            this._bufferedWriter = null;
1483        }
1484    },
1485
1486    _didWriteToTempFile: function(tempFile)
1487    {
1488        if (this._wasDisposed) {
1489            if (tempFile)
1490                tempFile.remove();
1491            return;
1492        }
1493        this._tempFile = tempFile;
1494        if (!tempFile)
1495            this._failedToCreateTempFile = true;
1496        if (this._onTempFileReady) {
1497            this._onTempFileReady();
1498            this._onTempFileReady = null;
1499        }
1500    },
1501
1502    _setupWorker: function()
1503    {
1504        /**
1505         * @this {WebInspector.HeapProfileHeader}
1506         */
1507        function setProfileWait(event)
1508        {
1509            this.updateStatus(null, event.data);
1510        }
1511        console.assert(!this._workerProxy, "HeapSnapshotWorkerProxy already exists");
1512        this._workerProxy = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this));
1513        this._workerProxy.addEventListener("wait", setProfileWait, this);
1514        this._receiver = this._workerProxy.createLoader(this.uid, this._snapshotReceived.bind(this));
1515    },
1516
1517    /**
1518     * @param {string} eventName
1519     * @param {*} data
1520     */
1521    _handleWorkerEvent: function(eventName, data)
1522    {
1523        if (WebInspector.HeapSnapshotProgressEvent.Update !== eventName)
1524            return;
1525        var subtitle = /** @type {string} */ (data);
1526        this.updateStatus(subtitle);
1527    },
1528
1529    /**
1530     * @override
1531     */
1532    dispose: function()
1533    {
1534        if (this._workerProxy)
1535            this._workerProxy.dispose();
1536        this.removeTempFile();
1537        this._wasDisposed = true;
1538    },
1539
1540    _didCompleteSnapshotTransfer: function()
1541    {
1542        if (!this._snapshotProxy)
1543            return;
1544        this.updateStatus(Number.bytesToString(this._snapshotProxy.totalSize), false);
1545    },
1546
1547    /**
1548     * @param {string} chunk
1549     */
1550    transferChunk: function(chunk)
1551    {
1552        if (!this._bufferedWriter)
1553            this._bufferedWriter = new WebInspector.DeferredTempFile("heap-profiler", String(this.uid));
1554        this._bufferedWriter.write([chunk]);
1555
1556        ++this._totalNumberOfChunks;
1557        this._receiver.write(chunk, function() {});
1558    },
1559
1560    _snapshotReceived: function(snapshotProxy)
1561    {
1562        if (this._wasDisposed)
1563            return;
1564        this._receiver = null;
1565        this._snapshotProxy = snapshotProxy;
1566        this.maxJSObjectId = snapshotProxy.maxJSObjectId();
1567        this._didCompleteSnapshotTransfer();
1568        this._workerProxy.startCheckingForLongRunningCalls();
1569        this.notifySnapshotReceived();
1570    },
1571
1572    notifySnapshotReceived: function()
1573    {
1574        for (var i = 0; i < this._loadCallbacks.length; i++)
1575            this._loadCallbacks[i](/** @type {!WebInspector.HeapSnapshotProxy} */ (this._snapshotProxy));
1576        this._loadCallbacks = null;
1577        this._profileType._snapshotReceived(this);
1578        if (this.canSaveToFile())
1579            this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.ProfileReceived);
1580    },
1581
1582    // Hook point for tests.
1583    _wasShown: function()
1584    {
1585    },
1586
1587    /**
1588     * @override
1589     * @return {boolean}
1590     */
1591    canSaveToFile: function()
1592    {
1593        return !this.fromFile() && !!this._snapshotProxy;
1594    },
1595
1596    /**
1597     * @override
1598     */
1599    saveToFile: function()
1600    {
1601        var fileOutputStream = new WebInspector.FileOutputStream();
1602
1603        /**
1604         * @param {boolean} accepted
1605         * @this {WebInspector.HeapProfileHeader}
1606         */
1607        function onOpen(accepted)
1608        {
1609            if (!accepted)
1610                return;
1611            if (this._failedToCreateTempFile) {
1612                WebInspector.console.error("Failed to open temp file with heap snapshot");
1613                fileOutputStream.close();
1614            } else if (this._tempFile) {
1615                var delegate = new WebInspector.SaveSnapshotOutputStreamDelegate(this);
1616                this._tempFile.writeToOutputSteam(fileOutputStream, delegate);
1617            } else {
1618                this._onTempFileReady = onOpen.bind(this, accepted);
1619                this._updateSaveProgress(0, 1);
1620            }
1621        }
1622        this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
1623        fileOutputStream.open(this._fileName, onOpen.bind(this));
1624    },
1625
1626    _updateSaveProgress: function(value, total)
1627    {
1628        var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0);
1629        this.updateStatus(WebInspector.UIString("Saving\u2026 %d\%", percentValue));
1630    },
1631
1632    /**
1633     * @override
1634     * @param {!File} file
1635     */
1636    loadFromFile: function(file)
1637    {
1638        this.updateStatus(WebInspector.UIString("Loading\u2026"), true);
1639        this._setupWorker();
1640        var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
1641        var fileReader = this._createFileReader(file, delegate);
1642        fileReader.start(this._receiver);
1643    },
1644
1645    _createFileReader: function(file, delegate)
1646    {
1647        return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
1648    },
1649
1650    __proto__: WebInspector.ProfileHeader.prototype
1651}
1652
1653/**
1654 * @constructor
1655 * @implements {WebInspector.OutputStreamDelegate}
1656 */
1657WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
1658{
1659    this._snapshotHeader = snapshotHeader;
1660}
1661
1662WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
1663    onTransferStarted: function()
1664    {
1665    },
1666
1667    /**
1668     * @param {!WebInspector.ChunkedReader} reader
1669     */
1670    onChunkTransferred: function(reader)
1671    {
1672    },
1673
1674    onTransferFinished: function()
1675    {
1676    },
1677
1678    /**
1679     * @param {!WebInspector.ChunkedReader} reader
1680     * @param {!Event} e
1681     */
1682    onError: function (reader, e)
1683    {
1684        var subtitle;
1685        switch(e.target.error.code) {
1686        case e.target.error.NOT_FOUND_ERR:
1687            subtitle = WebInspector.UIString("'%s' not found.", reader.fileName());
1688            break;
1689        case e.target.error.NOT_READABLE_ERR:
1690            subtitle = WebInspector.UIString("'%s' is not readable", reader.fileName());
1691            break;
1692        case e.target.error.ABORT_ERR:
1693            return;
1694        default:
1695            subtitle = WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code);
1696        }
1697        this._snapshotHeader.updateStatus(subtitle);
1698    }
1699}
1700
1701/**
1702 * @constructor
1703 * @implements {WebInspector.OutputStreamDelegate}
1704 * @param {!WebInspector.HeapProfileHeader} profileHeader
1705 */
1706WebInspector.SaveSnapshotOutputStreamDelegate = function(profileHeader)
1707{
1708    this._profileHeader = profileHeader;
1709}
1710
1711WebInspector.SaveSnapshotOutputStreamDelegate.prototype = {
1712    onTransferStarted: function()
1713    {
1714        this._profileHeader._updateSaveProgress(0, 1);
1715    },
1716
1717    onTransferFinished: function()
1718    {
1719        this._profileHeader._didCompleteSnapshotTransfer();
1720    },
1721
1722    /**
1723     * @param {!WebInspector.ChunkedReader} reader
1724     */
1725    onChunkTransferred: function(reader)
1726    {
1727        this._profileHeader._updateSaveProgress(reader.loadedSize(), reader.fileSize());
1728    },
1729
1730    /**
1731     * @param {!WebInspector.ChunkedReader} reader
1732     * @param {!Event} event
1733     */
1734    onError: function(reader, event)
1735    {
1736        WebInspector.console.error("Failed to read heap snapshot from temp file: " + /** @type {!ErrorEvent} */ (event).message);
1737        this.onTransferFinished();
1738    }
1739}
1740
1741/**
1742 * @constructor
1743 * @extends {WebInspector.VBox}
1744 * @param {!WebInspector.HeapProfileHeader} heapProfileHeader
1745 */
1746WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader)
1747{
1748    WebInspector.VBox.call(this);
1749    this.registerRequiredCSS("flameChart.css");
1750    this.element.id = "heap-recording-view";
1751    this.element.classList.add("heap-tracking-overview");
1752
1753    this._overviewContainer = this.element.createChild("div", "overview-container");
1754    this._overviewGrid = new WebInspector.OverviewGrid("heap-recording");
1755    this._overviewGrid.element.classList.add("fill");
1756
1757    this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas");
1758    this._overviewContainer.appendChild(this._overviewGrid.element);
1759    this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator();
1760    this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
1761
1762    this._profileSamples = heapProfileHeader._profileSamples;
1763    if (heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) {
1764        this._profileType = heapProfileHeader.profileType();
1765        this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
1766        this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
1767    }
1768    var timestamps = this._profileSamples.timestamps;
1769    var totalTime = this._profileSamples.totalTime;
1770    this._windowLeft = 0.0;
1771    this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0;
1772    this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
1773    this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
1774    this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
1775}
1776
1777WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged";
1778
1779WebInspector.HeapTrackingOverviewGrid.prototype = {
1780    _onStopTracking: function(event)
1781    {
1782        this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
1783        this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
1784    },
1785
1786    _onHeapStatsUpdate: function(event)
1787    {
1788        this._profileSamples = event.data;
1789        this._scheduleUpdate();
1790    },
1791
1792     /**
1793      * @param {number} width
1794      * @param {number} height
1795      */
1796    _drawOverviewCanvas: function(width, height)
1797    {
1798        if (!this._profileSamples)
1799            return;
1800        var profileSamples = this._profileSamples;
1801        var sizes = profileSamples.sizes;
1802        var topSizes = profileSamples.max;
1803        var timestamps = profileSamples.timestamps;
1804        var startTime = timestamps[0];
1805        var endTime = timestamps[timestamps.length - 1];
1806
1807        var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
1808        var maxSize = 0;
1809        /**
1810          * @param {!Array.<number>} sizes
1811          * @param {function(number, number):void} callback
1812          */
1813        function aggregateAndCall(sizes, callback)
1814        {
1815            var size = 0;
1816            var currentX = 0;
1817            for (var i = 1; i < timestamps.length; ++i) {
1818                var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
1819                if (x !== currentX) {
1820                    if (size)
1821                        callback(currentX, size);
1822                    size = 0;
1823                    currentX = x;
1824                }
1825                size += sizes[i];
1826            }
1827            callback(currentX, size);
1828        }
1829
1830        /**
1831          * @param {number} x
1832          * @param {number} size
1833          */
1834        function maxSizeCallback(x, size)
1835        {
1836            maxSize = Math.max(maxSize, size);
1837        }
1838
1839        aggregateAndCall(sizes, maxSizeCallback);
1840
1841        var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);
1842
1843        this._overviewCanvas.width = width * window.devicePixelRatio;
1844        this._overviewCanvas.height = height * window.devicePixelRatio;
1845        this._overviewCanvas.style.width = width + "px";
1846        this._overviewCanvas.style.height = height + "px";
1847
1848        var context = this._overviewCanvas.getContext("2d");
1849        context.scale(window.devicePixelRatio, window.devicePixelRatio);
1850
1851        context.beginPath();
1852        context.lineWidth = 2;
1853        context.strokeStyle = "rgba(192, 192, 192, 0.6)";
1854        var currentX = (endTime - startTime) * scaleFactor;
1855        context.moveTo(currentX, height - 1);
1856        context.lineTo(currentX, 0);
1857        context.stroke();
1858        context.closePath();
1859
1860        var gridY;
1861        var gridValue;
1862        var gridLabelHeight = 14;
1863        if (yScaleFactor) {
1864            const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
1865            // The round value calculation is a bit tricky, because
1866            // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
1867            // e.g. a round value 10KB is 10240 bytes.
1868            gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
1869            gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10));
1870            if (gridValue * 5 <= maxGridValue)
1871                gridValue *= 5;
1872            gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
1873            context.beginPath();
1874            context.lineWidth = 1;
1875            context.strokeStyle = "rgba(0, 0, 0, 0.2)";
1876            context.moveTo(0, gridY);
1877            context.lineTo(width, gridY);
1878            context.stroke();
1879            context.closePath();
1880        }
1881
1882        /**
1883          * @param {number} x
1884          * @param {number} size
1885          */
1886        function drawBarCallback(x, size)
1887        {
1888            context.moveTo(x, height - 1);
1889            context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
1890        }
1891
1892        context.beginPath();
1893        context.lineWidth = 2;
1894        context.strokeStyle = "rgba(192, 192, 192, 0.6)";
1895        aggregateAndCall(topSizes, drawBarCallback);
1896        context.stroke();
1897        context.closePath();
1898
1899        context.beginPath();
1900        context.lineWidth = 2;
1901        context.strokeStyle = "rgba(0, 0, 192, 0.8)";
1902        aggregateAndCall(sizes, drawBarCallback);
1903        context.stroke();
1904        context.closePath();
1905
1906        if (gridValue) {
1907            var label = Number.bytesToString(gridValue);
1908            var labelPadding = 4;
1909            var labelX = 0;
1910            var labelY = gridY - 0.5;
1911            var labelWidth = 2 * labelPadding + context.measureText(label).width;
1912            context.beginPath();
1913            context.textBaseline = "bottom";
1914            context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
1915            context.fillStyle = "rgba(255, 255, 255, 0.75)";
1916            context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
1917            context.fillStyle = "rgb(64, 64, 64)";
1918            context.fillText(label, labelX + labelPadding, labelY);
1919            context.fill();
1920            context.closePath();
1921        }
1922    },
1923
1924    onResize: function()
1925    {
1926        this._updateOverviewCanvas = true;
1927        this._scheduleUpdate();
1928    },
1929
1930    _onWindowChanged: function()
1931    {
1932        if (!this._updateGridTimerId)
1933            this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
1934    },
1935
1936    _scheduleUpdate: function()
1937    {
1938        if (this._updateTimerId)
1939            return;
1940        this._updateTimerId = setTimeout(this.update.bind(this), 10);
1941    },
1942
1943    _updateBoundaries: function()
1944    {
1945        this._windowLeft = this._overviewGrid.windowLeft();
1946        this._windowRight = this._overviewGrid.windowRight();
1947        this._windowWidth = this._windowRight - this._windowLeft;
1948    },
1949
1950    update: function()
1951    {
1952        this._updateTimerId = null;
1953        if (!this.isShowing())
1954            return;
1955        this._updateBoundaries();
1956        this._overviewCalculator._updateBoundaries(this);
1957        this._overviewGrid.updateDividers(this._overviewCalculator);
1958        this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
1959    },
1960
1961    _updateGrid: function()
1962    {
1963        this._updateGridTimerId = 0;
1964        this._updateBoundaries();
1965        var ids = this._profileSamples.ids;
1966        var timestamps = this._profileSamples.timestamps;
1967        var sizes = this._profileSamples.sizes;
1968        var startTime = timestamps[0];
1969        var totalTime = this._profileSamples.totalTime;
1970        var timeLeft = startTime + totalTime * this._windowLeft;
1971        var timeRight = startTime + totalTime * this._windowRight;
1972        var minId = 0;
1973        var maxId = ids[ids.length - 1] + 1;
1974        var size = 0;
1975        for (var i = 0; i < timestamps.length; ++i) {
1976            if (!timestamps[i])
1977                continue;
1978            if (timestamps[i] > timeRight)
1979                break;
1980            maxId = ids[i];
1981            if (timestamps[i] < timeLeft) {
1982                minId = ids[i];
1983                continue;
1984            }
1985            size += sizes[i];
1986        }
1987
1988        this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size});
1989    },
1990
1991    __proto__: WebInspector.VBox.prototype
1992}
1993
1994
1995/**
1996 * @constructor
1997 */
1998WebInspector.HeapTrackingOverviewGrid.SmoothScale = function()
1999{
2000    this._lastUpdate = 0;
2001    this._currentScale = 0.0;
2002}
2003
2004WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = {
2005    /**
2006     * @param {number} target
2007     * @return {number}
2008     */
2009    nextScale: function(target) {
2010        target = target || this._currentScale;
2011        if (this._currentScale) {
2012            var now = Date.now();
2013            var timeDeltaMs = now - this._lastUpdate;
2014            this._lastUpdate = now;
2015            var maxChangePerSec = 20;
2016            var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
2017            var scaleChange = target / this._currentScale;
2018            this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
2019        } else
2020            this._currentScale = target;
2021        return this._currentScale;
2022    }
2023}
2024
2025
2026/**
2027 * @constructor
2028 * @implements {WebInspector.TimelineGrid.Calculator}
2029 */
2030WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function()
2031{
2032}
2033
2034WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = {
2035    /**
2036     * @return {number}
2037     */
2038    paddingLeft: function()
2039    {
2040        return 0;
2041    },
2042
2043    /**
2044     * @param {!WebInspector.HeapTrackingOverviewGrid} chart
2045     */
2046    _updateBoundaries: function(chart)
2047    {
2048        this._minimumBoundaries = 0;
2049        this._maximumBoundaries = chart._profileSamples.totalTime;
2050        this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
2051    },
2052
2053    /**
2054     * @param {number} time
2055     * @return {number}
2056     */
2057    computePosition: function(time)
2058    {
2059        return (time - this._minimumBoundaries) * this._xScaleFactor;
2060    },
2061
2062    /**
2063     * @param {number} value
2064     * @param {number=} precision
2065     * @return {string}
2066     */
2067    formatTime: function(value, precision)
2068    {
2069        return Number.secondsToString(value / 1000, !!precision);
2070    },
2071
2072    /**
2073     * @return {number}
2074     */
2075    maximumBoundary: function()
2076    {
2077        return this._maximumBoundaries;
2078    },
2079
2080    /**
2081     * @return {number}
2082     */
2083    minimumBoundary: function()
2084    {
2085        return this._minimumBoundaries;
2086    },
2087
2088    /**
2089     * @return {number}
2090     */
2091    zeroTime: function()
2092    {
2093        return this._minimumBoundaries;
2094    },
2095
2096    /**
2097     * @return {number}
2098     */
2099    boundarySpan: function()
2100    {
2101        return this._maximumBoundaries - this._minimumBoundaries;
2102    }
2103}
2104
2105
2106/**
2107 * @constructor
2108 * @extends {WebInspector.VBox}
2109 */
2110WebInspector.HeapSnapshotStatisticsView = function()
2111{
2112    WebInspector.VBox.call(this);
2113    this.setMinimumSize(50, 25);
2114    this._pieChart = new WebInspector.PieChart(150, WebInspector.HeapSnapshotStatisticsView._valueFormatter);
2115    this.element.appendChild(this._pieChart.element);
2116    this._labels = this.element.createChild("div", "heap-snapshot-stats-legend");
2117}
2118
2119/**
2120 * @param {number} value
2121 * @return {string}
2122 */
2123WebInspector.HeapSnapshotStatisticsView._valueFormatter = function(value)
2124{
2125    return WebInspector.UIString("%s KB", Number.withThousandsSeparator(Math.round(value / 1024)));
2126}
2127
2128WebInspector.HeapSnapshotStatisticsView.prototype = {
2129    /**
2130     * @param {number} value
2131     */
2132    setTotal: function(value)
2133    {
2134        this._pieChart.setTotal(value);
2135    },
2136
2137    /**
2138     * @param {number} value
2139     * @param {string} name
2140     * @param {string=} color
2141     */
2142    addRecord: function(value, name, color)
2143    {
2144        if (color)
2145            this._pieChart.addSlice(value, color);
2146
2147        var node = this._labels.createChild("div");
2148        var swatchDiv = node.createChild("div", "heap-snapshot-stats-swatch");
2149        var nameDiv = node.createChild("div", "heap-snapshot-stats-name");
2150        var sizeDiv = node.createChild("div", "heap-snapshot-stats-size");
2151        if (color)
2152            swatchDiv.style.backgroundColor = color;
2153        else
2154            swatchDiv.classList.add("heap-snapshot-stats-empty-swatch");
2155        nameDiv.textContent = name;
2156        sizeDiv.textContent = WebInspector.HeapSnapshotStatisticsView._valueFormatter(value);
2157    },
2158
2159    __proto__: WebInspector.VBox.prototype
2160}
2161
2162/**
2163 * @constructor
2164 * @extends {WebInspector.View}
2165 * @param {?WebInspector.Target} target
2166 */
2167WebInspector.HeapAllocationStackView = function(target)
2168{
2169    WebInspector.View.call(this);
2170    this._target = target;;
2171    this._linkifier = new WebInspector.Linkifier();
2172}
2173
2174WebInspector.HeapAllocationStackView.prototype = {
2175    /**
2176     * @param {!WebInspector.HeapSnapshotProxy} snapshot
2177     * @param {number} snapshotNodeIndex
2178     */
2179    setAllocatedObject: function(snapshot, snapshotNodeIndex)
2180    {
2181        this.clear();
2182        snapshot.allocationStack(snapshotNodeIndex, this._didReceiveAllocationStack.bind(this));
2183    },
2184
2185    clear: function()
2186    {
2187        this.element.removeChildren();
2188        this._linkifier.reset();
2189    },
2190
2191    /**
2192     * @param {?Array.<!WebInspector.HeapSnapshotCommon.AllocationStackFrame>} frames
2193     */
2194    _didReceiveAllocationStack: function(frames)
2195    {
2196        if (!frames) {
2197            var stackDiv = this.element.createChild("div", "no-heap-allocation-stack");
2198            stackDiv.createTextChild(WebInspector.UIString("Stack was not recorded for this object because it had been allocated before this profile recording started."));
2199            return;
2200        }
2201
2202        var stackDiv = this.element.createChild("div", "heap-allocation-stack");
2203        for (var i = 0; i < frames.length; i++) {
2204            var frame = frames[i];
2205            var frameDiv = stackDiv.createChild("div", "stack-frame");
2206            var name = frameDiv.createChild("div");
2207            name.textContent = frame.functionName;
2208            if (frame.scriptId) {
2209                var urlElement = this._linkifier.linkifyScriptLocation(this._target, String(frame.scriptId), frame.scriptName, frame.line - 1, frame.column - 1);
2210                frameDiv.appendChild(urlElement);
2211            }
2212        }
2213    },
2214
2215    __proto__: WebInspector.View.prototype
2216}
2217