1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.View} 34 * @param {!WebInspector.ProfilesPanel} parent 35 * @param {!WebInspector.HeapProfileHeader} profile 36 */ 37 WebInspector.HeapSnapshotView = function(parent, profile) 38 { 39 WebInspector.View.call(this); 40 41 this.element.classList.add("heap-snapshot-view"); 42 43 this.parent = parent; 44 this.parent.addEventListener("profile added", this._onProfileHeaderAdded, this); 45 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); 50 } 51 52 this.viewsContainer = document.createElement("div"); 53 this.viewsContainer.classList.add("views-container"); 54 this.element.appendChild(this.viewsContainer); 55 56 this.containmentView = new WebInspector.View(); 57 this.containmentView.element.classList.add("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); 62 63 this.constructorsView = new WebInspector.View(); 64 this.constructorsView.element.classList.add("view"); 65 this.constructorsView.element.appendChild(this._createToolbarWithClassNameFilter()); 66 67 this.constructorsDataGrid = new WebInspector.HeapSnapshotConstructorsDataGrid(); 68 this.constructorsDataGrid.element.classList.add("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); 72 73 this.dataGrid = /** @type {!WebInspector.HeapSnapshotSortableDataGrid} */ (this.constructorsDataGrid); 74 this.currentView = this.constructorsView; 75 this.currentView.show(this.viewsContainer); 76 77 this.diffView = new WebInspector.View(); 78 this.diffView.element.classList.add("view"); 79 this.diffView.element.appendChild(this._createToolbarWithClassNameFilter()); 80 81 this.diffDataGrid = new WebInspector.HeapSnapshotDiffDataGrid(); 82 this.diffDataGrid.element.classList.add("class-view-grid"); 83 this.diffDataGrid.show(this.diffView.element); 84 this.diffDataGrid.addEventListener(WebInspector.DataGrid.Events.SelectedNode, this._selectionChanged, this); 85 86 this.dominatorView = new WebInspector.View(); 87 this.dominatorView.element.classList.add("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); 92 93 if (WebInspector.HeapSnapshot.enableAllocationProfiler) { 94 this.allocationView = new WebInspector.View(); 95 this.allocationView.element.classList.add("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); 100 } 101 102 this.retainmentViewHeader = document.createElement("div"); 103 this.retainmentViewHeader.classList.add("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); 112 113 this.retainmentView = new WebInspector.View(); 114 this.retainmentView.element.classList.add("view"); 115 this.retainmentView.element.classList.add("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(); 121 122 this.viewSelect = new WebInspector.StatusBarComboBox(this._onSelectedViewChanged.bind(this)); 123 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)); 134 135 this._profile = profile; 136 137 this.baseSelect = new WebInspector.StatusBarComboBox(this._changeBase.bind(this)); 138 this.baseSelect.element.classList.add("hidden"); 139 this._updateBaseOptions(); 140 141 this.filterSelect = new WebInspector.StatusBarComboBox(this._changeFilter.bind(this)); 142 this._updateFilterOptions(); 143 144 this.selectedSizeText = new WebInspector.StatusBarText(""); 145 146 this._popoverHelper = new WebInspector.ObjectPopoverHelper(this.element, this._getHoverAnchor.bind(this), this._resolveObjectForPopover.bind(this), undefined, true); 147 148 this._refreshView(); 149 } 150 151 WebInspector.HeapSnapshotView.prototype = { 152 _refreshView: function() 153 { 154 this.profile.load(profileCallback.bind(this)); 155 156 /** 157 * @this {WebInspector.HeapSnapshotView} 158 */ 159 function profileCallback(heapSnapshotProxy) 160 { 161 var list = this._profiles(); 162 var profileIndex = list.indexOf(this._profile); 163 this.baseSelect.setSelectedIndex(Math.max(0, profileIndex - 1)); 164 this.dataGrid.setDataSource(heapSnapshotProxy); 165 if (this._trackingOverviewGrid) 166 this._trackingOverviewGrid._updateGrid(); 167 } 168 }, 169 170 _onIdsRangeChanged: function(event) 171 { 172 var minId = event.data.minId; 173 var maxId = event.data.maxId; 174 this.selectedSizeText.setText(WebInspector.UIString("Selected size: %s", Number.bytesToString(event.data.size))); 175 if (this.constructorsDataGrid.snapshot) 176 this.constructorsDataGrid.setSelectionRange(minId, maxId); 177 }, 178 179 dispose: function() 180 { 181 this.parent.removeEventListener("profile added", this._onProfileHeaderAdded, this); 182 this.profile.dispose(); 183 if (this.baseProfile) 184 this.baseProfile.dispose(); 185 this.containmentDataGrid.dispose(); 186 this.constructorsDataGrid.dispose(); 187 this.diffDataGrid.dispose(); 188 this.dominatorDataGrid.dispose(); 189 this.retainmentDataGrid.dispose(); 190 }, 191 192 get statusBarItems() 193 { 194 return [this.viewSelect.element, this.baseSelect.element, this.filterSelect.element, this.selectedSizeText.element]; 195 }, 196 197 get profile() 198 { 199 return this._profile; 200 }, 201 202 get baseProfile() 203 { 204 return this._profile.profileType().getProfile(this._baseProfileUid); 205 }, 206 207 wasShown: function() 208 { 209 // FIXME: load base and current snapshots in parallel 210 this.profile.load(profileCallback.bind(this)); 211 212 /** 213 * @this {WebInspector.HeapSnapshotView} 214 */ 215 function profileCallback() { 216 this.profile._wasShown(); 217 if (this.baseProfile) 218 this.baseProfile.load(function() { }); 219 } 220 }, 221 222 willHide: function() 223 { 224 this._currentSearchResultIndex = -1; 225 this._popoverHelper.hidePopover(); 226 if (this.helpPopover && this.helpPopover.isShowing()) 227 this.helpPopover.hide(); 228 }, 229 230 onResize: function() 231 { 232 var height = this.retainmentView.element.clientHeight; 233 this._updateRetainmentViewHeight(height); 234 }, 235 236 searchCanceled: function() 237 { 238 if (this._searchResults) { 239 for (var i = 0; i < this._searchResults.length; ++i) { 240 var node = this._searchResults[i].node; 241 delete node._searchMatched; 242 node.refresh(); 243 } 244 } 245 246 delete this._searchFinishedCallback; 247 this._currentSearchResultIndex = -1; 248 this._searchResults = []; 249 }, 250 251 /** 252 * @param {string} query 253 * @param {function(!WebInspector.View, number)} finishedCallback 254 */ 255 performSearch: function(query, finishedCallback) 256 { 257 // Call searchCanceled since it will reset everything we need before doing a new search. 258 this.searchCanceled(); 259 260 query = query.trim(); 261 262 if (!query) 263 return; 264 if (this.currentView !== this.constructorsView && this.currentView !== this.diffView) 265 return; 266 267 /** 268 * @param {boolean} found 269 * @this {WebInspector.HeapSnapshotView} 270 */ 271 function didHighlight(found) 272 { 273 finishedCallback(this, found ? 1 : 0); 274 } 275 276 if (query.charAt(0) === "@") { 277 var snapshotNodeId = parseInt(query.substring(1), 10); 278 if (!isNaN(snapshotNodeId)) 279 this.dataGrid.highlightObjectByHeapSnapshotId(String(snapshotNodeId), didHighlight.bind(this)); 280 else 281 finishedCallback(this, 0); 282 return; 283 } 284 285 this._searchFinishedCallback = finishedCallback; 286 var nameRegExp = createPlainTextSearchRegex(query, "i"); 287 288 function matchesByName(gridNode) { 289 return ("_name" in gridNode) && nameRegExp.test(gridNode._name); 290 } 291 292 function matchesQuery(gridNode) 293 { 294 delete gridNode._searchMatched; 295 if (matchesByName(gridNode)) { 296 gridNode._searchMatched = true; 297 gridNode.refresh(); 298 return true; 299 } 300 return false; 301 } 302 303 var current = this.dataGrid.rootNode().children[0]; 304 var depth = 0; 305 var info = {}; 306 307 // Restrict to type nodes and instances. 308 const maxDepth = 1; 309 310 while (current) { 311 if (matchesQuery(current)) 312 this._searchResults.push({ node: current }); 313 current = current.traverseNextNode(false, null, (depth >= maxDepth), info); 314 depth += info.depthChange; 315 } 316 317 finishedCallback(this, this._searchResults.length); 318 }, 319 320 jumpToFirstSearchResult: function() 321 { 322 if (!this._searchResults || !this._searchResults.length) 323 return; 324 this._currentSearchResultIndex = 0; 325 this._jumpToSearchResult(this._currentSearchResultIndex); 326 }, 327 328 jumpToLastSearchResult: function() 329 { 330 if (!this._searchResults || !this._searchResults.length) 331 return; 332 this._currentSearchResultIndex = (this._searchResults.length - 1); 333 this._jumpToSearchResult(this._currentSearchResultIndex); 334 }, 335 336 jumpToNextSearchResult: function() 337 { 338 if (!this._searchResults || !this._searchResults.length) 339 return; 340 if (++this._currentSearchResultIndex >= this._searchResults.length) 341 this._currentSearchResultIndex = 0; 342 this._jumpToSearchResult(this._currentSearchResultIndex); 343 }, 344 345 jumpToPreviousSearchResult: function() 346 { 347 if (!this._searchResults || !this._searchResults.length) 348 return; 349 if (--this._currentSearchResultIndex < 0) 350 this._currentSearchResultIndex = (this._searchResults.length - 1); 351 this._jumpToSearchResult(this._currentSearchResultIndex); 352 }, 353 354 showingFirstSearchResult: function() 355 { 356 return (this._currentSearchResultIndex === 0); 357 }, 358 359 showingLastSearchResult: function() 360 { 361 return (this._searchResults && this._currentSearchResultIndex === (this._searchResults.length - 1)); 362 }, 363 364 currentSearchResultIndex: function() { 365 return this._currentSearchResultIndex; 366 }, 367 368 _jumpToSearchResult: function(index) 369 { 370 var searchResult = this._searchResults[index]; 371 if (!searchResult) 372 return; 373 374 var node = searchResult.node; 375 node.revealAndSelect(); 376 }, 377 378 refreshVisibleData: function() 379 { 380 var child = this.dataGrid.rootNode().children[0]; 381 while (child) { 382 child.refresh(); 383 child = child.traverseNextNode(false, null, true); 384 } 385 }, 386 387 _changeBase: function() 388 { 389 if (this._baseProfileUid === this._profiles()[this.baseSelect.selectedIndex()].uid) 390 return; 391 392 this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid; 393 var dataGrid = /** @type {!WebInspector.HeapSnapshotDiffDataGrid} */ (this.dataGrid); 394 // Change set base data source only if main data source is already set. 395 if (dataGrid.snapshot) 396 this.baseProfile.load(dataGrid.setBaseDataSource.bind(dataGrid)); 397 398 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 399 return; 400 401 // The current search needs to be performed again. First negate out previous match 402 // count by calling the search finished callback with a negative number of matches. 403 // Then perform the search again with the same query and callback. 404 this._searchFinishedCallback(this, -this._searchResults.length); 405 this.performSearch(this.currentQuery, this._searchFinishedCallback); 406 }, 407 408 _changeFilter: function() 409 { 410 var profileIndex = this.filterSelect.selectedIndex() - 1; 411 this.dataGrid.filterSelectIndexChanged(this._profiles(), profileIndex); 412 413 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 414 action: WebInspector.UserMetrics.UserActionNames.HeapSnapshotFilterChanged, 415 label: this.filterSelect.selectedOption().label 416 }); 417 418 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 419 return; 420 421 // The current search needs to be performed again. First negate out previous match 422 // count by calling the search finished callback with a negative number of matches. 423 // Then perform the search again with the same query and callback. 424 this._searchFinishedCallback(this, -this._searchResults.length); 425 this.performSearch(this.currentQuery, this._searchFinishedCallback); 426 }, 427 428 _createToolbarWithClassNameFilter: function() 429 { 430 var toolbar = document.createElement("div"); 431 toolbar.classList.add("class-view-toolbar"); 432 var classNameFilter = document.createElement("input"); 433 classNameFilter.classList.add("class-name-filter"); 434 classNameFilter.setAttribute("placeholder", WebInspector.UIString("Class filter")); 435 classNameFilter.addEventListener("keyup", this._changeNameFilter.bind(this, classNameFilter), false); 436 toolbar.appendChild(classNameFilter); 437 return toolbar; 438 }, 439 440 _changeNameFilter: function(classNameInputElement) 441 { 442 var filter = classNameInputElement.value; 443 this.dataGrid.changeNameFilter(filter); 444 }, 445 446 /** 447 * @return {!Array.<!WebInspector.ProfileHeader>} 448 */ 449 _profiles: function() 450 { 451 return this._profile.profileType().getProfiles(); 452 }, 453 454 /** 455 * @param {!WebInspector.ContextMenu} contextMenu 456 * @param {?Event} event 457 */ 458 populateContextMenu: function(contextMenu, event) 459 { 460 this.dataGrid.populateContextMenu(this.parent, contextMenu, event); 461 }, 462 463 _selectionChanged: function(event) 464 { 465 var selectedNode = event.target.selectedNode; 466 this._setRetainmentDataGridSource(selectedNode); 467 this._inspectedObjectChanged(event); 468 }, 469 470 _inspectedObjectChanged: function(event) 471 { 472 var selectedNode = event.target.selectedNode; 473 if (!this.profile.fromFile() && selectedNode instanceof WebInspector.HeapSnapshotGenericObjectNode) 474 ConsoleAgent.addInspectedHeapObject(selectedNode.snapshotNodeId); 475 }, 476 477 _setRetainmentDataGridSource: function(nodeItem) 478 { 479 if (nodeItem && nodeItem.snapshotNodeIndex) 480 this.retainmentDataGrid.setDataSource(nodeItem.isDeletedNode ? nodeItem.dataGrid.baseSnapshot : nodeItem.dataGrid.snapshot, nodeItem.snapshotNodeIndex); 481 else 482 this.retainmentDataGrid.reset(); 483 }, 484 485 _mouseDownInContentsGrid: function(event) 486 { 487 if (event.detail < 2) 488 return; 489 490 var cell = event.target.enclosingNodeOrSelfWithNodeName("td"); 491 if (!cell || (!cell.classList.contains("count-column") && !cell.classList.contains("shallowSize-column") && !cell.classList.contains("retainedSize-column"))) 492 return; 493 494 event.consume(true); 495 }, 496 497 changeView: function(viewTitle, callback) 498 { 499 var viewIndex = null; 500 for (var i = 0; i < this.views.length; ++i) { 501 if (this.views[i].title === viewTitle) { 502 viewIndex = i; 503 break; 504 } 505 } 506 if (this.views.current === viewIndex || viewIndex == null) { 507 setTimeout(callback, 0); 508 return; 509 } 510 511 /** 512 * @this {WebInspector.HeapSnapshotView} 513 */ 514 function dataGridContentShown(event) 515 { 516 var dataGrid = event.data; 517 dataGrid.removeEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); 518 if (dataGrid === this.dataGrid) 519 callback(); 520 } 521 this.views[viewIndex].grid.addEventListener(WebInspector.HeapSnapshotSortableDataGrid.Events.ContentShown, dataGridContentShown, this); 522 523 this.viewSelect.setSelectedIndex(viewIndex); 524 this._changeView(viewIndex); 525 }, 526 527 _updateDataSourceAndView: function() 528 { 529 var dataGrid = this.dataGrid; 530 if (dataGrid.snapshot) 531 return; 532 533 this.profile.load(didLoadSnapshot.bind(this)); 534 535 /** 536 * @this {WebInspector.HeapSnapshotView} 537 */ 538 function didLoadSnapshot(snapshotProxy) 539 { 540 if (this.dataGrid !== dataGrid) 541 return; 542 if (dataGrid.snapshot !== snapshotProxy) 543 dataGrid.setDataSource(snapshotProxy); 544 if (dataGrid === this.diffDataGrid) { 545 if (!this._baseProfileUid) 546 this._baseProfileUid = this._profiles()[this.baseSelect.selectedIndex()].uid; 547 this.baseProfile.load(didLoadBaseSnaphot.bind(this)); 548 } 549 } 550 551 /** 552 * @this {WebInspector.HeapSnapshotView} 553 */ 554 function didLoadBaseSnaphot(baseSnapshotProxy) 555 { 556 if (this.diffDataGrid.baseSnapshot !== baseSnapshotProxy) 557 this.diffDataGrid.setBaseDataSource(baseSnapshotProxy); 558 } 559 }, 560 561 _onSelectedViewChanged: function(event) 562 { 563 this._changeView(event.target.selectedIndex); 564 }, 565 566 _updateSelectorsVisibility: function() 567 { 568 if (this.currentView === this.diffView) 569 this.baseSelect.element.classList.remove("hidden"); 570 else 571 this.baseSelect.element.classList.add("hidden"); 572 573 if (this.currentView === this.constructorsView) { 574 if (this._trackingOverviewGrid) { 575 this._trackingOverviewGrid.element.classList.remove("hidden"); 576 this._trackingOverviewGrid.update(); 577 this.viewsContainer.classList.add("reserve-80px-at-top"); 578 } 579 this.filterSelect.element.classList.remove("hidden"); 580 } else { 581 this.filterSelect.element.classList.add("hidden"); 582 if (this._trackingOverviewGrid) { 583 this._trackingOverviewGrid.element.classList.add("hidden"); 584 this.viewsContainer.classList.remove("reserve-80px-at-top"); 585 } 586 } 587 }, 588 589 _changeView: function(selectedIndex) 590 { 591 if (selectedIndex === this.views.current) 592 return; 593 594 this.views.current = selectedIndex; 595 this.currentView.detach(); 596 var view = this.views[this.views.current]; 597 this.currentView = view.view; 598 this.dataGrid = view.grid; 599 this.currentView.show(this.viewsContainer); 600 this.refreshVisibleData(); 601 this.dataGrid.updateWidths(); 602 603 this._updateSelectorsVisibility(); 604 605 this._updateDataSourceAndView(); 606 607 if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults) 608 return; 609 610 // The current search needs to be performed again. First negate out previous match 611 // count by calling the search finished callback with a negative number of matches. 612 // Then perform the search again the with same query and callback. 613 this._searchFinishedCallback(this, -this._searchResults.length); 614 this.performSearch(this.currentQuery, this._searchFinishedCallback); 615 }, 616 617 _getHoverAnchor: function(target) 618 { 619 var span = target.enclosingNodeOrSelfWithNodeName("span"); 620 if (!span) 621 return; 622 var row = target.enclosingNodeOrSelfWithNodeName("tr"); 623 if (!row) 624 return; 625 span.node = row._dataGridNode; 626 return span; 627 }, 628 629 _resolveObjectForPopover: function(element, showCallback, objectGroupName) 630 { 631 if (this.profile.fromFile()) 632 return; 633 element.node.queryObjectContent(showCallback, objectGroupName); 634 }, 635 636 /** 637 * @return {boolean} 638 */ 639 _startRetainersHeaderDragging: function(event) 640 { 641 if (!this.isShowing()) 642 return false; 643 644 this._previousDragPosition = event.pageY; 645 return true; 646 }, 647 648 _retainersHeaderDragging: function(event) 649 { 650 var height = this.retainmentView.element.clientHeight; 651 height += this._previousDragPosition - event.pageY; 652 this._previousDragPosition = event.pageY; 653 this._updateRetainmentViewHeight(height); 654 event.consume(true); 655 }, 656 657 _endRetainersHeaderDragging: function(event) 658 { 659 delete this._previousDragPosition; 660 event.consume(); 661 }, 662 663 _updateRetainmentViewHeight: function(height) 664 { 665 height = Number.constrain(height, Preferences.minConsoleHeight, this.element.clientHeight - Preferences.minConsoleHeight); 666 this.viewsContainer.style.bottom = (height + this.retainmentViewHeader.clientHeight) + "px"; 667 if (this._trackingOverviewGrid && this.currentView === this.constructorsView) 668 this.viewsContainer.classList.add("reserve-80px-at-top"); 669 this.retainmentView.element.style.height = height + "px"; 670 this.retainmentViewHeader.style.bottom = height + "px"; 671 this.currentView.doResize(); 672 }, 673 674 _updateBaseOptions: function() 675 { 676 var list = this._profiles(); 677 // We're assuming that snapshots can only be added. 678 if (this.baseSelect.size() === list.length) 679 return; 680 681 for (var i = this.baseSelect.size(), n = list.length; i < n; ++i) { 682 var title = list[i].title; 683 this.baseSelect.createOption(title); 684 } 685 }, 686 687 _updateFilterOptions: function() 688 { 689 var list = this._profiles(); 690 // We're assuming that snapshots can only be added. 691 if (this.filterSelect.size() - 1 === list.length) 692 return; 693 694 if (!this.filterSelect.size()) 695 this.filterSelect.createOption(WebInspector.UIString("All objects")); 696 697 for (var i = this.filterSelect.size() - 1, n = list.length; i < n; ++i) { 698 var title = list[i].title; 699 if (!i) 700 title = WebInspector.UIString("Objects allocated before %s", title); 701 else 702 title = WebInspector.UIString("Objects allocated between %s and %s", list[i - 1].title, title); 703 this.filterSelect.createOption(title); 704 } 705 }, 706 707 /** 708 * @param {!WebInspector.Event} event 709 */ 710 _onProfileHeaderAdded: function(event) 711 { 712 if (!event.data || event.data.type !== this._profile.profileType().id) 713 return; 714 this._updateBaseOptions(); 715 this._updateFilterOptions(); 716 }, 717 718 __proto__: WebInspector.View.prototype 719 } 720 721 /** 722 * @constructor 723 * @implements {HeapProfilerAgent.Dispatcher} 724 */ 725 WebInspector.HeapProfilerDispatcher = function() 726 { 727 this._dispatchers = []; 728 InspectorBackend.registerHeapProfilerDispatcher(this); 729 } 730 731 WebInspector.HeapProfilerDispatcher.prototype = { 732 /** 733 * @param {!HeapProfilerAgent.Dispatcher} dispatcher 734 */ 735 register: function(dispatcher) 736 { 737 this._dispatchers.push(dispatcher); 738 }, 739 740 _genericCaller: function(eventName) 741 { 742 var args = Array.prototype.slice.call(arguments.callee.caller.arguments); 743 for (var i = 0; i < this._dispatchers.length; ++i) 744 this._dispatchers[i][eventName].apply(this._dispatchers[i], args); 745 }, 746 747 /** 748 * @override 749 * @param {!Array.<number>} samples 750 */ 751 heapStatsUpdate: function(samples) 752 { 753 this._genericCaller("heapStatsUpdate"); 754 }, 755 756 /** 757 * @override 758 * @param {number} lastSeenObjectId 759 * @param {number} timestamp 760 */ 761 lastSeenObjectId: function(lastSeenObjectId, timestamp) 762 { 763 this._genericCaller("lastSeenObjectId"); 764 }, 765 766 /** 767 * @param {!HeapProfilerAgent.ProfileHeader} profileHeader 768 */ 769 addProfileHeader: function(profileHeader) 770 { 771 this._genericCaller("addProfileHeader"); 772 }, 773 774 /** 775 * @override 776 * @param {number} uid 777 * @param {string} chunk 778 */ 779 addHeapSnapshotChunk: function(uid, chunk) 780 { 781 this._genericCaller("addHeapSnapshotChunk"); 782 }, 783 784 /** 785 * @override 786 * @param {number} done 787 * @param {number} total 788 */ 789 reportHeapSnapshotProgress: function(done, total) 790 { 791 this._genericCaller("reportHeapSnapshotProgress"); 792 }, 793 794 /** 795 * @override 796 */ 797 resetProfiles: function() 798 { 799 this._genericCaller("resetProfiles"); 800 } 801 } 802 803 WebInspector.HeapProfilerDispatcher._dispatcher = new WebInspector.HeapProfilerDispatcher(); 804 805 /** 806 * @constructor 807 * @extends {WebInspector.ProfileType} 808 * @implements {HeapProfilerAgent.Dispatcher} 809 */ 810 WebInspector.HeapSnapshotProfileType = function() 811 { 812 WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("Take Heap Snapshot")); 813 WebInspector.HeapProfilerDispatcher._dispatcher.register(this); 814 } 815 816 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP"; 817 WebInspector.HeapSnapshotProfileType.SnapshotReceived = "SnapshotReceived"; 818 819 WebInspector.HeapSnapshotProfileType.prototype = { 820 /** 821 * @override 822 * @return {string} 823 */ 824 fileExtension: function() 825 { 826 return ".heapsnapshot"; 827 }, 828 829 get buttonTooltip() 830 { 831 return WebInspector.UIString("Take heap snapshot."); 832 }, 833 834 /** 835 * @override 836 * @return {boolean} 837 */ 838 isInstantProfile: function() 839 { 840 return true; 841 }, 842 843 /** 844 * @override 845 * @return {boolean} 846 */ 847 buttonClicked: function() 848 { 849 this._takeHeapSnapshot(function() {}); 850 WebInspector.userMetrics.ProfilesHeapProfileTaken.record(); 851 return false; 852 }, 853 854 /** 855 * @override 856 * @param {!Array.<number>} samples 857 */ 858 heapStatsUpdate: function(samples) 859 { 860 }, 861 862 /** 863 * @override 864 * @param {number} lastSeenObjectId 865 * @param {number} timestamp 866 */ 867 lastSeenObjectId: function(lastSeenObjectId, timestamp) 868 { 869 }, 870 871 get treeItemTitle() 872 { 873 return WebInspector.UIString("HEAP SNAPSHOTS"); 874 }, 875 876 get description() 877 { 878 return WebInspector.UIString("Heap snapshot profiles show memory distribution among your page's JavaScript objects and related DOM nodes."); 879 }, 880 881 /** 882 * @override 883 * @param {!string} title 884 * @return {!WebInspector.ProfileHeader} 885 */ 886 createProfileLoadedFromFile: function(title) 887 { 888 return new WebInspector.HeapProfileHeader(this, title); 889 }, 890 891 _takeHeapSnapshot: function(callback) 892 { 893 if (this.profileBeingRecorded()) 894 return; 895 this._profileBeingRecorded = new WebInspector.HeapProfileHeader(this, WebInspector.UIString("Snapshotting\u2026")); 896 this.addProfile(this._profileBeingRecorded); 897 HeapProfilerAgent.takeHeapSnapshot(true, callback); 898 }, 899 900 /** 901 * @param {!HeapProfilerAgent.ProfileHeader} profileHeader 902 */ 903 addProfileHeader: function(profileHeader) 904 { 905 var profile = this.profileBeingRecorded(); 906 if (!profile) 907 return; 908 profile.title = profileHeader.title; 909 profile.uid = profileHeader.uid; 910 profile.maxJSObjectId = profileHeader.maxJSObjectId || 0; 911 912 profile.sidebarElement.mainTitle = profile.title; 913 profile.sidebarElement.subtitle = ""; 914 profile.sidebarElement.wait = false; 915 916 this._profileSamples = null; 917 this._profileBeingRecorded = null; 918 919 WebInspector.panels.profiles._showProfile(profile); 920 profile.existingView()._refreshView(); 921 }, 922 923 /** 924 * @override 925 * @param {number} uid 926 * @param {string} chunk 927 */ 928 addHeapSnapshotChunk: function(uid, chunk) 929 { 930 var profile = this.getProfile(uid); 931 if (profile) 932 profile.transferChunk(chunk); 933 }, 934 935 /** 936 * @override 937 * @param {number} done 938 * @param {number} total 939 */ 940 reportHeapSnapshotProgress: function(done, total) 941 { 942 var profile = this.profileBeingRecorded(); 943 if (!profile) 944 return; 945 profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100); 946 profile.sidebarElement.wait = true; 947 }, 948 949 /** 950 * @override 951 */ 952 resetProfiles: function() 953 { 954 this._reset(); 955 }, 956 957 /** 958 * @override 959 * @param {!WebInspector.ProfileHeader} profile 960 */ 961 removeProfile: function(profile) 962 { 963 if (this._profileBeingRecorded !== profile && !profile.fromFile()) 964 HeapProfilerAgent.removeProfile(profile.uid); 965 WebInspector.ProfileType.prototype.removeProfile.call(this, profile); 966 }, 967 968 _snapshotReceived: function(profile) 969 { 970 if (this._profileBeingRecorded === profile) 971 this._profileBeingRecorded = null; 972 this.dispatchEventToListeners(WebInspector.HeapSnapshotProfileType.SnapshotReceived, profile); 973 }, 974 975 __proto__: WebInspector.ProfileType.prototype 976 } 977 978 979 /** 980 * @constructor 981 * @extends {WebInspector.HeapSnapshotProfileType} 982 * @param {!WebInspector.ProfilesPanel} profilesPanel 983 */ 984 WebInspector.TrackingHeapSnapshotProfileType = function(profilesPanel) 985 { 986 WebInspector.ProfileType.call(this, WebInspector.TrackingHeapSnapshotProfileType.TypeId, WebInspector.UIString("Record Heap Allocations")); 987 this._profilesPanel = profilesPanel; 988 WebInspector.HeapProfilerDispatcher._dispatcher.register(this); 989 } 990 991 WebInspector.TrackingHeapSnapshotProfileType.TypeId = "HEAP-RECORD"; 992 993 WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate = "HeapStatsUpdate"; 994 WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted = "TrackingStarted"; 995 WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped = "TrackingStopped"; 996 997 WebInspector.TrackingHeapSnapshotProfileType.prototype = { 998 999 /** 1000 * @override 1001 * @param {!Array.<number>} samples 1002 */ 1003 heapStatsUpdate: function(samples) 1004 { 1005 if (!this._profileSamples) 1006 return; 1007 var index; 1008 for (var i = 0; i < samples.length; i += 3) { 1009 index = samples[i]; 1010 var count = samples[i+1]; 1011 var size = samples[i+2]; 1012 this._profileSamples.sizes[index] = size; 1013 if (!this._profileSamples.max[index] || size > this._profileSamples.max[index]) 1014 this._profileSamples.max[index] = size; 1015 } 1016 this._lastUpdatedIndex = index; 1017 }, 1018 1019 /** 1020 * @override 1021 * @param {number} lastSeenObjectId 1022 * @param {number} timestamp 1023 */ 1024 lastSeenObjectId: function(lastSeenObjectId, timestamp) 1025 { 1026 var profileSamples = this._profileSamples; 1027 if (!profileSamples) 1028 return; 1029 var currentIndex = Math.max(profileSamples.ids.length, profileSamples.max.length - 1); 1030 profileSamples.ids[currentIndex] = lastSeenObjectId; 1031 if (!profileSamples.max[currentIndex]) { 1032 profileSamples.max[currentIndex] = 0; 1033 profileSamples.sizes[currentIndex] = 0; 1034 } 1035 profileSamples.timestamps[currentIndex] = timestamp; 1036 if (profileSamples.totalTime < timestamp - profileSamples.timestamps[0]) 1037 profileSamples.totalTime *= 2; 1038 this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._profileSamples); 1039 var profile = this._profileBeingRecorded; 1040 profile.sidebarElement.wait = true; 1041 if (profile.sidebarElement && !profile.sidebarElement.wait) 1042 profile.sidebarElement.wait = true; 1043 }, 1044 1045 /** 1046 * @override 1047 * @return {boolean} 1048 */ 1049 hasTemporaryView: function() 1050 { 1051 return true; 1052 }, 1053 1054 get buttonTooltip() 1055 { 1056 return this._recording ? WebInspector.UIString("Stop recording heap profile.") : WebInspector.UIString("Start recording heap profile."); 1057 }, 1058 1059 /** 1060 * @override 1061 * @return {boolean} 1062 */ 1063 isInstantProfile: function() 1064 { 1065 return false; 1066 }, 1067 1068 /** 1069 * @override 1070 * @return {boolean} 1071 */ 1072 buttonClicked: function() 1073 { 1074 return this._toggleRecording(); 1075 }, 1076 1077 _startRecordingProfile: function() 1078 { 1079 if (this.profileBeingRecorded()) 1080 return; 1081 this._profileBeingRecorded = new WebInspector.HeapProfileHeader(this, WebInspector.UIString("Recording\u2026")); 1082 this._lastSeenIndex = -1; 1083 this._profileSamples = { 1084 'sizes': [], 1085 'ids': [], 1086 'timestamps': [], 1087 'max': [], 1088 'totalTime': 30000 1089 }; 1090 this._profileBeingRecorded._profileSamples = this._profileSamples; 1091 this._recording = true; 1092 this.addProfile(this._profileBeingRecorded); 1093 HeapProfilerAgent.startTrackingHeapObjects(); 1094 this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStarted); 1095 }, 1096 1097 _stopRecordingProfile: function() 1098 { 1099 HeapProfilerAgent.stopTrackingHeapObjects(true); 1100 this._recording = false; 1101 this.dispatchEventToListeners(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped); 1102 }, 1103 1104 _toggleRecording: function() 1105 { 1106 if (this._recording) 1107 this._stopRecordingProfile(); 1108 else 1109 this._startRecordingProfile(); 1110 return this._recording; 1111 }, 1112 1113 get treeItemTitle() 1114 { 1115 return WebInspector.UIString("HEAP TIMELINES"); 1116 }, 1117 1118 get description() 1119 { 1120 return WebInspector.UIString("Record JavaScript object allocations over time. Use this profile type to isolate memory leaks."); 1121 }, 1122 1123 _reset: function() 1124 { 1125 WebInspector.HeapSnapshotProfileType.prototype._reset.call(this); 1126 if (this._recording) 1127 this._stopRecordingProfile(); 1128 this._profileSamples = null; 1129 this._lastSeenIndex = -1; 1130 }, 1131 1132 __proto__: WebInspector.HeapSnapshotProfileType.prototype 1133 } 1134 1135 /** 1136 * @constructor 1137 * @extends {WebInspector.ProfileHeader} 1138 * @param {!WebInspector.ProfileType} type 1139 * @param {string} title 1140 * @param {number=} uid 1141 * @param {number=} maxJSObjectId 1142 */ 1143 WebInspector.HeapProfileHeader = function(type, title, uid, maxJSObjectId) 1144 { 1145 WebInspector.ProfileHeader.call(this, type, title, uid); 1146 this.maxJSObjectId = maxJSObjectId; 1147 /** 1148 * @type {?WebInspector.OutputStream} 1149 */ 1150 this._receiver = null; 1151 /** 1152 * @type {?WebInspector.HeapSnapshotProxy} 1153 */ 1154 this._snapshotProxy = null; 1155 this._totalNumberOfChunks = 0; 1156 this._transferHandler = null; 1157 } 1158 1159 WebInspector.HeapProfileHeader.prototype = { 1160 /** 1161 * @override 1162 */ 1163 createSidebarTreeElement: function() 1164 { 1165 return new WebInspector.ProfileSidebarTreeElement(this, "heap-snapshot-sidebar-tree-item"); 1166 }, 1167 1168 /** 1169 * @override 1170 * @param {!WebInspector.ProfilesPanel} profilesPanel 1171 */ 1172 createView: function(profilesPanel) 1173 { 1174 return new WebInspector.HeapSnapshotView(profilesPanel, this); 1175 }, 1176 1177 /** 1178 * @override 1179 * @param {function(!WebInspector.HeapSnapshotProxy):void} callback 1180 */ 1181 load: function(callback) 1182 { 1183 if (this.uid === -1) 1184 return; 1185 if (this._snapshotProxy) { 1186 callback(this._snapshotProxy); 1187 return; 1188 } 1189 1190 this._numberOfChunks = 0; 1191 if (!this._receiver) { 1192 this._setupWorker(); 1193 this._transferHandler = new WebInspector.BackendSnapshotLoader(this); 1194 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026"); 1195 this.sidebarElement.wait = true; 1196 this._transferSnapshot(); 1197 } 1198 var loaderProxy = /** @type {?WebInspector.HeapSnapshotLoaderProxy} */ (this._receiver); 1199 console.assert(loaderProxy); 1200 loaderProxy.addConsumer(callback); 1201 }, 1202 1203 _transferSnapshot: function() 1204 { 1205 /** 1206 * @this {WebInspector.HeapProfileHeader} 1207 */ 1208 function finishTransfer() 1209 { 1210 if (this._transferHandler) { 1211 this._transferHandler.finishTransfer(); 1212 this._totalNumberOfChunks = this._transferHandler._totalNumberOfChunks; 1213 } 1214 } 1215 HeapProfilerAgent.getHeapSnapshot(this.uid, finishTransfer.bind(this)); 1216 }, 1217 1218 snapshotConstructorName: function() 1219 { 1220 return "JSHeapSnapshot"; 1221 }, 1222 1223 snapshotProxyConstructor: function() 1224 { 1225 return WebInspector.HeapSnapshotProxy; 1226 }, 1227 1228 _setupWorker: function() 1229 { 1230 /** 1231 * @this {WebInspector.HeapProfileHeader} 1232 */ 1233 function setProfileWait(event) 1234 { 1235 this.sidebarElement.wait = event.data; 1236 } 1237 var worker = new WebInspector.HeapSnapshotWorkerProxy(this._handleWorkerEvent.bind(this)); 1238 worker.addEventListener("wait", setProfileWait, this); 1239 var loaderProxy = worker.createLoader(this.snapshotConstructorName(), this.snapshotProxyConstructor()); 1240 loaderProxy.addConsumer(this._snapshotReceived.bind(this)); 1241 this._receiver = loaderProxy; 1242 }, 1243 1244 /** 1245 * @param {string} eventName 1246 * @param {*} data 1247 */ 1248 _handleWorkerEvent: function(eventName, data) 1249 { 1250 if (WebInspector.HeapSnapshotProgress.Event.Update !== eventName) 1251 return; 1252 this._updateSubtitle(data); 1253 }, 1254 1255 /** 1256 * @override 1257 */ 1258 dispose: function() 1259 { 1260 if (this._receiver) 1261 this._receiver.close(); 1262 else if (this._snapshotProxy) 1263 this._snapshotProxy.dispose(); 1264 if (this._view) { 1265 var view = this._view; 1266 this._view = null; 1267 view.dispose(); 1268 } 1269 }, 1270 1271 _updateSubtitle: function(value) 1272 { 1273 this.sidebarElement.subtitle = value; 1274 }, 1275 1276 _didCompleteSnapshotTransfer: function() 1277 { 1278 this.sidebarElement.subtitle = Number.bytesToString(this._snapshotProxy.totalSize); 1279 this.sidebarElement.wait = false; 1280 }, 1281 1282 /** 1283 * @param {string} chunk 1284 */ 1285 transferChunk: function(chunk) 1286 { 1287 this._transferHandler.transferChunk(chunk); 1288 }, 1289 1290 _snapshotReceived: function(snapshotProxy) 1291 { 1292 this._receiver = null; 1293 if (snapshotProxy) 1294 this._snapshotProxy = snapshotProxy; 1295 this._didCompleteSnapshotTransfer(); 1296 var worker = /** @type {!WebInspector.HeapSnapshotWorkerProxy} */ (this._snapshotProxy.worker); 1297 worker.startCheckingForLongRunningCalls(); 1298 this.notifySnapshotReceived(); 1299 1300 /** 1301 * @this {WebInspector.HeapProfileHeader} 1302 */ 1303 function didGetMaxNodeId(id) 1304 { 1305 this.maxJSObjectId = id; 1306 } 1307 1308 if (this.fromFile()) 1309 snapshotProxy.maxJsNodeId(didGetMaxNodeId.bind(this)); 1310 }, 1311 1312 notifySnapshotReceived: function() 1313 { 1314 this._profileType._snapshotReceived(this); 1315 }, 1316 1317 // Hook point for tests. 1318 _wasShown: function() 1319 { 1320 }, 1321 1322 /** 1323 * @override 1324 * @return {boolean} 1325 */ 1326 canSaveToFile: function() 1327 { 1328 return !this.fromFile() && !!this._snapshotProxy && !this._receiver; 1329 }, 1330 1331 /** 1332 * @override 1333 */ 1334 saveToFile: function() 1335 { 1336 var fileOutputStream = new WebInspector.FileOutputStream(); 1337 1338 /** 1339 * @param {boolean} accepted 1340 * @this {WebInspector.HeapProfileHeader} 1341 */ 1342 function onOpen(accepted) 1343 { 1344 if (!accepted) 1345 return; 1346 this._receiver = fileOutputStream; 1347 this._transferHandler = new WebInspector.SaveSnapshotHandler(this); 1348 this._transferSnapshot(); 1349 } 1350 this._fileName = this._fileName || "Heap-" + new Date().toISO8601Compact() + this._profileType.fileExtension(); 1351 fileOutputStream.open(this._fileName, onOpen.bind(this)); 1352 }, 1353 1354 /** 1355 * @override 1356 * @param {!File} file 1357 */ 1358 loadFromFile: function(file) 1359 { 1360 this.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026"); 1361 this.sidebarElement.wait = true; 1362 this._setupWorker(); 1363 1364 var delegate = new WebInspector.HeapSnapshotLoadFromFileDelegate(this); 1365 var fileReader = this._createFileReader(file, delegate); 1366 fileReader.start(this._receiver); 1367 }, 1368 1369 _createFileReader: function(file, delegate) 1370 { 1371 return new WebInspector.ChunkedFileReader(file, 10000000, delegate); 1372 }, 1373 1374 __proto__: WebInspector.ProfileHeader.prototype 1375 } 1376 1377 1378 /** 1379 * @constructor 1380 * @param {!WebInspector.HeapProfileHeader} header 1381 * @param {string} title 1382 */ 1383 WebInspector.SnapshotTransferHandler = function(header, title) 1384 { 1385 this._numberOfChunks = 0; 1386 this._savedChunks = 0; 1387 this._header = header; 1388 this._totalNumberOfChunks = 0; 1389 this._title = title; 1390 } 1391 1392 1393 WebInspector.SnapshotTransferHandler.prototype = { 1394 /** 1395 * @param {string} chunk 1396 */ 1397 transferChunk: function(chunk) 1398 { 1399 ++this._numberOfChunks; 1400 this._header._receiver.write(chunk, this._didTransferChunk.bind(this)); 1401 }, 1402 1403 finishTransfer: function() 1404 { 1405 }, 1406 1407 _didTransferChunk: function() 1408 { 1409 this._updateProgress(++this._savedChunks, this._totalNumberOfChunks); 1410 }, 1411 1412 _updateProgress: function(value, total) 1413 { 1414 } 1415 } 1416 1417 1418 /** 1419 * @constructor 1420 * @param {!WebInspector.HeapProfileHeader} header 1421 * @extends {WebInspector.SnapshotTransferHandler} 1422 */ 1423 WebInspector.SaveSnapshotHandler = function(header) 1424 { 1425 WebInspector.SnapshotTransferHandler.call(this, header, "Saving\u2026 %d\%"); 1426 this._totalNumberOfChunks = header._totalNumberOfChunks; 1427 this._updateProgress(0, this._totalNumberOfChunks); 1428 } 1429 1430 1431 WebInspector.SaveSnapshotHandler.prototype = { 1432 _updateProgress: function(value, total) 1433 { 1434 var percentValue = ((total ? (value / total) : 0) * 100).toFixed(0); 1435 this._header._updateSubtitle(WebInspector.UIString(this._title, percentValue)); 1436 if (value === total) { 1437 this._header._receiver.close(); 1438 this._header._didCompleteSnapshotTransfer(); 1439 } 1440 }, 1441 1442 __proto__: WebInspector.SnapshotTransferHandler.prototype 1443 } 1444 1445 1446 /** 1447 * @constructor 1448 * @param {!WebInspector.HeapProfileHeader} header 1449 * @extends {WebInspector.SnapshotTransferHandler} 1450 */ 1451 WebInspector.BackendSnapshotLoader = function(header) 1452 { 1453 WebInspector.SnapshotTransferHandler.call(this, header, "Loading\u2026 %d\%"); 1454 } 1455 1456 1457 WebInspector.BackendSnapshotLoader.prototype = { 1458 finishTransfer: function() 1459 { 1460 this._header._receiver.close(this._didFinishTransfer.bind(this)); 1461 this._totalNumberOfChunks = this._numberOfChunks; 1462 }, 1463 1464 _didFinishTransfer: function() 1465 { 1466 console.assert(this._totalNumberOfChunks === this._savedChunks, "Not all chunks were transfered."); 1467 }, 1468 1469 __proto__: WebInspector.SnapshotTransferHandler.prototype 1470 } 1471 1472 1473 /** 1474 * @constructor 1475 * @implements {WebInspector.OutputStreamDelegate} 1476 */ 1477 WebInspector.HeapSnapshotLoadFromFileDelegate = function(snapshotHeader) 1478 { 1479 this._snapshotHeader = snapshotHeader; 1480 } 1481 1482 WebInspector.HeapSnapshotLoadFromFileDelegate.prototype = { 1483 onTransferStarted: function() 1484 { 1485 }, 1486 1487 /** 1488 * @param {!WebInspector.ChunkedReader} reader 1489 */ 1490 onChunkTransferred: function(reader) 1491 { 1492 }, 1493 1494 onTransferFinished: function() 1495 { 1496 }, 1497 1498 /** 1499 * @param {!WebInspector.ChunkedReader} reader 1500 */ 1501 onError: function (reader, e) 1502 { 1503 switch(e.target.error.code) { 1504 case e.target.error.NOT_FOUND_ERR: 1505 this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' not found.", reader.fileName())); 1506 break; 1507 case e.target.error.NOT_READABLE_ERR: 1508 this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' is not readable", reader.fileName())); 1509 break; 1510 case e.target.error.ABORT_ERR: 1511 break; 1512 default: 1513 this._snapshotHeader._updateSubtitle(WebInspector.UIString("'%s' error %d", reader.fileName(), e.target.error.code)); 1514 } 1515 } 1516 } 1517 1518 /** 1519 * @constructor 1520 * @extends {WebInspector.View} 1521 * @param {!WebInspector.HeapProfileHeader} heapProfileHeader 1522 */ 1523 WebInspector.HeapTrackingOverviewGrid = function(heapProfileHeader) 1524 { 1525 WebInspector.View.call(this); 1526 this.registerRequiredCSS("flameChart.css"); 1527 this.element.id = "heap-recording-view"; 1528 1529 this._overviewContainer = this.element.createChild("div", "overview-container"); 1530 this._overviewGrid = new WebInspector.OverviewGrid("heap-recording"); 1531 this._overviewGrid.element.classList.add("fill"); 1532 1533 this._overviewCanvas = this._overviewContainer.createChild("canvas", "heap-recording-overview-canvas"); 1534 this._overviewContainer.appendChild(this._overviewGrid.element); 1535 this._overviewCalculator = new WebInspector.HeapTrackingOverviewGrid.OverviewCalculator(); 1536 this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); 1537 1538 this._profileSamples = heapProfileHeader._profileSamples; 1539 if (heapProfileHeader.profileType().profileBeingRecorded() === heapProfileHeader) { 1540 this._profileType = heapProfileHeader._profileType; 1541 this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); 1542 this._profileType.addEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); 1543 } 1544 var timestamps = this._profileSamples.timestamps; 1545 var totalTime = this._profileSamples.totalTime; 1546 this._windowLeft = 0.0; 1547 this._windowRight = totalTime && timestamps.length ? (timestamps[timestamps.length - 1] - timestamps[0]) / totalTime : 1.0; 1548 this._overviewGrid.setWindow(this._windowLeft, this._windowRight); 1549 this._yScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale(); 1550 this._xScale = new WebInspector.HeapTrackingOverviewGrid.SmoothScale(); 1551 } 1552 1553 WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged = "IdsRangeChanged"; 1554 1555 WebInspector.HeapTrackingOverviewGrid.prototype = { 1556 _onStopTracking: function(event) 1557 { 1558 this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.HeapStatsUpdate, this._onHeapStatsUpdate, this); 1559 this._profileType.removeEventListener(WebInspector.TrackingHeapSnapshotProfileType.TrackingStopped, this._onStopTracking, this); 1560 }, 1561 1562 _onHeapStatsUpdate: function(event) 1563 { 1564 this._profileSamples = event.data; 1565 this._scheduleUpdate(); 1566 }, 1567 1568 /** 1569 * @param {number} width 1570 * @param {number} height 1571 */ 1572 _drawOverviewCanvas: function(width, height) 1573 { 1574 if (!this._profileSamples) 1575 return; 1576 var profileSamples = this._profileSamples; 1577 var sizes = profileSamples.sizes; 1578 var topSizes = profileSamples.max; 1579 var timestamps = profileSamples.timestamps; 1580 var startTime = timestamps[0]; 1581 var endTime = timestamps[timestamps.length - 1]; 1582 1583 var scaleFactor = this._xScale.nextScale(width / profileSamples.totalTime); 1584 var maxSize = 0; 1585 /** 1586 * @param {!Array.<number>} sizes 1587 * @param {function(number, number):void} callback 1588 */ 1589 function aggregateAndCall(sizes, callback) 1590 { 1591 var size = 0; 1592 var currentX = 0; 1593 for (var i = 1; i < timestamps.length; ++i) { 1594 var x = Math.floor((timestamps[i] - startTime) * scaleFactor); 1595 if (x !== currentX) { 1596 if (size) 1597 callback(currentX, size); 1598 size = 0; 1599 currentX = x; 1600 } 1601 size += sizes[i]; 1602 } 1603 callback(currentX, size); 1604 } 1605 1606 /** 1607 * @param {number} x 1608 * @param {number} size 1609 */ 1610 function maxSizeCallback(x, size) 1611 { 1612 maxSize = Math.max(maxSize, size); 1613 } 1614 1615 aggregateAndCall(sizes, maxSizeCallback); 1616 1617 var yScaleFactor = this._yScale.nextScale(maxSize ? height / (maxSize * 1.1) : 0.0); 1618 1619 this._overviewCanvas.width = width * window.devicePixelRatio; 1620 this._overviewCanvas.height = height * window.devicePixelRatio; 1621 this._overviewCanvas.style.width = width + "px"; 1622 this._overviewCanvas.style.height = height + "px"; 1623 1624 var context = this._overviewCanvas.getContext("2d"); 1625 context.scale(window.devicePixelRatio, window.devicePixelRatio); 1626 1627 context.beginPath(); 1628 context.lineWidth = 2; 1629 context.strokeStyle = "rgba(192, 192, 192, 0.6)"; 1630 var currentX = (endTime - startTime) * scaleFactor; 1631 context.moveTo(currentX, height - 1); 1632 context.lineTo(currentX, 0); 1633 context.stroke(); 1634 context.closePath(); 1635 1636 var gridY; 1637 var gridValue; 1638 var gridLabelHeight = 14; 1639 if (yScaleFactor) { 1640 const maxGridValue = (height - gridLabelHeight) / yScaleFactor; 1641 // The round value calculation is a bit tricky, because 1642 // it has a form k*10^n*1024^m, where k=[1,5], n=[0..3], m is an integer, 1643 // e.g. a round value 10KB is 10240 bytes. 1644 gridValue = Math.pow(1024, Math.floor(Math.log(maxGridValue) / Math.log(1024))); 1645 gridValue *= Math.pow(10, Math.floor(Math.log(maxGridValue / gridValue) / Math.LN10)); 1646 if (gridValue * 5 <= maxGridValue) 1647 gridValue *= 5; 1648 gridY = Math.round(height - gridValue * yScaleFactor - 0.5) + 0.5; 1649 context.beginPath(); 1650 context.lineWidth = 1; 1651 context.strokeStyle = "rgba(0, 0, 0, 0.2)"; 1652 context.moveTo(0, gridY); 1653 context.lineTo(width, gridY); 1654 context.stroke(); 1655 context.closePath(); 1656 } 1657 1658 /** 1659 * @param {number} x 1660 * @param {number} size 1661 */ 1662 function drawBarCallback(x, size) 1663 { 1664 context.moveTo(x, height - 1); 1665 context.lineTo(x, Math.round(height - size * yScaleFactor - 1)); 1666 } 1667 1668 context.beginPath(); 1669 context.lineWidth = 2; 1670 context.strokeStyle = "rgba(192, 192, 192, 0.6)"; 1671 aggregateAndCall(topSizes, drawBarCallback); 1672 context.stroke(); 1673 context.closePath(); 1674 1675 context.beginPath(); 1676 context.lineWidth = 2; 1677 context.strokeStyle = "rgba(0, 0, 192, 0.8)"; 1678 aggregateAndCall(sizes, drawBarCallback); 1679 context.stroke(); 1680 context.closePath(); 1681 1682 if (gridValue) { 1683 var label = Number.bytesToString(gridValue); 1684 var labelPadding = 4; 1685 var labelX = 0; 1686 var labelY = gridY - 0.5; 1687 var labelWidth = 2 * labelPadding + context.measureText(label).width; 1688 context.beginPath(); 1689 context.textBaseline = "bottom"; 1690 context.font = "10px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family"); 1691 context.fillStyle = "rgba(255, 255, 255, 0.75)"; 1692 context.fillRect(labelX, labelY - gridLabelHeight, labelWidth, gridLabelHeight); 1693 context.fillStyle = "rgb(64, 64, 64)"; 1694 context.fillText(label, labelX + labelPadding, labelY); 1695 context.fill(); 1696 context.closePath(); 1697 } 1698 }, 1699 1700 onResize: function() 1701 { 1702 this._updateOverviewCanvas = true; 1703 this._scheduleUpdate(); 1704 }, 1705 1706 _onWindowChanged: function() 1707 { 1708 if (!this._updateGridTimerId) 1709 this._updateGridTimerId = setTimeout(this._updateGrid.bind(this), 10); 1710 }, 1711 1712 _scheduleUpdate: function() 1713 { 1714 if (this._updateTimerId) 1715 return; 1716 this._updateTimerId = setTimeout(this.update.bind(this), 10); 1717 }, 1718 1719 _updateBoundaries: function() 1720 { 1721 this._windowLeft = this._overviewGrid.windowLeft(); 1722 this._windowRight = this._overviewGrid.windowRight(); 1723 this._windowWidth = this._windowRight - this._windowLeft; 1724 }, 1725 1726 update: function() 1727 { 1728 this._updateTimerId = null; 1729 if (!this.isShowing()) 1730 return; 1731 this._updateBoundaries(); 1732 this._overviewCalculator._updateBoundaries(this); 1733 this._overviewGrid.updateDividers(this._overviewCalculator); 1734 this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20); 1735 }, 1736 1737 _updateGrid: function() 1738 { 1739 this._updateGridTimerId = 0; 1740 this._updateBoundaries(); 1741 var ids = this._profileSamples.ids; 1742 var timestamps = this._profileSamples.timestamps; 1743 var sizes = this._profileSamples.sizes; 1744 var startTime = timestamps[0]; 1745 var totalTime = this._profileSamples.totalTime; 1746 var timeLeft = startTime + totalTime * this._windowLeft; 1747 var timeRight = startTime + totalTime * this._windowRight; 1748 var minId = 0; 1749 var maxId = ids[ids.length - 1] + 1; 1750 var size = 0; 1751 for (var i = 0; i < timestamps.length; ++i) { 1752 if (!timestamps[i]) 1753 continue; 1754 if (timestamps[i] > timeRight) 1755 break; 1756 maxId = ids[i]; 1757 if (timestamps[i] < timeLeft) { 1758 minId = ids[i]; 1759 continue; 1760 } 1761 size += sizes[i]; 1762 } 1763 1764 this.dispatchEventToListeners(WebInspector.HeapTrackingOverviewGrid.IdsRangeChanged, {minId: minId, maxId: maxId, size: size}); 1765 }, 1766 1767 __proto__: WebInspector.View.prototype 1768 } 1769 1770 1771 /** 1772 * @constructor 1773 */ 1774 WebInspector.HeapTrackingOverviewGrid.SmoothScale = function() 1775 { 1776 this._lastUpdate = 0; 1777 this._currentScale = 0.0; 1778 } 1779 1780 WebInspector.HeapTrackingOverviewGrid.SmoothScale.prototype = { 1781 /** 1782 * @param {number} target 1783 * @return {number} 1784 */ 1785 nextScale: function(target) { 1786 target = target || this._currentScale; 1787 if (this._currentScale) { 1788 var now = Date.now(); 1789 var timeDeltaMs = now - this._lastUpdate; 1790 this._lastUpdate = now; 1791 var maxChangePerSec = 20; 1792 var maxChangePerDelta = Math.pow(maxChangePerSec, timeDeltaMs / 1000); 1793 var scaleChange = target / this._currentScale; 1794 this._currentScale *= Number.constrain(scaleChange, 1 / maxChangePerDelta, maxChangePerDelta); 1795 } else 1796 this._currentScale = target; 1797 return this._currentScale; 1798 } 1799 } 1800 1801 1802 /** 1803 * @constructor 1804 * @implements {WebInspector.TimelineGrid.Calculator} 1805 */ 1806 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator = function() 1807 { 1808 } 1809 1810 WebInspector.HeapTrackingOverviewGrid.OverviewCalculator.prototype = { 1811 /** 1812 * @param {!WebInspector.HeapTrackingOverviewGrid} chart 1813 */ 1814 _updateBoundaries: function(chart) 1815 { 1816 this._minimumBoundaries = 0; 1817 this._maximumBoundaries = chart._profileSamples.totalTime; 1818 this._xScaleFactor = chart._overviewContainer.clientWidth / this._maximumBoundaries; 1819 }, 1820 1821 /** 1822 * @param {number} time 1823 */ 1824 computePosition: function(time) 1825 { 1826 return (time - this._minimumBoundaries) * this._xScaleFactor; 1827 }, 1828 1829 /** 1830 * @param {number} value 1831 * @param {boolean=} hires 1832 * @return {string} 1833 */ 1834 formatTime: function(value, hires) 1835 { 1836 return Number.secondsToString((value + this._minimumBoundaries) / 1000, hires); 1837 }, 1838 1839 maximumBoundary: function() 1840 { 1841 return this._maximumBoundaries; 1842 }, 1843 1844 minimumBoundary: function() 1845 { 1846 return this._minimumBoundaries; 1847 }, 1848 1849 zeroTime: function() 1850 { 1851 return this._minimumBoundaries; 1852 }, 1853 1854 boundarySpan: function() 1855 { 1856 return this._maximumBoundaries - this._minimumBoundaries; 1857 } 1858 } 1859