2 * Copyright (C) 2007, 2008, 2013, 2015 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
4 * Copyright (C) 2009 Joseph Pecoraro
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 WebInspector.DOMTreeOutline = class DOMTreeOutline extends WebInspector.TreeOutline
33 constructor(omitRootDOMNode, selectEnabled, showInElementsPanelEnabled)
35 var element = document.createElement("ol");
39 element.addEventListener("mousedown", this._onmousedown.bind(this), false);
40 element.addEventListener("mousemove", this._onmousemove.bind(this), false);
41 element.addEventListener("mouseout", this._onmouseout.bind(this), false);
42 element.addEventListener("dragstart", this._ondragstart.bind(this), false);
43 element.addEventListener("dragover", this._ondragover.bind(this), false);
44 element.addEventListener("dragleave", this._ondragleave.bind(this), false);
45 element.addEventListener("drop", this._ondrop.bind(this), false);
46 element.addEventListener("dragend", this._ondragend.bind(this), false);
48 element.classList.add("dom-tree-outline");
49 element.classList.add(WebInspector.SyntaxHighlightedStyleClassName);
51 this._includeRootDOMNode = !omitRootDOMNode;
52 this._selectEnabled = selectEnabled;
53 this._showInElementsPanelEnabled = showInElementsPanelEnabled;
54 this._rootDOMNode = null;
55 this._selectedDOMNode = null;
56 this._eventSupport = new WebInspector.Object();
57 this._editing = false;
59 this._visible = false;
61 this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true);
63 this._hideElementKeyboardShortcut = new WebInspector.KeyboardShortcut(null, "H", this._hideElement.bind(this), this.element);
64 this._hideElementKeyboardShortcut.implicitlyPreventsDefault = false;
66 WebInspector.showShadowDOMSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showShadowDOMSettingChanged, this);
73 this._elementsTreeUpdater = new WebInspector.DOMTreeUpdater(this);
78 if (this._elementsTreeUpdater) {
79 this._elementsTreeUpdater.close();
80 this._elementsTreeUpdater = null;
84 setVisible(visible, omitFocus)
86 this._visible = visible;
90 this._updateModifiedNodes();
91 if (this._selectedDOMNode)
92 this._revealAndSelectNode(this._selectedDOMNode, omitFocus);
95 addEventListener(eventType, listener, thisObject)
97 this._eventSupport.addEventListener(eventType, listener, thisObject);
100 removeEventListener(eventType, listener, thisObject)
102 this._eventSupport.removeEventListener(eventType, listener, thisObject);
107 return this._rootDOMNode;
112 if (this._rootDOMNode === x)
115 this._rootDOMNode = x;
117 this._isXMLMimeType = x && x.isXMLNode();
124 return this._isXMLMimeType;
129 return this._selectedDOMNode;
132 selectDOMNode(node, focus)
134 if (this._selectedDOMNode === node) {
135 this._revealAndSelectNode(node, !focus);
139 this._selectedDOMNode = node;
140 this._revealAndSelectNode(node, !focus);
142 // The _revealAndSelectNode() method might find a different element if there is inlined text,
143 // and the select() call would change the selectedDOMNode and reenter this setter. So to
144 // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNode is the same
145 // node as the one passed in.
146 // Note that _revealAndSelectNode will not do anything for a null node.
147 if (!node || this._selectedDOMNode === node)
148 this._selectedNodeChanged();
153 return this._editing;
158 var selectedNode = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null;
160 this.removeChildren();
162 if (!this.rootDOMNode)
166 if (this._includeRootDOMNode) {
167 treeElement = new WebInspector.DOMTreeElement(this.rootDOMNode);
168 treeElement.selectable = this._selectEnabled;
169 this.appendChild(treeElement);
171 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
172 var node = this.rootDOMNode.firstChild;
174 treeElement = new WebInspector.DOMTreeElement(node);
175 treeElement.selectable = this._selectEnabled;
176 this.appendChild(treeElement);
177 node = node.nextSibling;
182 this._revealAndSelectNode(selectedNode, true);
187 if (!this.selectedTreeElement)
189 var element = this.treeOutline.selectedTreeElement;
190 element.updateSelection();
193 _selectedNodeChanged()
195 this._eventSupport.dispatchEventToListeners(WebInspector.DOMTreeOutline.Event.SelectedNodeChanged);
198 findTreeElement(node)
200 function isAncestorNode(ancestor, node)
202 return ancestor.isAncestor(node);
205 function parentNode(node)
207 return node.parentNode;
210 var treeElement = super.findTreeElement(node, isAncestorNode, parentNode);
211 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
212 // The text node might have been inlined if it was short, so try to find the parent element.
213 treeElement = super.findTreeElement(node.parentNode, isAncestorNode, parentNode);
219 createTreeElementFor(node)
221 var treeElement = this.findTreeElement(node);
224 if (!node.parentNode)
227 treeElement = this.createTreeElementFor(node.parentNode);
228 if (treeElement && treeElement.showChild(node.index))
229 return treeElement.children[node.index];
234 set suppressRevealAndSelect(x)
236 if (this._suppressRevealAndSelect === x)
238 this._suppressRevealAndSelect = x;
241 populateContextMenu(contextMenu, event, treeElement)
243 var tag = event.target.enclosingNodeOrSelfWithClass("html-tag");
244 var textNode = event.target.enclosingNodeOrSelfWithClass("html-text-node");
245 var commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment");
247 var populated = false;
248 if (tag && treeElement._populateTagContextMenu) {
250 contextMenu.appendSeparator();
251 treeElement._populateTagContextMenu(contextMenu, event);
253 } else if (textNode && treeElement._populateTextContextMenu) {
255 contextMenu.appendSeparator();
256 treeElement._populateTextContextMenu(contextMenu, textNode);
258 } else if (commentNode && treeElement._populateNodeContextMenu) {
260 contextMenu.appendSeparator();
261 treeElement._populateNodeContextMenu(contextMenu, textNode);
268 adjustCollapsedRange()
274 _revealAndSelectNode(node, omitFocus)
276 if (!node || this._suppressRevealAndSelect)
279 var treeElement = this.createTreeElementFor(node);
283 treeElement.revealAndSelect(omitFocus);
286 _treeElementFromEvent(event)
288 var scrollContainer = this.element.parentElement;
290 // We choose this X coordinate based on the knowledge that our list
291 // items extend at least to the right edge of the outer <ol> container.
292 // In the no-word-wrap mode the outer <ol> may be wider than the tree container
293 // (and partially hidden), in which case we are left to use only its right boundary.
294 var x = scrollContainer.totalOffsetLeft + scrollContainer.offsetWidth - 36;
298 // Our list items have 1-pixel cracks between them vertically. We avoid
299 // the cracks by checking slightly above and slightly below the mouse
300 // and seeing if we hit the same element each time.
301 var elementUnderMouse = this.treeElementFromPoint(x, y);
302 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
304 if (elementUnderMouse === elementAboveMouse)
305 element = elementUnderMouse;
307 element = this.treeElementFromPoint(x, y + 2);
314 var element = this._treeElementFromEvent(event);
315 if (!element || element.isEventWithinDisclosureTriangle(event)) {
316 event.preventDefault();
325 var element = this._treeElementFromEvent(event);
326 if (element && this._previousHoveredElement === element)
329 if (this._previousHoveredElement) {
330 this._previousHoveredElement.hovered = false;
331 delete this._previousHoveredElement;
335 element.hovered = true;
336 this._previousHoveredElement = element;
338 // Lazily compute tag-specific tooltips.
339 if (element.representedObject && !element.tooltip && element._createTooltipForNode)
340 element._createTooltipForNode();
343 WebInspector.domTreeManager.highlightDOMNode(element ? element.representedObject.id : 0);
348 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
349 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element))
352 if (this._previousHoveredElement) {
353 this._previousHoveredElement.hovered = false;
354 delete this._previousHoveredElement;
357 WebInspector.domTreeManager.hideDOMNodeHighlight();
362 var treeElement = this._treeElementFromEvent(event);
366 if (!this._isValidDragSourceOrTarget(treeElement))
369 if (treeElement.representedObject.nodeName() === "BODY" || treeElement.representedObject.nodeName() === "HEAD")
372 event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent);
373 event.dataTransfer.effectAllowed = "copyMove";
374 this._nodeBeingDragged = treeElement.representedObject;
376 WebInspector.domTreeManager.hideDOMNodeHighlight();
383 if (!this._nodeBeingDragged)
386 var treeElement = this._treeElementFromEvent(event);
387 if (!this._isValidDragSourceOrTarget(treeElement))
390 var node = treeElement.representedObject;
392 if (node === this._nodeBeingDragged)
394 node = node.parentNode;
397 treeElement.updateSelection();
398 treeElement.listItemElement.classList.add("elements-drag-over");
399 this._dragOverTreeElement = treeElement;
400 event.preventDefault();
401 event.dataTransfer.dropEffect = "move";
407 this._clearDragOverTreeElementMarker();
408 event.preventDefault();
412 _isValidDragSourceOrTarget(treeElement)
417 var node = treeElement.representedObject;
418 if (!(node instanceof WebInspector.DOMNode))
421 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
429 event.preventDefault();
431 function callback(error, newNodeId)
436 this._updateModifiedNodes();
437 var newNode = WebInspector.domTreeManager.nodeForId(newNodeId);
439 this.selectDOMNode(newNode, true);
442 var treeElement = this._treeElementFromEvent(event);
443 if (this._nodeBeingDragged && treeElement) {
447 if (treeElement._elementCloseTag) {
448 // Drop onto closing tag -> insert as last child.
449 parentNode = treeElement.representedObject;
451 var dragTargetNode = treeElement.representedObject;
452 parentNode = dragTargetNode.parentNode;
453 anchorNode = dragTargetNode;
456 this._nodeBeingDragged.moveTo(parentNode, anchorNode, callback.bind(this));
459 delete this._nodeBeingDragged;
464 event.preventDefault();
465 this._clearDragOverTreeElementMarker();
466 delete this._nodeBeingDragged;
469 _clearDragOverTreeElementMarker()
471 if (this._dragOverTreeElement) {
472 this._dragOverTreeElement.updateSelection();
473 this._dragOverTreeElement.listItemElement.classList.remove("elements-drag-over");
474 delete this._dragOverTreeElement;
478 _contextMenuEventFired(event)
480 var treeElement = this._treeElementFromEvent(event);
484 var contextMenu = new WebInspector.ContextMenu(event);
485 this.populateContextMenu(contextMenu, event, treeElement);
489 _updateModifiedNodes()
491 if (this._elementsTreeUpdater)
492 this._elementsTreeUpdater._updateModifiedNodes();
495 _populateContextMenu(contextMenu, domNode)
497 if (!this._showInElementsPanelEnabled)
500 function revealElement()
502 WebInspector.domTreeManager.inspectElement(domNode.id);
505 contextMenu.appendSeparator();
506 contextMenu.appendItem(WebInspector.UIString("Reveal in DOM Tree"), revealElement);
509 _showShadowDOMSettingChanged(event)
511 var nodeToSelect = this.selectedTreeElement ? this.selectedTreeElement.representedObject : null;
512 while (nodeToSelect) {
513 if (!nodeToSelect.isInShadowTree())
515 nodeToSelect = nodeToSelect.parentNode;
518 this.children.forEach(function(child) {
519 child.updateChildren(true);
523 this.selectDOMNode(nodeToSelect);
526 _hideElement(event, keyboardShortcut)
528 if (!this.selectedTreeElement || WebInspector.isEditingAnyField())
531 event.preventDefault();
533 var selectedNode = this.selectedTreeElement.representedObject;
534 console.assert(selectedNode);
538 if (selectedNode.nodeType() !== Node.ELEMENT_NODE)
541 if (this._togglePending)
543 this._togglePending = true;
545 function toggleProperties()
547 nodeStyles.removeEventListener(WebInspector.DOMNodeStyles.Event.Refreshed, toggleProperties, this);
549 var opacityProperty = nodeStyles.inlineStyle.propertyForName("opacity");
550 opacityProperty.value = "0";
551 opacityProperty.important = true;
553 var pointerEventsProperty = nodeStyles.inlineStyle.propertyForName("pointer-events");
554 pointerEventsProperty.value = "none";
555 pointerEventsProperty.important = true;
557 if (opacityProperty.enabled && pointerEventsProperty.enabled) {
558 opacityProperty.remove();
559 pointerEventsProperty.remove();
561 opacityProperty.add();
562 pointerEventsProperty.add();
565 delete this._togglePending;
568 var nodeStyles = WebInspector.cssStyleManager.stylesForNode(selectedNode);
569 if (nodeStyles.needsRefresh) {
570 nodeStyles.addEventListener(WebInspector.DOMNodeStyles.Event.Refreshed, toggleProperties, this);
571 nodeStyles.refresh();
573 toggleProperties.call(this);
577 WebInspector.DOMTreeOutline.Event = {
578 SelectedNodeChanged: "dom-tree-outline-selected-node-changed"