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