| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.View} |
| * @param {!WebInspector.ProfilesPanel} parent |
| * @param {!WebInspector.HeapProfileHeader} profile |
| */ |
| WebInspector.HeapSnapshotView = function(parent, profile) |
| { |
| WebInspector.View.call(this); |
| |
| this.element.addStyleClass("heap-snapshot-view"); |
| |
| this.parent = parent; |
| this.parent.addEventListener("profile added", this._onProfileHeaderAdded, this); |
| |
| if (profile._profileType.id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) { |
| this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile); |
| this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this)); |
| this._trackingOverviewGrid.show(this.element); |
| } |
| |
| this.viewsContainer = document.createElement("div"); |
| this.viewsContainer.addStyleClass("views-container"); |
| this.element.appendChild(this.viewsContainer); |
| |
| this.containmentView = new WebInspector.View(); |
| this.containmentView.element.addStyleClass("view"); |
| this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid(); |
| this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); |
| this.containmentDataGrid.show(this.containmentView.element); |
| this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| |
| this.constructorsView = new WebInspector.View(); |
| this.constructorsView.element.addStyleClass("view"); |
| this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter()); |
| |
| this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(); |
| this.constructorsDataGrid.element.addStyleClass("class-view-grid"); |
| this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); |
| this.constructorsDataGrid.show(this.constructorsView.element); |
| this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| |
| this.dataGrid = /** @type {WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid); |
| this.currentView = this.constructorsView; |
| this.currentView.show(this.viewsContainer); |
| |
| this.diffView = new WebInspector.View(); |
| this.diffView.element.addStyleClass("view"); |
| this.diffView.element.appendChild(this._createToolbarWithClassNameFilter()); |
| |
| this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(); |
| this.diffDataGrid.element.addStyleClass("class-view-grid"); |
| this.diffDataGrid.show(this.diffView.element); |
| this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| |
| this.dominatorView = new WebInspector.View(); |
| this.dominatorView.element.addStyleClass("view"); |
| this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid(); |
| this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); |
| this.dominatorDataGrid.show(this.dominatorView.element); |
| this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| |
| if (WebInspector.HeapSnapshot.enableAllocationProfiler) { |
| this.allocationView = new WebInspector.View(); |
| this.allocationView.element.addStyleClass("view"); |
| this.allocationDataGrid = new WebInspector.AllocationDataGrid(); |
| this.allocationDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true); |
| this.allocationDataGrid.show(this.allocationView.element); |
| this.allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); |
| } |
| |
| this.retainmentViewHeader = document.createElement("div"); |
| this.retainmentViewHeader.addStyleClass("retainers-view-header"); |
| WebInspector.installDragHandle(this.retainmentViewHeader, this._startRetainersHeaderDragging.bind(this), this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), "row-resize"); |
| var retainingPathsTitleDiv = document.createElement("div"); |
| retainingPathsTitleDiv.className = "title"; |
| var retainingPathsTitle = document.createElement("span"); |
| retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree"); |
| retainingPathsTitleDiv.appendChild(retainingPathsTitle); |
| this.retainmentViewHeader.appendChild(retainingPathsTitleDiv); |
| this.element.appendChild(this.retainmentViewHeader); |
| |
| this.retainmentView = new WebInspector.View(); |
| this.retainmentView.element.addStyleClass("view"); |
| this.retainmentView.element.addStyleClass("retaining-paths-view"); |
| this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid(); |
| this.retainmentDataGrid.show(this.retainmentView.element); |
| this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this); |
| this.retainmentView.show(this.element); |
| this.retainmentDataGrid.reset(); |
| |
| this.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this)); |
| |
| this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid}, |
| {title: "Comparison", view: this.diffView, grid: this.diffDataGrid}, |
| {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}]; |
| if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get()) |
| this.views.push({title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid}); |
| if (WebInspector.HeapSnapshot.enableAllocationProfiler) |
| this.views.push({title: "Allocation", view: this.allocationView, grid: this.allocationDataGrid}); |
| this.views.current = 0; |
| for (var i = 0; i < this.views.length; ++i) |
| this.viewSelect.createOption(WebInspector.UIString(this.views[i].title)); |
| |
| this._profileUid = profile.uid; |
| this._profileTypeId = profile.profileType().id; |
| |
| this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this)); |
| this.baseSelect.element.addStyleClass("hidden"); |
| this._updateBaseOptions(); |
| |
| this.filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this)); |
| this._updateFilterOptions(); |
| |
| this.selectedSizeText = new WebInspector.StatusBarText(""); |
| |
| this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); |
| |
| this.profile.load(profileCallback.bind(this)); |
| |
| function profileCallback(heapSnapshotProxy) |
| { |
| var list = this._profiles(); |
| var profileIndex; |
| for (var i = 0; i < list.length; ++i) { |
| if (list[i].uid === this._profileUid) { |
| profileIndex = i; |
| break; |
| } |
| } |
| |
| if (profileIndex > 0) |
| this.baseSelect.setSelectedIndex(profileIndex - 1); |
| else |
| this.baseSelect.setSelectedIndex(profileIndex); |
| this.dataGrid.setDataSource(heapSnapshotProxy); |
| } |
| } |
| |
| WebInspector.HeapSnapshotView.prototype = { |
| _onIdsRangeChanged: function(event) |
| { |
| var minId = event.data.minId; |
| var maxId = event.data.maxId; |
| this.selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size))); |
| if (this.constructorsDataGrid.snapshot) |
| this.constructorsDataGrid.setSelectionRange(minId, maxId); |
| }, |
| |
| dispose: function() |
| { |
| this.parent.removeEventListener("profile added", this._onProfileHeaderAdded, this); |
| this.profile.dispose(); |
| if (this.baseProfile) |
| this.baseProfile.dispose(); |
| this.containmentDataGrid.dispose(); |
| this.constructorsDataGrid.dispose(); |
| this.diffDataGrid.dispose(); |
| this.dominatorDataGrid.dispose(); |
| this.retainmentDataGrid.dispose(); |
| }, |
| |
| get statusBarItems() |
| { |
| return [this.viewSelect.element, this.baseSelect.element, this.filterSelect.element, this.selectedSizeText.element]; |
| }, |
| |
| get profile() |
| { |
| return this.parent.getProfile(this._profileTypeId, this._profileUid); |
| }, |
| |
| get baseProfile() |
| { |
| return this.parent.getProfile(this._profileTypeId, this._baseProfileUid); |
| }, |
| |
| wasShown: function() |
| { |
| // FIXME: load base and current snapshots in parallel |
| this.profile.load(profileCallback.bind(this)); |
| function profileCallback() { |
| this.profile._wasShown(); |
| if (this.baseProfile) |
| this.baseProfile.load(function() { }); |
| } |
| }, |
| |
| willHide: function() |
| { |
| this._currentSearchResultIndex = -1; |
| this._popoverHelper.hidePopover(); |
| if (this.helpPopover && this.helpPopover.isShowing()) |
| this.helpPopover.hide(); |
| }, |
| |
| onResize: function() |
| { |
| var height = this.retainmentView.element.clientHeight; |
| this._updateRetainmentViewHeight(height); |
| }, |
| |
| searchCanceled: function() |
| { |
| if (this._searchResults) { |
| for (var i = 0; i < this._searchResults.length; ++i) { |
| var node = this._searchResults[i].node; |
| delete node._searchMatched; |
| node.refresh(); |
| } |
| } |
| |
| delete this._searchFinishedCallback; |
| this._currentSearchResultIndex = -1; |
| this._searchResults = []; |
| }, |
| |
| /** |
| * @param {string} query |
| * @param {function(!WebInspector.View, number)} finishedCallback |
| */ |
| performSearch: function(query, finishedCallback) |
| { |
| // Call searchCanceled since it will reset everything we need before doing a new search. |
| this.searchCanceled(); |
| |
| query = query.trim(); |
| |
| if (!query) |
| return; |
| if (this.currentView !== this.constructorsView && this.currentView !== this.diffView) |
| return; |
| |
| this._searchFinishedCallback = finishedCallback; |
| var nameRegExp = createPlainTextSearchRegex(query, "i"); |
| var snapshotNodeId = null; |
| |
| function matchesByName(gridNode) { |
| return ("_name" in gridNode) && nameRegExp.test(gridNode._name); |
| } |
| |
| function matchesById(gridNode) { |
| return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === snapshotNodeId; |
| } |
| |
| var matchPredicate; |
| if (query.charAt(0) !== "@") |
| matchPredicate = matchesByName; |
| else { |
| snapshotNodeId = parseInt(query.substring(1), 10); |
| matchPredicate = matchesById; |
| } |
| |
| function matchesQuery(gridNode) |
| { |
| delete gridNode._searchMatched; |
| if (matchPredicate(gridNode)) { |
| gridNode._searchMatched = true; |
| gridNode.refresh(); |
| return true; |
| } |
| return false; |
| } |
| |
| var current = this.dataGrid.rootNode().children[0]; |
| var depth = 0; |
| var info = {}; |
| |
| // Restrict to type nodes and instances. |
| const maxDepth = 1; |
| |
| while (current) { |
| if (matchesQuery(current)) |
| this._searchResults.push({ node: current }); |
| current = current.traverseNextNode(false, null, (depth >= maxDepth), info); |
| depth += info.depthChange; |
| } |
| |
| finishedCallback(this, this._searchResults.length); |
| }, |
| |
| jumpToFirstSearchResult: function() |
| { |
| if (!this._searchResults || !this._searchResults.length) |
| return; |
| this._currentSearchResultIndex = 0; |
| this._jumpToSearchResult(this._currentSearchResultIndex); |
| }, |
| |
| jumpToLastSearchResult: function() |
| { |
| if (!this._searchResults || !this._searchResults.length) |
| return; |
| this._currentSearchResultIndex = (this._searchResults.length - 1); |
| this._jumpToSearchResult(this._currentSearchResultIndex); |
| }, |
| |
| jumpToNextSearchResult: function() |
| { |
| if (!this._searchResults || !this._searchResults.length) |
| return; |
| if (++this._currentSearchResultIndex >= this._searchResults.length) |
| this._currentSearchResultIndex = 0; |
| this._jumpToSearchResult(this._currentSearchResultIndex); |
| }, |
| |
| jumpToPreviousSearchResult: function() |
| { |
| if (!this._searchResults || !this._searchResults.length) |
| return; |
| if (--this._currentSearchResultIndex < 0) |
| this._currentSearchResultIndex = (this._searchResults.length - 1); |
| this._jumpToSearchResult(this._currentSearchResultIndex); |
| }, |
| |
| showingFirstSearchResult: function() |
| { |
| return (this._currentSearchResultIndex === 0); |
| }, |
| |
| showingLastSearchResult: function() |
| { |
| return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); |
| }, |
| |
| _jumpToSearchResult: function(index) |
| { |
| var searchResult = this._searchResults[index]; |
| if (!searchResult) |
| return; |
| |
| var node = searchResult.node; |
| node.revealAndSelect(); |
| }, |
| |
| refreshVisibleData: function() |
| { |
| var child = this.dataGrid.rootNode().children[0]; |
| while (child) { |
| child.refresh(); |
| child = child.traverseNextNode(false, null, true); |
| } |
| }, |
| |
| _changeBase: function() |
| { |
| if (this._baseProfileUid === this._profiles()[this.baseSelect.selectedIndex()].uid) |
| return; |
| |
| this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid; |
| var dataGrid = /** @type {WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid); |
| // Change set base data source only if main data source is already set. |
| if (dataGrid.snapshot) |
| this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid)); |
| |
| if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) |
| return; |
| |
| // The current search needs to be performed again. First negate out previous match |
| // count by calling the search finished callback with a negative number of matches. |
| // Then perform the search again with the same query and callback. |
| this._searchFinishedCallback(this, -this._searchResults.length); |
| this.performSearch(this.currentQuery, this._searchFinishedCallback); |
| }, |
| |
| _changeFilter: function() |
| { |
| var profileIndex = this.filterSelect.selectedIndex() - 1; |
| this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex); |
| |
| WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { |
| action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged, |
| label: this.filterSelect.selectedOption().label |
| }); |
| |
| if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) |
| return; |
| |
| // The current search needs to be performed again. First negate out previous match |
| // count by calling the search finished callback with a negative number of matches. |
| // Then perform the search again with the same query and callback. |
| this._searchFinishedCallback(this, -this._searchResults.length); |
| this.performSearch(this.currentQuery, this._searchFinishedCallback); |
| }, |
| |
| _createToolbarWithClassNameFilter: function() |
| { |
| var toolbar = document.createElement("div"); |
| toolbar.addStyleClass("class-view-toolbar"); |
| var classNameFilter = document.createElement("input"); |
| classNameFilter.addStyleClass("class-name-filter"); |
| classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter")); |
| classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false); |
| toolbar.appendChild(classNameFilter); |
| return toolbar; |
| }, |
| |
| _changeNameFilter: function(classNameInputElement) |
| { |
| var filter = classNameInputElement.value; |
| this.dataGrid.changeNameFilter(filter); |
| }, |
| |
| /** |
| * @return {!Array.<!WebInspector.ProfileHeader>} |
| */ |
| _profiles: function() |
| { |
| return this.parent.getProfileType(this._profileTypeId).getProfiles(); |
| }, |
| |
| /** |
| * @param {WebInspector.ContextMenu} contextMenu |
| * @param {Event} event |
| */ |
| populateContextMenu: function(contextMenu, event) |
| { |
| this.dataGrid.populateContextMenu(this.parent, contextMenu, event); |
| }, |
| |
| _selectionChanged: function(event) |
| { |
| var selectedNode = event.target.selectedNode; |
| this._setRetainmentDataGridSource(selectedNode); |
| this._inspectedObjectChanged(event); |
| }, |
| |
| _inspectedObjectChanged: function(event) |
| { |
| var selectedNode = event.target.selectedNode; |
| if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode) |
| ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId); |
| }, |
| |
| _setRetainmentDataGridSource: function(nodeItem) |
| { |
| if (nodeItem && nodeItem.snapshotNodeIndex) |
| this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex); |
| else |
| this.retainmentDataGrid.reset(); |
| }, |
| |
| _mouseDownInContentsGrid: function(event) |
| { |
| if (event.detail < 2) |
| return; |
| |
| var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); |
| if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column"))) |
| return; |
| |
| event.consume(true); |
| }, |
| |
| changeView: function(viewTitle, callback) |
| { |
| var viewIndex = null; |
| for (var i = 0; i < this.views.length; ++i) { |
| if (this.views[i].title === viewTitle) { |
| viewIndex = i; |
| break; |
| } |
| } |
| if (this.views.current === viewIndex || viewIndex == null) { |
| setTimeout(callback, 0); |
| return; |
| } |
| |
| function dataGridContentShown(event) |
| { |
| var dataGrid = event.data; |
| dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); |
| if (dataGrid === this.dataGrid) |
| callback(); |
| } |
| this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); |
| |
| this.viewSelect.setSelectedIndex(viewIndex); |
| this._changeView(viewIndex); |
| }, |
| |
| _updateDataSourceAndView: function() |
| { |
| var dataGrid = this.dataGrid; |
| if (dataGrid.snapshot) |
| return; |
| |
| this.profile.load(didLoadSnapshot.bind(this)); |
| function didLoadSnapshot(snapshotProxy) |
| { |
| if (this.dataGrid !== dataGrid) |
| return; |
| if (dataGrid.snapshot !== snapshotProxy) |
| dataGrid.setDataSource(snapshotProxy); |
| if (dataGrid === this.diffDataGrid) { |
| if (!this._baseProfileUid) |
| this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid; |
| this.baseProfile.load(didLoadBaseSnaphot.bind(this)); |
| } |
| } |
| |
| function didLoadBaseSnaphot(baseSnapshotProxy) |
| { |
| if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy) |
| this.diffDataGrid.setBaseDataSource(baseSnapshotProxy); |
| } |
| }, |
| |
| _onSelectedViewChanged: function(event) |
| { |
| this._changeView(event.target.selectedIndex); |
| }, |
| |
| _updateSelectorsVisibility: function() |
| { |
| if (this.currentView === this.diffView) |
| this.baseSelect.element.removeStyleClass("hidden"); |
| else |
| this.baseSelect.element.addStyleClass("hidden"); |
| |
| if (this.currentView === this.constructorsView) { |
| if (this._trackingOverviewGrid) { |
| this._trackingOverviewGrid.element.removeStyleClass("hidden"); |
| this._trackingOverviewGrid.update(); |
| this.viewsContainer.addStyleClass("reserve-80px-at-top"); |
| } |
| this.filterSelect.element.removeStyleClass("hidden"); |
| } else { |
| this.filterSelect.element.addStyleClass("hidden"); |
| if (this._trackingOverviewGrid) { |
| this._trackingOverviewGrid.element.addStyleClass("hidden"); |
| this.viewsContainer.removeStyleClass("reserve-80px-at-top"); |
| } |
| } |
| }, |
| |
| _changeView: function(selectedIndex) |
| { |
| if (selectedIndex === this.views.current) |
| return; |
| |
| this.views.current = selectedIndex; |
| this.currentView.detach(); |
| var view = this.views[this.views.current]; |
| this.currentView = view.view; |
| this.dataGrid = view.grid; |
| this.currentView.show(this.viewsContainer); |
| this.refreshVisibleData(); |
| this.dataGrid.updateWidths(); |
| |
| this._updateSelectorsVisibility(); |
| |
| this._updateDataSourceAndView(); |
| |
| if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) |
| return; |
| |
| // The current search needs to be performed again. First negate out previous match |
| // count by calling the search finished callback with a negative number of matches. |
| // Then perform the search again the with same query and callback. |
| this._searchFinishedCallback(this, -this._searchResults.length); |
| this.performSearch(this.currentQuery, this._searchFinishedCallback); |
| }, |
| |
| _getHoverAnchor: function(target) |
| { |
| var span = target.enclosingNodeOrSelfWithNodeName("span"); |
| if (!span) |
| return; |
| var row = target.enclosingNodeOrSelfWithNodeName("tr"); |
| if (!row) |
| return; |
| span.node = row._dataGridNode; |
| return span; |
| }, |
| |
| _resolveObjectForPopover: function(element, showCallback, objectGroupName) |
| { |
| if (this.profile.fromFile()) |
| return; |
| element.node.queryObjectContent(showCallback, objectGroupName); |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| _startRetainersHeaderDragging: function(event) |
| { |
| if (!this.isShowing()) |
| return false; |
| |
| this._previousDragPosition = event.pageY; |
| return true; |
| }, |
| |
| _retainersHeaderDragging: function(event) |
| { |
| var height = this.retainmentView.element.clientHeight; |
| height += this._previousDragPosition - event.pageY; |
| this._previousDragPosition = event.pageY; |
| this._updateRetainmentViewHeight(height); |
| event.consume(true); |
| }, |
| |
| _endRetainersHeaderDragging: function(event) |
| { |
| delete this._previousDragPosition; |
| event.consume(); |
| }, |
| |
| _updateRetainmentViewHeight: function(height) |
| { |
| height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight); |
| this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px"; |
| if (this._trackingOverviewGrid && this.currentView === this.constructorsView) |
| this.viewsContainer.addStyleClass("reserve-80px-at-top"); |
| this.retainmentView.element.style.height = height + "px"; |
| this.retainmentViewHeader.style.bottom = height + "px"; |
| this.currentView.doResize(); |
| }, |
| |
| _updateBaseOptions: function() |
| { |
| var list = this._profiles(); |
| // We're assuming that snapshots can only be added. |
| if (this.baseSelect.size() === list.length) |
| return; |
| |
| for (var i = this.baseSelect.size(), n = list.length; i < n; ++i) { |
| var title = list[i].title; |
| this.baseSelect.createOption(title); |
| } |
| }, |
| |
| _updateFilterOptions: function() |
| { |
| var list = this._profiles(); |
| // We're assuming that snapshots can only be added. |
| if (this.filterSelect.size() - 1 === list.length) |
| return; |
| |
| if (!this.filterSelect.size()) |
| this.filterSelect.createOption(WebInspector.UIString("All objects")); |
| |
| for (var i = this.filterSelect.size() - 1, n = list.length; i < n; ++i) { |
| var title = list[i].title; |
| if (!i) |
| title = WebInspector.UIString("Objects allocated before %s", title); |
| else |
| title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title); |
| this.filterSelect.createOption(title); |
| } |
| }, |
| |
| /** |
| * @param {WebInspector.Event} event |
| */ |
| _onProfileHeaderAdded: function(event) |
| { |
| if (!event.data || event.data.type !== this._profileTypeId) |
| return; |
| this._updateBaseOptions(); |
| this._updateFilterOptions(); |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |
| |
| /** |
| * @constructor |
| * @implements {HeapProfilerAgent.Dispatcher} |
| */ |
| WebInspector.HeapProfilerDispatcher = function() |
| { |
| this._dispatchers = []; |
| InspectorBackend.registerHeapProfilerDispatcher(this); |
| } |
| |
| WebInspector.HeapProfilerDispatcher.prototype = { |
| /** |
| * @param {HeapProfilerAgent.Dispatcher} dispatcher |
| */ |
| register: function(dispatcher) |
| { |
| this._dispatchers.push(dispatcher); |
| }, |
| |
| _genericCaller: function(eventName) |
| { |
| var args = Array.prototype.slice.call(arguments.callee.caller.arguments); |
| for (var i = 0; i < this._dispatchers.length; ++i) |
| this._dispatchers[i][eventName].apply(this._dispatchers[i], args); |
| }, |
| |
| /** |
| * @override |
| * @param {Array.<number>} samples |
| */ |
| heapStatsUpdate: function(samples) |
| { |
| this._genericCaller("heapStatsUpdate"); |
| }, |
| |
| /** |
| * @override |
| * @param {number} lastSeenObjectId |
| * @param {number} timestamp |
| */ |
| lastSeenObjectId: function(lastSeenObjectId, timestamp) |
| { |
| this._genericCaller("lastSeenObjectId"); |
| }, |
| |
| /** |
| * @param {HeapProfilerAgent.ProfileHeader} profileHeader |
| */ |
| addProfileHeader: function(profileHeader) |
| { |
| this._genericCaller("addProfileHeader"); |
| }, |
| |
| /** |
| * @override |
| * @param {number} uid |
| * @param {string} chunk |
| */ |
| addHeapSnapshotChunk: function(uid, chunk) |
| { |
| this._genericCaller("addHeapSnapshotChunk"); |
| }, |
| |
| /** |
| * @override |
| * @param {number} done |
| * @param {number} total |
| */ |
| reportHeapSnapshotProgress: function(done, total) |
| { |
| this._genericCaller("reportHeapSnapshotProgress"); |
| }, |
| |
| /** |
| * @override |
| */ |
| resetProfiles: function() |
| { |
| this._genericCaller("resetProfiles"); |
| } |
| } |
| |
| WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher(); |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.ProfileType} |
| * @implements {HeapProfilerAgent.Dispatcher} |
| */ |
| WebInspector.HeapSnapshotProfileType = function() |
| { |
| WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot")); |
| WebInspector.HeapProfilerDispatcher._dispatcher.register(this); |
| } |
| |
| WebInspector.HeapSnapshotProfileType.TypeId = "HEAP"; |
| WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived"; |
| |
| WebInspector.HeapSnapshotProfileType.prototype = { |
| /** |
| * @override |
| * @return {string} |
| */ |
| fileExtension: function() |
| { |
| return ".heapsnapshot"; |
| }, |
| |
| get buttonTooltip() |
| { |
| return WebInspector.UIString("Take heap snapshot."); |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isInstantProfile: function() |
| { |
| return true; |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| buttonClicked: function() |
| { |
| this._takeHeapSnapshot(function() {}); |
| WebInspector.userMetrics.ProfilesHeapProfileTaken.record(); |
| return false; |
| }, |
| |
| /** |
| * @override |
| * @param {Array.<number>} samples |
| */ |
| heapStatsUpdate: function(samples) |
| { |
| }, |
| |
| /** |
| * @override |
| * @param {number} lastSeenObjectId |
| * @param {number} timestamp |
| */ |
| lastSeenObjectId: function(lastSeenObjectId, timestamp) |
| { |
| }, |
| |
| get treeItemTitle() |
| { |
| return WebInspector.UIString("HEAP SNAPSHOTS"); |
| }, |
| |
| get description() |
| { |
| return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes."); |
| }, |
| |
| /** |
| * @override |
| * @param {string=} title |
| * @return {!WebInspector.ProfileHeader} |
| */ |
| createTemporaryProfile: function(title) |
| { |
| title = title || WebInspector.UIString("Snapshotting\u2026"); |
| return new WebInspector.HeapProfileHeader(this, title); |
| }, |
| |
| /** |
| * @override |
| * @param {HeapProfilerAgent.ProfileHeader} profile |
| * @return {!WebInspector.ProfileHeader} |
| */ |
| createProfile: function(profile) |
| { |
| return new WebInspector.HeapProfileHeader(this, profile.title, profile.uid, profile.maxJSObjectId || 0); |
| }, |
| |
| _takeHeapSnapshot: function(callback) |
| { |
| var temporaryProfile = this.findTemporaryProfile(); |
| if (!temporaryProfile) |
| this.addProfile(this.createTemporaryProfile()); |
| HeapProfilerAgent.takeHeapSnapshot(true, callback); |
| }, |
| |
| /** |
| * @param {HeapProfilerAgent.ProfileHeader} profileHeader |
| */ |
| addProfileHeader: function(profileHeader) |
| { |
| if (!this.findTemporaryProfile()) |
| return; |
| var profile = this.createProfile(profileHeader); |
| profile._profileSamples = this._profileSamples; |
| this._profileSamples = null; |
| this.addProfile(profile); |
| }, |
| |
| /** |
| * @override |
| * @param {number} uid |
| * @param {string} chunk |
| */ |
| addHeapSnapshotChunk: function(uid, chunk) |
| { |
| var profile = this._profilesIdMap[this._makeKey(uid)]; |
| if (profile) |
| profile.transferChunk(chunk); |
| }, |
| |
| /** |
| * @override |
| * @param {number} done |
| * @param {number} total |
| */ |
| reportHeapSnapshotProgress: function(done, total) |
| { |
| var profile = this.findTemporaryProfile(); |
| if (profile) |
| this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProgressUpdated, {"profile": profile, "done": done, "total": total}); |
| }, |
| |
| /** |
| * @override |
| */ |
| resetProfiles: function() |
| { |
| this._reset(); |
| }, |
| |
| /** |
| * @override |
| * @param {!WebInspector.ProfileHeader} profile |
| */ |
| removeProfile: function(profile) |
| { |
| WebInspector.ProfileType.prototype.removeProfile.call(this, profile); |
| if (!profile.isTemporary && !profile.fromFile()) |
| HeapProfilerAgent.removeProfile(profile.uid); |
| }, |
| |
| _snapshotReceived: function(profile) |
| { |
| this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile); |
| }, |
| |
| __proto__: WebInspector.ProfileType.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.HeapSnapshotProfileType} |
| * @param {WebInspector.ProfilesPanel} profilesPanel |
| */ |
| WebInspector.TrackingHeapSnapshotProfileType = function(profilesPanel) |
| { |
| WebInspector.ProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations")); |
| this._profilesPanel = profilesPanel; |
| WebInspector.HeapProfilerDispatcher._dispatcher.register(this); |
| } |
| |
| WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD"; |
| |
| WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate"; |
| WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted"; |
| WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped"; |
| |
| WebInspector.TrackingHeapSnapshotProfileType.prototype = { |
| |
| /** |
| * @override |
| * @param {Array.<number>} samples |
| */ |
| heapStatsUpdate: function(samples) |
| { |
| if (!this._profileSamples) |
| return; |
| var index; |
| for (var i = 0; i < samples.length; i += 3) { |
| index = samples[i]; |
| var count = samples[i+1]; |
| var size = samples[i+2]; |
| this._profileSamples.sizes[index] = size; |
| if (!this._profileSamples.max[index] || size > this._profileSamples.max[index]) |
| this._profileSamples.max[index] = size; |
| } |
| this._lastUpdatedIndex = index; |
| }, |
| |
| /** |
| * @override |
| * @param {number} lastSeenObjectId |
| * @param {number} timestamp |
| */ |
| lastSeenObjectId: function(lastSeenObjectId, timestamp) |
| { |
| var profileSamples = this._profileSamples; |
| if (!profileSamples) |
| return; |
| var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1); |
| profileSamples.ids[currentIndex] = lastSeenObjectId; |
| if (!profileSamples.max[currentIndex]) { |
| profileSamples.max[currentIndex] = 0; |
| profileSamples.sizes[currentIndex] = 0; |
| } |
| profileSamples.timestamps[currentIndex] = timestamp; |
| if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0]) |
| profileSamples.totalTime *= 2; |
| this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples); |
| var profile = this.findTemporaryProfile(); |
| profile.sidebarElement.wait = true; |
| if (profile.sidebarElement && !profile.sidebarElement.wait) |
| profile.sidebarElement.wait = true; |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| hasTemporaryView: function() |
| { |
| return true; |
| }, |
| |
| get buttonTooltip() |
| { |
| return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile."); |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isInstantProfile: function() |
| { |
| return false; |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| buttonClicked: function() |
| { |
| return this._toggleRecording(); |
| }, |
| |
| _startRecordingProfile: function() |
| { |
| this._lastSeenIndex = -1; |
| this._profileSamples = { |
| 'sizes': [], |
| 'ids': [], |
| 'timestamps': [], |
| 'max': [], |
| 'totalTime': 30000 |
| }; |
| this._recording = true; |
| HeapProfilerAgent.startTrackingHeapObjects(); |
| this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted); |
| }, |
| |
| _stopRecordingProfile: function() |
| { |
| HeapProfilerAgent.stopTrackingHeapObjects(); |
| HeapProfilerAgent.takeHeapSnapshot(true); |
| this._recording = false; |
| this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped); |
| }, |
| |
| _toggleRecording: function() |
| { |
| if (this._recording) |
| this._stopRecordingProfile(); |
| else |
| this._startRecordingProfile(); |
| return this._recording; |
| }, |
| |
| get treeItemTitle() |
| { |
| return WebInspector.UIString("HEAP TIMELINES"); |
| }, |
| |
| get description() |
| { |
| return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks."); |
| }, |
| |
| _reset: function() |
| { |
| WebInspector.HeapSnapshotProfileType.prototype._reset.call(this); |
| if (this._recording) |
| this._stopRecordingProfile(); |
| this._profileSamples = null; |
| this._lastSeenIndex = -1; |
| }, |
| |
| /** |
| * @override |
| * @param {string=} title |
| * @return {!WebInspector.ProfileHeader} |
| */ |
| createTemporaryProfile: function(title) |
| { |
| title = title || WebInspector.UIString("Recording\u2026"); |
| return new WebInspector.HeapProfileHeader(this, title); |
| }, |
| |
| __proto__: WebInspector.HeapSnapshotProfileType.prototype |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.ProfileHeader} |
| * @param {!WebInspector.ProfileType} type |
| * @param {string} title |
| * @param {number=} uid |
| * @param {number=} maxJSObjectId |
| */ |
| WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId) |
| { |
| WebInspector.ProfileHeader.call(this, type, title, uid); |
| this.maxJSObjectId = maxJSObjectId; |
| /** |
| * @type {WebInspector.OutputStream} |
| */ |
| this._receiver = null; |
| /** |
| * @type {WebInspector.HeapSnapshotProxy} |
| */ |
| this._snapshotProxy = null; |
| this._totalNumberOfChunks = 0; |
| this._transferHandler = null; |
| } |
| |
| WebInspector.HeapProfileHeader.prototype = { |
| /** |
| * @override |
| */ |
| createSidebarTreeElement: function() |
| { |
| return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item"); |
| }, |
| |
| /** |
| * @override |
| * @param {!WebInspector.ProfilesPanel} profilesPanel |
| */ |
| createView: function(profilesPanel) |
| { |
| return new WebInspector.HeapSnapshotView(profilesPanel, this); |
| }, |
| |
| /** |
| * @override |
| * @param {function(WebInspector.HeapSnapshotProxy):void} callback |
| */ |
| load: function(callback) |
| { |
| if (this.uid === -1) |
| return; |
| if (this._snapshotProxy) { |
| callback(this._snapshotProxy); |
| return; |
| } |
| |
| this._numberOfChunks = 0; |
| if (!this._receiver) { |
| this._setupWorker(); |
| this._transferHandler = new WebInspector.BackendSnapshotLoader(this); |
| this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026"); |
| this.sidebarElement.wait = true; |
| this._transferSnapshot(); |
| } |
| var loaderProxy = /** @type {WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver); |
| loaderProxy.addConsumer(callback); |
| }, |
| |
| _transferSnapshot: function() |
| { |
| function finishTransfer() |
| { |
| if (this._transferHandler) { |
| this._transferHandler.finishTransfer(); |
| this._totalNumberOfChunks = this._transferHandler._totalNumberOfChunks; |
| } |
| } |
| HeapProfilerAgent.getHeapSnapshot(this.uid, finishTransfer.bind(this)); |
| }, |
| |
| snapshotConstructorName: function() |
| { |
| return "JSHeapSnapshot"; |
| }, |
| |
| snapshotProxyConstructor: function() |
| { |
| return WebInspector.HeapSnapshotProxy; |
| }, |
| |
| _setupWorker: function() |
| { |
| function setProfileWait(event) |
| { |
| this.sidebarElement.wait = event.data; |
| } |
| var worker = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this)); |
| worker.addEventListener("wait", setProfileWait, this); |
| var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor()); |
| loaderProxy.addConsumer(this._snapshotReceived.bind(this)); |
| this._receiver = loaderProxy; |
| }, |
| |
| /** |
| * @param{string} eventName |
| * @param{*} data |
| */ |
| _handleWorkerEvent: function(eventName, data) |
| { |
| if (WebInspector.HeapSnapshotProgress.Event.Update !== eventName) |
| return; |
| this._updateSubtitle(data); |
| }, |
| |
| /** |
| * @override |
| */ |
| dispose: function() |
| { |
| if (this._receiver) |
| this._receiver.close(); |
| else if (this._snapshotProxy) |
| this._snapshotProxy.dispose(); |
| if (this._view) { |
| var view = this._view; |
| this._view = null; |
| view.dispose(); |
| } |
| }, |
| |
| _updateSubtitle: function(value) |
| { |
| this.sidebarElement.subtitle = value; |
| }, |
| |
| _didCompleteSnapshotTransfer: function() |
| { |
| this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize); |
| this.sidebarElement.wait = false; |
| }, |
| |
| /** |
| * @param {string} chunk |
| */ |
| transferChunk: function(chunk) |
| { |
| this._transferHandler.transferChunk(chunk); |
| }, |
| |
| _snapshotReceived: function(snapshotProxy) |
| { |
| this._receiver = null; |
| if (snapshotProxy) |
| this._snapshotProxy = snapshotProxy; |
| this._didCompleteSnapshotTransfer(); |
| var worker = /** @type {WebInspector.HeapSnapshotWorkerProxy} */ (this._snapshotProxy.worker); |
| this.isTemporary = false; |
| worker.startCheckingForLongRunningCalls(); |
| this.notifySnapshotReceived(); |
| |
| if (this.fromFile()) { |
| function didGetMaxNodeId(id) |
| { |
| this.maxJSObjectId = id; |
| } |
| snapshotProxy.maxJsNodeId(didGetMaxNodeId.bind(this)); |
| } |
| }, |
| |
| notifySnapshotReceived: function() |
| { |
| this._profileType._snapshotReceived(this); |
| }, |
| |
| // Hook point for tests. |
| _wasShown: function() |
| { |
| }, |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| canSaveToFile: function() |
| { |
| return !this.fromFile() && !!this._snapshotProxy && !this._receiver; |
| }, |
| |
| /** |
| * @override |
| */ |
| saveToFile: function() |
| { |
| var fileOutputStream = new WebInspector.FileOutputStream(); |
| function onOpen() |
| { |
| this._receiver = fileOutputStream; |
| this._transferHandler = new WebInspector.SaveSnapshotHandler(this); |
| this._transferSnapshot(); |
| } |
| this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension(); |
| fileOutputStream.open(this._fileName, onOpen.bind(this)); |
| }, |
| |
| /** |
| * @override |
| * @param {File} file |
| */ |
| loadFromFile: function(file) |
| { |
| this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026"); |
| this.sidebarElement.wait = true; |
| this._setupWorker(); |
| |
| var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this); |
| var fileReader = this._createFileReader(file, delegate); |
| fileReader.start(this._receiver); |
| }, |
| |
| _createFileReader: function(file, delegate) |
| { |
| return new WebInspector.ChunkedFileReader(file, 10000000, delegate); |
| }, |
| |
| __proto__: WebInspector.ProfileHeader.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| * @param {WebInspector.HeapProfileHeader} header |
| * @param {string} title |
| */ |
| WebInspector.SnapshotTransferHandler = function(header, title) |
| { |
| this._numberOfChunks = 0; |
| this._savedChunks = 0; |
| this._header = header; |
| this._totalNumberOfChunks = 0; |
| this._title = title; |
| } |
| |
| |
| WebInspector.SnapshotTransferHandler.prototype = { |
| /** |
| * @param {string} chunk |
| */ |
| transferChunk: function(chunk) |
| { |
| ++this._numberOfChunks; |
| this._header._receiver.write(chunk, this._didTransferChunk.bind(this)); |
| }, |
| |
| finishTransfer: function() |
| { |
| }, |
| |
| _didTransferChunk: function() |
| { |
| this._updateProgress(++this._savedChunks, this._totalNumberOfChunks); |
| }, |
| |
| _updateProgress: function(value, total) |
| { |
| } |
| } |
| |
| |
| /** |
| * @constructor |
| * @param {WebInspector.HeapProfileHeader} header |
| * @extends {WebInspector.SnapshotTransferHandler} |
| */ |
| WebInspector.SaveSnapshotHandler = function(header) |
| { |
| WebInspector.SnapshotTransferHandler.call(this, header, "Saving\u2026 %d\%"); |
| this._totalNumberOfChunks = header._totalNumberOfChunks; |
| this._updateProgress(0, this._totalNumberOfChunks); |
| } |
| |
| |
| WebInspector.SaveSnapshotHandler.prototype = { |
| _updateProgress: function(value, total) |
| { |
| var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); |
| this._header._updateSubtitle(WebInspector.UIString(this._title, percentValue)); |
| if (value === total) { |
| this._header._receiver.close(); |
| this._header._didCompleteSnapshotTransfer(); |
| } |
| }, |
| |
| __proto__: WebInspector.SnapshotTransferHandler.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| * @param {WebInspector.HeapProfileHeader} header |
| * @extends {WebInspector.SnapshotTransferHandler} |
| */ |
| WebInspector.BackendSnapshotLoader = function(header) |
| { |
| WebInspector.SnapshotTransferHandler.call(this, header, "Loading\u2026 %d\%"); |
| } |
| |
| |
| WebInspector.BackendSnapshotLoader.prototype = { |
| finishTransfer: function() |
| { |
| this._header._receiver.close(this._didFinishTransfer.bind(this)); |
| this._totalNumberOfChunks = this._numberOfChunks; |
| }, |
| |
| _didFinishTransfer: function() |
| { |
| console.assert(this._totalNumberOfChunks === this._savedChunks, "Not all chunks were transfered."); |
| }, |
| |
| __proto__: WebInspector.SnapshotTransferHandler.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.OutputStreamDelegate} |
| */ |
| WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader) |
| { |
| this._snapshotHeader = snapshotHeader; |
| } |
| |
| WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = { |
| onTransferStarted: function() |
| { |
| }, |
| |
| /** |
| * @param {WebInspector.ChunkedReader} reader |
| */ |
| onChunkTransferred: function(reader) |
| { |
| }, |
| |
| onTransferFinished: function() |
| { |
| }, |
| |
| /** |
| * @param {WebInspector.ChunkedReader} reader |
| */ |
| onError: function (reader, e) |
| { |
| switch(e.target.error.code) { |
| case e.target.error.NOT_FOUND_ERR: |
| this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' not found.", reader.fileName())); |
| break; |
| case e.target.error.NOT_READABLE_ERR: |
| this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' is not readable", reader.fileName())); |
| break; |
| case e.target.error.ABORT_ERR: |
| break; |
| default: |
| this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code)); |
| } |
| } |
| } |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.View} |
| * @param {!WebInspector.HeapProfileHeader} heapProfileHeader |
| */ |
| WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader) |
| { |
| WebInspector.View.call(this); |
| this.registerRequiredCSS("flameChart.css"); |
| this.element.id = "heap-recording-view"; |
| |
| this._overviewContainer = this.element.createChild("div", "overview-container"); |
| this._overviewGrid = new WebInspector.OverviewGrid("heap-recording"); |
| this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas"); |
| this._overviewContainer.appendChild(this._overviewGrid.element); |
| this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator(); |
| this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); |
| |
| this._profileSamples = heapProfileHeader._profileSamples || heapProfileHeader._profileType._profileSamples; |
| if (heapProfileHeader.isTemporary) { |
| this._profileType = heapProfileHeader._profileType; |
| this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); |
| this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); |
| } |
| var timestamps = this._profileSamples.timestamps; |
| var totalTime = this._profileSamples.totalTime; |
| this._windowLeft = 0.0; |
| this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0; |
| this._overviewGrid.setWindow(this._windowLeft, this._windowRight); |
| this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale(); |
| this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale(); |
| } |
| |
| WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged"; |
| |
| WebInspector.HeapTrackingOverviewGrid.prototype = { |
| _onStopTracking: function(event) |
| { |
| this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); |
| this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); |
| }, |
| |
| _onHeapStatsUpdate: function(event) |
| { |
| this._profileSamples = event.data; |
| this._scheduleUpdate(); |
| }, |
| |
| /** |
| * @param {number} width |
| * @param {number} height |
| */ |
| _drawOverviewCanvas: function(width, height) |
| { |
| if (!this._profileSamples) |
| return; |
| var profileSamples = this._profileSamples; |
| var sizes = profileSamples.sizes; |
| var topSizes = profileSamples.max; |
| var timestamps = profileSamples.timestamps; |
| var startTime = timestamps[0]; |
| var endTime = timestamps[timestamps.length - 1]; |
| |
| var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime); |
| var maxSize = 0; |
| /** |
| * @param {Array.<number>} sizes |
| * @param {function(number, number):void} callback |
| */ |
| function aggregateAndCall(sizes, callback) |
| { |
| var size = 0; |
| var currentX = 0; |
| for (var i = 1; i < timestamps.length; ++i) { |
| var x = Math.floor((timestamps[i] - startTime) * scaleFactor); |
| if (x !== currentX) { |
| if (size) |
| callback(currentX, size); |
| size = 0; |
| currentX = x; |
| } |
| size += sizes[i]; |
| } |
| callback(currentX, size); |
| } |
| |
| /** |
| * @param {number} x |
| * @param {number} size |
| */ |
| function maxSizeCallback(x, size) |
| { |
| maxSize = Math.max(maxSize, size); |
| } |
| |
| aggregateAndCall(sizes, maxSizeCallback); |
| |
| var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0); |
| |
| this._overviewCanvas.width = width * window.devicePixelRatio; |
| this._overviewCanvas.height = height * window.devicePixelRatio; |
| this._overviewCanvas.style.width = width + "px"; |
| this._overviewCanvas.style.height = height + "px"; |
| |
| var context = this._overviewCanvas.getContext("2d"); |
| context.scale(window.devicePixelRatio, window.devicePixelRatio); |
| |
| context.beginPath(); |
| context.lineWidth = 2; |
| context.strokeStyle = "rgba(192, 192, 192, 0.6)"; |
| var currentX = (endTime - startTime) * scaleFactor; |
| context.moveTo(currentX, height - 1); |
| context.lineTo(currentX, 0); |
| context.stroke(); |
| context.closePath(); |
| |
| var gridY; |
| var gridValue; |
| var gridLabelHeight = 14; |
| if (yScaleFactor) { |
| const maxGridValue = (height - gridLabelHeight) / yScaleFactor; |
| // The round value calculation is a bit tricky, because |
| // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer, |
| // e.g. a round value 10KB is 10240 bytes. |
| gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024))); |
| gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10)); |
| if (gridValue * 5 <= maxGridValue) |
| gridValue *= 5; |
| gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5; |
| context.beginPath(); |
| context.lineWidth = 1; |
| context.strokeStyle = "rgba(0, 0, 0, 0.2)"; |
| context.moveTo(0, gridY); |
| context.lineTo(width, gridY); |
| context.stroke(); |
| context.closePath(); |
| } |
| |
| /** |
| * @param {number} x |
| * @param {number} size |
| */ |
| function drawBarCallback(x, size) |
| { |
| context.moveTo(x, height - 1); |
| context.lineTo(x, Math.round(height - size * yScaleFactor - 1)); |
| } |
| |
| context.beginPath(); |
| context.lineWidth = 2; |
| context.strokeStyle = "rgba(192, 192, 192, 0.6)"; |
| aggregateAndCall(topSizes, drawBarCallback); |
| context.stroke(); |
| context.closePath(); |
| |
| context.beginPath(); |
| context.lineWidth = 2; |
| context.strokeStyle = "rgba(0, 0, 192, 0.8)"; |
| aggregateAndCall(sizes, drawBarCallback); |
| context.stroke(); |
| context.closePath(); |
| |
| if (gridValue) { |
| var label = Number.bytesToString(gridValue); |
| var labelPadding = 4; |
| var labelX = 0; |
| var labelY = gridY - 0.5; |
| var labelWidth = 2 * labelPadding + context.measureText(label).width; |
| context.beginPath(); |
| context.textBaseline = "bottom"; |
| context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family"); |
| context.fillStyle = "rgba(255, 255, 255, 0.75)"; |
| context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight); |
| context.fillStyle = "rgb(64, 64, 64)"; |
| context.fillText(label, labelX + labelPadding, labelY); |
| context.fill(); |
| context.closePath(); |
| } |
| }, |
| |
| onResize: function() |
| { |
| this._updateOverviewCanvas = true; |
| this._scheduleUpdate(); |
| }, |
| |
| _onWindowChanged: function() |
| { |
| if (!this._updateGridTimerId) |
| this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10); |
| }, |
| |
| _scheduleUpdate: function() |
| { |
| if (this._updateTimerId) |
| return; |
| this._updateTimerId = setTimeout(this.update.bind(this), 10); |
| }, |
| |
| _updateBoundaries: function() |
| { |
| this._windowLeft = this._overviewGrid.windowLeft(); |
| this._windowRight = this._overviewGrid.windowRight(); |
| this._windowWidth = this._windowRight - this._windowLeft; |
| }, |
| |
| update: function() |
| { |
| this._updateTimerId = null; |
| if (!this.isShowing()) |
| return; |
| this._updateBoundaries(); |
| this._overviewCalculator._updateBoundaries(this); |
| this._overviewGrid.updateDividers(this._overviewCalculator); |
| this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20); |
| }, |
| |
| _updateGrid: function() |
| { |
| this._updateGridTimerId = 0; |
| this._updateBoundaries(); |
| var ids = this._profileSamples.ids; |
| var timestamps = this._profileSamples.timestamps; |
| var sizes = this._profileSamples.sizes; |
| var startTime = timestamps[0]; |
| var totalTime = this._profileSamples.totalTime; |
| var timeLeft = startTime + totalTime * this._windowLeft; |
| var timeRight = startTime + totalTime * this._windowRight; |
| var minId = 0; |
| var maxId = ids[ids.length - 1] + 1; |
| var size = 0; |
| for (var i = 0; i < timestamps.length; ++i) { |
| if (!timestamps[i]) |
| continue; |
| if (timestamps[i] > timeRight) |
| break; |
| maxId = ids[i]; |
| if (timestamps[i] < timeLeft) { |
| minId = ids[i]; |
| continue; |
| } |
| size += sizes[i]; |
| } |
| |
| this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size}); |
| }, |
| |
| __proto__: WebInspector.View.prototype |
| } |
| |
| |
| /** |
| * @constructor |
| */ |
| WebInspector.HeapTrackingOverviewGrid.SmoothScale = function() |
| { |
| this._lastUpdate = 0; |
| this._currentScale = 0.0; |
| } |
| |
| WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = { |
| /** |
| * @param {number} target |
| * @return {number} |
| */ |
| nextScale: function(target) { |
| target = target || this._currentScale; |
| if (this._currentScale) { |
| var now = Date.now(); |
| var timeDeltaMs = now - this._lastUpdate; |
| this._lastUpdate = now; |
| var maxChangePerSec = 20; |
| var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000); |
| var scaleChange = target / this._currentScale; |
| this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta); |
| } else |
| this._currentScale = target; |
| return this._currentScale; |
| } |
| } |
| |
| |
| /** |
| * @constructor |
| * @implements {WebInspector.TimelineGrid.Calculator} |
| */ |
| WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function() |
| { |
| } |
| |
| WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = { |
| /** |
| * @param {WebInspector.HeapTrackingOverviewGrid} chart |
| */ |
| _updateBoundaries: function(chart) |
| { |
| this._minimumBoundaries = 0; |
| this._maximumBoundaries = chart._profileSamples.totalTime; |
| this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries; |
| }, |
| |
| /** |
| * @param {number} time |
| */ |
| computePosition: function(time) |
| { |
| return (time - this._minimumBoundaries) * this._xScaleFactor; |
| }, |
| |
| formatTime: function(value) |
| { |
| return Number.secondsToString((value + this._minimumBoundaries) / 1000); |
| }, |
| |
| maximumBoundary: function() |
| { |
| return this._maximumBoundaries; |
| }, |
| |
| minimumBoundary: function() |
| { |
| return this._minimumBoundaries; |
| }, |
| |
| zeroTime: function() |
| { |
| return this._minimumBoundaries; |
| }, |
| |
| boundarySpan: function() |
| { |
| return this._maximumBoundaries - this._minimumBoundaries; |
| } |
| } |