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