happy-dom
Version:
Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.
707 lines • 26.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a, _b, _c, _d, _e, _f, _g, _h;
Object.defineProperty(exports, "__esModule", { value: true });
const EventTarget_js_1 = __importDefault(require("../../event/EventTarget.cjs"));
const PropertySymbol = __importStar(require("../../PropertySymbol.cjs"));
const NodeTypeEnum_js_1 = __importDefault(require("./NodeTypeEnum.cjs"));
const NodeDocumentPositionEnum_js_1 = __importDefault(require("./NodeDocumentPositionEnum.cjs"));
const NodeUtility_js_1 = __importDefault(require("./NodeUtility.cjs"));
const NodeList_js_1 = __importDefault(require("./NodeList.cjs"));
const NodeFactory_js_1 = __importDefault(require("../NodeFactory.cjs"));
/**
* Node.
*/
class Node extends EventTarget_js_1.default {
/**
* Constructor.
*/
constructor() {
super();
// Internal properties
this[_a] = false;
this[_b] = null;
this[_c] = null;
this[_d] = null;
this[_e] = null;
this[_f] = null;
this[_g] = [];
this[_h] = new NodeList_js_1.default();
if (this.constructor[PropertySymbol.ownerDocument] !== undefined) {
this[PropertySymbol.ownerDocument] = this.constructor[PropertySymbol.ownerDocument];
}
else {
const ownerDocument = NodeFactory_js_1.default.pullOwnerDocument();
if (!ownerDocument) {
throw new Error('Failed to construct "Node": No owner document in queue. Please use "NodeFactory" to create instances of a Node.');
}
this[PropertySymbol.ownerDocument] = ownerDocument;
}
}
/**
* Returns `Symbol.toStringTag`.
*
* @returns `Symbol.toStringTag`.
*/
get [(PropertySymbol.ownerDocument, _a = PropertySymbol.isConnected, PropertySymbol.ownerDocument, _b = PropertySymbol.parentNode, PropertySymbol.nodeType, _c = PropertySymbol.rootNode, _d = PropertySymbol.formNode, _e = PropertySymbol.selectNode, _f = PropertySymbol.textAreaNode, _g = PropertySymbol.observers, _h = PropertySymbol.childNodes, 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() {
return this[PropertySymbol.childNodes];
}
/**
* Get text value of children.
*
* @returns Text content.
*/
get textContent() {
// Sub-classes should implement this method.
return null;
}
/**
* 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 index = this[PropertySymbol.parentNode][PropertySymbol.childNodes].indexOf(this);
if (index > 0) {
return this[PropertySymbol.parentNode][PropertySymbol.childNodes][index - 1];
}
}
return null;
}
/**
* Next sibling.
*
* @returns Node.
*/
get nextSibling() {
if (this[PropertySymbol.parentNode]) {
const index = this[PropertySymbol.parentNode][PropertySymbol.childNodes].indexOf(this);
if (index > -1 &&
index + 1 < this[PropertySymbol.parentNode][PropertySymbol.childNodes].length) {
return this[PropertySymbol.parentNode][PropertySymbol.childNodes][index + 1];
}
}
return null;
}
/**
* First child.
*
* @returns Node.
*/
get firstChild() {
if (this[PropertySymbol.childNodes].length > 0) {
return this[PropertySymbol.childNodes][0];
}
return null;
}
/**
* Last child.
*
* @returns Node.
*/
get lastChild() {
if (this[PropertySymbol.childNodes].length > 0) {
return this[PropertySymbol.childNodes][this[PropertySymbol.childNodes].length - 1];
}
return null;
}
/**
* Returns parent element.
*
* @returns Element.
*/
get parentElement() {
let parent = this[PropertySymbol.parentNode];
while (parent && parent[PropertySymbol.nodeType] !== NodeTypeEnum_js_1.default.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.ownerDocument][PropertySymbol.ownerWindow].location.href;
}
/**
* Returns "true" if the node has child nodes.
*
* @returns "true" if the node has child nodes.
*/
hasChildNodes() {
return this[PropertySymbol.childNodes].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_js_1.default.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) {
return this[PropertySymbol.appendChild](node);
}
/**
* Remove Child element from childNodes array.
*
* @param node Node to remove.
* @returns Removed node.
*/
removeChild(node) {
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 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) {
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_js_1.default.createNode(this[PropertySymbol.ownerDocument], this.constructor);
// Document has childNodes directly when it is created
if (clone[PropertySymbol.childNodes].length) {
for (const node of clone[PropertySymbol.childNodes].slice()) {
node[PropertySymbol.parentNode].removeChild(node);
}
}
if (deep) {
for (const childNode of this[PropertySymbol.childNodes]) {
const childClone = childNode.cloneNode(true);
childClone[PropertySymbol.parentNode] = clone;
clone[PropertySymbol.childNodes].push(childClone);
}
}
return clone;
}
/**
* Append a child node to childNodes.
*
* @param node Node to append.
* @returns Appended node.
*/
[PropertySymbol.appendChild](node) {
return NodeUtility_js_1.default.appendChild(this, node);
}
/**
* Remove Child element from childNodes array.
*
* @param node Node to remove.
* @returns Removed node.
*/
[PropertySymbol.removeChild](node) {
return NodeUtility_js_1.default.removeChild(this, node);
}
/**
* Inserts a node before another.
*
* @param newNode Node to insert.
* @param referenceNode Node to insert before.
* @returns Inserted node.
*/
[PropertySymbol.insertBefore](newNode, referenceNode) {
return NodeUtility_js_1.default.insertBefore(this, newNode, referenceNode);
}
/**
* 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_js_1.default.isEqualNode(this, node);
}
/**
* Converts the node to a string.
*
* @param listener Listener.
*/
toString() {
return `[object ${this.constructor.name}]`;
}
/**
* Observeres the node.
* Used by MutationObserver, but it is not part of the HTML standard.
*
* @param listener Listener.
*/
[PropertySymbol.observe](listener) {
this[PropertySymbol.observers].push(listener);
if (listener.options.subtree) {
for (const node of this[PropertySymbol.childNodes]) {
node[PropertySymbol.observe](listener);
}
}
}
/**
* Stops observing the node.
* Used by MutationObserver, but it is not part of the HTML standard.
*
* @param listener Listener.
*/
[PropertySymbol.unobserve](listener) {
const index = this[PropertySymbol.observers].indexOf(listener);
if (index !== -1) {
this[PropertySymbol.observers].splice(index, 1);
}
if (listener.options.subtree) {
for (const node of this[PropertySymbol.childNodes]) {
node[PropertySymbol.unobserve](listener);
}
}
}
/**
* Connects this element to another element.
*
* @param parentNode Parent node.
*/
[PropertySymbol.connectToNode](parentNode = null) {
const isConnected = !!parentNode && parentNode[PropertySymbol.isConnected];
const formNode = this[PropertySymbol.formNode];
const selectNode = this[PropertySymbol.selectNode];
const textAreaNode = this[PropertySymbol.textAreaNode];
if (this[PropertySymbol.nodeType] !== NodeTypeEnum_js_1.default.documentFragmentNode) {
this[PropertySymbol.parentNode] = parentNode;
this[PropertySymbol.rootNode] =
isConnected && parentNode ? parentNode[PropertySymbol.rootNode] : null;
if (this['tagName'] !== 'FORM') {
this[PropertySymbol.formNode] = parentNode
? parentNode[PropertySymbol.formNode]
: null;
}
if (this['tagName'] !== 'SELECT') {
this[PropertySymbol.selectNode] = parentNode
? parentNode[PropertySymbol.selectNode]
: null;
}
if (this['tagName'] !== 'TEXTAREA') {
this[PropertySymbol.textAreaNode] = parentNode
? parentNode[PropertySymbol.textAreaNode]
: null;
}
}
if (this[PropertySymbol.isConnected] !== isConnected) {
this[PropertySymbol.isConnected] = isConnected;
if (!isConnected) {
if (this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] === this) {
this[PropertySymbol.ownerDocument][PropertySymbol.activeElement] = null;
}
}
if (isConnected && this.connectedCallback) {
this.connectedCallback();
}
else if (!isConnected && this.disconnectedCallback) {
this.disconnectedCallback();
}
for (const child of this[PropertySymbol.childNodes]) {
child[PropertySymbol.connectToNode](this);
}
// eslint-disable-next-line
if (this[PropertySymbol.shadowRoot]) {
// eslint-disable-next-line
this[PropertySymbol.shadowRoot][PropertySymbol.connectToNode](this);
}
}
else if (formNode !== this[PropertySymbol.formNode] ||
selectNode !== this[PropertySymbol.selectNode] ||
textAreaNode !== this[PropertySymbol.textAreaNode]) {
for (const child of this[PropertySymbol.childNodes]) {
child[PropertySymbol.connectToNode](this);
}
}
}
/**
* 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;
/**
* 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_js_1.default.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_js_1.default.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 attr of Object.values(node2[PropertySymbol.attributes])) {
/**
* 5.2.1.1. If attr equals attr1, then return the result of adding DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC and DOCUMENT_POSITION_PRECEDING.
*/
if (NodeUtility_js_1.default.isEqualNode(attr, 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_js_1.default.isEqualNode(attr, 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.childNodes]);
if (childNode === node2Node) {
node2Index = indexes;
}
else if (childNode === node1Node) {
node1Index = indexes;
}
if (node2Index !== -1 && node1Index !== -1) {
break;
}
indexes++;
}
};
computeNodeIndexes(commonAncestor[PropertySymbol.childNodes]);
/**
* 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_js_1.default.isTextNode(child)) {
// Append text of all following text nodes, and remove them.
while (NodeUtility_js_1.default.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;
}
}
// Public properties
Node.ELEMENT_NODE = NodeTypeEnum_js_1.default.elementNode;
Node.ATTRIBUTE_NODE = NodeTypeEnum_js_1.default.attributeNode;
Node.TEXT_NODE = NodeTypeEnum_js_1.default.textNode;
Node.CDATA_SECTION_NODE = NodeTypeEnum_js_1.default.cdataSectionNode;
Node.COMMENT_NODE = NodeTypeEnum_js_1.default.commentNode;
Node.DOCUMENT_NODE = NodeTypeEnum_js_1.default.documentNode;
Node.DOCUMENT_TYPE_NODE = NodeTypeEnum_js_1.default.documentTypeNode;
Node.DOCUMENT_FRAGMENT_NODE = NodeTypeEnum_js_1.default.documentFragmentNode;
Node.PROCESSING_INSTRUCTION_NODE = NodeTypeEnum_js_1.default.processingInstructionNode;
Node.DOCUMENT_POSITION_CONTAINED_BY = NodeDocumentPositionEnum_js_1.default.containedBy;
Node.DOCUMENT_POSITION_CONTAINS = NodeDocumentPositionEnum_js_1.default.contains;
Node.DOCUMENT_POSITION_DISCONNECTED = NodeDocumentPositionEnum_js_1.default.disconnect;
Node.DOCUMENT_POSITION_FOLLOWING = NodeDocumentPositionEnum_js_1.default.following;
Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = NodeDocumentPositionEnum_js_1.default.implementationSpecific;
Node.DOCUMENT_POSITION_PRECEDING = NodeDocumentPositionEnum_js_1.default.preceding;
exports.default = Node;
// According to the spec, these properties should be on the prototype.
Node.prototype.ELEMENT_NODE = NodeTypeEnum_js_1.default.elementNode;
Node.prototype.ATTRIBUTE_NODE = NodeTypeEnum_js_1.default.attributeNode;
Node.prototype.TEXT_NODE = NodeTypeEnum_js_1.default.textNode;
Node.prototype.CDATA_SECTION_NODE = NodeTypeEnum_js_1.default.cdataSectionNode;
Node.prototype.COMMENT_NODE = NodeTypeEnum_js_1.default.commentNode;
Node.prototype.DOCUMENT_NODE = NodeTypeEnum_js_1.default.documentNode;
Node.prototype.DOCUMENT_TYPE_NODE = NodeTypeEnum_js_1.default.documentTypeNode;
Node.prototype.DOCUMENT_FRAGMENT_NODE = NodeTypeEnum_js_1.default.documentFragmentNode;
Node.prototype.PROCESSING_INSTRUCTION_NODE = NodeTypeEnum_js_1.default.processingInstructionNode;
Node.prototype.DOCUMENT_POSITION_CONTAINED_BY =
NodeDocumentPositionEnum_js_1.default.containedBy;
Node.prototype.DOCUMENT_POSITION_CONTAINS =
NodeDocumentPositionEnum_js_1.default.contains;
Node.prototype.DOCUMENT_POSITION_DISCONNECTED =
NodeDocumentPositionEnum_js_1.default.disconnect;
Node.prototype.DOCUMENT_POSITION_FOLLOWING =
NodeDocumentPositionEnum_js_1.default.following;
Node.prototype.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC =
NodeDocumentPositionEnum_js_1.default.implementationSpecific;
Node.prototype.DOCUMENT_POSITION_PRECEDING =
NodeDocumentPositionEnum_js_1.default.preceding;
//# sourceMappingURL=Node.cjs.map