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