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

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 var kMaxChunkDuration = 30; // ms
     9 function escapeHTML(html) {
    10   var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre");
    11   var text = document.createTextNode(html);
    12   pre.appendChild(text);
    13   return pre.innerHTML;
    14 }
    16 RegExp.escape = function(text) {
    17     return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    18 }
    20 var requestAnimationFrame = window.webkitRequestAnimationFrame ||
    21                             window.mozRequestAnimationFrame ||
    22                             window.oRequestAnimationFrame ||
    23                             window.msRequestAnimationFrame ||
    24                             function(callback, element) {
    25                               return window.setTimeout(callback, 1000 / 60);
    26                             };
    28 var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
    29                            window.mozCancelAnimationFrame ||
    30                            window.oCancelAnimationFrame ||
    31                            window.msCancelAnimationFrame ||
    32                            function(req) {
    33                              window.clearTimeout(req);
    34                            };
    36 function TreeView() {
    37   this._eventListeners = {};
    38   this._pendingActions = [];
    39   this._pendingActionsProcessingCallback = null;
    41   this._container = document.createElement("div");
    42   this._container.className = "treeViewContainer";
    43   this._container.setAttribute("tabindex", "0"); // make it focusable
    45   this._header = document.createElement("ul");
    46   this._header.className = "treeHeader";
    47   this._container.appendChild(this._header);
    49   this._verticalScrollbox = document.createElement("div");
    50   this._verticalScrollbox.className = "treeViewVerticalScrollbox";
    51   this._container.appendChild(this._verticalScrollbox);
    53   this._leftColumnBackground = document.createElement("div");
    54   this._leftColumnBackground.className = "leftColumnBackground";
    55   this._verticalScrollbox.appendChild(this._leftColumnBackground);
    57   this._horizontalScrollbox = document.createElement("div");
    58   this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
    59   this._verticalScrollbox.appendChild(this._horizontalScrollbox);
    61   this._styleElement = document.createElement("style");
    62   this._styleElement.setAttribute("type", "text/css");
    63   this._container.appendChild(this._styleElement);
    65   this._contextMenu = document.createElement("menu");
    66   this._contextMenu.setAttribute("type", "context");
    67   this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
    68   this._container.appendChild(this._contextMenu);
    70   this._busyCover = document.createElement("div");
    71   this._busyCover.className = "busyCover";
    72   this._container.appendChild(this._busyCover);
    73   this._abortToggleAll = false;
    74   this.initSelection = true;
    76   var self = this;
    77   this._container.onkeydown = function (e) {
    78     self._onkeypress(e);
    79   };
    80   this._container.onkeypress = function (e) {
    81     // on key down gives us '8' and mapping shift+8='*' may not be portable.
    82     if (String.fromCharCode(e.charCode) == '*')
    83       self._onkeypress(e);
    84   };
    85   this._container.onclick = function (e) {
    86     self._onclick(e);
    87   };
    88   this._verticalScrollbox.addEventListener("contextmenu", function(event) {
    89     self._populateContextMenu(event);
    90   }, true);
    91   this._setUpScrolling();
    92 };
    93 TreeView.instanceCounter = 0;
    95 TreeView.prototype = {
    96   getContainer: function TreeView_getContainer() {
    97     return this._container;
    98   },
    99   setColumns: function TreeView_setColumns(columns) {
   100     this._header.innerHTML = "";
   101     for (var i = 0; i < columns.length; i++) {
   102       var li = document.createElement("li");
   103       li.className = "treeColumnHeader treeColumnHeader" + i;
   104       li.id = columns[i].name + "Header";
   105       li.textContent = columns[i].title;
   106       this._header.appendChild(li);
   107     }
   108   },
   109   dataIsOutdated: function TreeView_dataIsOutdated() {
   110     this._busyCover.classList.add("busy");
   111   },
   112   display: function TreeView_display(data, resources, filterByName) {
   113     this._busyCover.classList.remove("busy");
   114     this._filterByName = filterByName;
   115     this._resources = resources;
   116     this._addResourceIconStyles();
   117     this._filterByNameReg = null; // lazy init
   118     if (this._filterByName === "")
   119       this._filterByName = null;
   120     this._horizontalScrollbox.innerHTML = "";
   121     this._horizontalScrollbox.data = data[0].getData();
   122     if (this._pendingActionsProcessingCallback) {
   123       cancelAnimationFrame(this._pendingActionsProcessingCallback);
   124       this._pendingActionsProcessingCallback = 0;
   125     }
   126     this._pendingActions = [];
   128     this._pendingActions.push({
   129       parentElement: this._horizontalScrollbox,
   130       parentNode: null,
   131       data: data[0].getData()
   132     });
   133     this._processPendingActionsChunk();
   134     changeFocus(this._container);
   135   },
   136   // Provide a snapshot of the reverse selection to restore with 'invert callback'
   137   getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
   138     var snapshot = [];
   140     if (!this._selectedNode) {
   141       return snapshot;
   142     }
   144     var curr = this._selectedNode.data;
   146     while(curr) {
   147       if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) {
   148         snapshot.push(curr.name);
   149         //dump(JSON.stringify(curr.name) + "\n");
   150       }
   151       if (curr.treeChildren && curr.treeChildren.length >= 1) {
   152         curr = curr.treeChildren[0].getData();
   153       } else {
   154         break;
   155       }
   156     }
   158     return snapshot.reverse();
   159   },
   160   // Provide a snapshot of the current selection to restore
   161   getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) {
   162     var snapshot = [];
   163     var curr = this._selectedNode;
   165     while(curr) {
   166       if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) {
   167         snapshot.push(curr.data.name);
   168         //dump(JSON.stringify(curr.data.name) + "\n");
   169       }
   170       curr = curr.treeParent;
   171     }
   173     return snapshot.reverse();
   174   },
   175   setSelection: function TreeView_setSelection(frames) {
   176     this.restoreSelectionSnapshot(frames, false);
   177   },
   178   // Take a selection snapshot and restore the selection
   179   restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) {
   180     var currNode = this._horizontalScrollbox.firstChild;
   181     if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
   182       snapshot.shift();
   183     }
   184     //dump("len: " + snapshot.length + "\n");
   185     next_level: while (currNode && snapshot.length > 0) {
   186       this._toggle(currNode, false, true);
   187       this._syncProcessPendingActionProcessing();
   188       for (var i = 0; i < currNode.treeChildren.length; i++) {
   189         if (currNode.treeChildren[i].data.name == snapshot[0]) {
   190           snapshot.shift();
   191           this._toggle(currNode, false, true);
   192           currNode = currNode.treeChildren[i];
   193           continue next_level;
   194         }
   195       }
   196       if (allowNonContiguous) {
   197         // We need to do a Breadth-first search to find a match
   198         var pendingSearch = [currNode.data];
   199         while (pendingSearch.length > 0) {
   200           var node = pendingSearch.shift();
   201           if (!node.treeChildren)
   202             continue;
   203           for (var i = 0; i < node.treeChildren.length; i++) {
   204             var childNode = node.treeChildren[i].getData();
   205             if (childNode.name == snapshot[0]) {
   206               //dump("found: " + childNode.name + "\n");
   207               snapshot.shift();
   208               var nodesToToggle = [childNode];
   209               while (nodesToToggle[0].name != currNode.data.name) {
   210                 nodesToToggle.splice(0, 0, nodesToToggle[0].parent);
   211               }
   212               var lastToggle = currNode;
   213               for (var j = 0; j < nodesToToggle.length; j++) {
   214                 for (var k = 0; k < lastToggle.treeChildren.length; k++) {
   215                   if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) {
   216                     //dump("Expend: " + nodesToToggle[j].name + "\n");
   217                     this._toggle(lastToggle.treeChildren[k], false, true);
   218                     lastToggle = lastToggle.treeChildren[k];
   219                     this._syncProcessPendingActionProcessing();
   220                   }
   221                 }
   222               }
   223               currNode = lastToggle;
   224               continue next_level;
   225             }
   226             //dump("pending: " + childNode.name + "\n");
   227             pendingSearch.push(childNode);
   228           }
   229         }
   230       }
   231       break; // Didn't find child node matching
   232     }
   234     if (currNode == this._horizontalScrollbox) {
   235       PROFILERERROR("Failed to restore selection, could not find root.\n");
   236       return;
   237     }
   239     this._toggle(currNode, true, true);
   240     this._select(currNode);
   241   },
   242   _processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) {
   243     this._pendingActionsProcessingCallback = 0;
   245     var startTime = Date.now();
   246     var endTime = startTime + kMaxChunkDuration;
   247     while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) {
   248       this._processOneAction(this._pendingActions.shift());
   249     }
   250     this._scrollHeightChanged();
   252     this._schedulePendingActionProcessing();
   253   },
   254   _schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() {
   255     if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) {
   256       var self = this;
   257       this._pendingActionsProcessingCallback = requestAnimationFrame(function () {
   258         self._processPendingActionsChunk();
   259       });
   260     }
   261   },
   262   _syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() {
   263     this._processPendingActionsChunk(true);
   264   },
   265   _processOneAction: function TreeView__processOneAction(action) {
   266     var li = this._createTree(action.parentElement, action.parentNode, action.data);
   267     if ("allChildrenCollapsedValue" in action) {
   268       if (this._abortToggleAll)
   269         return;
   270       this._toggleAll(li, action.allChildrenCollapsedValue, true);
   271     }
   272   },
   273   addEventListener: function TreeView_addEventListener(eventName, callbackFunction) {
   274     if (!(eventName in this._eventListeners))
   275       this._eventListeners[eventName] = [];
   276     if (this._eventListeners[eventName].indexOf(callbackFunction) != -1)
   277       return;
   278     this._eventListeners[eventName].push(callbackFunction);
   279   },
   280   removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) {
   281     if (!(eventName in this._eventListeners))
   282       return;
   283     var index = this._eventListeners[eventName].indexOf(callbackFunction);
   284     if (index == -1)
   285       return;
   286     this._eventListeners[eventName].splice(index, 1);
   287   },
   288   _fireEvent: function TreeView__fireEvent(eventName, eventObject) {
   289     if (!(eventName in this._eventListeners))
   290       return;
   291     this._eventListeners[eventName].forEach(function (callbackFunction) {
   292       callbackFunction(eventObject);
   293     });
   294   },
   295   _setUpScrolling: function TreeView__setUpScrolling() {
   296     var waitingForPaint = false;
   297     var accumulatedDeltaX = 0;
   298     var accumulatedDeltaY = 0;
   299     var self = this;
   300     function scrollListener(e) {
   301       if (!waitingForPaint) {
   302         requestAnimationFrame(function () {
   303           self._horizontalScrollbox.scrollLeft += accumulatedDeltaX;
   304           self._verticalScrollbox.scrollTop += accumulatedDeltaY;
   305           accumulatedDeltaX = 0;
   306           accumulatedDeltaY = 0;
   307           waitingForPaint = false;
   308         });
   309         waitingForPaint = true;
   310       }
   311       if (e.axis == e.HORIZONTAL_AXIS) {
   312         accumulatedDeltaX += e.detail;
   313       } else {
   314         accumulatedDeltaY += e.detail;
   315       }
   316       e.preventDefault();
   317     }
   318     this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false);
   319     this._verticalScrollbox.cleanUp = function () {
   320       self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false);
   321     };
   322   },
   323   _scrollHeightChanged: function TreeView__scrollHeightChanged() {
   324     if (!this._pendingScrollHeightChanged) {
   325       var self = this;
   326       this._pendingScrollHeightChanged = setTimeout(function() {
   327         self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
   328         self._pendingScrollHeightChanged = null;
   329       }, 0);
   330     }
   331   },
   332   _createTree: function TreeView__createTree(parentElement, parentNode, data) {
   333     var div = document.createElement("div");
   334     div.className = "treeViewNode collapsed";
   335     var hasChildren = ("children" in data) && (data.children.length > 0);
   336     if (!hasChildren)
   337       div.classList.add("leaf");
   338     var treeLine = document.createElement("div");
   339     treeLine.className = "treeLine";
   340     treeLine.innerHTML = this._HTMLForFunction(data);
   341     div.depth = parentNode ? parentNode.depth + 1 : 0;
   342     div.style.marginLeft = div.depth + "em";
   343     // When this item is toggled we will expand its children
   344     div.pendingExpand = [];
   345     div.treeLine = treeLine;
   346     div.data = data;
   347     // Useful for debugging
   348     //this.uniqueID = this.uniqueID || 0;
   349     //div.id = "Node" + this.uniqueID++;
   350     div.appendChild(treeLine);
   351     div.treeChildren = [];
   352     div.treeParent = parentNode;
   353     if (hasChildren) {
   354       for (var i = 0; i < data.children.length; ++i) {
   355         div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
   356       }
   357     }
   358     if (parentNode) {
   359       parentNode.treeChildren.push(div);
   360     }
   361     if (parentNode != null) {
   362       var nextTo;
   363       if (parentNode.treeChildren.length > 1) {
   364         nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
   365       } else {
   366         nextTo = parentNode.nextSibling;
   367       }
   368       parentElement.insertBefore(div, nextTo);
   369     } else {
   370       parentElement.appendChild(div);
   371     }
   372     return div;
   373   },
   374   _addResourceIconStyles: function TreeView__addResourceIconStyles() {
   375     var styles = [];
   376     for (var resourceName in this._resources) {
   377       var resource = this._resources[resourceName];
   378       if (resource.icon) {
   379         styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
   380       }
   381     }
   382     this._styleElement.textContent = styles.join("\n");
   383   },
   384   _populateContextMenu: function TreeView__populateContextMenu(event) {
   385     this._verticalScrollbox.setAttribute("contextmenu", "");
   387     var target = event.target;
   388     if (target.classList.contains("expandCollapseButton") ||
   389         target.classList.contains("focusCallstackButton"))
   390       return;
   392     var li = this._getParentTreeViewNode(target);
   393     if (!li)
   394       return;
   396     this._select(li);
   398     this._contextMenu.innerHTML = "";
   400     var self = this;
   401     this._contextMenuForFunction(li.data).forEach(function (menuItem) {
   402       var menuItemNode = document.createElement("menuitem");
   403       menuItemNode.onclick = (function (menuItem) {
   404         return function() {
   405           self._contextMenuClick(li.data, menuItem);
   406         };
   407       })(menuItem);
   408       menuItemNode.label = menuItem;
   409       self._contextMenu.appendChild(menuItemNode);
   410     });
   412     this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id);
   413   },
   414   _contextMenuClick: function TreeView__contextMenuClick(node, menuItem) {
   415     this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem });
   416   },
   417   _contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
   418     // TODO move me outside tree.js
   419     var menu = [];
   420     if (node.library && (
   421       node.library.toLowerCase() == "lib_xul" ||
   422       node.library.toLowerCase() == "lib_xul.dll"
   423       )) {
   424       menu.push("View Source");
   425     }
   426     if (node.isJSFrame && node.scriptLocation) {
   427       menu.push("View JS Source");
   428     }
   429     menu.push("Focus Frame");
   430     menu.push("Focus Callstack");
   431     menu.push("Google Search");
   432     menu.push("Plugin View: Pie");
   433     menu.push("Plugin View: Tree");
   434     return menu;
   435   },
   436   _HTMLForFunction: function TreeView__HTMLForFunction(node) {
   437     var nodeName = escapeHTML(node.name);
   438     var resource = this._resources[node.library] || {};
   439     var libName = escapeHTML(resource.name || "");
   440     if (this._filterByName) {
   441       if (!this._filterByNameReg) {
   442         this._filterByName = RegExp.escape(this._filterByName);
   443         this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi");
   444       }
   445       nodeName = nodeName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
   446       libName = libName.replace(this._filterByNameReg, "<a style='color:red;'>$1</a>");
   447     }
   448     var samplePercentage;
   449     if (isNaN(node.ratio)) {
   450       samplePercentage = "";
   451     } else {
   452       samplePercentage = (100 * node.ratio).toFixed(1) + "%";
   453     }
   454     return '<input type="button" value="Expand / Collapse" class="expandCollapseButton" tabindex="-1"> ' +
   455       '<span class="sampleCount">' + node.counter + '</span> ' +
   456       '<span class="samplePercentage">' + samplePercentage + '</span> ' +
   457       '<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
   458       '<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
   459       '<span class="functionName">' + nodeName + '</span>' +
   460       '<span class="libraryName">' + libName + '</span>' +
   461       ((nodeName === '(total)' || gHideSourceLinks) ? '' :
   462         '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">');
   463   },
   464   _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) {
   465     while (div.pendingExpand != null && div.pendingExpand.length > 0) {
   466       var pendingExpand = div.pendingExpand.shift();
   467       pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue;
   468       this._pendingActions.push(pendingExpand);
   469       this._schedulePendingActionProcessing();
   470     }
   471   },
   472   _showChild: function TreeView__showChild(div, isVisible) {
   473     for (var i = 0; i < div.treeChildren.length; i++) {
   474       div.treeChildren[i].style.display = isVisible?"":"none";
   475       if (!isVisible) {
   476         div.treeChildren[i].classList.add("collapsed");
   477         this._showChild(div.treeChildren[i], isVisible);
   478       }
   479     }
   480   },
   481   _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
   482     var currentCollapsedValue = this._isCollapsed(div);
   483     if (newCollapsedValue === undefined)
   484       newCollapsedValue = !currentCollapsedValue;
   485     if (newCollapsedValue) {
   486       div.classList.add("collapsed");
   487       this._showChild(div, false);
   488     } else {
   489       this._resolveChildren(div, true);
   490       div.classList.remove("collapsed");
   491       this._showChild(div, true);
   492     }
   493     if (!suppressScrollHeightNotification)
   494       this._scrollHeightChanged();
   495   },
   496   _toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
   498     // Reset abort
   499     this._abortToggleAll = false;
   501     // Expands / collapses all child nodes, too.
   503     if (newCollapsedValue === undefined)
   504       newCollapsedValue = !this._isCollapsed(subtreeRoot);
   505     if (!newCollapsedValue) {
   506       // expanding
   507       this._resolveChildren(subtreeRoot, newCollapsedValue);
   508     }
   509     this._toggle(subtreeRoot, newCollapsedValue, true);
   510     for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) {
   511       this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true);
   512     }
   513     if (!suppressScrollHeightNotification)
   514       this._scrollHeightChanged();
   515   },
   516   _getParent: function TreeView__getParent(div) {
   517     return div.treeParent;
   518   },
   519   _getFirstChild: function TreeView__getFirstChild(div) {
   520     if (this._isCollapsed(div))
   521       return null;
   522     var child = div.treeChildren[0];
   523     return child;
   524   },
   525   _getLastChild: function TreeView__getLastChild(div) {
   526     if (this._isCollapsed(div))
   527       return div;
   528     var lastChild = div.treeChildren[div.treeChildren.length-1];
   529     if (lastChild == null)
   530       return div;
   531     return this._getLastChild(lastChild);
   532   },
   533   _getPrevSib: function TreeView__getPevSib(div) {
   534     if (div.treeParent == null)
   535       return null;
   536     var nodeIndex = div.treeParent.treeChildren.indexOf(div);
   537     if (nodeIndex == 0)
   538       return null;
   539     return div.treeParent.treeChildren[nodeIndex-1];
   540   },
   541   _getNextSib: function TreeView__getNextSib(div) {
   542     if (div.treeParent == null)
   543       return null;
   544     var nodeIndex = div.treeParent.treeChildren.indexOf(div);
   545     if (nodeIndex == div.treeParent.treeChildren.length - 1)
   546       return this._getNextSib(div.treeParent);
   547     return div.treeParent.treeChildren[nodeIndex+1];
   548   },
   549   _scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
   550     // Schedule this on the animation frame otherwise we may run this more then once per frames
   551     // causing more work then needed.
   552     var self = this;
   553     if (self._pendingAnimationFrame != null) {
   554       return;
   555     }
   556     self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
   557       cancelAnimationFrame(self._pendingAnimationFrame);
   558       self._pendingAnimationFrame = null;
   559       self._scrollIntoView(element, maxImportantWidth);
   560     });
   561   },
   562   _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
   563     // Make sure that element is inside the visible part of our scrollbox by
   564     // adjusting the scroll positions. If element is wider or
   565     // higher than the scroll port, the left and top edges are prioritized over
   566     // the right and bottom edges.
   567     // If maxImportantWidth is set, parts of the beyond this widths are
   568     // considered as not important; they'll not be moved into view.
   570     if (maxImportantWidth === undefined)
   571       maxImportantWidth = Infinity;
   573     var visibleRect = {
   574       left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150
   575       top: this._verticalScrollbox.getBoundingClientRect().top,
   576       right: this._horizontalScrollbox.getBoundingClientRect().right,
   577       bottom: this._verticalScrollbox.getBoundingClientRect().bottom
   578     }
   579     var r = element.getBoundingClientRect();
   580     var right = Math.min(r.right, r.left + maxImportantWidth);
   581     var leftCutoff = visibleRect.left - r.left;
   582     var rightCutoff = right - visibleRect.right;
   583     var topCutoff = visibleRect.top - r.top;
   584     var bottomCutoff = r.bottom - visibleRect.bottom;
   585     if (leftCutoff > 0)
   586       this._horizontalScrollbox.scrollLeft -= leftCutoff;
   587     else if (rightCutoff > 0)
   588       this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff);
   589     if (topCutoff > 0)
   590       this._verticalScrollbox.scrollTop -= topCutoff;
   591     else if (bottomCutoff > 0)
   592       this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff);
   593   },
   594   _select: function TreeView__select(li) {
   595     if (this._selectedNode != null) {
   596       this._selectedNode.treeLine.classList.remove("selected");
   597       this._selectedNode = null;
   598     }
   599     if (li) {
   600       li.treeLine.classList.add("selected");
   601       this._selectedNode = li;
   602       var functionName = li.treeLine.querySelector(".functionName");
   603       this._scheduleScrollIntoView(functionName, 400);
   604       this._fireEvent("select", li.data);
   605     }
   606     updateDocumentURL();
   607   },
   608   _isCollapsed: function TreeView__isCollapsed(div) {
   609     return div.classList.contains("collapsed");
   610   },
   611   _getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) {
   612     while (node) {
   613       if (node.nodeType != node.ELEMENT_NODE)
   614         break;
   615       if (node.classList.contains("treeViewNode"))
   616         return node;
   617       node = node.parentNode;
   618     }
   619     return null;
   620   },
   621   _onclick: function TreeView__onclick(event) {
   622     var target = event.target;
   623     var node = this._getParentTreeViewNode(target);
   624     if (!node)
   625       return;
   626     if (target.classList.contains("expandCollapseButton")) {
   627       if (event.altKey)
   628         this._toggleAll(node);
   629       else
   630         this._toggle(node);
   631     } else if (target.classList.contains("focusCallstackButton")) {
   632       this._fireEvent("focusCallstackButtonClicked", node.data);
   633     } else {
   634       this._select(node);
   635       if (event.detail == 2) // dblclick
   636         this._toggle(node);
   637     }
   638   },
   639   _onkeypress: function TreeView__onkeypress(event) {
   640     if (event.ctrlKey || event.altKey || event.metaKey)
   641       return;
   643     this._abortToggleAll = true;
   645     var selected = this._selectedNode;
   646     if (event.keyCode < 37 || event.keyCode > 40) {
   647       if (event.keyCode != 0 ||
   648           String.fromCharCode(event.charCode) != '*') {
   649         return;
   650       }
   651     }
   652     event.stopPropagation();
   653     event.preventDefault();
   654     if (!selected)
   655       return;
   656     if (event.keyCode == 37) { // KEY_LEFT
   657       var isCollapsed = this._isCollapsed(selected);
   658       if (!isCollapsed) {
   659         this._toggle(selected);
   660       } else {
   661         var parent = this._getParent(selected); 
   662         if (parent != null) {
   663           this._select(parent);
   664         }
   665       }
   666     } else if (event.keyCode == 38) { // KEY_UP
   667       var prevSib = this._getPrevSib(selected);
   668       var parent = this._getParent(selected); 
   669       if (prevSib != null) {
   670         this._select(this._getLastChild(prevSib));
   671       } else if (parent != null) {
   672         this._select(parent);
   673       }
   674     } else if (event.keyCode == 39) { // KEY_RIGHT
   675       var isCollapsed = this._isCollapsed(selected);
   676       if (isCollapsed) {
   677         this._toggle(selected);
   678         this._syncProcessPendingActionProcessing();
   679       } else {
   680         // Do KEY_DOWN
   681         var nextSib = this._getNextSib(selected);
   682         var child = this._getFirstChild(selected); 
   683         if (child != null) {
   684           this._select(child);
   685         } else if (nextSib) {
   686           this._select(nextSib);
   687         }
   688       }
   689     } else if (event.keyCode == 40) { // KEY_DOWN
   690       var nextSib = this._getNextSib(selected);
   691       var child = this._getFirstChild(selected); 
   692       if (child != null) {
   693         this._select(child);
   694       } else if (nextSib) {
   695         this._select(nextSib);
   696       }
   697     } else if (String.fromCharCode(event.charCode) == '*') {
   698       this._toggleAll(selected);
   699     }
   700   },
   701 };

mercurial