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.
289 lines • 10.9 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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const PropertySymbol = __importStar(require("../../PropertySymbol.cjs"));
const NodeTypeEnum_js_1 = __importDefault(require("./NodeTypeEnum.cjs"));
/**
* Node utility.
*/
class NodeUtility {
/**
* Returns whether the passed node is a text node, and narrows its type.
*
* @param node The node to be tested.
* @returns "true" if the node is a text node.
*/
static isTextNode(node) {
return node?.[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.textNode;
}
/**
* Returns boolean indicating if "ancestorNode" is an inclusive ancestor of "referenceNode".
*
* Based on:
* https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/helpers/node.js
*
* @see https://dom.spec.whatwg.org/#concept-tree-inclusive-ancestor
* @param ancestorNode Ancestor node.
* @param referenceNode Reference node.
* @param [includeShadowRoots = false] Include shadow roots.
* @returns "true" if inclusive ancestor.
*/
static isInclusiveAncestor(ancestorNode, referenceNode, includeShadowRoots = false) {
if (ancestorNode === null || referenceNode === null) {
return false;
}
if (ancestorNode === referenceNode) {
return true;
}
if (!ancestorNode[PropertySymbol.nodeArray].length) {
return false;
}
if (includeShadowRoots &&
referenceNode[PropertySymbol.isConnected] !== ancestorNode[PropertySymbol.isConnected]) {
return false;
}
if (includeShadowRoots &&
ancestorNode === referenceNode[PropertySymbol.ownerDocument] &&
referenceNode[PropertySymbol.isConnected]) {
return true;
}
let parent = referenceNode[PropertySymbol.parentNode];
while (parent) {
if (ancestorNode === parent) {
return true;
}
parent = parent[PropertySymbol.parentNode]
? parent[PropertySymbol.parentNode]
: includeShadowRoots && parent.host
? parent.host
: null;
}
return false;
}
/**
* Returns boolean indicating if nodeB is following nodeA in the document tree.
*
* Based on:
* https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/helpers/node.js
*
* @see https://dom.spec.whatwg.org/#concept-tree-following
* @param nodeA Node A.
* @param nodeB Node B.
* @returns "true" if following.
*/
static isFollowing(nodeA, nodeB) {
if (nodeA === nodeB) {
return false;
}
let current = nodeB;
while (current) {
current = this.following(current);
if (current === nodeA) {
return true;
}
}
return false;
}
/**
* Node length.
*
* Based on:
* https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/helpers/node.js
*
* @see https://dom.spec.whatwg.org/#concept-node-length
* @param node Node.
* @returns Node length.
*/
static getNodeLength(node) {
switch (node[PropertySymbol.nodeType]) {
case NodeTypeEnum_js_1.default.documentTypeNode:
return 0;
case NodeTypeEnum_js_1.default.textNode:
case NodeTypeEnum_js_1.default.processingInstructionNode:
case NodeTypeEnum_js_1.default.commentNode:
return node.data.length;
default:
return node[PropertySymbol.nodeArray].length;
}
}
/**
* Returns boolean indicating if nodeB is following nodeA in the document tree.
*
* Based on:
* https://github.com/jsdom/js-symbol-tree/blob/master/lib/SymbolTree.js#L220
*
* @param node Node.
* @param [root] Root.
* @returns Following node.
*/
static following(node, root) {
const firstChild = node.firstChild;
if (firstChild) {
return firstChild;
}
let current = node;
while (current) {
if (current === root) {
return null;
}
const nextSibling = current.nextSibling;
if (nextSibling) {
return nextSibling;
}
current = current[PropertySymbol.parentNode];
}
return null;
}
/**
* Returns the next sibling or parents sibling.
*
* @param node Node.
* @returns Next descendant node.
*/
static nextDescendantNode(node) {
while (node && !node.nextSibling) {
node = node[PropertySymbol.parentNode];
}
if (!node) {
return null;
}
return node.nextSibling;
}
/**
* Needed by https://dom.spec.whatwg.org/#concept-node-equals
*
* @param elementA
* @param elementB
*/
static attributeListsEqual(elementA, elementB) {
const attributesA = Array.from(elementA[PropertySymbol.attributes][PropertySymbol.items].values());
const attributesB = Array.from(elementB[PropertySymbol.attributes][PropertySymbol.items].values());
for (const attributeA of attributesA) {
let found = false;
for (const attributeB of attributesB) {
if (attributeA[PropertySymbol.namespaceURI] === attributeB[PropertySymbol.namespaceURI] &&
attributeA.localName === attributeB.localName &&
attributeA[PropertySymbol.value] === attributeB[PropertySymbol.value]) {
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
/**
* Check if node nodeA equals node nodeB.
* Reference: https://dom.spec.whatwg.org/#concept-node-equals
*
* @param nodeA Node A.
* @param nodeB Node B.
*/
static isEqualNode(nodeA, nodeB) {
if (nodeA[PropertySymbol.nodeType] !== nodeB[PropertySymbol.nodeType]) {
return false;
}
switch (nodeA[PropertySymbol.nodeType]) {
case NodeTypeEnum_js_1.default.documentTypeNode:
const documentTypeA = nodeA;
const documentTypeB = nodeB;
if (documentTypeA.name !== documentTypeB.name ||
documentTypeA.publicId !== documentTypeB.publicId ||
documentTypeA.systemId !== documentTypeB.systemId) {
return false;
}
break;
case NodeTypeEnum_js_1.default.elementNode:
const elementA = nodeA;
const elementB = nodeB;
if (elementA[PropertySymbol.namespaceURI] !== elementB[PropertySymbol.namespaceURI] ||
elementA[PropertySymbol.prefix] !== elementB[PropertySymbol.prefix] ||
elementA[PropertySymbol.localName] !== elementB[PropertySymbol.localName] ||
elementA[PropertySymbol.attributes][PropertySymbol.items].size !==
elementB[PropertySymbol.attributes][PropertySymbol.items].size) {
return false;
}
break;
case NodeTypeEnum_js_1.default.attributeNode:
const attributeA = nodeA;
const attributeB = nodeB;
if (attributeA[PropertySymbol.namespaceURI] !== attributeB[PropertySymbol.namespaceURI] ||
attributeA.localName !== attributeB.localName ||
attributeA[PropertySymbol.value] !== attributeB[PropertySymbol.value]) {
return false;
}
break;
case NodeTypeEnum_js_1.default.processingInstructionNode:
const processingInstructionA = nodeA;
const processingInstructionB = nodeB;
if (processingInstructionA.target !== processingInstructionB.target ||
processingInstructionA.data !== processingInstructionB.data) {
return false;
}
break;
case NodeTypeEnum_js_1.default.textNode:
case NodeTypeEnum_js_1.default.commentNode:
const textOrCommentA = nodeA;
const textOrCommentB = nodeB;
if (textOrCommentA.data !== textOrCommentB.data) {
return false;
}
break;
}
if (nodeA[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.elementNode &&
!NodeUtility.attributeListsEqual(nodeA, nodeB)) {
return false;
}
if (nodeA[PropertySymbol.nodeArray].length !==
nodeB[PropertySymbol.nodeArray].length) {
return false;
}
for (let i = 0; i < nodeA[PropertySymbol.nodeArray].length; i++) {
const childNodeA = nodeA[PropertySymbol.nodeArray][i];
const childNodeB = nodeB[PropertySymbol.nodeArray][i];
if (!NodeUtility.isEqualNode(childNodeA, childNodeB)) {
return false;
}
}
return true;
}
}
exports.default = NodeUtility;
//# sourceMappingURL=NodeUtility.cjs.map