“use strict”;
const EventTargetImpl = require(“../events/EventTarget-impl”).implementation; const idlUtils = require(“../generated/utils”);
const domSymbolTree = require(“../helpers/internal-constants”).domSymbolTree; const simultaneousIterators = require(“../../utils”).simultaneousIterators; const DOMException = require(“../../web-idl/DOMException”); const NODE_TYPE = require(“../node-type”); const NODE_DOCUMENT_POSITION = require(“../node-document-position”); const createLiveNodeList = require(“../node-list”).createLive; const updateNodeList = require(“../node-list”).update; const updateHTMLCollection = require(“../html-collection”).update; const documentBaseURLSerialized = require(“../helpers/document-base-url”).documentBaseURLSerialized; const cloneNode = require(“../node”).clone; const attributes = require(“../attributes”);
function isObsoleteNodeType(node) {
return node.nodeType === NODE_TYPE.ENTITY_NODE || node.nodeType === NODE_TYPE.ENTITY_REFERENCE_NODE || node.nodeType === NODE_TYPE.NOTATION_NODE ||
// node.nodeType === NODE_TYPE.ATTRIBUTE_NODE || // this is missing how do we handle?
node.nodeType === NODE_TYPE.CDATA_SECTION_NODE;
}
function nodeEquals(a, b) {
if (a.nodeType !== b.nodeType) { return false; } switch (a.nodeType) { case NODE_TYPE.DOCUMENT_TYPE_NODE: if (a.name !== b.name || a.publicId !== b.publicId || a.systemId !== b.systemId) { return false; } break; case NODE_TYPE.ELEMENT_NODE: if (a._namespaceURI !== b._namespaceURI || a._prefix !== b._prefix || a._localName !== b._localName || a._attributes.length !== b._attributes.length) { return false; } break; case NODE_TYPE.PROCESSING_INSTRUCTION_NODE: if (a._target !== b._target || a._data !== b._data) { return false; } break; case NODE_TYPE.TEXT_NODE: case NODE_TYPE.COMMENT_NODE: if (a._data !== b._data) { return false; } break; } if (a.nodeType === NODE_TYPE.ELEMENT_NODE && !attributes.attributeListsEqual(a, b)) { return false; } for (const nodes of simultaneousIterators(domSymbolTree.childrenIterator(a), domSymbolTree.childrenIterator(b))) { if (!nodes[0] || !nodes[1]) { // mismatch in the amount of childNodes return false; } if (!nodeEquals(nodes[0], nodes[1])) { return false; } } return true;
}
class NodeImpl extends EventTargetImpl {
constructor(args, privateData) { super(); domSymbolTree.initialize(this); this._core = privateData.core; this._ownerDocument = privateData.ownerDocument; this._childNodesList = null; this._childrenList = null; this._version = 0; this._memoizedQueries = {}; } get nodeValue() { if (this.nodeType === NODE_TYPE.TEXT_NODE || this.nodeType === NODE_TYPE.COMMENT_NODE || this.nodeType === NODE_TYPE.CDATA_SECTION_NODE || this.nodeType === NODE_TYPE.PROCESSING_INSTRUCTION_NODE) { return this._data; } return null; } set nodeValue(value) { if (this.nodeType === NODE_TYPE.TEXT_NODE || this.nodeType === NODE_TYPE.COMMENT_NODE || this.nodeType === NODE_TYPE.CDATA_SECTION_NODE || this.nodeType === NODE_TYPE.PROCESSING_INSTRUCTION_NODE) { this.replaceData(0, this.length, value); } } get parentNode() { return domSymbolTree.parent(this); } get nodeName() { switch (this.nodeType) { case NODE_TYPE.ELEMENT_NODE: return this.tagName; case NODE_TYPE.TEXT_NODE: return "#text"; case NODE_TYPE.CDATA_SECTION_NODE: return "#cdata-section"; case NODE_TYPE.PROCESSING_INSTRUCTION_NODE: return this.target; case NODE_TYPE.COMMENT_NODE: return "#comment"; case NODE_TYPE.DOCUMENT_NODE: return "#document"; case NODE_TYPE.DOCUMENT_TYPE_NODE: return this.name; case NODE_TYPE.DOCUMENT_FRAGMENT_NODE: return "#document-fragment"; } // should never happen return null; } get firstChild() { return domSymbolTree.firstChild(this); } get ownerDocument() { return this.nodeType === NODE_TYPE.DOCUMENT_NODE ? null : this._ownerDocument; } get lastChild() { return domSymbolTree.lastChild(this); } get childNodes() { if (!this._childNodesList) { this._childNodesList = createLiveNodeList(this, () => domSymbolTree.childrenToArray(this)); } else { updateNodeList(this._childNodesList); } return this._childNodesList; } get nextSibling() { return domSymbolTree.nextSibling(this); } get previousSibling() { return domSymbolTree.previousSibling(this); } insertBefore(newChildImpl, refChildImpl) { // TODO branding if (!newChildImpl || !(newChildImpl instanceof NodeImpl)) { throw new TypeError("First argument to Node.prototype.insertBefore must be a Node"); } if (refChildImpl !== null && !(refChildImpl instanceof NodeImpl)) { throw new TypeError("Second argument to Node.prototype.insertBefore must be a Node or null or undefined"); } // DocumentType must be implicitly adopted if (newChildImpl.nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE) { newChildImpl._ownerDocument = this._ownerDocument; } if (newChildImpl.nodeType && newChildImpl.nodeType === NODE_TYPE.ATTRIBUTE_NODE) { throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR); } if (this._ownerDocument !== newChildImpl._ownerDocument) { // adopt the node when it's not in this document this._ownerDocument.adoptNode(newChildImpl); } else { // search for parents matching the newChild for (const ancestor of domSymbolTree.ancestorsIterator(this)) { if (ancestor === newChildImpl) { throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR); } } } // fragments are merged into the element (except parser-created fragments in <template>) if (newChildImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE) { let grandChildImpl; while ((grandChildImpl = domSymbolTree.firstChild(newChildImpl))) { newChildImpl.removeChild(grandChildImpl); this.insertBefore(grandChildImpl, refChildImpl); } } else if (newChildImpl === refChildImpl) { return newChildImpl; } else { const oldParentImpl = domSymbolTree.parent(newChildImpl); // if the newChild is already in the tree elsewhere, remove it first if (oldParentImpl) { oldParentImpl.removeChild(newChildImpl); } if (refChildImpl === null) { domSymbolTree.appendChild(this, newChildImpl); } else { if (domSymbolTree.parent(refChildImpl) !== this) { throw new DOMException(DOMException.NOT_FOUND_ERR); } domSymbolTree.insertBefore(refChildImpl, newChildImpl); } this._modified(); if (this._attached && newChildImpl._attach) { newChildImpl._attach(); } this._descendantAdded(this, newChildImpl); } return newChildImpl; } // raises(DOMException); _modified() { this._version++; for (const ancestor of domSymbolTree.ancestorsIterator(this)) { ancestor._version++; } if (this._childrenList) { updateHTMLCollection(this._childrenList); } if (this._childNodesList) { updateNodeList(this._childNodesList); } this._clearMemoizedQueries(); } _clearMemoizedQueries() { this._memoizedQueries = {}; const myParent = domSymbolTree.parent(this); if (myParent) { myParent._clearMemoizedQueries(); } } _descendantRemoved(parent, child) { const myParent = domSymbolTree.parent(this); if (myParent) { myParent._descendantRemoved(parent, child); } } _descendantAdded(parent, child) { const myParent = domSymbolTree.parent(this); if (myParent) { myParent._descendantAdded(parent, child); } } replaceChild(node, child) { if (arguments.length < 2) { throw new TypeError("Not enough arguments to Node.prototype.replaceChild"); } // TODO branding if (!node || !(node instanceof NodeImpl)) { throw new TypeError("First argument to Node.prototype.replaceChild must be a Node"); } if (!child || !(child instanceof NodeImpl)) { throw new TypeError("Second argument to Node.prototype.replaceChild must be a Node"); } this.insertBefore(node, child); return this.removeChild(child); } _attach() { this._attached = true; for (const child of domSymbolTree.childrenIterator(this)) { if (child._attach) { child._attach(); } } } _detach() { this._attached = false; if (this._ownerDocument && this._ownerDocument._lastFocusedElement === this) { this._ownerDocument._lastFocusedElement = null; } for (const child of domSymbolTree.childrenIterator(this)) { if (child._detach) { child._detach(); } } } removeChild(/* Node */ oldChildImpl) { if (!oldChildImpl || domSymbolTree.parent(oldChildImpl) !== this) { throw new DOMException(DOMException.NOT_FOUND_ERR); } const oldPreviousSibling = oldChildImpl.previousSibling; domSymbolTree.remove(oldChildImpl); this._modified(); oldChildImpl._detach(); this._descendantRemoved(this, oldChildImpl); if (this._ownerDocument) { this._ownerDocument._runRemovingSteps(oldChildImpl, this, oldPreviousSibling); } return oldChildImpl; } // raises(DOMException); appendChild(newChild) { if (!("nodeType" in newChild)) { throw new TypeError("First argument to Node.prototype.appendChild must be a Node"); } return this.insertBefore(newChild, null); } hasChildNodes() { return domSymbolTree.hasChildren(this); } normalize() { for (const child of domSymbolTree.childrenIterator(this)) { if (child.normalize) { child.normalize(); } if (child.nodeValue === "") { this.removeChild(child); continue; } const prevChild = domSymbolTree.previousSibling(child); if (prevChild && prevChild.nodeType === NODE_TYPE.TEXT_NODE && child.nodeType === NODE_TYPE.TEXT_NODE) { // merge text nodes prevChild.appendData(child.nodeValue); this.removeChild(child); } } } get parentElement() { const parentNode = domSymbolTree.parent(this); return parentNode !== null && parentNode.nodeType === NODE_TYPE.ELEMENT_NODE ? parentNode : null; } get baseURI() { return documentBaseURLSerialized(this._ownerDocument); } compareDocumentPosition(otherImpl) { // Let reference be the context object. const reference = this; if (!(otherImpl instanceof NodeImpl)) { throw new Error("Comparing position against non-Node values is not allowed"); } if (isObsoleteNodeType(reference) || isObsoleteNodeType(otherImpl)) { throw new Error("Obsolete node type"); } const result = domSymbolTree.compareTreePosition(reference, otherImpl); // “If other and reference are not in the same tree, return the result of adding DOCUMENT_POSITION_DISCONNECTED, // DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC, and either DOCUMENT_POSITION_PRECEDING or // DOCUMENT_POSITION_FOLLOWING, with the constraint that this is to be consistent, together.” if (result === NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_DISCONNECTED) { // symbol-tree does not add these bits required by the spec: return NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_DISCONNECTED | NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_FOLLOWING; } return result; } contains(other) { if (!(other instanceof NodeImpl)) { return false; } else if (this === other) { return true; } return Boolean(this.compareDocumentPosition(other) & NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_CONTAINED_BY); } isEqualNode(node) { if (node === null) { return false; } // Fast-path, not in the spec if (this === node) { return true; } return nodeEquals(this, node); } cloneNode(deep) { deep = Boolean(deep); return cloneNode(this._core, this, undefined, deep); } get textContent() { let text; switch (this.nodeType) { case NODE_TYPE.COMMENT_NODE: case NODE_TYPE.CDATA_SECTION_NODE: case NODE_TYPE.PROCESSING_INSTRUCTION_NODE: case NODE_TYPE.TEXT_NODE: return this.nodeValue; case NODE_TYPE.ATTRIBUTE_NODE: case NODE_TYPE.DOCUMENT_FRAGMENT_NODE: case NODE_TYPE.ELEMENT_NODE: text = ""; for (const child of domSymbolTree.treeIterator(this)) { if (child.nodeType === NODE_TYPE.TEXT_NODE || child.nodeType === NODE_TYPE.CDATA_SECTION_NODE) { text += child.nodeValue; } } return text; default: return null; } } set textContent(txt) { switch (this.nodeType) { case NODE_TYPE.COMMENT_NODE: case NODE_TYPE.CDATA_SECTION_NODE: case NODE_TYPE.PROCESSING_INSTRUCTION_NODE: case NODE_TYPE.TEXT_NODE: this.nodeValue = String(txt); return; } let child = domSymbolTree.firstChild(this); while (child) { this.removeChild(child); child = domSymbolTree.firstChild(this); } if (txt !== "" && txt !== null) { this.appendChild(this._ownerDocument.createTextNode(txt)); } } toString() { const wrapper = idlUtils.wrapperForImpl(this); return `[object ${wrapper.constructor.name}]`; }
}
module.exports = {
implementation: NodeImpl
};