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 WebInspector.Object.call(this); 35 this._id = id; 36 this._name = name; 37 /** @type {!Array.<!WebInspector.ProfileHeader>} */ 38 this._profiles = []; 39 /** @type {?WebInspector.ProfileHeader} */ 40 this._profileBeingRecorded = null; 41 this._nextProfileUid = 1; 42 43 window.addEventListener("unload", this._clearTempStorage.bind(this), false); 44 } 45 46 /** 47 * @enum {string} 48 */ 49 WebInspector.ProfileType.Events = { 50 AddProfileHeader: "add-profile-header", 51 ProfileComplete: "profile-complete", 52 RemoveProfileHeader: "remove-profile-header", 53 ViewUpdated: "view-updated" 54 } 55 56 WebInspector.ProfileType.prototype = { 57 /** 58 * @return {number} 59 */ 60 nextProfileUid: function() 61 { 62 return this._nextProfileUid; 63 }, 64 65 /** 66 * @return {boolean} 67 */ 68 hasTemporaryView: function() 69 { 70 return false; 71 }, 72 73 /** 74 * @return {?string} 75 */ 76 fileExtension: function() 77 { 78 return null; 79 }, 80 81 get statusBarItems() 82 { 83 return []; 84 }, 85 86 get buttonTooltip() 87 { 88 return ""; 89 }, 90 91 get id() 92 { 93 return this._id; 94 }, 95 96 get treeItemTitle() 97 { 98 return this._name; 99 }, 100 101 get name() 102 { 103 return this._name; 104 }, 105 106 /** 107 * @return {boolean} 108 */ 109 buttonClicked: function() 110 { 111 return false; 112 }, 113 114 get description() 115 { 116 return ""; 117 }, 118 119 /** 120 * @return {boolean} 121 */ 122 isInstantProfile: function() 123 { 124 return false; 125 }, 126 127 /** 128 * @return {boolean} 129 */ 130 isEnabled: function() 131 { 132 return true; 133 }, 134 135 /** 136 * @return {!Array.<!WebInspector.ProfileHeader>} 137 */ 138 getProfiles: function() 139 { 140 /** 141 * @param {!WebInspector.ProfileHeader} profile 142 * @return {boolean} 143 * @this {WebInspector.ProfileType} 144 */ 145 function isFinished(profile) 146 { 147 return this._profileBeingRecorded !== profile; 148 } 149 return this._profiles.filter(isFinished.bind(this)); 150 }, 151 152 /** 153 * @return {?Element} 154 */ 155 decorationElement: function() 156 { 157 return null; 158 }, 159 160 /** 161 * @nosideeffects 162 * @param {number} uid 163 * @return {?WebInspector.ProfileHeader} 164 */ 165 getProfile: function(uid) 166 { 167 168 for (var i = 0; i < this._profiles.length; ++i) { 169 if (this._profiles[i].uid === uid) 170 return this._profiles[i]; 171 } 172 return null; 173 }, 174 175 /** 176 * @param {!File} file 177 */ 178 loadFromFile: function(file) 179 { 180 var name = file.name; 181 if (name.endsWith(this.fileExtension())) 182 name = name.substr(0, name.length - this.fileExtension().length); 183 var profile = this.createProfileLoadedFromFile(name); 184 profile.setFromFile(); 185 this.setProfileBeingRecorded(profile); 186 this.addProfile(profile); 187 profile.loadFromFile(file); 188 }, 189 190 /** 191 * @param {string} title 192 * @return {!WebInspector.ProfileHeader} 193 */ 194 createProfileLoadedFromFile: function(title) 195 { 196 throw new Error("Needs implemented."); 197 }, 198 199 /** 200 * @param {!WebInspector.ProfileHeader} profile 201 */ 202 addProfile: function(profile) 203 { 204 this._profiles.push(profile); 205 this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile); 206 }, 207 208 /** 209 * @param {!WebInspector.ProfileHeader} profile 210 */ 211 removeProfile: function(profile) 212 { 213 var index = this._profiles.indexOf(profile); 214 if (index === -1) 215 return; 216 this._profiles.splice(index, 1); 217 this._disposeProfile(profile); 218 }, 219 220 _clearTempStorage: function() 221 { 222 for (var i = 0; i < this._profiles.length; ++i) 223 this._profiles[i].removeTempFile(); 224 }, 225 226 /** 227 * @return {?WebInspector.ProfileHeader} 228 */ 229 profileBeingRecorded: function() 230 { 231 return this._profileBeingRecorded; 232 }, 233 234 /** 235 * @param {?WebInspector.ProfileHeader} profile 236 */ 237 setProfileBeingRecorded: function(profile) 238 { 239 if (this._profileBeingRecorded && this._profileBeingRecorded.target()) 240 WebInspector.profilingLock().release(); 241 if (profile && profile.target()) 242 WebInspector.profilingLock().acquire(); 243 this._profileBeingRecorded = profile; 244 }, 245 246 profileBeingRecordedRemoved: function() 247 { 248 }, 249 250 _reset: function() 251 { 252 var profiles = this._profiles.slice(0); 253 for (var i = 0; i < profiles.length; ++i) 254 this._disposeProfile(profiles[i]); 255 this._profiles = []; 256 257 this._nextProfileUid = 1; 258 }, 259 260 /** 261 * @param {!WebInspector.ProfileHeader} profile 262 */ 263 _disposeProfile: function(profile) 264 { 265 this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile); 266 profile.dispose(); 267 if (this._profileBeingRecorded === profile) { 268 this.profileBeingRecordedRemoved(); 269 this.setProfileBeingRecorded(null); 270 } 271 }, 272 273 __proto__: WebInspector.Object.prototype 274 } 275 276 /** 277 * @interface 278 */ 279 WebInspector.ProfileType.DataDisplayDelegate = function() 280 { 281 } 282 283 WebInspector.ProfileType.DataDisplayDelegate.prototype = { 284 /** 285 * @param {?WebInspector.ProfileHeader} profile 286 * @return {?WebInspector.View} 287 */ 288 showProfile: function(profile) { }, 289 290 /** 291 * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId 292 * @param {string} perspectiveName 293 */ 294 showObject: function(snapshotObjectId, perspectiveName) { } 295 } 296 297 /** 298 * @constructor 299 * @extends {WebInspector.Object} 300 * @param {?WebInspector.Target} target 301 * @param {!WebInspector.ProfileType} profileType 302 * @param {string} title 303 */ 304 WebInspector.ProfileHeader = function(target, profileType, title) 305 { 306 this._target = target; 307 this._profileType = profileType; 308 this.title = title; 309 this.uid = profileType._nextProfileUid++; 310 this._fromFile = false; 311 } 312 313 /** 314 * @constructor 315 * @param {?string} subtitle 316 * @param {boolean|undefined} wait 317 */ 318 WebInspector.ProfileHeader.StatusUpdate = function(subtitle, wait) 319 { 320 /** @type {?string} */ 321 this.subtitle = subtitle; 322 /** @type {boolean|undefined} */ 323 this.wait = wait; 324 } 325 326 WebInspector.ProfileHeader.Events = { 327 UpdateStatus: "UpdateStatus", 328 ProfileReceived: "ProfileReceived" 329 } 330 331 WebInspector.ProfileHeader.prototype = { 332 /** 333 * @return {?WebInspector.Target} 334 */ 335 target: function() 336 { 337 return this._target; 338 }, 339 340 /** 341 * @return {!WebInspector.ProfileType} 342 */ 343 profileType: function() 344 { 345 return this._profileType; 346 }, 347 348 /** 349 * @param {?string} subtitle 350 * @param {boolean=} wait 351 */ 352 updateStatus: function(subtitle, wait) 353 { 354 this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.UpdateStatus, new WebInspector.ProfileHeader.StatusUpdate(subtitle, wait)); 355 }, 356 357 /** 358 * Must be implemented by subclasses. 359 * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate 360 * @return {!WebInspector.ProfileSidebarTreeElement} 361 */ 362 createSidebarTreeElement: function(dataDisplayDelegate) 363 { 364 throw new Error("Needs implemented."); 365 }, 366 367 /** 368 * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate 369 * @return {!WebInspector.View} 370 */ 371 createView: function(dataDisplayDelegate) 372 { 373 throw new Error("Not implemented."); 374 }, 375 376 removeTempFile: function() 377 { 378 if (this._tempFile) 379 this._tempFile.remove(); 380 }, 381 382 dispose: function() 383 { 384 }, 385 386 /** 387 * @param {!Function} callback 388 */ 389 load: function(callback) 390 { 391 }, 392 393 /** 394 * @return {boolean} 395 */ 396 canSaveToFile: function() 397 { 398 return false; 399 }, 400 401 saveToFile: function() 402 { 403 throw new Error("Needs implemented"); 404 }, 405 406 /** 407 * @param {!File} file 408 */ 409 loadFromFile: function(file) 410 { 411 throw new Error("Needs implemented"); 412 }, 413 414 /** 415 * @return {boolean} 416 */ 417 fromFile: function() 418 { 419 return this._fromFile; 420 }, 421 422 setFromFile: function() 423 { 424 this._fromFile = true; 425 }, 426 427 __proto__: WebInspector.Object.prototype 428 } 429 430 /** 431 * @constructor 432 * @implements {WebInspector.Searchable} 433 * @implements {WebInspector.ProfileType.DataDisplayDelegate} 434 * @extends {WebInspector.PanelWithSidebarTree} 435 */ 436 WebInspector.ProfilesPanel = function() 437 { 438 WebInspector.PanelWithSidebarTree.call(this, "profiles"); 439 this.registerRequiredCSS("panelEnablerView.css"); 440 this.registerRequiredCSS("heapProfiler.css"); 441 this.registerRequiredCSS("profilesPanel.css"); 442 443 this._searchableView = new WebInspector.SearchableView(this); 444 445 var mainView = new WebInspector.VBox(); 446 this._searchableView.show(mainView.element); 447 mainView.show(this.mainElement()); 448 449 this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this); 450 this.sidebarTree.appendChild(this.profilesItemTreeElement); 451 452 this.profileViews = document.createElement("div"); 453 this.profileViews.id = "profile-views"; 454 this.profileViews.classList.add("vbox"); 455 this._searchableView.element.appendChild(this.profileViews); 456 457 var statusBarContainer = document.createElementWithClass("div", "profiles-status-bar"); 458 mainView.element.insertBefore(statusBarContainer, mainView.element.firstChild); 459 this._statusBarElement = statusBarContainer.createChild("div", "status-bar"); 460 461 this.sidebarElement().classList.add("profiles-sidebar-tree-box"); 462 var statusBarContainerLeft = document.createElementWithClass("div", "profiles-status-bar"); 463 this.sidebarElement().insertBefore(statusBarContainerLeft, this.sidebarElement().firstChild); 464 this._statusBarButtons = statusBarContainerLeft.createChild("div", "status-bar"); 465 466 this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); 467 this.recordButton.addEventListener("click", this.toggleRecordButton, this); 468 this._statusBarButtons.appendChild(this.recordButton.element); 469 470 this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item"); 471 this.clearResultsButton.addEventListener("click", this._reset, this); 472 this._statusBarButtons.appendChild(this.clearResultsButton.element); 473 474 this._profileTypeStatusBarItemsContainer = this._statusBarElement.createChild("div"); 475 this._profileViewStatusBarItemsContainer = this._statusBarElement.createChild("div"); 476 477 this._profileGroups = {}; 478 this._launcherView = new WebInspector.MultiProfileLauncherView(this); 479 this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this); 480 481 this._profileToView = []; 482 this._typeIdToSidebarSection = {}; 483 var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); 484 for (var i = 0; i < types.length; i++) 485 this._registerProfileType(types[i]); 486 this._launcherView.restoreSelectedProfileType(); 487 this.profilesItemTreeElement.select(); 488 this._showLauncherView(); 489 490 this._createFileSelectorElement(); 491 this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 492 this._registerShortcuts(); 493 494 WebInspector.profilingLock().addEventListener(WebInspector.Lock.Events.StateChanged, this._onProfilingStateChanged, this); 495 } 496 497 WebInspector.ProfilesPanel.prototype = { 498 /** 499 * @return {!WebInspector.SearchableView} 500 */ 501 searchableView: function() 502 { 503 return this._searchableView; 504 }, 505 506 _createFileSelectorElement: function() 507 { 508 if (this._fileSelectorElement) 509 this.element.removeChild(this._fileSelectorElement); 510 this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this)); 511 this.element.appendChild(this._fileSelectorElement); 512 }, 513 514 _findProfileTypeByExtension: function(fileName) 515 { 516 var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); 517 for (var i = 0; i < types.length; i++) { 518 var type = types[i]; 519 var extension = type.fileExtension(); 520 if (!extension) 521 continue; 522 if (fileName.endsWith(type.fileExtension())) 523 return type; 524 } 525 return null; 526 }, 527 528 _registerShortcuts: function() 529 { 530 this.registerShortcuts(WebInspector.ShortcutsScreen.ProfilesPanelShortcuts.StartStopRecording, this.toggleRecordButton.bind(this)); 531 }, 532 533 /** 534 * @param {!File} file 535 */ 536 _loadFromFile: function(file) 537 { 538 this._createFileSelectorElement(); 539 540 var profileType = this._findProfileTypeByExtension(file.name); 541 if (!profileType) { 542 var extensions = []; 543 var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); 544 for (var i = 0; i < types.length; i++) { 545 var extension = types[i].fileExtension(); 546 if (!extension || extensions.indexOf(extension) !== -1) 547 continue; 548 extensions.push(extension); 549 } 550 WebInspector.console.error(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '"))); 551 return; 552 } 553 554 if (!!profileType.profileBeingRecorded()) { 555 WebInspector.console.error(WebInspector.UIString("Can't load profile while another profile is recording.")); 556 return; 557 } 558 559 profileType.loadFromFile(file); 560 }, 561 562 /** 563 * @return {boolean} 564 */ 565 toggleRecordButton: function() 566 { 567 if (!this.recordButton.enabled()) 568 return true; 569 var type = this._selectedProfileType; 570 var isProfiling = type.buttonClicked(); 571 this._updateRecordButton(isProfiling); 572 if (isProfiling) { 573 this._launcherView.profileStarted(); 574 if (type.hasTemporaryView()) 575 this.showProfile(type.profileBeingRecorded()); 576 } else { 577 this._launcherView.profileFinished(); 578 } 579 return true; 580 }, 581 582 _onProfilingStateChanged: function() 583 { 584 this._updateRecordButton(this.recordButton.toggled); 585 }, 586 587 /** 588 * @param {boolean} toggled 589 */ 590 _updateRecordButton: function(toggled) 591 { 592 if (Runtime.experiments.isEnabled("disableAgentsWhenProfile")) 593 WebInspector.inspectorView.setCurrentPanelLocked(toggled); 594 var isAcquiredInSomeTarget = WebInspector.profilingLock().isAcquired(); 595 var enable = toggled || !isAcquiredInSomeTarget; 596 this.recordButton.setEnabled(enable); 597 this.recordButton.toggled = toggled; 598 if (enable) 599 this.recordButton.title = this._selectedProfileType ? this._selectedProfileType.buttonTooltip : ""; 600 else 601 this.recordButton.title = WebInspector.anotherProfilerActiveLabel(); 602 if (this._selectedProfileType) 603 this._launcherView.updateProfileType(this._selectedProfileType, enable); 604 }, 605 606 _profileBeingRecordedRemoved: function() 607 { 608 this._updateRecordButton(false); 609 this._launcherView.profileFinished(); 610 }, 611 612 /** 613 * @param {!WebInspector.Event} event 614 */ 615 _onProfileTypeSelected: function(event) 616 { 617 this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data); 618 this._updateProfileTypeSpecificUI(); 619 }, 620 621 _updateProfileTypeSpecificUI: function() 622 { 623 this._updateRecordButton(this.recordButton.toggled); 624 this._profileTypeStatusBarItemsContainer.removeChildren(); 625 var statusBarItems = this._selectedProfileType.statusBarItems; 626 if (statusBarItems) { 627 for (var i = 0; i < statusBarItems.length; ++i) 628 this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]); 629 } 630 }, 631 632 _reset: function() 633 { 634 WebInspector.Panel.prototype.reset.call(this); 635 636 var types = WebInspector.ProfileTypeRegistry.instance.profileTypes(); 637 for (var i = 0; i < types.length; i++) 638 types[i]._reset(); 639 640 delete this.visibleView; 641 delete this.currentQuery; 642 this.searchCanceled(); 643 644 this._profileGroups = {}; 645 this._updateRecordButton(false); 646 this._launcherView.profileFinished(); 647 648 this.sidebarTree.element.classList.remove("some-expandable"); 649 650 this._launcherView.detach(); 651 this.profileViews.removeChildren(); 652 this._profileViewStatusBarItemsContainer.removeChildren(); 653 654 this.removeAllListeners(); 655 656 this.recordButton.visible = true; 657 this._profileViewStatusBarItemsContainer.classList.remove("hidden"); 658 this.clearResultsButton.element.classList.remove("hidden"); 659 this.profilesItemTreeElement.select(); 660 this._showLauncherView(); 661 }, 662 663 _showLauncherView: function() 664 { 665 this.closeVisibleView(); 666 this._profileViewStatusBarItemsContainer.removeChildren(); 667 this._launcherView.show(this.profileViews); 668 this.visibleView = this._launcherView; 669 }, 670 671 /** 672 * @param {!WebInspector.ProfileType} profileType 673 */ 674 _registerProfileType: function(profileType) 675 { 676 this._launcherView.addProfileType(profileType); 677 var profileTypeSection = new WebInspector.ProfileTypeSidebarSection(this, profileType); 678 this._typeIdToSidebarSection[profileType.id] = profileTypeSection 679 this.sidebarTree.appendChild(profileTypeSection); 680 profileTypeSection.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 681 682 /** 683 * @param {!WebInspector.Event} event 684 * @this {WebInspector.ProfilesPanel} 685 */ 686 function onAddProfileHeader(event) 687 { 688 this._addProfileHeader(/** @type {!WebInspector.ProfileHeader} */ (event.data)); 689 } 690 691 /** 692 * @param {!WebInspector.Event} event 693 * @this {WebInspector.ProfilesPanel} 694 */ 695 function onRemoveProfileHeader(event) 696 { 697 this._removeProfileHeader(/** @type {!WebInspector.ProfileHeader} */ (event.data)); 698 } 699 700 /** 701 * @param {!WebInspector.Event} event 702 * @this {WebInspector.ProfilesPanel} 703 */ 704 function profileComplete(event) 705 { 706 this.showProfile(/** @type {!WebInspector.ProfileHeader} */ (event.data)); 707 } 708 709 profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this); 710 profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this); 711 profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this); 712 profileType.addEventListener(WebInspector.ProfileType.Events.ProfileComplete, profileComplete, this); 713 714 var profiles = profileType.getProfiles(); 715 for (var i = 0; i < profiles.length; i++) 716 this._addProfileHeader(profiles[i]); 717 }, 718 719 /** 720 * @param {!Event} event 721 */ 722 _handleContextMenuEvent: function(event) 723 { 724 var element = event.srcElement; 725 while (element && !element.treeElement && element !== this.element) 726 element = element.parentElement; 727 if (!element) 728 return; 729 if (element.treeElement && element.treeElement.handleContextMenuEvent) { 730 element.treeElement.handleContextMenuEvent(event, this); 731 return; 732 } 733 734 var contextMenu = new WebInspector.ContextMenu(event); 735 if (this.visibleView instanceof WebInspector.HeapSnapshotView) { 736 this.visibleView.populateContextMenu(contextMenu, event); 737 } 738 if (element !== this.element || event.srcElement === this.sidebarElement()) { 739 contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement)); 740 } 741 contextMenu.show(); 742 }, 743 744 showLoadFromFileDialog: function() 745 { 746 this._fileSelectorElement.click(); 747 }, 748 749 /** 750 * @param {!WebInspector.ProfileHeader} profile 751 */ 752 _addProfileHeader: function(profile) 753 { 754 var profileType = profile.profileType(); 755 var typeId = profileType.id; 756 this._typeIdToSidebarSection[typeId].addProfileHeader(profile); 757 if (!this.visibleView || this.visibleView === this._launcherView) 758 this.showProfile(profile); 759 }, 760 761 /** 762 * @param {!WebInspector.ProfileHeader} profile 763 */ 764 _removeProfileHeader: function(profile) 765 { 766 if (profile.profileType()._profileBeingRecorded === profile) 767 this._profileBeingRecordedRemoved(); 768 769 var i = this._indexOfViewForProfile(profile); 770 if (i !== -1) 771 this._profileToView.splice(i, 1); 772 773 var profileType = profile.profileType(); 774 var typeId = profileType.id; 775 var sectionIsEmpty = this._typeIdToSidebarSection[typeId].removeProfileHeader(profile); 776 777 // No other item will be selected if there aren't any other profiles, so 778 // make sure that view gets cleared when the last profile is removed. 779 if (sectionIsEmpty) { 780 this.profilesItemTreeElement.select(); 781 this._showLauncherView(); 782 } 783 }, 784 785 /** 786 * @param {?WebInspector.ProfileHeader} profile 787 * @return {?WebInspector.View} 788 */ 789 showProfile: function(profile) 790 { 791 if (!profile || (profile.profileType().profileBeingRecorded() === profile) && !profile.profileType().hasTemporaryView()) 792 return null; 793 794 var view = this._viewForProfile(profile); 795 if (view === this.visibleView) 796 return view; 797 798 this.closeVisibleView(); 799 800 view.show(this.profileViews); 801 802 this.visibleView = view; 803 804 var profileTypeSection = this._typeIdToSidebarSection[profile.profileType().id]; 805 var sidebarElement = profileTypeSection.sidebarElementForProfile(profile); 806 sidebarElement.revealAndSelect(); 807 808 this._profileViewStatusBarItemsContainer.removeChildren(); 809 810 var statusBarItems = view.statusBarItems; 811 if (statusBarItems) 812 for (var i = 0; i < statusBarItems.length; ++i) 813 this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); 814 815 return view; 816 }, 817 818 /** 819 * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId 820 * @param {string} perspectiveName 821 */ 822 showObject: function(snapshotObjectId, perspectiveName) 823 { 824 var heapProfiles = WebInspector.ProfileTypeRegistry.instance.heapSnapshotProfileType.getProfiles(); 825 for (var i = 0; i < heapProfiles.length; i++) { 826 var profile = heapProfiles[i]; 827 // FIXME: allow to choose snapshot if there are several options. 828 if (profile.maxJSObjectId >= snapshotObjectId) { 829 this.showProfile(profile); 830 var view = this._viewForProfile(profile); 831 view.highlightLiveObject(perspectiveName, snapshotObjectId); 832 break; 833 } 834 } 835 }, 836 837 /** 838 * @param {!WebInspector.ProfileHeader} profile 839 * @return {!WebInspector.View} 840 */ 841 _viewForProfile: function(profile) 842 { 843 var index = this._indexOfViewForProfile(profile); 844 if (index !== -1) 845 return this._profileToView[index].view; 846 var view = profile.createView(this); 847 view.element.classList.add("profile-view"); 848 this._profileToView.push({ profile: profile, view: view}); 849 return view; 850 }, 851 852 /** 853 * @param {!WebInspector.ProfileHeader} profile 854 * @return {number} 855 */ 856 _indexOfViewForProfile: function(profile) 857 { 858 for (var i = 0; i < this._profileToView.length; i++) { 859 if (this._profileToView[i].profile === profile) 860 return i; 861 } 862 return -1; 863 }, 864 865 closeVisibleView: function() 866 { 867 if (this.visibleView) 868 this.visibleView.detach(); 869 delete this.visibleView; 870 }, 871 872 /** 873 * @param {string} query 874 * @param {boolean} shouldJump 875 * @param {boolean=} jumpBackwards 876 */ 877 performSearch: function(query, shouldJump, jumpBackwards) 878 { 879 this.searchCanceled(); 880 881 var visibleView = this.visibleView; 882 if (!visibleView) 883 return; 884 885 /** 886 * @this {WebInspector.ProfilesPanel} 887 */ 888 function finishedCallback(view, searchMatches) 889 { 890 if (!searchMatches) 891 return; 892 this._searchableView.updateSearchMatchesCount(searchMatches); 893 this._searchResultsView = view; 894 if (shouldJump) { 895 if (jumpBackwards) 896 view.jumpToLastSearchResult(); 897 else 898 view.jumpToFirstSearchResult(); 899 this._searchableView.updateCurrentMatchIndex(view.currentSearchResultIndex()); 900 } 901 } 902 903 visibleView.currentQuery = query; 904 visibleView.performSearch(query, finishedCallback.bind(this)); 905 }, 906 907 jumpToNextSearchResult: function() 908 { 909 if (!this._searchResultsView) 910 return; 911 if (this._searchResultsView !== this.visibleView) 912 return; 913 this._searchResultsView.jumpToNextSearchResult(); 914 this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex()); 915 }, 916 917 jumpToPreviousSearchResult: function() 918 { 919 if (!this._searchResultsView) 920 return; 921 if (this._searchResultsView !== this.visibleView) 922 return; 923 this._searchResultsView.jumpToPreviousSearchResult(); 924 this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex()); 925 }, 926 927 searchCanceled: function() 928 { 929 if (this._searchResultsView) { 930 if (this._searchResultsView.searchCanceled) 931 this._searchResultsView.searchCanceled(); 932 this._searchResultsView.currentQuery = null; 933 this._searchResultsView = null; 934 } 935 this._searchableView.updateSearchMatchesCount(0); 936 }, 937 938 /** 939 * @param {!Event} event 940 * @param {!WebInspector.ContextMenu} contextMenu 941 * @param {!Object} target 942 */ 943 appendApplicableItems: function(event, contextMenu, target) 944 { 945 if (!(target instanceof WebInspector.RemoteObject)) 946 return; 947 948 if (WebInspector.inspectorView.currentPanel() !== this) 949 return; 950 951 var object = /** @type {!WebInspector.RemoteObject} */ (target); 952 var objectId = object.objectId; 953 if (!objectId) 954 return; 955 956 var heapProfiles = WebInspector.ProfileTypeRegistry.instance.heapSnapshotProfileType.getProfiles(); 957 if (!heapProfiles.length) 958 return; 959 960 /** 961 * @this {WebInspector.ProfilesPanel} 962 */ 963 function revealInView(viewName) 964 { 965 object.target().heapProfilerAgent().getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName)); 966 } 967 968 /** 969 * @this {WebInspector.ProfilesPanel} 970 */ 971 function didReceiveHeapObjectId(viewName, error, result) 972 { 973 if (WebInspector.inspectorView.currentPanel() !== this) 974 return; 975 if (!error) 976 this.showObject(result, viewName); 977 } 978 979 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary")); 980 }, 981 982 __proto__: WebInspector.PanelWithSidebarTree.prototype 983 } 984 985 986 /** 987 * @constructor 988 * @extends {WebInspector.SidebarSectionTreeElement} 989 * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate 990 * @param {!WebInspector.ProfileType} profileType 991 */ 992 WebInspector.ProfileTypeSidebarSection = function(dataDisplayDelegate, profileType) 993 { 994 WebInspector.SidebarSectionTreeElement.call(this, profileType.treeItemTitle, null, true); 995 this._dataDisplayDelegate = dataDisplayDelegate; 996 this._profileTreeElements = []; 997 this._profileGroups = {}; 998 this.hidden = true; 999 } 1000 1001 /** 1002 * @constructor 1003 */ 1004 WebInspector.ProfileTypeSidebarSection.ProfileGroup = function() 1005 { 1006 this.profileSidebarTreeElements = []; 1007 this.sidebarTreeElement = null; 1008 } 1009 1010 WebInspector.ProfileTypeSidebarSection.prototype = { 1011 /** 1012 * @param {!WebInspector.ProfileHeader} profile 1013 */ 1014 addProfileHeader: function(profile) 1015 { 1016 this.hidden = false; 1017 var profileType = profile.profileType(); 1018 var sidebarParent = this; 1019 var profileTreeElement = profile.createSidebarTreeElement(this._dataDisplayDelegate); 1020 this._profileTreeElements.push(profileTreeElement); 1021 1022 if (!profile.fromFile() && profileType.profileBeingRecorded() !== profile) { 1023 var profileTitle = profile.title; 1024 var group = this._profileGroups[profileTitle]; 1025 if (!group) { 1026 group = new WebInspector.ProfileTypeSidebarSection.ProfileGroup(); 1027 this._profileGroups[profileTitle] = group; 1028 } 1029 group.profileSidebarTreeElements.push(profileTreeElement); 1030 1031 var groupSize = group.profileSidebarTreeElements.length; 1032 if (groupSize === 2) { 1033 // Make a group TreeElement now that there are 2 profiles. 1034 group.sidebarTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this._dataDisplayDelegate, profile.title); 1035 1036 var firstProfileTreeElement = group.profileSidebarTreeElements[0]; 1037 // Insert at the same index for the first profile of the group. 1038 var index = this.children.indexOf(firstProfileTreeElement); 1039 this.insertChild(group.sidebarTreeElement, index); 1040 1041 // Move the first profile to the group. 1042 var selected = firstProfileTreeElement.selected; 1043 this.removeChild(firstProfileTreeElement); 1044 group.sidebarTreeElement.appendChild(firstProfileTreeElement); 1045 if (selected) 1046 firstProfileTreeElement.revealAndSelect(); 1047 1048 firstProfileTreeElement.small = true; 1049 firstProfileTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); 1050 1051 this.treeOutline.element.classList.add("some-expandable"); 1052 } 1053 1054 if (groupSize >= 2) { 1055 sidebarParent = group.sidebarTreeElement; 1056 profileTreeElement.small = true; 1057 profileTreeElement.mainTitle = WebInspector.UIString("Run %d", groupSize); 1058 } 1059 } 1060 1061 sidebarParent.appendChild(profileTreeElement); 1062 }, 1063 1064 /** 1065 * @param {!WebInspector.ProfileHeader} profile 1066 * @return {boolean} 1067 */ 1068 removeProfileHeader: function(profile) 1069 { 1070 var index = this._sidebarElementIndex(profile); 1071 if (index === -1) 1072 return false; 1073 var profileTreeElement = this._profileTreeElements[index]; 1074 this._profileTreeElements.splice(index, 1); 1075 1076 var sidebarParent = this; 1077 var group = this._profileGroups[profile.title]; 1078 if (group) { 1079 var groupElements = group.profileSidebarTreeElements; 1080 groupElements.splice(groupElements.indexOf(profileTreeElement), 1); 1081 if (groupElements.length === 1) { 1082 // Move the last profile out of its group and remove the group. 1083 var pos = sidebarParent.children.indexOf(group.sidebarTreeElement); 1084 this.insertChild(groupElements[0], pos); 1085 groupElements[0].small = false; 1086 groupElements[0].mainTitle = group.sidebarTreeElement.title; 1087 this.removeChild(group.sidebarTreeElement); 1088 } 1089 if (groupElements.length !== 0) 1090 sidebarParent = group.sidebarTreeElement; 1091 } 1092 sidebarParent.removeChild(profileTreeElement); 1093 profileTreeElement.dispose(); 1094 1095 if (this.children.length) 1096 return false; 1097 this.hidden = true; 1098 return true; 1099 }, 1100 1101 /** 1102 * @param {!WebInspector.ProfileHeader} profile 1103 * @return {?WebInspector.ProfileSidebarTreeElement} 1104 */ 1105 sidebarElementForProfile: function(profile) 1106 { 1107 var index = this._sidebarElementIndex(profile); 1108 return index === -1 ? null : this._profileTreeElements[index]; 1109 }, 1110 1111 /** 1112 * @param {!WebInspector.ProfileHeader} profile 1113 * @return {number} 1114 */ 1115 _sidebarElementIndex: function(profile) 1116 { 1117 var elements = this._profileTreeElements; 1118 for (var i = 0; i < elements.length; i++) { 1119 if (elements[i].profile === profile) 1120 return i; 1121 } 1122 return -1; 1123 }, 1124 1125 __proto__: WebInspector.SidebarSectionTreeElement.prototype 1126 } 1127 1128 1129 /** 1130 * @constructor 1131 * @implements {WebInspector.ContextMenu.Provider} 1132 */ 1133 WebInspector.ProfilesPanel.ContextMenuProvider = function() 1134 { 1135 } 1136 1137 WebInspector.ProfilesPanel.ContextMenuProvider.prototype = { 1138 /** 1139 * @param {!Event} event 1140 * @param {!WebInspector.ContextMenu} contextMenu 1141 * @param {!Object} target 1142 */ 1143 appendApplicableItems: function(event, contextMenu, target) 1144 { 1145 WebInspector.inspectorView.panel("profiles").appendApplicableItems(event, contextMenu, target); 1146 } 1147 } 1148 1149 /** 1150 * @constructor 1151 * @extends {WebInspector.SidebarTreeElement} 1152 * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate 1153 * @param {!WebInspector.ProfileHeader} profile 1154 * @param {string} className 1155 */ 1156 WebInspector.ProfileSidebarTreeElement = function(dataDisplayDelegate, profile, className) 1157 { 1158 this._dataDisplayDelegate = dataDisplayDelegate; 1159 this.profile = profile; 1160 WebInspector.SidebarTreeElement.call(this, className, profile.title, "", profile, false); 1161 this.refreshTitles(); 1162 profile.addEventListener(WebInspector.ProfileHeader.Events.UpdateStatus, this._updateStatus, this); 1163 if (profile.canSaveToFile()) 1164 this._createSaveLink(); 1165 else 1166 profile.addEventListener(WebInspector.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this); 1167 } 1168 1169 WebInspector.ProfileSidebarTreeElement.prototype = { 1170 _createSaveLink: function() 1171 { 1172 this._saveLinkElement = this.titleContainer.createChild("span", "save-link"); 1173 this._saveLinkElement.textContent = WebInspector.UIString("Save"); 1174 this._saveLinkElement.addEventListener("click", this._saveProfile.bind(this), false); 1175 }, 1176 1177 _onProfileReceived: function(event) 1178 { 1179 this._createSaveLink(); 1180 }, 1181 1182 /** 1183 * @param {!WebInspector.Event} event 1184 */ 1185 _updateStatus: function(event) 1186 { 1187 var statusUpdate = event.data; 1188 if (statusUpdate.subtitle !== null) 1189 this.subtitle = statusUpdate.subtitle; 1190 if (typeof statusUpdate.wait === "boolean") 1191 this.wait = statusUpdate.wait; 1192 this.refreshTitles(); 1193 }, 1194 1195 dispose: function() 1196 { 1197 this.profile.removeEventListener(WebInspector.ProfileHeader.Events.UpdateStatus, this._updateStatus, this); 1198 this.profile.removeEventListener(WebInspector.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this); 1199 }, 1200 1201 /** 1202 * @return {boolean} 1203 */ 1204 onselect: function() 1205 { 1206 this._dataDisplayDelegate.showProfile(this.profile); 1207 return true; 1208 }, 1209 1210 /** 1211 * @return {boolean} 1212 */ 1213 ondelete: function() 1214 { 1215 this.profile.profileType().removeProfile(this.profile); 1216 return true; 1217 }, 1218 1219 /** 1220 * @param {!Event} event 1221 * @param {!WebInspector.ProfilesPanel} panel 1222 */ 1223 handleContextMenuEvent: function(event, panel) 1224 { 1225 var profile = this.profile; 1226 var contextMenu = new WebInspector.ContextMenu(event); 1227 // FIXME: use context menu provider 1228 contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement)); 1229 if (profile.canSaveToFile()) 1230 contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile)); 1231 contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this)); 1232 contextMenu.show(); 1233 }, 1234 1235 _saveProfile: function(event) 1236 { 1237 this.profile.saveToFile(); 1238 }, 1239 1240 __proto__: WebInspector.SidebarTreeElement.prototype 1241 } 1242 1243 /** 1244 * @constructor 1245 * @extends {WebInspector.SidebarTreeElement} 1246 * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate 1247 * @param {string} title 1248 * @param {string=} subtitle 1249 */ 1250 WebInspector.ProfileGroupSidebarTreeElement = function(dataDisplayDelegate, title, subtitle) 1251 { 1252 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); 1253 this._dataDisplayDelegate = dataDisplayDelegate; 1254 } 1255 1256 WebInspector.ProfileGroupSidebarTreeElement.prototype = { 1257 /** 1258 * @return {boolean} 1259 */ 1260 onselect: function() 1261 { 1262 var hasChildren = this.children.length > 0; 1263 if (hasChildren) 1264 this._dataDisplayDelegate.showProfile(this.children[this.children.length - 1].profile); 1265 return hasChildren; 1266 }, 1267 1268 __proto__: WebInspector.SidebarTreeElement.prototype 1269 } 1270 1271 /** 1272 * @constructor 1273 * @extends {WebInspector.SidebarTreeElement} 1274 * @param {!WebInspector.ProfilesPanel} panel 1275 */ 1276 WebInspector.ProfilesSidebarTreeElement = function(panel) 1277 { 1278 this._panel = panel; 1279 this.small = false; 1280 1281 WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false); 1282 } 1283 1284 WebInspector.ProfilesSidebarTreeElement.prototype = { 1285 /** 1286 * @return {boolean} 1287 */ 1288 onselect: function() 1289 { 1290 this._panel._showLauncherView(); 1291 return true; 1292 }, 1293 1294 get selectable() 1295 { 1296 return true; 1297 }, 1298 1299 __proto__: WebInspector.SidebarTreeElement.prototype 1300 } 1301