b14d8fd2d1f0da0fd78e1beada9f7aa80a7c137d
[WebKit-https.git] / Source / WebCore / inspector / front-end / ProfilesPanel.js
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     get welcomeMessage()
74     {
75         return "";
76     },
77
78     // Must be implemented by subclasses.
79     createView: function(profile)
80     {
81         throw new Error("Needs implemented.");
82     },
83
84     // Must be implemented by subclasses.
85     createSidebarTreeElementForProfile: function(profile)
86     {
87         throw new Error("Needs implemented.");
88     }
89 }
90
91 WebInspector.ProfilesPanel = function()
92 {
93     WebInspector.Panel.call(this, "profiles");
94
95     this.createSidebar();
96
97     this._profileTypesByIdMap = {};
98     this._profileTypeButtonsByIdMap = {};
99
100     var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
101     var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
102     var panelEnablerButton = WebInspector.UIString("Enable Profiling");
103     this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
104     this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);
105
106     this.element.appendChild(this.panelEnablerView.element);
107
108     this.profileViews = document.createElement("div");
109     this.profileViews.id = "profile-views";
110     this.element.appendChild(this.profileViews);
111
112     this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
113     this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);
114
115     this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item");
116     this.clearResultsButton.addEventListener("click", this._clearProfiles.bind(this), false);
117
118     this.profileViewStatusBarItemsContainer = document.createElement("div");
119     this.profileViewStatusBarItemsContainer.className = "status-bar-items";
120
121     this.welcomeView = new WebInspector.WelcomeView("profiles", WebInspector.UIString("Welcome to the Profiles panel"));
122     this.element.appendChild(this.welcomeView.element);
123
124     this._profiles = [];
125     this._profilerEnabled = Preferences.profilerAlwaysEnabled;
126     this._reset();
127
128     this._registerProfileType(new WebInspector.CPUProfileType());
129     if (Preferences.heapProfilerPresent)
130         this._registerProfileType(new WebInspector.DetailedHeapshotProfileType());
131
132     InspectorBackend.registerDomainDispatcher("Profiler", new WebInspector.ProfilerDispatcher(this));
133
134     if (Preferences.profilerAlwaysEnabled || WebInspector.settings.profilerEnabled.get())
135         ProfilerAgent.enable();
136     else {
137         function onProfilerEnebled(error, value) {
138             if (value)
139                 this._profilerWasEnabled();
140         }
141         ProfilerAgent.isEnabled(onProfilerEnebled.bind(this));
142     }
143 }
144
145 WebInspector.ProfilesPanel.prototype = {
146     get toolbarItemLabel()
147     {
148         return WebInspector.UIString("Profiles");
149     },
150
151     get statusBarItems()
152     {
153         function clickHandler(profileType, buttonElement)
154         {
155             profileType.buttonClicked.call(profileType);
156             this.updateProfileTypeButtons();
157         }
158
159         var items = [this.enableToggleButton.element];
160         // FIXME: Generate a single "combo-button".
161         for (var typeId in this._profileTypesByIdMap) {
162             var profileType = this.getProfileType(typeId);
163             if (profileType.buttonStyle) {
164                 var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
165                 this._profileTypeButtonsByIdMap[typeId] = button.element;
166                 button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false);
167                 items.push(button.element);
168             }
169         }
170         items.push(this.clearResultsButton.element, this.profileViewStatusBarItemsContainer);
171         return items;
172     },
173
174     show: function()
175     {
176         WebInspector.Panel.prototype.show.call(this);
177         this._populateProfiles();
178     },
179
180     _profilerWasEnabled: function()
181     {
182         if (this._profilerEnabled)
183             return;
184
185         this._profilerEnabled = true;
186
187         this._reset();
188         if (this.visible)
189             this._populateProfiles();
190     },
191
192     _profilerWasDisabled: function()
193     {
194         if (!this._profilerEnabled)
195             return;
196
197         this._profilerEnabled = false;
198         this._reset();
199     },
200
201     _reset: function()
202     {
203         WebInspector.Panel.prototype.reset.call(this);
204
205         for (var i = 0; i < this._profiles.length; ++i) {
206             var view = this._profiles[i]._profileView;
207             if (view && ("dispose" in view))
208                 view.dispose();
209             delete this._profiles[i]._profileView;
210         }
211         delete this.visibleView;
212
213         delete this.currentQuery;
214         this.searchCanceled();
215
216         this._profiles = [];
217         this._profilesIdMap = {};
218         this._profileGroups = {};
219         this._profileGroupsForLinks = {};
220         this._profilesWereRequested = false;
221
222         this.sidebarTreeElement.removeStyleClass("some-expandable");
223
224         for (var typeId in this._profileTypesByIdMap)
225             this.getProfileType(typeId).treeElement.removeChildren();
226
227         this.profileViews.removeChildren();
228
229         this.profileViewStatusBarItemsContainer.removeChildren();
230
231         this.removeAllListeners();
232
233         this._updateInterface();
234         this.welcomeView.show();
235     },
236
237     _clearProfiles: function()
238     {
239         ProfilerAgent.clearProfiles();
240         this._reset();
241     },
242
243     _registerProfileType: function(profileType)
244     {
245         this._profileTypesByIdMap[profileType.id] = profileType;
246         profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true);
247         this.sidebarTree.appendChild(profileType.treeElement);
248         profileType.treeElement.expand();
249         this._addWelcomeMessage(profileType);
250     },
251
252     _addWelcomeMessage: function(profileType)
253     {
254         var message = profileType.welcomeMessage;
255         // Message text is supposed to have a '%s' substring as a placeholder
256         // for a status bar button. If it is there, we split the message in two
257         // parts, and insert the button between them.
258         var buttonPos = message.indexOf("%s");
259         if (buttonPos > -1) {
260             var container = document.createDocumentFragment();
261             var part1 = document.createElement("span");
262             part1.textContent = message.substr(0, buttonPos);
263             container.appendChild(part1);
264      
265             var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
266             container.appendChild(button.element);
267        
268             var part2 = document.createElement("span");
269             part2.textContent = message.substr(buttonPos + 2);
270             container.appendChild(part2);
271             this.welcomeView.addMessage(container);
272         } else
273             this.welcomeView.addMessage(message);
274     },
275
276     _makeKey: function(text, profileTypeId)
277     {
278         return escape(text) + '/' + escape(profileTypeId);
279     },
280
281     _addProfileHeader: function(profile)
282     {
283         if (this.hasTemporaryProfile(profile.typeId)) {
284             if (profile.typeId === WebInspector.CPUProfileType.TypeId)
285                 this._removeProfileHeader(this._temporaryRecordingProfile);
286             else
287                 this._removeProfileHeader(this._temporaryTakingSnapshot);
288         }
289
290         var typeId = profile.typeId;
291         var profileType = this.getProfileType(typeId);
292         var sidebarParent = profileType.treeElement;
293         var small = false;
294         var alternateTitle;
295
296         profile.__profilesPanelProfileType = profileType;
297         this._profiles.push(profile);
298         this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile;
299
300         if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
301             var profileTitleKey = this._makeKey(profile.title, typeId);
302             if (!(profileTitleKey in this._profileGroups))
303                 this._profileGroups[profileTitleKey] = [];
304
305             var group = this._profileGroups[profileTitleKey];
306             group.push(profile);
307
308             if (group.length === 2) {
309                 // Make a group TreeElement now that there are 2 profiles.
310                 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);
311
312                 // Insert at the same index for the first profile of the group.
313                 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
314                 sidebarParent.insertChild(group._profilesTreeElement, index);
315
316                 // Move the first profile to the group.
317                 var selected = group[0]._profilesTreeElement.selected;
318                 sidebarParent.removeChild(group[0]._profilesTreeElement);
319                 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
320                 if (selected)
321                     group[0]._profilesTreeElement.revealAndSelect();
322
323                 group[0]._profilesTreeElement.small = true;
324                 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
325
326                 this.sidebarTreeElement.addStyleClass("some-expandable");
327             }
328
329             if (group.length >= 2) {
330                 sidebarParent = group._profilesTreeElement;
331                 alternateTitle = WebInspector.UIString("Run %d", group.length);
332                 small = true;
333             }
334         }
335
336         var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile);
337         profile.sidebarElement = profileTreeElement;
338         profileTreeElement.small = small;
339         if (alternateTitle)
340             profileTreeElement.mainTitle = alternateTitle;
341         profile._profilesTreeElement = profileTreeElement;
342
343         sidebarParent.appendChild(profileTreeElement);
344         if (!profile.isTemporary) {
345             this.welcomeView.hide();
346             if (!this.visibleView)
347                 this.showProfile(profile);
348             this.dispatchEventToListeners("profile added");
349         }
350     },
351
352     _removeProfileHeader: function(profile)
353     {
354         var typeId = profile.typeId;
355         var profileType = this.getProfileType(typeId);
356         var sidebarParent = profileType.treeElement;
357
358         for (var i = 0; i < this._profiles.length; ++i) {
359             if (this._profiles[i].uid === profile.uid) {
360                 profile = this._profiles[i];
361                 this._profiles.splice(i, 1);
362                 break;
363             }
364         }
365         delete this._profilesIdMap[this._makeKey(profile.uid, typeId)];
366
367         var profileTitleKey = this._makeKey(profile.title, typeId);
368         delete this._profileGroups[profileTitleKey];
369
370         sidebarParent.removeChild(profile._profilesTreeElement);
371
372         if (!profile.isTemporary)
373             ProfilerAgent.removeProfile(profile.typeId, profile.uid);
374
375         // No other item will be selected if there aren't any other profiles, so
376         // make sure that view gets cleared when the last profile is removed.
377         if (!this._profiles.length)
378             this.closeVisibleView();
379     },
380
381     showProfile: function(profile)
382     {
383         if (!profile || profile.isTemporary)
384             return;
385
386         this.closeVisibleView();
387
388         var view = profile.__profilesPanelProfileType.viewForProfile(profile);
389
390         view.show(this.profileViews);
391
392         profile._profilesTreeElement._suppressOnSelect = true;
393         profile._profilesTreeElement.revealAndSelect();
394         delete profile._profilesTreeElement._suppressOnSelect;
395
396         this.visibleView = view;
397
398         this.profileViewStatusBarItemsContainer.removeChildren();
399
400         var statusBarItems = view.statusBarItems;
401         if (statusBarItems)
402             for (var i = 0; i < statusBarItems.length; ++i)
403                 this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
404     },
405
406     getProfiles: function(typeId)
407     {
408         var result = [];
409         var profilesCount = this._profiles.length;
410         for (var i = 0; i < profilesCount; ++i) {
411             var profile = this._profiles[i];
412             if (!profile.isTemporary && profile.typeId === typeId)
413                 result.push(profile);
414         }
415         return result;
416     },
417
418     hasTemporaryProfile: function(typeId)
419     {
420         var profilesCount = this._profiles.length;
421         for (var i = 0; i < profilesCount; ++i)
422             if (this._profiles[i].typeId === typeId && this._profiles[i].isTemporary)
423                 return true;
424         return false;
425     },
426
427     hasProfile: function(profile)
428     {
429         return !!this._profilesIdMap[this._makeKey(profile.uid, profile.typeId)];
430     },
431
432     getProfile: function(typeId, uid)
433     {
434         return this._profilesIdMap[this._makeKey(uid, typeId)];
435     },
436
437     loadHeapSnapshot: function(uid, callback)
438     {
439         var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.DetailedHeapshotProfileType.TypeId)];
440         if (!profile)
441             return;
442
443         if (!profile.proxy) {
444             function setProfileWait(event) {
445                 profile.sidebarElement.wait = event.data;
446             }
447             var worker = new WebInspector.HeapSnapshotWorker();
448             worker.addEventListener("wait", setProfileWait, this);
449             profile.proxy = worker.createObject("WebInspector.HeapSnapshotLoader");
450         }
451         var proxy = profile.proxy;
452         if (proxy.startLoading(callback)) {
453             profile.sidebarElement.subtitle = WebInspector.UIString("Loading\u2026");
454             profile.sidebarElement.wait = true;
455             ProfilerAgent.getProfile(profile.typeId, profile.uid);
456         }
457     },
458
459     _addHeapSnapshotChunk: function(uid, chunk)
460     {
461         var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.DetailedHeapshotProfileType.TypeId)];
462         if (!profile || !profile.proxy)
463             return;
464         profile.proxy.pushJSONChunk(chunk);
465     },
466
467     _finishHeapSnapshot: function(uid)
468     {
469         var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.DetailedHeapshotProfileType.TypeId)];
470         if (!profile || !profile.proxy)
471             return;
472         var proxy = profile.proxy;
473         function parsed(snapshotProxy)
474         {
475             profile.proxy = snapshotProxy;
476             profile.sidebarElement.subtitle = Number.bytesToString(snapshotProxy.totalSize);
477             profile.sidebarElement.wait = false;
478             snapshotProxy.worker.startCheckingForLongRunningCalls();
479         }
480         if (proxy.finishLoading(parsed))
481             profile.sidebarElement.subtitle = WebInspector.UIString("Parsing\u2026");
482     },
483
484     showView: function(view)
485     {
486         this.showProfile(view.profile);
487     },
488
489     getProfileType: function(typeId)
490     {
491         return this._profileTypesByIdMap[typeId];
492     },
493
494     showProfileForURL: function(url)
495     {
496         var match = url.match(WebInspector.ProfileType.URLRegExp);
497         if (!match)
498             return;
499         this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]);
500     },
501
502     updateProfileTypeButtons: function()
503     {
504         for (var typeId in this._profileTypeButtonsByIdMap) {
505             var buttonElement = this._profileTypeButtonsByIdMap[typeId];
506             var profileType = this.getProfileType(typeId);
507             buttonElement.className = profileType.buttonStyle;
508             buttonElement.title = profileType.buttonTooltip;
509             // FIXME: Apply profileType.buttonCaption once captions are added to button controls.
510         }
511     },
512
513     closeVisibleView: function()
514     {
515         if (this.visibleView)
516             this.visibleView.hide();
517         delete this.visibleView;
518     },
519
520     displayTitleForProfileLink: function(title, typeId)
521     {
522         title = unescape(title);
523         if (title.indexOf(UserInitiatedProfileName) === 0) {
524             title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
525         } else {
526             var titleKey = this._makeKey(title, typeId);
527             if (!(titleKey in this._profileGroupsForLinks))
528                 this._profileGroupsForLinks[titleKey] = 0;
529
530             var groupNumber = ++this._profileGroupsForLinks[titleKey];
531
532             if (groupNumber > 2)
533                 // The title is used in the console message announcing that a profile has started so it gets
534                 // incremented twice as often as it's displayed
535                 title += " " + WebInspector.UIString("Run %d", (groupNumber + 1) / 2);
536         }
537         
538         return title;
539     },
540
541     get searchableViews()
542     {
543         var views = [];
544
545         const visibleView = this.visibleView;
546         if (visibleView && visibleView.performSearch)
547             views.push(visibleView);
548
549         var profilesLength = this._profiles.length;
550         for (var i = 0; i < profilesLength; ++i) {
551             var profile = this._profiles[i];
552             var view = profile.__profilesPanelProfileType.viewForProfile(profile);
553             if (!view.performSearch || view === visibleView)
554                 continue;
555             views.push(view);
556         }
557
558         return views;
559     },
560
561     searchMatchFound: function(view, matches)
562     {
563         view.profile._profilesTreeElement.searchMatches = matches;
564     },
565
566     searchCanceled: function(startingNewSearch)
567     {
568         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
569
570         if (!this._profiles)
571             return;
572
573         for (var i = 0; i < this._profiles.length; ++i) {
574             var profile = this._profiles[i];
575             profile._profilesTreeElement.searchMatches = 0;
576         }
577     },
578
579     _updateInterface: function()
580     {
581         // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
582         if (this._profilerEnabled) {
583             this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
584             this.enableToggleButton.toggled = true;
585             for (var typeId in this._profileTypeButtonsByIdMap)
586                 this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden");
587             this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
588             this.clearResultsButton.element.removeStyleClass("hidden");
589             this.panelEnablerView.visible = false;
590         } else {
591             this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
592             this.enableToggleButton.toggled = false;
593             for (var typeId in this._profileTypeButtonsByIdMap)
594                 this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden");
595             this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
596             this.clearResultsButton.element.addStyleClass("hidden");
597             this.panelEnablerView.visible = true;
598         }
599     },
600
601     _enableProfiling: function()
602     {
603         if (this._profilerEnabled)
604             return;
605         this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
606     },
607
608     _toggleProfiling: function(optionalAlways)
609     {
610         if (this._profilerEnabled) {
611             WebInspector.settings.profilerEnabled.set(false);
612             ProfilerAgent.disable();
613         } else {
614             WebInspector.settings.profilerEnabled.set(!!optionalAlways);
615             ProfilerAgent.enable();
616         }
617     },
618
619     _populateProfiles: function()
620     {
621         if (!this._profilerEnabled || this._profilesWereRequested)
622             return;
623
624         function populateCallback(error, profileHeaders) {
625             if (error)
626                 return;
627             profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
628             var profileHeadersLength = profileHeaders.length;
629             for (var i = 0; i < profileHeadersLength; ++i)
630                 if (!this.hasProfile(profileHeaders[i]))
631                    this._addProfileHeader(profileHeaders[i]);
632         }
633
634         ProfilerAgent.getProfileHeaders(populateCallback.bind(this));
635
636         this._profilesWereRequested = true;
637     },
638
639     updateMainViewWidth: function(width)
640     {
641         this.welcomeView.element.style.left = width + "px";
642         this.profileViews.style.left = width + "px";
643         // Min width = <number of buttons on the left> * 31
644         this.profileViewStatusBarItemsContainer.style.left = Math.max(6 * 31, width) + "px";
645     },
646
647     _setRecordingProfile: function(isProfiling)
648     {
649         this.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling);
650         if (this.hasTemporaryProfile(WebInspector.CPUProfileType.TypeId) !== isProfiling) {
651             if (!this._temporaryRecordingProfile) {
652                 this._temporaryRecordingProfile = {
653                     typeId: WebInspector.CPUProfileType.TypeId,
654                     title: WebInspector.UIString("Recording…"),
655                     uid: -1,
656                     isTemporary: true
657                 };
658             }
659             if (isProfiling) {
660                 this._addProfileHeader(this._temporaryRecordingProfile);
661                 WebInspector.userMetrics.ProfilesCPUProfileTaken.record();
662             } else
663                 this._removeProfileHeader(this._temporaryRecordingProfile);
664         }
665         this.updateProfileTypeButtons();
666     },
667
668     takeHeapSnapshot: function()
669     {
670         if (!this.hasTemporaryProfile(WebInspector.DetailedHeapshotProfileType.TypeId)) {
671             if (!this._temporaryTakingSnapshot) {
672                 this._temporaryTakingSnapshot = {
673                     typeId: WebInspector.DetailedHeapshotProfileType.TypeId,
674                     title: WebInspector.UIString("Snapshotting…"),
675                     uid: -1,
676                     isTemporary: true
677                 };
678             }
679             this._addProfileHeader(this._temporaryTakingSnapshot);
680         }
681         ProfilerAgent.takeHeapSnapshot();
682         WebInspector.userMetrics.ProfilesHeapProfileTaken.record();
683     },
684
685     _reportHeapSnapshotProgress: function(done, total)
686     {
687         if (this.hasTemporaryProfile(WebInspector.DetailedHeapshotProfileType.TypeId)) {
688             this._temporaryTakingSnapshot.sidebarElement.subtitle = WebInspector.UIString("%.2f%%", (done / total) * 100);
689             this._temporaryTakingSnapshot.sidebarElement.wait = true;
690             if (done >= total)
691                 this._removeProfileHeader(this._temporaryTakingSnapshot);
692         }
693     },
694
695     _enableDetailedHeapProfiles: function(resetAgent)
696     {
697         if (resetAgent)
698             this._clearProfiles();
699         else
700             this._reset();
701         var oldProfileType = this._profileTypesByIdMap[WebInspector.DetailedHeapshotProfileType.TypeId];
702         var profileType = new WebInspector.DetailedHeapshotProfileType();
703         profileType.treeElement = oldProfileType.treeElement;
704         this._profileTypesByIdMap[profileType.id] = profileType;
705         Preferences.detailedHeapProfiles = true;
706         this.hide();
707         this.show();
708     }
709 }
710
711 WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
712
713
714 WebInspector.ProfilerDispatcher = function(profiler)
715 {
716     this._profiler = profiler;
717 }
718
719 WebInspector.ProfilerDispatcher.prototype = {
720     profilerWasEnabled: function()
721     {
722         this._profiler._profilerWasEnabled();
723     },
724
725     profilerWasDisabled: function()
726     {
727         this._profiler._profilerWasDisabled();
728     },
729
730     resetProfiles: function()
731     {
732         this._profiler._reset();
733     },
734
735     addProfileHeader: function(profile)
736     {
737         this._profiler._addProfileHeader(profile);
738     },
739
740     addHeapSnapshotChunk: function(uid, chunk)
741     {
742         this._profiler._addHeapSnapshotChunk(uid, chunk);
743     },
744
745     finishHeapSnapshot: function(uid)
746     {
747         this._profiler._finishHeapSnapshot(uid);
748     },
749
750     setRecordingProfile: function(isProfiling)
751     {
752         this._profiler._setRecordingProfile(isProfiling);
753     },
754
755     reportHeapSnapshotProgress: function(done, total)
756     {
757         this._profiler._reportHeapSnapshotProgress(done, total);
758     }
759 }
760
761 WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className)
762 {
763     this.profile = profile;
764     this._titleFormat = titleFormat;
765
766     if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
767         this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
768
769     WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false);
770
771     this.refreshTitles();
772 }
773
774 WebInspector.ProfileSidebarTreeElement.prototype = {
775     onselect: function()
776     {
777         if (!this._suppressOnSelect)
778             this.treeOutline.panel.showProfile(this.profile);
779     },
780
781     ondelete: function()
782     {
783         this.treeOutline.panel._removeProfileHeader(this.profile);
784         return true;
785     },
786
787     get mainTitle()
788     {
789         if (this._mainTitle)
790             return this._mainTitle;
791         if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
792             return WebInspector.UIString(this._titleFormat, this._profileNumber);
793         return this.profile.title;
794     },
795
796     set mainTitle(x)
797     {
798         this._mainTitle = x;
799         this.refreshTitles();
800     },
801
802     set searchMatches(matches)
803     {
804         if (!matches) {
805             if (!this.bubbleElement)
806                 return;
807             this.bubbleElement.removeStyleClass("search-matches");
808             this.bubbleText = "";
809             return;
810         }
811
812         this.bubbleText = matches;
813         this.bubbleElement.addStyleClass("search-matches");
814     }
815 }
816
817 WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
818
819 WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
820 {
821     WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
822 }
823
824 WebInspector.ProfileGroupSidebarTreeElement.prototype = {
825     onselect: function()
826     {
827         if (this.children.length > 0)
828             WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
829     }
830 }
831
832 WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;