/* * Copyright (C) 2007, 2008, 2013, 2015 Apple Inc. All rights reserved. * Copyright (C) 2008 Matt Lilek * 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.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this)); 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() { var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null; if (!this.rootDOMNode) return; 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() { if (!this.selectedTreeElement) return; var element = this.treeOutline.selectedTreeElement; element.updateSelection(); } _selectedNodeChanged() { this.dispatchEventToListeners(WebInspector.DOMTreeOutline.Event.SelectedNodeChanged); } findTreeElement(node) { function isAncestorNode(ancestor, node) { return ancestor.isAncestor(node); } function parentNode(node) { return node.parentNode; } var 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) { var tag = event.target.enclosingNodeOrSelfWithClass("html-tag"); var textNode = event.target.enclosingNodeOrSelfWithClass("html-text-node"); var commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment"); var populated = false; if (tag && treeElement._populateTagContextMenu) { if (populated) contextMenu.appendSeparator(); treeElement._populateTagContextMenu(contextMenu, event); populated = true; } else if (textNode && treeElement._populateTextContextMenu) { if (populated) contextMenu.appendSeparator(); treeElement._populateTextContextMenu(contextMenu, textNode); populated = true; } else if (commentNode && treeElement._populateNodeContextMenu) { if (populated) contextMenu.appendSeparator(); treeElement._populateNodeContextMenu(contextMenu); populated = true; } return populated; } adjustCollapsedRange() { } // Private _revealAndSelectNode(node, omitFocus) { if (!node || this._suppressRevealAndSelect) return; var treeElement = this.createTreeElementFor(node); if (!treeElement) return; treeElement.revealAndSelect(omitFocus); } _treeElementFromEvent(event) { var 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
    container. // In the no-word-wrap mode the outer
      may be wider than the tree container // (and partially hidden), in which case we are left to use only its right boundary. var x = scrollContainer.totalOffsetLeft + scrollContainer.offsetWidth - 36; var 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. var elementUnderMouse = this.treeElementFromPoint(x, y); var elementAboveMouse = this.treeElementFromPoint(x, y - 2); var element; if (elementUnderMouse === elementAboveMouse) element = elementUnderMouse; else element = this.treeElementFromPoint(x, y + 2); return element; } _onmousedown(event) { var element = this._treeElementFromEvent(event); if (!element || element.isEventWithinDisclosureTriangle(event)) { event.preventDefault(); return; } element.select(); } _onmousemove(event) { var element = this._treeElementFromEvent(event); if (element && this._previousHoveredElement === element) return; if (this._previousHoveredElement) { this._previousHoveredElement.hovered = false; delete this._previousHoveredElement; } 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; delete this._previousHoveredElement; } WebInspector.domTreeManager.hideDOMNodeHighlight(); } _ondragstart(event) { var 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 (!this._nodeBeingDragged) return false; var treeElement = this._treeElementFromEvent(event); if (!this._isValidDragSourceOrTarget(treeElement)) return false; var node = treeElement.representedObject; while (node) { if (node === this._nodeBeingDragged) return false; node = node.parentNode; } treeElement.updateSelection(); treeElement.listItemElement.classList.add("elements-drag-over"); this._dragOverTreeElement = treeElement; 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); } var treeElement = this._treeElementFromEvent(event); if (this._nodeBeingDragged && treeElement) { var parentNode; var anchorNode; if (treeElement._elementCloseTag) { // Drop onto closing tag -> insert as last child. parentNode = treeElement.representedObject; } else { var dragTargetNode = treeElement.representedObject; parentNode = dragTargetNode.parentNode; anchorNode = dragTargetNode; } this._nodeBeingDragged.moveTo(parentNode, anchorNode, callback.bind(this)); } delete this._nodeBeingDragged; } _ondragend(event) { event.preventDefault(); this._clearDragOverTreeElementMarker(); delete this._nodeBeingDragged; } _clearDragOverTreeElementMarker() { if (this._dragOverTreeElement) { this._dragOverTreeElement.updateSelection(); this._dragOverTreeElement.listItemElement.classList.remove("elements-drag-over"); delete this._dragOverTreeElement; } } _contextMenuEventFired(event) { let treeElement = this._treeElementFromEvent(event); if (!treeElement) return; let contextMenu = WebInspector.ContextMenu.createFromEvent(event); this.populateContextMenu(contextMenu, event, treeElement); } _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); 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" };