diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js new file mode 100644 index 000000000..b85ff0293 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js @@ -0,0 +1,567 @@ +/* + * Copyright (C) 2007, 2008, 2013, 2015 Apple Inc. All rights reserved. + * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> + * Copyright (C) 2009 Joseph Pecoraro + * + * 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.DOMTreeOutline = class DOMTreeOutline extends WebInspector.TreeOutline +{ + constructor(omitRootDOMNode, selectEnabled, excludeRevealElementContextMenu) + { + super(); + + this.element.addEventListener("mousedown", this._onmousedown.bind(this), false); + this.element.addEventListener("mousemove", this._onmousemove.bind(this), false); + this.element.addEventListener("mouseout", this._onmouseout.bind(this), false); + this.element.addEventListener("dragstart", this._ondragstart.bind(this), false); + this.element.addEventListener("dragover", this._ondragover.bind(this), false); + this.element.addEventListener("dragleave", this._ondragleave.bind(this), false); + this.element.addEventListener("drop", this._ondrop.bind(this), false); + this.element.addEventListener("dragend", this._ondragend.bind(this), false); + + this.element.classList.add("dom", WebInspector.SyntaxHighlightedStyleClassName); + + this._includeRootDOMNode = !omitRootDOMNode; + this._selectEnabled = selectEnabled; + this._excludeRevealElementContextMenu = excludeRevealElementContextMenu; + this._rootDOMNode = null; + this._selectedDOMNode = null; + + this._editable = false; + this._editing = false; + this._visible = false; + + this._hideElementKeyboardShortcut = new WebInspector.KeyboardShortcut(null, "H", this._hideElement.bind(this), this.element); + this._hideElementKeyboardShortcut.implicitlyPreventsDefault = false; + + WebInspector.showShadowDOMSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showShadowDOMSettingChanged, this); + } + + // Public + + wireToDomAgent() + { + this._elementsTreeUpdater = new WebInspector.DOMTreeUpdater(this); + } + + close() + { + WebInspector.showShadowDOMSetting.removeEventListener(null, null, this); + + if (this._elementsTreeUpdater) { + this._elementsTreeUpdater.close(); + this._elementsTreeUpdater = null; + } + } + + setVisible(visible, omitFocus) + { + this._visible = visible; + if (!this._visible) + return; + + this._updateModifiedNodes(); + + if (this._selectedDOMNode) + this._revealAndSelectNode(this._selectedDOMNode, omitFocus); + + this.update(); + } + + get rootDOMNode() + { + return this._rootDOMNode; + } + + set rootDOMNode(x) + { + if (this._rootDOMNode === x) + return; + + this._rootDOMNode = x; + + this._isXMLMimeType = x && x.isXMLNode(); + + this.update(); + } + + get isXMLMimeType() + { + return this._isXMLMimeType; + } + + selectedDOMNode() + { + return this._selectedDOMNode; + } + + selectDOMNode(node, focus) + { + if (this._selectedDOMNode === node) { + this._revealAndSelectNode(node, !focus); + return; + } + + this._selectedDOMNode = node; + this._revealAndSelectNode(node, !focus); + + // The _revealAndSelectNode() method might find a different element if there is inlined text, + // and the select() call would change the selectedDOMNode and reenter this setter. So to + // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same + // node as the one passed in. + // Note that _revealAndSelectNode will not do anything for a null node. + if (!node || this._selectedDOMNode === node) + this._selectedNodeChanged(); + } + + get editable() + { + return this._editable; + } + + set editable(x) + { + this._editable = x; + } + + get editing() + { + return this._editing; + } + + update() + { + if (!this.rootDOMNode) + return; + + let selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null; + + this.removeChildren(); + + var treeElement; + if (this._includeRootDOMNode) { + treeElement = new WebInspector.DOMTreeElement(this.rootDOMNode); + treeElement.selectable = this._selectEnabled; + this.appendChild(treeElement); + } else { + // FIXME: this could use findTreeElement to reuse a tree element if it already exists + var node = this.rootDOMNode.firstChild; + while (node) { + treeElement = new WebInspector.DOMTreeElement(node); + treeElement.selectable = this._selectEnabled; + this.appendChild(treeElement); + node = node.nextSibling; + + if (treeElement.hasChildren && !treeElement.expanded) + treeElement.expand(); + } + } + + if (selectedNode) + this._revealAndSelectNode(selectedNode, true); + } + + updateSelection() + { + // This will miss updating selection areas used for the hovered tree element and + // and those used to show forced pseudo class indicators, but this should be okay. + // The hovered element will update when user moves the mouse, and indicators don't need the + // selection area height to be accurate since they use ::before to place the indicator. + if (this.selectedTreeElement) + this.selectedTreeElement.updateSelectionArea(); + } + + _selectedNodeChanged() + { + this.dispatchEventToListeners(WebInspector.DOMTreeOutline.Event.SelectedNodeChanged); + } + + findTreeElement(node) + { + let isAncestorNode = (ancestor, node) => ancestor.isAncestor(node); + let parentNode = (node) => node.parentNode; + let treeElement = super.findTreeElement(node, isAncestorNode, parentNode); + if (!treeElement && node.nodeType() === Node.TEXT_NODE) { + // The text node might have been inlined if it was short, so try to find the parent element. + treeElement = super.findTreeElement(node.parentNode, isAncestorNode, parentNode); + } + + return treeElement; + } + + createTreeElementFor(node) + { + var treeElement = this.findTreeElement(node); + if (treeElement) + return treeElement; + + if (!node.parentNode) + return null; + + treeElement = this.createTreeElementFor(node.parentNode); + if (!treeElement) + return null; + + return treeElement.showChildNode(node); + } + + set suppressRevealAndSelect(x) + { + if (this._suppressRevealAndSelect === x) + return; + this._suppressRevealAndSelect = x; + } + + populateContextMenu(contextMenu, event, treeElement) + { + let tag = event.target.enclosingNodeOrSelfWithClass("html-tag"); + let textNode = event.target.enclosingNodeOrSelfWithClass("html-text-node"); + let commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment"); + let pseudoElement = event.target.enclosingNodeOrSelfWithClass("html-pseudo-element"); + + if (tag && treeElement._populateTagContextMenu) { + contextMenu.appendSeparator(); + + treeElement._populateTagContextMenu(contextMenu, event); + } else if (textNode && treeElement._populateTextContextMenu) { + contextMenu.appendSeparator(); + + treeElement._populateTextContextMenu(contextMenu, textNode); + } else if ((commentNode || pseudoElement) && treeElement._populateNodeContextMenu) { + contextMenu.appendSeparator(); + + treeElement._populateNodeContextMenu(contextMenu); + } + + super.populateContextMenu(contextMenu, event, treeElement); + } + + adjustCollapsedRange() + { + } + + // Private + + _revealAndSelectNode(node, omitFocus) + { + if (!node || this._suppressRevealAndSelect) + return; + + if (!WebInspector.showShadowDOMSetting.value) { + while (node && node.isInShadowTree()) + node = node.parentNode; + if (!node) + return; + } + + var treeElement = this.createTreeElementFor(node); + if (!treeElement) + return; + + treeElement.revealAndSelect(omitFocus); + } + + _onmousedown(event) + { + let element = this.treeElementFromEvent(event); + if (!element || element.isEventWithinDisclosureTriangle(event)) { + event.preventDefault(); + return; + } + + element.select(); + } + + _onmousemove(event) + { + let element = this.treeElementFromEvent(event); + if (element && this._previousHoveredElement === element) + return; + + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + this._previousHoveredElement = null; + } + + if (element) { + element.hovered = true; + this._previousHoveredElement = element; + + // Lazily compute tag-specific tooltips. + if (element.representedObject && !element.tooltip && element._createTooltipForNode) + element._createTooltipForNode(); + } + + WebInspector.domTreeManager.highlightDOMNode(element ? element.representedObject.id : 0); + } + + _onmouseout(event) + { + var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY); + if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element)) + return; + + if (this._previousHoveredElement) { + this._previousHoveredElement.hovered = false; + this._previousHoveredElement = null; + } + + WebInspector.domTreeManager.hideDOMNodeHighlight(); + } + + _ondragstart(event) + { + let treeElement = this.treeElementFromEvent(event); + if (!treeElement) + return false; + + if (!this._isValidDragSourceOrTarget(treeElement)) + return false; + + if (treeElement.representedObject.nodeName() === "BODY" || treeElement.representedObject.nodeName() === "HEAD") + return false; + + event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent); + event.dataTransfer.effectAllowed = "copyMove"; + this._nodeBeingDragged = treeElement.representedObject; + + WebInspector.domTreeManager.hideDOMNodeHighlight(); + + return true; + } + + _ondragover(event) + { + if (event.dataTransfer.types.includes(WebInspector.CSSStyleDetailsSidebarPanel.ToggledClassesDragType)) { + event.preventDefault(); + event.dataTransfer.dropEffect = "copy"; + return false; + } + + if (!this._nodeBeingDragged) + return false; + + let treeElement = this.treeElementFromEvent(event); + if (!this._isValidDragSourceOrTarget(treeElement)) + return false; + + let node = treeElement.representedObject; + while (node) { + if (node === this._nodeBeingDragged) + return false; + node = node.parentNode; + } + + this.dragOverTreeElement = treeElement; + treeElement.listItemElement.classList.add("elements-drag-over"); + treeElement.updateSelectionArea(); + + event.preventDefault(); + event.dataTransfer.dropEffect = "move"; + return false; + } + + _ondragleave(event) + { + this._clearDragOverTreeElementMarker(); + event.preventDefault(); + return false; + } + + _isValidDragSourceOrTarget(treeElement) + { + if (!treeElement) + return false; + + var node = treeElement.representedObject; + if (!(node instanceof WebInspector.DOMNode)) + return false; + + if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE) + return false; + + return true; + } + + _ondrop(event) + { + event.preventDefault(); + + function callback(error, newNodeId) + { + if (error) + return; + + this._updateModifiedNodes(); + var newNode = WebInspector.domTreeManager.nodeForId(newNodeId); + if (newNode) + this.selectDOMNode(newNode, true); + } + + let treeElement = this.treeElementFromEvent(event); + if (this._nodeBeingDragged && treeElement) { + let parentNode = null; + let anchorNode = null; + + if (treeElement._elementCloseTag) { + // Drop onto closing tag -> insert as last child. + parentNode = treeElement.representedObject; + } else { + let dragTargetNode = treeElement.representedObject; + parentNode = dragTargetNode.parentNode; + anchorNode = dragTargetNode; + } + + this._nodeBeingDragged.moveTo(parentNode, anchorNode, callback.bind(this)); + } else { + let className = event.dataTransfer.getData(WebInspector.CSSStyleDetailsSidebarPanel.ToggledClassesDragType); + if (className && treeElement) + treeElement.representedObject.toggleClass(className, true); + } + + delete this._nodeBeingDragged; + } + + _ondragend(event) + { + event.preventDefault(); + this._clearDragOverTreeElementMarker(); + delete this._nodeBeingDragged; + } + + _clearDragOverTreeElementMarker() + { + if (this.dragOverTreeElement) { + let element = this.dragOverTreeElement; + this.dragOverTreeElement = null; + + element.listItemElement.classList.remove("elements-drag-over"); + element.updateSelectionArea(); + } + } + + _updateModifiedNodes() + { + if (this._elementsTreeUpdater) + this._elementsTreeUpdater._updateModifiedNodes(); + } + + _populateContextMenu(contextMenu, domNode) + { + function revealElement() + { + WebInspector.domTreeManager.inspectElement(domNode.id); + } + + function logElement() + { + WebInspector.RemoteObject.resolveNode(domNode, WebInspector.RuntimeManager.ConsoleObjectGroup, function(remoteObject) { + if (!remoteObject) + return; + let text = WebInspector.UIString("Selected Element"); + WebInspector.consoleLogViewController.appendImmediateExecutionWithResult(text, remoteObject, true); + }); + } + + contextMenu.appendSeparator(); + + if (!this._excludeRevealElementContextMenu) + contextMenu.appendItem(WebInspector.UIString("Reveal in DOM Tree"), revealElement); + + if (!domNode.isInUserAgentShadowTree()) + contextMenu.appendItem(WebInspector.UIString("Log Element"), logElement); + } + + _showShadowDOMSettingChanged(event) + { + var nodeToSelect = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null; + while (nodeToSelect) { + if (!nodeToSelect.isInShadowTree()) + break; + nodeToSelect = nodeToSelect.parentNode; + } + + this.children.forEach(function(child) { + child.updateChildren(true); + }); + + if (nodeToSelect) + this.selectDOMNode(nodeToSelect); + } + + _hideElement(event, keyboardShortcut) + { + if (!this.selectedTreeElement || WebInspector.isEditingAnyField()) + return; + + event.preventDefault(); + + var effectiveNode = this.selectedTreeElement.representedObject; + console.assert(effectiveNode); + if (!effectiveNode) + return; + + if (effectiveNode.isPseudoElement()) { + effectiveNode = effectiveNode.parentNode; + console.assert(effectiveNode); + if (!effectiveNode) + return; + } + + if (effectiveNode.nodeType() !== Node.ELEMENT_NODE) + return; + + function resolvedNode(object) + { + if (!object) + return; + + function injectStyleAndToggleClass() + { + var hideElementStyleSheetIdOrClassName = "__WebInspectorHideElement__"; + var styleElement = document.getElementById(hideElementStyleSheetIdOrClassName); + if (!styleElement) { + styleElement = document.createElement("style"); + styleElement.id = hideElementStyleSheetIdOrClassName; + styleElement.textContent = "." + hideElementStyleSheetIdOrClassName + " { visibility: hidden !important; }"; + document.head.appendChild(styleElement); + } + + this.classList.toggle(hideElementStyleSheetIdOrClassName); + } + + object.callFunction(injectStyleAndToggleClass, undefined, false, function(){}); + object.release(); + } + + WebInspector.RemoteObject.resolveNode(effectiveNode, "", resolvedNode); + } +}; + +WebInspector.DOMTreeOutline.Event = { + SelectedNodeChanged: "dom-tree-outline-selected-node-changed" +}; |