text: /*
   * Copyright (C) 2009 280 North 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.
   */
  
  WebInspector.ProfileDataGridNode = function(profileView, profileNode, owningTree, hasChildren)
  {
      this.profileView = profileView;
      this.profileNode = profileNode;
  
      WebInspector.DataGridNode.call(this, null, hasChildren);
  
      this.addEventListener("populate", this._populate, this);
  
      this.tree = owningTree;
  
      this.childrenByCallUID = {};
      this.lastComparator = null;
  
      this.callUID = profileNode.callUID;
      this.selfTime = profileNode.selfTime;
      this.totalTime = profileNode.totalTime;
      this.functionName = profileNode.functionName;
      this.numberOfCalls = profileNode.numberOfCalls;
      this.url = profileNode.url;
  }
  
  WebInspector.ProfileDataGridNode.prototype = {
      get data()
      {
          function formatMilliseconds(time)
          {
              return Number.secondsToString(time / 1000, WebInspector.UIString.bind(WebInspector), !Preferences.samplingCPUProfiler);
          }
  
          var data = {};
  
          data["function"] = this.functionName;
          data["calls"] = this.numberOfCalls;
  
          if (this.profileView.showSelfTimeAsPercent)
              data["self"] = WebInspector.UIString("%.2f%%", this.selfPercent);
          else
              data["self"] = formatMilliseconds(this.selfTime);
  
          if (this.profileView.showTotalTimeAsPercent)
              data["total"] = WebInspector.UIString("%.2f%%", this.totalPercent);
          else
              data["total"] = formatMilliseconds(this.totalTime);
  
          if (this.profileView.showAverageTimeAsPercent)
              data["average"] = WebInspector.UIString("%.2f%%", this.averagePercent);
          else
              data["average"] = formatMilliseconds(this.averageTime);
  
          return data;
      },
  
      createCell: function(columnIdentifier)
      {
          var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
  
          if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
              cell.addStyleClass("highlight");
          else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
              cell.addStyleClass("highlight");
          else if (columnIdentifier === "average" && this._searchMatchedAverageColumn)
              cell.addStyleClass("highlight");
          else if (columnIdentifier === "calls" && this._searchMatchedCallsColumn)
              cell.addStyleClass("highlight");
  
          if (columnIdentifier !== "function")
              return cell;
  
          if (this.profileNode._searchMatchedFunctionColumn)
              cell.addStyleClass("highlight");
  
          if (this.profileNode.url) {
              var fileName = WebInspector.displayNameForURL(this.profileNode.url);
  
              var urlElement = document.createElement("a");
              urlElement.className = "profile-node-file webkit-html-resource-link";
              urlElement.href = this.profileNode.url;
              urlElement.lineNumber = this.profileNode.lineNumber;
              urlElement.preferredPanel = "scripts";
  
              if (this.profileNode.lineNumber > 0)
                  urlElement.textContent = fileName + ":" + this.profileNode.lineNumber;
              else
                  urlElement.textContent = fileName;
  
              cell.insertBefore(urlElement, cell.firstChild);
          }
  
          return cell;
      },
  
      select: function(supressSelectedEvent)
      {
          WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
          this.profileView._dataGridNodeSelected(this);
      },
  
      deselect: function(supressDeselectedEvent)
      {
          WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
          this.profileView._dataGridNodeDeselected(this);
      },
  
      sort: function(/*Function*/ comparator, /*Boolean*/ force)
      {
          var gridNodeGroups = [[this]];
  
          for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
              var gridNodes = gridNodeGroups[gridNodeGroupIndex];
              var count = gridNodes.length;
  
              for (var index = 0; index < count; ++index) {
                  var gridNode = gridNodes[index];
  
                  // If the grid node is collapsed, then don't sort children (save operation for later).
                  // If the grid node has the same sorting as previously, then there is no point in sorting it again.
                  if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
                      if (gridNode.children.length)
                          gridNode.shouldRefreshChildren = true;
                      continue;
                  }
  
                  gridNode.lastComparator = comparator;
  
                  var children = gridNode.children;
                  var childCount = children.length;
  
                  if (childCount) {
                      children.sort(comparator);
  
                      for (var childIndex = 0; childIndex < childCount; ++childIndex)
                          children[childIndex]._recalculateSiblings(childIndex);
  
                      gridNodeGroups.push(children);
                  }
              }
          }
      },
  
      insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index)
      {
          WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);
  
          this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
      },
  
      removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode)
      {
          WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);
  
          delete this.childrenByCallUID[profileDataGridNode.callUID];
      },
  
      removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode)
      {
          WebInspector.DataGridNode.prototype.removeChildren.call(this);
  
          this.childrenByCallUID = {};
      },
  
      findChild: function(/*Node*/ node)
      {
          if (!node)
              return null;
          return this.childrenByCallUID[node.callUID];
      },
  
      get averageTime()
      {
          return this.selfTime / Math.max(1, this.numberOfCalls);
      },
  
      get averagePercent()
      {
          return this.averageTime / this.tree.totalTime * 100.0;
      },
  
      get selfPercent()
      {
          return this.selfTime / this.tree.totalTime * 100.0;
      },
  
      get totalPercent()
      {
          return this.totalTime / this.tree.totalTime * 100.0;
      },
  
      get _parent()
      {
          return this.parent !== this.dataGrid ? this.parent : this.tree;
      },
  
      _populate: function(event)
      {
          this._sharedPopulate();
  
          if (this._parent) {
              var currentComparator = this._parent.lastComparator;
  
              if (currentComparator)
                  this.sort(currentComparator, true);
          }
  
          if (this.removeEventListener)
              this.removeEventListener("populate", this._populate, this);
      },
  
      // When focusing and collapsing we modify lots of nodes in the tree.
      // This allows us to restore them all to their original state when we revert.
      _save: function()
      {
          if (this._savedChildren)
              return;
  
          this._savedSelfTime = this.selfTime;
          this._savedTotalTime = this.totalTime;
          this._savedNumberOfCalls = this.numberOfCalls;
  
          this._savedChildren = this.children.slice();
      },
  
      // When focusing and collapsing we modify lots of nodes in the tree.
      // This allows us to restore them all to their original state when we revert.
      _restore: function()
      {
          if (!this._savedChildren)
              return;
  
          this.selfTime = this._savedSelfTime;
          this.totalTime = this._savedTotalTime;
          this.numberOfCalls = this._savedNumberOfCalls;
  
          this.removeChildren();
  
          var children = this._savedChildren;
          var count = children.length;
  
          for (var index = 0; index < count; ++index) {
              children[index]._restore();
              this.appendChild(children[index]);
          }
      },
  
      _merge: function(child, shouldAbsorb)
      {
          this.selfTime += child.selfTime;
  
          if (!shouldAbsorb) {
              this.totalTime += child.totalTime;
              this.numberOfCalls += child.numberOfCalls;
          }
  
          var children = this.children.slice();
  
          this.removeChildren();
  
          var count = children.length;
  
          for (var index = 0; index < count; ++index) {
              if (!shouldAbsorb || children[index] !== child)
                  this.appendChild(children[index]);
          }
  
          children = child.children.slice();
          count = children.length;
  
          for (var index = 0; index < count; ++index) {
              var orphanedChild = children[index],
                  existingChild = this.childrenByCallUID[orphanedChild.callUID];
  
              if (existingChild)
                  existingChild._merge(orphanedChild, false);
              else
                  this.appendChild(orphanedChild);
          }
      }
  }
  
  WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
  
  WebInspector.ProfileDataGridTree = function(profileView, profileNode)
  {
      this.tree = this;
      this.children = [];
  
      this.profileView = profileView;
  
      this.totalTime = profileNode.totalTime;
      this.lastComparator = null;
  
      this.childrenByCallUID = {};
  }
  
  WebInspector.ProfileDataGridTree.prototype = {
      get expanded()
      {
          return true;
      },
  
      appendChild: function(child)
      {
          this.insertChild(child, this.children.length);
      },
  
      insertChild: function(child, index)
      {
          this.children.splice(index, 0, child);
          this.childrenByCallUID[child.callUID] = child;
      },
  
      removeChildren: function()
      {
          this.children = [];
          this.childrenByCallUID = {};
      },
  
      findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
      sort: WebInspector.ProfileDataGridNode.prototype.sort,
  
      _save: function()
      {
          if (this._savedChildren)
              return;
  
          this._savedTotalTime = this.totalTime;
          this._savedChildren = this.children.slice();
      },
  
      restore: function()
      {
          if (!this._savedChildren)
              return;
  
          this.children = this._savedChildren;
          this.totalTime = this._savedTotalTime;
  
          var children = this.children;
          var count = children.length;
  
          for (var index = 0; index < count; ++index)
              children[index]._restore();
  
          this._savedChildren = null;
      }
  }
  
  WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];
  
  WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending)
  {
      var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];
  
      if (!comparator) {
          if (isAscending) {
              comparator = function(lhs, rhs)
              {
                  if (lhs[property] < rhs[property])
                      return -1;
  
                  if (lhs[property] > rhs[property])
                      return 1;
  
                  return 0;
              }
          } else {
              comparator = function(lhs, rhs)
              {
                  if (lhs[property] > rhs[property])
                      return -1;
  
                  if (lhs[property] < rhs[property])
                      return 1;
  
                  return 0;
              }
          }
  
          this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
      }
  
      return comparator;
  }
  