Rubber-stamped by Brady Eidson.
[webbrowser.git] / WebCore / inspector / front-end / ProfilesPanel.js
blobb276ecb7428746d404243e11d0076a5c90275274
1 /*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
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.
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.
26 const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
28 WebInspector.ProfileType = function(id, name)
30 this._id = id;
31 this._name = name;
34 WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/;
36 WebInspector.ProfileType.prototype = {
37 get buttonTooltip()
39 return "";
42 get buttonStyle()
44 return undefined;
47 get buttonCaption()
49 return this.name;
52 get id()
54 return this._id;
57 get name()
59 return this._name;
62 buttonClicked: function()
66 viewForProfile: function(profile)
68 if (!profile._profileView)
69 profile._profileView = this.createView(profile);
70 return profile._profileView;
73 // Must be implemented by subclasses.
74 createView: function(profile)
76 throw new Error("Needs implemented.");
79 // Must be implemented by subclasses.
80 createSidebarTreeElementForProfile: function(profile)
82 throw new Error("Needs implemented.");
86 WebInspector.ProfilesPanel = function()
88 WebInspector.Panel.call(this);
90 this.createSidebar();
92 this.element.addStyleClass("profiles");
93 this._profileTypesByIdMap = {};
94 this._profileTypeButtonsByIdMap = {};
96 var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
97 var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
98 var panelEnablerButton = WebInspector.UIString("Enable Profiling");
99 this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
100 this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);
102 this.element.appendChild(this.panelEnablerView.element);
104 this.profileViews = document.createElement("div");
105 this.profileViews.id = "profile-views";
106 this.element.appendChild(this.profileViews);
108 this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
109 this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);
111 this.profileViewStatusBarItemsContainer = document.createElement("div");
112 this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";
114 this._profiles = [];
115 this.reset();
118 WebInspector.ProfilesPanel.prototype = {
119 toolbarItemClass: "profiles",
121 get toolbarItemLabel()
123 return WebInspector.UIString("Profiles");
126 get statusBarItems()
128 function clickHandler(profileType, buttonElement)
130 profileType.buttonClicked.call(profileType);
131 this.updateProfileTypeButtons();
134 var items = [this.enableToggleButton.element];
135 // FIXME: Generate a single "combo-button".
136 for (var typeId in this._profileTypesByIdMap) {
137 var profileType = this.getProfileType(typeId);
138 if (profileType.buttonStyle) {
139 var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
140 this._profileTypeButtonsByIdMap[typeId] = button.element;
141 button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false);
142 items.push(button.element);
145 items.push(this.profileViewStatusBarItemsContainer);
146 return items;
149 show: function()
151 WebInspector.Panel.prototype.show.call(this);
152 if (this._shouldPopulateProfiles)
153 this._populateProfiles();
156 populateInterface: function()
158 if (this.visible)
159 this._populateProfiles();
160 else
161 this._shouldPopulateProfiles = true;
164 profilerWasEnabled: function()
166 this.reset();
167 this.populateInterface();
170 profilerWasDisabled: function()
172 this.reset();
175 reset: function()
177 for (var i = 0; i < this._profiles.length; ++i)
178 delete this._profiles[i]._profileView;
180 delete this.currentQuery;
181 this.searchCanceled();
183 this._profiles = [];
184 this._profilesIdMap = {};
185 this._profileGroups = {};
186 this._profileGroupsForLinks = {}
188 this.sidebarTreeElement.removeStyleClass("some-expandable");
190 for (var typeId in this._profileTypesByIdMap)
191 this.getProfileType(typeId).treeElement.removeChildren();
193 this.profileViews.removeChildren();
195 this.profileViewStatusBarItemsContainer.removeChildren();
197 this._updateInterface();
200 registerProfileType: function(profileType)
202 this._profileTypesByIdMap[profileType.id] = profileType;
203 profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true);
204 this.sidebarTree.appendChild(profileType.treeElement);
205 profileType.treeElement.expand();
208 _makeKey: function(text, profileTypeId)
210 return escape(text) + '/' + escape(profileTypeId);
213 addProfileHeader: function(profile)
215 var typeId = profile.typeId;
216 var profileType = this.getProfileType(typeId);
217 var sidebarParent = profileType.treeElement;
218 var small = false;
219 var alternateTitle;
221 profile.__profilesPanelProfileType = profileType;
222 this._profiles.push(profile);
223 this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile;
225 if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
226 var profileTitleKey = this._makeKey(profile.title, typeId);
227 if (!(profileTitleKey in this._profileGroups))
228 this._profileGroups[profileTitleKey] = [];
230 var group = this._profileGroups[profileTitleKey];
231 group.push(profile);
233 if (group.length === 2) {
234 // Make a group TreeElement now that there are 2 profiles.
235 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);
237 // Insert at the same index for the first profile of the group.
238 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
239 sidebarParent.insertChild(group._profilesTreeElement, index);
241 // Move the first profile to the group.
242 var selected = group[0]._profilesTreeElement.selected;
243 sidebarParent.removeChild(group[0]._profilesTreeElement);
244 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
245 if (selected) {
246 group[0]._profilesTreeElement.select();
247 group[0]._profilesTreeElement.reveal();
250 group[0]._profilesTreeElement.small = true;
251 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
253 this.sidebarTreeElement.addStyleClass("some-expandable");
256 if (group.length >= 2) {
257 sidebarParent = group._profilesTreeElement;
258 alternateTitle = WebInspector.UIString("Run %d", group.length);
259 small = true;
263 var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile);
264 profileTreeElement.small = small;
265 if (alternateTitle)
266 profileTreeElement.mainTitle = alternateTitle;
267 profile._profilesTreeElement = profileTreeElement;
269 sidebarParent.appendChild(profileTreeElement);
270 if (!this.visibleView)
271 this.showProfile(profile);
274 showProfile: function(profile)
276 if (!profile)
277 return;
279 if (this.visibleView)
280 this.visibleView.hide();
282 var view = profile.__profilesPanelProfileType.viewForProfile(profile);
284 view.show(this.profileViews);
286 profile._profilesTreeElement.select(true);
287 profile._profilesTreeElement.reveal();
289 this.visibleView = view;
291 this.profileViewStatusBarItemsContainer.removeChildren();
293 var statusBarItems = view.statusBarItems;
294 for (var i = 0; i < statusBarItems.length; ++i)
295 this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
298 showView: function(view)
300 this.showProfile(view.profile);
303 getProfileType: function(typeId)
305 return this._profileTypesByIdMap[typeId];
308 showProfileForURL: function(url)
310 var match = url.match(WebInspector.ProfileType.URLRegExp);
311 if (!match)
312 return;
313 this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]);
316 updateProfileTypeButtons: function()
318 for (var typeId in this._profileTypeButtonsByIdMap) {
319 var buttonElement = this._profileTypeButtonsByIdMap[typeId];
320 var profileType = this.getProfileType(typeId);
321 buttonElement.className = profileType.buttonStyle;
322 buttonElement.title = profileType.buttonTooltip;
323 // FIXME: Apply profileType.buttonCaption once captions are added to button controls.
327 closeVisibleView: function()
329 if (this.visibleView)
330 this.visibleView.hide();
331 delete this.visibleView;
334 displayTitleForProfileLink: function(title, typeId)
336 title = unescape(title);
337 if (title.indexOf(UserInitiatedProfileName) === 0) {
338 title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
339 } else {
340 var titleKey = this._makeKey(title, typeId);
341 if (!(titleKey in this._profileGroupsForLinks))
342 this._profileGroupsForLinks[titleKey] = 0;
344 groupNumber = ++this._profileGroupsForLinks[titleKey];
346 if (groupNumber > 2)
347 // The title is used in the console message announcing that a profile has started so it gets
348 // incremented twice as often as it's displayed
349 title += " " + WebInspector.UIString("Run %d", groupNumber / 2);
352 return title;
355 get searchableViews()
357 var views = [];
359 const visibleView = this.visibleView;
360 if (visibleView && visibleView.performSearch)
361 views.push(visibleView);
363 var profilesLength = this._profiles.length;
364 for (var i = 0; i < profilesLength; ++i) {
365 var profile = this._profiles[i];
366 var view = profile.__profilesPanelProfileType.viewForProfile(profile);
367 if (!view.performSearch || view === visibleView)
368 continue;
369 views.push(view);
372 return views;
375 searchMatchFound: function(view, matches)
377 view.profile._profilesTreeElement.searchMatches = matches;
380 searchCanceled: function(startingNewSearch)
382 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
384 if (!this._profiles)
385 return;
387 for (var i = 0; i < this._profiles.length; ++i) {
388 var profile = this._profiles[i];
389 profile._profilesTreeElement.searchMatches = 0;
393 resize: function()
395 var visibleView = this.visibleView;
396 if (visibleView && "resize" in visibleView)
397 visibleView.resize();
400 _updateInterface: function()
402 // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
403 if (InspectorBackend.profilerEnabled()) {
404 this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
405 this.enableToggleButton.toggled = true;
406 for (var typeId in this._profileTypeButtonsByIdMap)
407 this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden");
408 this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
409 this.panelEnablerView.visible = false;
410 } else {
411 this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
412 this.enableToggleButton.toggled = false;
413 for (var typeId in this._profileTypeButtonsByIdMap)
414 this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden");
415 this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
416 this.panelEnablerView.visible = true;
420 _enableProfiling: function()
422 if (InspectorBackend.profilerEnabled())
423 return;
424 this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
427 _toggleProfiling: function(optionalAlways)
429 if (InspectorBackend.profilerEnabled())
430 InspectorBackend.disableProfiler(true);
431 else
432 InspectorBackend.enableProfiler(!!optionalAlways);
435 _populateProfiles: function()
437 var sidebarTreeChildrenCount = this.sidebarTree.children.length;
438 for (var i = 0; i < sidebarTreeChildrenCount; ++i) {
439 var treeElement = this.sidebarTree.children[i];
440 if (treeElement.children.length)
441 return;
444 function populateCallback(profileHeaders) {
445 profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
446 var profileHeadersLength = profileHeaders.length;
447 for (var i = 0; i < profileHeadersLength; ++i)
448 WebInspector.addProfileHeader(profileHeaders[i]);
451 var callId = WebInspector.Callback.wrap(populateCallback);
452 InspectorBackend.getProfileHeaders(callId);
454 delete this._shouldPopulateProfiles;
457 updateMainViewWidth: function(width)
459 this.profileViews.style.left = width + "px";
460 this.profileViewStatusBarItemsContainer.style.left = width + "px";
464 WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
466 WebInspector.ProfileSidebarTreeElement = function(profile)
468 this.profile = profile;
470 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
471 this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
473 WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
475 this.refreshTitles();
478 WebInspector.ProfileSidebarTreeElement.prototype = {
479 onselect: function()
481 WebInspector.panels.profiles.showProfile(this.profile);
484 get mainTitle()
486 if (this._mainTitle)
487 return this._mainTitle;
488 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
489 return WebInspector.UIString("Profile %d", this._profileNumber);
490 return this.profile.title;
493 set mainTitle(x)
495 this._mainTitle = x;
496 this.refreshTitles();
499 get subtitle()
501 // There is no subtitle.
504 set subtitle(x)
506 // Can't change subtitle.
509 set searchMatches(matches)
511 if (!matches) {
512 if (!this.bubbleElement)
513 return;
514 this.bubbleElement.removeStyleClass("search-matches");
515 this.bubbleText = "";
516 return;
519 this.bubbleText = matches;
520 this.bubbleElement.addStyleClass("search-matches");
524 WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
526 WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
528 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
531 WebInspector.ProfileGroupSidebarTreeElement.prototype = {
532 onselect: function()
534 WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
538 WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
540 WebInspector.didGetProfileHeaders = WebInspector.Callback.processCallback;
541 WebInspector.didGetProfile = WebInspector.Callback.processCallback;