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