UNPKG

monaca-lib

Version:

Monaca cloud API bindings for JavaScript

1,707 lines (1,526 loc) 64.3 kB
/* * Copyright (C) 2009, 2010 Google Inc. All rights reserved. * 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: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 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. * * Neither the name of Google Inc. 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT * OWNER OR 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 {WebInspector.SDKObject} * @param {!WebInspector.DOMModel} domModel * @param {?WebInspector.DOMDocument} doc * @param {boolean} isInShadowTree * @param {!DOMAgent.Node} payload */ WebInspector.DOMNode = function(domModel, doc, isInShadowTree, payload) { WebInspector.SDKObject.call(this, domModel.target()); this._domModel = domModel; this._agent = domModel._agent; this.ownerDocument = doc; this._isInShadowTree = isInShadowTree; this.id = payload.nodeId; domModel._idToDOMNode[this.id] = this; this._nodeType = payload.nodeType; this._nodeName = payload.nodeName; this._localName = payload.localName; this._nodeValue = payload.nodeValue; this._pseudoType = payload.pseudoType; this._shadowRootType = payload.shadowRootType; this._frameId = payload.frameId || null; this._shadowRoots = []; this._attributes = []; this._attributesMap = {}; if (payload.attributes) this._setAttributesPayload(payload.attributes); this._userProperties = {}; this._descendantUserPropertyCounters = {}; this._childNodeCount = payload.childNodeCount || 0; this._children = null; this.nextSibling = null; this.previousSibling = null; this.firstChild = null; this.lastChild = null; this.parentNode = null; if (payload.shadowRoots) { for (var i = 0; i < payload.shadowRoots.length; ++i) { var root = payload.shadowRoots[i]; var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, true, root); this._shadowRoots.push(node); node.parentNode = this; } } if (payload.templateContent) { this._templateContent = new WebInspector.DOMNode(this._domModel, this.ownerDocument, true, payload.templateContent); this._templateContent.parentNode = this; } if (payload.importedDocument) { this._importedDocument = new WebInspector.DOMNode(this._domModel, this.ownerDocument, true, payload.importedDocument); this._importedDocument.parentNode = this; } if (payload.children) this._setChildrenPayload(payload.children); this._setPseudoElements(payload.pseudoElements); if (payload.contentDocument) { this._contentDocument = new WebInspector.DOMDocument(domModel, payload.contentDocument); this._children = [this._contentDocument]; this._renumber(); } if (this._nodeType === Node.ELEMENT_NODE) { // HTML and BODY from internal iframes should not overwrite top-level ones. if (this.ownerDocument && !this.ownerDocument.documentElement && this._nodeName === "HTML") this.ownerDocument.documentElement = this; if (this.ownerDocument && !this.ownerDocument.body && this._nodeName === "BODY") this.ownerDocument.body = this; } else if (this._nodeType === Node.DOCUMENT_TYPE_NODE) { this.publicId = payload.publicId; this.systemId = payload.systemId; this.internalSubset = payload.internalSubset; } else if (this._nodeType === Node.ATTRIBUTE_NODE) { this.name = payload.name; this.value = payload.value; } } /** * @enum {string} */ WebInspector.DOMNode.PseudoElementNames = { Before: "before", After: "after" } /** * @enum {string} */ WebInspector.DOMNode.ShadowRootTypes = { UserAgent: "user-agent", Author: "author" } WebInspector.DOMNode.prototype = { /** * @return {!WebInspector.DOMModel} */ domModel: function() { return this._domModel; }, /** * @return {?Array.<!WebInspector.DOMNode>} */ children: function() { return this._children ? this._children.slice() : null; }, /** * @return {boolean} */ hasAttributes: function() { return this._attributes.length > 0; }, /** * @return {number} */ childNodeCount: function() { return this._childNodeCount; }, /** * @return {boolean} */ hasShadowRoots: function() { return !!this._shadowRoots.length; }, /** * @return {!Array.<!WebInspector.DOMNode>} */ shadowRoots: function() { return this._shadowRoots.slice(); }, /** * @return {?WebInspector.DOMNode} */ templateContent: function() { return this._templateContent || null; }, /** * @return {?WebInspector.DOMNode} */ importedDocument: function() { return this._importedDocument || null; }, /** * @return {number} */ nodeType: function() { return this._nodeType; }, /** * @return {string} */ nodeName: function() { return this._nodeName; }, /** * @return {string|undefined} */ pseudoType: function() { return this._pseudoType; }, /** * @return {boolean} */ hasPseudoElements: function() { return this._pseudoElements.size > 0; }, /** * @return {!Map<string, !WebInspector.DOMNode>} */ pseudoElements: function() { return this._pseudoElements; }, /** * @return {?WebInspector.DOMNode} */ beforePseudoElement: function() { if (!this._pseudoElements) return null; return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementNames.Before); }, /** * @return {?WebInspector.DOMNode} */ afterPseudoElement: function() { if (!this._pseudoElements) return null; return this._pseudoElements.get(WebInspector.DOMNode.PseudoElementNames.After); }, /** * @return {boolean} */ isInsertionPoint: function() { return !this.isXMLNode() && (this._nodeName === "SHADOW" || this._nodeName === "CONTENT"); }, /** * @return {!Array.<!WebInspector.DOMNode>} */ distributedNodes: function() { return this._distributedNodes || []; }, /** * @return {!Array.<!WebInspector.DOMNode>} */ insertionPoints: function() { return this._insertionPoints || []; }, /** * @return {?WebInspector.DOMNode} */ distributedShadowRoot: function() { return this._distributedShadowRoot; }, /** * @return {boolean} */ isInShadowTree: function() { return this._isInShadowTree; }, /** * @return {?WebInspector.DOMNode} */ ancestorShadowHost: function() { var ancestorShadowRoot = this.ancestorShadowRoot(); return ancestorShadowRoot ? ancestorShadowRoot.parentNode : null; }, /** * @return {?WebInspector.DOMNode} */ ancestorShadowRoot: function() { if (!this._isInShadowTree) return null; var current = this; while (current && !current.isShadowRoot()) current = current.parentNode; return current; }, /** * @return {?WebInspector.DOMNode} */ ancestorUserAgentShadowRoot: function() { var ancestorShadowRoot = this.ancestorShadowRoot(); if (!ancestorShadowRoot) return null; return ancestorShadowRoot.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? ancestorShadowRoot : null; }, /** * @return {boolean} */ isShadowRoot: function() { return !!this._shadowRootType; }, /** * @return {?string} */ shadowRootType: function() { return this._shadowRootType || null; }, /** * @return {string} */ nodeNameInCorrectCase: function() { var shadowRootType = this.shadowRootType(); if (shadowRootType) return "#shadow-root" + (shadowRootType === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? " (user-agent)" : ""); return this.isXMLNode() ? this.nodeName() : this.nodeName().toLowerCase(); }, /** * @param {string} name * @param {function(?Protocol.Error, number)=} callback */ setNodeName: function(name, callback) { this._agent.setNodeName(this.id, name, this._domModel._markRevision(this, callback)); }, /** * @return {string} */ localName: function() { return this._localName; }, /** * @return {string} */ nodeValue: function() { return this._nodeValue; }, /** * @param {string} value * @param {function(?Protocol.Error)=} callback */ setNodeValue: function(value, callback) { this._agent.setNodeValue(this.id, value, this._domModel._markRevision(this, callback)); }, /** * @param {string} name * @return {string} */ getAttribute: function(name) { var attr = this._attributesMap[name]; return attr ? attr.value : undefined; }, /** * @param {string} name * @param {string} text * @param {function(?Protocol.Error)=} callback */ setAttribute: function(name, text, callback) { this._agent.setAttributesAsText(this.id, text, name, this._domModel._markRevision(this, callback)); }, /** * @param {string} name * @param {string} value * @param {function(?Protocol.Error)=} callback */ setAttributeValue: function(name, value, callback) { this._agent.setAttributeValue(this.id, name, value, this._domModel._markRevision(this, callback)); }, /** * @return {!Object} */ attributes: function() { return this._attributes; }, /** * @param {string} name * @param {function(?Protocol.Error)=} callback */ removeAttribute: function(name, callback) { /** * @param {?Protocol.Error} error * @this {WebInspector.DOMNode} */ function mycallback(error) { if (!error) { delete this._attributesMap[name]; for (var i = 0; i < this._attributes.length; ++i) { if (this._attributes[i].name === name) { this._attributes.splice(i, 1); break; } } } this._domModel._markRevision(this, callback)(error); } this._agent.removeAttribute(this.id, name, mycallback.bind(this)); }, /** * @param {function()} callback */ ensureShadowHostDistributedNodesLoaded: function(callback) { if (this.hasShadowRoots() && !this._distributedNodesLoaded) { this._requestShadowHostDistribution(innerCallback.bind(this)); return; } /** * @this {WebInspector.DOMNode} */ function innerCallback() { this._distributedNodesLoaded = true; callback(); } callback(); }, /** * @param {function()} callback */ _requestShadowHostDistribution: function(callback) { this._agent.requestShadowHostDistributedNodes(this.id, innerCallback.bind(this)); /** * @this {WebInspector.DOMNode} * @param {?Protocol.Error} error * @param {!Array.<!DOMAgent.InsertionPointDistribution>} insertionPointDistributions */ function innerCallback(error, insertionPointDistributions) { if (error) console.error(error); this._setShadowHostDistribution(insertionPointDistributions); callback(); } }, /** * @param {!Array.<!DOMAgent.InsertionPointDistribution>} insertionPointDistributions */ _setShadowHostDistribution: function(insertionPointDistributions) { this._nodeDistributionPaths = new WeakMap(); this._insertionPoints = []; for (var insertionPointDistribution of insertionPointDistributions) { var insertionPointId = insertionPointDistribution.nodeId; var insertionPoint = this._domModel.nodeForId(insertionPointId) if (!insertionPoint) return; this._insertionPoints.push(insertionPoint); if (insertionPoint._nodeName === "SHADOW") { var ancestorShadowRoot = insertionPoint.ancestorShadowRoot(); var ancestorShadowHost = ancestorShadowRoot.parentNode; var shadowRootIndex = ancestorShadowHost._shadowRoots.indexOf(ancestorShadowRoot); if (shadowRootIndex + 1 < ancestorShadowHost._shadowRoots.length) insertionPoint._distributedShadowRoot = ancestorShadowHost._shadowRoots[shadowRootIndex + 1]; } else { var distributedNodes = insertionPointDistribution.distributedNodes; insertionPoint._distributedNodes = []; var addedNodes = new Set(); for (var distributedNodeObject of distributedNodes) { var distributedNodeId = distributedNodeObject.nodeId; var destinationInsertionPointIds = distributedNodeObject.destinationInsertionPointIds || [insertionPointId]; var distributedNode = this._domModel.nodeForId(distributedNodeId); var destinationInsertionPoints = destinationInsertionPointIds.map(this._domModel.nodeForId.bind(this._domModel)) this._nodeDistributionPaths.set(distributedNode, destinationInsertionPoints); var insertionPointIndex = destinationInsertionPoints.indexOf(insertionPoint); var redistributedInsertionPoint; if (insertionPointIndex > 0) redistributedInsertionPoint = destinationInsertionPoints[insertionPointIndex - 1]; var nodeToAdd = redistributedInsertionPoint || distributedNode; if (!addedNodes.has(nodeToAdd)) { insertionPoint._distributedNodes.push(redistributedInsertionPoint || distributedNode); addedNodes.add(nodeToAdd); } } } } }, /** * @param {function(?Array.<!WebInspector.DOMNode>)=} callback */ getChildNodes: function(callback) { if (this._children) { if (callback) callback(this.children()); return; } /** * @this {WebInspector.DOMNode} * @param {?Protocol.Error} error */ function mycallback(error) { if (callback) callback(error ? null : this.children()); } this._agent.requestChildNodes(this.id, undefined, mycallback.bind(this)); }, /** * @param {number} depth * @param {function(?Array.<!WebInspector.DOMNode>)=} callback */ getSubtree: function(depth, callback) { /** * @this {WebInspector.DOMNode} * @param {?Protocol.Error} error */ function mycallback(error) { if (callback) callback(error ? null : this._children); } this._agent.requestChildNodes(this.id, depth, mycallback.bind(this)); }, /** * @param {function(?Protocol.Error, string)=} callback */ getOuterHTML: function(callback) { this._agent.getOuterHTML(this.id, callback); }, /** * @param {string} html * @param {function(?Protocol.Error)=} callback */ setOuterHTML: function(html, callback) { this._agent.setOuterHTML(this.id, html, this._domModel._markRevision(this, callback)); }, /** * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback */ removeNode: function(callback) { this._agent.removeNode(this.id, this._domModel._markRevision(this, callback)); }, /** * @param {function(?string)=} callback */ copyNode: function(callback) { function copy(error, text) { if (!error) InspectorFrontendHost.copyText(text); if (callback) callback(error ? null : text); } this._agent.getOuterHTML(this.id, copy); }, /** * @param {string} objectGroupId * @param {function(?Array.<!WebInspector.DOMModel.EventListener>)} callback */ eventListeners: function(objectGroupId, callback) { var target = this.target(); /** * @param {?Protocol.Error} error * @param {!Array.<!DOMAgent.EventListener>} payloads */ function mycallback(error, payloads) { if (error) { callback(null); return; } callback(payloads.map(function(payload) { return new WebInspector.DOMModel.EventListener(target, payload); })); } this._agent.getEventListenersForNode(this.id, objectGroupId, mycallback); }, /** * @return {string} */ path: function() { /** * @param {?WebInspector.DOMNode} node */ function canPush(node) { return node && ("index" in node || (node.isShadowRoot() && node.parentNode)) && node._nodeName.length; } var path = []; var node = this; while (canPush(node)) { var index = typeof node.index === "number" ? node.index : (node.shadowRootType() === WebInspector.DOMNode.ShadowRootTypes.UserAgent ? "u" : "a"); path.push([index, node._nodeName]); node = node.parentNode; } path.reverse(); return path.join(","); }, /** * @param {!WebInspector.DOMNode} node * @return {boolean} */ isAncestor: function(node) { if (!node) return false; var currentNode = node.parentNode; while (currentNode) { if (this === currentNode) return true; currentNode = currentNode.parentNode; } return false; }, /** * @param {!WebInspector.DOMNode} descendant * @return {boolean} */ isDescendant: function(descendant) { return descendant !== null && descendant.isAncestor(this); }, /** * @return {?PageAgent.FrameId} */ frameId: function() { var node = this; while (!node._frameId && node.parentNode) node = node.parentNode; return node._frameId; }, /** * @param {!Array.<string>} attrs * @return {boolean} */ _setAttributesPayload: function(attrs) { var attributesChanged = !this._attributes || attrs.length !== this._attributes.length * 2; var oldAttributesMap = this._attributesMap || {}; this._attributes = []; this._attributesMap = {}; for (var i = 0; i < attrs.length; i += 2) { var name = attrs[i]; var value = attrs[i + 1]; this._addAttribute(name, value); if (attributesChanged) continue; if (!oldAttributesMap[name] || oldAttributesMap[name].value !== value) attributesChanged = true; } return attributesChanged; }, /** * @param {!WebInspector.DOMNode} prev * @param {!DOMAgent.Node} payload * @return {!WebInspector.DOMNode} */ _insertChild: function(prev, payload) { var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payload); this._children.splice(this._children.indexOf(prev) + 1, 0, node); this._renumber(); return node; }, /** * @param {!WebInspector.DOMNode} node */ _removeChild: function(node) { if (node.pseudoType()) { this._pseudoElements.delete(node.pseudoType()); } else { var shadowRootIndex = this._shadowRoots.indexOf(node); if (shadowRootIndex !== -1) this._shadowRoots.splice(shadowRootIndex, 1); else this._children.splice(this._children.indexOf(node), 1); } node.parentNode = null; node._updateChildUserPropertyCountsOnRemoval(this); this._renumber(); }, /** * @param {!Array.<!DOMAgent.Node>} payloads */ _setChildrenPayload: function(payloads) { // We set children in the constructor. if (this._contentDocument) return; this._children = []; for (var i = 0; i < payloads.length; ++i) { var payload = payloads[i]; var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payload); this._children.push(node); } this._renumber(); }, /** * @param {!Array.<!DOMAgent.Node>|undefined} payloads */ _setPseudoElements: function(payloads) { this._pseudoElements = new Map(); if (!payloads) return; for (var i = 0; i < payloads.length; ++i) { var node = new WebInspector.DOMNode(this._domModel, this.ownerDocument, this._isInShadowTree, payloads[i]); node.parentNode = this; this._pseudoElements.set(node.pseudoType(), node); } }, _renumber: function() { this._childNodeCount = this._children.length; if (this._childNodeCount == 0) { this.firstChild = null; this.lastChild = null; return; } this.firstChild = this._children[0]; this.lastChild = this._children[this._childNodeCount - 1]; for (var i = 0; i < this._childNodeCount; ++i) { var child = this._children[i]; child.index = i; child.nextSibling = i + 1 < this._childNodeCount ? this._children[i + 1] : null; child.previousSibling = i - 1 >= 0 ? this._children[i - 1] : null; child.parentNode = this; } }, /** * @param {string} name * @param {string} value */ _addAttribute: function(name, value) { var attr = { name: name, value: value, _node: this }; this._attributesMap[name] = attr; this._attributes.push(attr); }, /** * @param {string} name * @param {string} value */ _setAttribute: function(name, value) { var attr = this._attributesMap[name]; if (attr) attr.value = value; else this._addAttribute(name, value); }, /** * @param {string} name */ _removeAttribute: function(name) { var attr = this._attributesMap[name]; if (attr) { this._attributes.remove(attr); delete this._attributesMap[name]; } }, /** * @param {!WebInspector.DOMNode} targetNode * @param {?WebInspector.DOMNode} anchorNode * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback */ copyTo: function(targetNode, anchorNode, callback) { this._agent.copyTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._domModel._markRevision(this, callback)); }, /** * @param {!WebInspector.DOMNode} targetNode * @param {?WebInspector.DOMNode} anchorNode * @param {function(?Protocol.Error, !DOMAgent.NodeId=)=} callback */ moveTo: function(targetNode, anchorNode, callback) { this._agent.moveTo(this.id, targetNode.id, anchorNode ? anchorNode.id : undefined, this._domModel._markRevision(this, callback)); }, /** * @return {boolean} */ isXMLNode: function() { return !!this.ownerDocument && !!this.ownerDocument.xmlVersion; }, _updateChildUserPropertyCountsOnRemoval: function(parentNode) { var result = {}; if (this._userProperties) { for (var name in this._userProperties) result[name] = (result[name] || 0) + 1; } if (this._descendantUserPropertyCounters) { for (var name in this._descendantUserPropertyCounters) { var counter = this._descendantUserPropertyCounters[name]; result[name] = (result[name] || 0) + counter; } } for (var name in result) parentNode._updateDescendantUserPropertyCount(name, -result[name]); }, _updateDescendantUserPropertyCount: function(name, delta) { if (!this._descendantUserPropertyCounters.hasOwnProperty(name)) this._descendantUserPropertyCounters[name] = 0; this._descendantUserPropertyCounters[name] += delta; if (!this._descendantUserPropertyCounters[name]) delete this._descendantUserPropertyCounters[name]; if (this.parentNode) this.parentNode._updateDescendantUserPropertyCount(name, delta); }, setUserProperty: function(name, value) { if (value === null) { this.removeUserProperty(name); return; } if (this.parentNode && !this._userProperties.hasOwnProperty(name)) this.parentNode._updateDescendantUserPropertyCount(name, 1); this._userProperties[name] = value; }, removeUserProperty: function(name) { if (!this._userProperties.hasOwnProperty(name)) return; delete this._userProperties[name]; if (this.parentNode) this.parentNode._updateDescendantUserPropertyCount(name, -1); }, /** * @param {string} name * @return {?T} * @template T */ getUserProperty: function(name) { return (this._userProperties && this._userProperties[name]) || null; }, /** * @param {string} name * @return {number} */ descendantUserPropertyCount: function(name) { return this._descendantUserPropertyCounters && this._descendantUserPropertyCounters[name] ? this._descendantUserPropertyCounters[name] : 0; }, /** * @param {string} url * @return {?string} */ resolveURL: function(url) { if (!url) return url; for (var frameOwnerCandidate = this; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) { if (frameOwnerCandidate.baseURL) return WebInspector.ParsedURL.completeURL(frameOwnerCandidate.baseURL, url); } return null; }, /** * @param {string=} mode * @param {!RuntimeAgent.RemoteObjectId=} objectId */ highlight: function(mode, objectId) { this._domModel.highlightDOMNode(this.id, mode, objectId); }, highlightForTwoSeconds: function() { this._domModel.highlightDOMNodeForTwoSeconds(this.id); }, /** * @param {string=} objectGroup * @param {function(?WebInspector.RemoteObject)=} callback */ resolveToObject: function(objectGroup, callback) { this._agent.resolveNode(this.id, objectGroup, mycallback.bind(this)); /** * @param {?Protocol.Error} error * @param {!RuntimeAgent.RemoteObject} object * @this {WebInspector.DOMNode} */ function mycallback(error, object) { if (!callback) return; if (error || !object) callback(null); else callback(this.target().runtimeModel.createRemoteObject(object)); } }, /** * @param {function(?DOMAgent.BoxModel)} callback */ boxModel: function(callback) { this._agent.getBoxModel(this.id, this._domModel._wrapClientCallback(callback)); }, __proto__: WebInspector.SDKObject.prototype } /** * @param {!WebInspector.Target} target * @param {number} backendNodeId * @constructor */ WebInspector.DeferredDOMNode = function(target, backendNodeId) { this._target = target; this._backendNodeId = backendNodeId; } WebInspector.DeferredDOMNode.prototype = { /** * @param {function(?WebInspector.DOMNode)} callback */ resolve: function(callback) { this._target.domModel.pushNodesByBackendIdsToFrontend([this._backendNodeId], onGotNode.bind(this)); /** * @param {?Array.<number>} nodeIds * @this {WebInspector.DeferredDOMNode} */ function onGotNode(nodeIds) { if (!nodeIds || !nodeIds[0]) { callback(null); return; } callback(this._target.domModel.nodeForId(nodeIds[0])); } } } /** * @extends {WebInspector.DOMNode} * @constructor * @param {!WebInspector.DOMModel} domModel * @param {!DOMAgent.Node} payload */ WebInspector.DOMDocument = function(domModel, payload) { WebInspector.DOMNode.call(this, domModel, this, false, payload); this.documentURL = payload.documentURL || ""; this.baseURL = payload.baseURL || ""; this.xmlVersion = payload.xmlVersion; this._listeners = {}; } WebInspector.DOMDocument.prototype = { __proto__: WebInspector.DOMNode.prototype } /** * @constructor * @extends {WebInspector.SDKModel} * @param {!WebInspector.Target} target */ WebInspector.DOMModel = function(target) { WebInspector.SDKModel.call(this, WebInspector.DOMModel, target); this._agent = target.domAgent(); /** @type {!Object.<number, !WebInspector.DOMNode>} */ this._idToDOMNode = {}; /** @type {?WebInspector.DOMDocument} */ this._document = null; /** @type {!Object.<number, boolean>} */ this._attributeLoadNodeIds = {}; /** @type {!Set<number>} */ this._shadowHostDistributionRequestNodeIds = new Set(); target.registerDOMDispatcher(new WebInspector.DOMDispatcher(this)); this._defaultHighlighter = new WebInspector.DefaultDOMNodeHighlighter(this._agent); this._highlighter = this._defaultHighlighter; this._agent.enable(); } WebInspector.DOMModel.Events = { AttrModified: "AttrModified", AttrRemoved: "AttrRemoved", CharacterDataModified: "CharacterDataModified", NodeInserted: "NodeInserted", NodeInspected: "NodeInspected", NodeRemoved: "NodeRemoved", DocumentUpdated: "DocumentUpdated", ChildNodeCountUpdated: "ChildNodeCountUpdated", UndoRedoRequested: "UndoRedoRequested", UndoRedoCompleted: "UndoRedoCompleted", DistributedNodesChanged: "DistributedNodesChanged", } WebInspector.DOMModel.prototype = { suspendModel: function() { this._agent.disable(); }, resumeModel: function() { this._agent.enable(); }, /** * @param {function(!WebInspector.DOMDocument)=} callback */ requestDocument: function(callback) { if (this._document) { if (callback) callback(this._document); return; } if (this._pendingDocumentRequestCallbacks) { this._pendingDocumentRequestCallbacks.push(callback); return; } this._pendingDocumentRequestCallbacks = [callback]; /** * @this {WebInspector.DOMModel} * @param {?Protocol.Error} error * @param {!DOMAgent.Node} root */ function onDocumentAvailable(error, root) { if (!error) this._setDocument(root); for (var i = 0; i < this._pendingDocumentRequestCallbacks.length; ++i) { var callback = this._pendingDocumentRequestCallbacks[i]; if (callback) callback(this._document); } delete this._pendingDocumentRequestCallbacks; } this._agent.getDocument(onDocumentAvailable.bind(this)); }, /** * @return {?WebInspector.DOMDocument} */ existingDocument: function() { return this._document; }, /** * @param {!RuntimeAgent.RemoteObjectId} objectId * @param {function(?WebInspector.DOMNode)=} callback */ pushNodeToFrontend: function(objectId, callback) { /** * @param {?DOMAgent.NodeId} nodeId * @this {!WebInspector.DOMModel} */ function mycallback(nodeId) { callback(nodeId ? this.nodeForId(nodeId) : null); } this._dispatchWhenDocumentAvailable(this._agent.requestNode.bind(this._agent, objectId), mycallback.bind(this)); }, /** * @param {string} path * @param {function(?number)=} callback */ pushNodeByPathToFrontend: function(path, callback) { this._dispatchWhenDocumentAvailable(this._agent.pushNodeByPathToFrontend.bind(this._agent, path), callback); }, /** * @param {!Array.<number>} backendNodeIds * @param {function(?Array.<number>)=} callback */ pushNodesByBackendIdsToFrontend: function(backendNodeIds, callback) { this._dispatchWhenDocumentAvailable(this._agent.pushNodesByBackendIdsToFrontend.bind(this._agent, backendNodeIds), callback); }, /** * @param {function(!T)=} callback * @return {function(?Protocol.Error, !T=)|undefined} * @template T */ _wrapClientCallback: function(callback) { if (!callback) return; /** * @param {?Protocol.Error} error * @param {!T=} result * @template T */ var wrapper = function(error, result) { // Caller is responsible for handling the actual error. callback(error ? null : result); }; return wrapper; }, /** * @param {function(function(?Protocol.Error, !T=)=)} func * @param {function(!T)=} callback * @template T */ _dispatchWhenDocumentAvailable: function(func, callback) { var callbackWrapper = this._wrapClientCallback(callback); /** * @this {WebInspector.DOMModel} */ function onDocumentAvailable() { if (this._document) func(callbackWrapper); else { if (callbackWrapper) callbackWrapper("No document"); } } this.requestDocument(onDocumentAvailable.bind(this)); }, /** * @param {!DOMAgent.NodeId} nodeId * @param {string} name * @param {string} value */ _attributeModified: function(nodeId, name, value) { var node = this._idToDOMNode[nodeId]; if (!node) return; node._setAttribute(name, value); this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrModified, { node: node, name: name }); }, /** * @param {!DOMAgent.NodeId} nodeId * @param {string} name */ _attributeRemoved: function(nodeId, name) { var node = this._idToDOMNode[nodeId]; if (!node) return; node._removeAttribute(name); this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrRemoved, { node: node, name: name }); }, /** * @param {!Array.<!DOMAgent.NodeId>} nodeIds */ _inlineStyleInvalidated: function(nodeIds) { for (var i = 0; i < nodeIds.length; ++i) this._attributeLoadNodeIds[nodeIds[i]] = true; if ("_loadNodeAttributesTimeout" in this) return; this._loadNodeAttributesTimeout = setTimeout(this._loadNodeAttributes.bind(this), 20); }, _loadNodeAttributes: function() { /** * @this {WebInspector.DOMModel} * @param {!DOMAgent.NodeId} nodeId * @param {?Protocol.Error} error * @param {!Array.<string>} attributes */ function callback(nodeId, error, attributes) { if (error) { // We are calling _loadNodeAttributes asynchronously, it is ok if node is not found. return; } var node = this._idToDOMNode[nodeId]; if (node) { if (node._setAttributesPayload(attributes)) this.dispatchEventToListeners(WebInspector.DOMModel.Events.AttrModified, { node: node, name: "style" }); } } delete this._loadNodeAttributesTimeout; for (var nodeId in this._attributeLoadNodeIds) { var nodeIdAsNumber = parseInt(nodeId, 10); this._agent.getAttributes(nodeIdAsNumber, callback.bind(this, nodeIdAsNumber)); } this._attributeLoadNodeIds = {}; }, /** * @param {!Array.<!DOMAgent.NodeId>} nodeIds */ _shadowHostDistributionInvalidated: function(nodeIds) { if (!this._shadowHostDistributionRequestThrottler) this._shadowHostDistributionRequestThrottler = new WebInspector.Throttler(20); this._shadowHostDistributionRequestThrottler.schedule(this._requestShadowHostDistributions.bind(this)); for (var nodeId of nodeIds) this._shadowHostDistributionRequestNodeIds.add(nodeId); }, /** * @param {function()} callback */ _requestShadowHostDistributions: function(callback) { var barrier = new CallbackBarrier(); for (var nodeId of this._shadowHostDistributionRequestNodeIds) { var node = this._idToDOMNode[nodeId]; var barrierCallback = barrier.createCallback(); node._requestShadowHostDistribution(shadowHostDistributionLoaded.bind(this, barrierCallback, node)); } this._shadowHostDistributionRequestNodeIds.clear(); barrier.callWhenDone(callback); /** * @this {WebInspector.DOMModel} * @param {function()} barrierCallback * @param {!WebInspector.DOMNode} shadowHost */ function shadowHostDistributionLoaded(barrierCallback, shadowHost) { this.dispatchEventToListeners(WebInspector.DOMModel.Events.DistributedNodesChanged, shadowHost); barrierCallback(); } }, /** * @param {!DOMAgent.NodeId} nodeId * @param {string} newValue */ _characterDataModified: function(nodeId, newValue) { var node = this._idToDOMNode[nodeId]; node._nodeValue = newValue; this.dispatchEventToListeners(WebInspector.DOMModel.Events.CharacterDataModified, node); }, /** * @param {!DOMAgent.NodeId} nodeId * @return {?WebInspector.DOMNode} */ nodeForId: function(nodeId) { return this._idToDOMNode[nodeId] || null; }, _documentUpdated: function() { this._setDocument(null); }, /** * @param {?DOMAgent.Node} payload */ _setDocument: function(payload) { this._idToDOMNode = {}; if (payload && "nodeId" in payload) this._document = new WebInspector.DOMDocument(this, payload); else this._document = null; this.dispatchEventToListeners(WebInspector.DOMModel.Events.DocumentUpdated, this._document); }, /** * @param {!DOMAgent.Node} payload */ _setDetachedRoot: function(payload) { if (payload.nodeName === "#document") new WebInspector.DOMDocument(this, payload); else new WebInspector.DOMNode(this, null, false, payload); }, /** * @param {!DOMAgent.NodeId} parentId * @param {!Array.<!DOMAgent.Node>} payloads */ _setChildNodes: function(parentId, payloads) { if (!parentId && payloads.length) { this._setDetachedRoot(payloads[0]); return; } var parent = this._idToDOMNode[parentId]; parent._setChildrenPayload(payloads); }, /** * @param {!DOMAgent.NodeId} nodeId * @param {number} newValue */ _childNodeCountUpdated: function(nodeId, newValue) { var node = this._idToDOMNode[nodeId]; node._childNodeCount = newValue; this.dispatchEventToListeners(WebInspector.DOMModel.Events.ChildNodeCountUpdated, node); }, /** * @param {!DOMAgent.NodeId} parentId * @param {!DOMAgent.NodeId} prevId * @param {!DOMAgent.Node} payload */ _childNodeInserted: function(parentId, prevId, payload) { var parent = this._idToDOMNode[parentId]; var prev = this._idToDOMNode[prevId]; var node = parent._insertChild(prev, payload); this._idToDOMNode[node.id] = node; this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node); }, /** * @param {!DOMAgent.NodeId} parentId * @param {!DOMAgent.NodeId} nodeId */ _childNodeRemoved: function(parentId, nodeId) { var parent = this._idToDOMNode[parentId]; var node = this._idToDOMNode[nodeId]; parent._removeChild(node); this._unbind(node); this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: node, parent: parent}); }, /** * @param {!DOMAgent.NodeId} hostId * @param {!DOMAgent.Node} root */ _shadowRootPushed: function(hostId, root) { var host = this._idToDOMNode[hostId]; if (!host) return; var node = new WebInspector.DOMNode(this, host.ownerDocument, true, root); node.parentNode = host; this._idToDOMNode[node.id] = node; host._shadowRoots.unshift(node); this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node); }, /** * @param {!DOMAgent.NodeId} hostId * @param {!DOMAgent.NodeId} rootId */ _shadowRootPopped: function(hostId, rootId) { var host = this._idToDOMNode[hostId]; if (!host) return; var root = this._idToDOMNode[rootId]; if (!root) return; host._removeChild(root); this._unbind(root); this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: root, parent: host}); }, /** * @param {!DOMAgent.NodeId} parentId * @param {!DOMAgent.Node} pseudoElement */ _pseudoElementAdded: function(parentId, pseudoElement) { var parent = this._idToDOMNode[parentId]; if (!parent) return; var node = new WebInspector.DOMNode(this, parent.ownerDocument, false, pseudoElement); node.parentNode = parent; this._idToDOMNode[node.id] = node; console.assert(!parent._pseudoElements.get(node.pseudoType())); parent._pseudoElements.set(node.pseudoType(), node); this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInserted, node); }, /** * @param {!DOMAgent.NodeId} parentId * @param {!DOMAgent.NodeId} pseudoElementId */ _pseudoElementRemoved: function(parentId, pseudoElementId) { var parent = this._idToDOMNode[parentId]; if (!parent) return; var pseudoElement = this._idToDOMNode[pseudoElementId]; if (!pseudoElement) return; parent._removeChild(pseudoElement); this._unbind(pseudoElement); this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeRemoved, {node: pseudoElement, parent: parent}); }, /** * @param {!WebInspector.DOMNode} node */ _unbind: function(node) { delete this._idToDOMNode[node.id]; for (var i = 0; node._children && i < node._children.length; ++i) this._unbind(node._children[i]); for (var i = 0; i < node._shadowRoots.length; ++i) this._unbind(node._shadowRoots[i]); var pseudoElements = node.pseudoElements(); for (var pseudoType of pseudoElements.keys()) this._unbind(pseudoElements.get(pseudoType)); if (node._templateContent) this._unbind(node._templateContent); }, /** * @param {!DOMAgent.NodeId} nodeId */ _inspectNodeRequested: function(nodeId) { this.dispatchEventToListeners(WebInspector.DOMModel.Events.NodeInspected, this.nodeForId(nodeId)); }, /** * @param {string} query * @param {boolean} includeUserAgentShadowDOM * @param {function(number)} searchCallback */ performSearch: function(query, includeUserAgentShadowDOM, searchCallback) { this.cancelSearch(); /** * @param {?Protocol.Error} error * @param {string} searchId * @param {number} resultsCount * @this {WebInspector.DOMModel} */ function callback(error, searchId, resultsCount) { this._searchId = searchId; searchCallback(resultsCount); } this._agent.performSearch(query, includeUserAgentShadowDOM, callback.bind(this)); }, /** * @param {string} query * @param {boolean} includeUserAgentShadowDOM * @return {!Promise.<number>} */ performSearchPromise: function(query, includeUserAgentShadowDOM) { return new Promise(performSearch.bind(this)); /** * @param {function(number)} resolve * @this {WebInspector.DOMModel} */ function performSearch(resolve) { this._agent.performSearch(query, includeUserAgentShadowDOM, callback.bind(this)); /** * @param {?Protocol.Error} error * @param {string} searchId * @param {number} resultsCount * @this {WebInspector.DOMModel} */ function callback(error, searchId, resultsCount) { if (!error) this._searchId = searchId; resolve(error ? 0 : resultsCount); } } }, /** * @param {number} index * @param {?function(?WebInspector.DOMNode)} callback */ searchResult: function(index, callback) { if (this._searchId) this._agent.getSearchResults(this._searchId, index, index + 1, searchResultsCallback.bind(this)); else callback(null); /** * @param {?Protocol.Error} error * @param {!Array.<number>} nodeIds * @this {WebInspector.DOMModel} */ function searchResultsCallback(error, nodeIds) { if (error) { console.error(error); callback(null); return; } if (nodeIds.length != 1) return; callback(this.nodeForId(nodeIds[0])); } }, cancelSearch: function() { if (this._searchId) { this._agent.discardSearchResults(this._searchId); delete this._searchId; } }, /** * @param {!DOMAgent.NodeId} nodeId * @param {string} selectors * @param {function(?DOMAgent.NodeId)=} callback */ querySelector: function(nodeId, selectors, callback) { this._agent.querySelector(nodeId, selectors, this._wrapClientCallback(callback)); }, /** * @param {!DOMAgent.NodeId} nodeId * @param {string} selectors * @param {function(!Array.<!DOMAgent.NodeId>=)=} callback */ querySelectorAll: function(nodeId, selectors, callback) { this._agent.querySelectorAll(nodeId, selectors, this._wrapClientCallback(callback)); }, /** * @param {!DOMAgent.NodeId=} nodeId * @param {string=} mode * @param {!RuntimeAgent.RemoteObjectId=} objectId */ highlightDOMNode: function(nodeId, mode, objectId) { this.highlightDOMNodeWithConfig(nodeId, { mode: mode }, objectId); }, /** * @param {!DOMAgent.NodeId=} nodeId * @param {!{mode: (string|undefined), showInfo: (boolean|undefined)}=} config * @param {!RuntimeAgent.RemoteObjectId=} objectId */ highlightDOMNodeWithConfig: function(nodeId, config, objectId) { config = config || { mode: "all", showInfo: undefined }; if (this._hideDOMNodeHighlightTimeout) { clearTimeout(this._hideDOMNodeHighlightTimeout); delete this._hideDOMNodeHighlightTimeout; } var highlightConfig = this._buildHighlightConfig(