1080 lines
41 KiB
JavaScript
1080 lines
41 KiB
JavaScript
|
|
import EventTarget from '../../event/EventTarget.js';
|
|||
|
|
import * as PropertySymbol from '../../PropertySymbol.js';
|
|||
|
|
import NodeTypeEnum from './NodeTypeEnum.js';
|
|||
|
|
import NodeDocumentPositionEnum from './NodeDocumentPositionEnum.js';
|
|||
|
|
import NodeUtility from './NodeUtility.js';
|
|||
|
|
import NodeList from './NodeList.js';
|
|||
|
|
import MutationRecord from '../../mutation-observer/MutationRecord.js';
|
|||
|
|
import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum.js';
|
|||
|
|
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
|
|||
|
|
import NodeFactory from '../NodeFactory.js';
|
|||
|
|
/**
|
|||
|
|
* Node.
|
|||
|
|
*/
|
|||
|
|
export default class Node extends EventTarget {
|
|||
|
|
// Public properties
|
|||
|
|
static ELEMENT_NODE = NodeTypeEnum.elementNode;
|
|||
|
|
static ATTRIBUTE_NODE = NodeTypeEnum.attributeNode;
|
|||
|
|
static TEXT_NODE = NodeTypeEnum.textNode;
|
|||
|
|
static CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode;
|
|||
|
|
static COMMENT_NODE = NodeTypeEnum.commentNode;
|
|||
|
|
static DOCUMENT_NODE = NodeTypeEnum.documentNode;
|
|||
|
|
static DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode;
|
|||
|
|
static DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode;
|
|||
|
|
static PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode;
|
|||
|
|
static DOCUMENT_POSITION_CONTAINED_BY = NodeDocumentPositionEnum.containedBy;
|
|||
|
|
static DOCUMENT_POSITION_CONTAINS = NodeDocumentPositionEnum.contains;
|
|||
|
|
static DOCUMENT_POSITION_DISCONNECTED = NodeDocumentPositionEnum.disconnect;
|
|||
|
|
static DOCUMENT_POSITION_FOLLOWING = NodeDocumentPositionEnum.following;
|
|||
|
|
static DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = NodeDocumentPositionEnum.implementationSpecific;
|
|||
|
|
static DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum.preceding;
|
|||
|
|
// Internal properties
|
|||
|
|
[PropertySymbol.isConnected] = false;
|
|||
|
|
[PropertySymbol.parentNode] = null;
|
|||
|
|
[PropertySymbol.rootNode] = null;
|
|||
|
|
[PropertySymbol.styleNode] = null;
|
|||
|
|
[PropertySymbol.textAreaNode] = null;
|
|||
|
|
[PropertySymbol.formNode] = null;
|
|||
|
|
[PropertySymbol.selectNode] = null;
|
|||
|
|
[PropertySymbol.mutationListeners] = [];
|
|||
|
|
[PropertySymbol.nodeArray] = [];
|
|||
|
|
[PropertySymbol.elementArray] = [];
|
|||
|
|
[PropertySymbol.childNodes] = null;
|
|||
|
|
[PropertySymbol.assignedToSlot] = null;
|
|||
|
|
[PropertySymbol.cache] = {
|
|||
|
|
querySelector: new Map(),
|
|||
|
|
querySelectorAll: new Map(),
|
|||
|
|
matches: new Map(),
|
|||
|
|
elementsByTagName: new Map(),
|
|||
|
|
elementsByTagNameNS: new Map(),
|
|||
|
|
elementByTagName: new Map(),
|
|||
|
|
elementById: new Map(),
|
|||
|
|
computedStyle: null
|
|||
|
|
};
|
|||
|
|
[PropertySymbol.affectsCache] = [];
|
|||
|
|
/**
|
|||
|
|
* Constructor.
|
|||
|
|
*/
|
|||
|
|
constructor() {
|
|||
|
|
super();
|
|||
|
|
// Window injected by WindowContextClassExtender (used when the Node can be created using "new" keyword)
|
|||
|
|
if (this[PropertySymbol.window]) {
|
|||
|
|
this[PropertySymbol.ownerDocument] = this[PropertySymbol.window].document;
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
const ownerDocument = NodeFactory.pullOwnerDocument();
|
|||
|
|
if (!ownerDocument) {
|
|||
|
|
throw new TypeError('Illegal constructor');
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.ownerDocument] = ownerDocument;
|
|||
|
|
this[PropertySymbol.window] = ownerDocument[PropertySymbol.window];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns `Symbol.toStringTag`.
|
|||
|
|
*
|
|||
|
|
* @returns `Symbol.toStringTag`.
|
|||
|
|
*/
|
|||
|
|
get [Symbol.toStringTag]() {
|
|||
|
|
return this.constructor.name;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns connected state.
|
|||
|
|
*
|
|||
|
|
* @returns Connected state.
|
|||
|
|
*/
|
|||
|
|
get isConnected() {
|
|||
|
|
return this[PropertySymbol.isConnected];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns owner document.
|
|||
|
|
*
|
|||
|
|
* @returns Owner document.
|
|||
|
|
*/
|
|||
|
|
get ownerDocument() {
|
|||
|
|
return this[PropertySymbol.ownerDocument];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns parent node.
|
|||
|
|
*
|
|||
|
|
* @returns Parent node.
|
|||
|
|
*/
|
|||
|
|
get parentNode() {
|
|||
|
|
return this[PropertySymbol.parentNode];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns node type.
|
|||
|
|
*
|
|||
|
|
* @returns Node type.
|
|||
|
|
*/
|
|||
|
|
get nodeType() {
|
|||
|
|
return this[PropertySymbol.nodeType];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Get child nodes.
|
|||
|
|
*
|
|||
|
|
* @returns Child nodes list.
|
|||
|
|
*/
|
|||
|
|
get childNodes() {
|
|||
|
|
if (!this[PropertySymbol.childNodes]) {
|
|||
|
|
this[PropertySymbol.childNodes] = new NodeList(PropertySymbol.illegalConstructor, this[PropertySymbol.nodeArray]);
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.childNodes];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Get text value of children.
|
|||
|
|
*
|
|||
|
|
* @returns Text content.
|
|||
|
|
*/
|
|||
|
|
get textContent() {
|
|||
|
|
// Sub-classes should implement this method.
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Sets text content.
|
|||
|
|
*
|
|||
|
|
* @param _textContent Text content.
|
|||
|
|
*/
|
|||
|
|
set textContent(_textContent) {
|
|||
|
|
// Do nothing.
|
|||
|
|
// Sub-classes should implement this method.
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Node value.
|
|||
|
|
*
|
|||
|
|
* @returns Node value.
|
|||
|
|
*/
|
|||
|
|
get nodeValue() {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Sets node value.
|
|||
|
|
*/
|
|||
|
|
set nodeValue(_nodeValue) {
|
|||
|
|
// Do nothing
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Node name.
|
|||
|
|
*
|
|||
|
|
* @returns Node name.
|
|||
|
|
*/
|
|||
|
|
get nodeName() {
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Previous sibling.
|
|||
|
|
*
|
|||
|
|
* @returns Node.
|
|||
|
|
*/
|
|||
|
|
get previousSibling() {
|
|||
|
|
if (this[PropertySymbol.parentNode]) {
|
|||
|
|
const nodeArray = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
|
|||
|
|
const index = nodeArray.indexOf(this[PropertySymbol.proxy] || this);
|
|||
|
|
if (index > 0) {
|
|||
|
|
return nodeArray[index - 1];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Next sibling.
|
|||
|
|
*
|
|||
|
|
* @returns Node.
|
|||
|
|
*/
|
|||
|
|
get nextSibling() {
|
|||
|
|
if (this[PropertySymbol.parentNode]) {
|
|||
|
|
const nodeArray = this[PropertySymbol.parentNode][PropertySymbol.nodeArray];
|
|||
|
|
const index = nodeArray.indexOf(this[PropertySymbol.proxy] || this);
|
|||
|
|
if (index > -1 && index + 1 < nodeArray.length) {
|
|||
|
|
return nodeArray[index + 1];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* First child.
|
|||
|
|
*
|
|||
|
|
* @returns Node.
|
|||
|
|
*/
|
|||
|
|
get firstChild() {
|
|||
|
|
const nodeArray = this[PropertySymbol.nodeArray];
|
|||
|
|
if (nodeArray.length > 0) {
|
|||
|
|
return nodeArray[0];
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Last child.
|
|||
|
|
*
|
|||
|
|
* @returns Node.
|
|||
|
|
*/
|
|||
|
|
get lastChild() {
|
|||
|
|
const nodeArray = this[PropertySymbol.nodeArray];
|
|||
|
|
if (nodeArray.length > 0) {
|
|||
|
|
return nodeArray[nodeArray.length - 1];
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns parent element.
|
|||
|
|
*
|
|||
|
|
* @returns Element.
|
|||
|
|
*/
|
|||
|
|
get parentElement() {
|
|||
|
|
let parent = this[PropertySymbol.parentNode];
|
|||
|
|
while (parent && parent[PropertySymbol.nodeType] !== NodeTypeEnum.elementNode) {
|
|||
|
|
parent = parent[PropertySymbol.parentNode];
|
|||
|
|
}
|
|||
|
|
return parent;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns base URI.
|
|||
|
|
*
|
|||
|
|
* @returns Base URI.
|
|||
|
|
*/
|
|||
|
|
get baseURI() {
|
|||
|
|
const base = this[PropertySymbol.ownerDocument].querySelector('base');
|
|||
|
|
if (base) {
|
|||
|
|
return base.href;
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.window].location.href;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns "true" if the node has child nodes.
|
|||
|
|
*
|
|||
|
|
* @returns "true" if the node has child nodes.
|
|||
|
|
*/
|
|||
|
|
hasChildNodes() {
|
|||
|
|
return this[PropertySymbol.nodeArray].length > 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns "true" if this node contains the other node.
|
|||
|
|
*
|
|||
|
|
* @param otherNode Node to test with.
|
|||
|
|
* @returns "true" if this node contains the other node.
|
|||
|
|
*/
|
|||
|
|
contains(otherNode) {
|
|||
|
|
if (otherNode === undefined) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
return NodeUtility.isInclusiveAncestor(this, otherNode);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Returns closest root node (Document or ShadowRoot).
|
|||
|
|
*
|
|||
|
|
* @param options Options.
|
|||
|
|
* @param options.composed A Boolean that indicates whether the shadow root should be returned (false, the default), or a root node beyond shadow root (true).
|
|||
|
|
* @returns Node.
|
|||
|
|
*/
|
|||
|
|
getRootNode(options) {
|
|||
|
|
if (!this[PropertySymbol.isConnected]) {
|
|||
|
|
return this;
|
|||
|
|
}
|
|||
|
|
if (this[PropertySymbol.rootNode] && !options?.composed) {
|
|||
|
|
return this[PropertySymbol.rootNode];
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.ownerDocument];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Clones a node.
|
|||
|
|
*
|
|||
|
|
* @param [deep=false] "true" to clone deep.
|
|||
|
|
* @returns Cloned node.
|
|||
|
|
*/
|
|||
|
|
cloneNode(deep = false) {
|
|||
|
|
return this[PropertySymbol.cloneNode](deep);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Append a child node to childNodes.
|
|||
|
|
*
|
|||
|
|
* @param node Node to append.
|
|||
|
|
* @returns Appended node.
|
|||
|
|
*/
|
|||
|
|
appendChild(node) {
|
|||
|
|
if (arguments.length < 1) {
|
|||
|
|
throw new this[PropertySymbol.window].TypeError(`Failed to execute 'appendChild' on 'Node': 1 argument required, but only 0 present`);
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.appendChild](node);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Remove Child element from childNodes array.
|
|||
|
|
*
|
|||
|
|
* @param node Node to remove.
|
|||
|
|
* @returns Removed node.
|
|||
|
|
*/
|
|||
|
|
removeChild(node) {
|
|||
|
|
if (arguments.length < 1) {
|
|||
|
|
throw new this[PropertySymbol.window].TypeError(`Failed to execute 'removeChild' on 'Node': 1 argument required, but only 0 present`);
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.removeChild](node);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Inserts a node before another.
|
|||
|
|
*
|
|||
|
|
* @param newNode Node to insert.
|
|||
|
|
* @param referenceNode Node to insert before.
|
|||
|
|
* @returns Inserted node.
|
|||
|
|
*/
|
|||
|
|
insertBefore(newNode, referenceNode) {
|
|||
|
|
if (arguments.length < 2) {
|
|||
|
|
throw new this[PropertySymbol.window].TypeError(`Failed to execute 'insertBefore' on 'Node': 2 arguments required, but only ${arguments.length} present.`);
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.insertBefore](newNode, referenceNode);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Replaces a node with another.
|
|||
|
|
*
|
|||
|
|
* @param newChild New child.
|
|||
|
|
* @param oldChild Old child.
|
|||
|
|
* @returns Replaced node.
|
|||
|
|
*/
|
|||
|
|
replaceChild(newChild, oldChild) {
|
|||
|
|
if (arguments.length < 2) {
|
|||
|
|
throw new this[PropertySymbol.window].TypeError(`Failed to execute 'replaceChild' on 'Node': 2 arguments required, but only ${arguments.length} present.`);
|
|||
|
|
}
|
|||
|
|
return this[PropertySymbol.replaceChild](newChild, oldChild);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Clones a node.
|
|||
|
|
*
|
|||
|
|
* @param [deep=false] "true" to clone deep.
|
|||
|
|
* @returns Cloned node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.cloneNode](deep = false) {
|
|||
|
|
const clone = NodeFactory.createNode(this[PropertySymbol.ownerDocument], this.constructor);
|
|||
|
|
// Document has childNodes directly when it is created
|
|||
|
|
// We need to remove them
|
|||
|
|
if (clone[PropertySymbol.nodeArray].length) {
|
|||
|
|
const childNodes = clone[PropertySymbol.nodeArray];
|
|||
|
|
while (childNodes.length) {
|
|||
|
|
clone.removeChild(childNodes[0]);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (deep) {
|
|||
|
|
for (const childNode of this[PropertySymbol.nodeArray]) {
|
|||
|
|
const childClone = childNode.cloneNode(true);
|
|||
|
|
childClone[PropertySymbol.parentNode] = clone;
|
|||
|
|
clone[PropertySymbol.nodeArray].push(childClone);
|
|||
|
|
if (childClone[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
|
|||
|
|
clone[PropertySymbol.elementArray].push(childClone);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return clone;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Append a child node to childNodes.
|
|||
|
|
*
|
|||
|
|
* @param node Node to append.
|
|||
|
|
* @param [disableValidations=false] "true" to disable validations.
|
|||
|
|
* @returns Appended node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.appendChild](node, disableValidations = false) {
|
|||
|
|
if (node[PropertySymbol.proxy]) {
|
|||
|
|
node = node[PropertySymbol.proxy];
|
|||
|
|
}
|
|||
|
|
const self = this[PropertySymbol.proxy] || this;
|
|||
|
|
if (!disableValidations) {
|
|||
|
|
if (node === self) {
|
|||
|
|
throw new this[PropertySymbol.window].DOMException("Failed to execute 'appendChild' on 'Node': Not possible to append a node as a child of itself.");
|
|||
|
|
}
|
|||
|
|
if (NodeUtility.isInclusiveAncestor(node, self, true)) {
|
|||
|
|
throw new this[PropertySymbol.window].DOMException("Failed to execute 'appendChild' on 'Node': The new node is a parent of the node to insert to.", DOMExceptionNameEnum.domException);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
|
|||
|
|
// See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
|
|||
|
|
if (node[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode) {
|
|||
|
|
const childNodes = node[PropertySymbol.nodeArray];
|
|||
|
|
while (childNodes.length) {
|
|||
|
|
this.appendChild(childNodes[0]);
|
|||
|
|
}
|
|||
|
|
return node;
|
|||
|
|
}
|
|||
|
|
// Remove the node from its previous parent if it has any.
|
|||
|
|
if (node[PropertySymbol.parentNode]) {
|
|||
|
|
node[PropertySymbol.parentNode][PropertySymbol.removeChild](node);
|
|||
|
|
}
|
|||
|
|
node[PropertySymbol.parentNode] = self;
|
|||
|
|
node[PropertySymbol.clearCache]();
|
|||
|
|
this[PropertySymbol.nodeArray].push(node);
|
|||
|
|
if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
|
|||
|
|
this[PropertySymbol.elementArray].push(node);
|
|||
|
|
}
|
|||
|
|
node[PropertySymbol.connectedToNode]();
|
|||
|
|
// Mutation listeners
|
|||
|
|
for (const mutationListener of this[PropertySymbol.mutationListeners]) {
|
|||
|
|
if (mutationListener.options?.subtree && mutationListener.callback.deref()) {
|
|||
|
|
node[PropertySymbol.observeMutations](mutationListener);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.reportMutation](new MutationRecord({
|
|||
|
|
target: self,
|
|||
|
|
type: MutationTypeEnum.childList,
|
|||
|
|
addedNodes: [node]
|
|||
|
|
}));
|
|||
|
|
return node;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Remove Child element from childNodes array.
|
|||
|
|
*
|
|||
|
|
* @param node Node to remove.
|
|||
|
|
* @returns Removed node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.removeChild](node) {
|
|||
|
|
if (node[PropertySymbol.proxy]) {
|
|||
|
|
node = node[PropertySymbol.proxy];
|
|||
|
|
}
|
|||
|
|
const index = this[PropertySymbol.nodeArray].indexOf(node);
|
|||
|
|
if (index === -1) {
|
|||
|
|
throw new this[PropertySymbol.window].DOMException(`Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.`);
|
|||
|
|
}
|
|||
|
|
const previousSibling = node.previousSibling;
|
|||
|
|
const nextSibling = node.nextSibling;
|
|||
|
|
node[PropertySymbol.parentNode] = null;
|
|||
|
|
node[PropertySymbol.clearCache]();
|
|||
|
|
this[PropertySymbol.nodeArray].splice(index, 1);
|
|||
|
|
if (node[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
|
|||
|
|
const index = this[PropertySymbol.elementArray].indexOf(node);
|
|||
|
|
if (index !== -1) {
|
|||
|
|
this[PropertySymbol.elementArray].splice(index, 1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (node[PropertySymbol.assignedToSlot]) {
|
|||
|
|
const index = node[PropertySymbol.assignedToSlot][PropertySymbol.assignedNodes].indexOf(node);
|
|||
|
|
if (index !== -1) {
|
|||
|
|
node[PropertySymbol.assignedToSlot][PropertySymbol.assignedNodes].splice(index, 1);
|
|||
|
|
}
|
|||
|
|
node[PropertySymbol.assignedToSlot] = null;
|
|||
|
|
}
|
|||
|
|
node[PropertySymbol.disconnectedFromNode]();
|
|||
|
|
// Mutation listeners
|
|||
|
|
for (const mutationListener of this[PropertySymbol.mutationListeners]) {
|
|||
|
|
if (mutationListener.options?.subtree && mutationListener.callback.deref()) {
|
|||
|
|
node[PropertySymbol.unobserveMutations](mutationListener);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.reportMutation](new MutationRecord({
|
|||
|
|
target: this[PropertySymbol.proxy] || this,
|
|||
|
|
type: MutationTypeEnum.childList,
|
|||
|
|
removedNodes: [node],
|
|||
|
|
previousSibling,
|
|||
|
|
nextSibling
|
|||
|
|
}));
|
|||
|
|
return node;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Inserts a node before another.
|
|||
|
|
*
|
|||
|
|
* @param newNode Node to insert.
|
|||
|
|
* @param referenceNode Node to insert before.
|
|||
|
|
* @param [disableValidations=false] "true" to disable validations.
|
|||
|
|
* @returns Inserted node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.insertBefore](newNode, referenceNode, disableValidations = false) {
|
|||
|
|
if (newNode[PropertySymbol.proxy]) {
|
|||
|
|
newNode = newNode[PropertySymbol.proxy];
|
|||
|
|
}
|
|||
|
|
if (referenceNode && referenceNode[PropertySymbol.proxy]) {
|
|||
|
|
referenceNode = referenceNode[PropertySymbol.proxy];
|
|||
|
|
}
|
|||
|
|
if (newNode === referenceNode) {
|
|||
|
|
return newNode;
|
|||
|
|
}
|
|||
|
|
const self = this[PropertySymbol.proxy] || this;
|
|||
|
|
if (!disableValidations) {
|
|||
|
|
if (newNode === self) {
|
|||
|
|
throw new this[PropertySymbol.window].DOMException("Failed to execute 'insertBefore' on 'Node': Not possible to insert a node as a child of itself.");
|
|||
|
|
}
|
|||
|
|
if (NodeUtility.isInclusiveAncestor(newNode, self, true)) {
|
|||
|
|
throw new this[PropertySymbol.window].DOMException("Failed to execute 'insertBefore' on 'Node': The new node is a parent of the node to insert to.", DOMExceptionNameEnum.domException);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// If the type is DocumentFragment, then the child nodes of if it should be moved instead of the actual node.
|
|||
|
|
// See: https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
|
|||
|
|
if (newNode[PropertySymbol.nodeType] === NodeTypeEnum.documentFragmentNode) {
|
|||
|
|
const childNodes = newNode[PropertySymbol.nodeArray];
|
|||
|
|
while (childNodes.length > 0) {
|
|||
|
|
this[PropertySymbol.insertBefore](childNodes[0], referenceNode, true);
|
|||
|
|
}
|
|||
|
|
return newNode;
|
|||
|
|
}
|
|||
|
|
// If the referenceNode is null or undefined, then the newNode should be appended to the ancestorNode.
|
|||
|
|
// According to spec only null is valid, but browsers support undefined as well.
|
|||
|
|
if (!referenceNode) {
|
|||
|
|
this[PropertySymbol.appendChild](newNode, true);
|
|||
|
|
return newNode;
|
|||
|
|
}
|
|||
|
|
const nodeArray = this[PropertySymbol.nodeArray];
|
|||
|
|
// We need to check if the referenceNode is a child of this node before removing it from its parent, as the parent may be the same node and the index would be wrong.
|
|||
|
|
if (!nodeArray.includes(referenceNode)) {
|
|||
|
|
throw new this[PropertySymbol.window].DOMException("Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.");
|
|||
|
|
}
|
|||
|
|
if (newNode[PropertySymbol.parentNode]) {
|
|||
|
|
newNode[PropertySymbol.parentNode][PropertySymbol.removeChild](newNode);
|
|||
|
|
}
|
|||
|
|
newNode[PropertySymbol.parentNode] = self;
|
|||
|
|
newNode[PropertySymbol.clearCache]();
|
|||
|
|
const index = nodeArray.indexOf(referenceNode);
|
|||
|
|
if (newNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
|
|||
|
|
const elementArray = this[PropertySymbol.elementArray];
|
|||
|
|
if (referenceNode[PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
|
|||
|
|
elementArray.splice(elementArray.indexOf(referenceNode), 0, newNode);
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
let isInserted = false;
|
|||
|
|
for (let i = index, max = nodeArray.length; i < max; i++) {
|
|||
|
|
if (nodeArray[i][PropertySymbol.nodeType] === NodeTypeEnum.elementNode) {
|
|||
|
|
elementArray.splice(elementArray.indexOf(nodeArray[i]), 0, newNode);
|
|||
|
|
isInserted = true;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (!isInserted) {
|
|||
|
|
elementArray.push(newNode);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
nodeArray.splice(index, 0, newNode);
|
|||
|
|
newNode[PropertySymbol.connectedToNode]();
|
|||
|
|
// Mutation listeners
|
|||
|
|
for (const mutationListener of this[PropertySymbol.mutationListeners]) {
|
|||
|
|
if (mutationListener.options?.subtree && mutationListener.callback.deref()) {
|
|||
|
|
newNode[PropertySymbol.observeMutations](mutationListener);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.reportMutation](new MutationRecord({
|
|||
|
|
target: self,
|
|||
|
|
type: MutationTypeEnum.childList,
|
|||
|
|
addedNodes: [newNode]
|
|||
|
|
}));
|
|||
|
|
return newNode;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Replaces a node with another.
|
|||
|
|
*
|
|||
|
|
* @param newChild New child.
|
|||
|
|
* @param oldChild Old child.
|
|||
|
|
* @returns Replaced node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.replaceChild](newChild, oldChild) {
|
|||
|
|
this.insertBefore(newChild, oldChild);
|
|||
|
|
this.removeChild(oldChild);
|
|||
|
|
return oldChild;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Compares two nodes.
|
|||
|
|
* Two nodes are equal if they have the same type, defining the same attributes, and so on.
|
|||
|
|
*
|
|||
|
|
* @param node Node to compare.
|
|||
|
|
* @returns boolean - `true` if two nodes are equal.
|
|||
|
|
*/
|
|||
|
|
isEqualNode(node) {
|
|||
|
|
return NodeUtility.isEqualNode(this, node);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Converts the node to a string.
|
|||
|
|
*
|
|||
|
|
* @param listener Listener.
|
|||
|
|
*/
|
|||
|
|
toString() {
|
|||
|
|
return `[object ${this.constructor.name}]`;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Observeres mutations on the node.
|
|||
|
|
*
|
|||
|
|
* Used by MutationObserver and internal logic.
|
|||
|
|
*
|
|||
|
|
* @param listener Listener.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.observeMutations](listener) {
|
|||
|
|
this[PropertySymbol.mutationListeners].push(listener);
|
|||
|
|
if (listener.options.subtree) {
|
|||
|
|
for (const node of this[PropertySymbol.nodeArray]) {
|
|||
|
|
node[PropertySymbol.observeMutations](listener);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Stops observing mutations on the node.
|
|||
|
|
*
|
|||
|
|
* Used by MutationObserver and internal logic.
|
|||
|
|
*
|
|||
|
|
* @param listener Listener.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.unobserveMutations](listener) {
|
|||
|
|
const index = this[PropertySymbol.mutationListeners].indexOf(listener);
|
|||
|
|
if (index !== -1) {
|
|||
|
|
this[PropertySymbol.mutationListeners].splice(index, 1);
|
|||
|
|
}
|
|||
|
|
if (listener.options.subtree) {
|
|||
|
|
for (const node of this[PropertySymbol.nodeArray]) {
|
|||
|
|
node[PropertySymbol.unobserveMutations](listener);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Reports a mutation on the node.
|
|||
|
|
*
|
|||
|
|
* Used by MutationObserver and internal logic.
|
|||
|
|
*
|
|||
|
|
* @param record Mutation record.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.reportMutation](record) {
|
|||
|
|
this[PropertySymbol.clearCache]();
|
|||
|
|
const mutationListeners = this[PropertySymbol.mutationListeners];
|
|||
|
|
if (!mutationListeners.length) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
for (let i = 0, max = mutationListeners.length; i < max; i++) {
|
|||
|
|
const mutationListener = mutationListeners[i];
|
|||
|
|
const callback = mutationListener.callback.deref();
|
|||
|
|
if (callback) {
|
|||
|
|
switch (record.type) {
|
|||
|
|
case MutationTypeEnum.childList:
|
|||
|
|
if (mutationListener.options.childList) {
|
|||
|
|
callback(record);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case MutationTypeEnum.attributes:
|
|||
|
|
if (mutationListener.options.attributes &&
|
|||
|
|
(!mutationListener.options.attributeFilter ||
|
|||
|
|
mutationListener.options.attributeFilter.includes(record.attributeName))) {
|
|||
|
|
callback(record);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case MutationTypeEnum.characterData:
|
|||
|
|
if (mutationListener.options?.characterData) {
|
|||
|
|
callback(record);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
mutationListeners.splice(i, 1);
|
|||
|
|
i--;
|
|||
|
|
max--;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Clears query selector cache.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.clearCache]() {
|
|||
|
|
const cache = this[PropertySymbol.cache];
|
|||
|
|
if (cache.querySelector.size) {
|
|||
|
|
for (const item of cache.querySelector.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.querySelector = new Map();
|
|||
|
|
}
|
|||
|
|
if (cache.querySelectorAll.size) {
|
|||
|
|
for (const item of cache.querySelectorAll.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.querySelectorAll = new Map();
|
|||
|
|
}
|
|||
|
|
if (cache.matches.size) {
|
|||
|
|
for (const item of cache.matches.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.matches = new Map();
|
|||
|
|
}
|
|||
|
|
if (cache.elementsByTagName.size) {
|
|||
|
|
for (const item of cache.elementsByTagName.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.elementsByTagName = new Map();
|
|||
|
|
}
|
|||
|
|
if (cache.elementsByTagNameNS.size) {
|
|||
|
|
for (const item of cache.elementsByTagNameNS.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.elementsByTagNameNS = new Map();
|
|||
|
|
}
|
|||
|
|
if (cache.elementByTagName.size) {
|
|||
|
|
for (const item of cache.elementByTagName.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.elementByTagName = new Map();
|
|||
|
|
}
|
|||
|
|
if (cache.elementById.size) {
|
|||
|
|
for (const item of cache.elementById.values()) {
|
|||
|
|
if (item.result) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
cache.elementById = new Map();
|
|||
|
|
}
|
|||
|
|
const affectsCache = this[PropertySymbol.affectsCache];
|
|||
|
|
if (affectsCache.length) {
|
|||
|
|
for (const item of affectsCache) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.affectsCache] = [];
|
|||
|
|
}
|
|||
|
|
// Computed style cache is affected by all mutations.
|
|||
|
|
const document = this[PropertySymbol.ownerDocument];
|
|||
|
|
if (document && document[PropertySymbol.affectsComputedStyleCache].length) {
|
|||
|
|
for (const item of document[PropertySymbol.affectsComputedStyleCache]) {
|
|||
|
|
item.result = null;
|
|||
|
|
}
|
|||
|
|
document[PropertySymbol.affectsComputedStyleCache] = [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Called when connected to a node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.connectedToNode]() {
|
|||
|
|
const parentNode = this[PropertySymbol.parentNode];
|
|||
|
|
const parent = parentNode || this[PropertySymbol.host];
|
|||
|
|
let isConnected = false;
|
|||
|
|
let isDisconnected = false;
|
|||
|
|
if (!this[PropertySymbol.isConnected] && parent[PropertySymbol.isConnected]) {
|
|||
|
|
this[PropertySymbol.isConnected] = true;
|
|||
|
|
isConnected = true;
|
|||
|
|
}
|
|||
|
|
else if (this[PropertySymbol.isConnected] && !parent[PropertySymbol.isConnected]) {
|
|||
|
|
this[PropertySymbol.isConnected] = false;
|
|||
|
|
isDisconnected = true;
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.ownerDocument] = parent[PropertySymbol.ownerDocument] || parent;
|
|||
|
|
this[PropertySymbol.window] =
|
|||
|
|
parent[PropertySymbol.window] || parent[PropertySymbol.defaultView];
|
|||
|
|
if (parentNode) {
|
|||
|
|
if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
|
|||
|
|
this[PropertySymbol.rootNode] = parentNode[PropertySymbol.rootNode];
|
|||
|
|
}
|
|||
|
|
const tagName = this[PropertySymbol.tagName];
|
|||
|
|
if (parentNode[PropertySymbol.styleNode] && tagName !== 'STYLE') {
|
|||
|
|
this[PropertySymbol.styleNode] = parentNode[PropertySymbol.styleNode];
|
|||
|
|
}
|
|||
|
|
if (parentNode[PropertySymbol.textAreaNode] && tagName !== 'TEXTAREA') {
|
|||
|
|
this[PropertySymbol.textAreaNode] = parentNode[PropertySymbol.textAreaNode];
|
|||
|
|
}
|
|||
|
|
if (parentNode[PropertySymbol.formNode] && tagName !== 'FORM') {
|
|||
|
|
this[PropertySymbol.formNode] = parentNode[PropertySymbol.formNode];
|
|||
|
|
}
|
|||
|
|
if (parentNode[PropertySymbol.selectNode] && tagName !== 'SELECT') {
|
|||
|
|
this[PropertySymbol.selectNode] = parentNode[PropertySymbol.selectNode];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const childNodes = this[PropertySymbol.nodeArray];
|
|||
|
|
for (let i = 0, max = childNodes.length; i < max; i++) {
|
|||
|
|
childNodes[i][PropertySymbol.parentNode] = this;
|
|||
|
|
childNodes[i][PropertySymbol.connectedToNode]();
|
|||
|
|
}
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
if (this[PropertySymbol.shadowRoot]) {
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
this[PropertySymbol.shadowRoot][PropertySymbol.connectedToNode]();
|
|||
|
|
}
|
|||
|
|
if (isConnected) {
|
|||
|
|
this[PropertySymbol.connectedToDocument]();
|
|||
|
|
}
|
|||
|
|
else if (isDisconnected) {
|
|||
|
|
this[PropertySymbol.disconnectedFromDocument]();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Called when disconnected from a node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.disconnectedFromNode]() {
|
|||
|
|
if (this[PropertySymbol.nodeType] !== NodeTypeEnum.documentFragmentNode) {
|
|||
|
|
this[PropertySymbol.rootNode] = null;
|
|||
|
|
}
|
|||
|
|
const tagName = this[PropertySymbol.tagName];
|
|||
|
|
if (tagName !== 'STYLE') {
|
|||
|
|
this[PropertySymbol.styleNode] = null;
|
|||
|
|
}
|
|||
|
|
if (tagName !== 'TEXTAREA') {
|
|||
|
|
this[PropertySymbol.textAreaNode] = null;
|
|||
|
|
}
|
|||
|
|
if (tagName !== 'FORM') {
|
|||
|
|
this[PropertySymbol.formNode] = null;
|
|||
|
|
}
|
|||
|
|
if (tagName !== 'SELECT') {
|
|||
|
|
this[PropertySymbol.selectNode] = null;
|
|||
|
|
}
|
|||
|
|
if (this[PropertySymbol.isConnected]) {
|
|||
|
|
this[PropertySymbol.isConnected] = false;
|
|||
|
|
this[PropertySymbol.disconnectedFromDocument]();
|
|||
|
|
}
|
|||
|
|
const childNodes = this[PropertySymbol.nodeArray];
|
|||
|
|
for (let i = 0, max = childNodes.length; i < max; i++) {
|
|||
|
|
childNodes[i][PropertySymbol.connectedToNode]();
|
|||
|
|
}
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
if (this[PropertySymbol.shadowRoot]) {
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
this[PropertySymbol.shadowRoot][PropertySymbol.disconnectedFromNode]();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Called when connected to document.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.connectedToDocument]() {
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
if (this[PropertySymbol.shadowRoot]) {
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
this[PropertySymbol.shadowRoot][PropertySymbol.connectedToDocument]();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Called when disconnected from document.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.disconnectedFromDocument]() {
|
|||
|
|
if (this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === this) {
|
|||
|
|
this[PropertySymbol.ownerDocument][PropertySymbol.clearCache]();
|
|||
|
|
this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] = null;
|
|||
|
|
}
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
if (this[PropertySymbol.shadowRoot]) {
|
|||
|
|
// eslint-disable-next-line
|
|||
|
|
this[PropertySymbol.shadowRoot][PropertySymbol.disconnectedFromDocument]();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Destroys the node.
|
|||
|
|
*/
|
|||
|
|
[PropertySymbol.destroy]() {
|
|||
|
|
super[PropertySymbol.destroy]();
|
|||
|
|
this[PropertySymbol.isConnected] = false;
|
|||
|
|
while (this[PropertySymbol.nodeArray].length > 0) {
|
|||
|
|
const node = this[PropertySymbol.nodeArray][this[PropertySymbol.nodeArray].length - 1];
|
|||
|
|
// Makes sure that something won't be triggered by the disconnect.
|
|||
|
|
if (node.disconnectedCallback) {
|
|||
|
|
delete node.disconnectedCallback;
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.removeChild](node);
|
|||
|
|
node[PropertySymbol.destroy]();
|
|||
|
|
}
|
|||
|
|
this[PropertySymbol.parentNode] = null;
|
|||
|
|
this[PropertySymbol.rootNode] = null;
|
|||
|
|
this[PropertySymbol.styleNode] = null;
|
|||
|
|
this[PropertySymbol.textAreaNode] = null;
|
|||
|
|
this[PropertySymbol.formNode] = null;
|
|||
|
|
this[PropertySymbol.selectNode] = null;
|
|||
|
|
this[PropertySymbol.mutationListeners] = [];
|
|||
|
|
this[PropertySymbol.nodeArray] = [];
|
|||
|
|
this[PropertySymbol.elementArray] = [];
|
|||
|
|
this[PropertySymbol.childNodes] = null;
|
|||
|
|
this[PropertySymbol.assignedToSlot] = null;
|
|||
|
|
this[PropertySymbol.clearCache]();
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Reports the position of its argument node relative to the node on which it is called.
|
|||
|
|
*
|
|||
|
|
* @see https://dom.spec.whatwg.org/#dom-node-comparedocumentposition
|
|||
|
|
* @param otherNode Other node.
|
|||
|
|
*/
|
|||
|
|
compareDocumentPosition(otherNode) {
|
|||
|
|
/**
|
|||
|
|
* 1. If this is other, then return zero.
|
|||
|
|
*/
|
|||
|
|
if (this === otherNode) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 2. Let node1 be other and node2 be this.
|
|||
|
|
*/
|
|||
|
|
let node1 = otherNode;
|
|||
|
|
let node2 = this[PropertySymbol.proxy] || this;
|
|||
|
|
/**
|
|||
|
|
* 3. Let attr1 and attr2 be null.
|
|||
|
|
*/
|
|||
|
|
let attr1 = null;
|
|||
|
|
let attr2 = null;
|
|||
|
|
/**
|
|||
|
|
* 4. If node1 is an attribute, then set attr1 to node1 and node1 to attr1’s element.
|
|||
|
|
*/
|
|||
|
|
if (node1[PropertySymbol.nodeType] === NodeTypeEnum.attributeNode) {
|
|||
|
|
attr1 = node1;
|
|||
|
|
node1 = attr1[PropertySymbol.ownerElement];
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 5. If node2 is an attribute, then:
|
|||
|
|
* 5.1. Set attr2 to node2 and node2 to attr2’s element.
|
|||
|
|
*/
|
|||
|
|
if (node2[PropertySymbol.nodeType] === NodeTypeEnum.attributeNode) {
|
|||
|
|
attr2 = node2;
|
|||
|
|
node2 = attr2[PropertySymbol.ownerElement];
|
|||
|
|
/**
|
|||
|
|
* 5.2. If attr1 and node1 are non-null, and node2 is node1, then:
|
|||
|
|
*/
|
|||
|
|
if (attr1 !== null && node1 !== null && node2 === node1) {
|
|||
|
|
/**
|
|||
|
|
* 5.2.1. For each attr in node2’s attribute list:
|
|||
|
|
*/
|
|||
|
|
for (const attribute of node2[PropertySymbol.attributes][PropertySymbol.items].values()) {
|
|||
|
|
/**
|
|||
|
|
* 5.2.1.1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
|
|||
|
|
*/
|
|||
|
|
if (NodeUtility.isEqualNode(attribute, attr1)) {
|
|||
|
|
return (Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_PRECEDING);
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* 5.2.1.2. If attr equals attr2, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_FOLLOWING.
|
|||
|
|
*/
|
|||
|
|
if (NodeUtility.isEqualNode(attribute, attr2)) {
|
|||
|
|
return (Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | Node.DOCUMENT_POSITION_FOLLOWING);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
const node2Ancestors = [];
|
|||
|
|
let node2Ancestor = node2;
|
|||
|
|
while (node2Ancestor) {
|
|||
|
|
/**
|
|||
|
|
* 7. If node1 is an ancestor of node2 […] then return the result of adding DOCUMENT_POSITION_CONTAINS to DOCUMENT_POSITION_PRECEDING.
|
|||
|
|
*/
|
|||
|
|
if (node2Ancestor === node1) {
|
|||
|
|
return Node.DOCUMENT_POSITION_CONTAINS | Node.DOCUMENT_POSITION_PRECEDING;
|
|||
|
|
}
|
|||
|
|
node2Ancestors.push(node2Ancestor);
|
|||
|
|
node2Ancestor = node2Ancestor[PropertySymbol.parentNode];
|
|||
|
|
}
|
|||
|
|
const node1Ancestors = [];
|
|||
|
|
let node1Ancestor = node1;
|
|||
|
|
while (node1Ancestor) {
|
|||
|
|
/**
|
|||
|
|
* 8. If node1 is a descendant of node2 […] then return the result of adding DOCUMENT_POSITION_CONTAINED_BY to DOCUMENT_POSITION_FOLLOWING.
|
|||
|
|
*/
|
|||
|
|
if (node1Ancestor === node2) {
|
|||
|
|
return Node.DOCUMENT_POSITION_CONTAINED_BY | Node.DOCUMENT_POSITION_FOLLOWING;
|
|||
|
|
}
|
|||
|
|
node1Ancestors.push(node1Ancestor);
|
|||
|
|
node1Ancestor = node1Ancestor[PropertySymbol.parentNode];
|
|||
|
|
}
|
|||
|
|
const reverseArrayIndex = (array, reverseIndex) => {
|
|||
|
|
return array[array.length - 1 - reverseIndex];
|
|||
|
|
};
|
|||
|
|
const root = reverseArrayIndex(node2Ancestors, 0);
|
|||
|
|
/**
|
|||
|
|
* 6. If node1 or node2 is null, or node1’s root is not node2’s root, then 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 (!root || root !== reverseArrayIndex(node1Ancestors, 0)) {
|
|||
|
|
return (Node.DOCUMENT_POSITION_DISCONNECTED |
|
|||
|
|
Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC |
|
|||
|
|
Node.DOCUMENT_POSITION_FOLLOWING);
|
|||
|
|
}
|
|||
|
|
// Find the lowest common ancestor
|
|||
|
|
let commonAncestorIndex = 0;
|
|||
|
|
const ancestorsMinLength = Math.min(node2Ancestors.length, node1Ancestors.length);
|
|||
|
|
for (let i = 0; i < ancestorsMinLength; ++i) {
|
|||
|
|
const node2Ancestor = reverseArrayIndex(node2Ancestors, i);
|
|||
|
|
const node1Ancestor = reverseArrayIndex(node1Ancestors, i);
|
|||
|
|
if (node2Ancestor !== node1Ancestor) {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
commonAncestorIndex = i;
|
|||
|
|
}
|
|||
|
|
const commonAncestor = reverseArrayIndex(node2Ancestors, commonAncestorIndex);
|
|||
|
|
// Indexes within the common ancestor
|
|||
|
|
let indexes = 0;
|
|||
|
|
let node2Index = -1;
|
|||
|
|
let node1Index = -1;
|
|||
|
|
const node2Node = reverseArrayIndex(node2Ancestors, commonAncestorIndex + 1);
|
|||
|
|
const node1Node = reverseArrayIndex(node1Ancestors, commonAncestorIndex + 1);
|
|||
|
|
const computeNodeIndexes = (nodes) => {
|
|||
|
|
for (const childNode of nodes) {
|
|||
|
|
computeNodeIndexes(childNode[PropertySymbol.nodeArray]);
|
|||
|
|
if (childNode === node2Node) {
|
|||
|
|
node2Index = indexes;
|
|||
|
|
}
|
|||
|
|
else if (childNode === node1Node) {
|
|||
|
|
node1Index = indexes;
|
|||
|
|
}
|
|||
|
|
if (node2Index !== -1 && node1Index !== -1) {
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
indexes++;
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
computeNodeIndexes(commonAncestor[PropertySymbol.nodeArray]);
|
|||
|
|
/**
|
|||
|
|
* 9. If node1 is preceding node2, then return DOCUMENT_POSITION_PRECEDING.
|
|||
|
|
* 10. Return DOCUMENT_POSITION_FOLLOWING.
|
|||
|
|
*/
|
|||
|
|
return node1Index < node2Index
|
|||
|
|
? Node.DOCUMENT_POSITION_PRECEDING
|
|||
|
|
: Node.DOCUMENT_POSITION_FOLLOWING;
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Normalizes the sub-tree of the node, i.e. joins adjacent text nodes, and
|
|||
|
|
* removes all empty text nodes.
|
|||
|
|
*
|
|||
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Node/normalize
|
|||
|
|
*/
|
|||
|
|
normalize() {
|
|||
|
|
let child = this.firstChild;
|
|||
|
|
while (child) {
|
|||
|
|
if (NodeUtility.isTextNode(child)) {
|
|||
|
|
// Append text of all following text nodes, and remove them.
|
|||
|
|
while (NodeUtility.isTextNode(child.nextSibling)) {
|
|||
|
|
child.data += child.nextSibling.data;
|
|||
|
|
child.nextSibling.remove();
|
|||
|
|
}
|
|||
|
|
// Remove text node if it is still empty.
|
|||
|
|
if (!child.data.length) {
|
|||
|
|
const node = child;
|
|||
|
|
child = child.nextSibling;
|
|||
|
|
node.remove();
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else {
|
|||
|
|
// Normalize child nodes recursively.
|
|||
|
|
child.normalize();
|
|||
|
|
}
|
|||
|
|
child = child.nextSibling;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/**
|
|||
|
|
* Determines whether the given node is equal to the current node.
|
|||
|
|
*
|
|||
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/Node/isSameNode
|
|||
|
|
* @param node Node to check.
|
|||
|
|
* @returns True if the given node is equal to the current node, otherwise false.
|
|||
|
|
*/
|
|||
|
|
isSameNode(node) {
|
|||
|
|
return this === node;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// According to the spec, these properties should be on the prototype.
|
|||
|
|
Node.prototype.ELEMENT_NODE = NodeTypeEnum.elementNode;
|
|||
|
|
Node.prototype.ATTRIBUTE_NODE = NodeTypeEnum.attributeNode;
|
|||
|
|
Node.prototype.TEXT_NODE = NodeTypeEnum.textNode;
|
|||
|
|
Node.prototype.CDATA_SECTION_NODE = NodeTypeEnum.cdataSectionNode;
|
|||
|
|
Node.prototype.COMMENT_NODE = NodeTypeEnum.commentNode;
|
|||
|
|
Node.prototype.DOCUMENT_NODE = NodeTypeEnum.documentNode;
|
|||
|
|
Node.prototype.DOCUMENT_TYPE_NODE = NodeTypeEnum.documentTypeNode;
|
|||
|
|
Node.prototype.DOCUMENT_FRAGMENT_NODE = NodeTypeEnum.documentFragmentNode;
|
|||
|
|
Node.prototype.PROCESSING_INSTRUCTION_NODE = NodeTypeEnum.processingInstructionNode;
|
|||
|
|
Node.prototype.DOCUMENT_POSITION_CONTAINED_BY =
|
|||
|
|
NodeDocumentPositionEnum.containedBy;
|
|||
|
|
Node.prototype.DOCUMENT_POSITION_CONTAINS =
|
|||
|
|
NodeDocumentPositionEnum.contains;
|
|||
|
|
Node.prototype.DOCUMENT_POSITION_DISCONNECTED =
|
|||
|
|
NodeDocumentPositionEnum.disconnect;
|
|||
|
|
Node.prototype.DOCUMENT_POSITION_FOLLOWING =
|
|||
|
|
NodeDocumentPositionEnum.following;
|
|||
|
|
Node.prototype.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC =
|
|||
|
|
NodeDocumentPositionEnum.implementationSpecific;
|
|||
|
|
Node.prototype.DOCUMENT_POSITION_PRECEDING =
|
|||
|
|
NodeDocumentPositionEnum.preceding;
|
|||
|
|
//# sourceMappingURL=Node.js.map
|