• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/**
27 * @constructor
28 * @extends {WebInspector.Object}
29 * @param {string} id
30 * @param {string} name
31 */
32WebInspector.ProfileType = function(id, name)
33{
34    this._id = id;
35    this._name = name;
36    /** @type {!Array.<!WebInspector.ProfileHeader>} */
37    this._profiles = [];
38    /** @type {?WebInspector.SidebarSectionTreeElement} */
39    this.treeElement = null;
40    /** @type {?WebInspector.ProfileHeader} */
41    this._profileBeingRecorded = null;
42}
43
44WebInspector.ProfileType.Events = {
45    AddProfileHeader: "add-profile-header",
46    RemoveProfileHeader: "remove-profile-header",
47    ViewUpdated: "view-updated"
48}
49
50WebInspector.ProfileType.prototype = {
51    /**
52     * @return {boolean}
53     */
54    hasTemporaryView: function()
55    {
56        return false;
57    },
58
59    /**
60     * @return {?string}
61     */
62    fileExtension: function()
63    {
64        return null;
65    },
66
67    get statusBarItems()
68    {
69        return [];
70    },
71
72    get buttonTooltip()
73    {
74        return "";
75    },
76
77    get id()
78    {
79        return this._id;
80    },
81
82    get treeItemTitle()
83    {
84        return this._name;
85    },
86
87    get name()
88    {
89        return this._name;
90    },
91
92    /**
93     * @return {boolean}
94     */
95    buttonClicked: function()
96    {
97        return false;
98    },
99
100    get description()
101    {
102        return "";
103    },
104
105    /**
106     * @return {boolean}
107     */
108    isInstantProfile: function()
109    {
110        return false;
111    },
112
113    /**
114     * @return {boolean}
115     */
116    isEnabled: function()
117    {
118        return true;
119    },
120
121    /**
122     * @return {!Array.<!WebInspector.ProfileHeader>}
123     */
124    getProfiles: function()
125    {
126        /**
127         * @param {!WebInspector.ProfileHeader} profile
128         * @return {boolean}
129         * @this {WebInspector.ProfileType}
130         */
131        function isFinished(profile)
132        {
133            return this._profileBeingRecorded !== profile;
134        }
135        return this._profiles.filter(isFinished.bind(this));
136    },
137
138    /**
139     * @return {?Element}
140     */
141    decorationElement: function()
142    {
143        return null;
144    },
145
146    /**
147     * @nosideeffects
148     * @param {number} uid
149     * @return {?WebInspector.ProfileHeader}
150     */
151    getProfile: function(uid)
152    {
153
154        for (var i = 0; i < this._profiles.length; ++i) {
155            if (this._profiles[i].uid === uid)
156                return this._profiles[i];
157        }
158        return null;
159    },
160
161    /**
162     * @param {!File} file
163     */
164    loadFromFile: function(file)
165    {
166        var name = file.name;
167        if (name.endsWith(this.fileExtension()))
168            name = name.substr(0, name.length - this.fileExtension().length);
169        var profile = this.createProfileLoadedFromFile(name);
170        profile.setFromFile();
171        this._profileBeingRecorded = profile;
172        this.addProfile(profile);
173        profile.loadFromFile(file);
174    },
175
176    /**
177     * @param {!string} title
178     * @return {!WebInspector.ProfileHeader}
179     */
180    createProfileLoadedFromFile: function(title)
181    {
182        throw new Error("Needs implemented.");
183    },
184
185    /**
186     * @param {!WebInspector.ProfileHeader} profile
187     */
188    addProfile: function(profile)
189    {
190        this._profiles.push(profile);
191        this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile);
192    },
193
194    /**
195     * @param {!WebInspector.ProfileHeader} profile
196     */
197    removeProfile: function(profile)
198    {
199        if (this._profileBeingRecorded === profile)
200            this._profileBeingRecorded = null;
201        for (var i = 0; i < this._profiles.length; ++i) {
202            if (this._profiles[i].uid === profile.uid) {
203                this._profiles.splice(i, 1);
204                break;
205            }
206        }
207    },
208
209    /**
210     * @nosideeffects
211     * @return {?WebInspector.ProfileHeader}
212     */
213    profileBeingRecorded: function()
214    {
215        return this._profileBeingRecorded;
216    },
217
218    _reset: function()
219    {
220        var profiles = this._profiles.slice(0);
221        for (var i = 0; i < profiles.length; ++i) {
222            var profile = profiles[i];
223            var view = profile.existingView();
224            if (view) {
225                view.detach();
226                if ("dispose" in view)
227                    view.dispose();
228            }
229            this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile);
230        }
231        this.treeElement.removeChildren();
232        this._profiles = [];
233    },
234
235    __proto__: WebInspector.Object.prototype
236}
237
238/**
239 * @constructor
240 * @param {!WebInspector.ProfileType} profileType
241 * @param {string} title
242 * @param {number=} uid
243 */
244WebInspector.ProfileHeader = function(profileType, title, uid)
245{
246    this._profileType = profileType;
247    this.title = title;
248    this.uid = (uid === undefined) ? -1 : uid;
249    this._fromFile = false;
250}
251
252WebInspector.ProfileHeader._nextProfileFromFileUid = 1;
253
254WebInspector.ProfileHeader.prototype = {
255    /**
256     * @return {!WebInspector.ProfileType}
257     */
258    profileType: function()
259    {
260        return this._profileType;
261    },
262
263    /**
264     * Must be implemented by subclasses.
265     * @return {!WebInspector.ProfileSidebarTreeElement}
266     */
267    createSidebarTreeElement: function()
268    {
269        throw new Error("Needs implemented.");
270    },
271
272    /**
273     * @return {?WebInspector.View}
274     */
275    existingView: function()
276    {
277        return this._view;
278    },
279
280    /**
281     * @param {!WebInspector.ProfilesPanel} panel
282     * @return {!WebInspector.View}
283     */
284    view: function(panel)
285    {
286        if (!this._view)
287            this._view = this.createView(panel);
288        return this._view;
289    },
290
291    /**
292     * @param {!WebInspector.ProfilesPanel} panel
293     * @return {!WebInspector.View}
294     */
295    createView: function(panel)
296    {
297        throw new Error("Not implemented.");
298    },
299
300    dispose: function()
301    {
302    },
303
304    /**
305     * @param {!Function} callback
306     */
307    load: function(callback)
308    {
309    },
310
311    /**
312     * @return {boolean}
313     */
314    canSaveToFile: function()
315    {
316        return false;
317    },
318
319    saveToFile: function()
320    {
321        throw new Error("Needs implemented");
322    },
323
324    /**
325     * @param {!File} file
326     */
327    loadFromFile: function(file)
328    {
329        throw new Error("Needs implemented");
330    },
331
332    /**
333     * @return {boolean}
334     */
335    fromFile: function()
336    {
337        return this._fromFile;
338    },
339
340    setFromFile: function()
341    {
342        this._fromFile = true;
343        this.uid = "From file #" + WebInspector.ProfileHeader._nextProfileFromFileUid++;
344    }
345}
346
347/**
348 * @constructor
349 * @implements {WebInspector.Searchable}
350 * @implements {WebInspector.ContextMenu.Provider}
351 * @extends {WebInspector.Panel}
352 * @param {string=} name
353 * @param {!WebInspector.ProfileType=} type
354 */
355WebInspector.ProfilesPanel = function(name, type)
356{
357    // If the name is not specified the ProfilesPanel works in multi-profile mode.
358    var singleProfileMode = typeof name !== "undefined";
359    name = name || "profiles";
360    WebInspector.Panel.call(this, name);
361    this.registerRequiredCSS("panelEnablerView.css");
362    this.registerRequiredCSS("heapProfiler.css");
363    this.registerRequiredCSS("profilesPanel.css");
364
365    this.createSidebarViewWithTree();
366
367    this.splitView.mainElement.classList.add("vbox");
368    this.splitView.sidebarElement.classList.add("vbox");
369
370    this._searchableView = new WebInspector.SearchableView(this);
371    this._searchableView.show(this.splitView.mainElement);
372
373    this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this);
374    this.sidebarTree.appendChild(this.profilesItemTreeElement);
375
376    this._singleProfileMode = singleProfileMode;
377    this._profileTypesByIdMap = {};
378
379    this.profileViews = document.createElement("div");
380    this.profileViews.id = "profile-views";
381    this.profileViews.classList.add("vbox");
382    this._searchableView.element.appendChild(this.profileViews);
383
384    var statusBarContainer = this.splitView.mainElement.createChild("div", "profiles-status-bar");
385    this._statusBarElement = statusBarContainer.createChild("div", "status-bar");
386
387    var sidebarTreeBox = this.sidebarElement.createChild("div", "profiles-sidebar-tree-box");
388    sidebarTreeBox.appendChild(this.sidebarTreeElement);
389    var statusBarContainerLeft = this.sidebarElement.createChild("div", "profiles-status-bar");
390    this._statusBarButtons = statusBarContainerLeft.createChild("div", "status-bar");
391
392    this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
393    this.recordButton.addEventListener("click", this.toggleRecordButton, this);
394    this._statusBarButtons.appendChild(this.recordButton.element);
395
396    this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item");
397    this.clearResultsButton.addEventListener("click", this._clearProfiles, this);
398    this._statusBarButtons.appendChild(this.clearResultsButton.element);
399
400    this._profileTypeStatusBarItemsContainer = this._statusBarElement.createChild("div");
401    this._profileViewStatusBarItemsContainer = this._statusBarElement.createChild("div");
402
403    if (singleProfileMode) {
404        this._launcherView = this._createLauncherView();
405        this._registerProfileType(/** @type {!WebInspector.ProfileType} */ (type));
406        this._selectedProfileType = type;
407        this._updateProfileTypeSpecificUI();
408    } else {
409        this._launcherView = new WebInspector.MultiProfileLauncherView(this);
410        this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this);
411
412        this._registerProfileType(new WebInspector.CPUProfileType());
413        this._registerProfileType(new WebInspector.HeapSnapshotProfileType());
414        this._registerProfileType(new WebInspector.TrackingHeapSnapshotProfileType(this));
415        if (!WebInspector.WorkerManager.isWorkerFrontend() && WebInspector.experimentsSettings.canvasInspection.isEnabled())
416            this._registerProfileType(new WebInspector.CanvasProfileType());
417        this._launcherView.restoreSelectedProfileType();
418    }
419
420    this._reset();
421
422    this._createFileSelectorElement();
423    this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
424    this._registerShortcuts();
425
426    WebInspector.ContextMenu.registerProvider(this);
427
428    this._configureCpuProfilerSamplingInterval();
429    WebInspector.settings.highResolutionCpuProfiling.addChangeListener(this._configureCpuProfilerSamplingInterval, this);
430}
431
432WebInspector.ProfilesPanel.prototype = {
433    /**
434     * @return {!WebInspector.SearchableView}
435     */
436    searchableView: function()
437    {
438        return this._searchableView;
439    },
440
441    _createFileSelectorElement: function()
442    {
443        if (this._fileSelectorElement)
444            this.element.removeChild(this._fileSelectorElement);
445        this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
446        this.element.appendChild(this._fileSelectorElement);
447    },
448
449    /**
450     * @return {!WebInspector.ProfileLauncherView}
451     */
452    _createLauncherView: function()
453    {
454        return new WebInspector.ProfileLauncherView(this);
455    },
456
457    _findProfileTypeByExtension: function(fileName)
458    {
459        for (var id in this._profileTypesByIdMap) {
460            var type = this._profileTypesByIdMap[id];
461            var extension = type.fileExtension();
462            if (!extension)
463                continue;
464            if (fileName.endsWith(type.fileExtension()))
465                return type;
466        }
467        return null;
468    },
469
470    _registerShortcuts: function()
471    {
472        this.registerShortcuts(WebInspector.ProfilesPanelDescriptor.ShortcutKeys.StartStopRecording, this.toggleRecordButton.bind(this));
473    },
474
475    _configureCpuProfilerSamplingInterval: function()
476    {
477        var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000;
478        ProfilerAgent.setSamplingInterval(intervalUs, didChangeInterval.bind(this));
479        function didChangeInterval(error)
480        {
481            if (error)
482                WebInspector.showErrorMessage(error)
483        }
484    },
485
486    /**
487     * @param {!File} file
488     */
489    _loadFromFile: function(file)
490    {
491        this._createFileSelectorElement();
492
493        var profileType = this._findProfileTypeByExtension(file.name);
494        if (!profileType) {
495            var extensions = [];
496            for (var id in this._profileTypesByIdMap) {
497                var extension = this._profileTypesByIdMap[id].fileExtension();
498                if (!extension)
499                    continue;
500                extensions.push(extension);
501            }
502            WebInspector.log(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '")));
503            return;
504        }
505
506        if (!!profileType.profileBeingRecorded()) {
507            WebInspector.log(WebInspector.UIString("Can't load profile when other profile is recording."));
508            return;
509        }
510
511        profileType.loadFromFile(file);
512    },
513
514    /**
515     * @return {boolean}
516     */
517    toggleRecordButton: function()
518    {
519        var type = this._selectedProfileType;
520        var isProfiling = type.buttonClicked();
521        this.recordButton.toggled = isProfiling;
522        this.recordButton.title = type.buttonTooltip;
523        if (isProfiling) {
524            this._launcherView.profileStarted();
525            if (type.hasTemporaryView())
526                this._showProfile(type.profileBeingRecorded());
527        } else {
528            this._launcherView.profileFinished();
529        }
530        return true;
531    },
532
533    /**
534     * @param {!WebInspector.Event} event
535     */
536    _onProfileTypeSelected: function(event)
537    {
538        this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data);
539        this._updateProfileTypeSpecificUI();
540    },
541
542    _updateProfileTypeSpecificUI: function()
543    {
544        this.recordButton.title = this._selectedProfileType.buttonTooltip;
545        this._launcherView.updateProfileType(this._selectedProfileType);
546        this._profileTypeStatusBarItemsContainer.removeChildren();
547        var statusBarItems = this._selectedProfileType.statusBarItems;
548        if (statusBarItems) {
549            for (var i = 0; i < statusBarItems.length; ++i)
550                this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]);
551        }
552    },
553
554    _reset: function()
555    {
556        WebInspector.Panel.prototype.reset.call(this);
557
558        for (var typeId in this._profileTypesByIdMap)
559            this._profileTypesByIdMap[typeId]._reset();
560
561        delete this.visibleView;
562        delete this.currentQuery;
563        this.searchCanceled();
564
565        this._profileGroups = {};
566        this.recordButton.toggled = false;
567        if (this._selectedProfileType)
568            this.recordButton.title = this._selectedProfileType.buttonTooltip;
569        this._launcherView.profileFinished();
570
571        this.sidebarTreeElement.classList.remove("some-expandable");
572
573        this._launcherView.detach();
574        this.profileViews.removeChildren();
575        this._profileViewStatusBarItemsContainer.removeChildren();
576
577        this.removeAllListeners();
578
579        this.recordButton.visible = true;
580        this._profileViewStatusBarItemsContainer.classList.remove("hidden");
581        this.clearResultsButton.element.classList.remove("hidden");
582        this.profilesItemTreeElement.select();
583        this._showLauncherView();
584    },
585
586    _showLauncherView: function()
587    {
588        this.closeVisibleView();
589        this._profileViewStatusBarItemsContainer.removeChildren();
590        this._launcherView.show(this.profileViews);
591        this.visibleView = this._launcherView;
592    },
593
594    _clearProfiles: function()
595    {
596        HeapProfilerAgent.clearProfiles();
597        this._reset();
598    },
599
600    _garbageCollectButtonClicked: function()
601    {
602        HeapProfilerAgent.collectGarbage();
603    },
604
605    /**
606     * @param {!WebInspector.ProfileType} profileType
607     */
608    _registerProfileType: function(profileType)
609    {
610        this._profileTypesByIdMap[profileType.id] = profileType;
611        this._launcherView.addProfileType(profileType);
612        profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.treeItemTitle, null, true);
613        profileType.treeElement.hidden = !this._singleProfileMode;
614        this.sidebarTree.appendChild(profileType.treeElement);
615        profileType.treeElement.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
616
617        /**
618         * @this {WebInspector.ProfilesPanel}
619         */
620        function onAddProfileHeader(event)
621        {
622            this._addProfileHeader(event.data);
623        }
624
625        /**
626         * @this {WebInspector.ProfilesPanel}
627         */
628        function onRemoveProfileHeader(event)
629        {
630            this._removeProfileHeader(event.data);
631        }
632
633        profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this);
634        profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this);
635        profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this);
636    },
637
638    /**
639     * @param {?Event} event
640     */
641    _handleContextMenuEvent: function(event)
642    {
643        var element = event.srcElement;
644        while (element && !element.treeElement && element !== this.element)
645            element = element.parentElement;
646        if (!element)
647            return;
648        if (element.treeElement && element.treeElement.handleContextMenuEvent) {
649            element.treeElement.handleContextMenuEvent(event, this);
650            return;
651        }
652
653        var contextMenu = new WebInspector.ContextMenu(event);
654        if (this.visibleView instanceof WebInspector.HeapSnapshotView) {
655            this.visibleView.populateContextMenu(contextMenu, event);
656        }
657        if (element !== this.element || event.srcElement === this.sidebarElement) {
658            contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement));
659        }
660        contextMenu.show();
661    },
662
663    /**
664     * @nosideeffects
665     * @param {string} text
666     * @param {string} profileTypeId
667     * @return {string}
668     */
669    _makeTitleKey: function(text, profileTypeId)
670    {
671        return escape(text) + '/' + escape(profileTypeId);
672    },
673
674    /**
675     * @param {!WebInspector.ProfileHeader} profile
676     */
677    _addProfileHeader: function(profile)
678    {
679        var profileType = profile.profileType();
680        var typeId = profileType.id;
681        var sidebarParent = profileType.treeElement;
682        sidebarParent.hidden = false;
683        var small = false;
684        var alternateTitle;
685
686        if (!profile.fromFile() && profile.profileType().profileBeingRecorded() !== profile) {
687            var profileTitleKey = this._makeTitleKey(profile.title, typeId);
688            if (!(profileTitleKey in this._profileGroups))
689                this._profileGroups[profileTitleKey] = [];
690
691            var group = this._profileGroups[profileTitleKey];
692            group.push(profile);
693            if (group.length === 2) {
694                // Make a group TreeElement now that there are 2 profiles.
695                group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this, profile.title);
696
697                // Insert at the same index for the first profile of the group.
698                var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
699                sidebarParent.insertChild(group._profilesTreeElement, index);
700
701                // Move the first profile to the group.
702                var selected = group[0]._profilesTreeElement.selected;
703                sidebarParent.removeChild(group[0]._profilesTreeElement);
704                group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
705                if (selected)
706                    group[0]._profilesTreeElement.revealAndSelect();
707
708                group[0]._profilesTreeElement.small = true;
709                group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
710
711                this.sidebarTreeElement.classList.add("some-expandable");
712            }
713
714            if (group.length >= 2) {
715                sidebarParent = group._profilesTreeElement;
716                alternateTitle = WebInspector.UIString("Run %d", group.length);
717                small = true;
718            }
719        }
720
721        var profileTreeElement = profile.createSidebarTreeElement();
722        profile.sidebarElement = profileTreeElement;
723        profileTreeElement.small = small;
724        if (alternateTitle)
725            profileTreeElement.mainTitle = alternateTitle;
726        profile._profilesTreeElement = profileTreeElement;
727
728        sidebarParent.appendChild(profileTreeElement);
729        if (!this.visibleView || this.visibleView === this._launcherView)
730            this._showProfile(profile);
731
732        this.dispatchEventToListeners("profile added", {
733            type: typeId
734        });
735    },
736
737    /**
738     * @param {!WebInspector.ProfileHeader} profile
739     */
740    _removeProfileHeader: function(profile)
741    {
742        profile.dispose();
743        profile.profileType().removeProfile(profile);
744
745        var sidebarParent = profile.profileType().treeElement;
746        var profileTitleKey = this._makeTitleKey(profile.title, profile.profileType().id);
747        var group = this._profileGroups[profileTitleKey];
748        if (group) {
749            group.splice(group.indexOf(profile), 1);
750            if (group.length === 1) {
751                // Move the last profile out of its group and remove the group.
752                var index = sidebarParent.children.indexOf(group._profilesTreeElement);
753                sidebarParent.insertChild(group[0]._profilesTreeElement, index);
754                group[0]._profilesTreeElement.small = false;
755                group[0]._profilesTreeElement.mainTitle = group[0].title;
756                sidebarParent.removeChild(group._profilesTreeElement);
757            }
758            if (group.length !== 0)
759                sidebarParent = group._profilesTreeElement;
760            else
761                delete this._profileGroups[profileTitleKey];
762        }
763        sidebarParent.removeChild(profile._profilesTreeElement);
764
765        // No other item will be selected if there aren't any other profiles, so
766        // make sure that view gets cleared when the last profile is removed.
767        if (!sidebarParent.children.length) {
768            this.profilesItemTreeElement.select();
769            this._showLauncherView();
770            sidebarParent.hidden = !this._singleProfileMode;
771        }
772    },
773
774    /**
775     * @param {?WebInspector.ProfileHeader} profile
776     * @return {?WebInspector.View}
777     */
778    _showProfile: function(profile)
779    {
780        if (!profile || (profile.profileType().profileBeingRecorded() === profile) && !profile.profileType().hasTemporaryView())
781            return null;
782
783        var view = profile.view(this);
784        if (view === this.visibleView)
785            return view;
786
787        this.closeVisibleView();
788
789        view.show(this.profileViews);
790
791        profile._profilesTreeElement._suppressOnSelect = true;
792        profile._profilesTreeElement.revealAndSelect();
793        delete profile._profilesTreeElement._suppressOnSelect;
794
795        this.visibleView = view;
796
797        this._profileViewStatusBarItemsContainer.removeChildren();
798
799        var statusBarItems = view.statusBarItems;
800        if (statusBarItems)
801            for (var i = 0; i < statusBarItems.length; ++i)
802                this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
803
804        return view;
805    },
806
807    /**
808     * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId
809     * @param {string} viewName
810     */
811    showObject: function(snapshotObjectId, viewName)
812    {
813        var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles();
814        for (var i = 0; i < heapProfiles.length; i++) {
815            var profile = heapProfiles[i];
816            // FIXME: allow to choose snapshot if there are several options.
817            if (profile.maxJSObjectId >= snapshotObjectId) {
818                this._showProfile(profile);
819                var view = profile.view(this);
820                view.changeView(viewName, function() {
821                    function didHighlightObject(found) {
822                        if (!found)
823                            WebInspector.log("Cannot find corresponding heap snapshot node", WebInspector.ConsoleMessage.MessageLevel.Error, true);
824                    }
825                    view.dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId, didHighlightObject.bind(this));
826                });
827                break;
828            }
829        }
830    },
831
832    /**
833     * @param {string} typeId
834     * @param {number} uid
835     */
836    getProfile: function(typeId, uid)
837    {
838        return this.getProfileType(typeId).getProfile(uid);
839    },
840
841    /**
842     * @param {!WebInspector.View} view
843     */
844    showView: function(view)
845    {
846        this._showProfile(view.profile);
847    },
848
849    /**
850     * @param {string} typeId
851     */
852    getProfileType: function(typeId)
853    {
854        return this._profileTypesByIdMap[typeId];
855    },
856
857    /**
858     * @param {string} typeId
859     * @param {string} uid
860     * @return {?WebInspector.View}
861     */
862    showProfile: function(typeId, uid)
863    {
864        return this._showProfile(this.getProfile(typeId, Number(uid)));
865    },
866
867    closeVisibleView: function()
868    {
869        if (this.visibleView)
870            this.visibleView.detach();
871        delete this.visibleView;
872    },
873
874    /**
875     * @param {string} query
876     * @param {boolean} shouldJump
877     */
878    performSearch: function(query, shouldJump)
879    {
880        this.searchCanceled();
881
882        var visibleView = this.visibleView;
883        if (!visibleView)
884            return;
885
886        /**
887         * @this {WebInspector.ProfilesPanel}
888         */
889        function finishedCallback(view, searchMatches)
890        {
891            if (!searchMatches)
892                return;
893            this._searchableView.updateSearchMatchesCount(searchMatches);
894            this._searchResultsView = view;
895            if (shouldJump) {
896                view.jumpToFirstSearchResult();
897                this._searchableView.updateCurrentMatchIndex(view.currentSearchResultIndex());
898            }
899        }
900
901        visibleView.currentQuery = query;
902        visibleView.performSearch(query, finishedCallback.bind(this));
903    },
904
905    jumpToNextSearchResult: function()
906    {
907        if (!this._searchResultsView)
908            return;
909        if (this._searchResultsView !== this.visibleView)
910            return;
911        this._searchResultsView.jumpToNextSearchResult();
912        this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex());
913    },
914
915    jumpToPreviousSearchResult: function()
916    {
917        if (!this._searchResultsView)
918            return;
919        if (this._searchResultsView !== this.visibleView)
920            return;
921        this._searchResultsView.jumpToPreviousSearchResult();
922        this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex());
923    },
924
925    /**
926     * @return {!Array.<!WebInspector.ProfileHeader>}
927     */
928    _getAllProfiles: function()
929    {
930        var profiles = [];
931        for (var typeId in this._profileTypesByIdMap)
932            profiles = profiles.concat(this._profileTypesByIdMap[typeId].getProfiles());
933        return profiles;
934    },
935
936    searchCanceled: function()
937    {
938        if (this._searchResultsView) {
939            if (this._searchResultsView.searchCanceled)
940                this._searchResultsView.searchCanceled();
941            this._searchResultsView.currentQuery = null;
942            this._searchResultsView = null;
943        }
944        this._searchableView.updateSearchMatchesCount(0);
945    },
946
947    /**
948     * @param {!WebInspector.ProfileHeader} profile
949     * @param {number} done
950     * @param {number} total
951     */
952    _reportProfileProgress: function(profile, done, total)
953    {
954        profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100);
955        profile.sidebarElement.wait = true;
956    },
957
958    /**
959     * @param {!WebInspector.ContextMenu} contextMenu
960     * @param {!Object} target
961     */
962    appendApplicableItems: function(event, contextMenu, target)
963    {
964        if (WebInspector.inspectorView.currentPanel() !== this)
965            return;
966
967        var object = /** @type {!WebInspector.RemoteObject} */ (target);
968        var objectId = object.objectId;
969        if (!objectId)
970            return;
971
972        var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles();
973        if (!heapProfiles.length)
974            return;
975
976        /**
977         * @this {WebInspector.ProfilesPanel}
978         */
979        function revealInView(viewName)
980        {
981            HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName));
982        }
983
984        /**
985         * @this {WebInspector.ProfilesPanel}
986         */
987        function didReceiveHeapObjectId(viewName, error, result)
988        {
989            if (WebInspector.inspectorView.currentPanel() !== this)
990                return;
991            if (!error)
992                this.showObject(result, viewName);
993        }
994
995        contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators"));
996        contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary"));
997    },
998
999    __proto__: WebInspector.Panel.prototype
1000}
1001
1002/**
1003 * @constructor
1004 * @extends {WebInspector.SidebarTreeElement}
1005 * @param {!WebInspector.ProfileHeader} profile
1006 * @param {string} className
1007 */
1008WebInspector.ProfileSidebarTreeElement = function(profile, className)
1009{
1010    this.profile = profile;
1011    WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false);
1012    this.refreshTitles();
1013}
1014
1015WebInspector.ProfileSidebarTreeElement.prototype = {
1016    onselect: function()
1017    {
1018        if (!this._suppressOnSelect)
1019            this.treeOutline.panel._showProfile(this.profile);
1020    },
1021
1022    ondelete: function()
1023    {
1024        this.treeOutline.panel._removeProfileHeader(this.profile);
1025        return true;
1026    },
1027
1028    get mainTitle()
1029    {
1030        if (this._mainTitle)
1031            return this._mainTitle;
1032        return this.profile.title;
1033    },
1034
1035    set mainTitle(x)
1036    {
1037        this._mainTitle = x;
1038        this.refreshTitles();
1039    },
1040
1041    /**
1042     * @param {!Event} event
1043     * @param {!WebInspector.ProfilesPanel} panel
1044     */
1045    handleContextMenuEvent: function(event, panel)
1046    {
1047        var profile = this.profile;
1048        var contextMenu = new WebInspector.ContextMenu(event);
1049        // FIXME: use context menu provider
1050        contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement));
1051        if (profile.canSaveToFile())
1052            contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile));
1053        contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this));
1054        contextMenu.show();
1055    },
1056
1057    __proto__: WebInspector.SidebarTreeElement.prototype
1058}
1059
1060/**
1061 * @constructor
1062 * @extends {WebInspector.SidebarTreeElement}
1063 * @param {!WebInspector.ProfilesPanel} panel
1064 * @param {string} title
1065 * @param {string=} subtitle
1066 */
1067WebInspector.ProfileGroupSidebarTreeElement = function(panel, title, subtitle)
1068{
1069    WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
1070    this._panel = panel;
1071}
1072
1073WebInspector.ProfileGroupSidebarTreeElement.prototype = {
1074    onselect: function()
1075    {
1076        if (this.children.length > 0)
1077            this._panel._showProfile(this.children[this.children.length - 1].profile);
1078    },
1079
1080    __proto__: WebInspector.SidebarTreeElement.prototype
1081}
1082
1083/**
1084 * @constructor
1085 * @extends {WebInspector.SidebarTreeElement}
1086 * @param {!WebInspector.ProfilesPanel} panel
1087 */
1088WebInspector.ProfilesSidebarTreeElement = function(panel)
1089{
1090    this._panel = panel;
1091    this.small = false;
1092
1093    WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false);
1094}
1095
1096WebInspector.ProfilesSidebarTreeElement.prototype = {
1097    onselect: function()
1098    {
1099        this._panel._showLauncherView();
1100    },
1101
1102    get selectable()
1103    {
1104        return true;
1105    },
1106
1107    __proto__: WebInspector.SidebarTreeElement.prototype
1108}
1109
1110
1111/**
1112 * @constructor
1113 * @extends {WebInspector.ProfilesPanel}
1114 */
1115WebInspector.CPUProfilerPanel = function()
1116{
1117    WebInspector.ProfilesPanel.call(this, "cpu-profiler", new WebInspector.CPUProfileType());
1118}
1119
1120WebInspector.CPUProfilerPanel.prototype = {
1121    __proto__: WebInspector.ProfilesPanel.prototype
1122}
1123
1124
1125/**
1126 * @constructor
1127 * @extends {WebInspector.ProfilesPanel}
1128 */
1129WebInspector.HeapProfilerPanel = function()
1130{
1131    var heapSnapshotProfileType = new WebInspector.HeapSnapshotProfileType();
1132    WebInspector.ProfilesPanel.call(this, "heap-profiler", heapSnapshotProfileType);
1133    this._singleProfileMode = false;
1134    this._registerProfileType(new WebInspector.TrackingHeapSnapshotProfileType(this));
1135    this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this);
1136    this._launcherView._profileTypeChanged(heapSnapshotProfileType);
1137}
1138
1139WebInspector.HeapProfilerPanel.prototype = {
1140    _createLauncherView: function()
1141    {
1142        return new WebInspector.MultiProfileLauncherView(this);
1143    },
1144
1145    __proto__: WebInspector.ProfilesPanel.prototype
1146}
1147
1148
1149/**
1150 * @constructor
1151 * @extends {WebInspector.ProfilesPanel}
1152 */
1153WebInspector.CanvasProfilerPanel = function()
1154{
1155    WebInspector.ProfilesPanel.call(this, "canvas-profiler", new WebInspector.CanvasProfileType());
1156}
1157
1158WebInspector.CanvasProfilerPanel.prototype = {
1159    __proto__: WebInspector.ProfilesPanel.prototype
1160}
1161
1162
1163importScript("ProfileDataGridTree.js");
1164importScript("AllocationProfile.js");
1165importScript("BottomUpProfileDataGridTree.js");
1166importScript("CPUProfileView.js");
1167importScript("HeapSnapshot.js");
1168importScript("HeapSnapshotDataGrids.js");
1169importScript("HeapSnapshotGridNodes.js");
1170importScript("HeapSnapshotLoader.js");
1171importScript("HeapSnapshotProxy.js");
1172importScript("HeapSnapshotView.js");
1173importScript("HeapSnapshotWorkerDispatcher.js");
1174importScript("JSHeapSnapshot.js");
1175importScript("ProfileLauncherView.js");
1176importScript("TopDownProfileDataGridTree.js");
1177importScript("CanvasProfileView.js");
1178importScript("CanvasReplayStateView.js");
1179