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.
207 lines
6.2 KiB
JavaScript
207 lines
6.2 KiB
JavaScript
'use strict';
|
|
// https://dom.spec.whatwg.org/#node
|
|
|
|
const {
|
|
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
|
|
} = require('../shared/constants.js');
|
|
|
|
const {NEXT, PREV} = require('../shared/symbols.js');
|
|
|
|
const {EventTarget} = require('./event-target.js');
|
|
|
|
const {NodeList} = require('./node-list.js');
|
|
|
|
const getParentNodeCount = ({parentNode}) => {
|
|
let count = 0;
|
|
while (parentNode) {
|
|
count++;
|
|
parentNode = parentNode.parentNode;
|
|
}
|
|
return count;
|
|
};
|
|
|
|
/**
|
|
* @implements globalThis.Node
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
exports.Node = Node
|