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