UNPKG

strong-arc

Version:

A visual suite for the StrongLoop API Platform

1,563 lines (1,339 loc) 103 kB
/* * Copyright (C) 2007, 2008 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 Computer, 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. */ /** * @constructor * @extends {TreeOutline} * @param {!WebInspector.Target} target * @param {boolean=} omitRootDOMNode * @param {boolean=} selectEnabled * @param {function(!WebInspector.ContextMenu, !WebInspector.DOMNode)=} contextMenuCallback * @param {function(!WebInspector.DOMNode, string, boolean)=} setPseudoClassCallback */ WebInspector.ElementsTreeOutline = function(target, omitRootDOMNode, selectEnabled, contextMenuCallback, setPseudoClassCallback) { this._target = target; this._domModel = target.domModel; this.element = document.createElement("ol"); this.element.className = "elements-tree-outline"; 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.addEventListener("keydown", this._onkeydown.bind(this), false); this.element.addEventListener("webkitAnimationEnd", this._onAnimationEnd.bind(this), false); TreeOutline.call(this, this.element); this._includeRootDOMNode = !omitRootDOMNode; this._selectEnabled = selectEnabled; /** @type {?WebInspector.DOMNode} */ this._rootDOMNode = null; /** @type {?WebInspector.DOMNode} */ this._selectedDOMNode = null; this._eventSupport = new WebInspector.Object(); this._visible = false; this._pickNodeMode = false; this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), true); this._contextMenuCallback = contextMenuCallback; this._setPseudoClassCallback = setPseudoClassCallback; this._createNodeDecorators(); } /** @typedef {{node: !WebInspector.DOMNode, isCut: boolean}} */ WebInspector.ElementsTreeOutline.ClipboardData; /** * @enum {string} */ WebInspector.ElementsTreeOutline.Events = { NodePicked: "NodePicked", SelectedNodeChanged: "SelectedNodeChanged", ElementsTreeUpdated: "ElementsTreeUpdated" } /** * @const * @type {!Object.<string, string>} */ WebInspector.ElementsTreeOutline.MappedCharToEntity = { "\u00a0": "nbsp", "\u2002": "ensp", "\u2003": "emsp", "\u2009": "thinsp", "\u200a": "#8202", // Hairspace "\u200b": "#8203", // ZWSP "\u200c": "zwnj", "\u200d": "zwj", "\u200e": "lrm", "\u200f": "rlm", "\u202a": "#8234", // LRE "\u202b": "#8235", // RLE "\u202c": "#8236", // PDF "\u202d": "#8237", // LRO "\u202e": "#8238" // RLO } WebInspector.ElementsTreeOutline.prototype = { /** * @param {!Event} event */ _onAnimationEnd: function(event) { event.target.classList.remove("elements-tree-element-pick-node-1"); event.target.classList.remove("elements-tree-element-pick-node-2"); }, /** * @param {boolean} value */ setPickNodeMode: function(value) { this._pickNodeMode = value; this.element.classList.toggle("pick-node-mode", value); }, /** * @param {!Element} element * @param {?WebInspector.DOMNode} node */ _handlePickNode: function(element, node) { if (!this._pickNodeMode) return true; this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.NodePicked, node); var hasRunningAnimation = element.classList.contains("elements-tree-element-pick-node-1") || element.classList.contains("elements-tree-element-pick-node-2"); element.classList.toggle("elements-tree-element-pick-node-1"); if (hasRunningAnimation) element.classList.toggle("elements-tree-element-pick-node-2"); return false; }, /** * @return {!WebInspector.Target} */ target: function() { return this._target; }, /** * @return {!WebInspector.DOMModel} */ domModel: function() { return this._domModel; }, /** * @param {number} width */ setVisibleWidth: function(width) { this._visibleWidth = width; if (this._multilineEditing) this._multilineEditing.setWidth(this._visibleWidth); }, _createNodeDecorators: function() { this._nodeDecorators = []; this._nodeDecorators.push(new WebInspector.ElementsTreeOutline.PseudoStateDecorator()); }, wireToDOMModel: function() { this._elementsTreeUpdater = new WebInspector.ElementsTreeUpdater(this._target.domModel, this); }, unwireFromDOMModel: function() { if (this._elementsTreeUpdater) this._elementsTreeUpdater.dispose(); }, /** * @param {?WebInspector.ElementsTreeOutline.ClipboardData} data */ _setClipboardData: function(data) { if (this._clipboardNodeData) { var treeElement = this.findTreeElement(this._clipboardNodeData.node); if (treeElement) treeElement.setInClipboard(false); delete this._clipboardNodeData; } if (data) { var treeElement = this.findTreeElement(data.node); if (treeElement) treeElement.setInClipboard(true); this._clipboardNodeData = data; } }, /** * @param {!WebInspector.DOMNode} removedNode */ _resetClipboardIfNeeded: function(removedNode) { if (this._clipboardNodeData && this._clipboardNodeData.node === removedNode) this._setClipboardData(null); }, /** * @param {boolean} isCut * @param {!Event} event */ handleCopyOrCutKeyboardEvent: function(isCut, event) { this._setClipboardData(null); // Don't prevent the normal copy if the user has a selection. if (!window.getSelection().isCollapsed) return; // Do not interfere with text editing. var currentFocusElement = WebInspector.currentFocusElement(); if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement)) return; var targetNode = this.selectedDOMNode(); if (!targetNode) return; event.clipboardData.clearData(); event.preventDefault(); this._performCopyOrCut(isCut, targetNode); }, /** * @param {boolean} isCut * @param {?WebInspector.DOMNode} node */ _performCopyOrCut: function(isCut, node) { if (isCut && (node.isShadowRoot() || node.ancestorUserAgentShadowRoot())) return; node.copyNode(); this._setClipboardData({ node: node, isCut: isCut }); }, /** * @param {!WebInspector.DOMNode} targetNode * @return {boolean} */ _canPaste: function(targetNode) { if (targetNode.isShadowRoot() || targetNode.ancestorUserAgentShadowRoot()) return false; if (!this._clipboardNodeData) return false; var node = this._clipboardNodeData.node; if (this._clipboardNodeData.isCut && (node === targetNode || node.isAncestor(targetNode))) return false; if (targetNode.target() !== node.target()) return false; return true; }, /** * @param {!WebInspector.DOMNode} targetNode */ _pasteNode: function(targetNode) { if (this._canPaste(targetNode)) this._performPaste(targetNode); }, /** * @param {!Event} event */ handlePasteKeyboardEvent: function(event) { // Do not interfere with text editing. var currentFocusElement = WebInspector.currentFocusElement(); if (currentFocusElement && WebInspector.isBeingEdited(currentFocusElement)) return; var targetNode = this.selectedDOMNode(); if (!targetNode || !this._canPaste(targetNode)) return; event.preventDefault(); this._performPaste(targetNode); }, /** * @param {!WebInspector.DOMNode} targetNode */ _performPaste: function(targetNode) { if (this._clipboardNodeData.isCut) { this._clipboardNodeData.node.moveTo(targetNode, null, expandCallback.bind(this)); this._setClipboardData(null); } else { this._clipboardNodeData.node.copyTo(targetNode, null, expandCallback.bind(this)); } /** * @param {?Protocol.Error} error * @param {!DOMAgent.NodeId} nodeId * @this {WebInspector.ElementsTreeOutline} */ function expandCallback(error, nodeId) { if (error) return; var pastedNode = this._domModel.nodeForId(nodeId); if (!pastedNode) return; this.selectDOMNode(pastedNode); } }, /** * @param {boolean} visible */ setVisible: function(visible) { this._visible = visible; if (!this._visible) return; this._updateModifiedNodes(); if (this._selectedDOMNode) this._revealAndSelectNode(this._selectedDOMNode, false); }, addEventListener: function(eventType, listener, thisObject) { this._eventSupport.addEventListener(eventType, listener, thisObject); }, removeEventListener: function(eventType, listener, thisObject) { this._eventSupport.removeEventListener(eventType, listener, thisObject); }, 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; }, /** * @return {?WebInspector.DOMNode} */ selectedDOMNode: function() { return this._selectedDOMNode; }, /** * @param {?WebInspector.DOMNode} node * @param {boolean=} focus */ selectDOMNode: function(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. if (this._selectedDOMNode === node) this._selectedNodeChanged(); }, /** * @return {boolean} */ editing: function() { var node = this.selectedDOMNode(); if (!node) return false; var treeElement = this.findTreeElement(node); if (!treeElement) return false; return treeElement._editing || false; }, update: function() { var selectedNode = this.selectedTreeElement ? this.selectedTreeElement._node : null; this.removeChildren(); if (!this.rootDOMNode) return; var treeElement; if (this._includeRootDOMNode) { treeElement = new WebInspector.ElementsTreeElement(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.ElementsTreeElement(node); treeElement.selectable = this._selectEnabled; this.appendChild(treeElement); node = node.nextSibling; } } if (selectedNode) this._revealAndSelectNode(selectedNode, true); }, updateSelection: function() { if (!this.selectedTreeElement) return; var element = this.treeOutline.selectedTreeElement; element.updateSelection(); }, /** * @param {!WebInspector.DOMNode} node */ updateOpenCloseTags: function(node) { var treeElement = this.findTreeElement(node); if (treeElement) treeElement.updateTitle(); var children = treeElement.children; var closingTagElement = children[children.length - 1]; if (closingTagElement && closingTagElement._elementCloseTag) closingTagElement.updateTitle(); }, _selectedNodeChanged: function() { this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, this._selectedDOMNode); }, /** * @param {!Array.<!WebInspector.DOMNode>} nodes */ _fireElementsTreeUpdated: function(nodes) { this._eventSupport.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.ElementsTreeUpdated, nodes); }, /** * @param {!WebInspector.DOMNode} node * @return {?TreeElement} */ findTreeElement: function(node) { function parentNode(node) { return node.parentNode; } var treeElement = TreeOutline.prototype.findTreeElement.call(this, node, 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 = TreeOutline.prototype.findTreeElement.call(this, node.parentNode, parentNode); } return treeElement; }, /** * @param {!WebInspector.DOMNode} node * @return {?TreeElement} */ createTreeElementFor: function(node) { var treeElement = this.findTreeElement(node); if (treeElement) return treeElement; if (!node.parentNode) return null; treeElement = this.createTreeElementFor(node.parentNode); return treeElement ? treeElement._showChild(node) : null; }, set suppressRevealAndSelect(x) { if (this._suppressRevealAndSelect === x) return; this._suppressRevealAndSelect = x; }, /** * @param {?WebInspector.DOMNode} node * @param {boolean} omitFocus */ _revealAndSelectNode: function(node, omitFocus) { if (this._suppressRevealAndSelect) return; if (!this._includeRootDOMNode && node === this.rootDOMNode && this.rootDOMNode) node = this.rootDOMNode.firstChild; if (!node) return; var treeElement = this.createTreeElementFor(node); if (!treeElement) return; treeElement.revealAndSelect(omitFocus); }, /** * @return {?TreeElement} */ _treeElementFromEvent: function(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 <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. 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: function(event) { var element = this._treeElementFromEvent(event); if (!element || element.isEventWithinDisclosureTriangle(event)) return; element.select(); }, _onmousemove: function(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; } if (element && element._node) this._domModel.highlightDOMNodeWithConfig(element._node.id, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) }); else this._domModel.hideDOMNodeHighlight(); }, _onmouseout: function(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; } this._domModel.hideDOMNodeHighlight(); }, _ondragstart: function(event) { if (!window.getSelection().isCollapsed) return false; if (event.target.nodeName === "A") return false; var treeElement = this._treeElementFromEvent(event); if (!treeElement) return false; if (!this._isValidDragSourceOrTarget(treeElement)) return false; if (treeElement._node.nodeName() === "BODY" || treeElement._node.nodeName() === "HEAD") return false; event.dataTransfer.setData("text/plain", treeElement.listItemElement.textContent.replace(/\u200b/g, "")); event.dataTransfer.effectAllowed = "copyMove"; this._treeElementBeingDragged = treeElement; this._domModel.hideDOMNodeHighlight(); return true; }, _ondragover: function(event) { if (!this._treeElementBeingDragged) return false; var treeElement = this._treeElementFromEvent(event); if (!this._isValidDragSourceOrTarget(treeElement)) return false; var node = treeElement._node; while (node) { if (node === this._treeElementBeingDragged._node) 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: function(event) { this._clearDragOverTreeElementMarker(); event.preventDefault(); return false; }, /** * @param {?TreeElement} treeElement * @return {boolean} */ _isValidDragSourceOrTarget: function(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: function(event) { event.preventDefault(); var treeElement = this._treeElementFromEvent(event); if (treeElement) this._doMove(treeElement); }, /** * @param {!TreeElement} treeElement */ _doMove: function(treeElement) { if (!this._treeElementBeingDragged) return; var parentNode; var anchorNode; if (treeElement._elementCloseTag) { // Drop onto closing tag -> insert as last child. parentNode = treeElement._node; } else { var dragTargetNode = treeElement._node; parentNode = dragTargetNode.parentNode; anchorNode = dragTargetNode; } var wasExpanded = this._treeElementBeingDragged.expanded; this._treeElementBeingDragged._node.moveTo(parentNode, anchorNode, this._selectNodeAfterEdit.bind(this, wasExpanded)); delete this._treeElementBeingDragged; }, _ondragend: function(event) { event.preventDefault(); this._clearDragOverTreeElementMarker(); delete this._treeElementBeingDragged; }, _clearDragOverTreeElementMarker: function() { if (this._dragOverTreeElement) { this._dragOverTreeElement.updateSelection(); this._dragOverTreeElement.listItemElement.classList.remove("elements-drag-over"); delete this._dragOverTreeElement; } }, /** * @param {!Event} event */ _onkeydown: function(event) { var keyboardEvent = /** @type {!KeyboardEvent} */ (event); var node = /** @type {!WebInspector.DOMNode} */ (this.selectedDOMNode()); console.assert(node); var treeElement = this.getCachedTreeElement(node); if (!treeElement) return; if (!treeElement._editing && WebInspector.KeyboardShortcut.hasNoModifiers(keyboardEvent) && keyboardEvent.keyCode === WebInspector.KeyboardShortcut.Keys.H.code) { this._toggleHideShortcut(node); event.consume(true); return; } }, _contextMenuEventFired: function(event) { var treeElement = this._treeElementFromEvent(event); if (!treeElement || treeElement.treeOutline !== this) return; var contextMenu = new WebInspector.ContextMenu(event); contextMenu.appendApplicableItems(treeElement._node); contextMenu.show(); }, populateContextMenu: function(contextMenu, event) { var treeElement = this._treeElementFromEvent(event); if (!treeElement || treeElement.treeOutline !== this) return; var isPseudoElement = !!treeElement._node.pseudoType(); var isTag = treeElement._node.nodeType() === Node.ELEMENT_NODE && !isPseudoElement; var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-text-node"); if (textNode && textNode.classList.contains("bogus")) textNode = null; var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-comment"); contextMenu.appendApplicableItems(event.target); if (textNode) { contextMenu.appendSeparator(); treeElement._populateTextContextMenu(contextMenu, textNode); } else if (isTag) { contextMenu.appendSeparator(); treeElement._populateTagContextMenu(contextMenu, event); } else if (commentNode) { contextMenu.appendSeparator(); treeElement._populateNodeContextMenu(contextMenu); } else if (isPseudoElement) { treeElement._populateScrollIntoView(contextMenu); } else if (treeElement._node.isShadowRoot()) { this.treeOutline._populateContextMenu(contextMenu, treeElement._node); } }, _updateModifiedNodes: function() { if (this._elementsTreeUpdater) this._elementsTreeUpdater._updateModifiedNodes(); }, _populateContextMenu: function(contextMenu, node) { if (this._contextMenuCallback) this._contextMenuCallback(contextMenu, node); }, handleShortcut: function(event) { var node = this.selectedDOMNode(); var treeElement = this.getCachedTreeElement(node); if (!node || !treeElement) return; if (event.keyIdentifier === "F2" && treeElement.hasEditableNode()) { this._toggleEditAsHTML(node); event.handled = true; return; } if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && node.parentNode) { if (event.keyIdentifier === "Up" && node.previousSibling) { node.moveTo(node.parentNode, node.previousSibling, this._selectNodeAfterEdit.bind(this, treeElement.expanded)); event.handled = true; return; } if (event.keyIdentifier === "Down" && node.nextSibling) { node.moveTo(node.parentNode, node.nextSibling.nextSibling, this._selectNodeAfterEdit.bind(this, treeElement.expanded)); event.handled = true; return; } } }, /** * @param {!WebInspector.DOMNode} node */ _toggleEditAsHTML: function(node) { var treeElement = this.getCachedTreeElement(node); if (!treeElement) return; if (treeElement._editing && treeElement._htmlEditElement && WebInspector.isBeingEdited(treeElement._htmlEditElement)) treeElement._editing.commit(); else treeElement._editAsHTML(); }, /** * @param {boolean} wasExpanded * @param {?Protocol.Error} error * @param {!DOMAgent.NodeId=} nodeId */ _selectNodeAfterEdit: function(wasExpanded, error, nodeId) { if (error) return; // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date. this._updateModifiedNodes(); var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null; if (!newNode) return; this.selectDOMNode(newNode, true); var newTreeItem = this.findTreeElement(newNode); if (wasExpanded) { if (newTreeItem) newTreeItem.expand(); } return newTreeItem; }, /** * Runs a script on the node's remote object that toggles a class name on * the node and injects a stylesheet into the head of the node's document * containing a rule to set "visibility: hidden" on the class and all it's * ancestors. * * @param {!WebInspector.DOMNode} node * @param {function(?WebInspector.RemoteObject, boolean=)=} userCallback */ _toggleHideShortcut: function(node, userCallback) { var pseudoType = node.pseudoType(); var effectiveNode = pseudoType ? node.parentNode : node; if (!effectiveNode) return; function resolvedNode(object) { if (!object) return; /** * @param {?string} pseudoType * @suppressReceiverCheck * @this {!Element} */ function toggleClassAndInjectStyleRule(pseudoType) { const classNamePrefix = "__web-inspector-hide"; const classNameSuffix = "-shortcut__"; const styleTagId = "__web-inspector-hide-shortcut-style__"; const styleRules = ".__web-inspector-hide-shortcut__, .__web-inspector-hide-shortcut__ * { visibility: hidden !important; } .__web-inspector-hidebefore-shortcut__::before { visibility: hidden !important; } .__web-inspector-hideafter-shortcut__::after { visibility: hidden !important; }"; var className = classNamePrefix + (pseudoType || "") + classNameSuffix; this.classList.toggle(className); var style = document.head.querySelector("style#" + styleTagId); if (style) return; style = document.createElement("style"); style.id = styleTagId; style.type = "text/css"; style.textContent = styleRules; document.head.appendChild(style); } object.callFunction(toggleClassAndInjectStyleRule, [{ value: pseudoType }], userCallback); object.release(); } effectiveNode.resolveToObject("", resolvedNode); }, __proto__: TreeOutline.prototype } /** * @interface */ WebInspector.ElementsTreeOutline.ElementDecorator = function() { } WebInspector.ElementsTreeOutline.ElementDecorator.prototype = { /** * @param {!WebInspector.DOMNode} node * @return {?string} */ decorate: function(node) { }, /** * @param {!WebInspector.DOMNode} node * @return {?string} */ decorateAncestor: function(node) { } } /** * @constructor * @implements {WebInspector.ElementsTreeOutline.ElementDecorator} */ WebInspector.ElementsTreeOutline.PseudoStateDecorator = function() { WebInspector.ElementsTreeOutline.ElementDecorator.call(this); } WebInspector.ElementsTreeOutline.PseudoStateDecorator.prototype = { /** * @param {!WebInspector.DOMNode} node * @return {?string} */ decorate: function(node) { if (node.nodeType() !== Node.ELEMENT_NODE) return null; var propertyValue = node.getUserProperty(WebInspector.CSSStyleModel.PseudoStatePropertyName); if (!propertyValue) return null; return WebInspector.UIString("Element state: %s", ":" + propertyValue.join(", :")); }, /** * @param {!WebInspector.DOMNode} node * @return {?string} */ decorateAncestor: function(node) { if (node.nodeType() !== Node.ELEMENT_NODE) return null; var descendantCount = node.descendantUserPropertyCount(WebInspector.CSSStyleModel.PseudoStatePropertyName); if (!descendantCount) return null; if (descendantCount === 1) return WebInspector.UIString("%d descendant with forced state", descendantCount); return WebInspector.UIString("%d descendants with forced state", descendantCount); } } /** * @constructor * @extends {TreeElement} * @param {!WebInspector.DOMNode} node * @param {boolean=} elementCloseTag */ WebInspector.ElementsTreeElement = function(node, elementCloseTag) { // The title will be updated in onattach. TreeElement.call(this, "", node); this._node = node; this._elementCloseTag = elementCloseTag; this._updateHasChildren(); if (this._node.nodeType() == Node.ELEMENT_NODE && !elementCloseTag) this._canAddAttributes = true; this._searchQuery = null; this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildrenLimit; } WebInspector.ElementsTreeElement.InitialChildrenLimit = 500; // A union of HTML4 and HTML5-Draft elements that explicitly // or implicitly (for HTML5) forbid the closing tag. // FIXME: Revise once HTML5 Final is published. WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = [ "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "frame", "hr", "img", "input", "keygen", "link", "meta", "param", "source" ].keySet(); // These tags we do not allow editing their tag name. WebInspector.ElementsTreeElement.EditTagBlacklist = [ "html", "head", "body" ].keySet(); WebInspector.ElementsTreeElement.prototype = { highlightSearchResults: function(searchQuery) { if (this._searchQuery !== searchQuery) { this._updateSearchHighlight(false); delete this._highlightResult; // A new search query. } this._searchQuery = searchQuery; this._searchHighlightsVisible = true; this.updateTitle(true); }, hideSearchHighlights: function() { delete this._searchHighlightsVisible; this._updateSearchHighlight(false); }, _updateSearchHighlight: function(show) { if (!this._highlightResult) return; function updateEntryShow(entry) { switch (entry.type) { case "added": entry.parent.insertBefore(entry.node, entry.nextSibling); break; case "changed": entry.node.textContent = entry.newText; break; } } function updateEntryHide(entry) { switch (entry.type) { case "added": entry.node.remove(); break; case "changed": entry.node.textContent = entry.oldText; break; } } // Preserve the semantic of node by following the order of updates for hide and show. if (show) { for (var i = 0, size = this._highlightResult.length; i < size; ++i) updateEntryShow(this._highlightResult[i]); } else { for (var i = (this._highlightResult.length - 1); i >= 0; --i) updateEntryHide(this._highlightResult[i]); } }, /** * @param {boolean} inClipboard */ setInClipboard: function(inClipboard) { if (this._inClipboard === inClipboard) return; this._inClipboard = inClipboard; this.listItemElement.classList.toggle("in-clipboard", inClipboard); }, get hovered() { return this._hovered; }, set hovered(x) { if (this._hovered === x) return; this._hovered = x; if (this.listItemElement) { if (x) { this.updateSelection(); this.listItemElement.classList.add("hovered"); } else { this.listItemElement.classList.remove("hovered"); } } }, get expandedChildrenLimit() { return this._expandedChildrenLimit; }, set expandedChildrenLimit(x) { if (this._expandedChildrenLimit === x) return; this._expandedChildrenLimit = x; if (this.treeOutline && !this._updateChildrenInProgress) this._updateChildren(true); }, get expandedChildCount() { var count = this.children.length; if (count && this.children[count - 1]._elementCloseTag) count--; if (count && this.children[count - 1].expandAllButton) count--; return count; }, /** * @param {!WebInspector.DOMNode} child * @return {?WebInspector.ElementsTreeElement} */ _showChild: function(child) { if (this._elementCloseTag) return null; var index = this._visibleChildren().indexOf(child); if (index === -1) return null; if (index >= this.expandedChildrenLimit) { this._expandedChildrenLimit = index + 1; this._updateChildren(true); } // Whether index-th child is visible in the children tree return this.expandedChildCount > index ? this.children[index] : null; }, updateSelection: function() { var listItemElement = this.listItemElement; if (!listItemElement) return; if (!this._readyToUpdateSelection) { if (document.body.offsetWidth > 0) this._readyToUpdateSelection = true; else { // The stylesheet hasn't loaded yet or the window is closed, // so we can't calculate what we need. Return early. return; } } if (!this.selectionElement) { this.selectionElement = document.createElement("div"); this.selectionElement.className = "selection selected"; listItemElement.insertBefore(this.selectionElement, listItemElement.firstChild); } this.selectionElement.style.height = listItemElement.offsetHeight + "px"; }, onattach: function() { if (this._hovered) { this.updateSelection(); this.listItemElement.classList.add("hovered"); } this.updateTitle(); this._preventFollowingLinksOnDoubleClick(); this.listItemElement.draggable = true; }, _preventFollowingLinksOnDoubleClick: function() { var links = this.listItemElement.querySelectorAll("li .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-link, li .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link"); if (!links) return; for (var i = 0; i < links.length; ++i) links[i].preventFollowOnDoubleClick = true; }, onpopulate: function() { this.populated = true; if (this.children.length || !this.hasChildren) return; this.updateChildren(); }, /** * @param {boolean=} fullRefresh */ updateChildren: function(fullRefresh) { if (!this.hasChildren) return; console.assert(!this._elementCloseTag); this._node.getChildNodes(this._updateChildren.bind(this, fullRefresh)); }, /** * @param {!WebInspector.DOMNode} child * @param {number} index * @param {boolean=} closingTag * @return {!WebInspector.ElementsTreeElement} */ insertChildElement: function(child, index, closingTag) { var newElement = new WebInspector.ElementsTreeElement(child, closingTag); newElement.selectable = this.treeOutline._selectEnabled; this.insertChild(newElement, index); return newElement; }, moveChild: function(child, targetIndex) { var wasSelected = child.selected; this.removeChild(child); this.insertChild(child, targetIndex); if (wasSelected) child.select(); }, /** * @param {boolean=} fullRefresh */ _updateChildren: function(fullRefresh) { if (this._updateChildrenInProgress || !this.treeOutline._visible) return; this._updateChildrenInProgress = true; var selectedNode = this.treeOutline.selectedDOMNode(); var originalScrollTop = 0; if (fullRefresh) { var treeOutlineContainerElement = this.treeOutline.element.parentNode; originalScrollTop = treeOutlineContainerElement.scrollTop; var selectedTreeElement = this.treeOutline.selectedTreeElement; if (selectedTreeElement && selectedTreeElement.hasAncestor(this)) this.select(); this.removeChildren(); } /** * @this {WebInspector.ElementsTreeElement} * @return {?WebInspector.ElementsTreeElement} */ function updateChildrenOfNode() { var treeOutline = this.treeOutline; var visibleChildren = this._visibleChildren(); var treeChildIndex = 0; var elementToSelect = null; for (var i = 0; i < visibleChildren.length; ++i) { var child = visibleChildren[i]; var currentTreeElement = this.children[treeChildIndex]; if (!currentTreeElement || currentTreeElement._node !== child) { // Find any existing element that is later in the children list. var existingTreeElement = null; for (var j = (treeChildIndex + 1), size = this.expandedChildCount; j < size; ++j) { if (this.children[j]._node === child) { existingTreeElement = this.children[j]; break; } } if (existingTreeElement && existingTreeElement.parent === this) { // If an existing element was found and it has the same parent, just move it. this.moveChild(existingTreeElement, treeChildIndex); } else { // No existing element found, insert a new element. if (treeChildIndex < this.expandedChildrenLimit) { var newElement = this.insertChildElement(child, treeChildIndex); if (child === selectedNode) elementToSelect = newElement; if (this.expandedChildCount > this.expandedChildrenLimit) this.expandedChildrenLimit++; } } } ++treeChildIndex; } return elementToSelect; } // Remove any tree elements that no longer have this node (or this node's contentDocument) as their parent. for (var i = (this.children.length - 1); i >= 0; --i) { var currentChild = this.children[i]; var currentNode = currentChild._node; if (!currentNode) continue; var currentParentNode = currentNode.parentNode; if (currentParentNode === this._node) continue; var selectedTreeElement = this.treeOutline.selectedTreeElement; if (selectedTreeElement && (selectedTreeElement === currentChild || selectedTreeElement.hasAncestor(currentChild))) this.select(); this.removeChildAtIndex(i); } var elementToSelect = updateChildrenOfNode.call(this); this.updateTitle(); this._adjustCollapsedRange(); var lastChild = this.children[this.children.length - 1]; if (this._node.nodeType() === Node.ELEMENT_NODE && this.hasChildren) this.insertChildElement(this._node, this.children.length, true); // We want to restore the original selection and tree scroll position after a full refresh, if possible. if (fullRefresh && elementToSelect) { elementToSelect.select(); if (treeOutlineContainerElement && originalScrollTop <= treeOutlineContainerElement.scrollHeight) treeOutlineContainerElement.scrollTop = originalScrollTop; } delete this._updateChildrenInProgress; }, _adjustCollapsedRange: function() { var visibleChildren = this._visibleChildren(); // Ensure precondition: only the tree elements for node children are found in the tree // (not the Expand All button or the closing tag). if (this.expandAllButtonElement && this.expandAllButtonElement.__treeElement.parent) this.removeChild(this.expandAllButtonElement.__treeElement); const childNodeCount = visibleChildren.length; // In case some nodes from the expanded range were removed, pull some nodes from the collapsed range into the expanded range at the bottom. for (var i = this.expandedChildCount, limit = Math.min(this.expandedChildrenLimit, childNodeCount); i < limit; ++i) this.insertChildElement(visibleChildren[i], i); const expandedChildCount = this.expandedChildCount; if (childNodeCount > this.expandedChildCount) { var targetButtonIndex = expandedChildCount; if (!this.expandAllButtonElement) { var button = document.createElement("button"); button.className = "text-button"; button.value = ""; var item = new TreeElement(button, null, false); item.selectable = false; item.expandAllButton = true; this.insertChild(item, targetButtonIndex); this.expandAllButtonElement = item.listItemElement.firstChild; this.expandAllButtonElement.__treeElement = item; this.expandAllButtonElement.addEventListener("click", this.handleLoadAllChildren.bind(this), false); } else if (!this.expandAllButtonElement.__treeElement.parent) this.insertChild(this.expandAllButtonElement.__treeElement, targetButtonIndex); this.expandAllButtonElement.textContent = WebInspector.UIString("Show All Nodes (%d More)", childNodeCount - expandedChildCount); } else if (this.expandAllButtonElement) delete this.expandAllButtonElement; }, handleLoadAllChildren: function() { this.expandedChildrenLimit = Math.max(this._visibleChildCount(), this.expandedChildrenLimit + WebInspector.ElementsTreeElement.InitialChildrenLimit); }, expandRecursively: function() { /** * @this {WebInspector.ElementsTreeElement} */ function callback() { TreeElement.prototype.expandRecursively.call(this, Number.MAX_VALUE); } this._node.getSubtree(-1, callback.bind(this)); }, /** * @override */ onexpand: function() { if (this._elementCloseTag) return; this.updateTitle(); this.treeOutline.updateSelection(); }, oncollapse: function() { if (this._elementCloseTag) return; this.updateTitle(); this.treeOutline.updateSelection(); }, /** * @override */ onreveal: function() { if (this.listItemElement) { var tagSpans = this.listItemElement.getElementsByClassName("webkit-html-tag-name"); if (tagSpans.length) tagSpans[0].scrollIntoViewIfNeeded(true); else this.listItemElement.scrollIntoViewIfNeeded(true); } }, /** * @param {boolean=} omitFocus * @param {boolean=} selectedByUser * @return {boolean} */ select: function(omitFocus, selectedByUser) { if (!this.treeOutline._handlePickNode(this.title, this._node)) return false; return TreeElement.prototype.select.call(this, omitFocus, selectedByUser); }, /** * @override * @param {boolean=} selectedByUser * @return {boolean} */ onselect: function(selectedByUser) { this.treeOutline.suppressRevealAndSelect = true; this.treeOutline.selectDOMNode(this._node, selectedByUser); if (selectedByUser) this._node.highlight(); this.updateSelection(); this.treeOutline.suppressRevealAndSelect = false; return true; }, /** * @override * @return {boolean} */ ondelete: function() { var startTagTreeElement = this.treeOutline.findTreeElement(this._node); startTagTreeElement ? startTagTreeElement.remove() : this.remove(); return true; }, /** * @override * @return {boolean} */ onenter: function() { // On Enter or Return start editing the first attribute // or create a new attribute on the selected element. if (this._editing) return false; this._startEditing(); // prevent a newline from being immediately inserted return true; }, selectOnMouseDown: function(event) { TreeElement.prototype.selectOnMouseDown.call(this, event); if (this._editing) return; if (this.treeOutline._showInElementsPanelEnabled) { WebInspector.inspectorView.showPanel("elements"); this.treeOutline.selectDOMNode(this._node, true); } // Prevent selecting the nearest word on double click. if (event.detail >= 2) event.preventDefault(); }, /** * @override * @return {boolean} */ ondblclick: function(event) { if (this._editing || this._elementCloseTag) return false; if (this._startEditingTarget(/** @type {!Element} */(event.target))) return false; if (this.hasChildren && !this.expanded) this.expand(); return false; }, /** * @return {boolean} */ hasEditableNode: function() { return !this.representedObject.isShadowRoot() && !this.represe