text: /*
   * Copyright (C) 2008 Apple Inc. All Rights Reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  const UserInitiatedProfileName = "org.webkit.profiles.user-initiated";
  
  WebInspector.ProfileType = function(id, name)
  {
      this._id = id;
      this._name = name;
  }
  
  WebInspector.ProfileType.URLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/;
  
  WebInspector.ProfileType.prototype = {
      get buttonTooltip()
      {
          return "";
      },
  
      get buttonStyle()
      {
          return undefined;
      },
  
      get buttonCaption()
      {
          return this.name;
      },
  
      get id()
      {
          return this._id;
      },
  
      get name()
      {
          return this._name;
      },
  
      buttonClicked: function()
      {
      },
  
      viewForProfile: function(profile)
      {
          if (!profile._profileView)
              profile._profileView = this.createView(profile);
          return profile._profileView;
      },
  
      get welcomeMessage()
      {
          return "";
      },
  
      // Must be implemented by subclasses.
      createView: function(profile)
      {
          throw new Error("Needs implemented.");
      },
  
      // Must be implemented by subclasses.
      createSidebarTreeElementForProfile: function(profile)
      {
          throw new Error("Needs implemented.");
      }
  }
  
  WebInspector.ProfilesPanel = function()
  {
      WebInspector.Panel.call(this);
  
      this.createSidebar();
  
      this.element.addStyleClass("profiles");
      this._profileTypesByIdMap = {};
      this._profileTypeButtonsByIdMap = {};
  
      var panelEnablerHeading = WebInspector.UIString("You need to enable profiling before you can use the Profiles panel.");
      var panelEnablerDisclaimer = WebInspector.UIString("Enabling profiling will make scripts run slower.");
      var panelEnablerButton = WebInspector.UIString("Enable Profiling");
      this.panelEnablerView = new WebInspector.PanelEnablerView("profiles", panelEnablerHeading, panelEnablerDisclaimer, panelEnablerButton);
      this.panelEnablerView.addEventListener("enable clicked", this._enableProfiling, this);
  
      this.element.appendChild(this.panelEnablerView.element);
  
      this.profileViews = document.createElement("div");
      this.profileViews.id = "profile-views";
      this.element.appendChild(this.profileViews);
  
      this.enableToggleButton = new WebInspector.StatusBarButton("", "enable-toggle-status-bar-item");
      this.enableToggleButton.addEventListener("click", this._toggleProfiling.bind(this), false);
  
      this.profileViewStatusBarItemsContainer = document.createElement("div");
      this.profileViewStatusBarItemsContainer.id = "profile-view-status-bar-items";
  
      this.welcomeView = new WebInspector.WelcomeView("profiles", WebInspector.UIString("Welcome to the Profiles panel"));
      this.element.appendChild(this.welcomeView.element);
  
      this._profiles = [];
      this._profilerEnabled = Preferences.profilerAlwaysEnabled;
      this.reset();
  }
  
  WebInspector.ProfilesPanel.prototype = {
      toolbarItemClass: "profiles",
  
      get toolbarItemLabel()
      {
          return WebInspector.UIString("Profiles");
      },
  
      get statusBarItems()
      {
          function clickHandler(profileType, buttonElement)
          {
              profileType.buttonClicked.call(profileType);
              this.updateProfileTypeButtons();
          }
  
          var items = [this.enableToggleButton.element];
          // FIXME: Generate a single "combo-button".
          for (var typeId in this._profileTypesByIdMap) {
              var profileType = this.getProfileType(typeId);
              if (profileType.buttonStyle) {
                  var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
                  this._profileTypeButtonsByIdMap[typeId] = button.element;
                  button.element.addEventListener("click", clickHandler.bind(this, profileType, button.element), false);
                  items.push(button.element);
              }
          }
          items.push(this.profileViewStatusBarItemsContainer);
          return items;
      },
  
      show: function()
      {
          WebInspector.Panel.prototype.show.call(this);
          if (this._shouldPopulateProfiles)
              this._populateProfiles();
      },
  
      populateInterface: function()
      {
          if (this.visible)
              this._populateProfiles();
          else
              this._shouldPopulateProfiles = true;
      },
  
      profilerWasEnabled: function()
      {
          if (this._profilerEnabled)
              return;
  
          this._profilerEnabled = true;
          this.reset();
          this.populateInterface();
      },
  
      profilerWasDisabled: function()
      {
          if (!this._profilerEnabled)
              return;
  
          this._profilerEnabled = false;
          this.reset();
      },
  
      reset: function()
      {
          for (var i = 0; i < this._profiles.length; ++i)
              delete this._profiles[i]._profileView;
          delete this.visibleView;
  
          delete this.currentQuery;
          this.searchCanceled();
  
          this._profiles = [];
          this._profilesIdMap = {};
          this._profileGroups = {};
          this._profileGroupsForLinks = {}
  
          this.sidebarTreeElement.removeStyleClass("some-expandable");
  
          for (var typeId in this._profileTypesByIdMap)
              this.getProfileType(typeId).treeElement.removeChildren();
  
          this.profileViews.removeChildren();
  
          this.profileViewStatusBarItemsContainer.removeChildren();
  
          this._updateInterface();
          this.welcomeView.show();
      },
  
      registerProfileType: function(profileType)
      {
          this._profileTypesByIdMap[profileType.id] = profileType;
          profileType.treeElement = new WebInspector.SidebarSectionTreeElement(profileType.name, null, true);
          this.sidebarTree.appendChild(profileType.treeElement);
          profileType.treeElement.expand();
          this._addWelcomeMessage(profileType);
      },
  
      _addWelcomeMessage: function(profileType)
      {
          var message = profileType.welcomeMessage;
          // Message text is supposed to have a '%s' substring as a placeholder
          // for a status bar button. If it is there, we split the message in two
          // parts, and insert the button between them.
          var buttonPos = message.indexOf("%s");
          if (buttonPos > -1) {
              var container = document.createDocumentFragment();
              var part1 = document.createElement("span");
              part1.innerHTML = message.substr(0, buttonPos);
              container.appendChild(part1);
       
              var button = new WebInspector.StatusBarButton(profileType.buttonTooltip, profileType.buttonStyle, profileType.buttonCaption);
              button.element.addEventListener("click", profileType.buttonClicked.bind(profileType), false);
              container.appendChild(button.element);
         
              var part2 = document.createElement("span");
              part2.innerHTML = message.substr(buttonPos + 2);
              container.appendChild(part2);
              this.welcomeView.addMessage(container);
          } else
              this.welcomeView.addMessage(message);
      },
  
      _makeKey: function(text, profileTypeId)
      {
          return escape(text) + '/' + escape(profileTypeId);
      },
  
      addProfileHeader: function(profile)
      {
          var typeId = profile.typeId;
          var profileType = this.getProfileType(typeId);
          var sidebarParent = profileType.treeElement;
          var small = false;
          var alternateTitle;
  
          profile.__profilesPanelProfileType = profileType;
          this._profiles.push(profile);
          this._profilesIdMap[this._makeKey(profile.uid, typeId)] = profile;
  
          if (profile.title.indexOf(UserInitiatedProfileName) !== 0) {
              var profileTitleKey = this._makeKey(profile.title, typeId);
              if (!(profileTitleKey in this._profileGroups))
                  this._profileGroups[profileTitleKey] = [];
  
              var group = this._profileGroups[profileTitleKey];
              group.push(profile);
  
              if (group.length === 2) {
                  // Make a group TreeElement now that there are 2 profiles.
                  group._profilesTreeElement = new WebInspector.ProfileGroupSidebarTreeElement(profile.title);
  
                  // Insert at the same index for the first profile of the group.
                  var index = sidebarParent.children.indexOf(group[0]._profilesTreeElement);
                  sidebarParent.insertChild(group._profilesTreeElement, index);
  
                  // Move the first profile to the group.
                  var selected = group[0]._profilesTreeElement.selected;
                  sidebarParent.removeChild(group[0]._profilesTreeElement);
                  group._profilesTreeElement.appendChild(group[0]._profilesTreeElement);
                  if (selected) {
                      group[0]._profilesTreeElement.select();
                      group[0]._profilesTreeElement.reveal();
                  }
  
                  group[0]._profilesTreeElement.small = true;
                  group[0]._profilesTreeElement.mainTitle = WebInspector.UIString("Run %d", 1);
  
                  this.sidebarTreeElement.addStyleClass("some-expandable");
              }
  
              if (group.length >= 2) {
                  sidebarParent = group._profilesTreeElement;
                  alternateTitle = WebInspector.UIString("Run %d", group.length);
                  small = true;
              }
          }
  
          var profileTreeElement = profileType.createSidebarTreeElementForProfile(profile);
          profileTreeElement.small = small;
          if (alternateTitle)
              profileTreeElement.mainTitle = alternateTitle;
          profile._profilesTreeElement = profileTreeElement;
  
          sidebarParent.appendChild(profileTreeElement);
          this.welcomeView.hide();
          if (!this.visibleView)
              this.showProfile(profile);
      },
  
      showProfile: function(profile)
      {
          if (!profile)
              return;
  
          this.closeVisibleView();
  
          var view = profile.__profilesPanelProfileType.viewForProfile(profile);
  
          view.show(this.profileViews);
  
          profile._profilesTreeElement.select(true);
          profile._profilesTreeElement.reveal();
  
          this.visibleView = view;
  
          this.profileViewStatusBarItemsContainer.removeChildren();
  
          var statusBarItems = view.statusBarItems;
          for (var i = 0; i < statusBarItems.length; ++i)
              this.profileViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
      },
  
      showView: function(view)
      {
          this.showProfile(view.profile);
      },
  
      getProfileType: function(typeId)
      {
          return this._profileTypesByIdMap[typeId];
      },
  
      showProfileForURL: function(url)
      {
          var match = url.match(WebInspector.ProfileType.URLRegExp);
          if (!match)
              return;
          this.showProfile(this._profilesIdMap[this._makeKey(match[3], match[1])]);
      },
  
      updateProfileTypeButtons: function()
      {
          for (var typeId in this._profileTypeButtonsByIdMap) {
              var buttonElement = this._profileTypeButtonsByIdMap[typeId];
              var profileType = this.getProfileType(typeId);
              buttonElement.className = profileType.buttonStyle;
              buttonElement.title = profileType.buttonTooltip;
              // FIXME: Apply profileType.buttonCaption once captions are added to button controls.
          }
      },
  
      closeVisibleView: function()
      {
          if (this.visibleView)
              this.visibleView.hide();
          delete this.visibleView;
      },
  
      displayTitleForProfileLink: function(title, typeId)
      {
          title = unescape(title);
          if (title.indexOf(UserInitiatedProfileName) === 0) {
              title = WebInspector.UIString("Profile %d", title.substring(UserInitiatedProfileName.length + 1));
          } else {
              var titleKey = this._makeKey(title, typeId);
              if (!(titleKey in this._profileGroupsForLinks))
                  this._profileGroupsForLinks[titleKey] = 0;
  
              groupNumber = ++this._profileGroupsForLinks[titleKey];
  
              if (groupNumber > 2)
                  // The title is used in the console message announcing that a profile has started so it gets
                  // incremented twice as often as it's displayed
                  title += " " + WebInspector.UIString("Run %d", groupNumber / 2);
          }
          
          return title;
      },
  
      get searchableViews()
      {
          var views = [];
  
          const visibleView = this.visibleView;
          if (visibleView && visibleView.performSearch)
              views.push(visibleView);
  
          var profilesLength = this._profiles.length;
          for (var i = 0; i < profilesLength; ++i) {
              var profile = this._profiles[i];
              var view = profile.__profilesPanelProfileType.viewForProfile(profile);
              if (!view.performSearch || view === visibleView)
                  continue;
              views.push(view);
          }
  
          return views;
      },
  
      searchMatchFound: function(view, matches)
      {
          view.profile._profilesTreeElement.searchMatches = matches;
      },
  
      searchCanceled: function(startingNewSearch)
      {
          WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
  
          if (!this._profiles)
              return;
  
          for (var i = 0; i < this._profiles.length; ++i) {
              var profile = this._profiles[i];
              profile._profilesTreeElement.searchMatches = 0;
          }
      },
  
      _updateInterface: function()
      {
          // FIXME: Replace ProfileType-specific button visibility changes by a single ProfileType-agnostic "combo-button" visibility change.
          if (this._profilerEnabled) {
              this.enableToggleButton.title = WebInspector.UIString("Profiling enabled. Click to disable.");
              this.enableToggleButton.toggled = true;
              for (var typeId in this._profileTypeButtonsByIdMap)
                  this._profileTypeButtonsByIdMap[typeId].removeStyleClass("hidden");
              this.profileViewStatusBarItemsContainer.removeStyleClass("hidden");
              this.panelEnablerView.visible = false;
          } else {
              this.enableToggleButton.title = WebInspector.UIString("Profiling disabled. Click to enable.");
              this.enableToggleButton.toggled = false;
              for (var typeId in this._profileTypeButtonsByIdMap)
                  this._profileTypeButtonsByIdMap[typeId].addStyleClass("hidden");
              this.profileViewStatusBarItemsContainer.addStyleClass("hidden");
              this.panelEnablerView.visible = true;
          }
      },
  
      _enableProfiling: function()
      {
          if (this._profilerEnabled)
              return;
          this._toggleProfiling(this.panelEnablerView.alwaysEnabled);
      },
  
      _toggleProfiling: function(optionalAlways)
      {
          if (this._profilerEnabled)
              InspectorBackend.disableProfiler(true);
          else
              InspectorBackend.enableProfiler(!!optionalAlways);
      },
  
      _populateProfiles: function()
      {
          var sidebarTreeChildrenCount = this.sidebarTree.children.length;
          for (var i = 0; i < sidebarTreeChildrenCount; ++i) {
              var treeElement = this.sidebarTree.children[i];
              if (treeElement.children.length)
                  return;
          }
  
          function populateCallback(profileHeaders) {
              profileHeaders.sort(function(a, b) { return a.uid - b.uid; });
              var profileHeadersLength = profileHeaders.length;
              for (var i = 0; i < profileHeadersLength; ++i)
                  WebInspector.addProfileHeader(profileHeaders[i]);
          }
  
          var callId = WebInspector.Callback.wrap(populateCallback);
          InspectorBackend.getProfileHeaders(callId);
  
          delete this._shouldPopulateProfiles;
      },
  
      updateMainViewWidth: function(width)
      {
          this.welcomeView.element.style.left = width + "px";
          this.profileViews.style.left = width + "px";
          this.profileViewStatusBarItemsContainer.style.left = width + "px";
          this.resize();
      }
  }
  
  WebInspector.ProfilesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
  
  WebInspector.ProfileSidebarTreeElement = function(profile)
  {
      this.profile = profile;
  
      if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
          this._profileNumber = this.profile.title.substring(UserInitiatedProfileName.length + 1);
  
      WebInspector.SidebarTreeElement.call(this, "profile-sidebar-tree-item", "", "", profile, false);
  
      this.refreshTitles();
  }
  
  WebInspector.ProfileSidebarTreeElement.prototype = {
      onselect: function()
      {
          WebInspector.panels.profiles.showProfile(this.profile);
      },
  
      get mainTitle()
      {
          if (this._mainTitle)
              return this._mainTitle;
          if (this.profile.title.indexOf(UserInitiatedProfileName) === 0)
              return WebInspector.UIString("Profile %d", this._profileNumber);
          return this.profile.title;
      },
  
      set mainTitle(x)
      {
          this._mainTitle = x;
          this.refreshTitles();
      },
  
      get subtitle()
      {
          // There is no subtitle.
      },
  
      set subtitle(x)
      {
          // Can't change subtitle.
      },
  
      set searchMatches(matches)
      {
          if (!matches) {
              if (!this.bubbleElement)
                  return;
              this.bubbleElement.removeStyleClass("search-matches");
              this.bubbleText = "";
              return;
          }
  
          this.bubbleText = matches;
          this.bubbleElement.addStyleClass("search-matches");
      }
  }
  
  WebInspector.ProfileSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
  
  WebInspector.ProfileGroupSidebarTreeElement = function(title, subtitle)
  {
      WebInspector.SidebarTreeElement.call(this, "profile-group-sidebar-tree-item", title, subtitle, null, true);
  }
  
  WebInspector.ProfileGroupSidebarTreeElement.prototype = {
      onselect: function()
      {
          WebInspector.panels.profiles.showProfile(this.children[this.children.length - 1].profile);
      }
  }
  
  WebInspector.ProfileGroupSidebarTreeElement.prototype.__proto__ = WebInspector.SidebarTreeElement.prototype;
  
  WebInspector.didGetProfileHeaders = WebInspector.Callback.processCallback;
  WebInspector.didGetProfile = WebInspector.Callback.processCallback;
  