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 WebInspector.ProfileType = function(id, name) 29 { 30 this._id = id; 31 this._name = name; 32 } 33 34 WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/; 35 36 WebInspector.ProfileType.prototype = { 37 get buttonTooltip() 38 { 39 return ""; 40 }, 41 42 get buttonStyle() 43 { 44 return undefined; 45 }, 46 47 get buttonCaption() 48 { 49 return this.name; 50 }, 51 52 get id() 53 { 54 return this._id; 55 }, 56 57 get name() 58 { 59 return this._name; 60 }, 61 62 buttonClicked: function() 63 { 64 }, 65 66 viewForProfile: function(profile) 67 { 68 if (!profile._profileView) 69 profile._profileView = this.createView(profile); 70 return profile._profileView; 71 }, 72 73 get welcomeMessage() 74 { 75 return ""; 76 }, 77 78 // Must be implemented by subclasses. 79 createView: function(profile) 80 { 81 throw new Error("Needs implemented."); 82 }, 83 84 // Must be implemented by subclasses. 85 createSidebarTreeElementForProfile: function(profile) 86 { 87 throw new Error("Needs implemented."); 88 } 89 } 90 91 WebInspector.ProfilesPanel = function() 92 { 93 WebInspector.Panel.call(this, "profiles"); 94 95 this.createSidebar(); 96 97 this._profileTypesByIdMap = {}; 98 this._profileTypeButtonsByIdMap = {}; 99 100 var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel."); 101 var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower."); 102 var panelEnablerButton = WebInspector.UIString("Enable Profiling"); 103 this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); 104 this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this); 105 106 this.element.appendChild(this.panelEnablerView.element); 107 108 this.profileViews = document.createElement("div"); 109 this.profileViews.id = "profile-views"; 110 this.element.appendChild(this.profileViews); 111 112 this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); 113 this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false); 114 115 this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item"); 116 this.clearResultsButton.addEventListener("click", this._clearProfiles.bind(this), false); 117 118 this.profileViewStatusBarItemsContainer = document.createElement("div"); 119 this.profileViewStatusBarItemsContainer.className = "status-bar-items"; 120 121 this.welcomeView = new WebInspector.WelcomeView("profiles", WebInspector.UIString("Welcome to the Profiles panel")); 122 this.element.appendChild(this.welcomeView.element); 123 124 this._profiles = []; 125 this._profilerEnabled = Preferences.profilerAlwaysEnabled; 126 this._reset(); 127 128 this._registerProfileType(new WebInspector.CPUProfileType()); 129 if (Preferences.heapProfilerPresent) { 130 if (!Preferences.detailedHeapProfiles) 131 this._registerProfileType(new WebInspector.HeapSnapshotProfileType()); 132 else 133 this._registerProfileType(new WebInspector.DetailedHeapshotProfileType()); 134 } 135 136 InspectorBackend.registerDomainDispatcher("Profiler", new WebInspector.ProfilerDispatcher(this)); 137 138 if (Preferences.profilerAlwaysEnabled || WebInspector.settings.profilerEnabled) 139 ProfilerAgent.enable(); 140 else { 141 function onProfilerEnebled(error, value) { 142 if (value) 143 this._profilerWasEnabled(); 144 } 145 ProfilerAgent.isEnabled(onProfilerEnebled.bind(this)); 146 } 147 } 148 149 WebInspector.ProfilesPanel.prototype = { 150 get toolbarItemLabel() 151 { 152 return WebInspector.UIString("Profiles"); 153 }, 154 155 get statusBarItems() 156 { 157 function clickHandler(profileType, buttonElement) 158 { 159 profileType.buttonClicked.call(profileType); 160 this.updateProfileTypeButtons(); 161 } 162 163 var items = [this.enableToggleButton.element]; 164 // FIXME: Generate a single "combo-button". 165 for (var typeId in this._profileTypesByIdMap) { 166 var profileType = this.getProfileType(typeId); 167 if (profileType.buttonStyle) { 168 var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption); 169 this._profileTypeButtonsByIdMap[typeId] = button.element; 170 button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false); 171 items.push(button.element); 172 } 173 } 174 items.push(this.clearResultsButton.element, this.profileViewStatusBarItemsContainer); 175 return items; 176 }, 177 178 show: function() 179 { 180 WebInspector.Panel.prototype.show.call(this); 181 this._populateProfiles(); 182 }, 183 184 _profilerWasEnabled: function() 185 { 186 if (this._profilerEnabled) 187 return; 188 189 this._profilerEnabled = true; 190 191 this._reset(); 192 if (this.visible) 193 this._populateProfiles(); 194 }, 195 196 _profilerWasDisabled: function() 197 { 198 if (!this._profilerEnabled) 199 return; 200 201 this._profilerEnabled = false; 202 this._reset(); 203 }, 204 205 _reset: function() 206 { 207 WebInspector.Panel.prototype.reset.call(this); 208 209 for (var i = 0; i < this._profiles.length; ++i) { 210 var view = this._profiles[i]._profileView; 211 if (view && ("dispose" in view)) 212 view.dispose(); 213 delete this._profiles[i]._profileView; 214 } 215 delete this.visibleView; 216 217 delete this.currentQuery; 218 this.searchCanceled(); 219 220 this._profiles = []; 221 this._profilesIdMap = {}; 222 this._profileGroups = {}; 223 this._profileGroupsForLinks = {}; 224 this._profilesWereRequested = false; 225 226 this.sidebarTreeElement.removeStyleClass("some-expandable"); 227 228 for (var typeId in this._profileTypesByIdMap) 229 this.getProfileType(typeId).treeElement.removeChildren(); 230 231 this.profileViews.removeChildren(); 232 233 this.profileViewStatusBarItemsContainer.removeChildren(); 234 235 this.removeAllListeners(); 236 237 this._updateInterface(); 238 this.welcomeView.show(); 239 }, 240 241 _clearProfiles: function() 242 { 243 ProfilerAgent.clearProfiles(); 244 this._reset(); 245 }, 246 247 _registerProfileType: function(profileType) 248 { 249 this._profileTypesByIdMap[profileType.id] = profileType; 250 profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true); 251 this.sidebarTree.appendChild(profileType.treeElement); 252 profileType.treeElement.expand(); 253 this._addWelcomeMessage(profileType); 254 }, 255 256 _addWelcomeMessage: function(profileType) 257 { 258 var message = profileType.welcomeMessage; 259 // Message text is supposed to have a '%s' substring as a placeholder 260 // for a status bar button. If it is there, we split the message in two 261 // parts, and insert the button between them. 262 var buttonPos = message.indexOf("%s"); 263 if (buttonPos > -1) { 264 var container = document.createDocumentFragment(); 265 var part1 = document.createElement("span"); 266 part1.textContent = message.substr(0, buttonPos); 267 container.appendChild(part1); 268 269 var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption); 270 container.appendChild(button.element); 271 272 var part2 = document.createElement("span"); 273 part2.textContent = message.substr(buttonPos + 2); 274 container.appendChild(part2); 275 this.welcomeView.addMessage(container); 276 } else 277 this.welcomeView.addMessage(message); 278 }, 279 280 _makeKey: function(text, profileTypeId) 281 { 282 return escape(text) + '/' + escape(profileTypeId); 283 }, 284 285 _addProfileHeader: function(profile) 286 { 287 if (this.hasTemporaryProfile(profile.typeId)) { 288 if (profile.typeId === WebInspector.CPUProfileType.TypeId) 289 this._removeProfileHeader(this._temporaryRecordingProfile); 290 else 291 this._removeProfileHeader(this._temporaryTakingSnapshot); 292 } 293 294 var typeId = profile.typeId; 295 var profileType = this.getProfileType(typeId); 296 var sidebarParent = profileType.treeElement; 297 var small = false; 298 var alternateTitle; 299 300 profile.__profilesPanelProfileType = profileType; 301 this._profiles.push(profile); 302 this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile; 303 304 if (profile.title.indexOf(UserInitiatedProfileName) !== 0) { 305 var profileTitleKey = this._makeKey(profile.title, typeId); 306 if (!(profileTitleKey in this._profileGroups)) 307 this._profileGroups[profileTitleKey] = []; 308 309 var group = this._profileGroups[profileTitleKey]; 310 group.push(profile); 311 312 if (group.length === 2) { 313 // Make a group TreeElement now that there are 2 profiles. 314 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title); 315 316 // Insert at the same index for the first profile of the group. 317 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement); 318 sidebarParent.insertChild(group._profilesTreeElement, index); 319 320 // Move the first profile to the group. 321 var selected = group[0]._profilesTreeElement.selected; 322 sidebarParent.removeChild(group[0]._profilesTreeElement); 323 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); 324 if (selected) { 325 group[0]._profilesTreeElement.select(); 326 group[0]._profilesTreeElement.reveal(); 327 } 328 329 group[0]._profilesTreeElement.small = true; 330 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); 331 332 this.sidebarTreeElement.addStyleClass("some-expandable"); 333 } 334 335 if (group.length >= 2) { 336 sidebarParent = group._profilesTreeElement; 337 alternateTitle = WebInspector.UIString("Run %d", group.length); 338 small = true; 339 } 340 } 341 342 var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile); 343 profile.sideBarElement = profileTreeElement; 344 profileTreeElement.small = small; 345 if (alternateTitle) 346 profileTreeElement.mainTitle = alternateTitle; 347 profile._profilesTreeElement = profileTreeElement; 348 349 sidebarParent.appendChild(profileTreeElement); 350 if (!profile.isTemporary) { 351 this.welcomeView.hide(); 352 if (!this.visibleView) 353 this.showProfile(profile); 354 this.dispatchEventToListeners("profile added"); 355 } 356 }, 357 358 _removeProfileHeader: function(profile) 359 { 360 var typeId = profile.typeId; 361 var profileType = this.getProfileType(typeId); 362 var sidebarParent = profileType.treeElement; 363 364 for (var i = 0; i < this._profiles.length; ++i) { 365 if (this._profiles[i].uid === profile.uid) { 366 profile = this._profiles[i]; 367 this._profiles.splice(i, 1); 368 break; 369 } 370 } 371 delete this._profilesIdMap[this._makeKey(profile.uid, typeId)]; 372 373 var profileTitleKey = this._makeKey(profile.title, typeId); 374 delete this._profileGroups[profileTitleKey]; 375 376 sidebarParent.removeChild(profile._profilesTreeElement); 377 378 if (!profile.isTemporary) 379 ProfilerAgent.removeProfile(profile.typeId, profile.uid); 380 381 // No other item will be selected if there aren't any other profiles, so 382 // make sure that view gets cleared when the last profile is removed. 383 if (!this._profiles.length) 384 this.closeVisibleView(); 385 }, 386 387 showProfile: function(profile) 388 { 389 if (!profile || profile.isTemporary) 390 return; 391 392 this.closeVisibleView(); 393 394 var view = profile.__profilesPanelProfileType.viewForProfile(profile); 395 396 view.show(this.profileViews); 397 398 profile._profilesTreeElement.select(true); 399 profile._profilesTreeElement.reveal(); 400 401 this.visibleView = view; 402 403 this.profileViewStatusBarItemsContainer.removeChildren(); 404 405 var statusBarItems = view.statusBarItems; 406 if (statusBarItems) 407 for (var i = 0; i < statusBarItems.length; ++i) 408 this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); 409 }, 410 411 getProfiles: function(typeId) 412 { 413 var result = []; 414 var profilesCount = this._profiles.length; 415 for (var i = 0; i < profilesCount; ++i) { 416 var profile = this._profiles[i]; 417 if (!profile.isTemporary && profile.typeId === typeId) 418 result.push(profile); 419 } 420 return result; 421 }, 422 423 hasTemporaryProfile: function(typeId) 424 { 425 var profilesCount = this._profiles.length; 426 for (var i = 0; i < profilesCount; ++i) 427 if (this._profiles[i].typeId === typeId && this._profiles[i].isTemporary) 428 return true; 429 return false; 430 }, 431 432 hasProfile: function(profile) 433 { 434 return !!this._profilesIdMap[this._makeKey(profile.uid, profile.typeId)]; 435 }, 436 437 getProfile: function(typeId, uid) 438 { 439 return this._profilesIdMap[this._makeKey(uid, typeId)]; 440 }, 441 442 loadHeapSnapshot: function(uid, callback) 443 { 444 var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.HeapSnapshotProfileType.TypeId)]; 445 if (!profile) 446 return; 447 448 if (!Preferences.detailedHeapProfiles) { 449 if (profile._loaded) 450 callback(profile); 451 else if (profile._is_loading) 452 profile._callbacks.push(callback); 453 else { 454 profile._is_loading = true; 455 profile._callbacks = [callback]; 456 profile._json = ""; 457 profile.sideBarElement.subtitle = WebInspector.UIString("Loading\u2026"); 458 ProfilerAgent.getProfile(profile.typeId, profile.uid); 459 } 460 } else { 461 if (!profile.proxy) 462 profile.proxy = new WebInspector.HeapSnapshotProxy(); 463 var proxy = profile.proxy; 464 if (proxy.startLoading(callback)) { 465 profile.sideBarElement.subtitle = WebInspector.UIString("Loading\u2026"); 466 ProfilerAgent.getProfile(profile.typeId, profile.uid); 467 } 468 } 469 }, 470 471 _addHeapSnapshotChunk: function(uid, chunk) 472 { 473 var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.HeapSnapshotProfileType.TypeId)]; 474 if (!profile) 475 return; 476 if (!Preferences.detailedHeapProfiles) { 477 if (profile._loaded || !profile._is_loading) 478 return; 479 profile._json += chunk; 480 } else { 481 if (!profile.proxy) 482 return; 483 profile.proxy.pushJSONChunk(chunk); 484 } 485 }, 486 487 _finishHeapSnapshot: function(uid) 488 { 489 var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.HeapSnapshotProfileType.TypeId)]; 490 if (!profile) 491 return; 492 if (!Preferences.detailedHeapProfiles) { 493 if (profile._loaded || !profile._is_loading) 494 return; 495 profile.sideBarElement.subtitle = WebInspector.UIString("Parsing\u2026"); 496 function doParse() 497 { 498 var loadedSnapshot = JSON.parse(profile._json); 499 var callbacks = profile._callbacks; 500 delete profile._callbacks; 501 delete profile._json; 502 delete profile._is_loading; 503 profile._loaded = true; 504 profile.sideBarElement.subtitle = ""; 505 506 if (WebInspector.DetailedHeapshotView.prototype.isDetailedSnapshot(loadedSnapshot)) { 507 WebInspector.panels.profiles._enableDetailedHeapProfiles(false); 508 return; 509 } 510 511 WebInspector.HeapSnapshotView.prototype.processLoadedSnapshot(profile, loadedSnapshot); 512 for (var i = 0; i < callbacks.length; ++i) 513 callbacks[i](profile); 514 } 515 setTimeout(doParse, 0); 516 } else { 517 if (!profile.proxy) 518 return; 519 var proxy = profile.proxy; 520 function parsed() 521 { 522 profile.sideBarElement.subtitle = Number.bytesToString(proxy.totalSize); 523 } 524 if (proxy.finishLoading(parsed)) 525 profile.sideBarElement.subtitle = WebInspector.UIString("Parsing\u2026"); 526 } 527 }, 528 529 showView: function(view) 530 { 531 this.showProfile(view.profile); 532 }, 533 534 getProfileType: function(typeId) 535 { 536 return this._profileTypesByIdMap[typeId]; 537 }, 538 539 showProfileForURL: function(url) 540 { 541 var match = url.match(WebInspector.ProfileType.URLRegExp); 542 if (!match) 543 return; 544 this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]); 545 }, 546 547 updateProfileTypeButtons: function() 548 { 549 for (var typeId in this._profileTypeButtonsByIdMap) { 550 var buttonElement = this._profileTypeButtonsByIdMap[typeId]; 551 var profileType = this.getProfileType(typeId); 552 buttonElement.className = profileType.buttonStyle; 553 buttonElement.title = profileType.buttonTooltip; 554 // FIXME: Apply profileType.buttonCaption once captions are added to button controls. 555 } 556 }, 557 558 closeVisibleView: function() 559 { 560 if (this.visibleView) 561 this.visibleView.hide(); 562 delete this.visibleView; 563 }, 564 565 displayTitleForProfileLink: function(title, typeId) 566 { 567 title = unescape(title); 568 if (title.indexOf(UserInitiatedProfileName) === 0) { 569 title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1)); 570 } else { 571 var titleKey = this._makeKey(title, typeId); 572 if (!(titleKey in this._profileGroupsForLinks)) 573 this._profileGroupsForLinks[titleKey] = 0; 574 575 var groupNumber = ++this._profileGroupsForLinks[titleKey]; 576 577 if (groupNumber > 2) 578 // The title is used in the console message announcing that a profile has started so it gets 579 // incremented twice as often as it's displayed 580 title += " " + WebInspector.UIString("Run %d", (groupNumber + 1) / 2); 581 } 582 583 return title; 584 }, 585 586 get searchableViews() 587 { 588 var views = []; 589 590 const visibleView = this.visibleView; 591 if (visibleView && visibleView.performSearch) 592 views.push(visibleView); 593 594 var profilesLength = this._profiles.length; 595 for (var i = 0; i < profilesLength; ++i) { 596 var profile = this._profiles[i]; 597 var view = profile.__profilesPanelProfileType.viewForProfile(profile); 598 if (!view.performSearch || view === visibleView) 599 continue; 600 views.push(view); 601 } 602 603 return views; 604 }, 605 606 searchMatchFound: function(view, matches) 607 { 608 view.profile._profilesTreeElement.searchMatches = matches; 609 }, 610 611 searchCanceled: function(startingNewSearch) 612 { 613 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); 614 615 if (!this._profiles) 616 return; 617 618 for (var i = 0; i < this._profiles.length; ++i) { 619 var profile = this._profiles[i]; 620 profile._profilesTreeElement.searchMatches = 0; 621 } 622 }, 623 624 _updateInterface: function() 625 { 626 // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change. 627 if (this._profilerEnabled) { 628 this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); 629 this.enableToggleButton.toggled = true; 630 for (var typeId in this._profileTypeButtonsByIdMap) 631 this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden"); 632 this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); 633 this.clearResultsButton.element.removeStyleClass("hidden"); 634 this.panelEnablerView.visible = false; 635 } else { 636 this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); 637 this.enableToggleButton.toggled = false; 638 for (var typeId in this._profileTypeButtonsByIdMap) 639 this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden"); 640 this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); 641 this.clearResultsButton.element.addStyleClass("hidden"); 642 this.panelEnablerView.visible = true; 643 } 644 }, 645 646 _enableProfiling: function() 647 { 648 if (this._profilerEnabled) 649 return; 650 this._toggleProfiling(this.panelEnablerView.alwaysEnabled); 651 }, 652 653 _toggleProfiling: function(optionalAlways) 654 { 655 if (this._profilerEnabled) { 656 WebInspector.settings.profilerEnabled = false; 657 ProfilerAgent.disable(); 658 } else { 659 WebInspector.settings.profilerEnabled = !!optionalAlways; 660 ProfilerAgent.enable(); 661 } 662 }, 663 664 _populateProfiles: function() 665 { 666 if (!this._profilerEnabled || this._profilesWereRequested) 667 return; 668 669 function populateCallback(error, profileHeaders) { 670 if (error) 671 return; 672 profileHeaders.sort(function(a, b) { return a.uid - b.uid; }); 673 var profileHeadersLength = profileHeaders.length; 674 for (var i = 0; i < profileHeadersLength; ++i) 675 if (!this.hasProfile(profileHeaders[i])) 676 this._addProfileHeader(profileHeaders[i]); 677 } 678 679 ProfilerAgent.getProfileHeaders(populateCallback.bind(this)); 680 681 this._profilesWereRequested = true; 682 }, 683 684 updateMainViewWidth: function(width) 685 { 686 this.welcomeView.element.style.left = width + "px"; 687 this.profileViews.style.left = width + "px"; 688 this.profileViewStatusBarItemsContainer.style.left = Math.max(155, width) + "px"; 689 this.resize(); 690 }, 691 692 _setRecordingProfile: function(isProfiling) 693 { 694 this.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling); 695 if (this.hasTemporaryProfile(WebInspector.CPUProfileType.TypeId) !== isProfiling) { 696 if (!this._temporaryRecordingProfile) { 697 this._temporaryRecordingProfile = { 698 typeId: WebInspector.CPUProfileType.TypeId, 699 title: WebInspector.UIString("Recording"), 700 uid: -1, 701 isTemporary: true 702 }; 703 } 704 if (isProfiling) 705 this._addProfileHeader(this._temporaryRecordingProfile); 706 else 707 this._removeProfileHeader(this._temporaryRecordingProfile); 708 } 709 this.updateProfileTypeButtons(); 710 }, 711 712 takeHeapSnapshot: function(detailed) 713 { 714 if (!this.hasTemporaryProfile(WebInspector.HeapSnapshotProfileType.TypeId)) { 715 if (!this._temporaryTakingSnapshot) { 716 this._temporaryTakingSnapshot = { 717 typeId: WebInspector.HeapSnapshotProfileType.TypeId, 718 title: WebInspector.UIString("Snapshotting"), 719 uid: -1, 720 isTemporary: true 721 }; 722 } 723 this._addProfileHeader(this._temporaryTakingSnapshot); 724 } 725 ProfilerAgent.takeHeapSnapshot(detailed); 726 }, 727 728 _reportHeapSnapshotProgress: function(done, total) 729 { 730 if (this.hasTemporaryProfile(WebInspector.HeapSnapshotProfileType.TypeId)) { 731 this._temporaryTakingSnapshot.sideBarElement.subtitle = WebInspector.UIString("%.2f%%", (done / total) * 100); 732 if (done >= total) 733 this._removeProfileHeader(this._temporaryTakingSnapshot); 734 } 735 }, 736 737 handleShortcut: function(event) 738 { 739 if (!Preferences.heapProfilerPresent || Preferences.detailedHeapProfiles) 740 return; 741 var combo = ["U+004C", "U+0045", "U+0041", "U+004B", "U+005A"]; // "LEAKZ" 742 if (this._recognizeKeyboardCombo(combo, event)) { 743 this._displayDetailedHeapProfilesEnabledHint(); 744 this._enableDetailedHeapProfiles(true); 745 } 746 }, 747 748 _recognizeKeyboardCombo: function(combo, event) 749 { 750 var isRecognized = false; 751 if (!this._comboPosition) { 752 if (event.keyIdentifier === combo[0]) 753 this._comboPosition = 1; 754 } else if (event.keyIdentifier === combo[this._comboPosition]) { 755 if (++this._comboPosition === combo.length) 756 isRecognized = true; 757 } else 758 delete this._comboPosition; 759 if (this._comboPosition) 760 event.handled = true; 761 return isRecognized; 762 }, 763 764 _displayDetailedHeapProfilesEnabledHint: function() 765 { 766 var message = new WebInspector.HelpScreen("Congratulations!"); 767 message.contentElement.addStyleClass("help-table"); 768 message.contentElement.textContent = "Detailed Heap snapshots are now enabled."; 769 message.show(); 770 771 function hideHint() 772 { 773 message._hide(); 774 } 775 776 setTimeout(hideHint, 2000); 777 }, 778 779 _enableDetailedHeapProfiles: function(resetAgent) 780 { 781 if (resetAgent) 782 this._clearProfiles(); 783 else 784 this._reset(); 785 var oldProfileType = this._profileTypesByIdMap[WebInspector.HeapSnapshotProfileType.TypeId]; 786 var profileType = new WebInspector.DetailedHeapshotProfileType(); 787 profileType.treeElement = oldProfileType.treeElement; 788 this._profileTypesByIdMap[profileType.id] = profileType; 789 Preferences.detailedHeapProfiles = true; 790 this.hide(); 791 this.show(); 792 } 793 } 794 795 WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype; 796 797 798 WebInspector.ProfilerDispatcher = function(profiler) 799 { 800 this._profiler = profiler; 801 } 802 803 WebInspector.ProfilerDispatcher.prototype = { 804 profilerWasEnabled: function() 805 { 806 this._profiler._profilerWasEnabled(); 807 }, 808 809 profilerWasDisabled: function() 810 { 811 this._profiler._profilerWasDisabled(); 812 }, 813 814 resetProfiles: function() 815 { 816 this._profiler._reset(); 817 }, 818 819 addProfileHeader: function(profile) 820 { 821 this._profiler._addProfileHeader(profile); 822 }, 823 824 addHeapSnapshotChunk: function(uid, chunk) 825 { 826 this._profiler._addHeapSnapshotChunk(uid, chunk); 827 }, 828 829 finishHeapSnapshot: function(uid) 830 { 831 this._profiler._finishHeapSnapshot(uid); 832 }, 833 834 setRecordingProfile: function(isProfiling) 835 { 836 this._profiler._setRecordingProfile(isProfiling); 837 }, 838 839 reportHeapSnapshotProgress: function(done, total) 840 { 841 this._profiler._reportHeapSnapshotProgress(done, total); 842 } 843 } 844 845 WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className) 846 { 847 this.profile = profile; 848 this._titleFormat = titleFormat; 849 850 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) 851 this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1); 852 853 WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false); 854 855 this.refreshTitles(); 856 } 857 858 WebInspector.ProfileSidebarTreeElement.prototype = { 859 onselect: function() 860 { 861 this.treeOutline.panel.showProfile(this.profile); 862 }, 863 864 ondelete: function() 865 { 866 this.treeOutline.panel._removeProfileHeader(this.profile); 867 return true; 868 }, 869 870 get mainTitle() 871 { 872 if (this._mainTitle) 873 return this._mainTitle; 874 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) 875 return WebInspector.UIString(this._titleFormat, this._profileNumber); 876 return this.profile.title; 877 }, 878 879 set mainTitle(x) 880 { 881 this._mainTitle = x; 882 this.refreshTitles(); 883 }, 884 885 set searchMatches(matches) 886 { 887 if (!matches) { 888 if (!this.bubbleElement) 889 return; 890 this.bubbleElement.removeStyleClass("search-matches"); 891 this.bubbleText = ""; 892 return; 893 } 894 895 this.bubbleText = matches; 896 this.bubbleElement.addStyleClass("search-matches"); 897 } 898 } 899 900 WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 901 902 WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle) 903 { 904 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); 905 } 906 907 WebInspector.ProfileGroupSidebarTreeElement.prototype = { 908 onselect: function() 909 { 910 if (this.children.length > 0) 911 WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile); 912 } 913 } 914 915 WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 916