jsdom
Version:
A JavaScript implementation of many web standards
1,162 lines (965 loc) • 34.6 kB
JavaScript
const DOMException = require("../generated/DOMException");
const EventTargetImpl = require("../events/EventTarget-impl").implementation;
const { simultaneousIterators } = require("../../utils");
const NODE_TYPE = require("../node-type");
const NODE_DOCUMENT_POSITION = require("../node-document-position");
const { clone, locateNamespacePrefix, locateNamespace } = require("../node");
const { setAnExistingAttributeValue } = require("../attributes");
const NodeList = require("../generated/NodeList");
const { nodeRoot, nodeLength, isInclusiveAncestor } = require("../helpers/node");
const { domSymbolTree } = require("../helpers/internal-constants");
const { documentBaseURLSerialized } = require("../helpers/document-base-url");
const { queueTreeMutationRecord } = require("../helpers/mutation-observers");
const { enqueueCECallbackReaction, tryUpgradeElement } = require("../helpers/custom-elements");
const {
isShadowRoot, shadowIncludingRoot, assignSlot, assignSlotableForTree, assignSlotable, signalSlotChange, isSlot,
shadowIncludingInclusiveDescendantsIterator, shadowIncludingDescendantsIterator
} = require("../helpers/shadow-dom");
const { invalidateStyleCache } = require("../helpers/style-rules");
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.ATTRIBUTE_NODE:
if (a._namespace !== b._namespace || a._localName !== b._localName || a._value !== b._value) {
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 && !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;
}
// Needed by https://dom.spec.whatwg.org/#concept-node-equals
function attributeListsEqual(elementA, elementB) {
const listA = elementA._attributeList;
const listB = elementB._attributeList;
const lengthA = listA.length;
const lengthB = listB.length;
if (lengthA !== lengthB) {
return false;
}
for (let i = 0; i < lengthA; ++i) {
const attrA = listA[i];
if (!listB.some(attrB => nodeEquals(attrA, attrB))) {
return false;
}
}
return true;
}
// https://dom.spec.whatwg.org/#concept-tree-host-including-inclusive-ancestor
function isHostInclusiveAncestor(nodeImplA, nodeImplB) {
for (const ancestor of domSymbolTree.ancestorsIterator(nodeImplB)) {
if (ancestor === nodeImplA) {
return true;
}
}
const rootImplB = nodeRoot(nodeImplB);
if (rootImplB._host) {
return isHostInclusiveAncestor(nodeImplA, rootImplB._host);
}
return false;
}
class NodeImpl extends EventTargetImpl {
constructor(globalObject, args, privateData) {
super(globalObject, args, privateData);
domSymbolTree.initialize(this);
this._ownerDocument = privateData.ownerDocument;
this._childNodesList = null;
this._childrenList = null;
this._version = 0;
this._memoizedQueries = {};
this._registeredObserverList = [];
this._referencedRanges = new Set();
}
_getTheParent() {
if (this._assignedSlot) {
return this._assignedSlot;
}
return domSymbolTree.parent(this);
}
get parentNode() {
return domSymbolTree.parent(this);
}
getRootNode(options) {
return options.composed ? shadowIncludingRoot(this) : nodeRoot(this);
}
get nodeName() {
switch (this.nodeType) {
case NODE_TYPE.ELEMENT_NODE:
return this.tagName;
case NODE_TYPE.ATTRIBUTE_NODE:
return this._qualifiedName;
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);
}
// https://dom.spec.whatwg.org/#connected
// https://dom.spec.whatwg.org/#dom-node-isconnected
get isConnected() {
const root = shadowIncludingRoot(this);
return root && root.nodeType === NODE_TYPE.DOCUMENT_NODE;
}
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(this._globalObject, [], {
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);
}
_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();
invalidateStyleCache(this);
}
_childTextContentChangeSteps() {
invalidateStyleCache(this);
}
_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);
}
}
_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();
}
}
}
hasChildNodes() {
return domSymbolTree.hasChildren(this);
}
// https://dom.spec.whatwg.org/#dom-node-normalize
normalize() {
// It is important to use a treeToArray instead of a treeToIterator here, because the
// treeToIterator doesn't support tree mutation in the middle of the traversal.
for (const node of domSymbolTree.treeToArray(this)) {
const parentNode = domSymbolTree.parent(node);
if (parentNode === null || node.nodeType !== NODE_TYPE.TEXT_NODE) {
continue;
}
let length = nodeLength(node);
if (length === 0) {
parentNode._remove(node);
continue;
}
const continuousExclusiveTextNodes = [];
for (const currentNode of domSymbolTree.previousSiblingsIterator(node)) {
if (currentNode.nodeType !== NODE_TYPE.TEXT_NODE) {
break;
}
continuousExclusiveTextNodes.unshift(currentNode);
}
for (const currentNode of domSymbolTree.nextSiblingsIterator(node)) {
if (currentNode.nodeType !== NODE_TYPE.TEXT_NODE) {
break;
}
continuousExclusiveTextNodes.push(currentNode);
}
const data = continuousExclusiveTextNodes.reduce((d, n) => d + n._data, "");
node.replaceData(length, 0, data);
let currentNode = domSymbolTree.nextSibling(node);
while (currentNode && currentNode.nodeType === NODE_TYPE.TEXT_NODE) {
const currentNodeIndex = domSymbolTree.index(currentNode);
for (const range of node._referencedRanges) {
const { _start, _end } = range;
if (_start.node === currentNode) {
range._setLiveRangeStart(node, _start.offset + length);
}
if (_end.node === currentNode) {
range._setLiveRangeEnd(node, _end.offset + length);
}
}
for (const range of parentNode._referencedRanges) {
const { _start, _end } = range;
if (_start.node === parentNode && _start.offset === currentNodeIndex) {
range._setLiveRangeStart(node, length);
}
if (_end.node === parentNode && _end.offset === currentNodeIndex) {
range._setLiveRangeEnd(node, length);
}
}
length += nodeLength(currentNode);
currentNode = domSymbolTree.nextSibling(currentNode);
}
for (const continuousExclusiveTextNode of continuousExclusiveTextNodes) {
parentNode._remove(continuousExclusiveTextNode);
}
}
}
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(other) {
// Let node1 be other and node2 be the context object.
let node1 = other;
let node2 = this;
let attr1 = null;
let attr2 = null;
if (node1.nodeType === NODE_TYPE.ATTRIBUTE_NODE) {
attr1 = node1;
node1 = attr1._element;
}
if (node2.nodeType === NODE_TYPE.ATTRIBUTE_NODE) {
attr2 = node2;
node2 = attr2._element;
if (attr1 !== null && node1 !== null && node2 === node1) {
for (const attr of node2._attributeList) {
if (nodeEquals(attr, attr1)) {
return NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_PRECEDING;
}
if (nodeEquals(attr, attr2)) {
return NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
NODE_DOCUMENT_POSITION.DOCUMENT_POSITION_FOLLOWING;
}
}
}
}
const result = domSymbolTree.compareTreePosition(node2, node1);
// “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) {
return isInclusiveAncestor(this, other);
}
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) {
if (isShadowRoot(this)) {
throw DOMException.create(this._globalObject, ["ShadowRoot nodes are not clonable.", "NotSupportedError"]);
}
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: {
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;
}
}
}
// https://dom.spec.whatwg.org/#dom-node-textcontent
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) {
if (value === null) {
value = "";
}
switch (this.nodeType) {
case NODE_TYPE.DOCUMENT_FRAGMENT_NODE:
case NODE_TYPE.ELEMENT_NODE: {
// https://dom.spec.whatwg.org/#string-replace-all
let nodeImpl = null;
if (value !== "") {
nodeImpl = this._ownerDocument.createTextNode(value);
}
this._replaceAll(nodeImpl);
break;
}
case NODE_TYPE.ATTRIBUTE_NODE: {
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;
}
}
}
// https://dom.spec.whatwg.org/#dom-node-insertbefore
insertBefore(nodeImpl, childImpl) {
return this._preInsert(nodeImpl, childImpl);
}
// https://dom.spec.whatwg.org/#dom-node-appendchild
appendChild(nodeImpl) {
return this._append(nodeImpl);
}
// https://dom.spec.whatwg.org/#dom-node-replacechild
replaceChild(nodeImpl, childImpl) {
return this._replace(nodeImpl, childImpl);
}
// https://dom.spec.whatwg.org/#dom-node-removechild
removeChild(oldChildImpl) {
return this._preRemove(oldChildImpl);
}
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
_preInsertValidity(nodeImpl, childImpl) {
const { nodeType, nodeName } = nodeImpl;
const { nodeType: parentType, nodeName: parentName } = this;
if (
parentType !== NODE_TYPE.DOCUMENT_NODE &&
parentType !== NODE_TYPE.DOCUMENT_FRAGMENT_NODE &&
parentType !== NODE_TYPE.ELEMENT_NODE
) {
throw DOMException.create(this._globalObject, [
`Node can't be inserted in a ${parentName} parent.`,
"HierarchyRequestError"
]);
}
if (isHostInclusiveAncestor(nodeImpl, this)) {
throw DOMException.create(this._globalObject, [
"The operation would yield an incorrect node tree.",
"HierarchyRequestError"
]);
}
if (childImpl && domSymbolTree.parent(childImpl) !== this) {
throw DOMException.create(this._globalObject, [
"The child can not be found in the parent.",
"NotFoundError"
]);
}
if (
nodeType !== NODE_TYPE.DOCUMENT_FRAGMENT_NODE &&
nodeType !== NODE_TYPE.DOCUMENT_TYPE_NODE &&
nodeType !== NODE_TYPE.ELEMENT_NODE &&
nodeType !== NODE_TYPE.TEXT_NODE &&
nodeType !== NODE_TYPE.CDATA_SECTION_NODE && // CData section extends from Text
nodeType !== NODE_TYPE.PROCESSING_INSTRUCTION_NODE &&
nodeType !== NODE_TYPE.COMMENT_NODE
) {
throw DOMException.create(this._globalObject, [
`${nodeName} node can't be inserted in parent node.`,
"HierarchyRequestError"
]);
}
if (
(nodeType === NODE_TYPE.TEXT_NODE && parentType === NODE_TYPE.DOCUMENT_NODE) ||
(nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE && parentType !== NODE_TYPE.DOCUMENT_NODE)
) {
throw DOMException.create(this._globalObject, [
`${nodeName} node can't be inserted in ${parentName} parent.`,
"HierarchyRequestError"
]);
}
if (parentType === NODE_TYPE.DOCUMENT_NODE) {
const nodeChildren = domSymbolTree.childrenToArray(nodeImpl);
const parentChildren = domSymbolTree.childrenToArray(this);
switch (nodeType) {
case NODE_TYPE.DOCUMENT_FRAGMENT_NODE: {
const nodeChildrenElements = nodeChildren.filter(child => child.nodeType === NODE_TYPE.ELEMENT_NODE);
if (nodeChildrenElements.length > 1) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
const hasNodeTextChildren = nodeChildren.some(child => child.nodeType === NODE_TYPE.TEXT_NODE);
if (hasNodeTextChildren) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
if (
nodeChildrenElements.length === 1 &&
(
parentChildren.some(child => child.nodeType === NODE_TYPE.ELEMENT_NODE) ||
(childImpl && childImpl.nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE) ||
(
childImpl &&
domSymbolTree.nextSibling(childImpl) &&
domSymbolTree.nextSibling(childImpl).nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE
)
)
) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
break;
}
case NODE_TYPE.ELEMENT_NODE:
if (
parentChildren.some(child => child.nodeType === NODE_TYPE.ELEMENT_NODE) ||
(childImpl && childImpl.nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE) ||
(
childImpl &&
domSymbolTree.nextSibling(childImpl) &&
domSymbolTree.nextSibling(childImpl).nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE
)
) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
break;
case NODE_TYPE.DOCUMENT_TYPE_NODE:
if (
parentChildren.some(child => child.nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE) ||
(
childImpl &&
domSymbolTree.previousSibling(childImpl) &&
domSymbolTree.previousSibling(childImpl).nodeType === NODE_TYPE.ELEMENT_NODE
) ||
(!childImpl && parentChildren.some(child => child.nodeType === NODE_TYPE.ELEMENT_NODE))
) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
break;
}
}
}
// https://dom.spec.whatwg.org/#concept-node-pre-insert
_preInsert(nodeImpl, childImpl) {
this._preInsertValidity(nodeImpl, childImpl);
let referenceChildImpl = childImpl;
if (referenceChildImpl === nodeImpl) {
referenceChildImpl = domSymbolTree.nextSibling(nodeImpl);
}
this._ownerDocument._adoptNode(nodeImpl);
this._insert(nodeImpl, referenceChildImpl);
return nodeImpl;
}
// https://dom.spec.whatwg.org/#concept-node-insert
_insert(nodeImpl, childImpl, suppressObservers) {
const count = nodeImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE ?
domSymbolTree.childrenCount(nodeImpl) :
1;
if (childImpl) {
const childIndex = domSymbolTree.index(childImpl);
for (const range of this._referencedRanges) {
const { _start, _end } = range;
if (_start.offset > childIndex) {
range._setLiveRangeStart(this, _start.offset + count);
}
if (_end.offset > childIndex) {
range._setLiveRangeEnd(this, _end.offset + count);
}
}
}
const nodesImpl = nodeImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE ?
domSymbolTree.childrenToArray(nodeImpl) :
[nodeImpl];
if (nodeImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE) {
let grandChildImpl;
while ((grandChildImpl = domSymbolTree.firstChild(nodeImpl))) {
nodeImpl._remove(grandChildImpl, true);
}
}
if (nodeImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE) {
queueTreeMutationRecord(nodeImpl, [], nodesImpl, null, null);
}
const previousChildImpl = childImpl ?
domSymbolTree.previousSibling(childImpl) :
domSymbolTree.lastChild(this);
let isConnected;
for (const node of nodesImpl) {
if (!childImpl) {
domSymbolTree.appendChild(this, node);
} else {
domSymbolTree.insertBefore(childImpl, node);
}
if (
(this.nodeType === NODE_TYPE.ELEMENT_NODE && this._shadowRoot !== null) &&
(node.nodeType === NODE_TYPE.ELEMENT_NODE || node.nodeType === NODE_TYPE.TEXT_NODE)
) {
assignSlot(node);
}
this._modified();
if (node.nodeType === NODE_TYPE.TEXT_NODE ||
node.nodeType === NODE_TYPE.CDATA_SECTION_NODE) {
this._childTextContentChangeSteps();
}
if (isSlot(this) && this._assignedNodes.length === 0 && isShadowRoot(nodeRoot(this))) {
signalSlotChange(this);
}
const root = nodeRoot(node);
if (isShadowRoot(root)) {
assignSlotableForTree(root);
}
if (this._attached && nodeImpl._attach) {
node._attach();
}
this._descendantAdded(this, node);
if (isConnected === undefined) {
isConnected = node.isConnected;
}
if (isConnected) {
for (const inclusiveDescendant of shadowIncludingInclusiveDescendantsIterator(node)) {
if (inclusiveDescendant._ceState === "custom") {
enqueueCECallbackReaction(inclusiveDescendant, "connectedCallback", []);
} else {
tryUpgradeElement(inclusiveDescendant);
}
}
}
}
if (!suppressObservers) {
queueTreeMutationRecord(this, nodesImpl, [], previousChildImpl, childImpl);
}
}
// https://dom.spec.whatwg.org/#concept-node-append
_append(nodeImpl) {
return this._preInsert(nodeImpl, null);
}
// https://dom.spec.whatwg.org/#concept-node-replace
_replace(nodeImpl, childImpl) {
const { nodeType, nodeName } = nodeImpl;
const { nodeType: parentType, nodeName: parentName } = this;
// Note: This section differs from the pre-insert validation algorithm.
if (
parentType !== NODE_TYPE.DOCUMENT_NODE &&
parentType !== NODE_TYPE.DOCUMENT_FRAGMENT_NODE &&
parentType !== NODE_TYPE.ELEMENT_NODE
) {
throw DOMException.create(this._globalObject, [
`Node can't be inserted in a ${parentName} parent.`,
"HierarchyRequestError"
]);
}
if (isHostInclusiveAncestor(nodeImpl, this)) {
throw DOMException.create(this._globalObject, [
"The operation would yield an incorrect node tree.",
"HierarchyRequestError"
]);
}
if (childImpl && domSymbolTree.parent(childImpl) !== this) {
throw DOMException.create(this._globalObject, [
"The child can not be found in the parent.",
"NotFoundError"
]);
}
if (
nodeType !== NODE_TYPE.DOCUMENT_FRAGMENT_NODE &&
nodeType !== NODE_TYPE.DOCUMENT_TYPE_NODE &&
nodeType !== NODE_TYPE.ELEMENT_NODE &&
nodeType !== NODE_TYPE.TEXT_NODE &&
nodeType !== NODE_TYPE.CDATA_SECTION_NODE && // CData section extends from Text
nodeType !== NODE_TYPE.PROCESSING_INSTRUCTION_NODE &&
nodeType !== NODE_TYPE.COMMENT_NODE
) {
throw DOMException.create(this._globalObject, [
`${nodeName} node can't be inserted in parent node.`,
"HierarchyRequestError"
]);
}
if (
(nodeType === NODE_TYPE.TEXT_NODE && parentType === NODE_TYPE.DOCUMENT_NODE) ||
(nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE && parentType !== NODE_TYPE.DOCUMENT_NODE)
) {
throw DOMException.create(this._globalObject, [
`${nodeName} node can't be inserted in ${parentName} parent.`,
"HierarchyRequestError"
]);
}
if (parentType === NODE_TYPE.DOCUMENT_NODE) {
const nodeChildren = domSymbolTree.childrenToArray(nodeImpl);
const parentChildren = domSymbolTree.childrenToArray(this);
switch (nodeType) {
case NODE_TYPE.DOCUMENT_FRAGMENT_NODE: {
const nodeChildrenElements = nodeChildren.filter(child => child.nodeType === NODE_TYPE.ELEMENT_NODE);
if (nodeChildrenElements.length > 1) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
const hasNodeTextChildren = nodeChildren.some(child => child.nodeType === NODE_TYPE.TEXT_NODE);
if (hasNodeTextChildren) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
const parentChildElements = parentChildren.filter(child => child.nodeType === NODE_TYPE.ELEMENT_NODE);
if (
nodeChildrenElements.length === 1 &&
(
(parentChildElements.length === 1 && parentChildElements[0] !== childImpl) ||
(
childImpl &&
domSymbolTree.nextSibling(childImpl) &&
domSymbolTree.nextSibling(childImpl).nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE
)
)
) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
break;
}
case NODE_TYPE.ELEMENT_NODE:
if (
parentChildren.some(child => child.nodeType === NODE_TYPE.ELEMENT_NODE && child !== childImpl) ||
(
childImpl &&
domSymbolTree.nextSibling(childImpl) &&
domSymbolTree.nextSibling(childImpl).nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE
)
) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
break;
case NODE_TYPE.DOCUMENT_TYPE_NODE:
if (
parentChildren.some(child => child.nodeType === NODE_TYPE.DOCUMENT_TYPE_NODE && child !== childImpl) ||
(
childImpl &&
domSymbolTree.previousSibling(childImpl) &&
domSymbolTree.previousSibling(childImpl).nodeType === NODE_TYPE.ELEMENT_NODE
)
) {
throw DOMException.create(this._globalObject, [
`Invalid insertion of ${nodeName} node in ${parentName} node.`,
"HierarchyRequestError"
]);
}
break;
}
}
let referenceChildImpl = domSymbolTree.nextSibling(childImpl);
if (referenceChildImpl === nodeImpl) {
referenceChildImpl = domSymbolTree.nextSibling(nodeImpl);
}
const previousSiblingImpl = domSymbolTree.previousSibling(childImpl);
this._ownerDocument._adoptNode(nodeImpl);
let removedNodesImpl = [];
if (domSymbolTree.parent(childImpl)) {
removedNodesImpl = [childImpl];
this._remove(childImpl, true);
}
const nodesImpl = nodeImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE ?
domSymbolTree.childrenToArray(nodeImpl) :
[nodeImpl];
this._insert(nodeImpl, referenceChildImpl, true);
queueTreeMutationRecord(this, nodesImpl, removedNodesImpl, previousSiblingImpl, referenceChildImpl);
return childImpl;
}
// https://dom.spec.whatwg.org/#concept-node-replace-all
_replaceAll(nodeImpl) {
if (nodeImpl !== null) {
this._ownerDocument._adoptNode(nodeImpl);
}
const removedNodesImpl = domSymbolTree.childrenToArray(this);
let addedNodesImpl;
if (nodeImpl === null) {
addedNodesImpl = [];
} else if (nodeImpl.nodeType === NODE_TYPE.DOCUMENT_FRAGMENT_NODE) {
addedNodesImpl = domSymbolTree.childrenToArray(nodeImpl);
} else {
addedNodesImpl = [nodeImpl];
}
for (const childImpl of domSymbolTree.childrenIterator(this)) {
this._remove(childImpl, true);
}
if (nodeImpl !== null) {
this._insert(nodeImpl, null, true);
}
if (addedNodesImpl.length > 0 || removedNodesImpl.length > 0) {
queueTreeMutationRecord(this, addedNodesImpl, removedNodesImpl, null, null);
}
}
// https://dom.spec.whatwg.org/#concept-node-pre-remove
_preRemove(childImpl) {
if (domSymbolTree.parent(childImpl) !== this) {
throw DOMException.create(this._globalObject, [
"The node to be removed is not a child of this node.",
"NotFoundError"
]);
}
this._remove(childImpl);
return childImpl;
}
// https://dom.spec.whatwg.org/#concept-node-remove
_remove(nodeImpl, suppressObservers) {
const index = domSymbolTree.index(nodeImpl);
for (const descendant of domSymbolTree.treeIterator(nodeImpl)) {
for (const range of descendant._referencedRanges) {
const { _start, _end } = range;
if (_start.node === descendant) {
range._setLiveRangeStart(this, index);
}
if (_end.node === descendant) {
range._setLiveRangeEnd(this, index);
}
}
}
for (const range of this._referencedRanges) {
const { _start, _end } = range;
if (_start.node === this && _start.offset > index) {
range._setLiveRangeStart(this, _start.offset - 1);
}
if (_end.node === this && _end.offset > index) {
range._setLiveRangeEnd(this, _end.offset - 1);
}
}
if (this._ownerDocument) {
this._ownerDocument._runPreRemovingSteps(nodeImpl);
}
const oldPreviousSiblingImpl = domSymbolTree.previousSibling(nodeImpl);
const oldNextSiblingImpl = domSymbolTree.nextSibling(nodeImpl);
domSymbolTree.remove(nodeImpl);
if (nodeImpl._assignedSlot) {
assignSlotable(nodeImpl._assignedSlot);
}
if (isSlot(this) && this._assignedNodes.length === 0 && isShadowRoot(nodeRoot(this))) {
signalSlotChange(this);
}
let hasSlotDescendant = isSlot(nodeImpl);
if (!hasSlotDescendant) {
for (const child of domSymbolTree.treeIterator(nodeImpl)) {
if (isSlot(child)) {
hasSlotDescendant = true;
break;
}
}
}
if (hasSlotDescendant) {
assignSlotableForTree(nodeRoot(this));
assignSlotableForTree(nodeImpl);
}
this._modified();
nodeImpl._detach();
this._descendantRemoved(this, nodeImpl);
if (this.isConnected) {
if (nodeImpl._ceState === "custom") {
enqueueCECallbackReaction(nodeImpl, "disconnectedCallback", []);
}
for (const descendantImpl of shadowIncludingDescendantsIterator(nodeImpl)) {
if (descendantImpl._ceState === "custom") {
enqueueCECallbackReaction(descendantImpl, "disconnectedCallback", []);
}
}
}
if (!suppressObservers) {
queueTreeMutationRecord(this, [], [nodeImpl], oldPreviousSiblingImpl, oldNextSiblingImpl);
}
if (nodeImpl.nodeType === NODE_TYPE.TEXT_NODE) {
this._childTextContentChangeSteps();
}
}
}
module.exports = {
implementation: NodeImpl
};
;