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 26const UserInitiatedProfileName = "org.webkit.profiles.user-initiated"; 27 28/** 29 * @constructor 30 * @extends {WebInspector.Object} 31 * @param {string} id 32 * @param {string} name 33 */ 34WebInspector.ProfileType = function(id, name) 35{ 36 this._id = id; 37 this._name = name; 38 /** @type {!Array.<!WebInspector.ProfileHeader>} */ 39 this._profiles = []; 40 this._profilesIdMap = {}; 41 /** @type {WebInspector.SidebarSectionTreeElement} */ 42 this.treeElement = null; 43} 44 45WebInspector.ProfileType.Events = { 46 AddProfileHeader: "add-profile-header", 47 RemoveProfileHeader: "remove-profile-header", 48 ProgressUpdated: "progress-updated", 49 ViewUpdated: "view-updated" 50} 51 52WebInspector.ProfileType.prototype = { 53 statusBarItems: function() 54 { 55 return []; 56 }, 57 58 get buttonTooltip() 59 { 60 return ""; 61 }, 62 63 get id() 64 { 65 return this._id; 66 }, 67 68 get treeItemTitle() 69 { 70 return this._name; 71 }, 72 73 get name() 74 { 75 return this._name; 76 }, 77 78 /** 79 * @return {boolean} 80 */ 81 buttonClicked: function() 82 { 83 return false; 84 }, 85 86 get description() 87 { 88 return ""; 89 }, 90 91 /** 92 * @return {boolean} 93 */ 94 isInstantProfile: function() 95 { 96 return false; 97 }, 98 99 /** 100 * @return {!Array.<!WebInspector.ProfileHeader>} 101 */ 102 getProfiles: function() 103 { 104 return this._profiles.filter(function(profile) { return !profile.isTemporary; }); 105 }, 106 107 /** 108 * @return {Element} 109 */ 110 decorationElement: function() 111 { 112 return null; 113 }, 114 115 /** 116 * @nosideeffects 117 * @param {number} uid 118 * @return {WebInspector.ProfileHeader} 119 */ 120 getProfile: function(uid) 121 { 122 return this._profilesIdMap[this._makeKey(uid)]; 123 }, 124 125 // Must be implemented by subclasses. 126 /** 127 * @param {string=} title 128 * @return {!WebInspector.ProfileHeader} 129 */ 130 createTemporaryProfile: function(title) 131 { 132 throw new Error("Needs implemented."); 133 }, 134 135 /** 136 * @param {ProfilerAgent.ProfileHeader} profile 137 * @return {!WebInspector.ProfileHeader} 138 */ 139 createProfile: function(profile) 140 { 141 throw new Error("Not supported for " + this._name + " profiles."); 142 }, 143 144 /** 145 * @nosideeffects 146 * @param {number} id 147 * @return {string} 148 */ 149 _makeKey: function(id) 150 { 151 return id + '/' + escape(this.id); 152 }, 153 154 /** 155 * @param {!WebInspector.ProfileHeader} profile 156 */ 157 addProfile: function(profile) 158 { 159 this._profiles.push(profile); 160 // FIXME: uid only based key should be enough. 161 this._profilesIdMap[this._makeKey(profile.uid)] = profile; 162 this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile); 163 }, 164 165 /** 166 * @param {!WebInspector.ProfileHeader} profile 167 */ 168 removeProfile: function(profile) 169 { 170 for (var i = 0; i < this._profiles.length; ++i) { 171 if (this._profiles[i].uid === profile.uid) { 172 this._profiles.splice(i, 1); 173 break; 174 } 175 } 176 delete this._profilesIdMap[this._makeKey(profile.uid)]; 177 }, 178 179 /** 180 * @nosideeffects 181 * @return {WebInspector.ProfileHeader} 182 */ 183 findTemporaryProfile: function() 184 { 185 for (var i = 0; i < this._profiles.length; ++i) { 186 if (this._profiles[i].isTemporary) 187 return this._profiles[i]; 188 } 189 return null; 190 }, 191 192 _reset: function() 193 { 194 var profiles = this._profiles.slice(0); 195 for (var i = 0; i < profiles.length; ++i) { 196 var profile = profiles[i]; 197 var view = profile.existingView(); 198 if (view) { 199 view.detach(); 200 if ("dispose" in view) 201 view.dispose(); 202 } 203 this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile); 204 } 205 this.treeElement.removeChildren(); 206 this._profiles = []; 207 this._profilesIdMap = {}; 208 }, 209 210 /** 211 * @param {function(this:WebInspector.ProfileType, ?string, !Array.<!ProfilerAgent.ProfileHeader>)} populateCallback 212 */ 213 _requestProfilesFromBackend: function(populateCallback) 214 { 215 }, 216 217 _populateProfiles: function() 218 { 219 /** 220 * @param {?string} error 221 * @param {!Array.<!ProfilerAgent.ProfileHeader>} profileHeaders 222 */ 223 function populateCallback(error, profileHeaders) { 224 if (error) 225 return; 226 profileHeaders.sort(function(a, b) { return a.uid - b.uid; }); 227 var count = profileHeaders.length; 228 for (var i = 0; i < count; ++i) 229 this.addProfile(this.createProfile(profileHeaders[i])); 230 } 231 this._requestProfilesFromBackend(populateCallback.bind(this)); 232 }, 233 234 __proto__: WebInspector.Object.prototype 235} 236 237/** 238 * @constructor 239 * @param {!WebInspector.ProfileType} profileType 240 * @param {string} title 241 * @param {number=} uid 242 */ 243WebInspector.ProfileHeader = function(profileType, title, uid) 244{ 245 this._profileType = profileType; 246 this.title = title; 247 this.isTemporary = uid === undefined; 248 this.uid = this.isTemporary ? -1 : uid; 249 this._fromFile = false; 250} 251 252WebInspector.ProfileHeader.prototype = { 253 /** 254 * @return {!WebInspector.ProfileType} 255 */ 256 profileType: function() 257 { 258 return this._profileType; 259 }, 260 261 /** 262 * Must be implemented by subclasses. 263 * @return {WebInspector.ProfileSidebarTreeElement} 264 */ 265 createSidebarTreeElement: function() 266 { 267 throw new Error("Needs implemented."); 268 }, 269 270 /** 271 * @return {?WebInspector.View} 272 */ 273 existingView: function() 274 { 275 return this._view; 276 }, 277 278 /** 279 * @param {!WebInspector.ProfilesPanel} panel 280 * @return {!WebInspector.View} 281 */ 282 view: function(panel) 283 { 284 if (!this._view) 285 this._view = this.createView(panel); 286 return this._view; 287 }, 288 289 /** 290 * @param {!WebInspector.ProfilesPanel} panel 291 * @return {!WebInspector.View} 292 */ 293 createView: function(panel) 294 { 295 throw new Error("Not implemented."); 296 }, 297 298 dispose: function() 299 { 300 }, 301 302 /** 303 * @param {Function} callback 304 */ 305 load: function(callback) 306 { 307 }, 308 309 /** 310 * @return {boolean} 311 */ 312 canSaveToFile: function() 313 { 314 return false; 315 }, 316 317 saveToFile: function() 318 { 319 throw new Error("Needs implemented"); 320 }, 321 322 /** 323 * @param {File} file 324 */ 325 loadFromFile: function(file) 326 { 327 throw new Error("Needs implemented"); 328 }, 329 330 /** 331 * @return {boolean} 332 */ 333 fromFile: function() 334 { 335 return this._fromFile; 336 } 337} 338 339/** 340 * @constructor 341 * @extends {WebInspector.Panel} 342 * @implements {WebInspector.ContextMenu.Provider} 343 * @param {string=} name 344 * @param {WebInspector.ProfileType=} type 345 */ 346WebInspector.ProfilesPanel = function(name, type) 347{ 348 // If the name is not specified the ProfilesPanel works in multi-profile mode. 349 var singleProfileMode = typeof name !== "undefined"; 350 name = name || "profiles"; 351 WebInspector.Panel.call(this, name); 352 this.registerRequiredCSS("panelEnablerView.css"); 353 this.registerRequiredCSS("heapProfiler.css"); 354 this.registerRequiredCSS("profilesPanel.css"); 355 356 this.createSidebarViewWithTree(); 357 358 this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this); 359 this.sidebarTree.appendChild(this.profilesItemTreeElement); 360 361 this._singleProfileMode = singleProfileMode; 362 this._profileTypesByIdMap = {}; 363 364 var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel."); 365 var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower."); 366 var panelEnablerButton = WebInspector.UIString("Enable Profiling"); 367 this.panelEnablerView = new WebInspector.PanelEnablerView(name, panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); 368 this.panelEnablerView.addEventListener("enable clicked", this.enableProfiler, this); 369 370 this.profileViews = document.createElement("div"); 371 this.profileViews.id = "profile-views"; 372 this.splitView.mainElement.appendChild(this.profileViews); 373 374 this._statusBarButtons = []; 375 376 this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); 377 if (Capabilities.profilerCausesRecompilation) { 378 this._statusBarButtons.push(this.enableToggleButton); 379 this.enableToggleButton.addEventListener("click", this._onToggleProfiling, this); 380 } 381 this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item"); 382 this.recordButton.addEventListener("click", this.toggleRecordButton, this); 383 this._statusBarButtons.push(this.recordButton); 384 385 this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item"); 386 this.clearResultsButton.addEventListener("click", this._clearProfiles, this); 387 this._statusBarButtons.push(this.clearResultsButton); 388 389 this._profileTypeStatusBarItemsContainer = document.createElement("div"); 390 this._profileTypeStatusBarItemsContainer.className = "status-bar-items"; 391 392 this._profileViewStatusBarItemsContainer = document.createElement("div"); 393 this._profileViewStatusBarItemsContainer.className = "status-bar-items"; 394 395 this._profilerEnabled = !Capabilities.profilerCausesRecompilation; 396 397 if (singleProfileMode) { 398 this._launcherView = this._createLauncherView(); 399 this._registerProfileType(/** @type {!WebInspector.ProfileType} */ (type)); 400 this._selectedProfileType = type; 401 this._updateProfileTypeSpecificUI(); 402 } else { 403 this._launcherView = new WebInspector.MultiProfileLauncherView(this); 404 this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this); 405 406 this._registerProfileType(new WebInspector.CPUProfileType()); 407 if (!WebInspector.WorkerManager.isWorkerFrontend()) 408 this._registerProfileType(new WebInspector.CSSSelectorProfileType()); 409 if (Capabilities.heapProfilerPresent) 410 this._registerProfileType(new WebInspector.HeapSnapshotProfileType()); 411 if (!WebInspector.WorkerManager.isWorkerFrontend() && WebInspector.experimentsSettings.canvasInspection.isEnabled()) 412 this._registerProfileType(new WebInspector.CanvasProfileType()); 413 } 414 415 this._reset(); 416 417 this._createFileSelectorElement(); 418 this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 419 420 WebInspector.ContextMenu.registerProvider(this); 421} 422 423WebInspector.ProfilesPanel.prototype = { 424 _createFileSelectorElement: function() 425 { 426 if (this._fileSelectorElement) 427 this.element.removeChild(this._fileSelectorElement); 428 this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this)); 429 this.element.appendChild(this._fileSelectorElement); 430 }, 431 432 /** 433 * @return {!WebInspector.ProfileLauncherView} 434 */ 435 _createLauncherView: function() 436 { 437 return new WebInspector.ProfileLauncherView(this); 438 }, 439 440 /** 441 * @param {!File} file 442 */ 443 _loadFromFile: function(file) 444 { 445 if (!file.name.endsWith(".heapsnapshot")) { 446 WebInspector.log(WebInspector.UIString("Only heap snapshots from files with extension '.heapsnapshot' can be loaded.")); 447 return; 448 } 449 450 var profileType = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId); 451 if (!!profileType.findTemporaryProfile()) { 452 WebInspector.log(WebInspector.UIString("Can't load profile when other profile is recording.")); 453 return; 454 } 455 456 var temporaryProfile = profileType.createTemporaryProfile(WebInspector.ProfilesPanelDescriptor.UserInitiatedProfileName + "." + file.name); 457 profileType.addProfile(temporaryProfile); 458 459 temporaryProfile._fromFile = true; 460 temporaryProfile.loadFromFile(file); 461 this._createFileSelectorElement(); 462 }, 463 464 statusBarItems: function() 465 { 466 return this._statusBarButtons.select("element").concat(this._profileTypeStatusBarItemsContainer, this._profileViewStatusBarItemsContainer); 467 }, 468 469 toggleRecordButton: function() 470 { 471 var isProfiling = this._selectedProfileType.buttonClicked(); 472 this.setRecordingProfile(this._selectedProfileType.id, isProfiling); 473 }, 474 475 _populateAllProfiles: function() 476 { 477 if (!this._profilerEnabled || this._profilesWereRequested) 478 return; 479 this._profilesWereRequested = true; 480 for (var typeId in this._profileTypesByIdMap) 481 this._profileTypesByIdMap[typeId]._populateProfiles(); 482 }, 483 484 wasShown: function() 485 { 486 WebInspector.Panel.prototype.wasShown.call(this); 487 this._populateAllProfiles(); 488 }, 489 490 _profilerWasEnabled: function() 491 { 492 if (this._profilerEnabled) 493 return; 494 495 this._profilerEnabled = true; 496 497 this._reset(); 498 if (this.isShowing()) 499 this._populateAllProfiles(); 500 }, 501 502 _profilerWasDisabled: function() 503 { 504 if (!this._profilerEnabled) 505 return; 506 507 this._profilerEnabled = false; 508 this._reset(); 509 }, 510 511 /** 512 * @param {WebInspector.Event} event 513 */ 514 _onProfileTypeSelected: function(event) 515 { 516 this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data); 517 this._updateProfileTypeSpecificUI(); 518 }, 519 520 _updateProfileTypeSpecificUI: function() 521 { 522 this.recordButton.title = this._selectedProfileType.buttonTooltip; 523 524 this._profileTypeStatusBarItemsContainer.removeChildren(); 525 var statusBarItems = this._selectedProfileType.statusBarItems(); 526 if (statusBarItems) { 527 for (var i = 0; i < statusBarItems.length; ++i) 528 this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]); 529 } 530 this._resize(this.splitView.sidebarWidth()); 531 }, 532 533 _reset: function() 534 { 535 WebInspector.Panel.prototype.reset.call(this); 536 537 for (var typeId in this._profileTypesByIdMap) 538 this._profileTypesByIdMap[typeId]._reset(); 539 540 delete this.visibleView; 541 delete this.currentQuery; 542 this.searchCanceled(); 543 544 this._profileGroups = {}; 545 this._profilesWereRequested = false; 546 this.recordButton.toggled = false; 547 if (this._selectedProfileType) 548 this.recordButton.title = this._selectedProfileType.buttonTooltip; 549 this._launcherView.profileFinished(); 550 551 this.sidebarTreeElement.removeStyleClass("some-expandable"); 552 553 this.profileViews.removeChildren(); 554 this._profileViewStatusBarItemsContainer.removeChildren(); 555 556 this.removeAllListeners(); 557 558 this._updateInterface(); 559 this.profilesItemTreeElement.select(); 560 this._showLauncherView(); 561 }, 562 563 _showLauncherView: function() 564 { 565 this.closeVisibleView(); 566 this._profileViewStatusBarItemsContainer.removeChildren(); 567 this._launcherView.show(this.splitView.mainElement); 568 this.visibleView = this._launcherView; 569 }, 570 571 _clearProfiles: function() 572 { 573 ProfilerAgent.clearProfiles(); 574 HeapProfilerAgent.clearProfiles(); 575 this._reset(); 576 }, 577 578 _garbageCollectButtonClicked: function() 579 { 580 HeapProfilerAgent.collectGarbage(); 581 }, 582 583 /** 584 * @param {!WebInspector.ProfileType} profileType 585 */ 586 _registerProfileType: function(profileType) 587 { 588 this._profileTypesByIdMap[profileType.id] = profileType; 589 this._launcherView.addProfileType(profileType); 590 profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.treeItemTitle, null, true); 591 profileType.treeElement.hidden = !this._singleProfileMode; 592 this.sidebarTree.appendChild(profileType.treeElement); 593 profileType.treeElement.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true); 594 function onAddProfileHeader(event) 595 { 596 this._addProfileHeader(event.data); 597 } 598 function onRemoveProfileHeader(event) 599 { 600 this._removeProfileHeader(event.data); 601 } 602 function onProgressUpdated(event) 603 { 604 this._reportProfileProgress(event.data.profile, event.data.done, event.data.total); 605 } 606 profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this); 607 profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this); 608 profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this); 609 profileType.addEventListener(WebInspector.ProfileType.Events.ProgressUpdated, onProgressUpdated, this); 610 }, 611 612 /** 613 * @param {Event} event 614 */ 615 _handleContextMenuEvent: function(event) 616 { 617 var element = event.srcElement; 618 while (element && !element.treeElement && element !== this.element) 619 element = element.parentElement; 620 if (!element) 621 return; 622 if (element.treeElement && element.treeElement.handleContextMenuEvent) { 623 element.treeElement.handleContextMenuEvent(event, this); 624 return; 625 } 626 if (element !== this.element || event.srcElement === this.sidebarElement) { 627 var contextMenu = new WebInspector.ContextMenu(event); 628 if (this.visibleView instanceof WebInspector.HeapSnapshotView) 629 this.visibleView.populateContextMenu(contextMenu, event); 630 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load heap snapshot\u2026" : "Load Heap Snapshot\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement)); 631 contextMenu.show(); 632 } 633 634 }, 635 636 /** 637 * @nosideeffects 638 * @param {string} text 639 * @param {string} profileTypeId 640 * @return {string} 641 */ 642 _makeTitleKey: function(text, profileTypeId) 643 { 644 return escape(text) + '/' + escape(profileTypeId); 645 }, 646 647 /** 648 * @param {!WebInspector.ProfileHeader} profile 649 */ 650 _addProfileHeader: function(profile) 651 { 652 if (!profile.isTemporary) 653 this._removeTemporaryProfile(profile.profileType().id); 654 655 var profileType = profile.profileType(); 656 var typeId = profileType.id; 657 var sidebarParent = profileType.treeElement; 658 sidebarParent.hidden = false; 659 var small = false; 660 var alternateTitle; 661 662 if (!WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(profile.title) && !profile.isTemporary) { 663 var profileTitleKey = this._makeTitleKey(profile.title, typeId); 664 if (!(profileTitleKey in this._profileGroups)) 665 this._profileGroups[profileTitleKey] = []; 666 667 var group = this._profileGroups[profileTitleKey]; 668 group.push(profile); 669 if (group.length === 2) { 670 // Make a group TreeElement now that there are 2 profiles. 671 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this, profile.title); 672 673 // Insert at the same index for the first profile of the group. 674 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement); 675 sidebarParent.insertChild(group._profilesTreeElement, index); 676 677 // Move the first profile to the group. 678 var selected = group[0]._profilesTreeElement.selected; 679 sidebarParent.removeChild(group[0]._profilesTreeElement); 680 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); 681 if (selected) 682 group[0]._profilesTreeElement.revealAndSelect(); 683 684 group[0]._profilesTreeElement.small = true; 685 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); 686 687 this.sidebarTreeElement.addStyleClass("some-expandable"); 688 } 689 690 if (group.length >= 2) { 691 sidebarParent = group._profilesTreeElement; 692 alternateTitle = WebInspector.UIString("Run %d", group.length); 693 small = true; 694 } 695 } 696 697 var profileTreeElement = profile.createSidebarTreeElement(); 698 profile.sidebarElement = profileTreeElement; 699 profileTreeElement.small = small; 700 if (alternateTitle) 701 profileTreeElement.mainTitle = alternateTitle; 702 profile._profilesTreeElement = profileTreeElement; 703 704 sidebarParent.appendChild(profileTreeElement); 705 if (!profile.isTemporary) { 706 if (!this.visibleView) 707 this._showProfile(profile); 708 this.dispatchEventToListeners("profile added", { 709 type: typeId 710 }); 711 } 712 }, 713 714 /** 715 * @param {!WebInspector.ProfileHeader} profile 716 */ 717 _removeProfileHeader: function(profile) 718 { 719 profile.dispose(); 720 profile.profileType().removeProfile(profile); 721 722 var sidebarParent = profile.profileType().treeElement; 723 var profileTitleKey = this._makeTitleKey(profile.title, profile.profileType().id); 724 var group = this._profileGroups[profileTitleKey]; 725 if (group) { 726 group.splice(group.indexOf(profile), 1); 727 if (group.length === 1) { 728 // Move the last profile out of its group and remove the group. 729 var index = sidebarParent.children.indexOf(group._profilesTreeElement); 730 sidebarParent.insertChild(group[0]._profilesTreeElement, index); 731 group[0]._profilesTreeElement.small = false; 732 group[0]._profilesTreeElement.mainTitle = group[0].title; 733 sidebarParent.removeChild(group._profilesTreeElement); 734 } 735 if (group.length !== 0) 736 sidebarParent = group._profilesTreeElement; 737 else 738 delete this._profileGroups[profileTitleKey]; 739 } 740 sidebarParent.removeChild(profile._profilesTreeElement); 741 742 // No other item will be selected if there aren't any other profiles, so 743 // make sure that view gets cleared when the last profile is removed. 744 if (!sidebarParent.children.length) { 745 this.profilesItemTreeElement.select(); 746 this._showLauncherView(); 747 sidebarParent.hidden = !this._singleProfileMode; 748 } 749 }, 750 751 /** 752 * @param {!WebInspector.ProfileHeader} profile 753 */ 754 _showProfile: function(profile) 755 { 756 if (!profile || profile.isTemporary) 757 return; 758 759 var view = profile.view(this); 760 if (view === this.visibleView) 761 return; 762 763 this.closeVisibleView(); 764 765 view.show(this.profileViews); 766 767 profile._profilesTreeElement._suppressOnSelect = true; 768 profile._profilesTreeElement.revealAndSelect(); 769 delete profile._profilesTreeElement._suppressOnSelect; 770 771 this.visibleView = view; 772 773 this._profileViewStatusBarItemsContainer.removeChildren(); 774 775 var statusBarItems = view.statusBarItems(); 776 if (statusBarItems) 777 for (var i = 0; i < statusBarItems.length; ++i) 778 this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); 779 }, 780 781 /** 782 * @param {HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId 783 * @param {string} viewName 784 */ 785 showObject: function(snapshotObjectId, viewName) 786 { 787 var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles(); 788 for (var i = 0; i < heapProfiles.length; i++) { 789 var profile = heapProfiles[i]; 790 // FIXME: allow to choose snapshot if there are several options. 791 if (profile.maxJSObjectId >= snapshotObjectId) { 792 this._showProfile(profile); 793 var view = profile.view(this); 794 view.changeView(viewName, function() { 795 view.dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId); 796 }); 797 break; 798 } 799 } 800 }, 801 802 /** 803 * @param {string} typeId 804 */ 805 _createTemporaryProfile: function(typeId) 806 { 807 var type = this.getProfileType(typeId); 808 if (!type.findTemporaryProfile()) 809 type.addProfile(type.createTemporaryProfile()); 810 }, 811 812 /** 813 * @param {string} typeId 814 */ 815 _removeTemporaryProfile: function(typeId) 816 { 817 var temporaryProfile = this.getProfileType(typeId).findTemporaryProfile(); 818 if (!!temporaryProfile) 819 this._removeProfileHeader(temporaryProfile); 820 }, 821 822 /** 823 * @param {string} typeId 824 * @param {number} uid 825 */ 826 getProfile: function(typeId, uid) 827 { 828 return this.getProfileType(typeId).getProfile(uid); 829 }, 830 831 /** 832 * @param {WebInspector.View} view 833 */ 834 showView: function(view) 835 { 836 this._showProfile(view.profile); 837 }, 838 839 /** 840 * @param {string} typeId 841 */ 842 getProfileType: function(typeId) 843 { 844 return this._profileTypesByIdMap[typeId]; 845 }, 846 847 /** 848 * @param {string} typeId 849 * @param {string} uid 850 */ 851 showProfile: function(typeId, uid) 852 { 853 this._showProfile(this.getProfile(typeId, Number(uid))); 854 }, 855 856 closeVisibleView: function() 857 { 858 if (this.visibleView) 859 this.visibleView.detach(); 860 delete this.visibleView; 861 }, 862 863 /** 864 * @param {string} query 865 */ 866 performSearch: function(query) 867 { 868 this.searchCanceled(); 869 870 var searchableViews = this._searchableViews(); 871 if (!searchableViews || !searchableViews.length) 872 return; 873 874 var visibleView = this.visibleView; 875 876 var matchesCountUpdateTimeout = null; 877 878 function updateMatchesCount() 879 { 880 WebInspector.searchController.updateSearchMatchesCount(this._totalSearchMatches, this); 881 WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this); 882 matchesCountUpdateTimeout = null; 883 } 884 885 function updateMatchesCountSoon() 886 { 887 if (matchesCountUpdateTimeout) 888 return; 889 // Update the matches count every half-second so it doesn't feel twitchy. 890 matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500); 891 } 892 893 function finishedCallback(view, searchMatches) 894 { 895 if (!searchMatches) 896 return; 897 898 this._totalSearchMatches += searchMatches; 899 this._searchResults.push(view); 900 901 if (this.searchMatchFound) 902 this.searchMatchFound(view, searchMatches); 903 904 updateMatchesCountSoon.call(this); 905 906 if (view === visibleView) 907 view.jumpToFirstSearchResult(); 908 } 909 910 var i = 0; 911 var panel = this; 912 var boundFinishedCallback = finishedCallback.bind(this); 913 var chunkIntervalIdentifier = null; 914 915 // Split up the work into chunks so we don't block the 916 // UI thread while processing. 917 918 function processChunk() 919 { 920 var view = searchableViews[i]; 921 922 if (++i >= searchableViews.length) { 923 if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier) 924 delete panel._currentSearchChunkIntervalIdentifier; 925 clearInterval(chunkIntervalIdentifier); 926 } 927 928 if (!view) 929 return; 930 931 view.currentQuery = query; 932 view.performSearch(query, boundFinishedCallback); 933 } 934 935 processChunk(); 936 937 chunkIntervalIdentifier = setInterval(processChunk, 25); 938 this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier; 939 }, 940 941 jumpToNextSearchResult: function() 942 { 943 if (!this.showView || !this._searchResults || !this._searchResults.length) 944 return; 945 946 var showFirstResult = false; 947 948 this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); 949 if (this._currentSearchResultIndex === -1) { 950 this._currentSearchResultIndex = 0; 951 showFirstResult = true; 952 } 953 954 var currentView = this._searchResults[this._currentSearchResultIndex]; 955 956 if (currentView.showingLastSearchResult()) { 957 if (++this._currentSearchResultIndex >= this._searchResults.length) 958 this._currentSearchResultIndex = 0; 959 currentView = this._searchResults[this._currentSearchResultIndex]; 960 showFirstResult = true; 961 } 962 963 WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this); 964 965 if (currentView !== this.visibleView) { 966 this.showView(currentView); 967 WebInspector.searchController.showSearchField(); 968 } 969 970 if (showFirstResult) 971 currentView.jumpToFirstSearchResult(); 972 else 973 currentView.jumpToNextSearchResult(); 974 }, 975 976 jumpToPreviousSearchResult: function() 977 { 978 if (!this.showView || !this._searchResults || !this._searchResults.length) 979 return; 980 981 var showLastResult = false; 982 983 this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView); 984 if (this._currentSearchResultIndex === -1) { 985 this._currentSearchResultIndex = 0; 986 showLastResult = true; 987 } 988 989 var currentView = this._searchResults[this._currentSearchResultIndex]; 990 991 if (currentView.showingFirstSearchResult()) { 992 if (--this._currentSearchResultIndex < 0) 993 this._currentSearchResultIndex = (this._searchResults.length - 1); 994 currentView = this._searchResults[this._currentSearchResultIndex]; 995 showLastResult = true; 996 } 997 998 WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this); 999 1000 if (currentView !== this.visibleView) { 1001 this.showView(currentView); 1002 WebInspector.searchController.showSearchField(); 1003 } 1004 1005 if (showLastResult) 1006 currentView.jumpToLastSearchResult(); 1007 else 1008 currentView.jumpToPreviousSearchResult(); 1009 }, 1010 1011 /** 1012 * @return {!Array.<!WebInspector.ProfileHeader>} 1013 */ 1014 _getAllProfiles: function() 1015 { 1016 var profiles = []; 1017 for (var typeId in this._profileTypesByIdMap) 1018 profiles = profiles.concat(this._profileTypesByIdMap[typeId].getProfiles()); 1019 return profiles; 1020 }, 1021 1022 /** 1023 * @return {!Array.<!WebInspector.View>} 1024 */ 1025 _searchableViews: function() 1026 { 1027 var profiles = this._getAllProfiles(); 1028 var searchableViews = []; 1029 for (var i = 0; i < profiles.length; ++i) { 1030 var view = profiles[i].view(this); 1031 if (view.performSearch) 1032 searchableViews.push(view) 1033 } 1034 var index = searchableViews.indexOf(this.visibleView); 1035 if (index > 0) { 1036 // Move visibleView to the first position. 1037 searchableViews[index] = searchableViews[0]; 1038 searchableViews[0] = this.visibleView; 1039 } 1040 return searchableViews; 1041 }, 1042 1043 searchMatchFound: function(view, matches) 1044 { 1045 view.profile._profilesTreeElement.searchMatches = matches; 1046 }, 1047 1048 searchCanceled: function() 1049 { 1050 if (this._searchResults) { 1051 for (var i = 0; i < this._searchResults.length; ++i) { 1052 var view = this._searchResults[i]; 1053 if (view.searchCanceled) 1054 view.searchCanceled(); 1055 delete view.currentQuery; 1056 } 1057 } 1058 1059 WebInspector.Panel.prototype.searchCanceled.call(this); 1060 1061 if (this._currentSearchChunkIntervalIdentifier) { 1062 clearInterval(this._currentSearchChunkIntervalIdentifier); 1063 delete this._currentSearchChunkIntervalIdentifier; 1064 } 1065 1066 this._totalSearchMatches = 0; 1067 this._currentSearchResultIndex = 0; 1068 this._searchResults = []; 1069 1070 var profiles = this._getAllProfiles(); 1071 for (var i = 0; i < profiles.length; ++i) 1072 profiles[i]._profilesTreeElement.searchMatches = 0; 1073 }, 1074 1075 _updateInterface: function() 1076 { 1077 // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change. 1078 if (this._profilerEnabled) { 1079 this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); 1080 this.enableToggleButton.toggled = true; 1081 this.recordButton.visible = true; 1082 this._profileViewStatusBarItemsContainer.removeStyleClass("hidden"); 1083 this.clearResultsButton.element.removeStyleClass("hidden"); 1084 this.panelEnablerView.detach(); 1085 } else { 1086 this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); 1087 this.enableToggleButton.toggled = false; 1088 this.recordButton.visible = false; 1089 this._profileViewStatusBarItemsContainer.addStyleClass("hidden"); 1090 this.clearResultsButton.element.addStyleClass("hidden"); 1091 this.panelEnablerView.show(this.element); 1092 } 1093 }, 1094 1095 get profilerEnabled() 1096 { 1097 return this._profilerEnabled; 1098 }, 1099 1100 enableProfiler: function() 1101 { 1102 if (this._profilerEnabled) 1103 return; 1104 this._toggleProfiling(this.panelEnablerView.alwaysEnabled); 1105 }, 1106 1107 disableProfiler: function() 1108 { 1109 if (!this._profilerEnabled) 1110 return; 1111 this._toggleProfiling(this.panelEnablerView.alwaysEnabled); 1112 }, 1113 1114 /** 1115 * @param {WebInspector.Event} event 1116 */ 1117 _onToggleProfiling: function(event) { 1118 this._toggleProfiling(true); 1119 }, 1120 1121 /** 1122 * @param {boolean} always 1123 */ 1124 _toggleProfiling: function(always) 1125 { 1126 if (this._profilerEnabled) { 1127 WebInspector.settings.profilerEnabled.set(false); 1128 ProfilerAgent.disable(this._profilerWasDisabled.bind(this)); 1129 } else { 1130 WebInspector.settings.profilerEnabled.set(always); 1131 ProfilerAgent.enable(this._profilerWasEnabled.bind(this)); 1132 } 1133 }, 1134 1135 /** 1136 * @param {!WebInspector.Event} event 1137 */ 1138 sidebarResized: function(event) 1139 { 1140 var sidebarWidth = /** @type {number} */ (event.data); 1141 this._resize(sidebarWidth); 1142 }, 1143 1144 onResize: function() 1145 { 1146 this._resize(this.splitView.sidebarWidth()); 1147 }, 1148 1149 /** 1150 * @param {number} sidebarWidth 1151 */ 1152 _resize: function(sidebarWidth) 1153 { 1154 var lastItemElement = this._statusBarButtons[this._statusBarButtons.length - 1].element; 1155 var left = lastItemElement.totalOffsetLeft() + lastItemElement.offsetWidth; 1156 this._profileTypeStatusBarItemsContainer.style.left = left + "px"; 1157 left += this._profileTypeStatusBarItemsContainer.offsetWidth - 1; 1158 this._profileViewStatusBarItemsContainer.style.left = Math.max(left, sidebarWidth) + "px"; 1159 }, 1160 1161 /** 1162 * @param {string} profileType 1163 * @param {boolean} isProfiling 1164 */ 1165 setRecordingProfile: function(profileType, isProfiling) 1166 { 1167 var profileTypeObject = this.getProfileType(profileType); 1168 this.recordButton.toggled = isProfiling; 1169 this.recordButton.title = profileTypeObject.buttonTooltip; 1170 if (isProfiling) { 1171 this._launcherView.profileStarted(); 1172 this._createTemporaryProfile(profileType); 1173 } else 1174 this._launcherView.profileFinished(); 1175 }, 1176 1177 /** 1178 * @param {!WebInspector.ProfileHeader} profile 1179 * @param {number} done 1180 * @param {number} total 1181 */ 1182 _reportProfileProgress: function(profile, done, total) 1183 { 1184 profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100); 1185 profile.sidebarElement.wait = true; 1186 }, 1187 1188 /** 1189 * @param {WebInspector.ContextMenu} contextMenu 1190 * @param {Object} target 1191 */ 1192 appendApplicableItems: function(event, contextMenu, target) 1193 { 1194 if (WebInspector.inspectorView.currentPanel() !== this) 1195 return; 1196 1197 var object = /** @type {WebInspector.RemoteObject} */ (target); 1198 var objectId = object.objectId; 1199 if (!objectId) 1200 return; 1201 1202 var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles(); 1203 if (!heapProfiles.length) 1204 return; 1205 1206 function revealInView(viewName) 1207 { 1208 HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName)); 1209 } 1210 1211 function didReceiveHeapObjectId(viewName, error, result) 1212 { 1213 if (WebInspector.inspectorView.currentPanel() !== this) 1214 return; 1215 if (!error) 1216 this.showObject(result, viewName); 1217 } 1218 1219 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators")); 1220 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary")); 1221 }, 1222 1223 __proto__: WebInspector.Panel.prototype 1224} 1225 1226/** 1227 * @constructor 1228 * @extends {WebInspector.SidebarTreeElement} 1229 * @param {!WebInspector.ProfileHeader} profile 1230 * @param {string} titleFormat 1231 * @param {string} className 1232 */ 1233WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className) 1234{ 1235 this.profile = profile; 1236 this._titleFormat = titleFormat; 1237 1238 if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(this.profile.title)) 1239 this._profileNumber = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(this.profile.title); 1240 1241 WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false); 1242 1243 this.refreshTitles(); 1244} 1245 1246WebInspector.ProfileSidebarTreeElement.prototype = { 1247 onselect: function() 1248 { 1249 if (!this._suppressOnSelect) 1250 this.treeOutline.panel._showProfile(this.profile); 1251 }, 1252 1253 ondelete: function() 1254 { 1255 this.treeOutline.panel._removeProfileHeader(this.profile); 1256 return true; 1257 }, 1258 1259 get mainTitle() 1260 { 1261 if (this._mainTitle) 1262 return this._mainTitle; 1263 if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(this.profile.title)) 1264 return WebInspector.UIString(this._titleFormat, this._profileNumber); 1265 return this.profile.title; 1266 }, 1267 1268 set mainTitle(x) 1269 { 1270 this._mainTitle = x; 1271 this.refreshTitles(); 1272 }, 1273 1274 set searchMatches(matches) 1275 { 1276 if (!matches) { 1277 if (!this.bubbleElement) 1278 return; 1279 this.bubbleElement.removeStyleClass("search-matches"); 1280 this.bubbleText = ""; 1281 return; 1282 } 1283 1284 this.bubbleText = matches; 1285 this.bubbleElement.addStyleClass("search-matches"); 1286 }, 1287 1288 /** 1289 * @param {!Event} event 1290 * @param {!WebInspector.ProfilesPanel} panel 1291 */ 1292 handleContextMenuEvent: function(event, panel) 1293 { 1294 var profile = this.profile; 1295 var contextMenu = new WebInspector.ContextMenu(event); 1296 // FIXME: use context menu provider 1297 if (profile.canSaveToFile()) { 1298 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save heap snapshot\u2026" : "Save Heap Snapshot\u2026"), profile.saveToFile.bind(profile)); 1299 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load heap snapshot\u2026" : "Load Heap Snapshot\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement)); 1300 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete heap snapshot" : "Delete Heap Snapshot"), this.ondelete.bind(this)); 1301 } else { 1302 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Load heap snapshot\u2026" : "Load Heap Snapshot\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement)); 1303 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete profile" : "Delete Profile"), this.ondelete.bind(this)); 1304 } 1305 contextMenu.show(); 1306 }, 1307 1308 __proto__: WebInspector.SidebarTreeElement.prototype 1309} 1310 1311/** 1312 * @constructor 1313 * @extends {WebInspector.SidebarTreeElement} 1314 * @param {WebInspector.ProfilesPanel} panel 1315 * @param {string} title 1316 * @param {string=} subtitle 1317 */ 1318WebInspector.ProfileGroupSidebarTreeElement = function(panel, title, subtitle) 1319{ 1320 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); 1321 this._panel = panel; 1322} 1323 1324WebInspector.ProfileGroupSidebarTreeElement.prototype = { 1325 onselect: function() 1326 { 1327 if (this.children.length > 0) 1328 this._panel._showProfile(this.children[this.children.length - 1].profile); 1329 }, 1330 1331 __proto__: WebInspector.SidebarTreeElement.prototype 1332} 1333 1334/** 1335 * @constructor 1336 * @extends {WebInspector.SidebarTreeElement} 1337 * @param {!WebInspector.ProfilesPanel} panel 1338 */ 1339WebInspector.ProfilesSidebarTreeElement = function(panel) 1340{ 1341 this._panel = panel; 1342 this.small = false; 1343 1344 WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false); 1345} 1346 1347WebInspector.ProfilesSidebarTreeElement.prototype = { 1348 onselect: function() 1349 { 1350 this._panel._showLauncherView(); 1351 }, 1352 1353 get selectable() 1354 { 1355 return true; 1356 }, 1357 1358 __proto__: WebInspector.SidebarTreeElement.prototype 1359} 1360 1361 1362/** 1363 * @constructor 1364 * @extends {WebInspector.ProfilesPanel} 1365 */ 1366WebInspector.CPUProfilerPanel = function() 1367{ 1368 WebInspector.ProfilesPanel.call(this, "cpu-profiler", new WebInspector.CPUProfileType()); 1369} 1370 1371WebInspector.CPUProfilerPanel.prototype = { 1372 __proto__: WebInspector.ProfilesPanel.prototype 1373} 1374 1375 1376/** 1377 * @constructor 1378 * @extends {WebInspector.ProfilesPanel} 1379 */ 1380WebInspector.CSSSelectorProfilerPanel = function() 1381{ 1382 WebInspector.ProfilesPanel.call(this, "css-profiler", new WebInspector.CSSSelectorProfileType()); 1383} 1384 1385WebInspector.CSSSelectorProfilerPanel.prototype = { 1386 __proto__: WebInspector.ProfilesPanel.prototype 1387} 1388 1389 1390/** 1391 * @constructor 1392 * @extends {WebInspector.ProfilesPanel} 1393 */ 1394WebInspector.HeapProfilerPanel = function() 1395{ 1396 WebInspector.ProfilesPanel.call(this, "heap-profiler", new WebInspector.HeapSnapshotProfileType()); 1397} 1398 1399WebInspector.HeapProfilerPanel.prototype = { 1400 __proto__: WebInspector.ProfilesPanel.prototype 1401} 1402 1403 1404/** 1405 * @constructor 1406 * @extends {WebInspector.ProfilesPanel} 1407 */ 1408WebInspector.CanvasProfilerPanel = function() 1409{ 1410 WebInspector.ProfilesPanel.call(this, "canvas-profiler", new WebInspector.CanvasProfileType()); 1411} 1412 1413WebInspector.CanvasProfilerPanel.prototype = { 1414 __proto__: WebInspector.ProfilesPanel.prototype 1415} 1416 1417 1418importScript("ProfileDataGridTree.js"); 1419importScript("BottomUpProfileDataGridTree.js"); 1420importScript("CPUProfileView.js"); 1421importScript("CSSSelectorProfileView.js"); 1422importScript("FlameChart.js"); 1423importScript("HeapSnapshot.js"); 1424importScript("HeapSnapshotDataGrids.js"); 1425importScript("HeapSnapshotGridNodes.js"); 1426importScript("HeapSnapshotLoader.js"); 1427importScript("HeapSnapshotProxy.js"); 1428importScript("HeapSnapshotView.js"); 1429importScript("HeapSnapshotWorkerDispatcher.js"); 1430importScript("JSHeapSnapshot.js"); 1431importScript("NativeHeapSnapshot.js"); 1432importScript("ProfileLauncherView.js"); 1433importScript("TopDownProfileDataGridTree.js"); 1434importScript("CanvasProfileView.js"); 1435