Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
     27 
     28 /**
     29  * @constructor
     30  * @extends {WebInspector.Object}
     31  * @param {string} id
     32  * @param {string} name
     33  */
     34 WebInspector.ProfileType = function(id, name)
     35 {
     36     this._id = id;
     37     this._name = name;
     38     /** @type {!Array.<!WebInspector.ProfileHeader>} */
     39     this._profiles = [];
     40     this._profilesIdMap = {};
     41     /** @type {WebInspector.SidebarSectionTreeElement} */
     42     this.treeElement = null;
     43 }
     44 
     45 WebInspector.ProfileType.Events = {
     46     AddProfileHeader: "add-profile-header",
     47     RemoveProfileHeader: "remove-profile-header",
     48     ProgressUpdated: "progress-updated",
     49     ViewUpdated: "view-updated"
     50 }
     51 
     52 WebInspector.ProfileType.prototype = {
     53     /**
     54      * @return {boolean}
     55      */
     56     hasTemporaryView: function()
     57     {
     58         return false;
     59     },
     60 
     61     /**
     62      * @return {string|null}
     63      */
     64     fileExtension: function()
     65     {
     66         return null;
     67     },
     68 
     69     get statusBarItems()
     70     {
     71         return [];
     72     },
     73 
     74     get buttonTooltip()
     75     {
     76         return "";
     77     },
     78 
     79     get id()
     80     {
     81         return this._id;
     82     },
     83 
     84     get treeItemTitle()
     85     {
     86         return this._name;
     87     },
     88 
     89     get name()
     90     {
     91         return this._name;
     92     },
     93 
     94     /**
     95      * @return {boolean}
     96      */
     97     buttonClicked: function()
     98     {
     99         return false;
    100     },
    101 
    102     get description()
    103     {
    104         return "";
    105     },
    106 
    107     /**
    108      * @return {boolean}
    109      */
    110     isInstantProfile: function()
    111     {
    112         return false;
    113     },
    114 
    115     /**
    116      * @return {boolean}
    117      */
    118     isEnabled: function()
    119     {
    120         return true;
    121     },
    122 
    123     /**
    124      * @return {!Array.<!WebInspector.ProfileHeader>}
    125      */
    126     getProfiles: function()
    127     {
    128         return this._profiles.filter(function(profile) { return !profile.isTemporary; });
    129     },
    130 
    131     /**
    132      * @return {Element}
    133      */
    134     decorationElement: function()
    135     {
    136         return null;
    137     },
    138 
    139     /**
    140      * @nosideeffects
    141      * @param {number} uid
    142      * @return {WebInspector.ProfileHeader}
    143      */
    144     getProfile: function(uid)
    145     {
    146         return this._profilesIdMap[this._makeKey(uid)];
    147     },
    148 
    149     // Must be implemented by subclasses.
    150     /**
    151      * @param {string=} title
    152      * @return {!WebInspector.ProfileHeader}
    153      */
    154     createTemporaryProfile: function(title)
    155     {
    156         throw new Error("Needs implemented.");
    157     },
    158 
    159     /**
    160      * @param {ProfilerAgent.ProfileHeader} profile
    161      * @return {!WebInspector.ProfileHeader}
    162      */
    163     createProfile: function(profile)
    164     {
    165         throw new Error("Not supported for " + this._name + " profiles.");
    166     },
    167 
    168     /**
    169      * @nosideeffects
    170      * @param {number} id
    171      * @return {string}
    172      */
    173     _makeKey: function(id)
    174     {
    175         return id + '/' + escape(this.id);
    176     },
    177 
    178     /**
    179      * @param {!WebInspector.ProfileHeader} profile
    180      */
    181     addProfile: function(profile)
    182     {
    183         this._profiles.push(profile);
    184         // FIXME: uid only based key should be enough.
    185         this._profilesIdMap[this._makeKey(profile.uid)] = profile;
    186         this.dispatchEventToListeners(WebInspector.ProfileType.Events.AddProfileHeader, profile);
    187     },
    188 
    189     /**
    190      * @param {!WebInspector.ProfileHeader} profile
    191      */
    192     removeProfile: function(profile)
    193     {
    194         for (var i = 0; i < this._profiles.length; ++i) {
    195             if (this._profiles[i].uid === profile.uid) {
    196                 this._profiles.splice(i, 1);
    197                 break;
    198             }
    199         }
    200         delete this._profilesIdMap[this._makeKey(profile.uid)];
    201     },
    202 
    203     /**
    204      * @nosideeffects
    205      * @return {WebInspector.ProfileHeader}
    206      */
    207     findTemporaryProfile: function()
    208     {
    209         for (var i = 0; i < this._profiles.length; ++i) {
    210             if (this._profiles[i].isTemporary)
    211                 return this._profiles[i];
    212         }
    213         return null;
    214     },
    215 
    216     _reset: function()
    217     {
    218         var profiles = this._profiles.slice(0);
    219         for (var i = 0; i < profiles.length; ++i) {
    220             var profile = profiles[i];
    221             var view = profile.existingView();
    222             if (view) {
    223                 view.detach();
    224                 if ("dispose" in view)
    225                     view.dispose();
    226             }
    227             this.dispatchEventToListeners(WebInspector.ProfileType.Events.RemoveProfileHeader, profile);
    228         }
    229         this.treeElement.removeChildren();
    230         this._profiles = [];
    231         this._profilesIdMap = {};
    232     },
    233 
    234     /**
    235      * @param {function(this:WebInspector.ProfileType, ?string, !Array.<!ProfilerAgent.ProfileHeader>)} populateCallback
    236      */
    237     _requestProfilesFromBackend: function(populateCallback)
    238     {
    239     },
    240 
    241     _populateProfiles: function()
    242     {
    243         /**
    244          * @param {?string} error
    245          * @param {!Array.<!ProfilerAgent.ProfileHeader>} profileHeaders
    246          */
    247         function populateCallback(error, profileHeaders) {
    248             if (error)
    249                 return;
    250             profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
    251             var count = profileHeaders.length;
    252             for (var i = 0; i < count; ++i)
    253                 this.addProfile(this.createProfile(profileHeaders[i]));
    254         }
    255         this._requestProfilesFromBackend(populateCallback.bind(this));
    256     },
    257 
    258     __proto__: WebInspector.Object.prototype
    259 }
    260 
    261 /**
    262  * @constructor
    263  * @param {!WebInspector.ProfileType} profileType
    264  * @param {string} title
    265  * @param {number=} uid
    266  */
    267 WebInspector.ProfileHeader = function(profileType, title, uid)
    268 {
    269     this._profileType = profileType;
    270     this.title = title;
    271     this.isTemporary = uid === undefined;
    272     this.uid = this.isTemporary ? -1 : uid;
    273     this._fromFile = false;
    274 }
    275 
    276 WebInspector.ProfileHeader.prototype = {
    277     /**
    278      * @return {!WebInspector.ProfileType}
    279      */
    280     profileType: function()
    281     {
    282         return this._profileType;
    283     },
    284 
    285     /**
    286      * Must be implemented by subclasses.
    287      * @return {WebInspector.ProfileSidebarTreeElement}
    288      */
    289     createSidebarTreeElement: function()
    290     {
    291         throw new Error("Needs implemented.");
    292     },
    293 
    294     /**
    295      * @return {?WebInspector.View}
    296      */
    297     existingView: function()
    298     {
    299         return this._view;
    300     },
    301 
    302     /**
    303      * @param {!WebInspector.ProfilesPanel} panel
    304      * @return {!WebInspector.View}
    305      */
    306     view: function(panel)
    307     {
    308         if (!this._view)
    309             this._view = this.createView(panel);
    310         return this._view;
    311     },
    312 
    313     /**
    314      * @param {!WebInspector.ProfilesPanel} panel
    315      * @return {!WebInspector.View}
    316      */
    317     createView: function(panel)
    318     {
    319         throw new Error("Not implemented.");
    320     },
    321 
    322     dispose: function()
    323     {
    324     },
    325 
    326     /**
    327      * @param {Function} callback
    328      */
    329     load: function(callback)
    330     {
    331     },
    332 
    333     /**
    334      * @return {boolean}
    335      */
    336     canSaveToFile: function()
    337     {
    338         return false;
    339     },
    340 
    341     saveToFile: function()
    342     {
    343         throw new Error("Needs implemented");
    344     },
    345 
    346     /**
    347      * @param {File} file
    348      */
    349     loadFromFile: function(file)
    350     {
    351         throw new Error("Needs implemented");
    352     },
    353 
    354     /**
    355      * @return {boolean}
    356      */
    357     fromFile: function()
    358     {
    359         return this._fromFile;
    360     },
    361 
    362     setFromFile: function()
    363     {
    364         this._fromFile = true;
    365         this.uid = -2;
    366     }
    367 }
    368 
    369 /**
    370  * @constructor
    371  * @extends {WebInspector.Panel}
    372  * @implements {WebInspector.ContextMenu.Provider}
    373  * @param {string=} name
    374  * @param {WebInspector.ProfileType=} type
    375  */
    376 WebInspector.ProfilesPanel = function(name, type)
    377 {
    378     // If the name is not specified the ProfilesPanel works in multi-profile mode.
    379     var singleProfileMode = typeof name !== "undefined";
    380     name = name || "profiles";
    381     WebInspector.Panel.call(this, name);
    382     this.registerRequiredCSS("panelEnablerView.css");
    383     this.registerRequiredCSS("heapProfiler.css");
    384     this.registerRequiredCSS("profilesPanel.css");
    385 
    386     this.createSidebarViewWithTree();
    387 
    388     this.profilesItemTreeElement = new WebInspector.ProfilesSidebarTreeElement(this);
    389     this.sidebarTree.appendChild(this.profilesItemTreeElement);
    390 
    391     this._singleProfileMode = singleProfileMode;
    392     this._profileTypesByIdMap = {};
    393 
    394     this.profileViews = document.createElement("div");
    395     this.profileViews.id = "profile-views";
    396     this.splitView.mainElement.appendChild(this.profileViews);
    397 
    398     this._statusBarButtons = [];
    399 
    400     this.recordButton = new WebInspector.StatusBarButton("", "record-profile-status-bar-item");
    401     this.recordButton.addEventListener("click", this.toggleRecordButton, this);
    402     this._statusBarButtons.push(this.recordButton);
    403 
    404     this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item");
    405     this.clearResultsButton.addEventListener("click", this._clearProfiles, this);
    406     this._statusBarButtons.push(this.clearResultsButton);
    407 
    408     this._profileTypeStatusBarItemsContainer = document.createElement("div");
    409     this._profileTypeStatusBarItemsContainer.className = "status-bar-items";
    410 
    411     this._profileViewStatusBarItemsContainer = document.createElement("div");
    412     this._profileViewStatusBarItemsContainer.className = "status-bar-items";
    413 
    414     if (singleProfileMode) {
    415         this._launcherView = this._createLauncherView();
    416         this._registerProfileType(/** @type {!WebInspector.ProfileType} */ (type));
    417         this._selectedProfileType = type;
    418         this._updateProfileTypeSpecificUI();
    419     } else {
    420         this._launcherView = new WebInspector.MultiProfileLauncherView(this);
    421         this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this);
    422 
    423         this._registerProfileType(new WebInspector.CPUProfileType());
    424         this._registerProfileType(new WebInspector.HeapSnapshotProfileType());
    425         this._registerProfileType(new WebInspector.TrackingHeapSnapshotProfileType(this));
    426         if (!WebInspector.WorkerManager.isWorkerFrontend() && WebInspector.experimentsSettings.canvasInspection.isEnabled())
    427             this._registerProfileType(new WebInspector.CanvasProfileType());
    428     }
    429 
    430     this._profilesWereRequested = false;
    431     this._reset();
    432 
    433     this._createFileSelectorElement();
    434     this.element.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
    435     this._registerShortcuts();
    436 
    437     WebInspector.ContextMenu.registerProvider(this);
    438 }
    439 
    440 WebInspector.ProfilesPanel.prototype = {
    441     _createFileSelectorElement: function()
    442     {
    443         if (this._fileSelectorElement)
    444             this.element.removeChild(this._fileSelectorElement);
    445         this._fileSelectorElement = WebInspector.createFileSelectorElement(this._loadFromFile.bind(this));
    446         this.element.appendChild(this._fileSelectorElement);
    447     },
    448 
    449     /**
    450      * @return {!WebInspector.ProfileLauncherView}
    451      */
    452     _createLauncherView: function()
    453     {
    454         return new WebInspector.ProfileLauncherView(this);
    455     },
    456 
    457     _findProfileTypeByExtension: function(fileName)
    458     {
    459         for (var id in this._profileTypesByIdMap) {
    460             var type = this._profileTypesByIdMap[id];
    461             var extension = type.fileExtension();
    462             if (!extension)
    463                 continue;
    464             if (fileName.endsWith(type.fileExtension()))
    465                 return type;
    466         }
    467         return null;
    468     },
    469 
    470     _registerShortcuts: function()
    471     {
    472         this.registerShortcuts(WebInspector.ProfilesPanelDescriptor.ShortcutKeys.StartStopRecording, this.toggleRecordButton.bind(this));
    473     },
    474 
    475     /**
    476      * @param {!File} file
    477      */
    478     _loadFromFile: function(file)
    479     {
    480         this._createFileSelectorElement();
    481 
    482         var profileType = this._findProfileTypeByExtension(file.name);
    483         if (!profileType) {
    484             var extensions = [];
    485             for (var id in this._profileTypesByIdMap) {
    486                 var extension = this._profileTypesByIdMap[id].fileExtension();
    487                 if (!extension)
    488                     continue;
    489                 extensions.push(extension);
    490             }
    491             WebInspector.log(WebInspector.UIString("Can't load file. Only files with extensions '%s' can be loaded.", extensions.join("', '")));
    492             return;
    493         }
    494 
    495         if (!!profileType.findTemporaryProfile()) {
    496             WebInspector.log(WebInspector.UIString("Can't load profile when other profile is recording."));
    497             return;
    498         }
    499 
    500         var temporaryProfile = profileType.createTemporaryProfile(WebInspector.ProfilesPanelDescriptor.UserInitiatedProfileName + "." + file.name);
    501         temporaryProfile.setFromFile();
    502         profileType.addProfile(temporaryProfile);
    503         temporaryProfile.loadFromFile(file);
    504     },
    505 
    506     get statusBarItems()
    507     {
    508         return this._statusBarButtons.select("element").concat(this._profileTypeStatusBarItemsContainer, this._profileViewStatusBarItemsContainer);
    509     },
    510 
    511     /**
    512      * @param {WebInspector.Event|Event=} event
    513      * @return {boolean}
    514      */
    515     toggleRecordButton: function(event)
    516     {
    517         var isProfiling = this._selectedProfileType.buttonClicked();
    518         this.setRecordingProfile(this._selectedProfileType.id, isProfiling);
    519         return true;
    520     },
    521 
    522     _populateAllProfiles: function()
    523     {
    524         if (this._profilesWereRequested)
    525             return;
    526         this._profilesWereRequested = true;
    527         for (var typeId in this._profileTypesByIdMap)
    528             this._profileTypesByIdMap[typeId]._populateProfiles();
    529     },
    530 
    531     wasShown: function()
    532     {
    533         WebInspector.Panel.prototype.wasShown.call(this);
    534         this._populateAllProfiles();
    535     },
    536 
    537     /**
    538      * @param {WebInspector.Event} event
    539      */
    540     _onProfileTypeSelected: function(event)
    541     {
    542         this._selectedProfileType = /** @type {!WebInspector.ProfileType} */ (event.data);
    543         this._updateProfileTypeSpecificUI();
    544     },
    545 
    546     _updateProfileTypeSpecificUI: function()
    547     {
    548         this.recordButton.title = this._selectedProfileType.buttonTooltip;
    549 
    550         this._launcherView.updateProfileType(this._selectedProfileType);
    551         this._profileTypeStatusBarItemsContainer.removeChildren();
    552         var statusBarItems = this._selectedProfileType.statusBarItems;
    553         if (statusBarItems) {
    554             for (var i = 0; i < statusBarItems.length; ++i)
    555                 this._profileTypeStatusBarItemsContainer.appendChild(statusBarItems[i]);
    556         }
    557         this._resize(this.splitView.sidebarWidth());
    558     },
    559 
    560     _reset: function()
    561     {
    562         WebInspector.Panel.prototype.reset.call(this);
    563 
    564         for (var typeId in this._profileTypesByIdMap)
    565             this._profileTypesByIdMap[typeId]._reset();
    566 
    567         delete this.visibleView;
    568         delete this.currentQuery;
    569         this.searchCanceled();
    570 
    571         this._profileGroups = {};
    572         this.recordButton.toggled = false;
    573         if (this._selectedProfileType)
    574             this.recordButton.title = this._selectedProfileType.buttonTooltip;
    575         this._launcherView.profileFinished();
    576 
    577         this.sidebarTreeElement.removeStyleClass("some-expandable");
    578 
    579         this.profileViews.removeChildren();
    580         this._profileViewStatusBarItemsContainer.removeChildren();
    581 
    582         this.removeAllListeners();
    583 
    584         this.recordButton.visible = true;
    585         this._profileViewStatusBarItemsContainer.removeStyleClass("hidden");
    586         this.clearResultsButton.element.removeStyleClass("hidden");
    587         this.profilesItemTreeElement.select();
    588         this._showLauncherView();
    589     },
    590 
    591     _showLauncherView: function()
    592     {
    593         this.closeVisibleView();
    594         this._profileViewStatusBarItemsContainer.removeChildren();
    595         this._launcherView.show(this.splitView.mainElement);
    596         this.visibleView = this._launcherView;
    597     },
    598 
    599     _clearProfiles: function()
    600     {
    601         ProfilerAgent.clearProfiles();
    602         HeapProfilerAgent.clearProfiles();
    603         this._reset();
    604     },
    605 
    606     _garbageCollectButtonClicked: function()
    607     {
    608         HeapProfilerAgent.collectGarbage();
    609     },
    610 
    611     /**
    612      * @param {!WebInspector.ProfileType} profileType
    613      */
    614     _registerProfileType: function(profileType)
    615     {
    616         this._profileTypesByIdMap[profileType.id] = profileType;
    617         this._launcherView.addProfileType(profileType);
    618         profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.treeItemTitle, null, true);
    619         profileType.treeElement.hidden = !this._singleProfileMode;
    620         this.sidebarTree.appendChild(profileType.treeElement);
    621         profileType.treeElement.childrenListElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
    622         function onAddProfileHeader(event)
    623         {
    624             this._addProfileHeader(event.data);
    625         }
    626         function onRemoveProfileHeader(event)
    627         {
    628             this._removeProfileHeader(event.data);
    629         }
    630         function onProgressUpdated(event)
    631         {
    632             this._reportProfileProgress(event.data.profile, event.data.done, event.data.total);
    633         }
    634         profileType.addEventListener(WebInspector.ProfileType.Events.ViewUpdated, this._updateProfileTypeSpecificUI, this);
    635         profileType.addEventListener(WebInspector.ProfileType.Events.AddProfileHeader, onAddProfileHeader, this);
    636         profileType.addEventListener(WebInspector.ProfileType.Events.RemoveProfileHeader, onRemoveProfileHeader, this);
    637         profileType.addEventListener(WebInspector.ProfileType.Events.ProgressUpdated, onProgressUpdated, this);
    638     },
    639 
    640     /**
    641      * @param {Event} event
    642      */
    643     _handleContextMenuEvent: function(event)
    644     {
    645         var element = event.srcElement;
    646         while (element && !element.treeElement && element !== this.element)
    647             element = element.parentElement;
    648         if (!element)
    649             return;
    650         if (element.treeElement && element.treeElement.handleContextMenuEvent) {
    651             element.treeElement.handleContextMenuEvent(event, this);
    652             return;
    653         }
    654         if (element !== this.element || event.srcElement === this.sidebarElement) {
    655             var contextMenu = new WebInspector.ContextMenu(event);
    656             if (this.visibleView instanceof WebInspector.HeapSnapshotView)
    657                 this.visibleView.populateContextMenu(contextMenu, event);
    658             contextMenu.appendItem(WebInspector.UIString("Load\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement));
    659             contextMenu.show();
    660         }
    661 
    662     },
    663 
    664     /**
    665      * @nosideeffects
    666      * @param {string} text
    667      * @param {string} profileTypeId
    668      * @return {string}
    669      */
    670     _makeTitleKey: function(text, profileTypeId)
    671     {
    672         return escape(text) + '/' + escape(profileTypeId);
    673     },
    674 
    675     /**
    676      * @param {!WebInspector.ProfileHeader} profile
    677      */
    678     _addProfileHeader: function(profile)
    679     {
    680         var profileType = profile.profileType();
    681         var typeId = profileType.id;
    682         var sidebarParent = profileType.treeElement;
    683         sidebarParent.hidden = false;
    684         var small = false;
    685         var alternateTitle;
    686 
    687         if (!WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(profile.title) && !profile.isTemporary) {
    688             var profileTitleKey = this._makeTitleKey(profile.title, typeId);
    689             if (!(profileTitleKey in this._profileGroups))
    690                 this._profileGroups[profileTitleKey] = [];
    691 
    692             var group = this._profileGroups[profileTitleKey];
    693             group.push(profile);
    694             if (group.length === 2) {
    695                 // Make a group TreeElement now that there are 2 profiles.
    696                 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(this, profile.title);
    697 
    698                 // Insert at the same index for the first profile of the group.
    699                 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
    700                 sidebarParent.insertChild(group._profilesTreeElement, index);
    701 
    702                 // Move the first profile to the group.
    703                 var selected = group[0]._profilesTreeElement.selected;
    704                 sidebarParent.removeChild(group[0]._profilesTreeElement);
    705                 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
    706                 if (selected)
    707                     group[0]._profilesTreeElement.revealAndSelect();
    708 
    709                 group[0]._profilesTreeElement.small = true;
    710                 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
    711 
    712                 this.sidebarTreeElement.addStyleClass("some-expandable");
    713             }
    714 
    715             if (group.length >= 2) {
    716                 sidebarParent = group._profilesTreeElement;
    717                 alternateTitle = WebInspector.UIString("Run %d", group.length);
    718                 small = true;
    719             }
    720         }
    721 
    722         var profileTreeElement = profile.createSidebarTreeElement();
    723         profile.sidebarElement = profileTreeElement;
    724         profileTreeElement.small = small;
    725         if (alternateTitle)
    726             profileTreeElement.mainTitle = alternateTitle;
    727         profile._profilesTreeElement = profileTreeElement;
    728 
    729         var temporaryProfile = profileType.findTemporaryProfile();
    730         if (profile.isTemporary || !temporaryProfile)
    731             sidebarParent.appendChild(profileTreeElement);
    732         else {
    733             if (temporaryProfile) {
    734                 sidebarParent.insertBeforeChild(profileTreeElement, temporaryProfile._profilesTreeElement);
    735                 this._removeTemporaryProfile(profile.profileType().id);
    736             }
    737 
    738             if (!this.visibleView || this.visibleView === this._launcherView)
    739                 this._showProfile(profile);
    740 
    741             this.dispatchEventToListeners("profile added", {
    742                 type: typeId
    743             });
    744         }
    745     },
    746 
    747     /**
    748      * @param {!WebInspector.ProfileHeader} profile
    749      */
    750     _removeProfileHeader: function(profile)
    751     {
    752         profile.dispose();
    753         profile.profileType().removeProfile(profile);
    754 
    755         var sidebarParent = profile.profileType().treeElement;
    756         var profileTitleKey = this._makeTitleKey(profile.title, profile.profileType().id);
    757         var group = this._profileGroups[profileTitleKey];
    758         if (group) {
    759             group.splice(group.indexOf(profile), 1);
    760             if (group.length === 1) {
    761                 // Move the last profile out of its group and remove the group.
    762                 var index = sidebarParent.children.indexOf(group._profilesTreeElement);
    763                 sidebarParent.insertChild(group[0]._profilesTreeElement, index);
    764                 group[0]._profilesTreeElement.small = false;
    765                 group[0]._profilesTreeElement.mainTitle = group[0].title;
    766                 sidebarParent.removeChild(group._profilesTreeElement);
    767             }
    768             if (group.length !== 0)
    769                 sidebarParent = group._profilesTreeElement;
    770             else
    771                 delete this._profileGroups[profileTitleKey];
    772         }
    773         sidebarParent.removeChild(profile._profilesTreeElement);
    774 
    775         // No other item will be selected if there aren't any other profiles, so
    776         // make sure that view gets cleared when the last profile is removed.
    777         if (!sidebarParent.children.length) {
    778             this.profilesItemTreeElement.select();
    779             this._showLauncherView();
    780             sidebarParent.hidden = !this._singleProfileMode;
    781         }
    782     },
    783 
    784     /**
    785      * @param {!WebInspector.ProfileHeader} profile
    786      * @return {WebInspector.View}
    787      */
    788     _showProfile: function(profile)
    789     {
    790         if (!profile || (profile.isTemporary && !profile.profileType().hasTemporaryView()))
    791             return null;
    792 
    793         var view = profile.view(this);
    794         if (view === this.visibleView)
    795             return view;
    796 
    797         this.closeVisibleView();
    798 
    799         view.show(this.profileViews);
    800 
    801         profile._profilesTreeElement._suppressOnSelect = true;
    802         profile._profilesTreeElement.revealAndSelect();
    803         delete profile._profilesTreeElement._suppressOnSelect;
    804 
    805         this.visibleView = view;
    806 
    807         this._profileViewStatusBarItemsContainer.removeChildren();
    808 
    809         var statusBarItems = view.statusBarItems;
    810         if (statusBarItems)
    811             for (var i = 0; i < statusBarItems.length; ++i)
    812                 this._profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    813 
    814         return view;
    815     },
    816 
    817     /**
    818      * @param {HeapProfilerAgent.HeapSnapshotObjectId} snapshotObjectId
    819      * @param {string} viewName
    820      */
    821     showObject: function(snapshotObjectId, viewName)
    822     {
    823         var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles();
    824         for (var i = 0; i < heapProfiles.length; i++) {
    825             var profile = heapProfiles[i];
    826             // FIXME: allow to choose snapshot if there are several options.
    827             if (profile.maxJSObjectId >= snapshotObjectId) {
    828                 this._showProfile(profile);
    829                 var view = profile.view(this);
    830                 view.changeView(viewName, function() {
    831                     view.dataGrid.highlightObjectByHeapSnapshotId(snapshotObjectId);
    832                 });
    833                 break;
    834             }
    835         }
    836     },
    837 
    838     /**
    839      * @param {string} typeId
    840      */
    841     _createTemporaryProfile: function(typeId)
    842     {
    843         var type = this.getProfileType(typeId);
    844         if (!type.findTemporaryProfile())
    845             type.addProfile(type.createTemporaryProfile());
    846     },
    847 
    848     /**
    849      * @param {string} typeId
    850      */
    851     _removeTemporaryProfile: function(typeId)
    852     {
    853         var temporaryProfile = this.getProfileType(typeId).findTemporaryProfile();
    854         if (!!temporaryProfile)
    855             this._removeProfileHeader(temporaryProfile);
    856     },
    857 
    858     /**
    859      * @param {string} typeId
    860      * @param {number} uid
    861      */
    862     getProfile: function(typeId, uid)
    863     {
    864         return this.getProfileType(typeId).getProfile(uid);
    865     },
    866 
    867     /**
    868      * @param {WebInspector.View} view
    869      */
    870     showView: function(view)
    871     {
    872         this._showProfile(view.profile);
    873     },
    874 
    875     /**
    876      * @param {string} typeId
    877      */
    878     getProfileType: function(typeId)
    879     {
    880         return this._profileTypesByIdMap[typeId];
    881     },
    882 
    883     /**
    884      * @param {string} typeId
    885      * @param {string} uid
    886      * @return {WebInspector.View}
    887      */
    888     showProfile: function(typeId, uid)
    889     {
    890         return this._showProfile(this.getProfile(typeId, Number(uid)));
    891     },
    892 
    893     closeVisibleView: function()
    894     {
    895         if (this.visibleView)
    896             this.visibleView.detach();
    897         delete this.visibleView;
    898     },
    899 
    900     /**
    901      * @param {string} query
    902      * @param {boolean} shouldJump
    903      */
    904     performSearch: function(query, shouldJump)
    905     {
    906         this.searchCanceled();
    907 
    908         var searchableViews = this._searchableViews();
    909         if (!searchableViews || !searchableViews.length)
    910             return;
    911 
    912         var visibleView = this.visibleView;
    913 
    914         var matchesCountUpdateTimeout = null;
    915 
    916         function updateMatchesCount()
    917         {
    918             WebInspector.searchController.updateSearchMatchesCount(this._totalSearchMatches, this);
    919             WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this);
    920             matchesCountUpdateTimeout = null;
    921         }
    922 
    923         function updateMatchesCountSoon()
    924         {
    925             if (matchesCountUpdateTimeout)
    926                 return;
    927             // Update the matches count every half-second so it doesn't feel twitchy.
    928             matchesCountUpdateTimeout = setTimeout(updateMatchesCount.bind(this), 500);
    929         }
    930 
    931         function finishedCallback(view, searchMatches)
    932         {
    933             if (!searchMatches)
    934                 return;
    935 
    936             this._totalSearchMatches += searchMatches;
    937             this._searchResults.push(view);
    938 
    939             this.searchMatchFound(view, searchMatches);
    940 
    941             updateMatchesCountSoon.call(this);
    942 
    943             if (shouldJump && view === visibleView)
    944                 view.jumpToFirstSearchResult();
    945         }
    946 
    947         var i = 0;
    948         var panel = this;
    949         var boundFinishedCallback = finishedCallback.bind(this);
    950         var chunkIntervalIdentifier = null;
    951 
    952         // Split up the work into chunks so we don't block the
    953         // UI thread while processing.
    954 
    955         function processChunk()
    956         {
    957             var view = searchableViews[i];
    958 
    959             if (++i >= searchableViews.length) {
    960                 if (panel._currentSearchChunkIntervalIdentifier === chunkIntervalIdentifier)
    961                     delete panel._currentSearchChunkIntervalIdentifier;
    962                 clearInterval(chunkIntervalIdentifier);
    963             }
    964 
    965             if (!view)
    966                 return;
    967 
    968             view.currentQuery = query;
    969             view.performSearch(query, boundFinishedCallback);
    970         }
    971 
    972         processChunk();
    973 
    974         chunkIntervalIdentifier = setInterval(processChunk, 25);
    975         this._currentSearchChunkIntervalIdentifier = chunkIntervalIdentifier;
    976     },
    977 
    978     jumpToNextSearchResult: function()
    979     {
    980         if (!this.showView || !this._searchResults || !this._searchResults.length)
    981             return;
    982 
    983         var showFirstResult = false;
    984 
    985         this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
    986         if (this._currentSearchResultIndex === -1) {
    987             this._currentSearchResultIndex = 0;
    988             showFirstResult = true;
    989         }
    990 
    991         var currentView = this._searchResults[this._currentSearchResultIndex];
    992 
    993         if (currentView.showingLastSearchResult()) {
    994             if (++this._currentSearchResultIndex >= this._searchResults.length)
    995                 this._currentSearchResultIndex = 0;
    996             currentView = this._searchResults[this._currentSearchResultIndex];
    997             showFirstResult = true;
    998         }
    999 
   1000         WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this);
   1001 
   1002         if (currentView !== this.visibleView) {
   1003             this.showView(currentView);
   1004             WebInspector.searchController.showSearchField();
   1005         }
   1006 
   1007         if (showFirstResult)
   1008             currentView.jumpToFirstSearchResult();
   1009         else
   1010             currentView.jumpToNextSearchResult();
   1011     },
   1012 
   1013     jumpToPreviousSearchResult: function()
   1014     {
   1015         if (!this.showView || !this._searchResults || !this._searchResults.length)
   1016             return;
   1017 
   1018         var showLastResult = false;
   1019 
   1020         this._currentSearchResultIndex = this._searchResults.indexOf(this.visibleView);
   1021         if (this._currentSearchResultIndex === -1) {
   1022             this._currentSearchResultIndex = 0;
   1023             showLastResult = true;
   1024         }
   1025 
   1026         var currentView = this._searchResults[this._currentSearchResultIndex];
   1027 
   1028         if (currentView.showingFirstSearchResult()) {
   1029             if (--this._currentSearchResultIndex < 0)
   1030                 this._currentSearchResultIndex = (this._searchResults.length - 1);
   1031             currentView = this._searchResults[this._currentSearchResultIndex];
   1032             showLastResult = true;
   1033         }
   1034 
   1035         WebInspector.searchController.updateCurrentMatchIndex(this._currentSearchResultIndex, this);
   1036 
   1037         if (currentView !== this.visibleView) {
   1038             this.showView(currentView);
   1039             WebInspector.searchController.showSearchField();
   1040         }
   1041 
   1042         if (showLastResult)
   1043             currentView.jumpToLastSearchResult();
   1044         else
   1045             currentView.jumpToPreviousSearchResult();
   1046     },
   1047 
   1048     /**
   1049      * @return {!Array.<!WebInspector.ProfileHeader>}
   1050      */
   1051     _getAllProfiles: function()
   1052     {
   1053         var profiles = [];
   1054         for (var typeId in this._profileTypesByIdMap)
   1055             profiles = profiles.concat(this._profileTypesByIdMap[typeId].getProfiles());
   1056         return profiles;
   1057     },
   1058 
   1059     /**
   1060      * @return {!Array.<!WebInspector.View>}
   1061      */
   1062     _searchableViews: function()
   1063     {
   1064         var profiles = this._getAllProfiles();
   1065         var searchableViews = [];
   1066         for (var i = 0; i < profiles.length; ++i) {
   1067             var view = profiles[i].view(this);
   1068             if (view.performSearch)
   1069                 searchableViews.push(view)
   1070         }
   1071         var index = searchableViews.indexOf(this.visibleView);
   1072         if (index > 0) {
   1073             // Move visibleView to the first position.
   1074             searchableViews[index] = searchableViews[0];
   1075             searchableViews[0] = this.visibleView;
   1076         }
   1077         return searchableViews;
   1078     },
   1079 
   1080     searchMatchFound: function(view, matches)
   1081     {
   1082         view.profile._profilesTreeElement.searchMatches = matches;
   1083     },
   1084 
   1085     searchCanceled: function()
   1086     {
   1087         if (this._searchResults) {
   1088             for (var i = 0; i < this._searchResults.length; ++i) {
   1089                 var view = this._searchResults[i];
   1090                 if (view.searchCanceled)
   1091                     view.searchCanceled();
   1092                 delete view.currentQuery;
   1093             }
   1094         }
   1095 
   1096         WebInspector.Panel.prototype.searchCanceled.call(this);
   1097 
   1098         if (this._currentSearchChunkIntervalIdentifier) {
   1099             clearInterval(this._currentSearchChunkIntervalIdentifier);
   1100             delete this._currentSearchChunkIntervalIdentifier;
   1101         }
   1102 
   1103         this._totalSearchMatches = 0;
   1104         this._currentSearchResultIndex = 0;
   1105         this._searchResults = [];
   1106 
   1107         var profiles = this._getAllProfiles();
   1108         for (var i = 0; i < profiles.length; ++i)
   1109             profiles[i]._profilesTreeElement.searchMatches = 0;
   1110     },
   1111 
   1112     /**
   1113      * @param {!WebInspector.Event} event
   1114      */
   1115     sidebarResized: function(event)
   1116     {
   1117         var sidebarWidth = /** @type {number} */ (event.data);
   1118         this._resize(sidebarWidth);
   1119     },
   1120 
   1121     onResize: function()
   1122     {
   1123         this._resize(this.splitView.sidebarWidth());
   1124     },
   1125 
   1126     /**
   1127      * @param {number} sidebarWidth
   1128      */
   1129     _resize: function(sidebarWidth)
   1130     {
   1131         var lastItemElement = this._statusBarButtons[this._statusBarButtons.length - 1].element;
   1132         var left = lastItemElement.totalOffsetLeft() + lastItemElement.offsetWidth;
   1133         this._profileTypeStatusBarItemsContainer.style.left = left + "px";
   1134         left += this._profileTypeStatusBarItemsContainer.offsetWidth - 1;
   1135         this._profileViewStatusBarItemsContainer.style.left = Math.max(left, sidebarWidth) + "px";
   1136     },
   1137 
   1138     /**
   1139      * @param {string} profileType
   1140      * @param {boolean} isProfiling
   1141      */
   1142     setRecordingProfile: function(profileType, isProfiling)
   1143     {
   1144         var profileTypeObject = this.getProfileType(profileType);
   1145         this.recordButton.toggled = isProfiling;
   1146         this.recordButton.title = profileTypeObject.buttonTooltip;
   1147         if (isProfiling) {
   1148             this._launcherView.profileStarted();
   1149             this._createTemporaryProfile(profileType);
   1150             if (profileTypeObject.hasTemporaryView())
   1151                 this._showProfile(profileTypeObject.findTemporaryProfile());
   1152         } else
   1153             this._launcherView.profileFinished();
   1154     },
   1155 
   1156     /**
   1157      * @param {!WebInspector.ProfileHeader} profile
   1158      * @param {number} done
   1159      * @param {number} total
   1160      */
   1161     _reportProfileProgress: function(profile, done, total)
   1162     {
   1163         profile.sidebarElement.subtitle = WebInspector.UIString("%.0f%", (done / total) * 100);
   1164         profile.sidebarElement.wait = true;
   1165     },
   1166 
   1167     /**
   1168      * @param {WebInspector.ContextMenu} contextMenu
   1169      * @param {Object} target
   1170      */
   1171     appendApplicableItems: function(event, contextMenu, target)
   1172     {
   1173         if (WebInspector.inspectorView.currentPanel() !== this)
   1174             return;
   1175 
   1176         var object = /** @type {WebInspector.RemoteObject} */ (target);
   1177         var objectId = object.objectId;
   1178         if (!objectId)
   1179             return;
   1180 
   1181         var heapProfiles = this.getProfileType(WebInspector.HeapSnapshotProfileType.TypeId).getProfiles();
   1182         if (!heapProfiles.length)
   1183             return;
   1184 
   1185         function revealInView(viewName)
   1186         {
   1187             HeapProfilerAgent.getHeapObjectId(objectId, didReceiveHeapObjectId.bind(this, viewName));
   1188         }
   1189 
   1190         function didReceiveHeapObjectId(viewName, error, result)
   1191         {
   1192             if (WebInspector.inspectorView.currentPanel() !== this)
   1193                 return;
   1194             if (!error)
   1195                 this.showObject(result, viewName);
   1196         }
   1197 
   1198         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Dominators view" : "Reveal in Dominators View"), revealInView.bind(this, "Dominators"));
   1199         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Summary view" : "Reveal in Summary View"), revealInView.bind(this, "Summary"));
   1200     },
   1201 
   1202     __proto__: WebInspector.Panel.prototype
   1203 }
   1204 
   1205 /**
   1206  * @constructor
   1207  * @extends {WebInspector.SidebarTreeElement}
   1208  * @param {!WebInspector.ProfileHeader} profile
   1209  * @param {string} titleFormat
   1210  * @param {string} className
   1211  */
   1212 WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className)
   1213 {
   1214     this.profile = profile;
   1215     this._titleFormat = titleFormat;
   1216 
   1217     if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(this.profile.title))
   1218         this._profileNumber = WebInspector.ProfilesPanelDescriptor.userInitiatedProfileIndex(this.profile.title);
   1219 
   1220     WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false);
   1221 
   1222     this.refreshTitles();
   1223 }
   1224 
   1225 WebInspector.ProfileSidebarTreeElement.prototype = {
   1226     onselect: function()
   1227     {
   1228         if (!this._suppressOnSelect)
   1229             this.treeOutline.panel._showProfile(this.profile);
   1230     },
   1231 
   1232     ondelete: function()
   1233     {
   1234         this.treeOutline.panel._removeProfileHeader(this.profile);
   1235         return true;
   1236     },
   1237 
   1238     get mainTitle()
   1239     {
   1240         if (this._mainTitle)
   1241             return this._mainTitle;
   1242         if (WebInspector.ProfilesPanelDescriptor.isUserInitiatedProfile(this.profile.title))
   1243             return WebInspector.UIString(this._titleFormat, this._profileNumber);
   1244         return this.profile.title;
   1245     },
   1246 
   1247     set mainTitle(x)
   1248     {
   1249         this._mainTitle = x;
   1250         this.refreshTitles();
   1251     },
   1252 
   1253     set searchMatches(matches)
   1254     {
   1255         if (!matches) {
   1256             if (!this.bubbleElement)
   1257                 return;
   1258             this.bubbleElement.removeStyleClass("search-matches");
   1259             this.bubbleText = "";
   1260             return;
   1261         }
   1262 
   1263         this.bubbleText = matches;
   1264         this.bubbleElement.addStyleClass("search-matches");
   1265     },
   1266 
   1267     /**
   1268      * @param {!Event} event
   1269      * @param {!WebInspector.ProfilesPanel} panel
   1270      */
   1271     handleContextMenuEvent: function(event, panel)
   1272     {
   1273         var profile = this.profile;
   1274         var contextMenu = new WebInspector.ContextMenu(event);
   1275         // FIXME: use context menu provider
   1276         contextMenu.appendItem(WebInspector.UIString("Load\u2026"), panel._fileSelectorElement.click.bind(panel._fileSelectorElement));
   1277         if (profile.canSaveToFile())
   1278             contextMenu.appendItem(WebInspector.UIString("Save\u2026"), profile.saveToFile.bind(profile));
   1279         contextMenu.appendItem(WebInspector.UIString("Delete"), this.ondelete.bind(this));
   1280         contextMenu.show();
   1281     },
   1282 
   1283     __proto__: WebInspector.SidebarTreeElement.prototype
   1284 }
   1285 
   1286 /**
   1287  * @constructor
   1288  * @extends {WebInspector.SidebarTreeElement}
   1289  * @param {WebInspector.ProfilesPanel} panel
   1290  * @param {string} title
   1291  * @param {string=} subtitle
   1292  */
   1293 WebInspector.ProfileGroupSidebarTreeElement = function(panel, title, subtitle)
   1294 {
   1295     WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
   1296     this._panel = panel;
   1297 }
   1298 
   1299 WebInspector.ProfileGroupSidebarTreeElement.prototype = {
   1300     onselect: function()
   1301     {
   1302         if (this.children.length > 0)
   1303             this._panel._showProfile(this.children[this.children.length - 1].profile);
   1304     },
   1305 
   1306     __proto__: WebInspector.SidebarTreeElement.prototype
   1307 }
   1308 
   1309 /**
   1310  * @constructor
   1311  * @extends {WebInspector.SidebarTreeElement}
   1312  * @param {!WebInspector.ProfilesPanel} panel
   1313  */
   1314 WebInspector.ProfilesSidebarTreeElement = function(panel)
   1315 {
   1316     this._panel = panel;
   1317     this.small = false;
   1318 
   1319     WebInspector.SidebarTreeElement.call(this, "profile-launcher-view-tree-item", WebInspector.UIString("Profiles"), "", null, false);
   1320 }
   1321 
   1322 WebInspector.ProfilesSidebarTreeElement.prototype = {
   1323     onselect: function()
   1324     {
   1325         this._panel._showLauncherView();
   1326     },
   1327 
   1328     get selectable()
   1329     {
   1330         return true;
   1331     },
   1332 
   1333     __proto__: WebInspector.SidebarTreeElement.prototype
   1334 }
   1335 
   1336 
   1337 /**
   1338  * @constructor
   1339  * @extends {WebInspector.ProfilesPanel}
   1340  */
   1341 WebInspector.CPUProfilerPanel = function()
   1342 {
   1343     WebInspector.ProfilesPanel.call(this, "cpu-profiler", new WebInspector.CPUProfileType());
   1344 }
   1345 
   1346 WebInspector.CPUProfilerPanel.prototype = {
   1347     __proto__: WebInspector.ProfilesPanel.prototype
   1348 }
   1349 
   1350 
   1351 /**
   1352  * @constructor
   1353  * @extends {WebInspector.ProfilesPanel}
   1354  */
   1355 WebInspector.HeapProfilerPanel = function()
   1356 {
   1357     var heapSnapshotProfileType = new WebInspector.HeapSnapshotProfileType();
   1358     WebInspector.ProfilesPanel.call(this, "heap-profiler", heapSnapshotProfileType);
   1359     this._singleProfileMode = false;
   1360     this._registerProfileType(new WebInspector.TrackingHeapSnapshotProfileType(this));
   1361     this._launcherView.addEventListener(WebInspector.MultiProfileLauncherView.EventTypes.ProfileTypeSelected, this._onProfileTypeSelected, this);
   1362     this._launcherView._profileTypeChanged(heapSnapshotProfileType);
   1363 }
   1364 
   1365 WebInspector.HeapProfilerPanel.prototype = {
   1366     _createLauncherView: function()
   1367     {
   1368         return new WebInspector.MultiProfileLauncherView(this);
   1369     },
   1370 
   1371     __proto__: WebInspector.ProfilesPanel.prototype
   1372 }
   1373 
   1374 
   1375 /**
   1376  * @constructor
   1377  * @extends {WebInspector.ProfilesPanel}
   1378  */
   1379 WebInspector.CanvasProfilerPanel = function()
   1380 {
   1381     WebInspector.ProfilesPanel.call(this, "canvas-profiler", new WebInspector.CanvasProfileType());
   1382 }
   1383 
   1384 WebInspector.CanvasProfilerPanel.prototype = {
   1385     __proto__: WebInspector.ProfilesPanel.prototype
   1386 }
   1387 
   1388 
   1389 importScript("ProfileDataGridTree.js");
   1390 importScript("BottomUpProfileDataGridTree.js");
   1391 importScript("CPUProfileView.js");
   1392 importScript("FlameChart.js");
   1393 importScript("HeapSnapshot.js");
   1394 importScript("HeapSnapshotDataGrids.js");
   1395 importScript("HeapSnapshotGridNodes.js");
   1396 importScript("HeapSnapshotLoader.js");
   1397 importScript("HeapSnapshotProxy.js");
   1398 importScript("HeapSnapshotView.js");
   1399 importScript("HeapSnapshotWorkerDispatcher.js");
   1400 importScript("JSHeapSnapshot.js");
   1401 importScript("ProfileLauncherView.js");
   1402 importScript("TopDownProfileDataGridTree.js");
   1403 importScript("CanvasProfileView.js");
   1404