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 28WebInspector.ProfilesPanel = function() 29{ 30 WebInspector.Panel.call(this); 31 32 this.element.addStyleClass("profiles"); 33 34 var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel."); 35 var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower."); 36 var panelEnablerButton = WebInspector.UIString("Enable Profiling"); 37 this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); 38 this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this); 39 40 this.element.appendChild(this.panelEnablerView.element); 41 42 this.sidebarElement = document.createElement("div"); 43 this.sidebarElement.id = "profiles-sidebar"; 44 this.sidebarElement.className = "sidebar"; 45 this.element.appendChild(this.sidebarElement); 46 47 this.sidebarResizeElement = document.createElement("div"); 48 this.sidebarResizeElement.className = "sidebar-resizer-vertical"; 49 this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); 50 this.element.appendChild(this.sidebarResizeElement); 51 52 this.sidebarTreeElement = document.createElement("ol"); 53 this.sidebarTreeElement.className = "sidebar-tree"; 54 this.sidebarElement.appendChild(this.sidebarTreeElement); 55 56 this.sidebarTree = new TreeOutline(this.sidebarTreeElement); 57 58 this.profileViews = document.createElement("div"); 59 this.profileViews.id = "profile-views"; 60 this.element.appendChild(this.profileViews); 61 62 this.enableToggleButton = this.createStatusBarButton(); 63 this.enableToggleButton.className = "enable-toggle-status-bar-item status-bar-item"; 64 this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false); 65 66 this.recordButton = this.createStatusBarButton(); 67 this.recordButton.title = WebInspector.UIString("Start profiling."); 68 this.recordButton.id = "record-profile-status-bar-item"; 69 this.recordButton.className = "status-bar-item"; 70 this.recordButton.addEventListener("click", this._recordClicked.bind(this), false); 71 72 this.recording = false; 73 74 this.profileViewStatusBarItemsContainer = document.createElement("div"); 75 this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items"; 76 77 this.reset(); 78} 79 80WebInspector.ProfilesPanel.prototype = { 81 toolbarItemClass: "profiles", 82 83 get toolbarItemLabel() 84 { 85 return WebInspector.UIString("Profiles"); 86 }, 87 88 get statusBarItems() 89 { 90 return [this.enableToggleButton, this.recordButton, this.profileViewStatusBarItemsContainer]; 91 }, 92 93 show: function() 94 { 95 WebInspector.Panel.prototype.show.call(this); 96 this._updateSidebarWidth(); 97 if (this._shouldPopulateProfiles) 98 this._populateProfiles(); 99 }, 100 101 populateInterface: function() 102 { 103 if (this.visible) 104 this._populateProfiles(); 105 else 106 this._shouldPopulateProfiles = true; 107 }, 108 109 profilerWasEnabled: function() 110 { 111 this.reset(); 112 this.populateInterface(); 113 }, 114 115 profilerWasDisabled: function() 116 { 117 this.reset(); 118 }, 119 120 reset: function() 121 { 122 if (this._profiles) { 123 var profiledLength = this._profiles.length; 124 for (var i = 0; i < profiledLength; ++i) { 125 var profile = this._profiles[i]; 126 delete profile._profileView; 127 } 128 } 129 130 delete this.currentQuery; 131 this.searchCanceled(); 132 133 this._profiles = []; 134 this._profilesIdMap = {}; 135 this._profileGroups = {}; 136 this._profileGroupsForLinks = {} 137 138 this.sidebarTreeElement.removeStyleClass("some-expandable"); 139 140 this.sidebarTree.removeChildren(); 141 this.profileViews.removeChildren(); 142 143 this.profileViewStatusBarItemsContainer.removeChildren(); 144 145 this._updateInterface(); 146 }, 147 148 handleKeyEvent: function(event) 149 { 150 this.sidebarTree.handleKeyEvent(event); 151 }, 152 153 addProfile: function(profile) 154 { 155 this._profiles.push(profile); 156 this._profilesIdMap[profile.uid] = profile; 157 158 var sidebarParent = this.sidebarTree; 159 var small = false; 160 var alternateTitle; 161 162 if (profile.title.indexOf(UserInitiatedProfileName) !== 0) { 163 if (!(profile.title in this._profileGroups)) 164 this._profileGroups[profile.title] = []; 165 166 var group = this._profileGroups[profile.title]; 167 group.push(profile); 168 169 if (group.length === 2) { 170 // Make a group TreeElement now that there are 2 profiles. 171 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title); 172 173 // Insert at the same index for the first profile of the group. 174 var index = this.sidebarTree.children.indexOf(group[0]._profilesTreeElement); 175 this.sidebarTree.insertChild(group._profilesTreeElement, index); 176 177 // Move the first profile to the group. 178 var selected = group[0]._profilesTreeElement.selected; 179 this.sidebarTree.removeChild(group[0]._profilesTreeElement); 180 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); 181 if (selected) { 182 group[0]._profilesTreeElement.select(); 183 group[0]._profilesTreeElement.reveal(); 184 } 185 186 group[0]._profilesTreeElement.small = true; 187 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); 188 189 this.sidebarTreeElement.addStyleClass("some-expandable"); 190 } 191 192 if (group.length >= 2) { 193 sidebarParent = group._profilesTreeElement; 194 alternateTitle = WebInspector.UIString("Run %d", group.length); 195 small = true; 196 } 197 } 198 199 var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile); 200 profileTreeElement.small = small; 201 if (alternateTitle) 202 profileTreeElement.mainTitle = alternateTitle; 203 profile._profilesTreeElement = profileTreeElement; 204 205 sidebarParent.appendChild(profileTreeElement); 206 }, 207 208 showProfile: function(profile) 209 { 210 if (!profile) 211 return; 212 213 if (this.visibleView) 214 this.visibleView.hide(); 215 216 var view = this.profileViewForProfile(profile); 217 218 view.show(this.profileViews); 219 220 profile._profilesTreeElement.select(true); 221 profile._profilesTreeElement.reveal(); 222 223 this.visibleView = view; 224 225 this.profileViewStatusBarItemsContainer.removeChildren(); 226 227 var statusBarItems = view.statusBarItems; 228 for (var i = 0; i < statusBarItems.length; ++i) 229 this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); 230 }, 231 232 showView: function(view) 233 { 234 this.showProfile(view.profile); 235 }, 236 237 profileViewForProfile: function(profile) 238 { 239 if (!profile) 240 return null; 241 if (!profile._profileView) 242 profile._profileView = new WebInspector.ProfileView(profile); 243 return profile._profileView; 244 }, 245 246 showProfileById: function(uid) 247 { 248 this.showProfile(this._profilesIdMap[uid]); 249 }, 250 251 closeVisibleView: function() 252 { 253 if (this.visibleView) 254 this.visibleView.hide(); 255 delete this.visibleView; 256 }, 257 258 displayTitleForProfileLink: function(title) 259 { 260 title = unescape(title); 261 if (title.indexOf(UserInitiatedProfileName) === 0) { 262 title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1)); 263 } else { 264 if (!(title in this._profileGroupsForLinks)) 265 this._profileGroupsForLinks[title] = 0; 266 267 groupNumber = ++this._profileGroupsForLinks[title]; 268 269 if (groupNumber > 2) 270 // The title is used in the console message announcing that a profile has started so it gets 271 // incremented twice as often as it's displayed 272 title += " " + WebInspector.UIString("Run %d", groupNumber / 2); 273 } 274 275 return title; 276 }, 277 278 get searchableViews() 279 { 280 var views = []; 281 282 const visibleView = this.visibleView; 283 if (visibleView && visibleView.performSearch) 284 views.push(visibleView); 285 286 var profilesLength = this._profiles.length; 287 for (var i = 0; i < profilesLength; ++i) { 288 var view = this.profileViewForProfile(this._profiles[i]); 289 if (!view.performSearch || view === visibleView) 290 continue; 291 views.push(view); 292 } 293 294 return views; 295 }, 296 297 searchMatchFound: function(view, matches) 298 { 299 view.profile._profilesTreeElement.searchMatches = matches; 300 }, 301 302 searchCanceled: function(startingNewSearch) 303 { 304 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); 305 306 if (!this._profiles) 307 return; 308 309 for (var i = 0; i < this._profiles.length; ++i) { 310 var profile = this._profiles[i]; 311 profile._profilesTreeElement.searchMatches = 0; 312 } 313 }, 314 315 setRecordingProfile: function(isProfiling) 316 { 317 this.recording = isProfiling; 318 319 if (isProfiling) { 320 this.recordButton.addStyleClass("toggled-on"); 321 this.recordButton.title = WebInspector.UIString("Stop profiling."); 322 } else { 323 this.recordButton.removeStyleClass("toggled-on"); 324 this.recordButton.title = WebInspector.UIString("Start profiling."); 325 } 326 }, 327 328 resize: function() 329 { 330 var visibleView = this.visibleView; 331 if (visibleView && "resize" in visibleView) 332 visibleView.resize(); 333 }, 334 335 _updateInterface: function() 336 { 337 if (InspectorController.profilerEnabled()) { 338 this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); 339 this.enableToggleButton.addStyleClass("toggled-on"); 340 this.recordButton.removeStyleClass("hidden"); 341 this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); 342 this.panelEnablerView.visible = false; 343 } else { 344 this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); 345 this.enableToggleButton.removeStyleClass("toggled-on"); 346 this.recordButton.addStyleClass("hidden"); 347 this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); 348 this.panelEnablerView.visible = true; 349 } 350 }, 351 352 _recordClicked: function() 353 { 354 this.recording = !this.recording; 355 356 if (this.recording) 357 InspectorController.startProfiling(); 358 else 359 InspectorController.stopProfiling(); 360 }, 361 362 _enableProfiling: function() 363 { 364 if (InspectorController.profilerEnabled()) 365 return; 366 this._toggleProfiling(this.panelEnablerView.alwaysEnabled); 367 }, 368 369 _toggleProfiling: function(optionalAlways) 370 { 371 if (InspectorController.profilerEnabled()) 372 InspectorController.disableProfiler(true); 373 else 374 InspectorController.enableProfiler(!!optionalAlways); 375 }, 376 377 _populateProfiles: function() 378 { 379 if (this.sidebarTree.children.length) 380 return; 381 382 var profiles = InspectorController.profiles(); 383 var profilesLength = profiles.length; 384 for (var i = 0; i < profilesLength; ++i) { 385 var profile = profiles[i]; 386 this.addProfile(profile); 387 } 388 389 if (this.sidebarTree.children[0]) 390 this.sidebarTree.children[0].select(); 391 392 delete this._shouldPopulateProfiles; 393 }, 394 395 _startSidebarDragging: function(event) 396 { 397 WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); 398 }, 399 400 _sidebarDragging: function(event) 401 { 402 this._updateSidebarWidth(event.pageX); 403 404 event.preventDefault(); 405 }, 406 407 _endSidebarDragging: function(event) 408 { 409 WebInspector.elementDragEnd(event); 410 }, 411 412 _updateSidebarWidth: function(width) 413 { 414 if (this.sidebarElement.offsetWidth <= 0) { 415 // The stylesheet hasn't loaded yet or the window is closed, 416 // so we can't calculate what is need. Return early. 417 return; 418 } 419 420 if (!("_currentSidebarWidth" in this)) 421 this._currentSidebarWidth = this.sidebarElement.offsetWidth; 422 423 if (typeof width === "undefined") 424 width = this._currentSidebarWidth; 425 426 width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); 427 428 this._currentSidebarWidth = width; 429 430 this.sidebarElement.style.width = width + "px"; 431 this.profileViews.style.left = width + "px"; 432 this.profileViewStatusBarItemsContainer.style.left = width + "px"; 433 this.sidebarResizeElement.style.left = (width - 3) + "px"; 434 435 var visibleView = this.visibleView; 436 if (visibleView && "resize" in visibleView) 437 visibleView.resize(); 438 } 439} 440 441WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype; 442 443WebInspector.ProfileSidebarTreeElement = function(profile) 444{ 445 this.profile = profile; 446 447 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) 448 this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1); 449 450 WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false); 451 452 this.refreshTitles(); 453} 454 455WebInspector.ProfileSidebarTreeElement.prototype = { 456 onselect: function() 457 { 458 WebInspector.panels.profiles.showProfile(this.profile); 459 }, 460 461 get mainTitle() 462 { 463 if (this._mainTitle) 464 return this._mainTitle; 465 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) 466 return WebInspector.UIString("Profile %d", this._profileNumber); 467 return this.profile.title; 468 }, 469 470 set mainTitle(x) 471 { 472 this._mainTitle = x; 473 this.refreshTitles(); 474 }, 475 476 get subtitle() 477 { 478 // There is no subtitle. 479 }, 480 481 set subtitle(x) 482 { 483 // Can't change subtitle. 484 }, 485 486 set searchMatches(matches) 487 { 488 if (!matches) { 489 if (!this.bubbleElement) 490 return; 491 this.bubbleElement.removeStyleClass("search-matches"); 492 this.bubbleText = ""; 493 return; 494 } 495 496 this.bubbleText = matches; 497 this.bubbleElement.addStyleClass("search-matches"); 498 } 499} 500 501WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 502 503WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle) 504{ 505 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); 506} 507 508WebInspector.ProfileGroupSidebarTreeElement.prototype = { 509 onselect: function() 510 { 511 WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile); 512 } 513} 514 515WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; 516