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.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 // Must be implemented by subclasses. | |
74 createView: function(profile) | |
75 { | |
76 throw new Error("Needs implemented."); | |
77 }, | |
78 | |
79 // Must be implemented by subclasses. | |
80 createSidebarTreeElementForProfile: function(profile) | |
81 { | |
82 throw new Error("Needs implemented."); | |
83 } | |
84 } | |
85 | |
86 WebInspector.ProfilesPanel = function() | |
87 { | |
88 WebInspector.Panel.call(this, true); | |
89 | |
90 this.element.addStyleClass("profiles"); | |
91 this._profileTypesByIdMap = {}; | |
92 this._profileTypeButtonsByIdMap = {}; | |
93 | |
94 var panelEnablerHeading = WebInspector.UIString("You need to enable profilin
g before you can use the Profiles panel."); | |
95 var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will
make scripts run slower."); | |
96 var panelEnablerButton = WebInspector.UIString("Enable Profiling"); | |
97 this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelE
nablerHeading, panelEnablerDisclaimer, panelEnablerButton); | |
98 this.panelEnablerView.addEventListener("enable clicked", this._enableProfili
ng, this); | |
99 | |
100 this.element.appendChild(this.panelEnablerView.element); | |
101 | |
102 this.profileViews = document.createElement("div"); | |
103 this.profileViews.id = "profile-views"; | |
104 this.element.appendChild(this.profileViews); | |
105 | |
106 this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggl
e-status-bar-item"); | |
107 this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind
(this), false); | |
108 | |
109 this.profileViewStatusBarItemsContainer = document.createElement("div"); | |
110 this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items"
; | |
111 | |
112 this._profiles = []; | |
113 this.reset(); | |
114 } | |
115 | |
116 WebInspector.ProfilesPanel.prototype = { | |
117 toolbarItemClass: "profiles", | |
118 | |
119 get toolbarItemLabel() | |
120 { | |
121 return WebInspector.UIString("Profiles"); | |
122 }, | |
123 | |
124 get statusBarItems() | |
125 { | |
126 function clickHandler(profileType, buttonElement) | |
127 { | |
128 profileType.buttonClicked.call(profileType); | |
129 this.updateProfileTypeButtons(); | |
130 } | |
131 | |
132 var items = [this.enableToggleButton.element]; | |
133 // FIXME: Generate a single "combo-button". | |
134 for (var typeId in this._profileTypesByIdMap) { | |
135 var profileType = this.getProfileType(typeId); | |
136 if (profileType.buttonStyle) { | |
137 var button = new WebInspector.StatusBarButton(profileType.button
Tooltip, profileType.buttonStyle, profileType.buttonCaption); | |
138 this._profileTypeButtonsByIdMap[typeId] = button.element; | |
139 button.element.addEventListener("click", clickHandler.bind(this,
profileType, button.element), false); | |
140 items.push(button.element); | |
141 } | |
142 } | |
143 items.push(this.profileViewStatusBarItemsContainer); | |
144 return items; | |
145 }, | |
146 | |
147 show: function() | |
148 { | |
149 WebInspector.Panel.prototype.show.call(this); | |
150 if (this._shouldPopulateProfiles) | |
151 this._populateProfiles(); | |
152 }, | |
153 | |
154 populateInterface: function() | |
155 { | |
156 if (this.visible) | |
157 this._populateProfiles(); | |
158 else | |
159 this._shouldPopulateProfiles = true; | |
160 }, | |
161 | |
162 profilerWasEnabled: function() | |
163 { | |
164 this.reset(); | |
165 this.populateInterface(); | |
166 }, | |
167 | |
168 profilerWasDisabled: function() | |
169 { | |
170 this.reset(); | |
171 }, | |
172 | |
173 reset: function() | |
174 { | |
175 for (var i = 0; i < this._profiles.length; ++i) | |
176 delete this._profiles[i]._profileView; | |
177 | |
178 delete this.currentQuery; | |
179 this.searchCanceled(); | |
180 | |
181 this._profiles = []; | |
182 this._profilesIdMap = {}; | |
183 this._profileGroups = {}; | |
184 this._profileGroupsForLinks = {} | |
185 | |
186 this.sidebarTreeElement.removeStyleClass("some-expandable"); | |
187 | |
188 for (var typeId in this._profileTypesByIdMap) | |
189 this.getProfileType(typeId).treeElement.removeChildren(); | |
190 | |
191 this.profileViews.removeChildren(); | |
192 | |
193 this.profileViewStatusBarItemsContainer.removeChildren(); | |
194 | |
195 this._updateInterface(); | |
196 }, | |
197 | |
198 registerProfileType: function(profileType) | |
199 { | |
200 this._profileTypesByIdMap[profileType.id] = profileType; | |
201 profileType.treeElement = new WebInspector.SidebarSectionTreeElement(pro
fileType.name, null, true); | |
202 this.sidebarTree.appendChild(profileType.treeElement); | |
203 profileType.treeElement.expand(); | |
204 }, | |
205 | |
206 _makeKey: function(text, profileTypeId) | |
207 { | |
208 return escape(text) + '/' + escape(profileTypeId); | |
209 }, | |
210 | |
211 addProfileHeader: function(typeId, profile) | |
212 { | |
213 var profileType = this.getProfileType(typeId); | |
214 var sidebarParent = profileType.treeElement; | |
215 var small = false; | |
216 var alternateTitle; | |
217 | |
218 profile.__profilesPanelProfileType = profileType; | |
219 this._profiles.push(profile); | |
220 this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile; | |
221 | |
222 if (profile.title.indexOf(UserInitiatedProfileName) !== 0) { | |
223 var profileTitleKey = this._makeKey(profile.title, typeId); | |
224 if (!(profileTitleKey in this._profileGroups)) | |
225 this._profileGroups[profileTitleKey] = []; | |
226 | |
227 var group = this._profileGroups[profileTitleKey]; | |
228 group.push(profile); | |
229 | |
230 if (group.length === 2) { | |
231 // Make a group TreeElement now that there are 2 profiles. | |
232 group._profilesTreeElement = new WebInspector.ProfileGroupSideba
rTreeElement(profile.title); | |
233 | |
234 // Insert at the same index for the first profile of the group. | |
235 var index = sidebarParent.children.indexOf(group[0]._profilesTre
eElement); | |
236 sidebarParent.insertChild(group._profilesTreeElement, index); | |
237 | |
238 // Move the first profile to the group. | |
239 var selected = group[0]._profilesTreeElement.selected; | |
240 sidebarParent.removeChild(group[0]._profilesTreeElement); | |
241 group._profilesTreeElement.appendChild(group[0]._profilesTreeEle
ment); | |
242 if (selected) { | |
243 group[0]._profilesTreeElement.select(); | |
244 group[0]._profilesTreeElement.reveal(); | |
245 } | |
246 | |
247 group[0]._profilesTreeElement.small = true; | |
248 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString(
"Run %d", 1); | |
249 | |
250 this.sidebarTreeElement.addStyleClass("some-expandable"); | |
251 } | |
252 | |
253 if (group.length >= 2) { | |
254 sidebarParent = group._profilesTreeElement; | |
255 alternateTitle = WebInspector.UIString("Run %d", group.length); | |
256 small = true; | |
257 } | |
258 } | |
259 | |
260 var profileTreeElement = profileType.createSidebarTreeElementForProfile(
profile); | |
261 profileTreeElement.small = small; | |
262 if (alternateTitle) | |
263 profileTreeElement.mainTitle = alternateTitle; | |
264 profile._profilesTreeElement = profileTreeElement; | |
265 | |
266 sidebarParent.appendChild(profileTreeElement); | |
267 if (!this.visibleView) | |
268 this.showProfile(profile); | |
269 }, | |
270 | |
271 showProfile: function(profile) | |
272 { | |
273 if (!profile) | |
274 return; | |
275 | |
276 if (this.visibleView) | |
277 this.visibleView.hide(); | |
278 | |
279 var view = profile.__profilesPanelProfileType.viewForProfile(profile); | |
280 | |
281 view.show(this.profileViews); | |
282 | |
283 profile._profilesTreeElement.select(true); | |
284 profile._profilesTreeElement.reveal(); | |
285 | |
286 this.visibleView = view; | |
287 | |
288 this.profileViewStatusBarItemsContainer.removeChildren(); | |
289 | |
290 var statusBarItems = view.statusBarItems; | |
291 for (var i = 0; i < statusBarItems.length; ++i) | |
292 this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i
]); | |
293 }, | |
294 | |
295 showView: function(view) | |
296 { | |
297 this.showProfile(view.profile); | |
298 }, | |
299 | |
300 getProfileType: function(typeId) | |
301 { | |
302 return this._profileTypesByIdMap[typeId]; | |
303 }, | |
304 | |
305 showProfileForURL: function(url) | |
306 { | |
307 var match = url.match(WebInspector.ProfileType.URLRegExp); | |
308 if (!match) | |
309 return; | |
310 this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])])
; | |
311 }, | |
312 | |
313 updateProfileTypeButtons: function() | |
314 { | |
315 for (var typeId in this._profileTypeButtonsByIdMap) { | |
316 var buttonElement = this._profileTypeButtonsByIdMap[typeId]; | |
317 var profileType = this.getProfileType(typeId); | |
318 buttonElement.className = profileType.buttonStyle; | |
319 buttonElement.title = profileType.buttonTooltip; | |
320 // FIXME: Apply profileType.buttonCaption once captions are added to
button controls. | |
321 } | |
322 }, | |
323 | |
324 closeVisibleView: function() | |
325 { | |
326 if (this.visibleView) | |
327 this.visibleView.hide(); | |
328 delete this.visibleView; | |
329 }, | |
330 | |
331 displayTitleForProfileLink: function(title, typeId) | |
332 { | |
333 title = unescape(title); | |
334 if (title.indexOf(UserInitiatedProfileName) === 0) { | |
335 title = WebInspector.UIString("Profile %d", title.substring(UserInit
iatedProfileName.length + 1)); | |
336 } else { | |
337 var titleKey = this._makeKey(title, typeId); | |
338 if (!(titleKey in this._profileGroupsForLinks)) | |
339 this._profileGroupsForLinks[titleKey] = 0; | |
340 | |
341 groupNumber = ++this._profileGroupsForLinks[titleKey]; | |
342 | |
343 if (groupNumber > 2) | |
344 // The title is used in the console message announcing that a pr
ofile has started so it gets | |
345 // incremented twice as often as it's displayed | |
346 title += " " + WebInspector.UIString("Run %d", groupNumber / 2); | |
347 } | |
348 | |
349 return title; | |
350 }, | |
351 | |
352 get searchableViews() | |
353 { | |
354 var views = []; | |
355 | |
356 const visibleView = this.visibleView; | |
357 if (visibleView && visibleView.performSearch) | |
358 views.push(visibleView); | |
359 | |
360 var profilesLength = this._profiles.length; | |
361 for (var i = 0; i < profilesLength; ++i) { | |
362 var view = this._profiles[i].viewForProfile(); | |
363 if (!view.performSearch || view === visibleView) | |
364 continue; | |
365 views.push(view); | |
366 } | |
367 | |
368 return views; | |
369 }, | |
370 | |
371 searchMatchFound: function(view, matches) | |
372 { | |
373 view.profile._profilesTreeElement.searchMatches = matches; | |
374 }, | |
375 | |
376 searchCanceled: function(startingNewSearch) | |
377 { | |
378 WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch
); | |
379 | |
380 if (!this._profiles) | |
381 return; | |
382 | |
383 for (var i = 0; i < this._profiles.length; ++i) { | |
384 var profile = this._profiles[i]; | |
385 profile._profilesTreeElement.searchMatches = 0; | |
386 } | |
387 }, | |
388 | |
389 resize: function() | |
390 { | |
391 var visibleView = this.visibleView; | |
392 if (visibleView && "resize" in visibleView) | |
393 visibleView.resize(); | |
394 }, | |
395 | |
396 _updateInterface: function() | |
397 { | |
398 // FIXME: Replace ProfileType-specific button visibility changes by a si
ngle ProfileType-agnostic "combo-button" visibility change. | |
399 if (InspectorController.profilerEnabled()) { | |
400 this.enableToggleButton.title = WebInspector.UIString("Profiling ena
bled. Click to disable."); | |
401 this.enableToggleButton.toggled = true; | |
402 for (var typeId in this._profileTypeButtonsByIdMap) | |
403 this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden
"); | |
404 this.profileViewStatusBarItemsContainer.removeStyleClass("hidden"); | |
405 this.panelEnablerView.visible = false; | |
406 } else { | |
407 this.enableToggleButton.title = WebInspector.UIString("Profiling dis
abled. Click to enable."); | |
408 this.enableToggleButton.toggled = false; | |
409 for (var typeId in this._profileTypeButtonsByIdMap) | |
410 this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden"); | |
411 this.profileViewStatusBarItemsContainer.addStyleClass("hidden"); | |
412 this.panelEnablerView.visible = true; | |
413 } | |
414 }, | |
415 | |
416 _enableProfiling: function() | |
417 { | |
418 if (InspectorController.profilerEnabled()) | |
419 return; | |
420 this._toggleProfiling(this.panelEnablerView.alwaysEnabled); | |
421 }, | |
422 | |
423 _toggleProfiling: function(optionalAlways) | |
424 { | |
425 if (InspectorController.profilerEnabled()) | |
426 InspectorController.disableProfiler(true); | |
427 else | |
428 InspectorController.enableProfiler(!!optionalAlways); | |
429 }, | |
430 | |
431 _populateProfiles: function() | |
432 { | |
433 // FIXME: This code needs to be adjusted when more profiling types are a
dded. | |
434 // Currently defaults to CPU profiles. | |
435 var cpuProfiles = this.getProfileType(WebInspector.CPUProfileType.TypeId
).treeElement; | |
436 if (cpuProfiles.children.length) | |
437 return; | |
438 | |
439 function populateCallback(profileHeaders) { | |
440 profileHeaders.sort(function(a, b) { return a.uid - b.uid; }); | |
441 var profileHeadersLength = profileHeaders.length; | |
442 for (var i = 0; i < profileHeadersLength; ++i) | |
443 WebInspector.addProfileHeader(profileHeaders[i]); | |
444 } | |
445 | |
446 var callId = WebInspector.Callback.wrap(populateCallback); | |
447 InspectorController.getProfileHeaders(callId); | |
448 | |
449 delete this._shouldPopulateProfiles; | |
450 }, | |
451 | |
452 setMainViewWidth: function(width) | |
453 { | |
454 this.profileViews.style.left = width + "px"; | |
455 this.profileViewStatusBarItemsContainer.style.left = width + "px"; | |
456 } | |
457 } | |
458 | |
459 WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype; | |
460 | |
461 WebInspector.ProfileSidebarTreeElement = function(profile) | |
462 { | |
463 this.profile = profile; | |
464 | |
465 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) | |
466 this._profileNumber = this.profile.title.substring(UserInitiatedProfileN
ame.length + 1); | |
467 | |
468 WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "",
"", profile, false); | |
469 | |
470 this.refreshTitles(); | |
471 } | |
472 | |
473 WebInspector.ProfileSidebarTreeElement.prototype = { | |
474 onselect: function() | |
475 { | |
476 WebInspector.panels.profiles.showProfile(this.profile); | |
477 }, | |
478 | |
479 get mainTitle() | |
480 { | |
481 if (this._mainTitle) | |
482 return this._mainTitle; | |
483 if (this.profile.title.indexOf(UserInitiatedProfileName) === 0) | |
484 return WebInspector.UIString("Profile %d", this._profileNumber); | |
485 return this.profile.title; | |
486 }, | |
487 | |
488 set mainTitle(x) | |
489 { | |
490 this._mainTitle = x; | |
491 this.refreshTitles(); | |
492 }, | |
493 | |
494 get subtitle() | |
495 { | |
496 // There is no subtitle. | |
497 }, | |
498 | |
499 set subtitle(x) | |
500 { | |
501 // Can't change subtitle. | |
502 }, | |
503 | |
504 set searchMatches(matches) | |
505 { | |
506 if (!matches) { | |
507 if (!this.bubbleElement) | |
508 return; | |
509 this.bubbleElement.removeStyleClass("search-matches"); | |
510 this.bubbleText = ""; | |
511 return; | |
512 } | |
513 | |
514 this.bubbleText = matches; | |
515 this.bubbleElement.addStyleClass("search-matches"); | |
516 } | |
517 } | |
518 | |
519 WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.Sideba
rTreeElement.prototype; | |
520 | |
521 WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle) | |
522 { | |
523 WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item"
, title, subtitle, null, true); | |
524 } | |
525 | |
526 WebInspector.ProfileGroupSidebarTreeElement.prototype = { | |
527 onselect: function() | |
528 { | |
529 WebInspector.panels.profiles.showProfile(this.children[this.children.len
gth - 1].profile); | |
530 } | |
531 } | |
532 | |
533 WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.S
idebarTreeElement.prototype; | |
534 | |
535 WebInspector.didGetProfileHeaders = WebInspector.Callback.processCallback; | |
536 WebInspector.didGetProfile = WebInspector.Callback.processCallback; | |
OLD | NEW |