summaryrefslogtreecommitdiff
path: root/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/TreeOutline.js
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/TreeOutline.js')
-rw-r--r--Source/WebInspectorUI/UserInterface/Views/TreeOutline.js687
1 files changed, 687 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js
new file mode 100644
index 000000000..34a45e046
--- /dev/null
+++ b/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2007, 2013, 2015 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.
+ * 3. Neither the name of Apple Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "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 OR ITS 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.TreeOutline = class TreeOutline extends WebInspector.Object
+{
+ constructor(element)
+ {
+ super();
+
+ this.element = element || document.createElement("ol");
+ this.element.classList.add(WebInspector.TreeOutline.ElementStyleClassName);
+ this.element.addEventListener("contextmenu", this._handleContextmenu.bind(this));
+
+ this.children = [];
+ this.selectedTreeElement = null;
+ this._childrenListNode = this.element;
+ this._childrenListNode.removeChildren();
+ this._knownTreeElements = [];
+ this._treeElementsExpandedState = [];
+ this.allowsRepeatSelection = false;
+ this.root = true;
+ this.hasChildren = false;
+ this.expanded = true;
+ this.selected = false;
+ this.treeOutline = this;
+ this._hidden = false;
+ this._compact = false;
+ this._large = false;
+ this._disclosureButtons = true;
+ this._customIndent = false;
+
+ this._childrenListNode.tabIndex = 0;
+ this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
+
+ WebInspector.TreeOutline._generateStyleRulesIfNeeded();
+ }
+
+ // Public
+
+ get hidden()
+ {
+ return this._hidden;
+ }
+
+ set hidden(x)
+ {
+ if (this._hidden === x)
+ return;
+
+ this._hidden = x;
+ this.element.hidden = this._hidden;
+ }
+
+ get compact()
+ {
+ return this._compact;
+ }
+
+ set compact(x)
+ {
+ if (this._compact === x)
+ return;
+
+ this._compact = x;
+ if (this._compact)
+ this.large = false;
+
+ this.element.classList.toggle("compact", this._compact);
+ }
+
+ get large()
+ {
+ return this._large;
+ }
+
+ set large(x)
+ {
+ if (this._large === x)
+ return;
+
+ this._large = x;
+ if (this._large)
+ this.compact = false;
+
+ this.element.classList.toggle("large", this._large);
+ }
+
+ get disclosureButtons()
+ {
+ return this._disclosureButtons;
+ }
+
+ set disclosureButtons(x)
+ {
+ if (this._disclosureButtons === x)
+ return;
+
+ this._disclosureButtons = x;
+ this.element.classList.toggle("hide-disclosure-buttons", !this._disclosureButtons);
+ }
+
+ get customIndent()
+ {
+ return this._customIndent;
+ }
+
+ set customIndent(x)
+ {
+ if (this._customIndent === x)
+ return;
+
+ this._customIndent = x;
+ this.element.classList.toggle(WebInspector.TreeOutline.CustomIndentStyleClassName, this._customIndent);
+ }
+
+ appendChild(child)
+ {
+ console.assert(child);
+ if (!child)
+ return;
+
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild) {
+ lastChild.nextSibling = child;
+ child.previousSibling = lastChild;
+ } else {
+ child.previousSibling = null;
+ child.nextSibling = null;
+ }
+
+ var isFirstChild = !this.children.length;
+
+ this.children.push(child);
+ this.hasChildren = true;
+ child.parent = this;
+ child.treeOutline = this.treeOutline;
+ child.treeOutline._rememberTreeElement(child);
+
+ var current = child.children[0];
+ while (current) {
+ current.treeOutline = this.treeOutline;
+ current.treeOutline._rememberTreeElement(current);
+ current = current.traverseNextTreeElement(false, child, true);
+ }
+
+ if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
+ child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
+
+ if (this._childrenListNode)
+ child._attach();
+
+ if (this.treeOutline)
+ this.treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementAdded, {element: child});
+
+ if (isFirstChild && this.expanded)
+ this.expand();
+ }
+
+ insertChild(child, index)
+ {
+ console.assert(child);
+ if (!child)
+ return;
+
+ var previousChild = (index > 0 ? this.children[index - 1] : null);
+ if (previousChild) {
+ previousChild.nextSibling = child;
+ child.previousSibling = previousChild;
+ } else {
+ child.previousSibling = null;
+ }
+
+ var nextChild = this.children[index];
+ if (nextChild) {
+ nextChild.previousSibling = child;
+ child.nextSibling = nextChild;
+ } else {
+ child.nextSibling = null;
+ }
+
+ var isFirstChild = !this.children.length;
+
+ this.children.splice(index, 0, child);
+ this.hasChildren = true;
+ child.parent = this;
+ child.treeOutline = this.treeOutline;
+ child.treeOutline._rememberTreeElement(child);
+
+ var current = child.children[0];
+ while (current) {
+ current.treeOutline = this.treeOutline;
+ current.treeOutline._rememberTreeElement(current);
+ current = current.traverseNextTreeElement(false, child, true);
+ }
+
+ if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined)
+ child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier];
+
+ if (this._childrenListNode)
+ child._attach();
+
+ if (this.treeOutline)
+ this.treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementAdded, {element: child});
+
+ if (isFirstChild && this.expanded)
+ this.expand();
+ }
+
+ removeChildAtIndex(childIndex, suppressOnDeselect, suppressSelectSibling)
+ {
+ console.assert(childIndex >= 0 && childIndex < this.children.length);
+ if (childIndex < 0 || childIndex >= this.children.length)
+ return;
+
+ let child = this.children[childIndex];
+ this.children.splice(childIndex, 1);
+
+ let parent = child.parent;
+ if (child.deselect(suppressOnDeselect)) {
+ if (child.previousSibling && !suppressSelectSibling)
+ child.previousSibling.select(true, false);
+ else if (child.nextSibling && !suppressSelectSibling)
+ child.nextSibling.select(true, false);
+ else if (!suppressSelectSibling)
+ parent.select(true, false);
+ }
+
+ if (child.previousSibling)
+ child.previousSibling.nextSibling = child.nextSibling;
+ if (child.nextSibling)
+ child.nextSibling.previousSibling = child.previousSibling;
+
+ let treeOutline = child.treeOutline;
+ if (treeOutline) {
+ treeOutline._forgetTreeElement(child);
+ treeOutline._forgetChildrenRecursive(child);
+ }
+
+ child._detach();
+ child.treeOutline = null;
+ child.parent = null;
+ child.nextSibling = null;
+ child.previousSibling = null;
+
+ if (treeOutline)
+ treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementRemoved, {element: child});
+ }
+
+ removeChild(child, suppressOnDeselect, suppressSelectSibling)
+ {
+ console.assert(child);
+ if (!child)
+ return;
+
+ var childIndex = this.children.indexOf(child);
+ console.assert(childIndex !== -1);
+ if (childIndex === -1)
+ return;
+
+ this.removeChildAtIndex(childIndex, suppressOnDeselect, suppressSelectSibling);
+
+ if (!this.children.length) {
+ if (this._listItemNode)
+ this._listItemNode.classList.remove("parent");
+ this.hasChildren = false;
+ }
+ }
+
+ removeChildren(suppressOnDeselect)
+ {
+ for (let child of this.children) {
+ child.deselect(suppressOnDeselect);
+
+ let treeOutline = child.treeOutline;
+ if (treeOutline) {
+ treeOutline._forgetTreeElement(child);
+ treeOutline._forgetChildrenRecursive(child);
+ }
+
+ child._detach();
+ child.treeOutline = null;
+ child.parent = null;
+ child.nextSibling = null;
+ child.previousSibling = null;
+
+ if (treeOutline)
+ treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementRemoved, {element: child});
+ }
+
+ this.children = [];
+ }
+
+ removeChildrenRecursive(suppressOnDeselect)
+ {
+ let childrenToRemove = this.children;
+ let child = this.children[0];
+ while (child) {
+ if (child.children.length)
+ childrenToRemove = childrenToRemove.concat(child.children);
+ child = child.traverseNextTreeElement(false, this, true);
+ }
+
+ for (let child of childrenToRemove) {
+ child.deselect(suppressOnDeselect);
+
+ let treeOutline = child.treeOutline;
+ if (treeOutline)
+ treeOutline._forgetTreeElement(child);
+
+ child._detach();
+ child.children = [];
+ child.treeOutline = null;
+ child.parent = null;
+ child.nextSibling = null;
+ child.previousSibling = null;
+
+ if (treeOutline)
+ treeOutline.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementRemoved, {element: child});
+ }
+
+ this.children = [];
+ }
+
+ _rememberTreeElement(element)
+ {
+ if (!this._knownTreeElements[element.identifier])
+ this._knownTreeElements[element.identifier] = [];
+
+ // check if the element is already known
+ var elements = this._knownTreeElements[element.identifier];
+ if (elements.indexOf(element) !== -1)
+ return;
+
+ // add the element
+ elements.push(element);
+ }
+
+ _forgetTreeElement(element)
+ {
+ if (this.selectedTreeElement === element) {
+ element.deselect(true);
+ this.selectedTreeElement = null;
+ }
+ if (this._knownTreeElements[element.identifier])
+ this._knownTreeElements[element.identifier].remove(element, true);
+ }
+
+ _forgetChildrenRecursive(parentElement)
+ {
+ var child = parentElement.children[0];
+ while (child) {
+ this._forgetTreeElement(child);
+ child = child.traverseNextTreeElement(false, parentElement, true);
+ }
+ }
+
+ getCachedTreeElement(representedObject)
+ {
+ if (!representedObject)
+ return null;
+
+ if (representedObject.__treeElementIdentifier) {
+ // 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) {
+ for (var i = 0; i < elements.length; ++i)
+ if (elements[i].representedObject === representedObject)
+ return elements[i];
+ }
+ }
+ return null;
+ }
+
+ findTreeElement(representedObject, isAncestor, getParent)
+ {
+ if (!representedObject)
+ return null;
+
+ var cachedElement = this.getCachedTreeElement(representedObject);
+ if (cachedElement)
+ return cachedElement;
+
+ // The representedObject isn't known, 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) {
+ item = this.children[i];
+ if (item.representedObject === representedObject || (isAncestor && isAncestor(item.representedObject, representedObject))) {
+ found = true;
+ break;
+ }
+ }
+
+ 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) {
+ ancestors.unshift(currentObject);
+ if (currentObject === item.representedObject)
+ break;
+ 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 (item)
+ item.onpopulate();
+ }
+
+ return this.getCachedTreeElement(representedObject);
+ }
+
+ _treeElementDidChange(treeElement)
+ {
+ if (treeElement.treeOutline !== this)
+ return;
+
+ this.dispatchEventToListeners(WebInspector.TreeOutline.Event.ElementDidChange, {element: treeElement});
+ }
+
+ treeElementFromNode(node)
+ {
+ var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]);
+ if (listNode)
+ return listNode.parentTreeElement || listNode.treeElement;
+ return null;
+ }
+
+ treeElementFromPoint(x, y)
+ {
+ var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y);
+ if (!node)
+ return null;
+
+ return this.treeElementFromNode(node);
+ }
+
+ _treeKeyDown(event)
+ {
+ if (event.target !== this._childrenListNode)
+ return;
+
+ if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
+ return;
+
+ var handled = false;
+ var nextSelectedElement;
+ if (event.keyIdentifier === "Up" && !event.altKey) {
+ nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
+ while (nextSelectedElement && !nextSelectedElement.selectable)
+ nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(true);
+ handled = nextSelectedElement ? true : false;
+ } else if (event.keyIdentifier === "Down" && !event.altKey) {
+ nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
+ while (nextSelectedElement && !nextSelectedElement.selectable)
+ nextSelectedElement = nextSelectedElement.traverseNextTreeElement(true);
+ handled = nextSelectedElement ? true : false;
+ } else if (event.keyIdentifier === "Left") {
+ if (this.selectedTreeElement.expanded) {
+ if (event.altKey)
+ this.selectedTreeElement.collapseRecursively();
+ else
+ this.selectedTreeElement.collapse();
+ handled = true;
+ } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) {
+ handled = true;
+ if (this.selectedTreeElement.parent.selectable) {
+ nextSelectedElement = this.selectedTreeElement.parent;
+ while (nextSelectedElement && !nextSelectedElement.selectable)
+ nextSelectedElement = nextSelectedElement.parent;
+ handled = nextSelectedElement ? true : false;
+ } else if (this.selectedTreeElement.parent)
+ this.selectedTreeElement.parent.collapse();
+ }
+ } else if (event.keyIdentifier === "Right") {
+ if (!this.selectedTreeElement.revealed()) {
+ this.selectedTreeElement.reveal();
+ handled = true;
+ } else if (this.selectedTreeElement.hasChildren) {
+ handled = true;
+ if (this.selectedTreeElement.expanded) {
+ nextSelectedElement = this.selectedTreeElement.children[0];
+ while (nextSelectedElement && !nextSelectedElement.selectable)
+ nextSelectedElement = nextSelectedElement.nextSibling;
+ handled = nextSelectedElement ? true : false;
+ } else {
+ if (event.altKey)
+ this.selectedTreeElement.expandRecursively();
+ else
+ this.selectedTreeElement.expand();
+ }
+ }
+ } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) {
+ if (this.selectedTreeElement.ondelete)
+ handled = this.selectedTreeElement.ondelete();
+ if (!handled && this.treeOutline.ondelete)
+ handled = this.treeOutline.ondelete(this.selectedTreeElement);
+ } else if (isEnterKey(event)) {
+ if (this.selectedTreeElement.onenter)
+ handled = this.selectedTreeElement.onenter();
+ if (!handled && this.treeOutline.onenter)
+ handled = this.treeOutline.onenter(this.selectedTreeElement);
+ } else if (event.keyIdentifier === "U+0020" /* Space */) {
+ if (this.selectedTreeElement.onspace)
+ handled = this.selectedTreeElement.onspace();
+ if (!handled && this.treeOutline.onspace)
+ handled = this.treeOutline.onspace(this.selectedTreeElement);
+ }
+
+ if (nextSelectedElement) {
+ nextSelectedElement.reveal();
+ nextSelectedElement.select(false, true);
+ }
+
+ if (handled) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+
+ expand()
+ {
+ // this is the root, do nothing
+ }
+
+ collapse()
+ {
+ // this is the root, do nothing
+ }
+
+ revealed()
+ {
+ return true;
+ }
+
+ reveal()
+ {
+ // this is the root, do nothing
+ }
+
+ select()
+ {
+ // this is the root, do nothing
+ }
+
+ revealAndSelect(omitFocus)
+ {
+ // this is the root, do nothing
+ }
+
+ get selectedTreeElementIndex()
+ {
+ if (!this.hasChildren || !this.selectedTreeElement)
+ return;
+
+ for (var i = 0; i < this.children.length; ++i) {
+ if (this.children[i] === this.selectedTreeElement)
+ return i;
+ }
+
+ return false;
+ }
+
+ // Protected
+
+ treeElementFromEvent(event)
+ {
+ let scrollContainer = this.element.parentElement;
+
+ // We choose this X coordinate based on the knowledge that our list
+ // items extend at least to the right edge of the outer <ol> container.
+ // In the no-word-wrap mode the outer <ol> may be wider than the tree container
+ // (and partially hidden), in which case we are left to use only its right boundary.
+ let x = scrollContainer.totalOffsetLeft + scrollContainer.offsetWidth - 36;
+ let y = event.pageY;
+
+ // Our list items have 1-pixel cracks between them vertically. We avoid
+ // the cracks by checking slightly above and slightly below the mouse
+ // and seeing if we hit the same element each time.
+ let elementUnderMouse = this.treeElementFromPoint(x, y);
+ let elementAboveMouse = this.treeElementFromPoint(x, y - 2);
+ let element = null;
+ if (elementUnderMouse === elementAboveMouse)
+ element = elementUnderMouse;
+ else
+ element = this.treeElementFromPoint(x, y + 2);
+
+ return element;
+ }
+
+ populateContextMenu(contextMenu, event, treeElement)
+ {
+ treeElement.populateContextMenu(contextMenu, event);
+ }
+
+ // Private
+
+ static _generateStyleRulesIfNeeded()
+ {
+ if (WebInspector.TreeOutline._styleElement)
+ return;
+
+ WebInspector.TreeOutline._styleElement = document.createElement("style");
+
+ let maximumTreeDepth = 32;
+ let baseLeftPadding = 5; // Matches the padding in TreeOutline.css for the item class. Keep in sync.
+ let depthPadding = 10;
+
+ let styleText = "";
+ let childrenSubstring = "";
+ for (let i = 1; i <= maximumTreeDepth; ++i) {
+ // Keep all the elements at the same depth once the maximum is reached.
+ childrenSubstring += i === maximumTreeDepth ? " .children" : " > .children";
+ styleText += `.${WebInspector.TreeOutline.ElementStyleClassName}:not(.${WebInspector.TreeOutline.CustomIndentStyleClassName})${childrenSubstring} > .item { `;
+ styleText += "padding-left: " + (baseLeftPadding + (depthPadding * i)) + "px; }\n";
+ }
+
+ WebInspector.TreeOutline._styleElement.textContent = styleText;
+
+ document.head.appendChild(WebInspector.TreeOutline._styleElement);
+ }
+
+ _handleContextmenu(event)
+ {
+ let treeElement = this.treeElementFromEvent(event);
+ if (!treeElement)
+ return;
+
+ let contextMenu = WebInspector.ContextMenu.createFromEvent(event);
+ this.populateContextMenu(contextMenu, event, treeElement);
+ }
+};
+
+WebInspector.TreeOutline._styleElement = null;
+
+WebInspector.TreeOutline.ElementStyleClassName = "tree-outline";
+WebInspector.TreeOutline.CustomIndentStyleClassName = "custom-indent";
+
+WebInspector.TreeOutline.Event = {
+ ElementAdded: Symbol("element-added"),
+ ElementDidChange: Symbol("element-did-change"),
+ ElementRemoved: Symbol("element-removed"),
+ ElementDisclosureDidChanged: Symbol("element-disclosure-did-change"),
+ ElementVisibilityDidChange: Symbol("element-visbility-did-change"),
+ SelectionDidChange: Symbol("selection-did-change")
+};
+
+WebInspector.TreeOutline._knownTreeElementNextIdentifier = 1;