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