Fixes a regression where TreeOutline.findTreeElement would
[WebKit-https.git] / WebCore / page / inspector / treeoutline.js
index ac8e6a0..5df0f7d 100644 (file)
@@ -243,18 +243,21 @@ TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor,
         return null;
 
     if ("__treeElementIdentifier" in representedObject) {
+        // If this representedObject has a tree element identifier, and it is a known TreeElement
+        // in our tree we can just return that tree element.
         var elements = this._knownTreeElements[representedObject.__treeElementIdentifier];
-        if (!elements)
-            return null;
-
-        for (var i = 0; i < elements.length; ++i)
-            if (elements[i].representedObject === representedObject)
-                return elements[i];
+        if (elements) {
+            for (var i = 0; i < elements.length; ++i)
+                if (elements[i].representedObject === representedObject)
+                    return elements[i];
+        }
     }
 
     if (!isAncestor || !(isAncestor instanceof Function) || !getParent || !(getParent instanceof Function))
         return null;
 
+    // The representedObject isn't know, so we start at the top of the tree and work down to find the first
+    // tree element that represents representedObject or one of its ancestors.
     var item;
     var found = false;
     for (var i = 0; i < this.children.length; ++i) {
@@ -268,6 +271,8 @@ TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor,
     if (!found)
         return null;
 
+    // Make sure the item that we found is connected to the root of the tree.
+    // Build up a list of representedObject's ancestors that aren't already in our tree.
     var ancestors = [];
     var currentObject = representedObject;
     while (currentObject) {
@@ -277,13 +282,31 @@ TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor,
         currentObject = getParent(currentObject);
     }
 
+    // For each of those ancestors we populate them to fill in the tree.
     for (var i = 0; i < ancestors.length; ++i) {
+        // Make sure we don't call findTreeElement with the same representedObject
+        // again, to prevent infinite recursion.
+        if (ancestors[i] === representedObject)
+            continue;
+        // FIXME: we could do something faster than findTreeElement since we will know the next
+        // ancestor exists in the tree.
         item = this.findTreeElement(ancestors[i], isAncestor, getParent);
-        if (ancestors[i] !== representedObject && item && item.onpopulate)
+        if (item && item.onpopulate)
             item.onpopulate(item);
     }
 
-    return item;
+    // Now that all the ancestors are populated, try to find the representedObject again. This time
+    // without the isAncestor and getParent functions to prevent an infinite recursion if it isn't found.
+    return this.findTreeElement(representedObject);
+}
+
+TreeOutline.prototype.treeElementFromPoint = function(x, y)
+{
+    var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
+    var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
+    if (listNode)
+        return listNode.parentTreeElement || listNode.treeElement;
+    return null;
 }
 
 TreeOutline.prototype.handleKeyEvent = function(event)
@@ -452,6 +475,16 @@ TreeElement.prototype = {
             if (this._childrenListNode)
                 this._childrenListNode.removeStyleClass("hidden");
         }
+    },
+
+    get shouldRefreshChildren() {
+        return this._shouldRefreshChildren;
+    },
+
+    set shouldRefreshChildren(x) {
+        this._shouldRefreshChildren = x;
+        if (x && this.expanded)
+            this.expand();
     }
 }
 
@@ -463,7 +496,7 @@ TreeElement.prototype.removeChildrenRecursive = TreeOutline._removeChildrenRecur
 
 TreeElement.prototype._attach = function()
 {
-    if (!this._listItemNode || this.parent.refreshChildren) {
+    if (!this._listItemNode || this.parent._shouldRefreshChildren) {
         if (this._listItemNode && this._listItemNode.parentNode)
             this._listItemNode.parentNode.removeChild(this._listItemNode);
 
@@ -512,8 +545,10 @@ TreeElement.treeElementSelected = function(event)
     if (!element || !element.treeElement || !element.treeElement.selectable)
         return;
 
-    if (event.offsetX > element.treeElement.arrowToggleWidth || !element.treeElement.hasChildren)
-        element.treeElement.select();
+    if (element.treeElement.isEventWithinDisclosureTriangle(event))
+        return;
+
+    element.treeElement.select();
 }
 
 TreeElement.treeElementToggled = function(event)
@@ -522,18 +557,19 @@ TreeElement.treeElementToggled = function(event)
     if (!element || !element.treeElement)
         return;
 
-    if (event.offsetX <= element.treeElement.arrowToggleWidth && element.treeElement.hasChildren) {
-        if (element.treeElement.expanded) {
-            if (event.altKey)
-                element.treeElement.collapseRecursively();
-            else
-                element.treeElement.collapse();
-        } else {
-            if (event.altKey)
-                element.treeElement.expandRecursively();
-            else
-                element.treeElement.expand();
-        }
+    if (!element.treeElement.isEventWithinDisclosureTriangle(event))
+        return;
+
+    if (element.treeElement.expanded) {
+        if (event.altKey)
+            element.treeElement.collapseRecursively();
+        else
+            element.treeElement.collapse();
+    } else {
+        if (event.altKey)
+            element.treeElement.expandRecursively();
+        else
+            element.treeElement.expand();
     }
 }
 
@@ -576,10 +612,10 @@ TreeElement.prototype.collapseRecursively = function()
 
 TreeElement.prototype.expand = function()
 {
-    if (!this.hasChildren || (this.expanded && !this.refreshChildren && this._childrenListNode))
+    if (!this.hasChildren || (this.expanded && !this._shouldRefreshChildren && this._childrenListNode))
         return;
 
-    if (!this._childrenListNode || this.refreshChildren) {
+    if (!this._childrenListNode || this._shouldRefreshChildren) {
         if (this._childrenListNode && this._childrenListNode.parentNode)
             this._childrenListNode.parentNode.removeChild(this._childrenListNode);
 
@@ -596,7 +632,7 @@ TreeElement.prototype.expand = function()
         for (var i = 0; i < this.children.length; ++i)
             this.children[i]._attach();
 
-        delete this.refreshChildren;
+        delete this._shouldRefreshChildren;
     }
 
     if (this._listItemNode) {
@@ -616,12 +652,23 @@ TreeElement.prototype.expand = function()
         this.onexpand(this);
 }
 
-TreeElement.prototype.expandRecursively = function()
+TreeElement.prototype.expandRecursively = function(maxDepth)
 {
     var item = this;
+    var info = {};
+    var depth = 0;
+
+    // The Inspector uses TreeOutlines to represents object properties, so recursive expansion
+    // in some case can be infinite, since JavaScript objects can hold circular references.
+    // So default to a recursion cap of 3 levels, since that gives fairly good results.
+    if (typeof maxDepth === "undefined" || typeof maxDepth === "null")
+        maxDepth = 3;
+
     while (item) {
-        item.expand();
-        item = item.traverseNextTreeElement(false, this);
+        if (depth < maxDepth)
+            item.expand();
+        item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info);
+        depth += info.depthChange;
     }
 }
 
@@ -681,14 +728,20 @@ TreeElement.prototype.deselect = function(supressOnDeselect)
         this.ondeselect(this);
 }
 
-TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate)
+TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin, dontPopulate, info)
 {
     if (!dontPopulate && this.hasChildren && this.onpopulate)
         this.onpopulate(this);
 
+    if (info)
+        info.depthChange = 0;
+
     var element = skipHidden ? (this.revealed() ? this.children[0] : null) : this.children[0];
-    if (element && (!skipHidden || (skipHidden && this.expanded)))
+    if (element && (!skipHidden || (skipHidden && this.expanded))) {
+        if (info)
+            info.depthChange = 1;
         return element;
+    }
 
     if (this === stayWithin)
         return null;
@@ -698,8 +751,11 @@ TreeElement.prototype.traverseNextTreeElement = function(skipHidden, stayWithin,
         return element;
 
     element = this;
-    while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin)
+    while (element && !element.root && !(skipHidden ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) {
+        if (info)
+            info.depthChange -= 1;
         element = element.parent;
+    }
 
     if (!element)
         return null;
@@ -727,3 +783,9 @@ TreeElement.prototype.traversePreviousTreeElement = function(skipHidden, dontPop
 
     return this.parent;
 }
+
+TreeElement.prototype.isEventWithinDisclosureTriangle = function(event)
+{
+    var left = this._listItemNode.totalOffsetLeft;
+    return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
+}