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