"use strict";
|
|
const EventTargetImpl = require("../events/EventTarget-impl").implementation;
|
const { domSymbolTree } = require("../helpers/internal-constants");
|
const { simultaneousIterators } = require("../../utils");
|
const DOMException = require("domexception");
|
const NODE_TYPE = require("../node-type");
|
const NODE_DOCUMENT_POSITION = require("../node-document-position");
|
const NodeList = require("../generated/NodeList");
|
const { documentBaseURLSerialized } = require("../helpers/document-base-url");
|
const { clone, locateNamespacePrefix, locateNamespace } = require("../node");
|
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._ownerDocument = privateData.ownerDocument;
|
|
this._childNodesList = null;
|
this._childrenList = null;
|
this._version = 0;
|
this._memoizedQueries = {};
|
}
|
|
get parentNode() {
|
return domSymbolTree.parent(this);
|
}
|
|
getRootNode() {
|
// ignore option for composed, because of no Shadow DOM support
|
let root;
|
for (const ancestor of domSymbolTree.ancestorsIterator(this)) {
|
root = ancestor;
|
}
|
return root;
|
}
|
|
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 isConnected() {
|
for (const ancestor of domSymbolTree.ancestorsIterator(this)) {
|
if (ancestor.nodeType === NODE_TYPE.DOCUMENT_NODE) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
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 = NodeList.createImpl([], {
|
element: this,
|
query: () => domSymbolTree.childrenToArray(this)
|
});
|
} else {
|
this._childNodesList._update();
|
}
|
|
return this._childNodesList;
|
}
|
|
get nextSibling() {
|
return domSymbolTree.nextSibling(this);
|
}
|
|
get previousSibling() {
|
return domSymbolTree.previousSibling(this);
|
}
|
|
insertBefore(newChildImpl, refChildImpl) {
|
// 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("The operation would yield an incorrect node tree.", "HierarchyRequestError");
|
}
|
|
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("The operation would yield an incorrect node tree.", "HierarchyRequestError");
|
}
|
}
|
}
|
|
// 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("The object can not be found here.", "NotFoundError");
|
}
|
|
domSymbolTree.insertBefore(refChildImpl, newChildImpl);
|
}
|
|
this._modified();
|
if (newChildImpl.nodeType === NODE_TYPE.TEXT_NODE) {
|
this._childTextContentChangeSteps();
|
}
|
|
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) {
|
this._childrenList._update();
|
}
|
if (this._childNodesList) {
|
this._childNodesList._update();
|
}
|
this._clearMemoizedQueries();
|
}
|
|
_childTextContentChangeSteps() {
|
// Default: do nothing
|
}
|
|
_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) {
|
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("The object can not be found here.", "NotFoundError");
|
}
|
|
if (this._ownerDocument) {
|
this._ownerDocument._runPreRemovingSteps(oldChildImpl);
|
}
|
domSymbolTree.remove(oldChildImpl);
|
this._modified();
|
oldChildImpl._detach();
|
this._descendantRemoved(this, oldChildImpl);
|
if (oldChildImpl.nodeType === NODE_TYPE.TEXT_NODE) {
|
this._childTextContentChangeSteps();
|
}
|
return oldChildImpl;
|
} // raises(DOMException);
|
|
appendChild(newChild) {
|
return this.insertBefore(newChild, null);
|
}
|
|
hasChildNodes() {
|
return domSymbolTree.hasChildren(this);
|
}
|
|
normalize() {
|
for (const child of domSymbolTree.childrenIterator(this)) {
|
if (child.normalize) {
|
child.normalize();
|
}
|
|
// Normalize should only transform Text nodes, and nothing else.
|
if (child.nodeType !== NODE_TYPE.TEXT_NODE) {
|
continue;
|
}
|
|
if (child.nodeValue === "") {
|
this.removeChild(child);
|
continue;
|
}
|
|
const prevChild = domSymbolTree.previousSibling(child);
|
|
if (prevChild && prevChild.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 (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;
|
}
|
|
lookupPrefix(namespace) {
|
if (namespace === null || namespace === "") {
|
return null;
|
}
|
|
switch (this.nodeType) {
|
case NODE_TYPE.ELEMENT_NODE: {
|
return locateNamespacePrefix(this, namespace);
|
}
|
case NODE_TYPE.DOCUMENT_NODE: {
|
return this.documentElement !== null ? locateNamespacePrefix(this.documentElement, namespace) : null;
|
}
|
case NODE_TYPE.DOCUMENT_TYPE_NODE:
|
case NODE_TYPE.DOCUMENT_FRAGMENT_NODE: {
|
return null;
|
}
|
case NODE_TYPE.ATTRIBUTE_NODE: {
|
return this._element !== null ? locateNamespacePrefix(this._element, namespace) : null;
|
}
|
default: {
|
return this.parentElement !== null ? locateNamespacePrefix(this.parentElement, namespace) : null;
|
}
|
}
|
}
|
|
lookupNamespaceURI(prefix) {
|
if (prefix === "") {
|
prefix = null;
|
}
|
|
return locateNamespace(this, prefix);
|
}
|
|
isDefaultNamespace(namespace) {
|
if (namespace === "") {
|
namespace = null;
|
}
|
|
const defaultNamespace = locateNamespace(this, null);
|
return defaultNamespace === namespace;
|
}
|
|
contains(other) {
|
if (other === null) {
|
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);
|
}
|
|
isSameNode(node) {
|
if (this === node) {
|
return true;
|
}
|
|
return false;
|
}
|
|
cloneNode(deep) {
|
deep = Boolean(deep);
|
|
return clone(this, undefined, deep);
|
}
|
|
get nodeValue() {
|
switch (this.nodeType) {
|
case NODE_TYPE.ATTRIBUTE_NODE: {
|
return this._value;
|
}
|
case NODE_TYPE.TEXT_NODE:
|
case NODE_TYPE.CDATA_SECTION_NODE: // CDATASection is a subclass of Text
|
case NODE_TYPE.PROCESSING_INSTRUCTION_NODE:
|
case NODE_TYPE.COMMENT_NODE: {
|
return this._data;
|
}
|
default: {
|
return null;
|
}
|
}
|
}
|
|
set nodeValue(value) {
|
if (value === null) {
|
value = "";
|
}
|
|
switch (this.nodeType) {
|
case NODE_TYPE.ATTRIBUTE_NODE: {
|
attributes.setAnExistingAttributeValue(this, value);
|
break;
|
}
|
case NODE_TYPE.TEXT_NODE:
|
case NODE_TYPE.CDATA_SECTION_NODE: // CDATASection is a subclass of Text
|
case NODE_TYPE.PROCESSING_INSTRUCTION_NODE:
|
case NODE_TYPE.COMMENT_NODE: {
|
this.replaceData(0, this.length, value);
|
break;
|
}
|
}
|
}
|
|
get textContent() {
|
switch (this.nodeType) {
|
case NODE_TYPE.DOCUMENT_FRAGMENT_NODE:
|
case NODE_TYPE.ELEMENT_NODE: {
|
let 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;
|
}
|
|
case NODE_TYPE.ATTRIBUTE_NODE: {
|
return this._value;
|
}
|
|
case NODE_TYPE.TEXT_NODE:
|
case NODE_TYPE.CDATA_SECTION_NODE: // CDATASection is a subclass of Text
|
case NODE_TYPE.PROCESSING_INSTRUCTION_NODE:
|
case NODE_TYPE.COMMENT_NODE: {
|
return this._data;
|
}
|
|
default: {
|
return null;
|
}
|
}
|
}
|
|
set textContent(value) {
|
switch (this.nodeType) {
|
case NODE_TYPE.DOCUMENT_FRAGMENT_NODE:
|
case NODE_TYPE.ELEMENT_NODE: {
|
let child = domSymbolTree.firstChild(this);
|
while (child) {
|
this.removeChild(child);
|
child = domSymbolTree.firstChild(this);
|
}
|
|
if (value !== null && value !== "") {
|
this.appendChild(this._ownerDocument.createTextNode(value));
|
}
|
|
break;
|
}
|
|
case NODE_TYPE.ATTRIBUTE_NODE: {
|
attributes.setAnExistingAttributeValue(this, value);
|
break;
|
}
|
|
case NODE_TYPE.TEXT_NODE:
|
case NODE_TYPE.CDATA_SECTION_NODE: // CDATASection is a subclass of Text
|
case NODE_TYPE.PROCESSING_INSTRUCTION_NODE:
|
case NODE_TYPE.COMMENT_NODE: {
|
this.replaceData(0, this.length, value);
|
break;
|
}
|
}
|
}
|
}
|
|
module.exports = {
|
implementation: NodeImpl
|
};
|