/* * Copyright (C) 2008 Apple Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ const UserInitiatedProfileName = "org.webkit.profiles.user-initiated"; WebInspector.ProfilesPanel = function() { WebInspector.Panel.call(this); this.element.addStyleClass("profiles"); var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel."); var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower."); var panelEnablerButton = WebInspector.UIString("Enable Profiling"); this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton); this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this); this.element.appendChild(this.panelEnablerView.element); this.sidebarElement = document.createElement("div"); this.sidebarElement.id = "profiles-sidebar"; this.sidebarElement.className = "sidebar"; this.element.appendChild(this.sidebarElement); this.sidebarResizeElement = document.createElement("div"); this.sidebarResizeElement.className = "sidebar-resizer-vertical"; this.sidebarResizeElement.addEventListener("mousedown", this._startSidebarDragging.bind(this), false); this.element.appendChild(this.sidebarResizeElement); this.sidebarTreeElement = document.createElement("ol"); this.sidebarTreeElement.className = "sidebar-tree"; this.sidebarElement.appendChild(this.sidebarTreeElement); this.sidebarTree = new TreeOutline(this.sidebarTreeElement); this.profilesListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("CPU PROFILES"), null, true); this.sidebarTree.appendChild(this.profilesListTreeElement); this.profilesListTreeElement.expand(); this.snapshotsListTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("HEAP SNAPSHOTS"), null, true); if (Preferences.heapProfilerPresent) { this.sidebarTree.appendChild(this.snapshotsListTreeElement); this.snapshotsListTreeElement.expand(); } this.profileViews = document.createElement("div"); this.profileViews.id = "profile-views"; this.element.appendChild(this.profileViews); this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item"); this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false); this.recordButton = new WebInspector.StatusBarButton(WebInspector.UIString("Start profiling."), "record-profile-status-bar-item"); this.recordButton.addEventListener("click", this._recordClicked.bind(this), false); this.recording = false; this.snapshotButton = new WebInspector.StatusBarButton(WebInspector.UIString("Take heap snapshot."), "heap-snapshot-status-bar-item"); this.snapshotButton.visible = Preferences.heapProfilerPresent; this.snapshotButton.addEventListener("click", this._snapshotClicked.bind(this), false); this.profileViewStatusBarItemsContainer = document.createElement("div"); this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items"; this.reset(); } WebInspector.ProfilesPanel.prototype = { toolbarItemClass: "profiles", get toolbarItemLabel() { return WebInspector.UIString("Profiles"); }, get statusBarItems() { return [this.enableToggleButton.element, this.recordButton.element, this.snapshotButton.element, this.profileViewStatusBarItemsContainer]; }, show: function() { WebInspector.Panel.prototype.show.call(this); this._updateSidebarWidth(); if (this._shouldPopulateProfiles) this._populateProfiles(); }, populateInterface: function() { if (this.visible) this._populateProfiles(); else this._shouldPopulateProfiles = true; }, profilerWasEnabled: function() { this.reset(); this.populateInterface(); }, profilerWasDisabled: function() { this.reset(); }, reset: function() { if (this._profiles) { var profiledLength = this._profiles.length; for (var i = 0; i < profiledLength; ++i) { var profile = this._profiles[i]; delete profile._profileView; } } delete this.currentQuery; this.searchCanceled(); this._profiles = []; this._profilesIdMap = {}; this._profileGroups = {}; this._profileGroupsForLinks = {} this.sidebarTreeElement.removeStyleClass("some-expandable"); this.profilesListTreeElement.removeChildren(); this.snapshotsListTreeElement.removeChildren(); this.profileViews.removeChildren(); this.profileViewStatusBarItemsContainer.removeChildren(); this._updateInterface(); }, handleKeyEvent: function(event) { this.sidebarTree.handleKeyEvent(event); }, addProfile: function(profile) { this._profiles.push(profile); this._profilesIdMap[profile.uid] = profile; var sidebarParent = this.profilesListTreeElement; var small = false; var alternateTitle; if (profile.title.indexOf(UserInitiatedProfileName) !== 0) { if (!(profile.title in this._profileGroups)) this._profileGroups[profile.title] = []; var group = this._profileGroups[profile.title]; group.push(profile); if (group.length === 2) { // Make a group TreeElement now that there are 2 profiles. group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title); // Insert at the same index for the first profile of the group. var index = this.sidebarTree.children.indexOf(group[0]._profilesTreeElement); this.sidebarTree.insertChild(group._profilesTreeElement, index); // Move the first profile to the group. var selected = group[0]._profilesTreeElement.selected; this.sidebarTree.removeChild(group[0]._profilesTreeElement); group._profilesTreeElement.appendChild(group[0]._profilesTreeElement); if (selected) { group[0]._profilesTreeElement.select(); group[0]._profilesTreeElement.reveal(); } group[0]._profilesTreeElement.small = true; group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1); this.sidebarTreeElement.addStyleClass("some-expandable"); } if (group.length >= 2) { sidebarParent = group._profilesTreeElement; alternateTitle = WebInspector.UIString("Run %d", group.length); small = true; } } var profileTreeElement = new WebInspector.ProfileSidebarTreeElement(profile); profileTreeElement.small = small; if (alternateTitle) profileTreeElement.mainTitle = alternateTitle; profile._profilesTreeElement = profileTreeElement; sidebarParent.appendChild(profileTreeElement); }, showProfile: function(profile) { if (!profile) return; if (this.visibleView) this.visibleView.hide(); var view = this.profileViewForProfile(profile); view.show(this.profileViews); profile._profilesTreeElement.select(true); profile._profilesTreeElement.reveal(); this.visibleView = view; this.profileViewStatusBarItemsContainer.removeChildren(); var statusBarItems = view.statusBarItems; for (var i = 0; i < statusBarItems.length; ++i) this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]); }, showView: function(view) { this.showProfile(view.profile); }, profileViewForProfile: function(profile) { if (!profile) return null; if (!profile._profileView) profile._profileView = new WebInspector.ProfileView(profile); return profile._profileView; }, showProfileById: function(uid) { this.showProfile(this._profilesIdMap[uid]); }, closeVisibleView: function() { if (this.visibleView) this.visibleView.hide(); delete this.visibleView; }, displayTitleForProfileLink: function(title) { title = unescape(title); if (title.indexOf(UserInitiatedProfileName) === 0) { title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1)); } else { if (!(title in this._profileGroupsForLinks)) this._profileGroupsForLinks[title] = 0; groupNumber = ++this._profileGroupsForLinks[title]; if (groupNumber > 2) // The title is used in the console message announcing that a profile has started so it gets // incremented twice as often as it's displayed title += " " + WebInspector.UIString("Run %d", groupNumber / 2); } return title; }, get searchableViews() { var views = []; const visibleView = this.visibleView; if (visibleView && visibleView.performSearch) views.push(visibleView); var profilesLength = this._profiles.length; for (var i = 0; i < profilesLength; ++i) { var view = this.profileViewForProfile(this._profiles[i]); if (!view.performSearch || view === visibleView) continue; views.push(view); } return views; }, searchMatchFound: function(view, matches) { view.profile._profilesTreeElement.searchMatches = matches; }, searchCanceled: function(startingNewSearch) { WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch); if (!this._profiles) return; for (var i = 0; i < this._profiles.length; ++i) { var profile = this._profiles[i]; profile._profilesTreeElement.searchMatches = 0; } }, setRecordingProfile: function(isProfiling) { this.recording = isProfiling; if (isProfiling) { this.recordButton.toggled = true; this.recordButton.title = WebInspector.UIString("Stop profiling."); } else { this.recordButton.toggled = false; this.recordButton.title = WebInspector.UIString("Start profiling."); } }, resize: function() { var visibleView = this.visibleView; if (visibleView && "resize" in visibleView) visibleView.resize(); }, _updateInterface: function() { if (InspectorController.profilerEnabled()) { this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable."); this.enableToggleButton.toggled = true; this.recordButton.visible = true; if (Preferences.heapProfilerPresent) this.snapshotButton.visible = true; this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); this.panelEnablerView.visible = false; } else { this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable."); this.enableToggleButton.toggled = false; this.recordButton.visible = false; this.snapshotButton.visible = false; this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); this.panelEnablerView.visible = true; } }, _recordClicked: function() { this.recording = !this.recording; if (this.recording) InspectorController.startProfiling(); else InspectorController.stopProfiling(); }, _snapshotClicked: function() { InspectorController.takeHeapSnapshot(); }, _enableProfiling: function() { if (InspectorController.profilerEnabled()) return; this._toggleProfiling(this.panelEnablerView.alwaysEnabled); }, _toggleProfiling: function(optionalAlways) { if (InspectorController.profilerEnabled()) InspectorController.disableProfiler(true); else InspectorController.enableProfiler(!!optionalAlways); }, _populateProfiles: function() { // FIXME: This code needs to be adjusted when more profiling types are added. // Currently defaults to CPU profiles. var cpuProfiles = this.sidebarTree.children[0]; if (cpuProfiles.children.length) return; var profiles = InspectorController.profiles(); var profilesLength = profiles.length; for (var i = 0; i < profilesLength; ++i) { var profile = profiles[i]; this.addProfile(profile); } if (cpuProfiles.children[0]) cpuProfiles.children[0].select(); delete this._shouldPopulateProfiles; }, _startSidebarDragging: function(event) { WebInspector.elementDragStart(this.sidebarResizeElement, this._sidebarDragging.bind(this), this._endSidebarDragging.bind(this), event, "col-resize"); }, _sidebarDragging: function(event) { this._updateSidebarWidth(event.pageX); event.preventDefault(); }, _endSidebarDragging: function(event) { WebInspector.elementDragEnd(event); }, _updateSidebarWidth: function(width) { if (this.sidebarElement.offsetWidth <= 0) { // The stylesheet hasn't loaded yet or the window is closed, // so we can't calculate what is need. Return early. return; } if (!("_currentSidebarWidth" in this)) this._currentSidebarWidth = this.sidebarElement.offsetWidth; if (typeof width === "undefined") width = this._currentSidebarWidth; width = Number.constrain(width, Preferences.minSidebarWidth, window.innerWidth / 2); this._currentSidebarWidth = width; this.sidebarElement.style.width = width + "px"; this.profileViews.style.left = width + "px"; this.profileViewStatusBarItemsContainer.style.left = width + "px"; this.sidebarResizeElement.style.left = (width - 3) + "px"; var visibleView = this.visibleView; if (visibleView && "resize" in visibleView) visibleView.resize(); } } WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype; WebInspector.ProfileSidebarTreeElement = function(profile) { this.profile = profile; if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1); WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false); this.refreshTitles(); } WebInspector.ProfileSidebarTreeElement.prototype = { onselect: function() { WebInspector.panels.profiles.showProfile(this.profile); }, get mainTitle() { if (this._mainTitle) return this._mainTitle; if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) return WebInspector.UIString("Profile %d", this._profileNumber); return this.profile.title; }, set mainTitle(x) { this._mainTitle = x; this.refreshTitles(); }, get subtitle() { // There is no subtitle. }, set subtitle(x) { // Can't change subtitle. }, set searchMatches(matches) { if (!matches) { if (!this.bubbleElement) return; this.bubbleElement.removeStyleClass("search-matches"); this.bubbleText = ""; return; } this.bubbleText = matches; this.bubbleElement.addStyleClass("search-matches"); } } WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype; WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle) { WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true); } WebInspector.ProfileGroupSidebarTreeElement.prototype = { onselect: function() { WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile); } } WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;