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