2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
31 WebInspector.HeapSnapshotSortableDataGrid = function(columns)
33 WebInspector.DataGrid.call(this, columns);
34 this.addEventListener("sorting changed", this.sortingChanged, this);
37 WebInspector.HeapSnapshotSortableDataGrid.prototype = {
40 for (var i = 0, l = this.children.length; i < l; ++i)
41 this.children[i].dispose();
44 resetSortingCache: function()
46 delete this._lastSortColumnIdentifier;
47 delete this._lastSortAscending;
50 sortingChanged: function()
52 var sortAscending = this.sortOrder === "ascending";
53 var sortColumnIdentifier = this.sortColumnIdentifier;
54 if (this._lastSortColumnIdentifier === sortColumnIdentifier && this._lastSortAscending === sortAscending)
56 this._lastSortColumnIdentifier = sortColumnIdentifier;
57 this._lastSortAscending = sortAscending;
58 var sortFields = this._sortFields(sortColumnIdentifier, sortAscending);
60 function SortByTwoFields(nodeA, nodeB)
62 var field1 = nodeA[sortFields[0]];
63 var field2 = nodeB[sortFields[0]];
64 var result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
69 field1 = nodeA[sortFields[2]];
70 field2 = nodeB[sortFields[2]];
71 result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
76 this._performSorting(SortByTwoFields);
79 _performSorting: function(sortFunction)
81 this.recursiveSortingEnter();
82 var children = this.children;
83 this.removeChildren();
84 children.sort(sortFunction);
85 for (var i = 0, l = children.length; i < l; ++i) {
86 var child = children[i];
87 this.appendChild(child);
91 this.recursiveSortingLeave();
94 recursiveSortingEnter: function()
96 if (!("_recursiveSortingDepth" in this))
97 this._recursiveSortingDepth = 1;
99 ++this._recursiveSortingDepth;
102 recursiveSortingLeave: function()
104 if (!("_recursiveSortingDepth" in this))
106 if (!--this._recursiveSortingDepth) {
107 delete this._recursiveSortingDepth;
108 this.dispatchEventToListeners("sorting complete");
113 WebInspector.HeapSnapshotSortableDataGrid.prototype.__proto__ = WebInspector.DataGrid.prototype;
115 WebInspector.HeapSnapshotContainmentDataGrid = function()
118 object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true, sort: "ascending" },
119 shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
120 retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sortable: true }
122 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
125 WebInspector.HeapSnapshotContainmentDataGrid.prototype = {
126 _defaultPopulateCount: 100,
128 expandRoute: function(route)
130 function nextStep(parent, hopIndex)
132 if (hopIndex >= route.length) {
133 parent.element.scrollIntoViewIfNeeded(true);
137 var nodeIndex = route[hopIndex];
138 for (var i = 0, l = parent.children.length; i < l; ++i) {
139 var child = parent.children[i];
140 if (child.snapshotNodeIndex === nodeIndex) {
142 nextStep(child, hopIndex + 1);
144 function afterExpand()
146 child.removeEventListener("populate complete", afterExpand, null);
147 var lastChild = child.children[child.children.length - 1];
148 if (!lastChild.showAll)
149 nextStep(child, hopIndex + 1);
151 child.addEventListener("populate complete", afterExpand, null);
152 lastChild.showAll.click();
155 child.addEventListener("populate complete", afterExpand, null);
165 setDataSource: function(snapshotView, snapshot)
167 this.snapshotView = snapshotView;
168 this.snapshot = snapshot;
169 this.snapshotNodeIndex = this.snapshot.rootNodeIndex;
170 this._provider = this._createProvider(snapshot, this.snapshotNodeIndex);
174 sortingChanged: function()
180 MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotObjectNode.prototype, WebInspector.HeapSnapshotContainmentDataGrid.prototype);
181 WebInspector.HeapSnapshotContainmentDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
183 WebInspector.HeapSnapshotConstructorsDataGrid = function()
186 object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
187 count: { title: WebInspector.UIString("#"), width: "45px", sortable: true },
188 shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
189 retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true }
191 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
192 this._filterProfileIndex = -1;
195 WebInspector.HeapSnapshotConstructorsDataGrid.prototype = {
196 _defaultPopulateCount: 100,
198 _sortFields: function(sortColumn, sortAscending)
201 object: ["_name", sortAscending, "_count", false],
202 count: ["_count", sortAscending, "_name", true],
203 shallowSize: ["_shallowSize", sortAscending, "_name", true],
204 retainedSize: ["_retainedSize", sortAscending, "_name", true]
208 setDataSource: function(snapshotView, snapshot)
210 this.snapshotView = snapshotView;
211 this.snapshot = snapshot;
212 if (this._filterProfileIndex === -1)
213 this.populateChildren();
216 populateChildren: function()
218 function aggregatesReceived(key, aggregates)
220 for (var constructor in aggregates)
221 this.appendChild(new WebInspector.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], key));
222 this.sortingChanged();
225 if (this._filterProfileIndex === -1) {
226 this.snapshot.aggregates(false, "allObjects", null, aggregatesReceived.bind(this, "allObjects"));
231 this.removeChildren();
232 this.resetSortingCache();
234 var key = this._minNodeId + ".." + this._maxNodeId;
235 var filter = "function(node) { var id = node.id; return id > " + this._minNodeId + " && id <= " + this._maxNodeId + "; }";
236 this.snapshot.aggregates(false, key, filter, aggregatesReceived.bind(this, key));
239 _filterSelectIndexChanged: function(loader, profileIndex)
241 this._filterProfileIndex = profileIndex;
243 delete this._maxNodeId;
244 delete this._minNodeId;
246 if (this._filterProfileIndex === -1) {
247 this.populateChildren();
251 function firstSnapshotLoaded(snapshot)
253 this._maxNodeId = snapshot.maxNodeId;
254 if (profileIndex > 0)
255 loader(profileIndex - 1, secondSnapshotLoaded.bind(this));
258 this.populateChildren();
262 function secondSnapshotLoaded(snapshot)
264 this._minNodeId = snapshot.maxNodeId;
265 this.populateChildren();
268 loader(profileIndex, firstSnapshotLoaded.bind(this));
272 WebInspector.HeapSnapshotConstructorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
274 WebInspector.HeapSnapshotDiffDataGrid = function()
277 object: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
278 addedCount: { title: WebInspector.UIString("# New"), width: "72px", sortable: true, sort: "descending" },
279 removedCount: { title: WebInspector.UIString("# Deleted"), width: "72px", sortable: true },
280 // \u0394 is a Greek delta letter.
281 countDelta: { title: "\u0394", width: "40px", sortable: true },
282 addedSize: { title: WebInspector.UIString("Alloc. Size"), width: "72px", sortable: true },
283 removedSize: { title: WebInspector.UIString("Freed Size"), width: "72px", sortable: true },
284 sizeDelta: { title: "\u0394", width: "72px", sortable: true }
286 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
289 WebInspector.HeapSnapshotDiffDataGrid.prototype = {
290 _defaultPopulateCount: 50,
292 _sortFields: function(sortColumn, sortAscending)
295 object: ["_name", sortAscending, "_count", false],
296 addedCount: ["_addedCount", sortAscending, "_name", true],
297 removedCount: ["_removedCount", sortAscending, "_name", true],
298 countDelta: ["_countDelta", sortAscending, "_name", true],
299 addedSize: ["_addedSize", sortAscending, "_name", true],
300 removedSize: ["_removedSize", sortAscending, "_name", true],
301 sizeDelta: ["_sizeDelta", sortAscending, "_name", true]
305 setDataSource: function(snapshotView, snapshot)
307 this.snapshotView = snapshotView;
308 this.snapshot = snapshot;
311 _baseProfileIndexChanged: function(loader, profileIndex)
313 loader(profileIndex, this.setBaseDataSource.bind(this));
316 setBaseDataSource: function(baseSnapshot)
318 this.baseSnapshot = baseSnapshot;
320 this.removeChildren();
321 this.resetSortingCache();
322 if (this.baseSnapshot === this.snapshot) {
323 this.dispatchEventToListeners("sorting complete");
326 this.populateChildren();
329 populateChildren: function()
331 function baseAggregatesReceived(baseClasses)
333 function aggregatesReceived(classes)
337 for (var clss in baseClasses)
338 nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, baseClasses[clss], classes[clss]));
339 for (clss in classes) {
340 if (!(clss in baseClasses))
341 nodes.push(new WebInspector.HeapSnapshotDiffNode(this, clss, null, classes[clss]));
343 nodeCount = nodes.length;
344 function addNodeIfNonZeroDiff(boundNode, zeroDiff)
347 this.appendChild(boundNode);
349 this.sortingChanged();
351 for (var i = 0, l = nodes.length; i < l; ++i) {
353 node.calculateDiff(this, addNodeIfNonZeroDiff.bind(this, node));
356 this.snapshot.aggregates(true, "allObjects", null, aggregatesReceived.bind(this));
358 this.baseSnapshot.aggregates(true, "allObjects", null, baseAggregatesReceived.bind(this));
362 WebInspector.HeapSnapshotDiffDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
364 WebInspector.HeapSnapshotDominatorsDataGrid = function()
367 object: { title: WebInspector.UIString("Object"), disclosure: true, sortable: true },
368 shallowSize: { title: WebInspector.UIString("Shallow Size"), width: "90px", sortable: true },
369 retainedSize: { title: WebInspector.UIString("Retained Size"), width: "90px", sort: "descending", sortable: true }
371 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
374 WebInspector.HeapSnapshotDominatorsDataGrid.prototype = {
375 _defaultPopulateCount: 25,
377 setDataSource: function(snapshotView, snapshot)
379 this.snapshotView = snapshotView;
380 this.snapshot = snapshot;
381 this.snapshotNodeIndex = this.snapshot.rootNodeIndex;
382 this._provider = this._createProvider(snapshot, this.snapshotNodeIndex);
386 sortingChanged: function()
392 MixInSnapshotNodeFunctions(WebInspector.HeapSnapshotDominatorObjectNode.prototype, WebInspector.HeapSnapshotDominatorsDataGrid.prototype);
393 WebInspector.HeapSnapshotDominatorsDataGrid.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
395 WebInspector.HeapSnapshotPathFinderState = function(snapshot, nodeIndex, rootFilter)
397 this._pathFinder = snapshot.createPathFinder(nodeIndex, !WebInspector.DetailedHeapshotView.prototype.showHiddenData);
398 this._pathFinder.updateRoots(rootFilter);
399 this._foundCount = 0;
400 this._foundCountMax = null;
401 this._totalFoundCount = 0;
402 this._cancelled = false;
405 WebInspector.HeapSnapshotPathFinderState.prototype = {
406 batchDone: function(status)
410 pathFound: function(path)
416 this._cancelled = true;
417 this._pathFinder.dispose();
420 startBatch: function(count)
424 this._foundCount = 0;
425 this._foundCountMax = count;
426 this._pathFinder.findNext(this._pathFound.bind(this));
429 _pathFound: function(result)
433 if (result === null) {
434 if (!this._totalFoundCount)
435 this.batchDone("no-paths-at-all");
436 } else if (result !== false) {
437 this.pathFound(result);
439 ++this._totalFoundCount;
440 if (this._foundCount < this._foundCountMax)
441 this._pathFinder.findNext(this._pathFound.bind(this));
443 this.batchDone("have-more-paths");
445 this.batchDone("no-more-paths");
450 WebInspector.HeapSnapshotRetainingPathsList = function()
453 path: { title: WebInspector.UIString("Retaining path"), sortable: true },
454 len: { title: WebInspector.UIString("Length"), width: "90px", sortable: true, sort: "ascending" }
456 WebInspector.HeapSnapshotSortableDataGrid.call(this, columns);
457 this._defaultPopulateCount = 100;
458 this._nodeIndex = null;
463 WebInspector.HeapSnapshotRetainingPathsList.prototype = {
467 this._state.cancel();
470 _sortFields: function(sortColumn, sortAscending)
473 path: ["path", sortAscending, "len", true],
474 len: ["len", sortAscending, "path", true]
478 _resetPaths: function()
480 var rootFilter = this.snapshotView.isTracingToWindowObjects ?
481 "function (node) { return node.name.substr(0, 9) === \"DOMWindow\"; }" : null;
483 this._state.cancel();
484 this._state = new WebInspector.HeapSnapshotPathFinderState(this._snapshot, this._nodeIndex, rootFilter);
485 this._state.batchDone = this._batchDone.bind(this);
486 this._state.pathFound = this._pathFound.bind(this);
487 this.removeChildren();
488 this.resetSortingCache();
489 this.showNext(this._defaultPopulateCount);
492 setDataSource: function(snapshotView, snapshot, nodeIndex, prefix)
494 if (this._nodeIndex === nodeIndex)
496 this.snapshotView = snapshotView;
497 this._snapshot = snapshot;
498 this._nodeIndex = nodeIndex;
499 this._prefix = prefix;
505 if (this.snapshotView)
512 this._state.cancel();
513 this.removeChildren();
514 this.resetSortingCache();
515 this.appendChild(new WebInspector.DataGridNode({path:WebInspector.UIString("Click on an object to show retaining paths"), len:""}, false));
518 _batchDone: function(state)
521 case "no-paths-at-all":
522 this.appendChild(new WebInspector.DataGridNode({path:WebInspector.UIString("Can't find any paths."), len:""}, false));
524 case "have-more-paths":
525 this.appendChild(new WebInspector.ShowMoreDataGridNode(this.showNext.bind(this), this._defaultPopulateCount));
526 this.resetSortingCache();
527 this.sortingChanged();
529 case "no-more-paths":
535 _pathFound: function(result)
537 if (WebInspector.HeapSnapshotGenericObjectNode.prototype.isDOMWindow(result.path))
538 result.path = WebInspector.HeapSnapshotGenericObjectNode.prototype.shortenWindowURL(result.path, true);
540 result.path = this._prefix + result.path;
541 var node = new WebInspector.DataGridNode(result, false);
542 node.route = result.route;
543 this.appendChild(node);
546 showNext: function(pathsCount)
548 this._state.startBatch(pathsCount);
551 _performSorting: function(sortFunction)
553 function DataExtractorWrapper(nodeA, nodeB)
555 return sortFunction(nodeA.data, nodeB.data);
557 this.recursiveSortingEnter();
558 this.sortNodes(DataExtractorWrapper);
559 this.recursiveSortingLeave();
563 WebInspector.HeapSnapshotRetainingPathsList.prototype.__proto__ = WebInspector.HeapSnapshotSortableDataGrid.prototype;
565 WebInspector.DetailedHeapshotView = function(parent, profile)
567 WebInspector.View.call(this);
569 this.element.addStyleClass("detailed-heapshot-view");
571 this.parent = parent;
572 this.parent.addEventListener("profile added", this._updateBaseOptions, this);
573 this.parent.addEventListener("profile added", this._updateFilterOptions, this);
575 this.showCountAsPercent = false;
576 this.showShallowSizeAsPercent = false;
577 this.showRetainedSizeAsPercent = false;
579 this.viewsContainer = document.createElement("div");
580 this.viewsContainer.addStyleClass("views-container");
581 this.element.appendChild(this.viewsContainer);
583 this.containmentView = new WebInspector.View();
584 this.containmentView.element.addStyleClass("view");
585 this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
586 this.containmentDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
587 this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
588 this.containmentDataGrid.show(this.containmentView.element);
590 this.constructorsView = new WebInspector.View();
591 this.constructorsView.element.addStyleClass("view");
592 this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
593 this.constructorsDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
594 this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
595 this.constructorsDataGrid.show(this.constructorsView.element);
597 this.diffView = new WebInspector.View();
598 this.diffView.element.addStyleClass("view");
599 this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
600 this.diffDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
601 this.diffDataGrid.show(this.diffView.element);
603 this.dominatorView = new WebInspector.View();
604 this.dominatorView.element.addStyleClass("view");
605 this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
606 this.dominatorDataGrid.element.addEventListener("click", this._mouseClickInContentsGrid.bind(this), true);
607 this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
608 this.dominatorDataGrid.show(this.dominatorView.element);
610 this.retainmentViewHeader = document.createElement("div");
611 this.retainmentViewHeader.addStyleClass("retainers-view-header");
612 this.retainmentViewHeader.addEventListener("mousedown", this._startRetainersHeaderDragging.bind(this), true);
613 var retainingPathsTitleDiv = document.createElement("div");
614 retainingPathsTitleDiv.className = "title";
615 var retainingPathsTitle = document.createElement("span");
616 retainingPathsTitle.textContent = WebInspector.UIString("Paths from the selected object");
617 this.retainingPathsRoot = document.createElement("select");
618 this.retainingPathsRoot.className = "status-bar-item";
619 this.retainingPathsRoot.addEventListener("change", this._changeRetainingPathsRoot.bind(this), false);
620 var toGCRootsTraceOption = document.createElement("option");
621 toGCRootsTraceOption.label = WebInspector.UIString("to GC roots");
622 var toWindowObjectsTraceOption = document.createElement("option");
623 toWindowObjectsTraceOption.label = WebInspector.UIString("to window objects");
624 this.retainingPathsRoot.appendChild(toGCRootsTraceOption);
625 this.retainingPathsRoot.appendChild(toWindowObjectsTraceOption);
626 retainingPathsTitleDiv.appendChild(retainingPathsTitle);
627 retainingPathsTitleDiv.appendChild(this.retainingPathsRoot);
628 this.retainmentViewHeader.appendChild(retainingPathsTitleDiv);
629 this.element.appendChild(this.retainmentViewHeader);
631 this.retainmentView = new WebInspector.View();
632 this.retainmentView.element.addStyleClass("view");
633 this.retainmentView.element.addStyleClass("retaining-paths-view");
634 this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainingPathsList();
635 this.retainmentDataGrid.element.addEventListener("click", this._mouseClickInRetainmentGrid.bind(this), true);
636 this.retainmentDataGrid.show(this.retainmentView.element);
637 this.retainmentView.show(this.element);
638 this.retainmentDataGrid.reset();
640 this.dataGrid = this.constructorsDataGrid;
641 this.currentView = this.constructorsView;
643 this.viewSelectElement = document.createElement("select");
644 this.viewSelectElement.className = "status-bar-item";
645 this.viewSelectElement.addEventListener("change", this._changeView.bind(this), false);
647 this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
648 {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
649 {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid},
650 {title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}];
651 this.views.current = 0;
652 for (var i = 0; i < this.views.length; ++i) {
653 var view = this.views[i];
654 var option = document.createElement("option");
655 option.label = WebInspector.UIString(view.title);
656 this.viewSelectElement.appendChild(option);
659 this._profileUid = profile.uid;
661 this.baseSelectElement = document.createElement("select");
662 this.baseSelectElement.className = "status-bar-item hidden";
663 this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
664 this._updateBaseOptions();
666 this.filterSelectElement = document.createElement("select");
667 this.filterSelectElement.className = "status-bar-item";
668 this.filterSelectElement.addEventListener("change", this._changeFilter.bind(this), false);
669 this._updateFilterOptions();
671 this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
672 this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
673 this.helpButton = new WebInspector.StatusBarButton("", "heapshot-help-status-bar-item status-bar-item");
674 this.helpButton.addEventListener("click", this._helpClicked.bind(this), false);
676 var popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._showObjectPopover.bind(this), null, true);
678 this._loadProfile(this._profileUid, profileCallback.bind(this));
680 function profileCallback()
682 var list = this._profiles();
684 for (var i = 0; i < list.length; ++i) {
685 if (list[i].uid === this._profileUid) {
691 if (profileIndex > 0)
692 this.baseSelectElement.selectedIndex = profileIndex - 1;
694 this.baseSelectElement.selectedIndex = profileIndex;
695 this.dataGrid.setDataSource(this, this.profileWrapper);
696 this._updatePercentButton();
700 WebInspector.DetailedHeapshotView.prototype = {
703 this.profileWrapper.dispose();
704 if (this.baseProfile)
705 this.baseProfileWrapper.dispose();
706 this.containmentDataGrid.dispose();
707 this.constructorsDataGrid.dispose();
708 this.diffDataGrid.dispose();
709 this.dominatorDataGrid.dispose();
710 this.retainmentDataGrid.dispose();
715 return [this.viewSelectElement, this.baseSelectElement, this.filterSelectElement, this.percentButton.element, this.helpButton.element];
720 return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._profileUid);
725 return this.profile.proxy;
730 return this.parent.getProfile(WebInspector.DetailedHeapshotProfileType.TypeId, this._baseProfileUid);
733 get baseProfileWrapper()
735 return this.baseProfile.proxy;
740 if (!this.profileWrapper.loaded)
741 this._loadProfile(this._profileUid, profileCallback1.bind(this));
743 profileCallback1.call(this);
745 function profileCallback1() {
746 if (this.baseProfile && !this.baseProfileWrapper.loaded)
747 this._loadProfile(this._baseProfileUid, profileCallback2.bind(this));
749 profileCallback2.call(this);
752 function profileCallback2() {
753 this.currentView.show(this.viewsContainer);
759 this._currentSearchResultIndex = -1;
764 var height = this.retainmentView.element.clientHeight;
765 this._updateRetainmentViewHeight(height);
768 refreshShowAsPercents: function()
770 this._updatePercentButton();
771 this.refreshVisibleData();
774 searchCanceled: function()
776 if (this._searchResults) {
777 for (var i = 0; i < this._searchResults.length; ++i) {
778 var node = this._searchResults[i].node;
779 delete node._searchMatched;
784 delete this._searchFinishedCallback;
785 this._currentSearchResultIndex = -1;
786 this._searchResults = [];
789 performSearch: function(query, finishedCallback)
791 // Call searchCanceled since it will reset everything we need before doing a new search.
792 this.searchCanceled();
794 query = query.trim();
798 if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
801 this._searchFinishedCallback = finishedCallback;
803 function matchesByName(gridNode) {
804 return ("name" in gridNode) && gridNode.name.hasSubstring(query, true);
807 function matchesById(gridNode) {
808 return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === query;
812 if (query.charAt(0) !== "@")
813 matchPredicate = matchesByName;
815 query = parseInt(query.substring(1), 10);
816 matchPredicate = matchesById;
819 function matchesQuery(gridNode)
821 delete gridNode._searchMatched;
822 if (matchPredicate(gridNode)) {
823 gridNode._searchMatched = true;
830 var current = this.dataGrid.children[0];
834 // Restrict to type nodes and instances.
838 if (matchesQuery(current))
839 this._searchResults.push({ node: current });
840 current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
841 depth += info.depthChange;
844 finishedCallback(this, this._searchResults.length);
847 jumpToFirstSearchResult: function()
849 if (!this._searchResults || !this._searchResults.length)
851 this._currentSearchResultIndex = 0;
852 this._jumpToSearchResult(this._currentSearchResultIndex);
855 jumpToLastSearchResult: function()
857 if (!this._searchResults || !this._searchResults.length)
859 this._currentSearchResultIndex = (this._searchResults.length - 1);
860 this._jumpToSearchResult(this._currentSearchResultIndex);
863 jumpToNextSearchResult: function()
865 if (!this._searchResults || !this._searchResults.length)
867 if (++this._currentSearchResultIndex >= this._searchResults.length)
868 this._currentSearchResultIndex = 0;
869 this._jumpToSearchResult(this._currentSearchResultIndex);
872 jumpToPreviousSearchResult: function()
874 if (!this._searchResults || !this._searchResults.length)
876 if (--this._currentSearchResultIndex < 0)
877 this._currentSearchResultIndex = (this._searchResults.length - 1);
878 this._jumpToSearchResult(this._currentSearchResultIndex);
881 showingFirstSearchResult: function()
883 return (this._currentSearchResultIndex === 0);
886 showingLastSearchResult: function()
888 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
891 _jumpToSearchResult: function(index)
893 var searchResult = this._searchResults[index];
897 var node = searchResult.node;
898 node.revealAndSelect();
901 refreshVisibleData: function()
903 var child = this.dataGrid.children[0];
906 child = child.traverseNextNode(false, null, true);
910 _changeBase: function()
912 if (this._baseProfileUid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
915 this._baseProfileUid = this._profiles()[this.baseSelectElement.selectedIndex].uid;
916 this.dataGrid._baseProfileIndexChanged(this._loadProfileByIndex.bind(this), this.baseSelectElement.selectedIndex);
918 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
921 // The current search needs to be performed again. First negate out previous match
922 // count by calling the search finished callback with a negative number of matches.
923 // Then perform the search again with the same query and callback.
924 this._searchFinishedCallback(this, -this._searchResults.length);
925 this.performSearch(this.currentQuery, this._searchFinishedCallback);
928 _changeFilter: function()
930 var profileIndex = this.filterSelectElement.selectedIndex - 1;
931 this.dataGrid._filterSelectIndexChanged(this._loadProfileByIndex.bind(this), profileIndex);
933 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
936 // The current search needs to be performed again. First negate out previous match
937 // count by calling the search finished callback with a negative number of matches.
938 // Then perform the search again with the same query and callback.
939 this._searchFinishedCallback(this, -this._searchResults.length);
940 this.performSearch(this.currentQuery, this._searchFinishedCallback);
943 _profiles: function()
945 return WebInspector.panels.profiles.getProfiles(WebInspector.DetailedHeapshotProfileType.TypeId);
948 _loadProfile: function(profileUid, callback)
950 WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback);
953 _loadProfileByIndex: function(profileIndex, callback)
955 var profileUid = this._profiles()[profileIndex].uid;
956 WebInspector.panels.profiles.loadHeapSnapshot(profileUid, callback);
959 isDetailedSnapshot: function(snapshot)
961 var s = new WebInspector.HeapSnapshot(snapshot);
962 for (var iter = s.rootNode.edges; iter.hasNext(); iter.next())
963 if (iter.edge.node.name === "(GC roots)")
968 processLoadedSnapshot: function(profile, snapshot)
970 profile.nodes = snapshot.nodes;
971 profile.strings = snapshot.strings;
972 var s = new WebInspector.HeapSnapshot(profile);
973 profile.sidebarElement.subtitle = Number.bytesToString(s.totalSize);
976 _mouseClickInContentsGrid: function(event)
978 var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
979 if (!cell || (!cell.hasStyleClass("object-column")))
981 var row = event.target.enclosingNodeOrSelfWithNodeName("tr");
984 var nodeItem = row._dataGridNode;
985 if (!nodeItem || nodeItem.isEventWithinDisclosureTriangle(event))
987 if (nodeItem.snapshotNodeIndex)
988 this.retainmentDataGrid.setDataSource(this, nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex, nodeItem.isDeletedNode ? this.baseSelectElement.childNodes[this.baseSelectElement.selectedIndex].label + " | " : "");
990 this.retainmentDataGrid.reset();
993 _mouseDownInContentsGrid: function(event)
995 if (event.detail < 2)
998 var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
999 if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
1002 if (cell.hasStyleClass("count-column"))
1003 this.showCountAsPercent = !this.showCountAsPercent;
1004 else if (cell.hasStyleClass("shallowSize-column"))
1005 this.showShallowSizeAsPercent = !this.showShallowSizeAsPercent;
1006 else if (cell.hasStyleClass("retainedSize-column"))
1007 this.showRetainedSizeAsPercent = !this.showRetainedSizeAsPercent;
1008 this.refreshShowAsPercents();
1010 event.preventDefault();
1011 event.stopPropagation();
1014 _mouseClickInRetainmentGrid: function(event)
1016 var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
1017 if (!cell || (!cell.hasStyleClass("path-column")))
1019 var row = event.target.enclosingNodeOrSelfWithNodeName("tr");
1020 var nodeItem = row._dataGridNode;
1021 if (!nodeItem || !nodeItem.route)
1023 function expandRoute()
1025 this.dataGrid.expandRoute(nodeItem.route);
1027 this.changeView("Containment", expandRoute.bind(this));
1030 changeView: function(viewTitle, callback)
1032 var viewIndex = null;
1033 for (var i = 0; i < this.views.length; ++i)
1034 if (this.views[i].title === viewTitle) {
1038 if (this.views.current === viewIndex) {
1039 setTimeout(callback, 0);
1042 var grid = this.views[viewIndex].grid;
1043 function sortingComplete()
1045 grid.removeEventListener("sorting complete", sortingComplete, this);
1046 setTimeout(callback, 0);
1048 this.views[viewIndex].grid.addEventListener("sorting complete", sortingComplete, this);
1049 this.viewSelectElement.selectedIndex = viewIndex;
1050 this._changeView({target: {selectedIndex: viewIndex}});
1053 _changeView: function(event)
1055 if (!event || !this._profileUid)
1057 if (event.target.selectedIndex === this.views.current)
1060 this.views.current = event.target.selectedIndex;
1061 this.currentView.detach();
1062 var view = this.views[this.views.current];
1063 this.currentView = view.view;
1064 this.dataGrid = view.grid;
1065 this.currentView.show(this.viewsContainer);
1066 this.refreshVisibleData();
1067 this.dataGrid.updateWidths();
1069 if (this.currentView === this.diffView) {
1070 this.baseSelectElement.removeStyleClass("hidden");
1071 if (!this.dataGrid.snapshotView) {
1073 this.dataGrid.setDataSource(this, this.profileWrapper);
1076 this.baseSelectElement.addStyleClass("hidden");
1077 if (!this.dataGrid.snapshotView)
1078 this.dataGrid.setDataSource(this, this.profileWrapper);
1081 if (this.currentView === this.constructorsView)
1082 this.filterSelectElement.removeStyleClass("hidden");
1084 this.filterSelectElement.addStyleClass("hidden");
1086 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
1089 // The current search needs to be performed again. First negate out previous match
1090 // count by calling the search finished callback with a negative number of matches.
1091 // Then perform the search again the with same query and callback.
1092 this._searchFinishedCallback(this, -this._searchResults.length);
1093 this.performSearch(this.currentQuery, this._searchFinishedCallback);
1096 _changeRetainingPathsRoot: function(event)
1100 this.retainmentDataGrid.refresh();
1103 _getHoverAnchor: function(target)
1105 var span = target.enclosingNodeOrSelfWithNodeName("span");
1108 var row = target.enclosingNodeOrSelfWithNodeName("tr");
1111 var gridNode = row._dataGridNode;
1112 if (!gridNode.hasHoverMessage)
1114 span.node = gridNode;
1118 get isTracingToWindowObjects()
1120 return this.retainingPathsRoot.selectedIndex === 1;
1123 get _isShowingAsPercent()
1125 return this.showCountAsPercent && this.showShallowSizeAsPercent && this.showRetainedSizeAsPercent;
1128 _percentClicked: function(event)
1130 var currentState = this._isShowingAsPercent;
1131 this.showCountAsPercent = !currentState;
1132 this.showShallowSizeAsPercent = !currentState;
1133 this.showRetainedSizeAsPercent = !currentState;
1134 this.refreshShowAsPercents();
1137 _showObjectPopover: function(element, showCallback)
1139 element.node.queryObjectContent(showCallback);
1142 _helpClicked: function(event)
1144 if (!this._helpPopoverContentElement) {
1145 var refTypes = ["a:", "console-formatted-name", WebInspector.UIString("property"),
1146 "0:", "console-formatted-name", WebInspector.UIString("element"),
1147 "a:", "console-formatted-number", WebInspector.UIString("context var"),
1148 "a:", "console-formatted-null", WebInspector.UIString("system prop")];
1149 var objTypes = [" a ", "console-formatted-object", "Object",
1150 "\"a\"", "console-formatted-string", "String",
1151 "/a/", "console-formatted-string", "RegExp",
1152 "a()", "console-formatted-function", "Function",
1153 "a[]", "console-formatted-object", "Array",
1154 "num", "console-formatted-number", "Number",
1155 " a ", "console-formatted-null", "System"];
1157 var contentElement = document.createElement("table");
1158 contentElement.className = "heapshot-help";
1159 var headerRow = document.createElement("tr");
1160 var propsHeader = document.createElement("th");
1161 propsHeader.textContent = WebInspector.UIString("Property types:");
1162 headerRow.appendChild(propsHeader);
1163 var objsHeader = document.createElement("th");
1164 objsHeader.textContent = WebInspector.UIString("Object types:");
1165 headerRow.appendChild(objsHeader);
1166 contentElement.appendChild(headerRow);
1167 var len = Math.max(refTypes.length, objTypes.length);
1168 for (var i = 0; i < len; i += 3) {
1169 var row = document.createElement("tr");
1170 var refCell = document.createElement("td");
1172 appendHelp(refTypes, i, refCell);
1173 row.appendChild(refCell);
1174 var objCell = document.createElement("td");
1176 appendHelp(objTypes, i, objCell);
1177 row.appendChild(objCell);
1178 contentElement.appendChild(row);
1180 this._helpPopoverContentElement = contentElement;
1181 this.helpPopover = new WebInspector.Popover();
1183 function appendHelp(help, index, cell)
1185 var div = document.createElement("div");
1186 div.className = "source-code event-properties";
1187 var name = document.createElement("span");
1188 name.textContent = help[index];
1189 name.className = help[index + 1];
1190 div.appendChild(name);
1191 var desc = document.createElement("span");
1192 desc.textContent = " " + help[index + 2];
1193 div.appendChild(desc);
1194 cell.appendChild(div);
1197 if (this.helpPopover.visible)
1198 this.helpPopover.hide();
1200 this.helpPopover.show(this._helpPopoverContentElement, this.helpButton.element);
1203 _startRetainersHeaderDragging: function(event)
1205 if (!this.isShowing() || event.target === this.retainingPathsRoot)
1208 WebInspector.elementDragStart(this.retainmentViewHeader, this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), event, "row-resize");
1209 this._previousDragPosition = event.pageY;
1210 event.stopPropagation();
1213 _retainersHeaderDragging: function(event)
1215 var height = this.retainmentView.element.clientHeight;
1216 height += this._previousDragPosition - event.pageY;
1217 this._previousDragPosition = event.pageY;
1218 this._updateRetainmentViewHeight(height);
1219 event.preventDefault();
1220 event.stopPropagation();
1223 _endRetainersHeaderDragging: function(event)
1225 WebInspector.elementDragEnd(event);
1226 delete this._previousDragPosition;
1227 event.stopPropagation();
1230 _updateRetainmentViewHeight: function(height)
1232 height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight);
1233 this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px";
1234 this.retainmentView.element.style.height = height + "px";
1235 this.retainmentViewHeader.style.bottom = height + "px";
1238 _updateBaseOptions: function()
1240 var list = this._profiles();
1241 // We're assuming that snapshots can only be added.
1242 if (this.baseSelectElement.length === list.length)
1245 for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
1246 var baseOption = document.createElement("option");
1247 var title = list[i].title;
1248 if (!title.indexOf(UserInitiatedProfileName))
1249 title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
1250 baseOption.label = title;
1251 this.baseSelectElement.appendChild(baseOption);
1255 _updateFilterOptions: function()
1257 var list = this._profiles();
1258 // We're assuming that snapshots can only be added.
1259 if (this.filterSelectElement.length - 1 === list.length)
1262 if (!this.filterSelectElement.length) {
1263 var filterOption = document.createElement("option");
1264 filterOption.label = WebInspector.UIString("All objects");
1265 this.filterSelectElement.appendChild(filterOption);
1268 for (var i = this.filterSelectElement.length - 1, n = list.length; i < n; ++i) {
1269 var filterOption = document.createElement("option");
1270 var title = list[i].title;
1271 if (!title.indexOf(UserInitiatedProfileName)) {
1273 title = WebInspector.UIString("Objects allocated before Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
1275 title = WebInspector.UIString("Objects allocated between Snapshots %d and %d", title.substring(UserInitiatedProfileName.length + 1) - 1, title.substring(UserInitiatedProfileName.length + 1));
1277 filterOption.label = title;
1278 this.filterSelectElement.appendChild(filterOption);
1282 _updatePercentButton: function()
1284 if (this._isShowingAsPercent) {
1285 this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
1286 this.percentButton.toggled = true;
1288 this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
1289 this.percentButton.toggled = false;
1294 WebInspector.DetailedHeapshotView.prototype.__proto__ = WebInspector.View.prototype;
1296 WebInspector.DetailedHeapshotView.prototype.showHiddenData = true;
1298 WebInspector.DetailedHeapshotProfileType = function()
1300 WebInspector.ProfileType.call(this, WebInspector.DetailedHeapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
1303 WebInspector.DetailedHeapshotProfileType.TypeId = "HEAP";
1305 WebInspector.DetailedHeapshotProfileType.prototype = {
1308 return WebInspector.UIString("Take heap snapshot.");
1313 return "heap-snapshot-status-bar-item status-bar-item";
1316 buttonClicked: function()
1318 WebInspector.panels.profiles.takeHeapSnapshot();
1321 get welcomeMessage()
1323 return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
1326 createSidebarTreeElementForProfile: function(profile)
1328 return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
1331 createView: function(profile)
1333 return new WebInspector.DetailedHeapshotView(WebInspector.panels.profiles, profile);
1337 WebInspector.DetailedHeapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;