• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
31WebInspector.HeapSnapshotContainmentDataGrid = function()
32{
33    var columns = {
34        object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true, sort: "ascending" },
35        shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
36        retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sortable: true }
37    };
38    WebInspector.DataGrid.call(this, columns);
39    this.addEventListener("sorting changed", this.sort, this);
40}
41
42WebInspector.HeapSnapshotContainmentDataGrid.prototype = {
43    _defaultPopulateCount: 100,
44
45    setDataSource: function(snapshotView, snapshot)
46    {
47        this.snapshotView = snapshotView;
48        this.snapshot = snapshot;
49        this.snapshotNodeIndex = this.snapshot.rootNodeIndex;
50        this._provider = this._createProvider(snapshot, this.snapshotNodeIndex);
51        this.sort();
52    }
53};
54
55MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotObjectNode.prototype, WebInspector.HeapSnapshotContainmentDataGrid.prototype);
56WebInspector.HeapSnapshotContainmentDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype;
57
58WebInspector.HeapSnapshotSortableDataGrid = function(columns)
59{
60    WebInspector.DataGrid.call(this, columns);
61    this.addEventListener("sorting changed", this.sortingChanged, this);
62}
63
64WebInspector.HeapSnapshotSortableDataGrid.prototype = {
65    sortingChanged: function()
66    {
67        var sortAscending = this.sortOrder === "ascending";
68        var sortColumnIdentifier = this.sortColumnIdentifier;
69        if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending)
70            return;
71        this._lastSortColumnIdentifier = sortColumnIdentifier;
72        this._lastSortAscending = sortAscending;
73        var sortFields = this._sortFields(sortColumnIdentifier, sortAscending);
74
75        function SortByTwoFields(nodeA, nodeB)
76        {
77            var field1 = nodeA[sortFields[0]];
78            var field2 = nodeB[sortFields[0]];
79            var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
80            if (!sortFields[1])
81                result = -result;
82            if (result !== 0)
83                return result;
84            field1 = nodeA[sortFields[2]];
85            field2 = nodeB[sortFields[2]];
86            result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
87            if (!sortFields[3])
88                result = -result;
89            return result;
90        }
91
92        this._performSorting(SortByTwoFields);
93    },
94
95    _performSorting: function(sortFunction)
96    {
97        this.dispatchEventToListeners("start sorting");
98        var children = this.children;
99        this.removeChildren();
100        children.sort(sortFunction);
101        for (var i = 0, l = children.length; i < l; ++i) {
102            var child = children[i];
103            this.appendChild(child);
104            if (child.expanded)
105                child.sort();
106        }
107        this.dispatchEventToListeners("sorting complete");
108    }
109};
110
111WebInspector.HeapSnapshotSortableDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype;
112
113WebInspector.HeapSnapshotConstructorsDataGrid = function()
114{
115    var columns = {
116        object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
117        count: { title: WebInspector.UIString("#"), width: "45px", sortable: true },
118        shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
119        retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true }
120    };
121    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
122}
123
124WebInspector.HeapSnapshotConstructorsDataGrid.prototype = {
125    _defaultPopulateCount: 100,
126
127    _sortFields: function(sortColumn, sortAscending)
128    {
129        return {
130            object: ["_name", sortAscending, "_count", false],
131            count: ["_count", sortAscending, "_name", true],
132            shallowSize: ["_shallowSize", sortAscending, "_name", true],
133            retainedSize: ["_retainedSize", sortAscending, "_name", true]
134        }[sortColumn];
135    },
136
137    setDataSource: function(snapshotView, snapshot)
138    {
139        this.snapshotView = snapshotView;
140        this.snapshot = snapshot;
141        this.populateChildren();
142    },
143
144    populateChildren: function()
145    {
146        function aggregatesReceived(aggregates)
147        {
148            for (var constructor in aggregates)
149                this.appendChild(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor]));
150            this.sortingChanged();
151        }
152        this.snapshot.aggregates(false, aggregatesReceived.bind(this));
153    }
154};
155
156WebInspector.HeapSnapshotConstructorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
157
158WebInspector.HeapSnapshotDiffDataGrid = function()
159{
160    var columns = {
161        object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
162        addedCount: { title: WebInspector.UIString("# New"), width: "72px", sortable: true, sort: "descending" },
163        removedCount: { title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true },
164        // \u0394 is a Greek delta letter.
165        countDelta: { title: "\u0394", width: "40px", sortable: true },
166        addedSize: { title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true },
167        removedSize: { title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true },
168        sizeDelta: { title: "\u0394", width: "72px", sortable: true }
169    };
170    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
171}
172
173WebInspector.HeapSnapshotDiffDataGrid.prototype = {
174    _defaultPopulateCount: 50,
175
176    _sortFields: function(sortColumn, sortAscending)
177    {
178        return {
179            object: ["_name", sortAscending, "_count", false],
180            addedCount: ["_addedCount", sortAscending, "_name", true],
181            removedCount: ["_removedCount", sortAscending, "_name", true],
182            countDelta: ["_countDelta", sortAscending, "_name", true],
183            addedSize: ["_addedSize", sortAscending, "_name", true],
184            removedSize: ["_removedSize", sortAscending, "_name", true],
185            sizeDelta: ["_sizeDelta", sortAscending, "_name", true]
186        }[sortColumn];
187    },
188
189    setDataSource: function(snapshotView, snapshot)
190    {
191        this.snapshotView = snapshotView;
192        this.snapshot = snapshot;
193    },
194
195    setBaseDataSource: function(baseSnapshot)
196    {
197        this.baseSnapshot = baseSnapshot;
198        this.removeChildren();
199        if (this.baseSnapshot === this.snapshot)
200            return;
201        this.populateChildren();
202    },
203
204    populateChildren: function()
205    {
206        function baseAggregatesReceived(baseClasses)
207        {
208            function aggregatesReceived(classes)
209            {
210                var nodeCount = 0;
211                function addNodeIfNonZeroDiff(node, zeroDiff)
212                {
213                    if (!zeroDiff)
214                        this.appendChild(node);
215                    if (!--nodeCount)
216                        this.sortingChanged();
217                }
218                for (var clss in baseClasses) {
219                    var node = new WebInspector.HeapSnapshotDiffNode(this, clss, baseClasses[clss], classes[clss]);
220                    ++nodeCount;
221                    node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node));
222                }
223                for (clss in classes) {
224                    if (!(clss in baseClasses)) {
225                        var node = new WebInspector.HeapSnapshotDiffNode(this, clss, null, classes[clss]);
226                        ++nodeCount;
227                        node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node));
228                    }
229                }
230            }
231            this.snapshot.aggregates(true, aggregatesReceived.bind(this));
232        }
233        this.baseSnapshot.aggregates(true, baseAggregatesReceived.bind(this));
234    }
235};
236
237WebInspector.HeapSnapshotDiffDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
238
239WebInspector.HeapSnapshotDominatorsDataGrid = function()
240{
241    var columns = {
242        object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true },
243        shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
244        retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true }
245    };
246    WebInspector.DataGrid.call(this, columns);
247    this.addEventListener("sorting changed", this.sort, this);
248}
249
250WebInspector.HeapSnapshotDominatorsDataGrid.prototype = {
251    _defaultPopulateCount: 25,
252
253    setDataSource: function(snapshotView, snapshot)
254    {
255        this.snapshotView = snapshotView;
256        this.snapshot = snapshot;
257        this.snapshotNodeIndex = this.snapshot.rootNodeIndex;
258        this._provider = this._createProvider(snapshot, this.snapshotNodeIndex);
259        this.sort();
260    }
261};
262
263MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotDominatorObjectNode.prototype, WebInspector.HeapSnapshotDominatorsDataGrid.prototype);
264WebInspector.HeapSnapshotDominatorsDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype;
265
266WebInspector.HeapSnapshotRetainingPathsList = function()
267{
268    var columns = {
269        path: { title: WebInspector.UIString("Retaining path"), sortable: true },
270        len: { title: WebInspector.UIString("Length"), width: "90px", sortable: true, sort: "ascending" }
271    };
272    WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
273    this._defaultPopulateCount = 100;
274}
275
276WebInspector.HeapSnapshotRetainingPathsList.prototype = {
277    _sortFields: function(sortColumn, sortAscending)
278    {
279        return {
280            path: ["path", sortAscending, "len", true],
281            len: ["len", sortAscending, "path", true]
282        }[sortColumn];
283    },
284
285    _resetPaths: function()
286    {
287        this._setRootChildrenForFinder();
288        this.removeChildren();
289        this._counter = 0;
290        this.showNext(this._defaultPopulateCount);
291    },
292
293    setDataSource: function(snapshotView, snapshot, nodeIndex, prefix)
294    {
295        this.snapshotView = snapshotView;
296        this._prefix = prefix;
297
298        if (this.pathFinder)
299            this.searchCancelled();
300        this.pathFinder = snapshot.createPathFinder(nodeIndex);
301        this._resetPaths();
302    },
303
304    refresh: function()
305    {
306        delete this._cancel;
307        this._resetPaths();
308    },
309
310    showNext: function(pathsCount)
311    {
312        WebInspector.PleaseWaitMessage.prototype.show(this.element, this.searchCancelled.bind(this, pathsCount));
313
314        function pathFound(result)
315        {
316            if (result === null) {
317                WebInspector.PleaseWaitMessage.prototype.hide();
318                if (!this.children.length)
319                    this.appendChild(new WebInspector.DataGridNode({path:WebInspector.UIString("Can't find any paths."), len:""}, false));
320                return;
321            } else if (result !== false) {
322                if (this._prefix)
323                    result.path = this._prefix + result.path;
324                this.appendChild(new WebInspector.DataGridNode(result, false));
325                ++this._counter;
326            }
327            setTimeout(startSearching.bind(this), 0);
328        }
329
330        function startSearching()
331        {
332            if (this._cancel === this.pathFinder)
333                return;
334            delete this._cancel;
335            if (this._counter < pathsCount)
336                this.pathFinder.findNext(pathFound.bind(this));
337            else {
338                this.searchCancelled.call(this, pathsCount);
339                delete this._cancel;
340            }
341        }
342        setTimeout(startSearching.bind(this), 0);
343    },
344
345    searchCancelled: function(pathsCount)
346    {
347        WebInspector.PleaseWaitMessage.prototype.hide();
348        this._counter = 0;
349        this._cancel = this.pathFinder;
350        if (pathsCount) {
351            this.appendChild(new WebInspector.ShowMoreDataGridNode(this.showNext.bind(this), pathsCount));
352            this.sortingChanged();
353        }
354    },
355
356    _setRootChildrenForFinder: function()
357    {
358        function FilterDOMWindow(node)
359        {
360            return node.name === "DOMWindow";
361        }
362
363        if (this.snapshotView.isTracingToWindowObjects)
364            this.pathFinder.updateRoots(FilterDOMWindow);
365        else
366            this.pathFinder.updateRoots();
367    },
368
369    _performSorting: function(sortFunction)
370    {
371        function DataExtractorWrapper(nodeA, nodeB)
372        {
373            return sortFunction(nodeA.data, nodeB.data);
374        }
375
376        this.sortNodes(DataExtractorWrapper);
377    }
378};
379
380WebInspector.HeapSnapshotRetainingPathsList.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
381
382WebInspector.DetailedHeapshotView = function(parent, profile)
383{
384    WebInspector.View.call(this);
385
386    this.element.addStyleClass("detailed-heapshot-view");
387
388    this.parent = parent;
389    this.parent.addEventListener("profile added", this._updateBaseOptions, this);
390
391    this.showCountAsPercent = false;
392    this.showShallowSizeAsPercent = false;
393    this.showRetainedSizeAsPercent = false;
394
395    this.containmentView = new WebInspector.View();
396    this.containmentView.element.addStyleClass("view");
397    this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
398    this.containmentDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true);
399    this.containmentView.element.appendChild(this.containmentDataGrid.element);
400    this.element.appendChild(this.containmentView.element);
401
402    this.constructorsView = new WebInspector.View();
403    this.constructorsView.element.addStyleClass("view");
404    this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
405    this.constructorsDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true);
406    this.constructorsView.element.appendChild(this.constructorsDataGrid.element);
407    this.element.appendChild(this.constructorsView.element);
408
409    this.diffView = new WebInspector.View();
410    this.diffView.element.addStyleClass("view");
411    this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
412    this.diffDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true);
413    this.diffView.element.appendChild(this.diffDataGrid.element);
414    this.element.appendChild(this.diffView.element);
415
416    this.dominatorView = new WebInspector.View();
417    this.dominatorView.element.addStyleClass("view");
418    this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
419    this.dominatorDataGrid.element.addEventListener("click", this._mouseClickInContainmentGrid.bind(this), true);
420    this.dominatorView.element.appendChild(this.dominatorDataGrid.element);
421    this.element.appendChild(this.dominatorView.element);
422
423    var retainmentView = new WebInspector.View();
424    retainmentView.element.addStyleClass("view");
425    retainmentView.element.addStyleClass("retaining-paths-view");
426    var retainingPathsTitleDiv = document.createElement("div");
427    retainingPathsTitleDiv.className = "title";
428    var retainingPathsTitle = document.createElement("span");
429    retainingPathsTitle.textContent = WebInspector.UIString("Paths from the selected object");
430    this.retainingPathsRoot = document.createElement("select");
431    this.retainingPathsRoot.className = "status-bar-item";
432    this.retainingPathsRoot.addEventListener("change", this._changeRetainingPathsRoot.bind(this), false);
433    var toGCRootsTraceOption = document.createElement("option");
434    toGCRootsTraceOption.label = WebInspector.UIString("to GC roots");
435    var toWindowObjectsTraceOption = document.createElement("option");
436    toWindowObjectsTraceOption.label = WebInspector.UIString("to window objects");
437    this.retainingPathsRoot.appendChild(toGCRootsTraceOption);
438    this.retainingPathsRoot.appendChild(toWindowObjectsTraceOption);
439    retainingPathsTitleDiv.appendChild(retainingPathsTitle);
440    retainingPathsTitleDiv.appendChild(this.retainingPathsRoot);
441    retainmentView.element.appendChild(retainingPathsTitleDiv);
442    this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainingPathsList();
443    retainmentView.element.appendChild(this.retainmentDataGrid.element);
444    retainmentView.visible = true;
445    this.element.appendChild(retainmentView.element);
446
447    this.dataGrid = this.constructorsDataGrid;
448    this.currentView = this.constructorsView;
449
450    this.viewSelectElement = document.createElement("select");
451    this.viewSelectElement.className = "status-bar-item";
452    this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false);
453
454    this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
455                  {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
456                  {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid},
457                  {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}];
458    this.views.current = 0;
459    for (var i = 0; i < this.views.length; ++i) {
460        var view = this.views[i];
461        var option = document.createElement("option");
462        option.label = WebInspector.UIString(view.title);
463        this.viewSelectElement.appendChild(option);
464    }
465
466    this._profileUid = profile.uid;
467
468    this.baseSelectElement = document.createElement("select");
469    this.baseSelectElement.className = "status-bar-item hidden";
470    this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
471    this._updateBaseOptions();
472
473    this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
474    this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
475    this.helpButton = new WebInspector.StatusBarButton("", "heapshot-help-status-bar-item status-bar-item");
476    this.helpButton.addEventListener("click", this._helpClicked.bind(this), false);
477
478    var popoverHelper = new WebInspector.PopoverHelper(this.element, this._getHoverAnchor.bind(this), this._showStringContentPopup.bind(this));
479
480    this._loadProfile(this._profileUid, profileCallback.bind(this));
481
482    function profileCallback()
483    {
484        var list = this._profiles();
485        var profileIndex;
486        for (var i = 0; i < list.length; ++i)
487            if (list[i].uid === this._profileUid) {
488                profileIndex = i;
489                break;
490            }
491        if (profileIndex > 0)
492            this.baseSelectElement.selectedIndex = profileIndex - 1;
493        else
494            this.baseSelectElement.selectedIndex = profileIndex;
495        this.dataGrid.setDataSource(this, this.profileWrapper);
496        this._updatePercentButton();
497    }
498}
499
500WebInspector.DetailedHeapshotView.prototype = {
501    dispose: function()
502    {
503        if (this._profileWrapper)
504            this._profileWrapper.dispose();
505        if (this._baseProfileWrapper)
506            this._baseProfileWrapper.dispose();
507    },
508
509    get statusBarItems()
510    {
511        return [this.viewSelectElement, this.baseSelectElement, this.percentButton.element, this.helpButton.element];
512    },
513
514    get profile()
515    {
516        return this.parent.getProfile(WebInspector.HeapSnapshotProfileType.TypeId, this._profileUid);
517    },
518
519    get profileWrapper()
520    {
521        if (!this._profileWrapper)
522            this._profileWrapper = this.profile.proxy;
523        return this._profileWrapper;
524    },
525
526    get baseProfile()
527    {
528        return this.parent.getProfile(WebInspector.HeapSnapshotProfileType.TypeId, this._baseProfileUid);
529    },
530
531    get baseProfileWrapper()
532    {
533        if (!this._baseProfileWrapper)
534            this._baseProfileWrapper = this.baseProfile.proxy;
535        return this._baseProfileWrapper;
536    },
537
538    show: function(parentElement)
539    {
540        WebInspector.View.prototype.show.call(this, parentElement);
541        if (!this.profileWrapper.loaded)
542            this._loadProfile(this._profileUid, profileCallback1.bind(this));
543        else
544            profileCallback1.call(this);
545
546        function profileCallback1() {
547            if (this.baseProfile && !this.baseProfileWrapper.loaded)
548                this._loadProfile(this._baseProfileUid, profileCallback2.bind(this));
549            else
550                profileCallback2.call(this);
551        }
552
553        function profileCallback2() {
554            this.currentView.show();
555            this.dataGrid.updateWidths();
556        }
557    },
558
559    hide: function()
560    {
561        WebInspector.View.prototype.hide.call(this);
562        this._currentSearchResultIndex = -1;
563    },
564
565    resize: function()
566    {
567        if (this.dataGrid)
568            this.dataGrid.updateWidths();
569    },
570
571    refreshShowAsPercents: function()
572    {
573        this._updatePercentButton();
574        this.refreshVisibleData();
575    },
576
577    searchCanceled: function()
578    {
579        if (this._searchResults) {
580            for (var i = 0; i < this._searchResults.length; ++i) {
581                var node = this._searchResults[i].node;
582                delete node._searchMatched;
583                node.refresh();
584            }
585        }
586
587        delete this._searchFinishedCallback;
588        this._currentSearchResultIndex = -1;
589        this._searchResults = [];
590    },
591
592    performSearch: function(query, finishedCallback)
593    {
594        // Call searchCanceled since it will reset everything we need before doing a new search.
595        this.searchCanceled();
596
597        query = query.trim();
598
599        if (!query.length)
600            return;
601        if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
602            return;
603
604        this._searchFinishedCallback = finishedCallback;
605
606        function matchesByName(gridNode) {
607            return ("name" in gridNode) && gridNode.name.hasSubstring(query, true);
608        }
609
610        function matchesById(gridNode) {
611            return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query;
612        }
613
614        var matchPredicate;
615        if (query.charAt(0) !== "@")
616            matchPredicate = matchesByName;
617        else {
618            query = parseInt(query.substring(1), 10);
619            matchPredicate = matchesById;
620        }
621
622        function matchesQuery(gridNode)
623        {
624            delete gridNode._searchMatched;
625            if (matchPredicate(gridNode)) {
626                gridNode._searchMatched = true;
627                gridNode.refresh();
628                return true;
629            }
630            return false;
631        }
632
633        var current = this.dataGrid.children[0];
634        var depth = 0;
635        var info = {};
636
637        // Restrict to type nodes and instances.
638        const maxDepth = 1;
639
640        while (current) {
641            if (matchesQuery(current))
642                this._searchResults.push({ node: current });
643            current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
644            depth += info.depthChange;
645        }
646
647        finishedCallback(this, this._searchResults.length);
648    },
649
650    jumpToFirstSearchResult: function()
651    {
652        if (!this._searchResults || !this._searchResults.length)
653            return;
654        this._currentSearchResultIndex = 0;
655        this._jumpToSearchResult(this._currentSearchResultIndex);
656    },
657
658    jumpToLastSearchResult: function()
659    {
660        if (!this._searchResults || !this._searchResults.length)
661            return;
662        this._currentSearchResultIndex = (this._searchResults.length - 1);
663        this._jumpToSearchResult(this._currentSearchResultIndex);
664    },
665
666    jumpToNextSearchResult: function()
667    {
668        if (!this._searchResults || !this._searchResults.length)
669            return;
670        if (++this._currentSearchResultIndex >= this._searchResults.length)
671            this._currentSearchResultIndex = 0;
672        this._jumpToSearchResult(this._currentSearchResultIndex);
673    },
674
675    jumpToPreviousSearchResult: function()
676    {
677        if (!this._searchResults || !this._searchResults.length)
678            return;
679        if (--this._currentSearchResultIndex < 0)
680            this._currentSearchResultIndex = (this._searchResults.length - 1);
681        this._jumpToSearchResult(this._currentSearchResultIndex);
682    },
683
684    showingFirstSearchResult: function()
685    {
686        return (this._currentSearchResultIndex === 0);
687    },
688
689    showingLastSearchResult: function()
690    {
691        return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
692    },
693
694    _jumpToSearchResult: function(index)
695    {
696        var searchResult = this._searchResults[index];
697        if (!searchResult)
698            return;
699
700        var node = searchResult.node;
701        node.reveal();
702        node.select();
703    },
704
705    refreshVisibleData: function()
706    {
707        var child = this.dataGrid.children[0];
708        while (child) {
709            child.refresh();
710            child = child.traverseNextNode(false, null, true);
711        }
712    },
713
714    _changeBase: function()
715    {
716        if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
717            return;
718
719        this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid;
720        this._loadProfile(this._baseProfileUid, baseProfileLoaded.bind(this));
721
722        function baseProfileLoaded()
723        {
724            delete this._baseProfileWrapper;
725            this.baseProfile._lastShown = Date.now();
726            WebInspector.PleaseWaitMessage.prototype.startAction(this.currentView.element, showDiffData.bind(this));
727        }
728
729        function showDiffData()
730        {
731            this.diffDataGrid.setBaseDataSource(this.baseProfileWrapper);
732        }
733
734        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
735            return;
736
737        // The current search needs to be performed again. First negate out previous match
738        // count by calling the search finished callback with a negative number of matches.
739        // Then perform the search again with the same query and callback.
740        this._searchFinishedCallback(this, -this._searchResults.length);
741        this.performSearch(this.currentQuery, this._searchFinishedCallback);
742    },
743
744    _profiles: function()
745    {
746        return WebInspector.panels.profiles.getProfiles(WebInspector.HeapSnapshotProfileType.TypeId);
747    },
748
749    _loadProfile: function(profileUid, callback)
750    {
751        WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback);
752    },
753
754    isDetailedSnapshot: function(snapshot)
755    {
756        var s = new WebInspector.HeapSnapshot(snapshot);
757        for (var iter = s.rootNode.edges; iter.hasNext(); iter.next())
758            if (iter.edge.node.name === "(GC roots)")
759                return true;
760        return false;
761    },
762
763    processLoadedSnapshot: function(profile, snapshot)
764    {
765        profile.nodes = snapshot.nodes;
766        profile.strings = snapshot.strings;
767        var s = new WebInspector.HeapSnapshot(profile);
768        profile.sideBarElement.subtitle = Number.bytesToString(s.totalSize);
769    },
770
771    _mouseClickInContainmentGrid: function(event)
772    {
773        var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
774        if (!cell || (!cell.hasStyleClass("object-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
775            return;
776        var row = event.target.enclosingNodeOrSelfWithNodeName("tr");
777        if (!row)
778            return;
779        var nodeItem = row._dataGridNode;
780        if (!nodeItem || nodeItem.isEventWithinDisclosureTriangle(event) || !nodeItem.snapshotNodeIndex)
781            return;
782
783        this.retainmentDataGrid.setDataSource(this, nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex, nodeItem.isDeletedNode ? this.baseSelectElement.childNodes[this.baseSelectElement.selectedIndex].label + " | " : "");
784    },
785
786    _changeView: function(event)
787    {
788        if (!event || !this._profileUid)
789            return;
790        if (event.target.selectedIndex === this.views.current)
791            return;
792
793        this.views.current = event.target.selectedIndex;
794        this.currentView.hide();
795        var view = this.views[this.views.current];
796        this.currentView = view.view;
797        this.dataGrid = view.grid;
798        this.currentView.show();
799        this.refreshVisibleData();
800        if (this.currentView === this.diffView) {
801            this.baseSelectElement.removeStyleClass("hidden");
802            if (!this.dataGrid.snapshotView) {
803                this.dataGrid.setDataSource(this, this.profileWrapper);
804                this._changeBase();
805            }
806        } else {
807            this.baseSelectElement.addStyleClass("hidden");
808            if (!this.dataGrid.snapshotView)
809                WebInspector.PleaseWaitMessage.prototype.startAction(this.currentView.element, loadData.bind(this));
810        }
811
812        function loadData()
813        {
814            this.dataGrid.setDataSource(this, this.profileWrapper);
815        }
816
817        if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
818            return;
819
820        // The current search needs to be performed again. First negate out previous match
821        // count by calling the search finished callback with a negative number of matches.
822        // Then perform the search again the with same query and callback.
823        this._searchFinishedCallback(this, -this._searchResults.length);
824        this.performSearch(this.currentQuery, this._searchFinishedCallback);
825    },
826
827    _changeRetainingPathsRoot: function(event)
828    {
829        if (!event)
830            return;
831        this.retainmentDataGrid.refresh();
832    },
833
834    _getHoverAnchor: function(target)
835    {
836        var span = target.enclosingNodeOrSelfWithNodeName("span");
837        if (!span || !span.hasStyleClass("console-formatted-string"))
838            return;
839        var row = target.enclosingNodeOrSelfWithNodeName("tr");
840        if (!row)
841            return;
842        var gridNode = row._dataGridNode;
843        if (!gridNode.snapshotNodeIndex)
844            return;
845        span.snapshotNodeIndex = gridNode.snapshotNodeIndex;
846        return span;
847    },
848
849    get isTracingToWindowObjects()
850    {
851        return this.retainingPathsRoot.selectedIndex === 1;
852    },
853
854    get _isShowingAsPercent()
855    {
856        return this.showCountAsPercent && this.showShallowSizeAsPercent && this.showRetainedSizeAsPercent;
857    },
858
859    _percentClicked: function(event)
860    {
861        var currentState = this._isShowingAsPercent;
862        this.showCountAsPercent = !currentState;
863        this.showShallowSizeAsPercent = !currentState;
864        this.showRetainedSizeAsPercent = !currentState;
865        this.refreshShowAsPercents();
866    },
867
868    _showStringContentPopup: function(span)
869    {
870        var snapshotNode = new WebInspector.HeapSnapshotNode(this.profileWrapper, span.snapshotNodeIndex);
871        var stringContentElement = document.createElement("span");
872        stringContentElement.className = "monospace console-formatted-string";
873        stringContentElement.style.whiteSpace = "pre";
874        stringContentElement.textContent = "\"" + snapshotNode.name + "\"";
875        var popover = new WebInspector.Popover(stringContentElement);
876        popover.show(span);
877        return popover;
878    },
879
880    _helpClicked: function(event)
881    {
882        if (!this.helpPopover) {
883            var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"),
884                            "0:", "console-formatted-name", WebInspector.UIString("element"),
885                            "a:", "console-formatted-number", WebInspector.UIString("context var"),
886                            "a:", "console-formatted-null", WebInspector.UIString("system prop")];
887            var objTypes = [" a ", "console-formatted-object", "Object",
888                            "\"a\"", "console-formatted-string", "String",
889                            "/a/", "console-formatted-string", "RegExp",
890                            "a()", "console-formatted-function", "Function",
891                            "a[]", "console-formatted-object", "Array",
892                            "num", "console-formatted-number", "Number",
893                            " a ", "console-formatted-null", "System"];
894
895            var contentElement = document.createElement("table");
896            contentElement.className = "heapshot-help";
897            var headerRow = document.createElement("tr");
898            var propsHeader = document.createElement("th");
899            propsHeader.textContent = WebInspector.UIString("Property types:");
900            headerRow.appendChild(propsHeader);
901            var objsHeader = document.createElement("th");
902            objsHeader.textContent = WebInspector.UIString("Object types:");
903            headerRow.appendChild(objsHeader);
904            contentElement.appendChild(headerRow);
905            var len = Math.max(refTypes.length, objTypes.length);
906            for (var i = 0; i < len; i += 3) {
907                var row = document.createElement("tr");
908                var refCell = document.createElement("td");
909                if (refTypes[i])
910                    appendHelp(refTypes, i, refCell);
911                row.appendChild(refCell);
912                var objCell = document.createElement("td");
913                if (objTypes[i])
914                    appendHelp(objTypes, i, objCell);
915                row.appendChild(objCell);
916                contentElement.appendChild(row);
917            }
918            this.helpPopover = new WebInspector.Popover(contentElement);
919
920            function appendHelp(help, index, cell)
921            {
922                var div = document.createElement("div");
923                div.className = "source-code event-properties";
924                var name = document.createElement("span");
925                name.textContent = help[index];
926                name.className = help[index + 1];
927                div.appendChild(name);
928                var desc = document.createElement("span");
929                desc.textContent = " " + help[index + 2];
930                div.appendChild(desc);
931                cell.appendChild(div);
932            }
933        }
934        if (this.helpPopover.visible)
935            this.helpPopover.hide();
936        else
937            this.helpPopover.show(this.helpButton.element);
938    },
939
940    _updateBaseOptions: function()
941    {
942        var list = this._profiles();
943        // We're assuming that snapshots can only be added.
944        if (this.baseSelectElement.length === list.length)
945            return;
946
947        for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
948            var baseOption = document.createElement("option");
949            var title = list[i].title;
950            if (!title.indexOf(UserInitiatedProfileName))
951                title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
952            baseOption.label = title;
953            this.baseSelectElement.appendChild(baseOption);
954        }
955    },
956
957    _updatePercentButton: function()
958    {
959        if (this._isShowingAsPercent) {
960            this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
961            this.percentButton.toggled = true;
962        } else {
963            this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
964            this.percentButton.toggled = false;
965        }
966    }
967};
968
969WebInspector.DetailedHeapshotView.prototype.__proto__ = WebInspector.View.prototype;
970
971WebInspector.DetailedHeapshotView.prototype.showHiddenData = true;
972
973WebInspector.DetailedHeapshotProfileType = function()
974{
975    WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
976}
977
978WebInspector.DetailedHeapshotProfileType.prototype = {
979    get buttonTooltip()
980    {
981        return WebInspector.UIString("Take heap snapshot.");
982    },
983
984    get buttonStyle()
985    {
986        return "heap-snapshot-status-bar-item status-bar-item";
987    },
988
989    buttonClicked: function()
990    {
991        WebInspector.panels.profiles.takeHeapSnapshot(true);
992    },
993
994    get welcomeMessage()
995    {
996        return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
997    },
998
999    createSidebarTreeElementForProfile: function(profile)
1000    {
1001        return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
1002    },
1003
1004    createView: function(profile)
1005    {
1006        return new WebInspector.DetailedHeapshotView(WebInspector.panels.profiles, profile);
1007    }
1008}
1009
1010WebInspector.DetailedHeapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
1011