Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / profiler / ProfilesPanel.js
1 /*
2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 /**
27  * @constructor
28  * @extends {WebInspector.Object}
29  * @param {string} id
30  * @param {string} name
31  */
32 WebInspector.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  */
49 WebInspector.ProfileType.Events = {
50     AddProfileHeader: "add-profile-header",
51     ProfileComplete: "profile-complete",
52     RemoveProfileHeader: "remove-profile-header",
53     ViewUpdated: "view-updated"
54 }
55
56 WebInspector.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  */
272 WebInspector.ProfileType.DataDisplayDelegate = function()
273 {
274 }
275
276 WebInspector.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.ProfileType} profileType
294  * @param {string} title
295  */
296 WebInspector.ProfileHeader = function(target, profileType, title)
297 {
298     WebInspector.TargetAwareObject.call(this, target);
299     this._profileType = profileType;
300     this.title = title;
301     this.uid = profileType._nextProfileUid++;
302     this._fromFile = false;
303 }
304
305 /**
306  * @constructor
307  * @param {?string} subtitle
308  * @param {boolean|undefined} wait
309  */
310 WebInspector.ProfileHeader.StatusUpdate = function(subtitle, wait)
311 {
312     /** @type {?string} */
313     this.subtitle = subtitle;
314     /** @type {boolean|undefined} */
315     this.wait = wait;
316 }
317
318 WebInspector.ProfileHeader.Events = {
319     UpdateStatus: "UpdateStatus",
320     ProfileReceived: "ProfileReceived"
321 }
322
323 WebInspector.ProfileHeader.prototype = {
324     /**
325      * @return {!WebInspector.ProfileType}
326      */
327     profileType: function()
328     {
329         return this._profileType;
330     },
331
332     /**
333      * @param {?string} subtitle
334      * @param {boolean=} wait
335      */
336     updateStatus: function(subtitle, wait)
337     {
338         this.dispatchEventToListeners(WebInspector.ProfileHeader.Events.UpdateStatus, new WebInspector.ProfileHeader.StatusUpdate(subtitle, wait));
339     },
340
341     /**
342      * Must be implemented by subclasses.
343      * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
344      * @return {!WebInspector.ProfileSidebarTreeElement}
345      */
346     createSidebarTreeElement: function(dataDisplayDelegate)
347     {
348         throw new Error("Needs implemented.");
349     },
350
351     /**
352      * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
353      * @return {!WebInspector.View}
354      */
355     createView: function(dataDisplayDelegate)
356     {
357         throw new Error("Not implemented.");
358     },
359
360     removeTempFile: function()
361     {
362         if (this._tempFile)
363             this._tempFile.remove();
364     },
365
366     dispose: function()
367     {
368     },
369
370     /**
371      * @param {!Function} callback
372      */
373     load: function(callback)
374     {
375     },
376
377     /**
378      * @return {boolean}
379      */
380     canSaveToFile: function()
381     {
382         return false;
383     },
384
385     saveToFile: function()
386     {
387         throw new Error("Needs implemented");
388     },
389
390     /**
391      * @param {!File} file
392      */
393     loadFromFile: function(file)
394     {
395         throw new Error("Needs implemented");
396     },
397
398     /**
399      * @return {boolean}
400      */
401     fromFile: function()
402     {
403         return this._fromFile;
404     },
405
406     setFromFile: function()
407     {
408         this._fromFile = true;
409     },
410
411     __proto__: WebInspector.TargetAwareObject.prototype
412 }
413
414 /**
415  * @constructor
416  * @implements {WebInspector.Searchable}
417  * @implements {WebInspector.ProfileType.DataDisplayDelegate}
418  * @extends {WebInspector.PanelWithSidebarTree}
419  */
420 WebInspector.ProfilesPanel = function()
421 {
422     WebInspector.PanelWithSidebarTree.call(this, "profiles");
423     this.registerRequiredCSS("panelEnablerView.css");
424     this.registerRequiredCSS("heapProfiler.css");
425     this.registerRequiredCSS("profilesPanel.css");
426
427     this._target = /** @type {!WebInspector.Target} */ (WebInspector.targetManager.activeTarget());
428     this._target.profilingLock.addEventListener(WebInspector.Lock.Events.StateChanged, this._onProfilingStateChanged, this);
429
430     this._searchableView = new WebInspector.SearchableView(this);
431
432     var mainView = new WebInspector.VBox();
433     this._searchableView.show(mainView.element);
434     mainView.show(this.mainElement());
435
436     this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this);
437     this.sidebarTree.appendChild(this.profilesItemTreeElement);
438
439     this.profileViews = document.createElement("div");
440     this.profileViews.id = "profile-views";
441     this.profileViews.classList.add("vbox");
442     this._searchableView.element.appendChild(this.profileViews);
443
444     var statusBarContainer = document.createElementWithClass("div", "profiles-status-bar");
445     mainView.element.insertBefore(statusBarContainer, mainView.element.firstChild);
446     this._statusBarElement = statusBarContainer.createChild("div", "status-bar");
447
448     this.sidebarElement().classList.add("profiles-sidebar-tree-box");
449     var statusBarContainerLeft = document.createElementWithClass("div", "profiles-status-bar");
450     this.sidebarElement().insertBefore(statusBarContainerLeft, this.sidebarElement().firstChild);
451     this._statusBarButtons = statusBarContainerLeft.createChild("div", "status-bar");
452
453     this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
454     this.recordButton.addEventListener("click", this.toggleRecordButton, this);
455     this._statusBarButtons.appendChild(this.recordButton.element);
456
457     this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item");
458     this.clearResultsButton.addEventListener("click", this._reset, this);
459     this._statusBarButtons.appendChild(this.clearResultsButton.element);
460
461     this._profileTypeStatusBarItemsContainer = this._statusBarElement.createChild("div");
462     this._profileViewStatusBarItemsContainer = this._statusBarElement.createChild("div");
463
464     this._profileGroups = {};
465     this._launcherView = new WebInspector.MultiProfileLauncherView(this);
466     this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this);
467
468     this._profileToView = [];
469     this._typeIdToSidebarSection = {};
470     var types = WebInspector.ProfileTypeRegistry.instance.profileTypes();
471     for (var i = 0; i < types.length; i++)
472         this._registerProfileType(types[i]);
473     this._launcherView.restoreSelectedProfileType();
474     this.profilesItemTreeElement.select();
475     this._showLauncherView();
476
477     this._createFileSelectorElement();
478     this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
479     this._registerShortcuts();
480
481     this._configureCpuProfilerSamplingInterval();
482     WebInspector.settings.highResolutionCpuProfiling.addChangeListener(this._configureCpuProfilerSamplingInterval, this);
483 }
484
485
486 /**
487  * @constructor
488  */
489 WebInspector.ProfileTypeRegistry = function() {
490     this._profileTypes = [];
491
492     this.cpuProfileType = new WebInspector.CPUProfileType();
493     this._addProfileType(this.cpuProfileType);
494     this.heapSnapshotProfileType = new WebInspector.HeapSnapshotProfileType();
495     this._addProfileType(this.heapSnapshotProfileType);
496     this.trackingHeapSnapshotProfileType = new WebInspector.TrackingHeapSnapshotProfileType();
497     this._addProfileType(this.trackingHeapSnapshotProfileType);
498     HeapProfilerAgent.enable();
499
500     if (Capabilities.isMainFrontend && WebInspector.experimentsSettings.canvasInspection.isEnabled()) {
501         this.canvasProfileType = new WebInspector.CanvasProfileType();
502         this._addProfileType(this.canvasProfileType);
503     }
504 }
505
506 WebInspector.ProfileTypeRegistry.prototype = {
507     /**
508      * @param {!WebInspector.ProfileType} profileType
509      */
510     _addProfileType: function(profileType)
511     {
512         this._profileTypes.push(profileType);
513     },
514
515     /**
516      * @return {!Array.<!WebInspector.ProfileType>}
517      */
518     profileTypes: function()
519     {
520         return this._profileTypes;
521     }
522 }
523
524
525
526 WebInspector.ProfilesPanel.prototype = {
527     /**
528      * @return {!WebInspector.SearchableView}
529      */
530     searchableView: function()
531     {
532         return this._searchableView;
533     },
534
535     _createFileSelectorElement: function()
536     {
537         if (this._fileSelectorElement)
538             this.element.removeChild(this._fileSelectorElement);
539         this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
540         this.element.appendChild(this._fileSelectorElement);
541     },
542
543     _findProfileTypeByExtension: function(fileName)
544     {
545         var types = WebInspector.ProfileTypeRegistry.instance.profileTypes();
546         for (var i = 0; i < types.length; i++) {
547             var type = types[i];
548             var extension = type.fileExtension();
549             if (!extension)
550                 continue;
551             if (fileName.endsWith(type.fileExtension()))
552                 return type;
553         }
554         return null;
555     },
556
557     _registerShortcuts: function()
558     {
559         this.registerShortcuts(WebInspector.ShortcutsScreen.ProfilesPanelShortcuts.StartStopRecording, this.toggleRecordButton.bind(this));
560     },
561
562     _configureCpuProfilerSamplingInterval: function()
563     {
564         var intervalUs = WebInspector.settings.highResolutionCpuProfiling.get() ? 100 : 1000;
565         ProfilerAgent.setSamplingInterval(intervalUs, didChangeInterval);
566         function didChangeInterval(error)
567         {
568             if (error)
569                 WebInspector.console.showErrorMessage(error);
570         }
571     },
572
573     /**
574      * @param {!File} file
575      */
576     _loadFromFile: function(file)
577     {
578         this._createFileSelectorElement();
579
580         var profileType = this._findProfileTypeByExtension(file.name);
581         if (!profileType) {
582             var extensions = [];
583             var types = WebInspector.ProfileTypeRegistry.instance.profileTypes();
584             for (var i = 0; i < types.length; i++) {
585                 var extension = types[i].fileExtension();
586                 if (!extension)
587                     continue;
588                 extensions.push(extension);
589             }
590             WebInspector.console.log(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '")));
591             return;
592         }
593
594         if (!!profileType.profileBeingRecorded()) {
595             WebInspector.console.log(WebInspector.UIString("Can't load profile when other profile is recording."));
596             return;
597         }
598
599         profileType.loadFromFile(file);
600     },
601
602     /**
603      * @return {boolean}
604      */
605     toggleRecordButton: function()
606     {
607         if (!this.recordButton.enabled())
608             return true;
609         var type = this._selectedProfileType;
610         var isProfiling = type.buttonClicked();
611         this._updateRecordButton(isProfiling);
612         if (isProfiling) {
613             this._launcherView.profileStarted();
614             if (type.hasTemporaryView())
615                 this.showProfile(type.profileBeingRecorded());
616         } else {
617             this._launcherView.profileFinished();
618         }
619         return true;
620     },
621
622     _onProfilingStateChanged: function()
623     {
624         this._updateRecordButton(this.recordButton.toggled);
625     },
626
627     /**
628      * @param {boolean} toggled
629      */
630     _updateRecordButton: function(toggled)
631     {
632         var enable = toggled || !this._target.profilingLock.isAcquired();
633         this.recordButton.setEnabled(enable);
634         this.recordButton.toggled = toggled;
635         if (enable)
636             this.recordButton.title = this._selectedProfileType ? this._selectedProfileType.buttonTooltip : "";
637         else
638             this.recordButton.title = WebInspector.UIString("Another profiler is already active");
639         if (this._selectedProfileType)
640             this._launcherView.updateProfileType(this._selectedProfileType, enable);
641     },
642
643     _profileBeingRecordedRemoved: function()
644     {
645         this._updateRecordButton(false);
646         this._launcherView.profileFinished();
647     },
648
649     /**
650      * @param {!WebInspector.Event} event
651      */
652     _onProfileTypeSelected: function(event)
653     {
654         this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data);
655         this._updateProfileTypeSpecificUI();
656     },
657
658     _updateProfileTypeSpecificUI: function()
659     {
660         this._updateRecordButton(this.recordButton.toggled);
661         this._profileTypeStatusBarItemsContainer.removeChildren();
662         var statusBarItems = this._selectedProfileType.statusBarItems;
663         if (statusBarItems) {
664             for (var i = 0; i < statusBarItems.length; ++i)
665                 this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]);
666         }
667     },
668
669     _reset: function()
670     {
671         WebInspector.Panel.prototype.reset.call(this);
672
673         var types = WebInspector.ProfileTypeRegistry.instance.profileTypes();
674         for (var i = 0; i < types.length; i++)
675             types[i]._reset();
676
677         delete this.visibleView;
678         delete this.currentQuery;
679         this.searchCanceled();
680
681         this._profileGroups = {};
682         this._updateRecordButton(false);
683         this._launcherView.profileFinished();
684
685         this.sidebarTree.element.classList.remove("some-expandable");
686
687         this._launcherView.detach();
688         this.profileViews.removeChildren();
689         this._profileViewStatusBarItemsContainer.removeChildren();
690
691         this.removeAllListeners();
692
693         this.recordButton.visible = true;
694         this._profileViewStatusBarItemsContainer.classList.remove("hidden");
695         this.clearResultsButton.element.classList.remove("hidden");
696         this.profilesItemTreeElement.select();
697         this._showLauncherView();
698     },
699
700     _showLauncherView: function()
701     {
702         this.closeVisibleView();
703         this._profileViewStatusBarItemsContainer.removeChildren();
704         this._launcherView.show(this.profileViews);
705         this.visibleView = this._launcherView;
706     },
707
708     _garbageCollectButtonClicked: function()
709     {
710         HeapProfilerAgent.collectGarbage();
711     },
712
713     /**
714      * @param {!WebInspector.ProfileType} profileType
715      */
716     _registerProfileType: function(profileType)
717     {
718         this._launcherView.addProfileType(profileType);
719         var profileTypeSection = new WebInspector.ProfileTypeSidebarSection(this, profileType);
720         this._typeIdToSidebarSection[profileType.id] = profileTypeSection
721         this.sidebarTree.appendChild(profileTypeSection);
722         profileTypeSection.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
723
724         /**
725          * @param {!WebInspector.Event} event
726          * @this {WebInspector.ProfilesPanel}
727          */
728         function onAddProfileHeader(event)
729         {
730             this._addProfileHeader(/** @type {!WebInspector.ProfileHeader} */ (event.data));
731         }
732
733         /**
734          * @param {!WebInspector.Event} event
735          * @this {WebInspector.ProfilesPanel}
736          */
737         function onRemoveProfileHeader(event)
738         {
739             this._removeProfileHeader(/** @type {!WebInspector.ProfileHeader} */ (event.data));
740         }
741
742         /**
743          * @param {!WebInspector.Event} event
744          * @this {WebInspector.ProfilesPanel}
745          */
746         function profileComplete(event)
747         {
748             this.showProfile(/** @type {!WebInspector.ProfileHeader} */ (event.data));
749         }
750
751         profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this);
752         profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this);
753         profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this);
754         profileType.addEventListener(WebInspector.ProfileType.Events.ProfileComplete, profileComplete, this);
755
756         var profiles = profileType.getProfiles();
757         for (var i = 0; i < profiles.length; i++)
758             this._addProfileHeader(profiles[i]);
759     },
760
761     /**
762      * @param {?Event} event
763      */
764     _handleContextMenuEvent: function(event)
765     {
766         var element = event.srcElement;
767         while (element && !element.treeElement && element !== this.element)
768             element = element.parentElement;
769         if (!element)
770             return;
771         if (element.treeElement && element.treeElement.handleContextMenuEvent) {
772             element.treeElement.handleContextMenuEvent(event, this);
773             return;
774         }
775
776         var contextMenu = new WebInspector.ContextMenu(event);
777         if (this.visibleView instanceof WebInspector.HeapSnapshotView) {
778             this.visibleView.populateContextMenu(contextMenu, event);
779         }
780         if (element !== this.element || event.srcElement === this.sidebarElement()) {
781             contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement));
782         }
783         contextMenu.show();
784     },
785
786     showLoadFromFileDialog: function()
787     {
788         this._fileSelectorElement.click();
789     },
790
791     /**
792      * @param {!WebInspector.ProfileHeader} profile
793      */
794     _addProfileHeader: function(profile)
795     {
796         var profileType = profile.profileType();
797         var typeId = profileType.id;
798         this._typeIdToSidebarSection[typeId].addProfileHeader(profile);;
799         if (!this.visibleView || this.visibleView === this._launcherView)
800             this.showProfile(profile);
801     },
802
803     /**
804      * @param {!WebInspector.ProfileHeader} profile
805      */
806     _removeProfileHeader: function(profile)
807     {
808         if (profile.profileType()._profileBeingRecorded === profile)
809             this._profileBeingRecordedRemoved();
810
811         var i = this._indexOfViewForProfile(profile);
812         if (i !== -1)
813             this._profileToView.splice(i, 1);
814
815         var profileType = profile.profileType();
816         var typeId = profileType.id;
817         var sectionIsEmpty = this._typeIdToSidebarSection[typeId].removeProfileHeader(profile);
818
819         // No other item will be selected if there aren't any other profiles, so
820         // make sure that view gets cleared when the last profile is removed.
821         if (sectionIsEmpty) {
822             this.profilesItemTreeElement.select();
823             this._showLauncherView();
824         }
825     },
826
827     /**
828      * @param {?WebInspector.ProfileHeader} profile
829      * @return {?WebInspector.View}
830      */
831     showProfile: function(profile)
832     {
833         if (!profile || (profile.profileType().profileBeingRecorded() === profile) && !profile.profileType().hasTemporaryView())
834             return null;
835
836         var view = this._viewForProfile(profile);
837         if (view === this.visibleView)
838             return view;
839
840         this.closeVisibleView();
841
842         view.show(this.profileViews);
843
844         this.visibleView = view;
845
846         var profileTypeSection = this._typeIdToSidebarSection[profile.profileType().id];
847         var sidebarElement = profileTypeSection.sidebarElementForProfile(profile);
848         sidebarElement.revealAndSelect();
849
850         this._profileViewStatusBarItemsContainer.removeChildren();
851
852         var statusBarItems = view.statusBarItems;
853         if (statusBarItems)
854             for (var i = 0; i < statusBarItems.length; ++i)
855                 this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
856
857         return view;
858     },
859
860     /**
861      * @param {!HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId
862      * @param {string} perspectiveName
863      */
864     showObject: function(snapshotObjectId, perspectiveName)
865     {
866         var heapProfiles = WebInspector.ProfileTypeRegistry.instance.heapSnapshotProfileType.getProfiles();
867         for (var i = 0; i < heapProfiles.length; i++) {
868             var profile = heapProfiles[i];
869             // FIXME: allow to choose snapshot if there are several options.
870             if (profile.maxJSObjectId >= snapshotObjectId) {
871                 this.showProfile(profile);
872                 var view = this._viewForProfile(profile);
873                 view.highlightLiveObject(perspectiveName, snapshotObjectId);
874                 break;
875             }
876         }
877     },
878
879     /**
880      * @param {!WebInspector.ProfileHeader} profile
881      * @return {!WebInspector.View}
882      */
883     _viewForProfile: function(profile)
884     {
885         var index = this._indexOfViewForProfile(profile);
886         if (index !== -1)
887             return this._profileToView[index].view;
888         var view = profile.createView(this);
889         view.element.classList.add("profile-view");
890         this._profileToView.push({ profile: profile, view: view});
891         return view;
892     },
893
894     /**
895      * @param {!WebInspector.ProfileHeader} profile
896      * @return {number}
897      */
898     _indexOfViewForProfile: function(profile)
899     {
900         for (var i = 0; i < this._profileToView.length; i++) {
901             if (this._profileToView[i].profile === profile)
902                 return i;
903         }
904         return -1;
905     },
906
907     closeVisibleView: function()
908     {
909         if (this.visibleView)
910             this.visibleView.detach();
911         delete this.visibleView;
912     },
913
914     /**
915      * @param {string} query
916      * @param {boolean} shouldJump
917      * @param {boolean=} jumpBackwards
918      */
919     performSearch: function(query, shouldJump, jumpBackwards)
920     {
921         this.searchCanceled();
922
923         var visibleView = this.visibleView;
924         if (!visibleView)
925             return;
926
927         /**
928          * @this {WebInspector.ProfilesPanel}
929          */
930         function finishedCallback(view, searchMatches)
931         {
932             if (!searchMatches)
933                 return;
934             this._searchableView.updateSearchMatchesCount(searchMatches);
935             this._searchResultsView = view;
936             if (shouldJump) {
937                 if (jumpBackwards)
938                     view.jumpToLastSearchResult();
939                 else
940                     view.jumpToFirstSearchResult();
941                 this._searchableView.updateCurrentMatchIndex(view.currentSearchResultIndex());
942             }
943         }
944
945         visibleView.currentQuery = query;
946         visibleView.performSearch(query, finishedCallback.bind(this));
947     },
948
949     jumpToNextSearchResult: function()
950     {
951         if (!this._searchResultsView)
952             return;
953         if (this._searchResultsView !== this.visibleView)
954             return;
955         this._searchResultsView.jumpToNextSearchResult();
956         this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex());
957     },
958
959     jumpToPreviousSearchResult: function()
960     {
961         if (!this._searchResultsView)
962             return;
963         if (this._searchResultsView !== this.visibleView)
964             return;
965         this._searchResultsView.jumpToPreviousSearchResult();
966         this._searchableView.updateCurrentMatchIndex(this._searchResultsView.currentSearchResultIndex());
967     },
968
969     searchCanceled: function()
970     {
971         if (this._searchResultsView) {
972             if (this._searchResultsView.searchCanceled)
973                 this._searchResultsView.searchCanceled();
974             this._searchResultsView.currentQuery = null;
975             this._searchResultsView = null;
976         }
977         this._searchableView.updateSearchMatchesCount(0);
978     },
979
980     /**
981      * @param {!WebInspector.ContextMenu} contextMenu
982      * @param {!Object} target
983      */
984     appendApplicableItems: function(event, contextMenu, target)
985     {
986         if (!(target instanceof WebInspector.RemoteObject))
987             return;
988
989         if (WebInspector.inspectorView.currentPanel() !== this)
990             return;
991
992         var object = /** @type {!WebInspector.RemoteObject} */ (target);
993         var objectId = object.objectId;
994         if (!objectId)
995             return;
996
997         var heapProfiles = WebInspector.ProfileTypeRegistry.instance.heapSnapshotProfileType.getProfiles();
998         if (!heapProfiles.length)
999             return;
1000
1001         /**
1002          * @this {WebInspector.ProfilesPanel}
1003          */
1004         function revealInView(viewName)
1005         {
1006             HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName));
1007         }
1008
1009         /**
1010          * @this {WebInspector.ProfilesPanel}
1011          */
1012         function didReceiveHeapObjectId(viewName, error, result)
1013         {
1014             if (WebInspector.inspectorView.currentPanel() !== this)
1015                 return;
1016             if (!error)
1017                 this.showObject(result, viewName);
1018         }
1019
1020         if (WebInspector.settings.showAdvancedHeapSnapshotProperties.get())
1021             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators"));
1022         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary"));
1023     },
1024
1025     __proto__: WebInspector.PanelWithSidebarTree.prototype
1026 }
1027
1028
1029 /**
1030  * @constructor
1031  * @extends {WebInspector.SidebarSectionTreeElement}
1032  * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
1033  * @param {!WebInspector.ProfileType} profileType
1034  */
1035 WebInspector.ProfileTypeSidebarSection = function(dataDisplayDelegate, profileType)
1036 {
1037     WebInspector.SidebarSectionTreeElement.call(this, profileType.treeItemTitle, null, true);
1038     this._dataDisplayDelegate = dataDisplayDelegate;
1039     this._profileTreeElements = [];
1040     this._profileGroups = {};
1041     this.hidden = true;
1042 }
1043
1044 /**
1045  * @constructor
1046  */
1047 WebInspector.ProfileTypeSidebarSection.ProfileGroup = function()
1048 {
1049     this.profileSidebarTreeElements = [];
1050     this.sidebarTreeElement = null;
1051 }
1052
1053 WebInspector.ProfileTypeSidebarSection.prototype = {
1054     /**
1055      * @param {!WebInspector.ProfileHeader} profile
1056      */
1057     addProfileHeader: function(profile)
1058     {
1059         this.hidden = false;
1060         var profileType = profile.profileType();
1061         var sidebarParent = this;
1062         var profileTreeElement = profile.createSidebarTreeElement(this._dataDisplayDelegate);
1063         this._profileTreeElements.push(profileTreeElement);
1064
1065         if (!profile.fromFile() && profileType.profileBeingRecorded() !== profile) {
1066             var profileTitle = profile.title;
1067             var group = this._profileGroups[profileTitle];
1068             if (!group) {
1069                 group = new WebInspector.ProfileTypeSidebarSection.ProfileGroup();
1070                 this._profileGroups[profileTitle] = group;
1071             }
1072             group.profileSidebarTreeElements.push(profileTreeElement);
1073
1074             var groupSize = group.profileSidebarTreeElements.length;
1075             if (groupSize === 2) {
1076                 // Make a group TreeElement now that there are 2 profiles.
1077                 group.sidebarTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this._dataDisplayDelegate, profile.title);
1078
1079                 var firstProfileTreeElement = group.profileSidebarTreeElements[0];
1080                 // Insert at the same index for the first profile of the group.
1081                 var index = this.children.indexOf(firstProfileTreeElement);
1082                 this.insertChild(group.sidebarTreeElement, index);
1083
1084                 // Move the first profile to the group.
1085                 var selected = firstProfileTreeElement.selected;
1086                 this.removeChild(firstProfileTreeElement);
1087                 group.sidebarTreeElement.appendChild(firstProfileTreeElement);
1088                 if (selected)
1089                     firstProfileTreeElement.revealAndSelect();
1090
1091                 firstProfileTreeElement.small = true;
1092                 firstProfileTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
1093
1094                 this.treeOutline.element.classList.add("some-expandable");
1095             }
1096
1097             if (groupSize >= 2) {
1098                 sidebarParent = group.sidebarTreeElement;
1099                 profileTreeElement.small = true;
1100                 profileTreeElement.mainTitle = WebInspector.UIString("Run %d", groupSize);
1101             }
1102         }
1103
1104         sidebarParent.appendChild(profileTreeElement);
1105     },
1106
1107     /**
1108      * @param {!WebInspector.ProfileHeader} profile
1109      * @return {boolean}
1110      */
1111     removeProfileHeader: function(profile)
1112     {
1113         var index = this._sidebarElementIndex(profile);
1114         if (index === -1)
1115             return false;
1116         var profileTreeElement = this._profileTreeElements[index];
1117         this._profileTreeElements.splice(index, 1);
1118
1119         var sidebarParent = this;
1120         var group = this._profileGroups[profile.title];
1121         if (group) {
1122             var groupElements = group.profileSidebarTreeElements;
1123             groupElements.splice(groupElements.indexOf(profileTreeElement), 1);
1124             if (groupElements.length === 1) {
1125                 // Move the last profile out of its group and remove the group.
1126                 var pos = sidebarParent.children.indexOf(group.sidebarTreeElement);
1127                 this.insertChild(groupElements[0], pos);
1128                 groupElements[0].small = false;
1129                 groupElements[0].mainTitle = group.sidebarTreeElement.title;
1130                 this.removeChild(group.sidebarTreeElement);
1131             }
1132             if (groupElements.length !== 0)
1133                 sidebarParent = group.sidebarTreeElement;
1134         }
1135         sidebarParent.removeChild(profileTreeElement);
1136         profileTreeElement.dispose();
1137
1138         if (this.children.length)
1139             return false;
1140         this.hidden = true;
1141         return true;
1142     },
1143
1144     /**
1145      * @param {!WebInspector.ProfileHeader} profile
1146      * @return {?WebInspector.ProfileSidebarTreeElement}
1147      */
1148     sidebarElementForProfile: function(profile)
1149     {
1150         var index = this._sidebarElementIndex(profile);
1151         return index === -1 ? null : this._profileTreeElements[index];
1152     },
1153
1154     /**
1155      * @param {!WebInspector.ProfileHeader} profile
1156      * @return {number}
1157      */
1158     _sidebarElementIndex: function(profile)
1159     {
1160         var elements = this._profileTreeElements;
1161         for (var i = 0; i < elements.length; i++) {
1162             if (elements[i].profile === profile)
1163                 return i;
1164         }
1165         return -1;
1166     },
1167
1168     __proto__: WebInspector.SidebarSectionTreeElement.prototype
1169 }
1170
1171
1172 /**
1173  * @constructor
1174  * @implements {WebInspector.ContextMenu.Provider}
1175  */
1176 WebInspector.ProfilesPanel.ContextMenuProvider = function()
1177 {
1178 }
1179
1180 WebInspector.ProfilesPanel.ContextMenuProvider.prototype = {
1181     /**
1182      * @param {!WebInspector.ContextMenu} contextMenu
1183      * @param {!Object} target
1184      */
1185     appendApplicableItems: function(event, contextMenu, target)
1186     {
1187         WebInspector.inspectorView.panel("profiles").appendApplicableItems(event, contextMenu, target);
1188     }
1189 }
1190
1191 /**
1192  * @constructor
1193  * @extends {WebInspector.SidebarTreeElement}
1194  * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
1195  * @param {!WebInspector.ProfileHeader} profile
1196  * @param {string} className
1197  */
1198 WebInspector.ProfileSidebarTreeElement = function(dataDisplayDelegate, profile, className)
1199 {
1200     this._dataDisplayDelegate = dataDisplayDelegate;
1201     this.profile = profile;
1202     WebInspector.SidebarTreeElement.call(this, className, profile.title, "", profile, false);
1203     this.refreshTitles();
1204     profile.addEventListener(WebInspector.ProfileHeader.Events.UpdateStatus, this._updateStatus, this);
1205     if (profile.canSaveToFile())
1206         this._createSaveLink();
1207     else
1208         profile.addEventListener(WebInspector.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this);
1209 }
1210
1211 WebInspector.ProfileSidebarTreeElement.prototype = {
1212     _createSaveLink: function()
1213     {
1214         this._saveLinkElement = this.titleContainer.createChild("span", "save-link");
1215         this._saveLinkElement.textContent = WebInspector.UIString("Save");
1216         this._saveLinkElement.addEventListener("click", this._saveProfile.bind(this), false);
1217     },
1218
1219     _onProfileReceived: function(event)
1220     {
1221         this._createSaveLink();
1222     },
1223
1224     /**
1225      * @param {!WebInspector.Event} event
1226      */
1227     _updateStatus: function(event)
1228     {
1229         var statusUpdate = event.data;
1230         if (statusUpdate.subtitle !== null)
1231             this.subtitle = statusUpdate.subtitle;
1232         if (typeof statusUpdate.wait === "boolean")
1233             this.wait = statusUpdate.wait;
1234         this.refreshTitles();
1235     },
1236
1237     dispose: function()
1238     {
1239         this.profile.removeEventListener(WebInspector.ProfileHeader.Events.UpdateStatus, this._updateStatus, this);
1240         this.profile.removeEventListener(WebInspector.ProfileHeader.Events.ProfileReceived, this._onProfileReceived, this);
1241     },
1242
1243     onselect: function()
1244     {
1245         this._dataDisplayDelegate.showProfile(this.profile);
1246     },
1247
1248     /**
1249      * @return {boolean}
1250      */
1251     ondelete: function()
1252     {
1253         this.profile.profileType().removeProfile(this.profile);
1254         return true;
1255     },
1256
1257     /**
1258      * @param {!Event} event
1259      * @param {!WebInspector.ProfilesPanel} panel
1260      */
1261     handleContextMenuEvent: function(event, panel)
1262     {
1263         var profile = this.profile;
1264         var contextMenu = new WebInspector.ContextMenu(event);
1265         // FIXME: use context menu provider
1266         contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement));
1267         if (profile.canSaveToFile())
1268             contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile));
1269         contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this));
1270         contextMenu.show();
1271     },
1272
1273     _saveProfile: function(event)
1274     {
1275         this.profile.saveToFile();
1276     },
1277
1278     __proto__: WebInspector.SidebarTreeElement.prototype
1279 }
1280
1281 /**
1282  * @constructor
1283  * @extends {WebInspector.SidebarTreeElement}
1284  * @param {!WebInspector.ProfileType.DataDisplayDelegate} dataDisplayDelegate
1285  * @param {string} title
1286  * @param {string=} subtitle
1287  */
1288 WebInspector.ProfileGroupSidebarTreeElement = function(dataDisplayDelegate, title, subtitle)
1289 {
1290     WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
1291     this._dataDisplayDelegate = dataDisplayDelegate;
1292 }
1293
1294 WebInspector.ProfileGroupSidebarTreeElement.prototype = {
1295     onselect: function()
1296     {
1297         if (this.children.length > 0)
1298             this._dataDisplayDelegate.showProfile(this.children[this.children.length - 1].profile);
1299     },
1300
1301     __proto__: WebInspector.SidebarTreeElement.prototype
1302 }
1303
1304 /**
1305  * @constructor
1306  * @extends {WebInspector.SidebarTreeElement}
1307  * @param {!WebInspector.ProfilesPanel} panel
1308  */
1309 WebInspector.ProfilesSidebarTreeElement = function(panel)
1310 {
1311     this._panel = panel;
1312     this.small = false;
1313
1314     WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false);
1315 }
1316
1317 WebInspector.ProfilesSidebarTreeElement.prototype = {
1318     onselect: function()
1319     {
1320         this._panel._showLauncherView();
1321     },
1322
1323     get selectable()
1324     {
1325         return true;
1326     },
1327
1328     __proto__: WebInspector.SidebarTreeElement.prototype
1329 }
1330
1331
1332 importScript("../components/CPUProfileModel.js");
1333 importScript("CPUProfileDataGrid.js");
1334 importScript("CPUProfileBottomUpDataGrid.js");
1335 importScript("CPUProfileTopDownDataGrid.js");
1336 importScript("CPUProfileFlameChart.js");
1337 importScript("CPUProfileView.js");
1338 importScript("HeapSnapshotCommon.js");
1339 importScript("HeapSnapshotProxy.js");
1340 importScript("HeapSnapshotDataGrids.js");
1341 importScript("HeapSnapshotGridNodes.js");
1342 importScript("HeapSnapshotView.js");
1343 importScript("ProfileLauncherView.js");
1344 importScript("CanvasProfileView.js");
1345 importScript("CanvasReplayStateView.js");
1346
1347 WebInspector.ProfileTypeRegistry.instance = new WebInspector.ProfileTypeRegistry();