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 WebInspector.ProfileType = function(id, name)
     29 {
     30     this._id = id;
     31     this._name = name;
     32 }
     33 
     34 WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/;
     35 
     36 WebInspector.ProfileType.prototype = {
     37     get buttonTooltip()
     38     {
     39         return "";
     40     },
     41 
     42     get buttonStyle()
     43     {
     44         return undefined;
     45     },
     46 
     47     get buttonCaption()
     48     {
     49         return this.name;
     50     },
     51 
     52     get id()
     53     {
     54         return this._id;
     55     },
     56 
     57     get name()
     58     {
     59         return this._name;
     60     },
     61 
     62     buttonClicked: function()
     63     {
     64     },
     65 
     66     viewForProfile: function(profile)
     67     {
     68         if (!profile._profileView)
     69             profile._profileView = this.createView(profile);
     70         return profile._profileView;
     71     },
     72 
     73     get welcomeMessage()
     74     {
     75         return "";
     76     },
     77 
     78     // Must be implemented by subclasses.
     79     createView: function(profile)
     80     {
     81         throw new Error("Needs implemented.");
     82     },
     83 
     84     // Must be implemented by subclasses.
     85     createSidebarTreeElementForProfile: function(profile)
     86     {
     87         throw new Error("Needs implemented.");
     88     }
     89 }
     90 
     91 WebInspector.ProfilesPanel = function()
     92 {
     93     WebInspector.Panel.call(this, "profiles");
     94 
     95     this.createSidebar();
     96 
     97     this._profileTypesByIdMap = {};
     98     this._profileTypeButtonsByIdMap = {};
     99 
    100     var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
    101     var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
    102     var panelEnablerButton = WebInspector.UIString("Enable Profiling");
    103     this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
    104     this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);
    105 
    106     this.element.appendChild(this.panelEnablerView.element);
    107 
    108     this.profileViews = document.createElement("div");
    109     this.profileViews.id = "profile-views";
    110     this.element.appendChild(this.profileViews);
    111 
    112     this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
    113     this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);
    114 
    115     this.clearResultsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear all profiles."), "clear-status-bar-item");
    116     this.clearResultsButton.addEventListener("click", this._clearProfiles.bind(this), false);
    117 
    118     this.profileViewStatusBarItemsContainer = document.createElement("div");
    119     this.profileViewStatusBarItemsContainer.className = "status-bar-items";
    120 
    121     this.welcomeView = new WebInspector.WelcomeView("profiles", WebInspector.UIString("Welcome to the Profiles panel"));
    122     this.element.appendChild(this.welcomeView.element);
    123 
    124     this._profiles = [];
    125     this._profilerEnabled = Preferences.profilerAlwaysEnabled;
    126     this._reset();
    127 
    128     this._registerProfileType(new WebInspector.CPUProfileType());
    129     if (Preferences.heapProfilerPresent) {
    130         if (!Preferences.detailedHeapProfiles)
    131             this._registerProfileType(new WebInspector.HeapSnapshotProfileType());
    132         else
    133             this._registerProfileType(new WebInspector.DetailedHeapshotProfileType());
    134     }
    135 
    136     InspectorBackend.registerDomainDispatcher("Profiler", new WebInspector.ProfilerDispatcher(this));
    137 
    138     if (Preferences.profilerAlwaysEnabled || WebInspector.settings.profilerEnabled)
    139         ProfilerAgent.enable();
    140     else {
    141         function onProfilerEnebled(error, value) {
    142             if (value)
    143                 this._profilerWasEnabled();
    144         }
    145         ProfilerAgent.isEnabled(onProfilerEnebled.bind(this));
    146     }
    147 }
    148 
    149 WebInspector.ProfilesPanel.prototype = {
    150     get toolbarItemLabel()
    151     {
    152         return WebInspector.UIString("Profiles");
    153     },
    154 
    155     get statusBarItems()
    156     {
    157         function clickHandler(profileType, buttonElement)
    158         {
    159             profileType.buttonClicked.call(profileType);
    160             this.updateProfileTypeButtons();
    161         }
    162 
    163         var items = [this.enableToggleButton.element];
    164         // FIXME: Generate a single "combo-button".
    165         for (var typeId in this._profileTypesByIdMap) {
    166             var profileType = this.getProfileType(typeId);
    167             if (profileType.buttonStyle) {
    168                 var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
    169                 this._profileTypeButtonsByIdMap[typeId] = button.element;
    170                 button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false);
    171                 items.push(button.element);
    172             }
    173         }
    174         items.push(this.clearResultsButton.element, this.profileViewStatusBarItemsContainer);
    175         return items;
    176     },
    177 
    178     show: function()
    179     {
    180         WebInspector.Panel.prototype.show.call(this);
    181         this._populateProfiles();
    182     },
    183 
    184     _profilerWasEnabled: function()
    185     {
    186         if (this._profilerEnabled)
    187             return;
    188 
    189         this._profilerEnabled = true;
    190 
    191         this._reset();
    192         if (this.visible)
    193             this._populateProfiles();
    194     },
    195 
    196     _profilerWasDisabled: function()
    197     {
    198         if (!this._profilerEnabled)
    199             return;
    200 
    201         this._profilerEnabled = false;
    202         this._reset();
    203     },
    204 
    205     _reset: function()
    206     {
    207         WebInspector.Panel.prototype.reset.call(this);
    208 
    209         for (var i = 0; i < this._profiles.length; ++i) {
    210             var view = this._profiles[i]._profileView;
    211             if (view && ("dispose" in view))
    212                 view.dispose();
    213             delete this._profiles[i]._profileView;
    214         }
    215         delete this.visibleView;
    216 
    217         delete this.currentQuery;
    218         this.searchCanceled();
    219 
    220         this._profiles = [];
    221         this._profilesIdMap = {};
    222         this._profileGroups = {};
    223         this._profileGroupsForLinks = {};
    224         this._profilesWereRequested = false;
    225 
    226         this.sidebarTreeElement.removeStyleClass("some-expandable");
    227 
    228         for (var typeId in this._profileTypesByIdMap)
    229             this.getProfileType(typeId).treeElement.removeChildren();
    230 
    231         this.profileViews.removeChildren();
    232 
    233         this.profileViewStatusBarItemsContainer.removeChildren();
    234 
    235         this.removeAllListeners();
    236 
    237         this._updateInterface();
    238         this.welcomeView.show();
    239     },
    240 
    241     _clearProfiles: function()
    242     {
    243         ProfilerAgent.clearProfiles();
    244         this._reset();
    245     },
    246 
    247     _registerProfileType: function(profileType)
    248     {
    249         this._profileTypesByIdMap[profileType.id] = profileType;
    250         profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true);
    251         this.sidebarTree.appendChild(profileType.treeElement);
    252         profileType.treeElement.expand();
    253         this._addWelcomeMessage(profileType);
    254     },
    255 
    256     _addWelcomeMessage: function(profileType)
    257     {
    258         var message = profileType.welcomeMessage;
    259         // Message text is supposed to have a '%s' substring as a placeholder
    260         // for a status bar button. If it is there, we split the message in two
    261         // parts, and insert the button between them.
    262         var buttonPos = message.indexOf("%s");
    263         if (buttonPos > -1) {
    264             var container = document.createDocumentFragment();
    265             var part1 = document.createElement("span");
    266             part1.textContent = message.substr(0, buttonPos);
    267             container.appendChild(part1);
    268 
    269             var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
    270             container.appendChild(button.element);
    271 
    272             var part2 = document.createElement("span");
    273             part2.textContent = message.substr(buttonPos + 2);
    274             container.appendChild(part2);
    275             this.welcomeView.addMessage(container);
    276         } else
    277             this.welcomeView.addMessage(message);
    278     },
    279 
    280     _makeKey: function(text, profileTypeId)
    281     {
    282         return escape(text) + '/' + escape(profileTypeId);
    283     },
    284 
    285     _addProfileHeader: function(profile)
    286     {
    287         if (this.hasTemporaryProfile(profile.typeId)) {
    288             if (profile.typeId === WebInspector.CPUProfileType.TypeId)
    289                 this._removeProfileHeader(this._temporaryRecordingProfile);
    290             else
    291                 this._removeProfileHeader(this._temporaryTakingSnapshot);
    292         }
    293 
    294         var typeId = profile.typeId;
    295         var profileType = this.getProfileType(typeId);
    296         var sidebarParent = profileType.treeElement;
    297         var small = false;
    298         var alternateTitle;
    299 
    300         profile.__profilesPanelProfileType = profileType;
    301         this._profiles.push(profile);
    302         this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile;
    303 
    304         if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
    305             var profileTitleKey = this._makeKey(profile.title, typeId);
    306             if (!(profileTitleKey in this._profileGroups))
    307                 this._profileGroups[profileTitleKey] = [];
    308 
    309             var group = this._profileGroups[profileTitleKey];
    310             group.push(profile);
    311 
    312             if (group.length === 2) {
    313                 // Make a group TreeElement now that there are 2 profiles.
    314                 group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);
    315 
    316                 // Insert at the same index for the first profile of the group.
    317                 var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
    318                 sidebarParent.insertChild(group._profilesTreeElement, index);
    319 
    320                 // Move the first profile to the group.
    321                 var selected = group[0]._profilesTreeElement.selected;
    322                 sidebarParent.removeChild(group[0]._profilesTreeElement);
    323                 group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
    324                 if (selected) {
    325                     group[0]._profilesTreeElement.select();
    326                     group[0]._profilesTreeElement.reveal();
    327                 }
    328 
    329                 group[0]._profilesTreeElement.small = true;
    330                 group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
    331 
    332                 this.sidebarTreeElement.addStyleClass("some-expandable");
    333             }
    334 
    335             if (group.length >= 2) {
    336                 sidebarParent = group._profilesTreeElement;
    337                 alternateTitle = WebInspector.UIString("Run %d", group.length);
    338                 small = true;
    339             }
    340         }
    341 
    342         var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile);
    343         profile.sideBarElement = profileTreeElement;
    344         profileTreeElement.small = small;
    345         if (alternateTitle)
    346             profileTreeElement.mainTitle = alternateTitle;
    347         profile._profilesTreeElement = profileTreeElement;
    348 
    349         sidebarParent.appendChild(profileTreeElement);
    350         if (!profile.isTemporary) {
    351             this.welcomeView.hide();
    352             if (!this.visibleView)
    353                 this.showProfile(profile);
    354             this.dispatchEventToListeners("profile added");
    355         }
    356     },
    357 
    358     _removeProfileHeader: function(profile)
    359     {
    360         var typeId = profile.typeId;
    361         var profileType = this.getProfileType(typeId);
    362         var sidebarParent = profileType.treeElement;
    363 
    364         for (var i = 0; i < this._profiles.length; ++i) {
    365             if (this._profiles[i].uid === profile.uid) {
    366                 profile = this._profiles[i];
    367                 this._profiles.splice(i, 1);
    368                 break;
    369             }
    370         }
    371         delete this._profilesIdMap[this._makeKey(profile.uid, typeId)];
    372 
    373         var profileTitleKey = this._makeKey(profile.title, typeId);
    374         delete this._profileGroups[profileTitleKey];
    375 
    376         sidebarParent.removeChild(profile._profilesTreeElement);
    377 
    378         if (!profile.isTemporary)
    379             ProfilerAgent.removeProfile(profile.typeId, profile.uid);
    380 
    381         // No other item will be selected if there aren't any other profiles, so
    382         // make sure that view gets cleared when the last profile is removed.
    383         if (!this._profiles.length)
    384             this.closeVisibleView();
    385     },
    386 
    387     showProfile: function(profile)
    388     {
    389         if (!profile || profile.isTemporary)
    390             return;
    391 
    392         this.closeVisibleView();
    393 
    394         var view = profile.__profilesPanelProfileType.viewForProfile(profile);
    395 
    396         view.show(this.profileViews);
    397 
    398         profile._profilesTreeElement.select(true);
    399         profile._profilesTreeElement.reveal();
    400 
    401         this.visibleView = view;
    402 
    403         this.profileViewStatusBarItemsContainer.removeChildren();
    404 
    405         var statusBarItems = view.statusBarItems;
    406         if (statusBarItems)
    407             for (var i = 0; i < statusBarItems.length; ++i)
    408                 this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    409     },
    410 
    411     getProfiles: function(typeId)
    412     {
    413         var result = [];
    414         var profilesCount = this._profiles.length;
    415         for (var i = 0; i < profilesCount; ++i) {
    416             var profile = this._profiles[i];
    417             if (!profile.isTemporary && profile.typeId === typeId)
    418                 result.push(profile);
    419         }
    420         return result;
    421     },
    422 
    423     hasTemporaryProfile: function(typeId)
    424     {
    425         var profilesCount = this._profiles.length;
    426         for (var i = 0; i < profilesCount; ++i)
    427             if (this._profiles[i].typeId === typeId && this._profiles[i].isTemporary)
    428                 return true;
    429         return false;
    430     },
    431 
    432     hasProfile: function(profile)
    433     {
    434         return !!this._profilesIdMap[this._makeKey(profile.uid, profile.typeId)];
    435     },
    436 
    437     getProfile: function(typeId, uid)
    438     {
    439         return this._profilesIdMap[this._makeKey(uid, typeId)];
    440     },
    441 
    442     loadHeapSnapshot: function(uid, callback)
    443     {
    444         var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.HeapSnapshotProfileType.TypeId)];
    445         if (!profile)
    446             return;
    447 
    448         if (!Preferences.detailedHeapProfiles) {
    449             if (profile._loaded)
    450                 callback(profile);
    451             else if (profile._is_loading)
    452                 profile._callbacks.push(callback);
    453             else {
    454                 profile._is_loading = true;
    455                 profile._callbacks = [callback];
    456                 profile._json = "";
    457                 profile.sideBarElement.subtitle = WebInspector.UIString("Loading\u2026");
    458                 ProfilerAgent.getProfile(profile.typeId, profile.uid);
    459             }
    460         } else {
    461             if (!profile.proxy)
    462                 profile.proxy = new WebInspector.HeapSnapshotProxy();
    463             var proxy = profile.proxy;
    464             if (proxy.startLoading(callback)) {
    465                 profile.sideBarElement.subtitle = WebInspector.UIString("Loading\u2026");
    466                 ProfilerAgent.getProfile(profile.typeId, profile.uid);
    467             }
    468         }
    469     },
    470 
    471     _addHeapSnapshotChunk: function(uid, chunk)
    472     {
    473         var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.HeapSnapshotProfileType.TypeId)];
    474         if (!profile)
    475             return;
    476         if (!Preferences.detailedHeapProfiles) {
    477             if (profile._loaded || !profile._is_loading)
    478                 return;
    479             profile._json += chunk;
    480         } else {
    481             if (!profile.proxy)
    482                 return;
    483             profile.proxy.pushJSONChunk(chunk);
    484         }
    485     },
    486 
    487     _finishHeapSnapshot: function(uid)
    488     {
    489         var profile = this._profilesIdMap[this._makeKey(uid, WebInspector.HeapSnapshotProfileType.TypeId)];
    490         if (!profile)
    491             return;
    492         if (!Preferences.detailedHeapProfiles) {
    493             if (profile._loaded || !profile._is_loading)
    494                 return;
    495             profile.sideBarElement.subtitle = WebInspector.UIString("Parsing\u2026");
    496             function doParse()
    497             {
    498                 var loadedSnapshot = JSON.parse(profile._json);
    499                 var callbacks = profile._callbacks;
    500                 delete profile._callbacks;
    501                 delete profile._json;
    502                 delete profile._is_loading;
    503                 profile._loaded = true;
    504                 profile.sideBarElement.subtitle = "";
    505 
    506                 if (WebInspector.DetailedHeapshotView.prototype.isDetailedSnapshot(loadedSnapshot)) {
    507                     WebInspector.panels.profiles._enableDetailedHeapProfiles(false);
    508                     return;
    509                 }
    510 
    511                 WebInspector.HeapSnapshotView.prototype.processLoadedSnapshot(profile, loadedSnapshot);
    512                 for (var i = 0; i < callbacks.length; ++i)
    513                     callbacks[i](profile);
    514             }
    515             setTimeout(doParse, 0);
    516         } else {
    517             if (!profile.proxy)
    518                 return;
    519             var proxy = profile.proxy;
    520             function parsed()
    521             {
    522                 profile.sideBarElement.subtitle = Number.bytesToString(proxy.totalSize);
    523             }
    524             if (proxy.finishLoading(parsed))
    525                 profile.sideBarElement.subtitle = WebInspector.UIString("Parsing\u2026");
    526         }
    527     },
    528 
    529     showView: function(view)
    530     {
    531         this.showProfile(view.profile);
    532     },
    533 
    534     getProfileType: function(typeId)
    535     {
    536         return this._profileTypesByIdMap[typeId];
    537     },
    538 
    539     showProfileForURL: function(url)
    540     {
    541         var match = url.match(WebInspector.ProfileType.URLRegExp);
    542         if (!match)
    543             return;
    544         this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]);
    545     },
    546 
    547     updateProfileTypeButtons: function()
    548     {
    549         for (var typeId in this._profileTypeButtonsByIdMap) {
    550             var buttonElement = this._profileTypeButtonsByIdMap[typeId];
    551             var profileType = this.getProfileType(typeId);
    552             buttonElement.className = profileType.buttonStyle;
    553             buttonElement.title = profileType.buttonTooltip;
    554             // FIXME: Apply profileType.buttonCaption once captions are added to button controls.
    555         }
    556     },
    557 
    558     closeVisibleView: function()
    559     {
    560         if (this.visibleView)
    561             this.visibleView.hide();
    562         delete this.visibleView;
    563     },
    564 
    565     displayTitleForProfileLink: function(title, typeId)
    566     {
    567         title = unescape(title);
    568         if (title.indexOf(UserInitiatedProfileName) === 0) {
    569             title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
    570         } else {
    571             var titleKey = this._makeKey(title, typeId);
    572             if (!(titleKey in this._profileGroupsForLinks))
    573                 this._profileGroupsForLinks[titleKey] = 0;
    574 
    575             var groupNumber = ++this._profileGroupsForLinks[titleKey];
    576 
    577             if (groupNumber > 2)
    578                 // The title is used in the console message announcing that a profile has started so it gets
    579                 // incremented twice as often as it's displayed
    580                 title += " " + WebInspector.UIString("Run %d", (groupNumber + 1) / 2);
    581         }
    582 
    583         return title;
    584     },
    585 
    586     get searchableViews()
    587     {
    588         var views = [];
    589 
    590         const visibleView = this.visibleView;
    591         if (visibleView && visibleView.performSearch)
    592             views.push(visibleView);
    593 
    594         var profilesLength = this._profiles.length;
    595         for (var i = 0; i < profilesLength; ++i) {
    596             var profile = this._profiles[i];
    597             var view = profile.__profilesPanelProfileType.viewForProfile(profile);
    598             if (!view.performSearch || view === visibleView)
    599                 continue;
    600             views.push(view);
    601         }
    602 
    603         return views;
    604     },
    605 
    606     searchMatchFound: function(view, matches)
    607     {
    608         view.profile._profilesTreeElement.searchMatches = matches;
    609     },
    610 
    611     searchCanceled: function(startingNewSearch)
    612     {
    613         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
    614 
    615         if (!this._profiles)
    616             return;
    617 
    618         for (var i = 0; i < this._profiles.length; ++i) {
    619             var profile = this._profiles[i];
    620             profile._profilesTreeElement.searchMatches = 0;
    621         }
    622     },
    623 
    624     _updateInterface: function()
    625     {
    626         // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
    627         if (this._profilerEnabled) {
    628             this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
    629             this.enableToggleButton.toggled = true;
    630             for (var typeId in this._profileTypeButtonsByIdMap)
    631                 this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden");
    632             this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
    633             this.clearResultsButton.element.removeStyleClass("hidden");
    634             this.panelEnablerView.visible = false;
    635         } else {
    636             this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
    637             this.enableToggleButton.toggled = false;
    638             for (var typeId in this._profileTypeButtonsByIdMap)
    639                 this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden");
    640             this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
    641             this.clearResultsButton.element.addStyleClass("hidden");
    642             this.panelEnablerView.visible = true;
    643         }
    644     },
    645 
    646     _enableProfiling: function()
    647     {
    648         if (this._profilerEnabled)
    649             return;
    650         this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
    651     },
    652 
    653     _toggleProfiling: function(optionalAlways)
    654     {
    655         if (this._profilerEnabled) {
    656             WebInspector.settings.profilerEnabled = false;
    657             ProfilerAgent.disable();
    658         } else {
    659             WebInspector.settings.profilerEnabled = !!optionalAlways;
    660             ProfilerAgent.enable();
    661         }
    662     },
    663 
    664     _populateProfiles: function()
    665     {
    666         if (!this._profilerEnabled || this._profilesWereRequested)
    667             return;
    668 
    669         function populateCallback(error, profileHeaders) {
    670             if (error)
    671                 return;
    672             profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
    673             var profileHeadersLength = profileHeaders.length;
    674             for (var i = 0; i < profileHeadersLength; ++i)
    675                 if (!this.hasProfile(profileHeaders[i]))
    676                    this._addProfileHeader(profileHeaders[i]);
    677         }
    678 
    679         ProfilerAgent.getProfileHeaders(populateCallback.bind(this));
    680 
    681         this._profilesWereRequested = true;
    682     },
    683 
    684     updateMainViewWidth: function(width)
    685     {
    686         this.welcomeView.element.style.left = width + "px";
    687         this.profileViews.style.left = width + "px";
    688         this.profileViewStatusBarItemsContainer.style.left = Math.max(155, width) + "px";
    689         this.resize();
    690     },
    691 
    692     _setRecordingProfile: function(isProfiling)
    693     {
    694         this.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling);
    695         if (this.hasTemporaryProfile(WebInspector.CPUProfileType.TypeId) !== isProfiling) {
    696             if (!this._temporaryRecordingProfile) {
    697                 this._temporaryRecordingProfile = {
    698                     typeId: WebInspector.CPUProfileType.TypeId,
    699                     title: WebInspector.UIString("Recording"),
    700                     uid: -1,
    701                     isTemporary: true
    702                 };
    703             }
    704             if (isProfiling)
    705                 this._addProfileHeader(this._temporaryRecordingProfile);
    706             else
    707                 this._removeProfileHeader(this._temporaryRecordingProfile);
    708         }
    709         this.updateProfileTypeButtons();
    710     },
    711 
    712     takeHeapSnapshot: function(detailed)
    713     {
    714         if (!this.hasTemporaryProfile(WebInspector.HeapSnapshotProfileType.TypeId)) {
    715             if (!this._temporaryTakingSnapshot) {
    716                 this._temporaryTakingSnapshot = {
    717                     typeId: WebInspector.HeapSnapshotProfileType.TypeId,
    718                     title: WebInspector.UIString("Snapshotting"),
    719                     uid: -1,
    720                     isTemporary: true
    721                 };
    722             }
    723             this._addProfileHeader(this._temporaryTakingSnapshot);
    724         }
    725         ProfilerAgent.takeHeapSnapshot(detailed);
    726     },
    727 
    728     _reportHeapSnapshotProgress: function(done, total)
    729     {
    730         if (this.hasTemporaryProfile(WebInspector.HeapSnapshotProfileType.TypeId)) {
    731             this._temporaryTakingSnapshot.sideBarElement.subtitle = WebInspector.UIString("%.2f%%", (done / total) * 100);
    732             if (done >= total)
    733                 this._removeProfileHeader(this._temporaryTakingSnapshot);
    734         }
    735     },
    736 
    737     handleShortcut: function(event)
    738     {
    739         if (!Preferences.heapProfilerPresent || Preferences.detailedHeapProfiles)
    740             return;
    741         var combo = ["U+004C", "U+0045", "U+0041", "U+004B", "U+005A"];  // "LEAKZ"
    742         if (this._recognizeKeyboardCombo(combo, event)) {
    743             this._displayDetailedHeapProfilesEnabledHint();
    744             this._enableDetailedHeapProfiles(true);
    745         }
    746     },
    747 
    748     _recognizeKeyboardCombo: function(combo, event)
    749     {
    750         var isRecognized = false;
    751         if (!this._comboPosition) {
    752             if (event.keyIdentifier === combo[0])
    753                 this._comboPosition = 1;
    754         } else if (event.keyIdentifier === combo[this._comboPosition]) {
    755             if (++this._comboPosition === combo.length)
    756                 isRecognized = true;
    757         } else
    758             delete this._comboPosition;
    759         if (this._comboPosition)
    760             event.handled = true;
    761         return isRecognized;
    762     },
    763 
    764     _displayDetailedHeapProfilesEnabledHint: function()
    765     {
    766         var message = new WebInspector.HelpScreen("Congratulations!");
    767         message.contentElement.addStyleClass("help-table");
    768         message.contentElement.textContent = "Detailed Heap snapshots are now enabled.";
    769         message.show();
    770 
    771         function hideHint()
    772         {
    773             message._hide();
    774         }
    775 
    776         setTimeout(hideHint, 2000);
    777     },
    778 
    779     _enableDetailedHeapProfiles: function(resetAgent)
    780     {
    781         if (resetAgent)
    782             this._clearProfiles();
    783         else
    784             this._reset();
    785         var oldProfileType = this._profileTypesByIdMap[WebInspector.HeapSnapshotProfileType.TypeId];
    786         var profileType = new WebInspector.DetailedHeapshotProfileType();
    787         profileType.treeElement = oldProfileType.treeElement;
    788         this._profileTypesByIdMap[profileType.id] = profileType;
    789         Preferences.detailedHeapProfiles = true;
    790         this.hide();
    791         this.show();
    792     }
    793 }
    794 
    795 WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
    796 
    797 
    798 WebInspector.ProfilerDispatcher = function(profiler)
    799 {
    800     this._profiler = profiler;
    801 }
    802 
    803 WebInspector.ProfilerDispatcher.prototype = {
    804     profilerWasEnabled: function()
    805     {
    806         this._profiler._profilerWasEnabled();
    807     },
    808 
    809     profilerWasDisabled: function()
    810     {
    811         this._profiler._profilerWasDisabled();
    812     },
    813 
    814     resetProfiles: function()
    815     {
    816         this._profiler._reset();
    817     },
    818 
    819     addProfileHeader: function(profile)
    820     {
    821         this._profiler._addProfileHeader(profile);
    822     },
    823 
    824     addHeapSnapshotChunk: function(uid, chunk)
    825     {
    826         this._profiler._addHeapSnapshotChunk(uid, chunk);
    827     },
    828 
    829     finishHeapSnapshot: function(uid)
    830     {
    831         this._profiler._finishHeapSnapshot(uid);
    832     },
    833 
    834     setRecordingProfile: function(isProfiling)
    835     {
    836         this._profiler._setRecordingProfile(isProfiling);
    837     },
    838 
    839     reportHeapSnapshotProgress: function(done, total)
    840     {
    841         this._profiler._reportHeapSnapshotProgress(done, total);
    842     }
    843 }
    844 
    845 WebInspector.ProfileSidebarTreeElement = function(profile, titleFormat, className)
    846 {
    847     this.profile = profile;
    848     this._titleFormat = titleFormat;
    849 
    850     if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
    851         this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
    852 
    853     WebInspector.SidebarTreeElement.call(this, className, "", "", profile, false);
    854 
    855     this.refreshTitles();
    856 }
    857 
    858 WebInspector.ProfileSidebarTreeElement.prototype = {
    859     onselect: function()
    860     {
    861         this.treeOutline.panel.showProfile(this.profile);
    862     },
    863 
    864     ondelete: function()
    865     {
    866         this.treeOutline.panel._removeProfileHeader(this.profile);
    867         return true;
    868     },
    869 
    870     get mainTitle()
    871     {
    872         if (this._mainTitle)
    873             return this._mainTitle;
    874         if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
    875             return WebInspector.UIString(this._titleFormat, this._profileNumber);
    876         return this.profile.title;
    877     },
    878 
    879     set mainTitle(x)
    880     {
    881         this._mainTitle = x;
    882         this.refreshTitles();
    883     },
    884 
    885     set searchMatches(matches)
    886     {
    887         if (!matches) {
    888             if (!this.bubbleElement)
    889                 return;
    890             this.bubbleElement.removeStyleClass("search-matches");
    891             this.bubbleText = "";
    892             return;
    893         }
    894 
    895         this.bubbleText = matches;
    896         this.bubbleElement.addStyleClass("search-matches");
    897     }
    898 }
    899 
    900 WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
    901 
    902 WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
    903 {
    904     WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
    905 }
    906 
    907 WebInspector.ProfileGroupSidebarTreeElement.prototype = {
    908     onselect: function()
    909     {
    910         if (this.children.length > 0)
    911             WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
    912     }
    913 }
    914 
    915 WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
    916