You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
6.1 KiB
JavaScript

// https://dom.spec.whatwg.org/#node
import {
ELEMENT_NODE,
ATTRIBUTE_NODE,
TEXT_NODE,
CDATA_SECTION_NODE,
COMMENT_NODE,
DOCUMENT_NODE,
DOCUMENT_FRAGMENT_NODE,
DOCUMENT_TYPE_NODE,
DOCUMENT_POSITION_DISCONNECTED,
DOCUMENT_POSITION_PRECEDING,
DOCUMENT_POSITION_FOLLOWING,
DOCUMENT_POSITION_CONTAINS,
DOCUMENT_POSITION_CONTAINED_BY,
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC
} from '../shared/constants.js';
import {NEXT, PREV} from '../shared/symbols.js';
import {EventTarget} from './event-target.js';
import {NodeList} from './node-list.js';
const getParentNodeCount = ({parentNode}) => {
let count = 0;
while (parentNode) {
count++;
parentNode = parentNode.parentNode;
}
return count;
};
/**
* @implements globalThis.Node
*/
export class Node extends EventTarget {
static get ELEMENT_NODE() { return ELEMENT_NODE; }
static get ATTRIBUTE_NODE() { return ATTRIBUTE_NODE; }
static get TEXT_NODE() { return TEXT_NODE; }
static get CDATA_SECTION_NODE() { return CDATA_SECTION_NODE; }
static get COMMENT_NODE() { return COMMENT_NODE; }
static get DOCUMENT_NODE() { return DOCUMENT_NODE; }
static get DOCUMENT_FRAGMENT_NODE() { return DOCUMENT_FRAGMENT_NODE; }
static get DOCUMENT_TYPE_NODE() { return DOCUMENT_TYPE_NODE; }
constructor(ownerDocument, localName, nodeType) {
super();
this.ownerDocument = ownerDocument;
this.localName = localName;
this.nodeType = nodeType;
this.parentNode = null;
this[NEXT] = null;
this[PREV] = null;
}
get ELEMENT_NODE() { return ELEMENT_NODE; }
get ATTRIBUTE_NODE() { return ATTRIBUTE_NODE; }
get TEXT_NODE() { return TEXT_NODE; }
get CDATA_SECTION_NODE() { return CDATA_SECTION_NODE; }
get COMMENT_NODE() { return COMMENT_NODE; }
get DOCUMENT_NODE() { return DOCUMENT_NODE; }
get DOCUMENT_FRAGMENT_NODE() { return DOCUMENT_FRAGMENT_NODE; }
get DOCUMENT_TYPE_NODE() { return DOCUMENT_TYPE_NODE; }
get baseURI() {
const ownerDocument = this.nodeType === DOCUMENT_NODE ?
this : this.ownerDocument;
if (ownerDocument) {
const base = ownerDocument.querySelector('base');
if (base)
return base.getAttribute('href');
const {location} = ownerDocument.defaultView;
if (location)
return location.href;
}
return null;
}
/* c8 ignore start */
// mixin: node
get isConnected() { return false; }
get nodeName() { return this.localName; }
get parentElement() { return null; }
get previousSibling() { return null; }
get previousElementSibling() { return null; }
get nextSibling() { return null; }
get nextElementSibling() { return null; }
get childNodes() { return new NodeList; }
get firstChild() { return null; }
get lastChild() { return null; }
// default values
get nodeValue() { return null; }
set nodeValue(value) {}
get textContent() { return null; }
set textContent(value) {}
normalize() {}
cloneNode() { return null; }
contains() { return false; }
/**
* Inserts a node before a reference node as a child of this parent node.
* @param {Node} newNode The node to be inserted.
* @param {Node} referenceNode The node before which newNode is inserted. If this is null, then newNode is inserted at the end of node's child nodes.
* @returns The added child
*/
// eslint-disable-next-line no-unused-vars
insertBefore(newNode, referenceNode) { return newNode }
/**
* Adds a node to the end of the list of children of this node.
* @param {Node} child The node to append to the given parent node.
* @returns The appended child.
*/
appendChild(child) { return child }
/**
* Replaces a child node within this node
* @param {Node} newChild The new node to replace oldChild.
* @param {Node} oldChild The child to be replaced.
* @returns The replaced Node. This is the same node as oldChild.
*/
replaceChild(newChild, oldChild) { return oldChild }
/**
* Removes a child node from the DOM.
* @param {Node} child A Node that is the child node to be removed from the DOM.
* @returns The removed node.
*/
removeChild(child) { return child }
toString() { return ''; }
/* c8 ignore stop */
hasChildNodes() { return !!this.lastChild; }
isSameNode(node) { return this === node; }
// TODO: attributes?
compareDocumentPosition(target) {
let result = 0;
if (this !== target) {
let self = getParentNodeCount(this);
let other = getParentNodeCount(target);
if (self < other) {
result += DOCUMENT_POSITION_FOLLOWING;
if (this.contains(target))
result += DOCUMENT_POSITION_CONTAINED_BY;
}
else if (other < self) {
result += DOCUMENT_POSITION_PRECEDING;
if (target.contains(this))
result += DOCUMENT_POSITION_CONTAINS;
}
else if (self && other) {
const {childNodes} = this.parentNode;
if (childNodes.indexOf(this) < childNodes.indexOf(target))
result += DOCUMENT_POSITION_FOLLOWING;
else
result += DOCUMENT_POSITION_PRECEDING;
}
if (!self || !other) {
result += DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC;
result += DOCUMENT_POSITION_DISCONNECTED;
}
}
return result;
}
isEqualNode(node) {
if (this === node)
return true;
if (this.nodeType === node.nodeType) {
switch (this.nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
const aNodes = this.childNodes;
const bNodes = node.childNodes;
return aNodes.length === bNodes.length && aNodes.every((node, i) => node.isEqualNode(bNodes[i]));
}
}
return this.toString() === node.toString();
}
return false;
}
/**
* @protected
*/
_getParent() {
return this.parentNode;
}
/**
* Calling it on an element inside a standard web page will return an HTMLDocument object representing the entire page (or <iframe>).
* Calling it on an element inside a shadow DOM will return the associated ShadowRoot.
* @return {ShadowRoot | HTMLDocument}
*/
getRootNode() {
let root = this;
while (root.parentNode)
root = root.parentNode;
return root;
}
}