browser/devtools/profiler/cleopatra/js/tree.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/devtools/profiler/cleopatra/js/tree.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,702 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +"use strict";
     1.9 +
    1.10 +var kMaxChunkDuration = 30; // ms
    1.11 +
    1.12 +function escapeHTML(html) {
    1.13 +  var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre");
    1.14 +  var text = document.createTextNode(html);
    1.15 +  pre.appendChild(text);
    1.16 +  return pre.innerHTML;
    1.17 +}
    1.18 +
    1.19 +RegExp.escape = function(text) {
    1.20 +    return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    1.21 +}
    1.22 +
    1.23 +var requestAnimationFrame = window.webkitRequestAnimationFrame ||
    1.24 +                            window.mozRequestAnimationFrame ||
    1.25 +                            window.oRequestAnimationFrame ||
    1.26 +                            window.msRequestAnimationFrame ||
    1.27 +                            function(callback, element) {
    1.28 +                              return window.setTimeout(callback, 1000 / 60);
    1.29 +                            };
    1.30 +
    1.31 +var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
    1.32 +                           window.mozCancelAnimationFrame ||
    1.33 +                           window.oCancelAnimationFrame ||
    1.34 +                           window.msCancelAnimationFrame ||
    1.35 +                           function(req) {
    1.36 +                             window.clearTimeout(req);
    1.37 +                           };
    1.38 +
    1.39 +function TreeView() {
    1.40 +  this._eventListeners = {};
    1.41 +  this._pendingActions = [];
    1.42 +  this._pendingActionsProcessingCallback = null;
    1.43 +
    1.44 +  this._container = document.createElement("div");
    1.45 +  this._container.className = "treeViewContainer";
    1.46 +  this._container.setAttribute("tabindex", "0"); // make it focusable
    1.47 +
    1.48 +  this._header = document.createElement("ul");
    1.49 +  this._header.className = "treeHeader";
    1.50 +  this._container.appendChild(this._header);
    1.51 +
    1.52 +  this._verticalScrollbox = document.createElement("div");
    1.53 +  this._verticalScrollbox.className = "treeViewVerticalScrollbox";
    1.54 +  this._container.appendChild(this._verticalScrollbox);
    1.55 +
    1.56 +  this._leftColumnBackground = document.createElement("div");
    1.57 +  this._leftColumnBackground.className = "leftColumnBackground";
    1.58 +  this._verticalScrollbox.appendChild(this._leftColumnBackground);
    1.59 +
    1.60 +  this._horizontalScrollbox = document.createElement("div");
    1.61 +  this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
    1.62 +  this._verticalScrollbox.appendChild(this._horizontalScrollbox);
    1.63 +
    1.64 +  this._styleElement = document.createElement("style");
    1.65 +  this._styleElement.setAttribute("type", "text/css");
    1.66 +  this._container.appendChild(this._styleElement);
    1.67 +
    1.68 +  this._contextMenu = document.createElement("menu");
    1.69 +  this._contextMenu.setAttribute("type", "context");
    1.70 +  this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
    1.71 +  this._container.appendChild(this._contextMenu);
    1.72 +
    1.73 +  this._busyCover = document.createElement("div");
    1.74 +  this._busyCover.className = "busyCover";
    1.75 +  this._container.appendChild(this._busyCover);
    1.76 +  this._abortToggleAll = false;
    1.77 +  this.initSelection = true;
    1.78 +
    1.79 +  var self = this;
    1.80 +  this._container.onkeydown = function (e) {
    1.81 +    self._onkeypress(e);
    1.82 +  };
    1.83 +  this._container.onkeypress = function (e) {
    1.84 +    // on key down gives us '8' and mapping shift+8='*' may not be portable.
    1.85 +    if (String.fromCharCode(e.charCode) == '*')
    1.86 +      self._onkeypress(e);
    1.87 +  };
    1.88 +  this._container.onclick = function (e) {
    1.89 +    self._onclick(e);
    1.90 +  };
    1.91 +  this._verticalScrollbox.addEventListener("contextmenu", function(event) {
    1.92 +    self._populateContextMenu(event);
    1.93 +  }, true);
    1.94 +  this._setUpScrolling();
    1.95 +};
    1.96 +TreeView.instanceCounter = 0;
    1.97 +
    1.98 +TreeView.prototype = {
    1.99 +  getContainer: function TreeView_getContainer() {
   1.100 +    return this._container;
   1.101 +  },
   1.102 +  setColumns: function TreeView_setColumns(columns) {
   1.103 +    this._header.innerHTML = "";
   1.104 +    for (var i = 0; i < columns.length; i++) {
   1.105 +      var li = document.createElement("li");
   1.106 +      li.className = "treeColumnHeader treeColumnHeader" + i;
   1.107 +      li.id = columns[i].name + "Header";
   1.108 +      li.textContent = columns[i].title;
   1.109 +      this._header.appendChild(li);
   1.110 +    }
   1.111 +  },
   1.112 +  dataIsOutdated: function TreeView_dataIsOutdated() {
   1.113 +    this._busyCover.classList.add("busy");
   1.114 +  },
   1.115 +  display: function TreeView_display(data, resources, filterByName) {
   1.116 +    this._busyCover.classList.remove("busy");
   1.117 +    this._filterByName = filterByName;
   1.118 +    this._resources = resources;
   1.119 +    this._addResourceIconStyles();
   1.120 +    this._filterByNameReg = null; // lazy init
   1.121 +    if (this._filterByName === "")
   1.122 +      this._filterByName = null;
   1.123 +    this._horizontalScrollbox.innerHTML = "";
   1.124 +    this._horizontalScrollbox.data = data[0].getData();
   1.125 +    if (this._pendingActionsProcessingCallback) {
   1.126 +      cancelAnimationFrame(this._pendingActionsProcessingCallback);
   1.127 +      this._pendingActionsProcessingCallback = 0;
   1.128 +    }
   1.129 +    this._pendingActions = [];
   1.130 +
   1.131 +    this._pendingActions.push({
   1.132 +      parentElement: this._horizontalScrollbox,
   1.133 +      parentNode: null,
   1.134 +      data: data[0].getData()
   1.135 +    });
   1.136 +    this._processPendingActionsChunk();
   1.137 +    changeFocus(this._container);
   1.138 +  },
   1.139 +  // Provide a snapshot of the reverse selection to restore with 'invert callback'
   1.140 +  getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
   1.141 +    var snapshot = [];
   1.142 +
   1.143 +    if (!this._selectedNode) {
   1.144 +      return snapshot;
   1.145 +    }
   1.146 +
   1.147 +    var curr = this._selectedNode.data;
   1.148 +
   1.149 +    while(curr) {
   1.150 +      if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) {
   1.151 +        snapshot.push(curr.name);
   1.152 +        //dump(JSON.stringify(curr.name) + "\n");
   1.153 +      }
   1.154 +      if (curr.treeChildren && curr.treeChildren.length >= 1) {
   1.155 +        curr = curr.treeChildren[0].getData();
   1.156 +      } else {
   1.157 +        break;
   1.158 +      }
   1.159 +    }
   1.160 +
   1.161 +    return snapshot.reverse();
   1.162 +  },
   1.163 +  // Provide a snapshot of the current selection to restore
   1.164 +  getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) {
   1.165 +    var snapshot = [];
   1.166 +    var curr = this._selectedNode;
   1.167 +
   1.168 +    while(curr) {
   1.169 +      if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) {
   1.170 +        snapshot.push(curr.data.name);
   1.171 +        //dump(JSON.stringify(curr.data.name) + "\n");
   1.172 +      }
   1.173 +      curr = curr.treeParent;
   1.174 +    }
   1.175 +
   1.176 +    return snapshot.reverse();
   1.177 +  },
   1.178 +  setSelection: function TreeView_setSelection(frames) {
   1.179 +    this.restoreSelectionSnapshot(frames, false);
   1.180 +  },
   1.181 +  // Take a selection snapshot and restore the selection
   1.182 +  restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) {
   1.183 +    var currNode = this._horizontalScrollbox.firstChild;
   1.184 +    if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
   1.185 +      snapshot.shift();
   1.186 +    }
   1.187 +    //dump("len: " + snapshot.length + "\n");
   1.188 +    next_level: while (currNode && snapshot.length > 0) {
   1.189 +      this._toggle(currNode, false, true);
   1.190 +      this._syncProcessPendingActionProcessing();
   1.191 +      for (var i = 0; i < currNode.treeChildren.length; i++) {
   1.192 +        if (currNode.treeChildren[i].data.name == snapshot[0]) {
   1.193 +          snapshot.shift();
   1.194 +          this._toggle(currNode, false, true);
   1.195 +          currNode = currNode.treeChildren[i];
   1.196 +          continue next_level;
   1.197 +        }
   1.198 +      }
   1.199 +      if (allowNonContiguous) {
   1.200 +        // We need to do a Breadth-first search to find a match
   1.201 +        var pendingSearch = [currNode.data];
   1.202 +        while (pendingSearch.length > 0) {
   1.203 +          var node = pendingSearch.shift();
   1.204 +          if (!node.treeChildren)
   1.205 +            continue;
   1.206 +          for (var i = 0; i < node.treeChildren.length; i++) {
   1.207 +            var childNode = node.treeChildren[i].getData();
   1.208 +            if (childNode.name == snapshot[0]) {
   1.209 +              //dump("found: " + childNode.name + "\n");
   1.210 +              snapshot.shift();
   1.211 +              var nodesToToggle = [childNode];
   1.212 +              while (nodesToToggle[0].name != currNode.data.name) {
   1.213 +                nodesToToggle.splice(0, 0, nodesToToggle[0].parent);
   1.214 +              }
   1.215 +              var lastToggle = currNode;
   1.216 +              for (var j = 0; j < nodesToToggle.length; j++) {
   1.217 +                for (var k = 0; k < lastToggle.treeChildren.length; k++) {
   1.218 +                  if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) {
   1.219 +                    //dump("Expend: " + nodesToToggle[j].name + "\n");
   1.220 +                    this._toggle(lastToggle.treeChildren[k], false, true);
   1.221 +                    lastToggle = lastToggle.treeChildren[k];
   1.222 +                    this._syncProcessPendingActionProcessing();
   1.223 +                  }
   1.224 +                }
   1.225 +              }
   1.226 +              currNode = lastToggle;
   1.227 +              continue next_level;
   1.228 +            }
   1.229 +            //dump("pending: " + childNode.name + "\n");
   1.230 +            pendingSearch.push(childNode);
   1.231 +          }
   1.232 +        }
   1.233 +      }
   1.234 +      break; // Didn't find child node matching
   1.235 +    }
   1.236 +
   1.237 +    if (currNode == this._horizontalScrollbox) {
   1.238 +      PROFILERERROR("Failed to restore selection, could not find root.\n");
   1.239 +      return;
   1.240 +    }
   1.241 +
   1.242 +    this._toggle(currNode, true, true);
   1.243 +    this._select(currNode);
   1.244 +  },
   1.245 +  _processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) {
   1.246 +    this._pendingActionsProcessingCallback = 0;
   1.247 +
   1.248 +    var startTime = Date.now();
   1.249 +    var endTime = startTime + kMaxChunkDuration;
   1.250 +    while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) {
   1.251 +      this._processOneAction(this._pendingActions.shift());
   1.252 +    }
   1.253 +    this._scrollHeightChanged();
   1.254 +
   1.255 +    this._schedulePendingActionProcessing();
   1.256 +  },
   1.257 +  _schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() {
   1.258 +    if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) {
   1.259 +      var self = this;
   1.260 +      this._pendingActionsProcessingCallback = requestAnimationFrame(function () {
   1.261 +        self._processPendingActionsChunk();
   1.262 +      });
   1.263 +    }
   1.264 +  },
   1.265 +  _syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() {
   1.266 +    this._processPendingActionsChunk(true);
   1.267 +  },
   1.268 +  _processOneAction: function TreeView__processOneAction(action) {
   1.269 +    var li = this._createTree(action.parentElement, action.parentNode, action.data);
   1.270 +    if ("allChildrenCollapsedValue" in action) {
   1.271 +      if (this._abortToggleAll)
   1.272 +        return;
   1.273 +      this._toggleAll(li, action.allChildrenCollapsedValue, true);
   1.274 +    }
   1.275 +  },
   1.276 +  addEventListener: function TreeView_addEventListener(eventName, callbackFunction) {
   1.277 +    if (!(eventName in this._eventListeners))
   1.278 +      this._eventListeners[eventName] = [];
   1.279 +    if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
   1.280 +      return;
   1.281 +    this._eventListeners[eventName].push(callbackFunction);
   1.282 +  },
   1.283 +  removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) {
   1.284 +    if (!(eventName in this._eventListeners))
   1.285 +      return;
   1.286 +    var index = this._eventListeners[eventName].indexOf(callbackFunction);
   1.287 +    if (index == -1)
   1.288 +      return;
   1.289 +    this._eventListeners[eventName].splice(index, 1);
   1.290 +  },
   1.291 +  _fireEvent: function TreeView__fireEvent(eventName, eventObject) {
   1.292 +    if (!(eventName in this._eventListeners))
   1.293 +      return;
   1.294 +    this._eventListeners[eventName].forEach(function (callbackFunction) {
   1.295 +      callbackFunction(eventObject);
   1.296 +    });
   1.297 +  },
   1.298 +  _setUpScrolling: function TreeView__setUpScrolling() {
   1.299 +    var waitingForPaint = false;
   1.300 +    var accumulatedDeltaX = 0;
   1.301 +    var accumulatedDeltaY = 0;
   1.302 +    var self = this;
   1.303 +    function scrollListener(e) {
   1.304 +      if (!waitingForPaint) {
   1.305 +        requestAnimationFrame(function () {
   1.306 +          self._horizontalScrollbox.scrollLeft += accumulatedDeltaX;
   1.307 +          self._verticalScrollbox.scrollTop += accumulatedDeltaY;
   1.308 +          accumulatedDeltaX = 0;
   1.309 +          accumulatedDeltaY = 0;
   1.310 +          waitingForPaint = false;
   1.311 +        });
   1.312 +        waitingForPaint = true;
   1.313 +      }
   1.314 +      if (e.axis == e.HORIZONTAL_AXIS) {
   1.315 +        accumulatedDeltaX += e.detail;
   1.316 +      } else {
   1.317 +        accumulatedDeltaY += e.detail;
   1.318 +      }
   1.319 +      e.preventDefault();
   1.320 +    }
   1.321 +    this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false);
   1.322 +    this._verticalScrollbox.cleanUp = function () {
   1.323 +      self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false);
   1.324 +    };
   1.325 +  },
   1.326 +  _scrollHeightChanged: function TreeView__scrollHeightChanged() {
   1.327 +    if (!this._pendingScrollHeightChanged) {
   1.328 +      var self = this;
   1.329 +      this._pendingScrollHeightChanged = setTimeout(function() {
   1.330 +        self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
   1.331 +        self._pendingScrollHeightChanged = null;
   1.332 +      }, 0);
   1.333 +    }
   1.334 +  },
   1.335 +  _createTree: function TreeView__createTree(parentElement, parentNode, data) {
   1.336 +    var div = document.createElement("div");
   1.337 +    div.className = "treeViewNode collapsed";
   1.338 +    var hasChildren = ("children" in data) && (data.children.length > 0);
   1.339 +    if (!hasChildren)
   1.340 +      div.classList.add("leaf");
   1.341 +    var treeLine = document.createElement("div");
   1.342 +    treeLine.className = "treeLine";
   1.343 +    treeLine.innerHTML = this._HTMLForFunction(data);
   1.344 +    div.depth = parentNode ? parentNode.depth + 1 : 0;
   1.345 +    div.style.marginLeft = div.depth + "em";
   1.346 +    // When this item is toggled we will expand its children
   1.347 +    div.pendingExpand = [];
   1.348 +    div.treeLine = treeLine;
   1.349 +    div.data = data;
   1.350 +    // Useful for debugging
   1.351 +    //this.uniqueID = this.uniqueID || 0;
   1.352 +    //div.id = "Node" + this.uniqueID++;
   1.353 +    div.appendChild(treeLine);
   1.354 +    div.treeChildren = [];
   1.355 +    div.treeParent = parentNode;
   1.356 +    if (hasChildren) {
   1.357 +      for (var i = 0; i < data.children.length; ++i) {
   1.358 +        div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
   1.359 +      }
   1.360 +    }
   1.361 +    if (parentNode) {
   1.362 +      parentNode.treeChildren.push(div);
   1.363 +    }
   1.364 +    if (parentNode != null) {
   1.365 +      var nextTo;
   1.366 +      if (parentNode.treeChildren.length > 1) {
   1.367 +        nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
   1.368 +      } else {
   1.369 +        nextTo = parentNode.nextSibling;
   1.370 +      }
   1.371 +      parentElement.insertBefore(div, nextTo);
   1.372 +    } else {
   1.373 +      parentElement.appendChild(div);
   1.374 +    }
   1.375 +    return div;
   1.376 +  },
   1.377 +  _addResourceIconStyles: function TreeView__addResourceIconStyles() {
   1.378 +    var styles = [];
   1.379 +    for (var resourceName in this._resources) {
   1.380 +      var resource = this._resources[resourceName];
   1.381 +      if (resource.icon) {
   1.382 +        styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
   1.383 +      }
   1.384 +    }
   1.385 +    this._styleElement.textContent = styles.join("\n");
   1.386 +  },
   1.387 +  _populateContextMenu: function TreeView__populateContextMenu(event) {
   1.388 +    this._verticalScrollbox.setAttribute("contextmenu", "");
   1.389 +
   1.390 +    var target = event.target;
   1.391 +    if (target.classList.contains("expandCollapseButton") ||
   1.392 +        target.classList.contains("focusCallstackButton"))
   1.393 +      return;
   1.394 +
   1.395 +    var li = this._getParentTreeViewNode(target);
   1.396 +    if (!li)
   1.397 +      return;
   1.398 +
   1.399 +    this._select(li);
   1.400 +
   1.401 +    this._contextMenu.innerHTML = "";
   1.402 +
   1.403 +    var self = this;
   1.404 +    this._contextMenuForFunction(li.data).forEach(function (menuItem) {
   1.405 +      var menuItemNode = document.createElement("menuitem");
   1.406 +      menuItemNode.onclick = (function (menuItem) {
   1.407 +        return function() {
   1.408 +          self._contextMenuClick(li.data, menuItem);
   1.409 +        };
   1.410 +      })(menuItem);
   1.411 +      menuItemNode.label = menuItem;
   1.412 +      self._contextMenu.appendChild(menuItemNode);
   1.413 +    });
   1.414 +
   1.415 +    this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id);
   1.416 +  },
   1.417 +  _contextMenuClick: function TreeView__contextMenuClick(node, menuItem) {
   1.418 +    this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem });
   1.419 +  },
   1.420 +  _contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
   1.421 +    // TODO move me outside tree.js
   1.422 +    var menu = [];
   1.423 +    if (node.library && (
   1.424 +      node.library.toLowerCase() == "lib_xul" ||
   1.425 +      node.library.toLowerCase() == "lib_xul.dll"
   1.426 +      )) {
   1.427 +      menu.push("View Source");
   1.428 +    }
   1.429 +    if (node.isJSFrame && node.scriptLocation) {
   1.430 +      menu.push("View JS Source");
   1.431 +    }
   1.432 +    menu.push("Focus Frame");
   1.433 +    menu.push("Focus Callstack");
   1.434 +    menu.push("Google Search");
   1.435 +    menu.push("Plugin View: Pie");
   1.436 +    menu.push("Plugin View: Tree");
   1.437 +    return menu;
   1.438 +  },
   1.439 +  _HTMLForFunction: function TreeView__HTMLForFunction(node) {
   1.440 +    var nodeName = escapeHTML(node.name);
   1.441 +    var resource = this._resources[node.library] || {};
   1.442 +    var libName = escapeHTML(resource.name || "");
   1.443 +    if (this._filterByName) {
   1.444 +      if (!this._filterByNameReg) {
   1.445 +        this._filterByName = RegExp.escape(this._filterByName);
   1.446 +        this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi");
   1.447 +      }
   1.448 +      nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
   1.449 +      libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
   1.450 +    }
   1.451 +    var samplePercentage;
   1.452 +    if (isNaN(node.ratio)) {
   1.453 +      samplePercentage = "";
   1.454 +    } else {
   1.455 +      samplePercentage = (100 * node.ratio).toFixed(1) + "%";
   1.456 +    }
   1.457 +    return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
   1.458 +      '<span class="sampleCount">' + node.counter + '</span> ' +
   1.459 +      '<span class="samplePercentage">' + samplePercentage + '</span> ' +
   1.460 +      '<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
   1.461 +      '<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
   1.462 +      '<span class="functionName">' + nodeName + '</span>' +
   1.463 +      '<span class="libraryName">' + libName + '</span>' +
   1.464 +      ((nodeName === '(total)' || gHideSourceLinks) ? '' :
   1.465 +        '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
   1.466 +  },
   1.467 +  _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
   1.468 +    while (div.pendingExpand != null && div.pendingExpand.length > 0) {
   1.469 +      var pendingExpand = div.pendingExpand.shift();
   1.470 +      pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
   1.471 +      this._pendingActions.push(pendingExpand);
   1.472 +      this._schedulePendingActionProcessing();
   1.473 +    }
   1.474 +  },
   1.475 +  _showChild: function TreeView__showChild(div, isVisible) {
   1.476 +    for (var i = 0; i < div.treeChildren.length; i++) {
   1.477 +      div.treeChildren[i].style.display = isVisible?"":"none";
   1.478 +      if (!isVisible) {
   1.479 +        div.treeChildren[i].classList.add("collapsed");
   1.480 +        this._showChild(div.treeChildren[i], isVisible);
   1.481 +      }
   1.482 +    }
   1.483 +  },
   1.484 +  _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
   1.485 +    var currentCollapsedValue = this._isCollapsed(div);
   1.486 +    if (newCollapsedValue === undefined)
   1.487 +      newCollapsedValue = !currentCollapsedValue;
   1.488 +    if (newCollapsedValue) {
   1.489 +      div.classList.add("collapsed");
   1.490 +      this._showChild(div, false);
   1.491 +    } else {
   1.492 +      this._resolveChildren(div, true);
   1.493 +      div.classList.remove("collapsed");
   1.494 +      this._showChild(div, true);
   1.495 +    }
   1.496 +    if (!suppressScrollHeightNotification)
   1.497 +      this._scrollHeightChanged();
   1.498 +  },
   1.499 +  _toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
   1.500 +
   1.501 +    // Reset abort
   1.502 +    this._abortToggleAll = false;
   1.503 +
   1.504 +    // Expands / collapses all child nodes, too.
   1.505 +
   1.506 +    if (newCollapsedValue === undefined)
   1.507 +      newCollapsedValue = !this._isCollapsed(subtreeRoot);
   1.508 +    if (!newCollapsedValue) {
   1.509 +      // expanding
   1.510 +      this._resolveChildren(subtreeRoot, newCollapsedValue);
   1.511 +    }
   1.512 +    this._toggle(subtreeRoot, newCollapsedValue, true);
   1.513 +    for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) {
   1.514 +      this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true);
   1.515 +    }
   1.516 +    if (!suppressScrollHeightNotification)
   1.517 +      this._scrollHeightChanged();
   1.518 +  },
   1.519 +  _getParent: function TreeView__getParent(div) {
   1.520 +    return div.treeParent;
   1.521 +  },
   1.522 +  _getFirstChild: function TreeView__getFirstChild(div) {
   1.523 +    if (this._isCollapsed(div))
   1.524 +      return null;
   1.525 +    var child = div.treeChildren[0];
   1.526 +    return child;
   1.527 +  },
   1.528 +  _getLastChild: function TreeView__getLastChild(div) {
   1.529 +    if (this._isCollapsed(div))
   1.530 +      return div;
   1.531 +    var lastChild = div.treeChildren[div.treeChildren.length-1];
   1.532 +    if (lastChild == null)
   1.533 +      return div;
   1.534 +    return this._getLastChild(lastChild);
   1.535 +  },
   1.536 +  _getPrevSib: function TreeView__getPevSib(div) {
   1.537 +    if (div.treeParent == null)
   1.538 +      return null;
   1.539 +    var nodeIndex = div.treeParent.treeChildren.indexOf(div);
   1.540 +    if (nodeIndex == 0)
   1.541 +      return null;
   1.542 +    return div.treeParent.treeChildren[nodeIndex-1];
   1.543 +  },
   1.544 +  _getNextSib: function TreeView__getNextSib(div) {
   1.545 +    if (div.treeParent == null)
   1.546 +      return null;
   1.547 +    var nodeIndex = div.treeParent.treeChildren.indexOf(div);
   1.548 +    if (nodeIndex == div.treeParent.treeChildren.length - 1)
   1.549 +      return this._getNextSib(div.treeParent);
   1.550 +    return div.treeParent.treeChildren[nodeIndex+1];
   1.551 +  },
   1.552 +  _scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
   1.553 +    // Schedule this on the animation frame otherwise we may run this more then once per frames
   1.554 +    // causing more work then needed.
   1.555 +    var self = this;
   1.556 +    if (self._pendingAnimationFrame != null) {
   1.557 +      return;
   1.558 +    }
   1.559 +    self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
   1.560 +      cancelAnimationFrame(self._pendingAnimationFrame);
   1.561 +      self._pendingAnimationFrame = null;
   1.562 +      self._scrollIntoView(element, maxImportantWidth);
   1.563 +    });
   1.564 +  },
   1.565 +  _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
   1.566 +    // Make sure that element is inside the visible part of our scrollbox by
   1.567 +    // adjusting the scroll positions. If element is wider or
   1.568 +    // higher than the scroll port, the left and top edges are prioritized over
   1.569 +    // the right and bottom edges.
   1.570 +    // If maxImportantWidth is set, parts of the beyond this widths are
   1.571 +    // considered as not important; they'll not be moved into view.
   1.572 +
   1.573 +    if (maxImportantWidth === undefined)
   1.574 +      maxImportantWidth = Infinity;
   1.575 +
   1.576 +    var visibleRect = {
   1.577 +      left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150
   1.578 +      top: this._verticalScrollbox.getBoundingClientRect().top,
   1.579 +      right: this._horizontalScrollbox.getBoundingClientRect().right,
   1.580 +      bottom: this._verticalScrollbox.getBoundingClientRect().bottom
   1.581 +    }
   1.582 +    var r = element.getBoundingClientRect();
   1.583 +    var right = Math.min(r.right, r.left + maxImportantWidth);
   1.584 +    var leftCutoff = visibleRect.left - r.left;
   1.585 +    var rightCutoff = right - visibleRect.right;
   1.586 +    var topCutoff = visibleRect.top - r.top;
   1.587 +    var bottomCutoff = r.bottom - visibleRect.bottom;
   1.588 +    if (leftCutoff > 0)
   1.589 +      this._horizontalScrollbox.scrollLeft -= leftCutoff;
   1.590 +    else if (rightCutoff > 0)
   1.591 +      this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff);
   1.592 +    if (topCutoff > 0)
   1.593 +      this._verticalScrollbox.scrollTop -= topCutoff;
   1.594 +    else if (bottomCutoff > 0)
   1.595 +      this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff);
   1.596 +  },
   1.597 +  _select: function TreeView__select(li) {
   1.598 +    if (this._selectedNode != null) {
   1.599 +      this._selectedNode.treeLine.classList.remove("selected");
   1.600 +      this._selectedNode = null;
   1.601 +    }
   1.602 +    if (li) {
   1.603 +      li.treeLine.classList.add("selected");
   1.604 +      this._selectedNode = li;
   1.605 +      var functionName = li.treeLine.querySelector(".functionName");
   1.606 +      this._scheduleScrollIntoView(functionName, 400);
   1.607 +      this._fireEvent("select", li.data);
   1.608 +    }
   1.609 +    updateDocumentURL();
   1.610 +  },
   1.611 +  _isCollapsed: function TreeView__isCollapsed(div) {
   1.612 +    return div.classList.contains("collapsed");
   1.613 +  },
   1.614 +  _getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) {
   1.615 +    while (node) {
   1.616 +      if (node.nodeType != node.ELEMENT_NODE)
   1.617 +        break;
   1.618 +      if (node.classList.contains("treeViewNode"))
   1.619 +        return node;
   1.620 +      node = node.parentNode;
   1.621 +    }
   1.622 +    return null;
   1.623 +  },
   1.624 +  _onclick: function TreeView__onclick(event) {
   1.625 +    var target = event.target;
   1.626 +    var node = this._getParentTreeViewNode(target);
   1.627 +    if (!node)
   1.628 +      return;
   1.629 +    if (target.classList.contains("expandCollapseButton")) {
   1.630 +      if (event.altKey)
   1.631 +        this._toggleAll(node);
   1.632 +      else
   1.633 +        this._toggle(node);
   1.634 +    } else if (target.classList.contains("focusCallstackButton")) {
   1.635 +      this._fireEvent("focusCallstackButtonClicked", node.data);
   1.636 +    } else {
   1.637 +      this._select(node);
   1.638 +      if (event.detail == 2) // dblclick
   1.639 +        this._toggle(node);
   1.640 +    }
   1.641 +  },
   1.642 +  _onkeypress: function TreeView__onkeypress(event) {
   1.643 +    if (event.ctrlKey || event.altKey || event.metaKey)
   1.644 +      return;
   1.645 +
   1.646 +    this._abortToggleAll = true;
   1.647 +
   1.648 +    var selected = this._selectedNode;
   1.649 +    if (event.keyCode < 37 || event.keyCode > 40) {
   1.650 +      if (event.keyCode != 0 ||
   1.651 +          String.fromCharCode(event.charCode) != '*') {
   1.652 +        return;
   1.653 +      }
   1.654 +    }
   1.655 +    event.stopPropagation();
   1.656 +    event.preventDefault();
   1.657 +    if (!selected)
   1.658 +      return;
   1.659 +    if (event.keyCode == 37) { // KEY_LEFT
   1.660 +      var isCollapsed = this._isCollapsed(selected);
   1.661 +      if (!isCollapsed) {
   1.662 +        this._toggle(selected);
   1.663 +      } else {
   1.664 +        var parent = this._getParent(selected); 
   1.665 +        if (parent != null) {
   1.666 +          this._select(parent);
   1.667 +        }
   1.668 +      }
   1.669 +    } else if (event.keyCode == 38) { // KEY_UP
   1.670 +      var prevSib = this._getPrevSib(selected);
   1.671 +      var parent = this._getParent(selected); 
   1.672 +      if (prevSib != null) {
   1.673 +        this._select(this._getLastChild(prevSib));
   1.674 +      } else if (parent != null) {
   1.675 +        this._select(parent);
   1.676 +      }
   1.677 +    } else if (event.keyCode == 39) { // KEY_RIGHT
   1.678 +      var isCollapsed = this._isCollapsed(selected);
   1.679 +      if (isCollapsed) {
   1.680 +        this._toggle(selected);
   1.681 +        this._syncProcessPendingActionProcessing();
   1.682 +      } else {
   1.683 +        // Do KEY_DOWN
   1.684 +        var nextSib = this._getNextSib(selected);
   1.685 +        var child = this._getFirstChild(selected); 
   1.686 +        if (child != null) {
   1.687 +          this._select(child);
   1.688 +        } else if (nextSib) {
   1.689 +          this._select(nextSib);
   1.690 +        }
   1.691 +      }
   1.692 +    } else if (event.keyCode == 40) { // KEY_DOWN
   1.693 +      var nextSib = this._getNextSib(selected);
   1.694 +      var child = this._getFirstChild(selected); 
   1.695 +      if (child != null) {
   1.696 +        this._select(child);
   1.697 +      } else if (nextSib) {
   1.698 +        this._select(nextSib);
   1.699 +      }
   1.700 +    } else if (String.fromCharCode(event.charCode) == '*') {
   1.701 +      this._toggleAll(selected);
   1.702 +    }
   1.703 +  },
   1.704 +};
   1.705 +

mercurial