UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

1,532 lines (1,318 loc) 72.3 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.DOMNode, string, boolean)=} setPseudoClassCallback */ WebInspector.ElementsTreeOutline = function(target, omitRootDOMNode, selectEnabled, setPseudoClassCallback) { this._target = target; this._domModel = target.domModel; var element = createElement("div"); this._shadowRoot = element.createShadowRoot(); this._shadowRoot.appendChild(WebInspector.View.createStyleElement("elements/elementsTreeOutline.css")); var outlineDisclosureElement = this._shadowRoot.createChild("div", "elements-disclosure"); WebInspector.installComponentRootStyles(outlineDisclosureElement); this._element = outlineDisclosureElement.createChild("ol", "elements-tree-outline source-code"); this._element.addEventListener("mousedown", this._onmousedown.bind(this), false); this._element.addEventListener("mousemove", this._onmousemove.bind(this), false); this._element.addEventListener("mouseleave", this._onmouseleave.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); this._element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false); TreeOutline.call(this, this._element); this.element = 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.setPseudoClassCallback = setPseudoClassCallback; this._createNodeDecorators(); this._popoverHelper = new WebInspector.PopoverHelper(this._element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this)); this._popoverHelper.setTimeout(0); /** @type {!WeakMap<!WebInspector.DOMNode, !WebInspector.ElementsTreeOutline.ShadowHostDisplayMode>} */ this._shadowHostDisplayModes = new WeakMap(); /** @type {!Set<!WebInspector.DOMNode>} */ this._recentlyModifiedNodes = new Set(); /** @type {!Set<!WebInspector.DOMNode>} */ this._recentlyModifiedParentNodes = new Set(); /** @type {!Map<!WebInspector.DOMNode, !WebInspector.ElementsTreeOutline.UpdateInfo>} */ this._updateInfos = new Map(); /** @type {!Set<!WebInspector.ElementsTreeElement>} */ this._treeElementsBeingUpdated = new Set(); } /** @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 } /** * @enum {string} */ WebInspector.ElementsTreeOutline.ShadowHostDisplayMode = { Composed: "Composed", Flattened: "Flattened", } WebInspector.ElementsTreeOutline.prototype = { focus: function() { this._element.focus(); }, /** * @param {boolean} wrap */ setWordWrap: function(wrap) { this._element.classList.toggle("elements-tree-nowrap", !wrap); }, /** * @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"); }, /** * @return {boolean} */ pickNodeMode: function() { return this._pickNodeMode; }, /** * @param {boolean} value */ setPickNodeMode: function(value) { this._pickNodeMode = value; this._element.classList.toggle("pick-node-mode", value); }, /** * @param {!Element} element * @param {?WebInspector.DOMNode} node * @return {boolean} */ handlePickNode: function(element, node) { if (!this._pickNodeMode) return false; 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 true; }, /** * @return {!WebInspector.Target} */ target: function() { return this._target; }, /** * @return {!WebInspector.DOMModel} */ domModel: function() { return this._domModel; }, /** * @param {?WebInspector.InplaceEditor.Controller} multilineEditing */ setMultilineEditing: function(multilineEditing) { this._multilineEditing = multilineEditing; }, /** * @return {number} */ visibleWidth: function() { return this._visibleWidth; }, /** * @param {number} width */ setVisibleWidth: function(width) { this._visibleWidth = width; if (this._multilineEditing) this._multilineEditing.setWidth(this._visibleWidth); }, /** * @return {!Array<!WebInspector.ElementsTreeOutline.ElementDecorator>} */ nodeDecorators: function() { return this._nodeDecorators; }, _createNodeDecorators: function() { this._nodeDecorators = []; this._nodeDecorators.push(new WebInspector.ElementsTreeOutline.PseudoStateDecorator()); }, /** * @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 (!event.target.isComponentSelectionCollapsed()) return; // Do not interfere with text editing. if (WebInspector.isEditing()) 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. if (WebInspector.isEditing()) 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) { this._popoverHelper.hidePopover(); return; } this.runPendingUpdates(); 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.isEditing() || false; }, update: function() { var selectedTreeElement = this.selectedTreeElement; var selectedNode = selectedTreeElement ? selectedTreeElement.node() : null; this.removeChildren(); if (!this.rootDOMNode) return; var treeElement; if (this._includeRootDOMNode) { treeElement = this._createElementTreeElement(this.rootDOMNode); 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 = this._createElementTreeElement(node); 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.isClosingTag()) 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 {?WebInspector.ElementsTreeElement} */ findTreeElement: function(node) { var treeElement = this._lookUpTreeElement(node); 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 = this._lookUpTreeElement(node.parentNode); } return /** @type {?WebInspector.ElementsTreeElement} */ (treeElement); }, /** * @param {?WebInspector.DOMNode} node * @return {?TreeElement} */ _lookUpTreeElement: function(node) { if (!node) return null; var cachedElement = this.getCachedTreeElement(node); if (cachedElement) return cachedElement; // Walk up the parent pointers from the desired node var ancestors = []; for (var currentNode = node.parentNode; currentNode; currentNode = currentNode.parentNode) { ancestors.push(currentNode); if (this.getCachedTreeElement(currentNode)) // stop climbing as soon as we hit break; } if (!currentNode) return null; // Walk down to populate each ancestor's children, to fill in the tree and the cache. for (var i = ancestors.length - 1; i >= 0; --i) { var treeElement = this.getCachedTreeElement(ancestors[i]); if (treeElement) treeElement.onpopulate(); // fill the cache with the children of treeElement } return this.getCachedTreeElement(node); }, /** * @param {!WebInspector.DOMNode} node * @return {?WebInspector.ElementsTreeElement} */ createTreeElementFor: function(node) { var treeElement = this.findTreeElement(node); if (treeElement) return treeElement; if (!node.parentNode) return null; treeElement = this.createTreeElementFor(node.parentNode); return treeElement ? this._showChild(treeElement, 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; }, /** * @param {!Element} element * @param {!Event} event * @return {!Element|!AnchorBox|undefined} */ _getPopoverAnchor: function(element, event) { var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource-link"); if (!anchor || !anchor.href) return; return anchor; }, /** * @param {!WebInspector.DOMNode} node * @param {function()} callback */ _loadDimensionsForNode: function(node, callback) { if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") { callback(); return; } node.resolveToObject("", resolvedNode); function resolvedNode(object) { if (!object) { callback(); return; } object.callFunctionJSON(features, undefined, callback); object.release(); /** * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: number, naturalHeight: number, currentSrc: (string|undefined)}} * @suppressReceiverCheck * @this {!Element} */ function features() { return { offsetWidth: this.offsetWidth, offsetHeight: this.offsetHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight, currentSrc: this.currentSrc }; } } }, /** * @param {!Element} anchor * @param {!WebInspector.Popover} popover */ _showPopover: function(anchor, popover) { var listItem = anchor.enclosingNodeOrSelfWithNodeName("li"); var node = /** @type {!WebInspector.ElementsTreeElement} */ (listItem.treeElement).node(); this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover)); /** * @param {!Element=} contents */ function showPopover(contents) { if (!contents) return; popover.setCanShrink(false); popover.showForAnchor(contents, anchor); } }, _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 instanceof WebInspector.ElementsTreeElement)) return; if (element && element.node()) this._domModel.highlightDOMNodeWithConfig(element.node().id, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) }); else this._domModel.hideDOMNodeHighlight(); }, _onmouseleave: function(event) { if (this._previousHoveredElement) { this._previousHoveredElement.hovered = false; delete this._previousHoveredElement; } this._domModel.hideDOMNodeHighlight(); }, _ondragstart: function(event) { if (!event.target.isComponentSelectionCollapsed()) return false; if (event.target.nodeName === "A") return false; var treeElement = this._treeElementFromEvent(event); 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; if (!(treeElement instanceof WebInspector.ElementsTreeElement)) return false; var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (treeElement); var node = elementsTreeElement.node(); 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.isClosingTag()) { // 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.isEditing() && 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 || WebInspector.isEditing()) return; var contextMenu = new WebInspector.ContextMenu(event); 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); } contextMenu.appendApplicableItems(treeElement.node()); contextMenu.show(); }, runPendingUpdates: function() { this._updateModifiedNodes(); }, 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 (node.pseudoType()) return; var parentNode = node.parentNode; var index = node.index; var wasExpanded = treeElement.expanded; treeElement.toggleEditAsHTML(editingFinished.bind(this)); /** * @this {WebInspector.ElementsTreeOutline} * @param {boolean} success */ function editingFinished(success) { if (!success) return; // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date. this.runPendingUpdates(); var newNode = parentNode ? parentNode.children()[index] || parentNode : null; if (!newNode) return; this.selectDOMNode(newNode, true); if (wasExpanded) { var newTreeItem = this.findTreeElement(newNode); if (newTreeItem) newTreeItem.expand(); } } }, /** * @param {boolean} wasExpanded * @param {?Protocol.Error} error * @param {!DOMAgent.NodeId=} nodeId * @return {?WebInspector.ElementsTreeElement} nodeId */ selectNodeAfterEdit: function(wasExpanded, error, nodeId) { if (error) return null; // Select it and expand if necessary. We force tree update so that it processes dom events and is up to date. this.runPendingUpdates(); var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null; if (!newNode) return null; 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 * @suppressGlobalPropertiesCheck * @suppressReceiverCheck * @this {!Element} */ function toggleClassAndInjectStyleRule(pseudoType) { const classNamePrefix = "__web-inspector-hide"; const classNameSuffix = "-shortcut__"; const styleTagId = "__web-inspector-hide-shortcut-style__"; var selectors = []; selectors.push("html /deep/ .__web-inspector-hide-shortcut__"); selectors.push("html /deep/ .__web-inspector-hide-shortcut__ /deep/ *"); selectors.push("html /deep/ .__web-inspector-hidebefore-shortcut__::before"); selectors.push("html /deep/ .__web-inspector-hideafter-shortcut__::after"); var selector = selectors.join(", "); var ruleBody = " visibility: hidden !important;"; var rule = "\n" + selector + "\n{\n" + ruleBody + "\n}\n"; 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 = rule; document.head.appendChild(style); } object.callFunction(toggleClassAndInjectStyleRule, [{ value: pseudoType }], userCallback); object.release(); } effectiveNode.resolveToObject("", resolvedNode); }, _reset: function() { this.rootDOMNode = null; this.selectDOMNode(null, false); this._popoverHelper.hidePopover(); delete this._clipboardNodeData; this._domModel.hideDOMNodeHighlight(); this._recentlyModifiedNodes.clear(); this._recentlyModifiedParentNodes.clear(); this._updateInfos.clear(); }, wireToDOMModel: function() { this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); this._domModel.addEventListener(WebInspector.DOMModel.Events.DistributedNodesChanged, this._distributedNodesChanged, this); }, unwireFromDOMModel: function() { this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeInserted, this._nodeInserted, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified, this._attributeModified, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataModified, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._documentUpdated, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCountUpdated, this); this._domModel.removeEventListener(WebInspector.DOMModel.Events.DistributedNodesChanged, this._distributedNodesChanged, this); }, /** * @param {!WebInspector.Event} event */ _distributedNodesChanged: function(event) { var shadowHost = /** @type {!WebInspector.DOMNode} */ (event.data); var shadowHostDisplayMode = this._shadowHostDisplayModes.get(shadowHost); if (!shadowHostDisplayMode) return; var insertionPoints = shadowHost.insertionPoints(); var shadowRoots = shadowHost.shadowRoots(); this._parentNodeModified(shadowHost); for (var shadowRoot of shadowRoots) { if (shadowHostDisplayMode === WebInspector.ElementsTreeOutline.ShadowHostDisplayMode.Composed) this._parentNodeModified(shadowRoot); } for (var insertionPoint of insertionPoints) { if (shadowHostDisplayMode === WebInspector.ElementsTreeOutline.ShadowHostDisplayMode.Flattened) this._parentNodeModified(insertionPoint.parentNode); else this._parentNodeModified(insertionPoint); } this._updateModifiedNodesSoon(); }, /** * @param {!WebInspector.DOMNode} node * @return {!WebInspector.ElementsTreeOutline.UpdateInfo} */ _updateRecord: function(node) { if (!WebInspector.settings.highlightDOMUpdates.get() || this._domUpdateHighlightsMuted) return new WebInspector.ElementsTreeOutline.UpdateInfo(); // Bogus info. var record = this._updateInfos.get(node); if (!record) { record = new WebInspector.ElementsTreeOutline.UpdateInfo(); this._updateInfos.set(node, record); } return record; }, /** * @param {!WebInspector.DOMNode} node * @return {?WebInspector.ElementsTreeOutline.UpdateInfo} */ _updateInfo: function(node) { if (!WebInspector.settings.highlightDOMUpdates.get()) return null; return this._updateInfos.get(node) || null; }, /** * @param {?WebInspector.DOMNode} parentNode * @return {!WebInspector.ElementsTreeOutline.UpdateInfo} */ _parentNodeModified: function(parentNode) { if (!parentNode) return new WebInspector.ElementsTreeOutline.UpdateInfo(); // Bogus info. this._recentlyModifiedParentNodes.add(parentNode); var treeElement = this.findTreeElement(parentNode); if (treeElement) { var oldDisplayMode = treeElement.childrenDisplayMode(); var newDisplayMode = this._calculateChildrenDisplayMode(treeElement); if (newDisplayMode !== oldDisplayMode) this._updateRecord(parentNode).childrenModified(); } return this._updateRecord(parentNode); }, /** * @param {!WebInspector.DOMNode} node * @return {!WebInspector.ElementsTreeOutline.UpdateInfo} */ _nodeModified: function(node) { this._recentlyModifiedNodes.add(node); return this._updateRecord(node); }, /** * @param {!WebInspector.Event} event */ _documentUpdated: function(event) { var inspectedRootDocument = event.data; this._reset(); if (!inspectedRootDocument) return; this.rootDOMNode = inspectedRootDocument; }, /** * @param {!WebInspector.Event} event */ _attributeModified: function(event) { var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); this._nodeModified(node).attributeModified(event.data.name); this._updateModifiedNodesSoon(); }, /** * @param {!WebInspector.Event} event */ _attributeRemoved: function(event) { var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); this._nodeModified(node).attributeRemoved(event.data.name); this._updateModifiedNodesSoon(); }, /** * @param {!WebInspector.Event} event */ _characterDataModified: function(event) { var node = /** @type {!WebInspector.DOMNode} */ (event.data); this._parentNodeModified(node.parentNode).charDataModified(); this._nodeModified(node).charDataModified(); this._updateModifiedNodesSoon(); }, /** * @param {!WebInspector.Event} event */ _nodeInserted: function(event) { var node = /** @type {!WebInspector.DOMNode} */ (event.data); this._parentNodeModified(node.parentNode).nodeInserted(node); this._updateModifiedNodesSoon(); }, /** * @param {!WebInspector.Event} event */ _nodeRemoved: function(event) { var node = /** @type {!WebInspector.DOMNode} */ (event.data.node); var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent); this.resetClipboardIfNeeded(node); this._parentNodeModified(parentNode).childrenModified(); this._updateModifiedNodesSoon(); }, /** * @param {!WebInspector.Event} event */ _childNodeCountUpdated: function(event) { var node = /** @type {!WebInspector.DOMNode} */ (event.data); this._parentNodeModified(node); this._updateModifiedNodesSoon(); }, _setUpdateInfos: function() { for (var node of this._updateInfos.keys()) { var treeElement = this.getCachedTreeElement(node); if (treeElement) treeElement.setUpdateInfo(this._updateInfo(node)); } }, _clearUpdateInfos: function() { for (var node of this._updateInfos.keys()) { var treeElement = this.getCachedTreeElement(node); if (treeElement) treeElement.setUpdateInfo(null); } this._updateInfos.clear(); }, _updateModifiedNodesSoon: function() { if (!this._recentlyModifiedNodes.size && !this._recentlyModifiedParentNodes.size) return; if (!this._visible) { this._updateInfos.clear(); return; } if (this._updateModifiedNodesTimeout) return; this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind(this), 50); }, _updateModifiedNodes: function() { if (this._updateModifiedNodesTimeout) { clearTimeout(this._updateModifiedNodesTimeout); delete this._updateModifiedNodesTimeout; } this._setUpdateInfos(); var updatedNodes = new Set(); for (var node of this._recentlyModifiedNodes) updatedNodes.add(node); for (var node of this._recentlyModifiedParentNodes) updatedNodes.add(node); var hidePanelWhileUpdating = updatedNodes.length > 10; if (hidePanelWhileUpdating) { var treeOutlineContainerElement = this.element.parentNode; var originalScrollTop = treeOutlineContainerElement ? treeOutlineContainerElement.scrollTop : 0; this._element.classList.add("hidden"); } if (this._rootDOMNode && this._recentlyModifiedParentNodes.has(this._rootDOMNode)) { // Document's children have changed, perform total update. this.update(); } else { for (var node of this._recentlyModifiedNodes) this._updateModifiedNode(node); for (var node of this._recentlyModifiedParentNodes) this._updateModifiedParentNode(node); } if (hidePanelWhileUpdating) { this._element.classList.remove("hidden"); if (originalScrollTop) treeOutlineContainerElement.scrollTop = originalScrollTop; this.updateSelection(); } this._clearUpdateInfos(); this._recentlyModifiedNodes.clear(); this._recentlyModifiedParentNodes.clear(); this._fireElementsTreeUpdated(updatedNodes.valuesArray()); }, _updateModifiedNode: function(node) { var treeElement = this.findTreeElement(node); if (treeElement) treeElement.updateTitle(false); }, _updateModifiedParentNode: function(node) { var parentTreeElement = this.findTreeElement(node); if (parentTreeElement) { this._updateChildrenDisplayMode(parentTreeElement); parentTreeElement.updateTitle(false); if (parentTreeElement.populated) this.updateChildren(parentTreeElement); } }, /** * @param {!WebInspector.ElementsTreeElement} treeElement */ populateTreeElement: function(treeElement) { if (treeElement.children.length || !treeElement.hasChildren) return; this._updateModifiedParentNode(treeElement.node()); }, /** * @param {!WebInspector.DOMNode} node * @param {boolean=} closingTag * @return {!WebInspector.ElementsTreeElement} */ _createElementTreeElement: function(node, closingTag) { var treeElement = new WebInspector.ElementsTreeElement(node, closingTag); treeElement.selectable = this._selectEnabled; if (!closingTag) treeElement.setUpdateInfo(this._updateInfo(node)); this._updateChildrenDisplayMode(treeElement); return treeElement; }, /** * @param {!WebInspector.ElementsTreeElement} treeElement * @param {!WebInspector.DOMNode} child * @return {?WebInspector.ElementsTreeElement} */ _showChild: function(treeElement, child) { if (treeElement.isClosingTag()) return null; var index = this._visibleChildren(treeElement.node()).indexOf(child); if (index === -1) return null; if (index >= treeElement.expandedChildrenLimit()) this.setExpandedChildrenLimit(treeElement, index + 1); return /** @type {!WebInspector.ElementsTreeElement} */ (treeElement.children[index]); }, /** * @param {!WebInspector.DOMNode} node * @return {!Array<!WebInspector.DOMNode>} */ _visibleShadowRoots: function(node) { var roots = node.shadowRoots(); if (roots.length && !WebInspector.settings.showUAShadowDOM.get()) { roots = roots.filter(function(root) { return root.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.Author; }); } return roots; }, /** * @param {!WebInspector.DOMNode} node * @return {boolean} */ _isShadowHostInComposedMode: function(node) { var shadowRoots = this._visibleShadowRoots(node); return this._shadowHostDisplayModes.has(node) && !!shadowRoots.length; }, /** * @param {!WebInspector.DOMNode} node * @return {boolean} */ _isInsertionPointInComposedMode: function(node) { var ancestorShadowHost = node.ancestorShadowHost(); var isInShadowTreeInComposedMode = !!ancestorShadowHost && this._shadowHostDisplayModes.has(ancestorShadowHost); return isInShadowTreeInComposedMode && node.isInsertionPoint(); }, /** * @param {!WebInspector.DOMNode} node * @return {boolean} */ _shouldFlatten: function(node) { var ancestorShadowHost = node.ancestorShadowHost(); if (!ancestorShadowHost) return false; if (this._shadowHostDisplayModes.get(ancestorShadowHost) !== WebInspector.ElementsTreeOutline.ShadowHostDisplayMode.Flattened) return false; return node.isShadowRoot() || node.isInsertionPoint(); }, /** * @param {!WebInspector.DOMNode} node * @return {!Array.<!WebInspector.DOMNode>} visibleChildren */ _visibleChildren: function(node) { var result = []; var unflattenedChildren = this._unflattenedChildren(node); for (var child of unflattenedChildren) { if (this._shouldFlatten(child)) { var flattenedChildren = this._visibleChildren(child); result = result.concat(flattenedChildren); } els