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.
33 * @extends {WebInspector.View}
34 * @param {!WebInspector.ProfilesPanel} parent
35 * @param {!WebInspector.HeapProfileHeader} profile
37 WebInspector.HeapSnapshotView = function(parent, profile)
39 WebInspector.View.call(this);
41 this.element.addStyleClass("heap-snapshot-view");
44 this.parent.addEventListener("profile added", this._onProfileHeaderAdded, this);
46 if (profile._profileType.id === WebInspector.TrackingHeapSnapshotProfileType.TypeId) {
47 this._trackingOverviewGrid = new WebInspector.HeapTrackingOverviewGrid(profile);
48 this._trackingOverviewGrid.addEventListener(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, this._onIdsRangeChanged.bind(this));
49 this._trackingOverviewGrid.show(this.element);
52 this.viewsContainer = document.createElement("div");
53 this.viewsContainer.addStyleClass("views-container");
54 this.element.appendChild(this.viewsContainer);
56 this.containmentView = new WebInspector.View();
57 this.containmentView.element.addStyleClass("view");
58 this.containmentDataGrid = new WebInspector.HeapSnapshotContainmentDataGrid();
59 this.containmentDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
60 this.containmentDataGrid.show(this.containmentView.element);
61 this.containmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
63 this.constructorsView = new WebInspector.View();
64 this.constructorsView.element.addStyleClass("view");
65 this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter());
67 this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid();
68 this.constructorsDataGrid.element.addStyleClass("class-view-grid");
69 this.constructorsDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
70 this.constructorsDataGrid.show(this.constructorsView.element);
71 this.constructorsDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
73 this.dataGrid = /** @type {WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid);
74 this.currentView = this.constructorsView;
75 this.currentView.show(this.viewsContainer);
77 this.diffView = new WebInspector.View();
78 this.diffView.element.addStyleClass("view");
79 this.diffView.element.appendChild(this._createToolbarWithClassNameFilter());
81 this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid();
82 this.diffDataGrid.element.addStyleClass("class-view-grid");
83 this.diffDataGrid.show(this.diffView.element);
84 this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
86 this.dominatorView = new WebInspector.View();
87 this.dominatorView.element.addStyleClass("view");
88 this.dominatorDataGrid = new WebInspector.HeapSnapshotDominatorsDataGrid();
89 this.dominatorDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
90 this.dominatorDataGrid.show(this.dominatorView.element);
91 this.dominatorDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
93 if (WebInspector.HeapSnapshot.enableAllocationProfiler) {
94 this.allocationView = new WebInspector.View();
95 this.allocationView.element.addStyleClass("view");
96 this.allocationDataGrid = new WebInspector.AllocationDataGrid();
97 this.allocationDataGrid.element.addEventListener("mousedown", this._mouseDownInContentsGrid.bind(this), true);
98 this.allocationDataGrid.show(this.allocationView.element);
99 this.allocationDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this);
102 this.retainmentViewHeader = document.createElement("div");
103 this.retainmentViewHeader.addStyleClass("retainers-view-header");
104 WebInspector.installDragHandle(this.retainmentViewHeader, this._startRetainersHeaderDragging.bind(this), this._retainersHeaderDragging.bind(this), this._endRetainersHeaderDragging.bind(this), "row-resize");
105 var retainingPathsTitleDiv = document.createElement("div");
106 retainingPathsTitleDiv.className = "title";
107 var retainingPathsTitle = document.createElement("span");
108 retainingPathsTitle.textContent = WebInspector.UIString("Object's retaining tree");
109 retainingPathsTitleDiv.appendChild(retainingPathsTitle);
110 this.retainmentViewHeader.appendChild(retainingPathsTitleDiv);
111 this.element.appendChild(this.retainmentViewHeader);
113 this.retainmentView = new WebInspector.View();
114 this.retainmentView.element.addStyleClass("view");
115 this.retainmentView.element.addStyleClass("retaining-paths-view");
116 this.retainmentDataGrid = new WebInspector.HeapSnapshotRetainmentDataGrid();
117 this.retainmentDataGrid.show(this.retainmentView.element);
118 this.retainmentDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._inspectedObjectChanged, this);
119 this.retainmentView.show(this.element);
120 this.retainmentDataGrid.reset();
122 this.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this));
124 this.views = [{title: "Summary", view: this.constructorsView, grid: this.constructorsDataGrid},
125 {title: "Comparison", view: this.diffView, grid: this.diffDataGrid},
126 {title: "Containment", view: this.containmentView, grid: this.containmentDataGrid}];
127 if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get())
128 this.views.push({title: "Dominators", view: this.dominatorView, grid: this.dominatorDataGrid});
129 if (WebInspector.HeapSnapshot.enableAllocationProfiler)
130 this.views.push({title: "Allocation", view: this.allocationView, grid: this.allocationDataGrid});
131 this.views.current = 0;
132 for (var i = 0; i < this.views.length; ++i)
133 this.viewSelect.createOption(WebInspector.UIString(this.views[i].title));
135 this._profileUid = profile.uid;
136 this._profileTypeId = profile.profileType().id;
138 this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this));
139 this.baseSelect.element.addStyleClass("hidden");
140 this._updateBaseOptions();
142 this.filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this));
143 this._updateFilterOptions();
145 this.selectedSizeText = new WebInspector.StatusBarText("");
147 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true);
149 this.profile.load(profileCallback.bind(this));
151 function profileCallback(heapSnapshotProxy)
153 var list = this._profiles();
155 for (var i = 0; i < list.length; ++i) {
156 if (list[i].uid === this._profileUid) {
162 if (profileIndex > 0)
163 this.baseSelect.setSelectedIndex(profileIndex - 1);
165 this.baseSelect.setSelectedIndex(profileIndex);
166 this.dataGrid.setDataSource(heapSnapshotProxy);
170 WebInspector.HeapSnapshotView.prototype = {
171 _onIdsRangeChanged: function(event)
173 var minId = event.data.minId;
174 var maxId = event.data.maxId;
175 this.selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size)));
176 if (this.constructorsDataGrid.snapshot)
177 this.constructorsDataGrid.setSelectionRange(minId, maxId);
182 this.parent.removeEventListener("profile added", this._onProfileHeaderAdded, this);
183 this.profile.dispose();
184 if (this.baseProfile)
185 this.baseProfile.dispose();
186 this.containmentDataGrid.dispose();
187 this.constructorsDataGrid.dispose();
188 this.diffDataGrid.dispose();
189 this.dominatorDataGrid.dispose();
190 this.retainmentDataGrid.dispose();
195 return [this.viewSelect.element, this.baseSelect.element, this.filterSelect.element, this.selectedSizeText.element];
200 return this.parent.getProfile(this._profileTypeId, this._profileUid);
205 return this.parent.getProfile(this._profileTypeId, this._baseProfileUid);
210 // FIXME: load base and current snapshots in parallel
211 this.profile.load(profileCallback.bind(this));
212 function profileCallback() {
213 this.profile._wasShown();
214 if (this.baseProfile)
215 this.baseProfile.load(function() { });
221 this._currentSearchResultIndex = -1;
222 this._popoverHelper.hidePopover();
223 if (this.helpPopover && this.helpPopover.isShowing())
224 this.helpPopover.hide();
229 var height = this.retainmentView.element.clientHeight;
230 this._updateRetainmentViewHeight(height);
233 searchCanceled: function()
235 if (this._searchResults) {
236 for (var i = 0; i < this._searchResults.length; ++i) {
237 var node = this._searchResults[i].node;
238 delete node._searchMatched;
243 delete this._searchFinishedCallback;
244 this._currentSearchResultIndex = -1;
245 this._searchResults = [];
249 * @param {string} query
250 * @param {function(!WebInspector.View, number)} finishedCallback
252 performSearch: function(query, finishedCallback)
254 // Call searchCanceled since it will reset everything we need before doing a new search.
255 this.searchCanceled();
257 query = query.trim();
261 if (this.currentView !== this.constructorsView && this.currentView !== this.diffView)
264 this._searchFinishedCallback = finishedCallback;
265 var nameRegExp = createPlainTextSearchRegex(query, "i");
266 var snapshotNodeId = null;
268 function matchesByName(gridNode) {
269 return ("_name" in gridNode) && nameRegExp.test(gridNode._name);
272 function matchesById(gridNode) {
273 return ("snapshotNodeId" in gridNode) && gridNode.snapshotNodeId === snapshotNodeId;
277 if (query.charAt(0) !== "@")
278 matchPredicate = matchesByName;
280 snapshotNodeId = parseInt(query.substring(1), 10);
281 matchPredicate = matchesById;
284 function matchesQuery(gridNode)
286 delete gridNode._searchMatched;
287 if (matchPredicate(gridNode)) {
288 gridNode._searchMatched = true;
295 var current = this.dataGrid.rootNode().children[0];
299 // Restrict to type nodes and instances.
303 if (matchesQuery(current))
304 this._searchResults.push({ node: current });
305 current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
306 depth += info.depthChange;
309 finishedCallback(this, this._searchResults.length);
312 jumpToFirstSearchResult: function()
314 if (!this._searchResults || !this._searchResults.length)
316 this._currentSearchResultIndex = 0;
317 this._jumpToSearchResult(this._currentSearchResultIndex);
320 jumpToLastSearchResult: function()
322 if (!this._searchResults || !this._searchResults.length)
324 this._currentSearchResultIndex = (this._searchResults.length - 1);
325 this._jumpToSearchResult(this._currentSearchResultIndex);
328 jumpToNextSearchResult: function()
330 if (!this._searchResults || !this._searchResults.length)
332 if (++this._currentSearchResultIndex >= this._searchResults.length)
333 this._currentSearchResultIndex = 0;
334 this._jumpToSearchResult(this._currentSearchResultIndex);
337 jumpToPreviousSearchResult: function()
339 if (!this._searchResults || !this._searchResults.length)
341 if (--this._currentSearchResultIndex < 0)
342 this._currentSearchResultIndex = (this._searchResults.length - 1);
343 this._jumpToSearchResult(this._currentSearchResultIndex);
346 showingFirstSearchResult: function()
348 return (this._currentSearchResultIndex === 0);
351 showingLastSearchResult: function()
353 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1));
356 _jumpToSearchResult: function(index)
358 var searchResult = this._searchResults[index];
362 var node = searchResult.node;
363 node.revealAndSelect();
366 refreshVisibleData: function()
368 var child = this.dataGrid.rootNode().children[0];
371 child = child.traverseNextNode(false, null, true);
375 _changeBase: function()
377 if (this._baseProfileUid === this._profiles()[this.baseSelect.selectedIndex()].uid)
380 this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
381 var dataGrid = /** @type {WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid);
382 // Change set base data source only if main data source is already set.
383 if (dataGrid.snapshot)
384 this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid));
386 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
389 // The current search needs to be performed again. First negate out previous match
390 // count by calling the search finished callback with a negative number of matches.
391 // Then perform the search again with the same query and callback.
392 this._searchFinishedCallback(this, -this._searchResults.length);
393 this.performSearch(this.currentQuery, this._searchFinishedCallback);
396 _changeFilter: function()
398 var profileIndex = this.filterSelect.selectedIndex() - 1;
399 this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex);
401 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
402 action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged,
403 label: this.filterSelect.selectedOption().label
406 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
409 // The current search needs to be performed again. First negate out previous match
410 // count by calling the search finished callback with a negative number of matches.
411 // Then perform the search again with the same query and callback.
412 this._searchFinishedCallback(this, -this._searchResults.length);
413 this.performSearch(this.currentQuery, this._searchFinishedCallback);
416 _createToolbarWithClassNameFilter: function()
418 var toolbar = document.createElement("div");
419 toolbar.addStyleClass("class-view-toolbar");
420 var classNameFilter = document.createElement("input");
421 classNameFilter.addStyleClass("class-name-filter");
422 classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter"));
423 classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false);
424 toolbar.appendChild(classNameFilter);
428 _changeNameFilter: function(classNameInputElement)
430 var filter = classNameInputElement.value;
431 this.dataGrid.changeNameFilter(filter);
435 * @return {!Array.<!WebInspector.ProfileHeader>}
437 _profiles: function()
439 return this.parent.getProfileType(this._profileTypeId).getProfiles();
443 * @param {WebInspector.ContextMenu} contextMenu
444 * @param {Event} event
446 populateContextMenu: function(contextMenu, event)
448 this.dataGrid.populateContextMenu(this.parent, contextMenu, event);
451 _selectionChanged: function(event)
453 var selectedNode = event.target.selectedNode;
454 this._setRetainmentDataGridSource(selectedNode);
455 this._inspectedObjectChanged(event);
458 _inspectedObjectChanged: function(event)
460 var selectedNode = event.target.selectedNode;
461 if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode)
462 ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId);
465 _setRetainmentDataGridSource: function(nodeItem)
467 if (nodeItem && nodeItem.snapshotNodeIndex)
468 this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex);
470 this.retainmentDataGrid.reset();
473 _mouseDownInContentsGrid: function(event)
475 if (event.detail < 2)
478 var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
479 if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("shallowSize-column") && !cell.hasStyleClass("retainedSize-column")))
485 changeView: function(viewTitle, callback)
487 var viewIndex = null;
488 for (var i = 0; i < this.views.length; ++i) {
489 if (this.views[i].title === viewTitle) {
494 if (this.views.current === viewIndex || viewIndex == null) {
495 setTimeout(callback, 0);
499 function dataGridContentShown(event)
501 var dataGrid = event.data;
502 dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
503 if (dataGrid === this.dataGrid)
506 this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this);
508 this.viewSelect.setSelectedIndex(viewIndex);
509 this._changeView(viewIndex);
512 _updateDataSourceAndView: function()
514 var dataGrid = this.dataGrid;
515 if (dataGrid.snapshot)
518 this.profile.load(didLoadSnapshot.bind(this));
519 function didLoadSnapshot(snapshotProxy)
521 if (this.dataGrid !== dataGrid)
523 if (dataGrid.snapshot !== snapshotProxy)
524 dataGrid.setDataSource(snapshotProxy);
525 if (dataGrid === this.diffDataGrid) {
526 if (!this._baseProfileUid)
527 this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid;
528 this.baseProfile.load(didLoadBaseSnaphot.bind(this));
532 function didLoadBaseSnaphot(baseSnapshotProxy)
534 if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy)
535 this.diffDataGrid.setBaseDataSource(baseSnapshotProxy);
539 _onSelectedViewChanged: function(event)
541 this._changeView(event.target.selectedIndex);
544 _updateSelectorsVisibility: function()
546 if (this.currentView === this.diffView)
547 this.baseSelect.element.removeStyleClass("hidden");
549 this.baseSelect.element.addStyleClass("hidden");
551 if (this.currentView === this.constructorsView) {
552 if (this._trackingOverviewGrid) {
553 this._trackingOverviewGrid.element.removeStyleClass("hidden");
554 this._trackingOverviewGrid.update();
555 this.viewsContainer.addStyleClass("reserve-80px-at-top");
557 this.filterSelect.element.removeStyleClass("hidden");
559 this.filterSelect.element.addStyleClass("hidden");
560 if (this._trackingOverviewGrid) {
561 this._trackingOverviewGrid.element.addStyleClass("hidden");
562 this.viewsContainer.removeStyleClass("reserve-80px-at-top");
567 _changeView: function(selectedIndex)
569 if (selectedIndex === this.views.current)
572 this.views.current = selectedIndex;
573 this.currentView.detach();
574 var view = this.views[this.views.current];
575 this.currentView = view.view;
576 this.dataGrid = view.grid;
577 this.currentView.show(this.viewsContainer);
578 this.refreshVisibleData();
579 this.dataGrid.updateWidths();
581 this._updateSelectorsVisibility();
583 this._updateDataSourceAndView();
585 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
588 // The current search needs to be performed again. First negate out previous match
589 // count by calling the search finished callback with a negative number of matches.
590 // Then perform the search again the with same query and callback.
591 this._searchFinishedCallback(this, -this._searchResults.length);
592 this.performSearch(this.currentQuery, this._searchFinishedCallback);
595 _getHoverAnchor: function(target)
597 var span = target.enclosingNodeOrSelfWithNodeName("span");
600 var row = target.enclosingNodeOrSelfWithNodeName("tr");
603 span.node = row._dataGridNode;
607 _resolveObjectForPopover: function(element, showCallback, objectGroupName)
609 if (this.profile.fromFile())
611 element.node.queryObjectContent(showCallback, objectGroupName);
617 _startRetainersHeaderDragging: function(event)
619 if (!this.isShowing())
622 this._previousDragPosition = event.pageY;
626 _retainersHeaderDragging: function(event)
628 var height = this.retainmentView.element.clientHeight;
629 height += this._previousDragPosition - event.pageY;
630 this._previousDragPosition = event.pageY;
631 this._updateRetainmentViewHeight(height);
635 _endRetainersHeaderDragging: function(event)
637 delete this._previousDragPosition;
641 _updateRetainmentViewHeight: function(height)
643 height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight);
644 this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px";
645 if (this._trackingOverviewGrid && this.currentView === this.constructorsView)
646 this.viewsContainer.addStyleClass("reserve-80px-at-top");
647 this.retainmentView.element.style.height = height + "px";
648 this.retainmentViewHeader.style.bottom = height + "px";
649 this.currentView.doResize();
652 _updateBaseOptions: function()
654 var list = this._profiles();
655 // We're assuming that snapshots can only be added.
656 if (this.baseSelect.size() === list.length)
659 for (var i = this.baseSelect.size(), n = list.length; i < n; ++i) {
660 var title = list[i].title;
661 this.baseSelect.createOption(title);
665 _updateFilterOptions: function()
667 var list = this._profiles();
668 // We're assuming that snapshots can only be added.
669 if (this.filterSelect.size() - 1 === list.length)
672 if (!this.filterSelect.size())
673 this.filterSelect.createOption(WebInspector.UIString("All objects"));
675 for (var i = this.filterSelect.size() - 1, n = list.length; i < n; ++i) {
676 var title = list[i].title;
678 title = WebInspector.UIString("Objects allocated before %s", title);
680 title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title);
681 this.filterSelect.createOption(title);
686 * @param {WebInspector.Event} event
688 _onProfileHeaderAdded: function(event)
690 if (!event.data || event.data.type !== this._profileTypeId)
692 this._updateBaseOptions();
693 this._updateFilterOptions();
696 __proto__: WebInspector.View.prototype
701 * @implements {HeapProfilerAgent.Dispatcher}
703 WebInspector.HeapProfilerDispatcher = function()
705 this._dispatchers = [];
706 InspectorBackend.registerHeapProfilerDispatcher(this);
709 WebInspector.HeapProfilerDispatcher.prototype = {
711 * @param {HeapProfilerAgent.Dispatcher} dispatcher
713 register: function(dispatcher)
715 this._dispatchers.push(dispatcher);
718 _genericCaller: function(eventName)
720 var args = Array.prototype.slice.call(arguments.callee.caller.arguments);
721 for (var i = 0; i < this._dispatchers.length; ++i)
722 this._dispatchers[i][eventName].apply(this._dispatchers[i], args);
727 * @param {Array.<number>} samples
729 heapStatsUpdate: function(samples)
731 this._genericCaller("heapStatsUpdate");
736 * @param {number} lastSeenObjectId
737 * @param {number} timestamp
739 lastSeenObjectId: function(lastSeenObjectId, timestamp)
741 this._genericCaller("lastSeenObjectId");
745 * @param {HeapProfilerAgent.ProfileHeader} profileHeader
747 addProfileHeader: function(profileHeader)
749 this._genericCaller("addProfileHeader");
754 * @param {number} uid
755 * @param {string} chunk
757 addHeapSnapshotChunk: function(uid, chunk)
759 this._genericCaller("addHeapSnapshotChunk");
764 * @param {number} done
765 * @param {number} total
767 reportHeapSnapshotProgress: function(done, total)
769 this._genericCaller("reportHeapSnapshotProgress");
775 resetProfiles: function()
777 this._genericCaller("resetProfiles");
781 WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher();
785 * @extends {WebInspector.ProfileType}
786 * @implements {HeapProfilerAgent.Dispatcher}
788 WebInspector.HeapSnapshotProfileType = function()
790 WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot"));
791 WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
794 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
795 WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived";
797 WebInspector.HeapSnapshotProfileType.prototype = {
802 fileExtension: function()
804 return ".heapsnapshot";
809 return WebInspector.UIString("Take heap snapshot.");
816 isInstantProfile: function()
825 buttonClicked: function()
827 this._takeHeapSnapshot(function() {});
828 WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
834 * @param {Array.<number>} samples
836 heapStatsUpdate: function(samples)
842 * @param {number} lastSeenObjectId
843 * @param {number} timestamp
845 lastSeenObjectId: function(lastSeenObjectId, timestamp)
851 return WebInspector.UIString("HEAP SNAPSHOTS");
856 return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes.");
861 * @param {string=} title
862 * @return {!WebInspector.ProfileHeader}
864 createTemporaryProfile: function(title)
866 title = title || WebInspector.UIString("Snapshotting\u2026");
867 return new WebInspector.HeapProfileHeader(this, title);
872 * @param {HeapProfilerAgent.ProfileHeader} profile
873 * @return {!WebInspector.ProfileHeader}
875 createProfile: function(profile)
877 return new WebInspector.HeapProfileHeader(this, profile.title, profile.uid, profile.maxJSObjectId || 0);
880 _takeHeapSnapshot: function(callback)
882 var temporaryProfile = this.findTemporaryProfile();
883 if (!temporaryProfile)
884 this.addProfile(this.createTemporaryProfile());
885 HeapProfilerAgent.takeHeapSnapshot(true, callback);
889 * @param {HeapProfilerAgent.ProfileHeader} profileHeader
891 addProfileHeader: function(profileHeader)
893 if (!this.findTemporaryProfile())
895 var profile = this.createProfile(profileHeader);
896 profile._profileSamples = this._profileSamples;
897 this._profileSamples = null;
898 this.addProfile(profile);
903 * @param {number} uid
904 * @param {string} chunk
906 addHeapSnapshotChunk: function(uid, chunk)
908 var profile = this._profilesIdMap[this._makeKey(uid)];
910 profile.transferChunk(chunk);
915 * @param {number} done
916 * @param {number} total
918 reportHeapSnapshotProgress: function(done, total)
920 var profile = this.findTemporaryProfile();
922 this.dispatchEventToListeners(WebInspector.ProfileType.Events.ProgressUpdated, {"profile": profile, "done": done, "total": total});
928 resetProfiles: function()
935 * @param {!WebInspector.ProfileHeader} profile
937 removeProfile: function(profile)
939 WebInspector.ProfileType.prototype.removeProfile.call(this, profile);
940 if (!profile.isTemporary && !profile.fromFile())
941 HeapProfilerAgent.removeProfile(profile.uid);
944 _snapshotReceived: function(profile)
946 this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile);
949 __proto__: WebInspector.ProfileType.prototype
955 * @extends {WebInspector.HeapSnapshotProfileType}
956 * @param {WebInspector.ProfilesPanel} profilesPanel
958 WebInspector.TrackingHeapSnapshotProfileType = function(profilesPanel)
960 WebInspector.ProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations"));
961 this._profilesPanel = profilesPanel;
962 WebInspector.HeapProfilerDispatcher._dispatcher.register(this);
965 WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD";
967 WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate";
968 WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted";
969 WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped";
971 WebInspector.TrackingHeapSnapshotProfileType.prototype = {
975 * @param {Array.<number>} samples
977 heapStatsUpdate: function(samples)
979 if (!this._profileSamples)
982 for (var i = 0; i < samples.length; i += 3) {
984 var count = samples[i+1];
985 var size = samples[i+2];
986 this._profileSamples.sizes[index] = size;
987 if (!this._profileSamples.max[index] || size > this._profileSamples.max[index])
988 this._profileSamples.max[index] = size;
990 this._lastUpdatedIndex = index;
995 * @param {number} lastSeenObjectId
996 * @param {number} timestamp
998 lastSeenObjectId: function(lastSeenObjectId, timestamp)
1000 var profileSamples = this._profileSamples;
1001 if (!profileSamples)
1003 var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1);
1004 profileSamples.ids[currentIndex] = lastSeenObjectId;
1005 if (!profileSamples.max[currentIndex]) {
1006 profileSamples.max[currentIndex] = 0;
1007 profileSamples.sizes[currentIndex] = 0;
1009 profileSamples.timestamps[currentIndex] = timestamp;
1010 if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0])
1011 profileSamples.totalTime *= 2;
1012 this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples);
1013 var profile = this.findTemporaryProfile();
1014 profile.sidebarElement.wait = true;
1015 if (profile.sidebarElement && !profile.sidebarElement.wait)
1016 profile.sidebarElement.wait = true;
1023 hasTemporaryView: function()
1030 return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile.");
1037 isInstantProfile: function()
1046 buttonClicked: function()
1048 return this._toggleRecording();
1051 _startRecordingProfile: function()
1053 this._lastSeenIndex = -1;
1054 this._profileSamples = {
1061 this._recording = true;
1062 HeapProfilerAgent.startTrackingHeapObjects();
1063 this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted);
1066 _stopRecordingProfile: function()
1068 HeapProfilerAgent.stopTrackingHeapObjects();
1069 HeapProfilerAgent.takeHeapSnapshot(true);
1070 this._recording = false;
1071 this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped);
1074 _toggleRecording: function()
1076 if (this._recording)
1077 this._stopRecordingProfile();
1079 this._startRecordingProfile();
1080 return this._recording;
1085 return WebInspector.UIString("HEAP TIMELINES");
1090 return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks.");
1095 WebInspector.HeapSnapshotProfileType.prototype._reset.call(this);
1096 if (this._recording)
1097 this._stopRecordingProfile();
1098 this._profileSamples = null;
1099 this._lastSeenIndex = -1;
1104 * @param {string=} title
1105 * @return {!WebInspector.ProfileHeader}
1107 createTemporaryProfile: function(title)
1109 title = title || WebInspector.UIString("Recording\u2026");
1110 return new WebInspector.HeapProfileHeader(this, title);
1113 __proto__: WebInspector.HeapSnapshotProfileType.prototype
1118 * @extends {WebInspector.ProfileHeader}
1119 * @param {!WebInspector.ProfileType} type
1120 * @param {string} title
1121 * @param {number=} uid
1122 * @param {number=} maxJSObjectId
1124 WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId)
1126 WebInspector.ProfileHeader.call(this, type, title, uid);
1127 this.maxJSObjectId = maxJSObjectId;
1129 * @type {WebInspector.OutputStream}
1131 this._receiver = null;
1133 * @type {WebInspector.HeapSnapshotProxy}
1135 this._snapshotProxy = null;
1136 this._totalNumberOfChunks = 0;
1137 this._transferHandler = null;
1140 WebInspector.HeapProfileHeader.prototype = {
1144 createSidebarTreeElement: function()
1146 return new WebInspector.ProfileSidebarTreeElement(this, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
1151 * @param {!WebInspector.ProfilesPanel} profilesPanel
1153 createView: function(profilesPanel)
1155 return new WebInspector.HeapSnapshotView(profilesPanel, this);
1160 * @param {function(WebInspector.HeapSnapshotProxy):void} callback
1162 load: function(callback)
1164 if (this.uid === -1)
1166 if (this._snapshotProxy) {
1167 callback(this._snapshotProxy);
1171 this._numberOfChunks = 0;
1172 if (!this._receiver) {
1173 this._setupWorker();
1174 this._transferHandler = new WebInspector.BackendSnapshotLoader(this);
1175 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
1176 this.sidebarElement.wait = true;
1177 this._transferSnapshot();
1179 var loaderProxy = /** @type {WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver);
1180 loaderProxy.addConsumer(callback);
1183 _transferSnapshot: function()
1185 function finishTransfer()
1187 if (this._transferHandler) {
1188 this._transferHandler.finishTransfer();
1189 this._totalNumberOfChunks = this._transferHandler._totalNumberOfChunks;
1192 HeapProfilerAgent.getHeapSnapshot(this.uid, finishTransfer.bind(this));
1195 snapshotConstructorName: function()
1197 return "JSHeapSnapshot";
1200 snapshotProxyConstructor: function()
1202 return WebInspector.HeapSnapshotProxy;
1205 _setupWorker: function()
1207 function setProfileWait(event)
1209 this.sidebarElement.wait = event.data;
1211 var worker = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this));
1212 worker.addEventListener("wait", setProfileWait, this);
1213 var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor());
1214 loaderProxy.addConsumer(this._snapshotReceived.bind(this));
1215 this._receiver = loaderProxy;
1219 * @param{string} eventName
1222 _handleWorkerEvent: function(eventName, data)
1224 if (WebInspector.HeapSnapshotProgress.Event.Update !== eventName)
1226 this._updateSubtitle(data);
1235 this._receiver.close();
1236 else if (this._snapshotProxy)
1237 this._snapshotProxy.dispose();
1239 var view = this._view;
1245 _updateSubtitle: function(value)
1247 this.sidebarElement.subtitle = value;
1250 _didCompleteSnapshotTransfer: function()
1252 this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize);
1253 this.sidebarElement.wait = false;
1257 * @param {string} chunk
1259 transferChunk: function(chunk)
1261 this._transferHandler.transferChunk(chunk);
1264 _snapshotReceived: function(snapshotProxy)
1266 this._receiver = null;
1268 this._snapshotProxy = snapshotProxy;
1269 this._didCompleteSnapshotTransfer();
1270 var worker = /** @type {WebInspector.HeapSnapshotWorkerProxy} */ (this._snapshotProxy.worker);
1271 this.isTemporary = false;
1272 worker.startCheckingForLongRunningCalls();
1273 this.notifySnapshotReceived();
1275 if (this.fromFile()) {
1276 function didGetMaxNodeId(id)
1278 this.maxJSObjectId = id;
1280 snapshotProxy.maxJsNodeId(didGetMaxNodeId.bind(this));
1284 notifySnapshotReceived: function()
1286 this._profileType._snapshotReceived(this);
1289 // Hook point for tests.
1290 _wasShown: function()
1298 canSaveToFile: function()
1300 return !this.fromFile() && !!this._snapshotProxy && !this._receiver;
1306 saveToFile: function()
1308 var fileOutputStream = new WebInspector.FileOutputStream();
1311 this._receiver = fileOutputStream;
1312 this._transferHandler = new WebInspector.SaveSnapshotHandler(this);
1313 this._transferSnapshot();
1315 this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension();
1316 fileOutputStream.open(this._fileName, onOpen.bind(this));
1321 * @param {File} file
1323 loadFromFile: function(file)
1325 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
1326 this.sidebarElement.wait = true;
1327 this._setupWorker();
1329 var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this);
1330 var fileReader = this._createFileReader(file, delegate);
1331 fileReader.start(this._receiver);
1334 _createFileReader: function(file, delegate)
1336 return new WebInspector.ChunkedFileReader(file, 10000000, delegate);
1339 __proto__: WebInspector.ProfileHeader.prototype
1345 * @param {WebInspector.HeapProfileHeader} header
1346 * @param {string} title
1348 WebInspector.SnapshotTransferHandler = function(header, title)
1350 this._numberOfChunks = 0;
1351 this._savedChunks = 0;
1352 this._header = header;
1353 this._totalNumberOfChunks = 0;
1354 this._title = title;
1358 WebInspector.SnapshotTransferHandler.prototype = {
1360 * @param {string} chunk
1362 transferChunk: function(chunk)
1364 ++this._numberOfChunks;
1365 this._header._receiver.write(chunk, this._didTransferChunk.bind(this));
1368 finishTransfer: function()
1372 _didTransferChunk: function()
1374 this._updateProgress(++this._savedChunks, this._totalNumberOfChunks);
1377 _updateProgress: function(value, total)
1385 * @param {WebInspector.HeapProfileHeader} header
1386 * @extends {WebInspector.SnapshotTransferHandler}
1388 WebInspector.SaveSnapshotHandler = function(header)
1390 WebInspector.SnapshotTransferHandler.call(this, header, "Saving\u2026 %d\%");
1391 this._totalNumberOfChunks = header._totalNumberOfChunks;
1392 this._updateProgress(0, this._totalNumberOfChunks);
1396 WebInspector.SaveSnapshotHandler.prototype = {
1397 _updateProgress: function(value, total)
1399 var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0);
1400 this._header._updateSubtitle(WebInspector.UIString(this._title, percentValue));
1401 if (value === total) {
1402 this._header._receiver.close();
1403 this._header._didCompleteSnapshotTransfer();
1407 __proto__: WebInspector.SnapshotTransferHandler.prototype
1413 * @param {WebInspector.HeapProfileHeader} header
1414 * @extends {WebInspector.SnapshotTransferHandler}
1416 WebInspector.BackendSnapshotLoader = function(header)
1418 WebInspector.SnapshotTransferHandler.call(this, header, "Loading\u2026 %d\%");
1422 WebInspector.BackendSnapshotLoader.prototype = {
1423 finishTransfer: function()
1425 this._header._receiver.close(this._didFinishTransfer.bind(this));
1426 this._totalNumberOfChunks = this._numberOfChunks;
1429 _didFinishTransfer: function()
1431 console.assert(this._totalNumberOfChunks === this._savedChunks, "Not all chunks were transfered.");
1434 __proto__: WebInspector.SnapshotTransferHandler.prototype
1440 * @implements {WebInspector.OutputStreamDelegate}
1442 WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader)
1444 this._snapshotHeader = snapshotHeader;
1447 WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = {
1448 onTransferStarted: function()
1453 * @param {WebInspector.ChunkedReader} reader
1455 onChunkTransferred: function(reader)
1459 onTransferFinished: function()
1464 * @param {WebInspector.ChunkedReader} reader
1466 onError: function (reader, e)
1468 switch(e.target.error.code) {
1469 case e.target.error.NOT_FOUND_ERR:
1470 this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' not found.", reader.fileName()));
1472 case e.target.error.NOT_READABLE_ERR:
1473 this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' is not readable", reader.fileName()));
1475 case e.target.error.ABORT_ERR:
1478 this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code));
1485 * @extends {WebInspector.View}
1486 * @param {!WebInspector.HeapProfileHeader} heapProfileHeader
1488 WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader)
1490 WebInspector.View.call(this);
1491 this.registerRequiredCSS("flameChart.css");
1492 this.element.id = "heap-recording-view";
1494 this._overviewContainer = this.element.createChild("div", "overview-container");
1495 this._overviewGrid = new WebInspector.OverviewGrid("heap-recording");
1496 this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas");
1497 this._overviewContainer.appendChild(this._overviewGrid.element);
1498 this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator();
1499 this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
1501 this._profileSamples = heapProfileHeader._profileSamples || heapProfileHeader._profileType._profileSamples;
1502 if (heapProfileHeader.isTemporary) {
1503 this._profileType = heapProfileHeader._profileType;
1504 this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
1505 this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
1507 var timestamps = this._profileSamples.timestamps;
1508 var totalTime = this._profileSamples.totalTime;
1509 this._windowLeft = 0.0;
1510 this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0;
1511 this._overviewGrid.setWindow(this._windowLeft, this._windowRight);
1512 this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
1513 this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale();
1516 WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged";
1518 WebInspector.HeapTrackingOverviewGrid.prototype = {
1519 _onStopTracking: function(event)
1521 this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this);
1522 this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this);
1525 _onHeapStatsUpdate: function(event)
1527 this._profileSamples = event.data;
1528 this._scheduleUpdate();
1532 * @param {number} width
1533 * @param {number} height
1535 _drawOverviewCanvas: function(width, height)
1537 if (!this._profileSamples)
1539 var profileSamples = this._profileSamples;
1540 var sizes = profileSamples.sizes;
1541 var topSizes = profileSamples.max;
1542 var timestamps = profileSamples.timestamps;
1543 var startTime = timestamps[0];
1544 var endTime = timestamps[timestamps.length - 1];
1546 var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime);
1549 * @param {Array.<number>} sizes
1550 * @param {function(number, number):void} callback
1552 function aggregateAndCall(sizes, callback)
1556 for (var i = 1; i < timestamps.length; ++i) {
1557 var x = Math.floor((timestamps[i] - startTime) * scaleFactor);
1558 if (x !== currentX) {
1560 callback(currentX, size);
1566 callback(currentX, size);
1571 * @param {number} size
1573 function maxSizeCallback(x, size)
1575 maxSize = Math.max(maxSize, size);
1578 aggregateAndCall(sizes, maxSizeCallback);
1580 var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0);
1582 this._overviewCanvas.width = width * window.devicePixelRatio;
1583 this._overviewCanvas.height = height * window.devicePixelRatio;
1584 this._overviewCanvas.style.width = width + "px";
1585 this._overviewCanvas.style.height = height + "px";
1587 var context = this._overviewCanvas.getContext("2d");
1588 context.scale(window.devicePixelRatio, window.devicePixelRatio);
1590 context.beginPath();
1591 context.lineWidth = 2;
1592 context.strokeStyle = "rgba(192, 192, 192, 0.6)";
1593 var currentX = (endTime - startTime) * scaleFactor;
1594 context.moveTo(currentX, height - 1);
1595 context.lineTo(currentX, 0);
1597 context.closePath();
1601 var gridLabelHeight = 14;
1603 const maxGridValue = (height - gridLabelHeight) / yScaleFactor;
1604 // The round value calculation is a bit tricky, because
1605 // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer,
1606 // e.g. a round value 10KB is 10240 bytes.
1607 gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024)));
1608 gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10));
1609 if (gridValue * 5 <= maxGridValue)
1611 gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5;
1612 context.beginPath();
1613 context.lineWidth = 1;
1614 context.strokeStyle = "rgba(0, 0, 0, 0.2)";
1615 context.moveTo(0, gridY);
1616 context.lineTo(width, gridY);
1618 context.closePath();
1623 * @param {number} size
1625 function drawBarCallback(x, size)
1627 context.moveTo(x, height - 1);
1628 context.lineTo(x, Math.round(height - size * yScaleFactor - 1));
1631 context.beginPath();
1632 context.lineWidth = 2;
1633 context.strokeStyle = "rgba(192, 192, 192, 0.6)";
1634 aggregateAndCall(topSizes, drawBarCallback);
1636 context.closePath();
1638 context.beginPath();
1639 context.lineWidth = 2;
1640 context.strokeStyle = "rgba(0, 0, 192, 0.8)";
1641 aggregateAndCall(sizes, drawBarCallback);
1643 context.closePath();
1646 var label = Number.bytesToString(gridValue);
1647 var labelPadding = 4;
1649 var labelY = gridY - 0.5;
1650 var labelWidth = 2 * labelPadding + context.measureText(label).width;
1651 context.beginPath();
1652 context.textBaseline = "bottom";
1653 context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
1654 context.fillStyle = "rgba(255, 255, 255, 0.75)";
1655 context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight);
1656 context.fillStyle = "rgb(64, 64, 64)";
1657 context.fillText(label, labelX + labelPadding, labelY);
1659 context.closePath();
1663 onResize: function()
1665 this._updateOverviewCanvas = true;
1666 this._scheduleUpdate();
1669 _onWindowChanged: function()
1671 if (!this._updateGridTimerId)
1672 this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10);
1675 _scheduleUpdate: function()
1677 if (this._updateTimerId)
1679 this._updateTimerId = setTimeout(this.update.bind(this), 10);
1682 _updateBoundaries: function()
1684 this._windowLeft = this._overviewGrid.windowLeft();
1685 this._windowRight = this._overviewGrid.windowRight();
1686 this._windowWidth = this._windowRight - this._windowLeft;
1691 this._updateTimerId = null;
1692 if (!this.isShowing())
1694 this._updateBoundaries();
1695 this._overviewCalculator._updateBoundaries(this);
1696 this._overviewGrid.updateDividers(this._overviewCalculator);
1697 this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
1700 _updateGrid: function()
1702 this._updateGridTimerId = 0;
1703 this._updateBoundaries();
1704 var ids = this._profileSamples.ids;
1705 var timestamps = this._profileSamples.timestamps;
1706 var sizes = this._profileSamples.sizes;
1707 var startTime = timestamps[0];
1708 var totalTime = this._profileSamples.totalTime;
1709 var timeLeft = startTime + totalTime * this._windowLeft;
1710 var timeRight = startTime + totalTime * this._windowRight;
1712 var maxId = ids[ids.length - 1] + 1;
1714 for (var i = 0; i < timestamps.length; ++i) {
1717 if (timestamps[i] > timeRight)
1720 if (timestamps[i] < timeLeft) {
1727 this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size});
1730 __proto__: WebInspector.View.prototype
1737 WebInspector.HeapTrackingOverviewGrid.SmoothScale = function()
1739 this._lastUpdate = 0;
1740 this._currentScale = 0.0;
1743 WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = {
1745 * @param {number} target
1748 nextScale: function(target) {
1749 target = target || this._currentScale;
1750 if (this._currentScale) {
1751 var now = Date.now();
1752 var timeDeltaMs = now - this._lastUpdate;
1753 this._lastUpdate = now;
1754 var maxChangePerSec = 20;
1755 var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000);
1756 var scaleChange = target / this._currentScale;
1757 this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta);
1759 this._currentScale = target;
1760 return this._currentScale;
1767 * @implements {WebInspector.TimelineGrid.Calculator}
1769 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function()
1773 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = {
1775 * @param {WebInspector.HeapTrackingOverviewGrid} chart
1777 _updateBoundaries: function(chart)
1779 this._minimumBoundaries = 0;
1780 this._maximumBoundaries = chart._profileSamples.totalTime;
1781 this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries;
1785 * @param {number} time
1787 computePosition: function(time)
1789 return (time - this._minimumBoundaries) * this._xScaleFactor;
1792 formatTime: function(value)
1794 return Number.secondsToString((value + this._minimumBoundaries) / 1000);
1797 maximumBoundary: function()
1799 return this._maximumBoundaries;
1802 minimumBoundary: function()
1804 return this._minimumBoundaries;
1807 zeroTime: function()
1809 return this._minimumBoundaries;
1812 boundarySpan: function()
1814 return this._maximumBoundaries - this._minimumBoundaries;