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