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