1 /* 2 * Copyright (C) 2008 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /** 27 * @constructor 28 * @extends {WebInspector.Object} 29 * @param {string} id 30 * @param {string} name 31 */ 32 WebInspector.ProfileType = function(id, name) 33 { 34 this._id = id; 35 this._name = name; 36 /** @type {!Array.<!WebInspector.ProfileHeader>} */ 37 this._profiles = []; 38 /** @type {?WebInspector.SidebarSectionTreeElement} */ 39 this.treeElement = null; 40 /** @type {?WebInspector.ProfileHeader} */ 41 this._profileBeingRecorded = null; 42 } 43 44 WebInspector.ProfileType.Events = { 45 AddProfileHeader: "add-profile-header", 46 RemoveProfileHeader: "remove-profile-header", 47 ViewUpdated: "view-updated" 48 } 49 50 WebInspector.ProfileType.prototype = { 51 /** 52 * @return {boolean} 53 */ 54 hasTemporaryView: function() 55 { 56 return false; 57 }, 58 59 /** 60 * @return {?string} 61 */ 62 fileExtension: function() 63 { 64 return null; 65 }, 66 67 get statusBarItems() 68 { 69 return []; 70 }, 71 72 get buttonTooltip() 73 { 74 return ""; 75 }, 76 77 get id() 78 { 79 return this._id; 80 }, 81 82 get treeItemTitle() 83 { 84 return this._name; 85 }, 86 87 get name() 88 { 89 return this._name; 90 }, 91 92 /** 93 * @return {boolean} 94 */ 95 buttonClicked: function() 96 { 97 return false; 98 }, 99 100 get description() 101 { 102 return ""; 103 }, 104 105 /** 106 * @return {boolean} 107 */ 108 isInstantProfile: function() 109 { 110 return false; 111 }, 112 113 /** 114 * @return {boolean} 115 */ 116 isEnabled: function() 117 { 118 return true; 119 }, 120 121 /** 122 * @return {!Array.<!WebInspector.ProfileHeader>} 123 */ 124 getProfiles: function() 125 { 126 /** 127 * @param {!WebInspector.ProfileHeader} profile 128 * @return {boolean} 129 * @this {WebInspector.ProfileType} 130 */ 131 function isFinished(profile) 132 { 133 return this._profileBeingRecorded !== profile; 134 } 135 return this._profiles.filter(isFinished.bind(this)); 136 }, 137 138 /** 139 * @return {?Element} 140 */ 141 decorationElement: function() 142 { 143 return null; 144 }, 145 146 /** 147 * @nosideeffects 148 * @param {number} uid 149 * @return {?WebInspector.ProfileHeader} 150 */ 151 getProfile: function(uid) 152 { 153 154 for (var i = 0; i < this._profiles.length; ++i) { 155 if (this._profiles[i].uid === uid) 156 return this._profiles[i]; 157 } 158 return null; 159 }, 160 161 /** 162 * @param {!File} file 163 */ 164 loadFromFile: function(file) 165 { 166 var name = file.name; 167 if (name.endsWith(this.fileExtension())) 168 name = name.substr(0, name.length - this.fileExtension().length); 169 var profile = this.createProfileLoadedFromFile(name); 170 profile.setFromFile(); 171 this._profileBeingRecorded = profile; 172 this.addProfile(profile); 173 profile.loadFromFile(file); 174 }, 175 176 /** 177 * @param {!string} title 178 * @return {!WebInspector.ProfileHeader} 179 */ 180 createProfileLoadedFromFile: function(title) 181 { 182 throw new Error("Needs implemented."); 183 }, 184 185 /** 186 * @param {!WebInspector.ProfileHeader} profile 187 */ 188 addProfile: function(profile) 189 { 190 this._profiles.push(profile); 191 this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile); 192 }, 193 194 /** 195 * @param {!WebInspector.ProfileHeader} profile 196 */ 197 removeProfile: function(profile) 198 { 199 if (this._profileBeingRecorded === profile) 200 this._profileBeingRecorded = null; 201 for (var i = 0; i < this._profiles.length; ++i) { 202 if (this._profiles[i].uid === profile.uid) { 203 this._profiles.splice(i, 1); 204 break; 205 } 206 } 207 }, 208 209 /** 210 * @nosideeffects 211 * @return {?WebInspector.ProfileHeader} 212 */ 213 profileBeingRecorded: function() 214 { 215 return this._profileBeingRecorded; 216 }, 217 218 _reset: function() 219 { 220 var profiles = this._profiles.slice(0); 221 for (var i = 0; i < profiles.length; ++i) { 222 var profile = profiles[i]; 223 var view = profile.existingView(); 224 if (view) { 225 view.detach(); 226 if ("dispose" in view) 227 view.dispose(); 228 } 229 this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile); 230 } 231 this.treeElement.removeChildren(); 232 this._profiles = []; 233 }, 234 235 __proto__: WebInspector.Object.prototype 236 } 237 238 /** 239 * @constructor 240 * @param {!WebInspector.ProfileType} profileType 241 * @param {string} title 242 * @param {number=} uid 243 */ 244 WebInspector.ProfileHeader = function(profileType, title, uid) 245 { 246 this._profileType = profileType; 247 this.title = title; 248 this.uid = (uid === undefined) ? -1 : uid; 249 this._fromFile = false; 250 } 251 252 WebInspector.ProfileHeader._nextProfileFromFileUid = 1; 253 254 WebInspector.ProfileHeader.prototype = { 255 /** 256 * @return {!WebInspector.ProfileType} 257 */ 258 profileType: function() 259 { 260 return this._profileType; 261 }, 262 263 /** 264 * Must be implemented by subclasses. 265 * @return {!WebInspector.ProfileSidebarTreeElement} 266 */ 267 createSidebarTreeElement: function() 268 { 269 throw new Error("Needs implemented."); 270 }, 271 272 /** 273 * @return {?WebInspector.View} 274 */ 275 existingView: function() 276 { 277 return this._view; 278 }, 279 280 /** 281 * @param {!WebInspector.ProfilesPanel} panel 282 * @return {!WebInspector.View} 283 */ 284 view: function(panel) 285 { 286 if (!this._view) 287 this._view = this.createView(panel); 288 return this._view; 289 }, 290 291 /** 292 * @param {!WebInspector.ProfilesPanel} panel 293 * @return {!WebInspector.View} 294 */ 295 createView: function(panel) 296 { 297 throw new Error("Not implemented."); 298 }, 299 300 dispose: function() 301 { 302 }, 303 304 /** 305 * @param {!Function} callback 306 */ 307 load: function(callback) 308 { 309 }, 310 311 /** 312 * @return {boolean} 313 */ 314 canSaveToFile: function() 315 { 316 return false; 317 }, 318 319 saveToFile: function() 320 { 321 throw new Error("Needs implemented"); 322 }, 323 324 /** 325 * @param {!File} file 326 */ 327 loadFromFile: function(file) 328 { 329 throw new Error("Needs implemented"); 330 }, 331 332 /** 333 * @return {boolean} 334 */ 335 fromFile: function() 336 { 337 return this._fromFile; 338 }, 339 340 setFromFile: function() 341 { 342 this._fromFile = true; 343 this.uid = "From file #" + WebInspector.ProfileHeader._nextProfileFromFileUid++; 344 } 345 } 346 347 /** 348 * @constructor 349 * @implements {WebInspector.Searchable} 350 * @implements {WebInspector.ContextMenu.Provider} 351 * @extends {WebInspector.Panel} 352 * @param {string=} name 353 * @param {!WebInspector.ProfileType=} type 354 */ 355 WebInspector.ProfilesPanel = function(name, type) 356 { 357 // If the name is not specified the ProfilesPanel works in multi-profile mode. 358 var singleProfileMode = typeof name !== "undefined"; 359 name = name || "profiles"; 360 WebInspector.Panel.call(this, name); 361 this.registerRequiredCSS("panelEnablerView.css"); 362 this.registerRequiredCSS("heapProfiler.css"); 363 this.registerRequiredCSS("profilesPanel.css"); 364 365 this.createSidebarViewWithTree(); 366 367 this.splitView.mainElement.classList.add("vbox"); 368 this.splitView.sidebarElement.classList.add("vbox"); 369 370 this._searchableView = new WebInspector.SearchableView(this); 371 this._searchableView.show(this.splitView.mainElement); 372 373 this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this); 374 this.sidebarTree.appendChild(this.profilesItemTreeElement); 375 376 this._singleProfileMode = singleProfileMode; 377 this._profileTypesByIdMap = {}; 378 379 this.profileViews = document.createElement("div"); 380 this.profileViews.id = "profile-views"; 381 this.profileViews.classList.add("vbox"); 382 this._searchableView.element.appendChild(this.profileViews); 383 384 var statusBarContainer = this.splitView.mainElement.createChild("div", "profiles-status-bar"); 385 this._statusBarElement = statusBarContainer.createChild("div", "status-bar"); 386 387 var sidebarTreeBox = this.sidebarElement.createChild("div", "profiles-sidebar-tree-box"); 388 sidebarTreeBox.appendChild(this.sidebarTreeElement); 389 var statusBarContainerLeft = this.sidebarElement.createChild("div", "profiles-status-bar"); 390 this._statusBarButtons = statusBarContainerLeft.createChild("div", "status-bar"); 391 392 this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); 393 this.recordButton.addEventListener("click", this.toggleRecordButton, this); 394 this._statusBarButtons.appendChild(this.recordButton.element); 395 396 this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item"); 397 this.clearResultsButton.addEventListener("click", this._clearProfiles, this); 398 this._statusBarButtons.appendChild(this.clearResultsButton.element); 399 400 this._profileTypeStatusBarItemsContainer = this._statusBarElement.createChild("div"); 401 this._profileViewStatusBarItemsContainer = this._statusBarElement.createChild("div"); 402 403 if (singleProfileMode) { 404 this._launcherView = this._createLauncherView(); 405 this._registerProfileType(/** @type {!WebInspector.ProfileType} */ (type)); 406 this._selectedProfileType = type; 407 this._updateProfileTypeSpecificUI(); 408 } else { 409 this._launcherView = new WebInspector.MultiProfileLauncherView(this); 410 this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this); 411 412 this._registerProfileType(new WebInspector.CPUProfileType()); 413 this._registerProfileType(new WebInspector.HeapSnapshotProfileType()); 414 this._registerProfileType(new WebInspector.TrackingHeapSnapshotProfileType(this)); 415 if (!WebInspector.WorkerManager.isWorkerFrontend() && WebInspector.experimentsSettings.canvasInspection.isEnabled()) 416 this._registerProfileType(new WebInspector.CanvasProfileType()); 417 this._launcherView.restoreSelectedProfileType(); 418 } 419 420 this._reset(); 421 422 this._createFileSelectorElement(); 423 this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 424 this._registerShortcuts(); 425 426 WebInspector.ContextMenu.registerProvider(this); 427 428 this._configureCpuProfilerSamplingInterval(); 429 WebInspector.settings.highResolutionCpuProfiling.addChangeListener(this._configureCpuProfilerSamplingInterval, this); 430 } 431 432 WebInspector.ProfilesPanel.prototype = { 433 /** 434 * @return {!WebInspector.SearchableView} 435 */ 436 searchableView: function() 437 { 438 return this._searchableView; 439 }, 440 441 _createFileSelectorElement: function() 442 { 443 if (this._fileSelectorElement) 444 this.element.removeChild(this._fileSelectorElement); 445 this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this)); 446 this.element.appendChild(this._fileSelectorElement); 447 }, 448 449 /** 450 * @return {!WebInspector.ProfileLauncherView} 451 */ 452 _createLauncherView: function() 453 { 454 return new WebInspector.ProfileLauncherView(this); 455 }, 456 457 _findProfileTypeByExtension: function(fileName) 458 { 459 for (var id in this._profileTypesByIdMap) { 460 var type = this._profileTypesByIdMap[id]; 461 var extension = type.fileExtension(); 462 if (!extension) 463 continue; 464 if (fileName.endsWith(type.fileExtension())) 465 return type; 466 } 467 return null; 468 }, 469 470 _registerShortcuts: function() 471 { 472 this.registerShortcuts(WebInspector.ProfilesPanelDescriptor.ShortcutKeys.StartStopRecording, this.toggleRecordButton.bind(this)); 473 }, 474 475 _configureCpuProfilerSamplingInterval: function() 476 { 477 var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000; 478 ProfilerAgent.setSamplingInterval(intervalUs, didChangeInterval.bind(this)); 479 function didChangeInterval(error) 480 { 481 if (error) 482 WebInspector.showErrorMessage(error) 483 } 484 }, 485 486 /** 487 * @param {!File} file 488 */ 489 _loadFromFile: function(file) 490 { 491 this._createFileSelectorElement(); 492 493 var profileType = this._findProfileTypeByExtension(file.name); 494 if (!profileType) { 495 var extensions = []; 496 for (var id in this._profileTypesByIdMap) { 497 var extension = this._profileTypesByIdMap[id].fileExtension(); 498 if (!extension) 499 continue; 500 extensions.push(extension); 501 } 502 WebInspector.log(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '"))); 503 return; 504 } 505 506 if (!!profileType.profileBeingRecorded()) { 507 WebInspector.log(WebInspector.UIString("Can't load profile when other profile is recording.")); 508 return; 509 } 510 511 profileType.loadFromFile(file); 512 }, 513 514 /** 515 * @return {boolean} 516 */ 517 toggleRecordButton: function() 518 { 519 var type = this._selectedProfileType; 520 var isProfiling = type.buttonClicked(); 521 this.recordButton.toggled = isProfiling; 522 this.recordButton.title = type.buttonTooltip; 523 if (isProfiling) { 524 this._launcherView.profileStarted(); 525 if (type.hasTemporaryView()) 526 this._showProfile(type.profileBeingRecorded()); 527 } else { 528 this._launcherView.profileFinished(); 529 } 530 return true; 531 }, 532 533 /** 534 * @param {!WebInspector.Event} event 535 */ 536 _onProfileTypeSelected: function(event) 537 { 538 this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data); 539 this._updateProfileTypeSpecificUI(); 540 }, 541 542 _updateProfileTypeSpecificUI: function() 543 { 544 this.recordButton.title = this._selectedProfileType.buttonTooltip; 545 this._launcherView.updateProfileType(this._selectedProfileType); 546 this._profileTypeStatusBarItemsContainer.removeChildren(); 547 var statusBarItems = this._selectedProfileType.statusBarItems; 548 if (statusBarItems) { 549 for (var i = 0; i < statusBarItems.length; ++i) 550 this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]); 551 } 552 }, 553 554 _reset: function() 555 { 556 WebInspector.Panel.prototype.reset.call(this); 557 558 for (var typeId in this._profileTypesByIdMap) 559 this._profileTypesByIdMap[typeId]._reset(); 560 561 delete this.visibleView; 562 delete this.currentQuery; 563 this.searchCanceled(); 564 565 this._profileGroups = {}; 566 this.recordButton.toggled = false; 567 if (this._selectedProfileType) 568 this.recordButton.title = this._selectedProfileType.buttonTooltip; 569 this._launcherView.profileFinished(); 570 571 this.sidebarTreeElement.classList.remove("some-expandable"); 572 573 this._launcherView.detach(); 574 this.profileViews.removeChildren(); 575 this._profileViewStatusBarItemsContainer.removeChildren(); 576 577 this.removeAllListeners(); 578 579 this.recordButton.visible = true; 580 this._profileViewStatusBarItemsContainer.classList.remove("hidden"); 581 this.clearResultsButton.element.classList.remove("hidden"); 582 this.profilesItemTreeElement.select(); 583 this._showLauncherView(); 584 }, 585 586 _showLauncherView: function() 587 { 588 this.closeVisibleView(); 589 this._profileViewStatusBarItemsContainer.removeChildren(); 590 this._launcherView.show(this.profileViews); 591 this.visibleView = this._launcherView; 592 }, 593 594 _clearProfiles: function() 595 { 596 HeapProfilerAgent.clearProfiles(); 597 this._reset(); 598 }, 599 600 _garbageCollectButtonClicked: function() 601 { 602 HeapProfilerAgent.collectGarbage(); 603 }, 604 605 /** 606 * @param {!WebInspector.ProfileType} profileType 607 */ 608 _registerProfileType: function(profileType) 609 { 610 this._profileTypesByIdMap[profileType.id] = profileType; 611 this._launcherView.addProfileType(profileType); 612 profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.treeItemTitle, null, true); 613 profileType.treeElement.hidden = !this._singleProfileMode; 614 this.sidebarTree.appendChild(profileType.treeElement); 615 profileType.treeElement.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 616 617 /** 618 * @this {WebInspector.ProfilesPanel} 619 */ 620 function onAddProfileHeader(event) 621 { 622 this._addProfileHeader(event.data); 623 } 624 625 /** 626 * @this {WebInspector.ProfilesPanel} 627 */ 628 function onRemoveProfileHeader(event) 629 { 630 this._removeProfileHeader(event.data); 631 } 632 633 profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this); 634 profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this); 635 profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this); 636 }, 637 638 /** 639 * @param {?Event} event 640 */ 641 _handleContextMenuEvent: function(event) 642 { 643 var element = event.srcElement; 644 while (element && !element.treeElement && element !== this.element) 645 element = element.parentElement; 646 if (!element) 647 return; 648 if (element.treeElement && element.treeElement.handleContextMenuEvent) { 649 element.treeElement.handleContextMenuEvent(event, this); 650 return; 651 } 652 653 var contextMenu = new WebInspector.ContextMenu(event); 654 if (this.visibleView instanceof WebInspector.HeapSnapshotView) { 655 this.visibleView.populateContextMenu(contextMenu, event); 656 } 657 if (element !== this.element || event.srcElement === this.sidebarElement) { 658 contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement)); 659 } 660 contextMenu.show(); 661 }, 662 663 /** 664 * @nosideeffects 665 * @param {string} text 666 * @param {string} profileTypeId 667 * @return {string} 668 */ 669 _makeTitleKey: function(text, profileTypeId) 670 { 671 return escape(text) + '/' + escape(profileTypeId); 672 }, 673 674 /** 675 * @param {!WebInspector.ProfileHeader} profile 676 */ 677 _addProfileHeader: function(profile) 678 { 679 var profileType = profile.profileType(); 680 var typeId = profileType.id; 681 var sidebarParent = profileType.treeElement; 682 sidebarParent.hidden = false; 683 var small = false; 684 var alternateTitle; 685 686 if (!profile.fromFile() && profile.profileType().profileBeingRecorded() !== profile) { 687 var profileTitleKey = this._makeTitleKey(profile.title, typeId); 688 if (!(profileTitleKey in this._profileGroups)) 689 this._profileGroups[profileTitleKey] = []; 690 691 var group = this._profileGroups[profileTitleKey]; 692 group.push(profile); 693 if (group.length === 2) { 694 // Make a group TreeElement now that there are 2 profiles. 695 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this, profile.title); 696 697 // Insert at the same index for the first profile of the group. 698 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement); 699 sidebarParent.insertChild(group._profilesTreeElement, index); 700 701 // Move the first profile to the group. 702 var selected = group[0]._profilesTreeElement.selected; 703 sidebarParent.removeChild(group[0]._profilesTreeElement); 704 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); 705 if (selected) 706 group[0]._profilesTreeElement.revealAndSelect(); 707 708 group[0]._profilesTreeElement.small = true; 709 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); 710 711 this.sidebarTreeElement.classList.add("some-expandable"); 712 } 713 714 if (group.length >= 2) { 715 sidebarParent = group._profilesTreeElement; 716 alternateTitle = WebInspector.UIString("Run %d", group.length); 717 small = true; 718 } 719 } 720 721 var profileTreeElement = profile.createSidebarTreeElement(); 722 profile.sidebarElement = profileTreeElement; 723 profileTreeElement.small = small; 724 if (alternateTitle) 725 profileTreeElement.mainTitle = alternateTitle; 726 profile._profilesTreeElement = profileTreeElement; 727 728 sidebarParent.appendChild(profileTreeElement); 729 if (!this.visibleView || this.visibleView === this._launcherView) 730 this._showProfile(profile); 731 732 this.dispatchEventToListeners("profile added", { 733 type: typeId 734 }); 735 }, 736 737 /** 738 * @param {!WebInspector.ProfileHeader} profile 739 */ 740 _removeProfileHeader: function(profile) 741 { 742 profile.dispose(); 743 profile.profileType().removeProfile(profile); 744 745 var sidebarParent = profile.profileType().treeElement; 746 var profileTitleKey = this._makeTitleKey(profile.title, profile.profileType().id); 747 var group = this._profileGroups[profileTitleKey]; 748 if (group) { 749 group.splice(group.indexOf(profile), 1); 750 if (group.length === 1) { 751 // Move the last profile out of its group and remove the group. 752 var index = sidebarParent.children.indexOf(group._profilesTreeElement); 753 sidebarParent.insertChild(group[0]._profilesTreeElement, index); 754 group[0]._profilesTreeElement.small = false; 755 group[0]._profilesTreeElement.mainTitle = group[0].title; 756 sidebarParent.removeChild(group._profilesTreeElement); 757 } 758 if (group.length !== 0) 759 sidebarParent = group._profilesTreeElement; 760 else 761 delete this._profileGroups[profileTitleKey]; 762 } 763 sidebarParent.removeChild(profile._profilesTreeElement); 764 765 // No other item will be selected if there aren't any other profiles, so 766 // make sure that view gets cleared when the last profile is removed. 767 if (!sidebarParent.children.length) { 768 this.profilesItemTreeElement.select(); 769 this._showLauncherView(); 770 sidebarParent.hidden = !this._singleProfileMode; 771 } 772 }, 773 774 /** 775 * @param {?WebInspector.ProfileHeader} profile 776 * @return {?WebInspector.View} 777 */ 778 _showProfile: function(profile) 779 { 780 if (!profile || (profile.profileType().profileBeingRecorded() === profile) && !profile.profileType().hasTemporaryView()) 781 return null; 782 783 var view = profile.view(this); 784 if (view === this.visibleView) 785 return view; 786 787 this.closeVisibleView(); 788 789 view.show(this.profileViews); 790 791 profile._profilesTreeElement._suppressOnSelect = true; 792 profile._profilesTreeElement.revealAndSelect(); 793 delete profile._profilesTreeElement._suppressOnSelect; 794 795 this.visibleView = view; 796 797 this._profileViewStatusBarItemsContainer.removeChildren(); 798 799 var statusBarItems = view.statusBarItems; 800 if (statusBarItems) 801 for (var i = 0; i < statusBarItems.length; ++i) 802 this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); 803 804 return view; 805 }, 806 807 /** 808 * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId 809 * @param {string} viewName 810 */ 811 showObject: function(snapshotObjectId, viewName) 812 { 813 var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles(); 814 for (var i = 0; i < heapProfiles.length; i++) { 815 var profile = heapProfiles[i]; 816 // FIXME: allow to choose snapshot if there are several options. 817 if (profile.maxJSObjectId >= snapshotObjectId) { 818 this._showProfile(profile); 819 var view = profile.view(this); 820 view.changeView(viewName, function() { 821 function didHighlightObject(found) { 822 if (!found) 823 WebInspector.log("Cannot find corresponding heap snapshot node", WebInspector.ConsoleMessage.MessageLevel.Error, true); 824 } 825 view.dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId, didHighlightObject.bind(this)); 826 }); 827 break; 828 } 829 } 830 }, 831 832 /** 833 * @param {string} typeId 834 * @param {number} uid 835 */ 836 getProfile: function(typeId, uid) 837 { 838 return this.getProfileType(typeId).getProfile(uid); 839 }, 840 841 /** 842 * @param {!WebInspector.View} view 843 */ 844 showView: function(view) 845 { 846 this._showProfile(view.profile); 847 }, 848 849 /** 850 * @param {string} typeId 851 */ 852 getProfileType: function(typeId) 853 { 854 return this._profileTypesByIdMap[typeId]; 855 }, 856 857 /** 858 * @param {string} typeId 859 * @param {string} uid 860 * @return {?WebInspector.View} 861 */ 862 showProfile: function(typeId, uid) 863 { 864 return this._showProfile(this.getProfile(typeId, Number(uid))); 865 }, 866 867 closeVisibleView: function() 868 { 869 if (this.visibleView) 870 this.visibleView.detach(); 871 delete this.visibleView; 872 }, 873 874 /** 875 * @param {string} query 876 * @param {boolean} shouldJump 877 */ 878 performSearch: function(query, shouldJump) 879 { 880 this.searchCanceled(); 881 882 var visibleView = this.visibleView; 883 if (!visibleView) 884 return; 885 886 /** 887 * @this {WebInspector.ProfilesPanel} 888 */ 889 function finishedCallback(view, searchMatches) 890 { 891 if (!searchMatches) 892 return; 893 this._searchableView.updateSearchMatchesCount(searchMatches); 894 this._searchResultsView = view; 895 if (shouldJump) { 896 view.jumpToFirstSearchResult(); 897 this._searchableView.updateCurrentMatchIndex(view.currentSearchResultIndex()); 898 } 899 } 900 901 visibleView.currentQuery = query; 902 visibleView.performSearch(query, finishedCallback.bind(this)); 903 }, 904 905 jumpToNextSearchResult: function() 906 { 907 if (!this._searchResultsView) 908 return; 909 if (this._searchResultsView !== this.visibleView) 910 return; 911 this._searchResultsView.jumpToNextSearchResult(); 912 this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex()); 913 }, 914 915 jumpToPreviousSearchResult: function() 916 { 917 if (!this._searchResultsView) 918 return; 919 if (this._searchResultsView !== this.visibleView) 920 return; 921 this._searchResultsView.jumpToPreviousSearchResult(); 922 this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex()); 923 }, 924 925 /** 926 * @return {!Array.<!WebInspector.ProfileHeader>} 927 */ 928 _getAllProfiles: function() 929 { 930 var profiles = []; 931 for (var typeId in this._profileTypesByIdMap) 932 profiles = profiles.concat(this._profileTypesByIdMap[typeId].getProfiles()); 933 return profiles; 934 }, 935 936 searchCanceled: function() 937 { 938 if (this._searchResultsView) { 939 if (this._searchResultsView.searchCanceled) 940 this._searchResultsView.searchCanceled(); 941 this._searchResultsView.currentQuery = null; 942 this._searchResultsView = null; 943 } 944 this._searchableView.updateSearchMatchesCount(0); 945 }, 946 947 /** 948 * @param {!WebInspector.ProfileHeader} profile 949 * @param {number} done 950 * @param {number} total 951 */ 952 _reportProfileProgress: function(profile, done, total) 953 { 954 profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100); 955 profile.sidebarElement.wait = true; 956 }, 957 958 /** 959 * @param {!WebInspector.ContextMenu} contextMenu 960 * @param {!Object} target 961 */ 962 appendApplicableItems: function(event, contextMenu, target) 963 { 964 if (WebInspector.inspectorView.currentPanel() !== this) 965 return; 966 967 var object = /** @type {!WebInspector.RemoteObject} */ (target); 968 var objectId = object.objectId; 969 if (!objectId) 970 return; 971 972 var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles(); 973 if (!heapProfiles.length) 974 return; 975 976 /** 977 * @this {WebInspector.ProfilesPanel} 978 */ 979 function revealInView(viewName) 980 { 981 HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName)); 982 } 983 984 /** 985 * @this {WebInspector.ProfilesPanel} 986 */ 987 function didReceiveHeapObjectId(viewName, error, result) 988 { 989 if (WebInspector.inspectorView.currentPanel() !== this) 990 return; 991 if (!error) 992 this.showObject(result, viewName); 993 } 994 995 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators")); 996 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary")); 997 }, 998 999 __proto__: WebInspector.Panel.prototype 1000 } 1001 1002 /** 1003 * @constructor 1004 * @extends {WebInspector.SidebarTreeElement} 1005 * @param {!WebInspector.ProfileHeader} profile 1006 * @param {string} className 1007 */ 1008 WebInspector.ProfileSidebarTreeElement = function(profile, className) 1009 { 1010 this.profile = profile; 1011 WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false); 1012 this.refreshTitles(); 1013 } 1014 1015 WebInspector.ProfileSidebarTreeElement.prototype = { 1016 onselect: function() 1017 { 1018 if (!this._suppressOnSelect) 1019 this.treeOutline.panel._showProfile(this.profile); 1020 }, 1021 1022 ondelete: function() 1023 { 1024 this.treeOutline.panel._removeProfileHeader(this.profile); 1025 return true; 1026 }, 1027 1028 get mainTitle() 1029 { 1030 if (this._mainTitle) 1031 return this._mainTitle; 1032 return this.profile.title; 1033 }, 1034 1035 set mainTitle(x) 1036 { 1037 this._mainTitle = x; 1038 this.refreshTitles(); 1039 }, 1040 1041 /** 1042 * @param {!Event} event 1043 * @param {!WebInspector.ProfilesPanel} panel 1044 */ 1045 handleContextMenuEvent: function(event, panel) 1046 { 1047 var profile = this.profile; 1048 var contextMenu = new WebInspector.ContextMenu(event); 1049 // FIXME: use context menu provider 1050 contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement)); 1051 if (profile.canSaveToFile()) 1052 contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile)); 1053 contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this)); 1054 contextMenu.show(); 1055 }, 1056 1057 __proto__: WebInspector.SidebarTreeElement.prototype 1058 } 1059 1060 /** 1061 * @constructor 1062 * @extends {WebInspector.SidebarTreeElement} 1063 * @param {!WebInspector.ProfilesPanel} panel 1064 * @param {string} title 1065 * @param {string=} subtitle 1066 */ 1067 WebInspector.ProfileGroupSidebarTreeElement = function(panel, title, subtitle) 1068 { 1069 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); 1070 this._panel = panel; 1071 } 1072 1073 WebInspector.ProfileGroupSidebarTreeElement.prototype = { 1074 onselect: function() 1075 { 1076 if (this.children.length > 0) 1077 this._panel._showProfile(this.children[this.children.length - 1].profile); 1078 }, 1079 1080 __proto__: WebInspector.SidebarTreeElement.prototype 1081 } 1082 1083 /** 1084 * @constructor 1085 * @extends {WebInspector.SidebarTreeElement} 1086 * @param {!WebInspector.ProfilesPanel} panel 1087 */ 1088 WebInspector.ProfilesSidebarTreeElement = function(panel) 1089 { 1090 this._panel = panel; 1091 this.small = false; 1092 1093 WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false); 1094 } 1095 1096 WebInspector.ProfilesSidebarTreeElement.prototype = { 1097 onselect: function() 1098 { 1099 this._panel._showLauncherView(); 1100 }, 1101 1102 get selectable() 1103 { 1104 return true; 1105 }, 1106 1107 __proto__: WebInspector.SidebarTreeElement.prototype 1108 } 1109 1110 1111 /** 1112 * @constructor 1113 * @extends {WebInspector.ProfilesPanel} 1114 */ 1115 WebInspector.CPUProfilerPanel = function() 1116 { 1117 WebInspector.ProfilesPanel.call(this, "cpu-profiler", new WebInspector.CPUProfileType()); 1118 } 1119 1120 WebInspector.CPUProfilerPanel.prototype = { 1121 __proto__: WebInspector.ProfilesPanel.prototype 1122 } 1123 1124 1125 /** 1126 * @constructor 1127 * @extends {WebInspector.ProfilesPanel} 1128 */ 1129 WebInspector.HeapProfilerPanel = function() 1130 { 1131 var heapSnapshotProfileType = new WebInspector.HeapSnapshotProfileType(); 1132 WebInspector.ProfilesPanel.call(this, "heap-profiler", heapSnapshotProfileType); 1133 this._singleProfileMode = false; 1134 this._registerProfileType(new WebInspector.TrackingHeapSnapshotProfileType(this)); 1135 this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this); 1136 this._launcherView._profileTypeChanged(heapSnapshotProfileType); 1137 } 1138 1139 WebInspector.HeapProfilerPanel.prototype = { 1140 _createLauncherView: function() 1141 { 1142 return new WebInspector.MultiProfileLauncherView(this); 1143 }, 1144 1145 __proto__: WebInspector.ProfilesPanel.prototype 1146 } 1147 1148 1149 /** 1150 * @constructor 1151 * @extends {WebInspector.ProfilesPanel} 1152 */ 1153 WebInspector.CanvasProfilerPanel = function() 1154 { 1155 WebInspector.ProfilesPanel.call(this, "canvas-profiler", new WebInspector.CanvasProfileType()); 1156 } 1157 1158 WebInspector.CanvasProfilerPanel.prototype = { 1159 __proto__: WebInspector.ProfilesPanel.prototype 1160 } 1161 1162 1163 importScript("ProfileDataGridTree.js"); 1164 importScript("AllocationProfile.js"); 1165 importScript("BottomUpProfileDataGridTree.js"); 1166 importScript("CPUProfileView.js"); 1167 importScript("HeapSnapshot.js"); 1168 importScript("HeapSnapshotDataGrids.js"); 1169 importScript("HeapSnapshotGridNodes.js"); 1170 importScript("HeapSnapshotLoader.js"); 1171 importScript("HeapSnapshotProxy.js"); 1172 importScript("HeapSnapshotView.js"); 1173 importScript("HeapSnapshotWorkerDispatcher.js"); 1174 importScript("JSHeapSnapshot.js"); 1175 importScript("ProfileLauncherView.js"); 1176 importScript("TopDownProfileDataGridTree.js"); 1177 importScript("CanvasProfileView.js"); 1178 importScript("CanvasReplayStateView.js"); 1179