UNPKG

projen

Version:

CDK for software projects

998 lines 44.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mutation_ensurePreInsertionValidity = mutation_ensurePreInsertionValidity; exports.mutation_preInsert = mutation_preInsert; exports.mutation_insert = mutation_insert; exports.mutation_append = mutation_append; exports.mutation_replace = mutation_replace; exports.mutation_replaceAll = mutation_replaceAll; exports.mutation_preRemove = mutation_preRemove; exports.mutation_remove = mutation_remove; const DOMImpl_1 = require("../dom/DOMImpl"); const DOMException_1 = require("../dom/DOMException"); const interfaces_1 = require("../dom/interfaces"); const util_1 = require("../util"); const util_2 = require("@oozcitak/util"); const infra_1 = require("@oozcitak/infra"); const CustomElementAlgorithm_1 = require("./CustomElementAlgorithm"); const TreeAlgorithm_1 = require("./TreeAlgorithm"); const NodeIteratorAlgorithm_1 = require("./NodeIteratorAlgorithm"); const ShadowTreeAlgorithm_1 = require("./ShadowTreeAlgorithm"); const MutationObserverAlgorithm_1 = require("./MutationObserverAlgorithm"); const DOMAlgorithm_1 = require("./DOMAlgorithm"); const DocumentAlgorithm_1 = require("./DocumentAlgorithm"); /** * Ensures pre-insertion validity of a node into a parent before a * child. * * @param node - node to insert * @param parent - parent node to receive node * @param child - child node to insert node before */ function mutation_ensurePreInsertionValidity(node, parent, child) { const parentNodeType = parent._nodeType; const nodeNodeType = node._nodeType; const childNodeType = child ? child._nodeType : null; /** * 1. If parent is not a Document, DocumentFragment, or Element node, * throw a "HierarchyRequestError" DOMException. */ if (parentNodeType !== interfaces_1.NodeType.Document && parentNodeType !== interfaces_1.NodeType.DocumentFragment && parentNodeType !== interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`Only document, document fragment and element nodes can contain child nodes. Parent node is ${parent.nodeName}.`); /** * 2. If node is a host-including inclusive ancestor of parent, throw a * "HierarchyRequestError" DOMException. */ if ((0, TreeAlgorithm_1.tree_isHostIncludingAncestorOf)(parent, node, true)) throw new DOMException_1.HierarchyRequestError(`The node to be inserted cannot be an inclusive ancestor of parent node. Node is ${node.nodeName}, parent node is ${parent.nodeName}.`); /** * 3. If child is not null and its parent is not parent, then throw a * "NotFoundError" DOMException. */ if (child !== null && child._parent !== parent) throw new DOMException_1.NotFoundError(`The reference child node cannot be found under parent node. Child node is ${child.nodeName}, parent node is ${parent.nodeName}.`); /** * 4. If node is not a DocumentFragment, DocumentType, Element, Text, * ProcessingInstruction, or Comment node, throw a "HierarchyRequestError" * DOMException. */ if (nodeNodeType !== interfaces_1.NodeType.DocumentFragment && nodeNodeType !== interfaces_1.NodeType.DocumentType && nodeNodeType !== interfaces_1.NodeType.Element && nodeNodeType !== interfaces_1.NodeType.Text && nodeNodeType !== interfaces_1.NodeType.ProcessingInstruction && nodeNodeType !== interfaces_1.NodeType.CData && nodeNodeType !== interfaces_1.NodeType.Comment) throw new DOMException_1.HierarchyRequestError(`Only document fragment, document type, element, text, processing instruction, cdata section or comment nodes can be inserted. Node is ${node.nodeName}.`); /** * 5. If either node is a Text node and parent is a document, or node is a * doctype and parent is not a document, throw a "HierarchyRequestError" * DOMException. */ if (nodeNodeType === interfaces_1.NodeType.Text && parentNodeType === interfaces_1.NodeType.Document) throw new DOMException_1.HierarchyRequestError(`Cannot insert a text node as a child of a document node. Node is ${node.nodeName}.`); if (nodeNodeType === interfaces_1.NodeType.DocumentType && parentNodeType !== interfaces_1.NodeType.Document) throw new DOMException_1.HierarchyRequestError(`A document type node can only be inserted under a document node. Parent node is ${parent.nodeName}.`); /** * 6. If parent is a document, and any of the statements below, switched on * node, are true, throw a "HierarchyRequestError" DOMException. * - DocumentFragment node * If node has more than one element child or has a Text node child. * Otherwise, if node has one element child and either parent has an element * child, child is a doctype, or child is not null and a doctype is * following child. * - element * parent has an element child, child is a doctype, or child is not null and * a doctype is following child. * - doctype * parent has a doctype child, child is non-null and an element is preceding * child, or child is null and parent has an element child. */ if (parentNodeType === interfaces_1.NodeType.Document) { if (nodeNodeType === interfaces_1.NodeType.DocumentFragment) { let eleCount = 0; for (const childNode of node._children) { if (childNode._nodeType === interfaces_1.NodeType.Element) eleCount++; else if (childNode._nodeType === interfaces_1.NodeType.Text) throw new DOMException_1.HierarchyRequestError(`Cannot insert text a node as a child of a document node. Node is ${childNode.nodeName}.`); } if (eleCount > 1) { throw new DOMException_1.HierarchyRequestError(`A document node can only have one document element node. Document fragment to be inserted has ${eleCount} element nodes.`); } else if (eleCount === 1) { for (const ele of parent._children) { if (ele._nodeType === interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`The document node already has a document element node.`); } if (child) { if (childNodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Cannot insert an element node before a document type node.`); let doctypeChild = child._nextSibling; while (doctypeChild) { if (doctypeChild._nodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Cannot insert an element node before a document type node.`); doctypeChild = doctypeChild._nextSibling; } } } } else if (nodeNodeType === interfaces_1.NodeType.Element) { for (const ele of parent._children) { if (ele._nodeType === interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`Document already has a document element node. Node is ${node.nodeName}.`); } if (child) { if (childNodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Cannot insert an element node before a document type node. Node is ${node.nodeName}.`); let doctypeChild = child._nextSibling; while (doctypeChild) { if (doctypeChild._nodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Cannot insert an element node before a document type node. Node is ${node.nodeName}.`); doctypeChild = doctypeChild._nextSibling; } } } else if (nodeNodeType === interfaces_1.NodeType.DocumentType) { for (const ele of parent._children) { if (ele._nodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Document already has a document type node. Node is ${node.nodeName}.`); } if (child) { let elementChild = child._previousSibling; while (elementChild) { if (elementChild._nodeType === interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`Cannot insert a document type node before an element node. Node is ${node.nodeName}.`); elementChild = elementChild._previousSibling; } } else { let elementChild = parent._firstChild; while (elementChild) { if (elementChild._nodeType === interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`Cannot insert a document type node before an element node. Node is ${node.nodeName}.`); elementChild = elementChild._nextSibling; } } } } } /** * Ensures pre-insertion validity of a node into a parent before a * child, then adopts the node to the tree and inserts it. * * @param node - node to insert * @param parent - parent node to receive node * @param child - child node to insert node before */ function mutation_preInsert(node, parent, child) { /** * 1. Ensure pre-insertion validity of node into parent before child. * 2. Let reference child be child. * 3. If reference child is node, set it to node’s next sibling. * 4. Adopt node into parent’s node document. * 5. Insert node into parent before reference child. * 6. Return node. */ mutation_ensurePreInsertionValidity(node, parent, child); let referenceChild = child; if (referenceChild === node) referenceChild = node._nextSibling; (0, DocumentAlgorithm_1.document_adopt)(node, parent._nodeDocument); mutation_insert(node, parent, referenceChild); return node; } /** * Inserts a node into a parent node before the given child node. * * @param node - node to insert * @param parent - parent node to receive node * @param child - child node to insert node before * @param suppressObservers - whether to notify observers */ function mutation_insert(node, parent, child, suppressObservers) { // Optimized common case if (child === null && node._nodeType !== interfaces_1.NodeType.DocumentFragment) { mutation_insert_single(node, parent, suppressObservers); return; } /** * 1. Let count be the number of children of node if it is a * DocumentFragment node, and one otherwise. */ const count = (node._nodeType === interfaces_1.NodeType.DocumentFragment ? node._children.size : 1); /** * 2. If child is non-null, then: */ if (child !== null) { /** * 2.1. For each live range whose start node is parent and start * offset is greater than child's index, increase its start * offset by count. * 2.2. For each live range whose end node is parent and end * offset is greater than child's index, increase its end * offset by count. */ if (DOMImpl_1.dom.rangeList.size !== 0) { const index = (0, TreeAlgorithm_1.tree_index)(child); for (const range of DOMImpl_1.dom.rangeList) { if (range._start[0] === parent && range._start[1] > index) { range._start[1] += count; } if (range._end[0] === parent && range._end[1] > index) { range._end[1] += count; } } } } /** * 3. Let nodes be node’s children, if node is a DocumentFragment node; * otherwise « node ». */ const nodes = node._nodeType === interfaces_1.NodeType.DocumentFragment ? new Array(...node._children) : [node]; /** * 4. If node is a DocumentFragment node, remove its children with the * suppress observers flag set. */ if (node._nodeType === interfaces_1.NodeType.DocumentFragment) { while (node._firstChild) { mutation_remove(node._firstChild, node, true); } } /** * 5. If node is a DocumentFragment node, then queue a tree mutation record * for node with « », nodes, null, and null. */ if (DOMImpl_1.dom.features.mutationObservers) { if (node._nodeType === interfaces_1.NodeType.DocumentFragment) { (0, MutationObserverAlgorithm_1.observer_queueTreeMutationRecord)(node, [], nodes, null, null); } } /** * 6. Let previousSibling be child’s previous sibling or parent’s last * child if child is null. */ const previousSibling = (child ? child._previousSibling : parent._lastChild); let index = child === null ? -1 : (0, TreeAlgorithm_1.tree_index)(child); /** * 7. For each node in nodes, in tree order: */ for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; if (util_1.Guard.isElementNode(node)) { // set document element node if (util_1.Guard.isDocumentNode(parent)) { parent._documentElement = node; } // mark that the document has namespaces if (!node._nodeDocument._hasNamespaces && (node._namespace !== null || node._namespacePrefix !== null)) { node._nodeDocument._hasNamespaces = true; } } /** * 7.1. If child is null, then append node to parent’s children. * 7.2. Otherwise, insert node into parent’s children before child’s * index. */ node._parent = parent; if (child === null) { infra_1.set.append(parent._children, node); } else { infra_1.set.insert(parent._children, node, index); index++; } // assign siblings and children for quick lookups if (parent._firstChild === null) { node._previousSibling = null; node._nextSibling = null; parent._firstChild = node; parent._lastChild = node; } else { const prev = (child ? child._previousSibling : parent._lastChild); const next = (child ? child : null); node._previousSibling = prev; node._nextSibling = next; if (prev) prev._nextSibling = node; if (next) next._previousSibling = node; if (!prev) parent._firstChild = node; if (!next) parent._lastChild = node; } /** * 7.3. If parent is a shadow host and node is a slotable, then * assign a slot for node. */ if (DOMImpl_1.dom.features.slots) { if (parent._shadowRoot !== null && util_1.Guard.isSlotable(node)) { (0, ShadowTreeAlgorithm_1.shadowTree_assignASlot)(node); } } /** * 7.4. If node is a Text node, run the child text content change * steps for parent. */ if (DOMImpl_1.dom.features.steps) { if (util_1.Guard.isTextNode(node)) { (0, DOMAlgorithm_1.dom_runChildTextContentChangeSteps)(parent); } } /** * 7.5. If parent's root is a shadow root, and parent is a slot * whose assigned nodes is the empty list, then run signal * a slot change for parent. */ if (DOMImpl_1.dom.features.slots) { if (util_1.Guard.isShadowRoot((0, TreeAlgorithm_1.tree_rootNode)(parent)) && util_1.Guard.isSlot(parent) && (0, util_2.isEmpty)(parent._assignedNodes)) { (0, ShadowTreeAlgorithm_1.shadowTree_signalASlotChange)(parent); } } /** * 7.6. Run assign slotables for a tree with node's root. */ if (DOMImpl_1.dom.features.slots) { (0, ShadowTreeAlgorithm_1.shadowTree_assignSlotablesForATree)((0, TreeAlgorithm_1.tree_rootNode)(node)); } /** * 7.7. For each shadow-including inclusive descendant * inclusiveDescendant of node, in shadow-including tree * order: */ let inclusiveDescendant = (0, TreeAlgorithm_1.tree_getFirstDescendantNode)(node, true, true); while (inclusiveDescendant !== null) { /** * 7.7.1. Run the insertion steps with inclusiveDescendant. */ if (DOMImpl_1.dom.features.steps) { (0, DOMAlgorithm_1.dom_runInsertionSteps)(inclusiveDescendant); } if (DOMImpl_1.dom.features.customElements) { /** * 7.7.2. If inclusiveDescendant is connected, then: */ if (util_1.Guard.isElementNode(inclusiveDescendant) && (0, ShadowTreeAlgorithm_1.shadowTree_isConnected)(inclusiveDescendant)) { if (util_1.Guard.isCustomElementNode(inclusiveDescendant)) { /** * 7.7.2.1. If inclusiveDescendant is custom, then enqueue a custom * element callback reaction with inclusiveDescendant, callback name * "connectedCallback", and an empty argument list. */ (0, CustomElementAlgorithm_1.customElement_enqueueACustomElementCallbackReaction)(inclusiveDescendant, "connectedCallback", []); } else { /** * 7.7.2.2. Otherwise, try to upgrade inclusiveDescendant. */ (0, CustomElementAlgorithm_1.customElement_tryToUpgrade)(inclusiveDescendant); } } } inclusiveDescendant = (0, TreeAlgorithm_1.tree_getNextDescendantNode)(node, inclusiveDescendant, true, true); } } /** * 8. If suppress observers flag is unset, then queue a tree mutation record * for parent with nodes, « », previousSibling, and child. */ if (DOMImpl_1.dom.features.mutationObservers) { if (!suppressObservers) { (0, MutationObserverAlgorithm_1.observer_queueTreeMutationRecord)(parent, nodes, [], previousSibling, child); } } } /** * Inserts a node into a parent node. Optimized routine for the common case where * node is not a document fragment node and it has no child nodes. * * @param node - node to insert * @param parent - parent node to receive node * @param suppressObservers - whether to notify observers */ function mutation_insert_single(node, parent, suppressObservers) { /** * 1. Let count be the number of children of node if it is a * DocumentFragment node, and one otherwise. * 2. If child is non-null, then: * 2.1. For each live range whose start node is parent and start * offset is greater than child's index, increase its start * offset by count. * 2.2. For each live range whose end node is parent and end * offset is greater than child's index, increase its end * offset by count. * 3. Let nodes be node’s children, if node is a DocumentFragment node; * otherwise « node ». * 4. If node is a DocumentFragment node, remove its children with the * suppress observers flag set. * 5. If node is a DocumentFragment node, then queue a tree mutation record * for node with « », nodes, null, and null. */ /** * 6. Let previousSibling be child’s previous sibling or parent’s last * child if child is null. */ const previousSibling = parent._lastChild; // set document element node if (util_1.Guard.isElementNode(node)) { // set document element node if (util_1.Guard.isDocumentNode(parent)) { parent._documentElement = node; } // mark that the document has namespaces if (!node._nodeDocument._hasNamespaces && (node._namespace !== null || node._namespacePrefix !== null)) { node._nodeDocument._hasNamespaces = true; } } /** * 7. For each node in nodes, in tree order: * 7.1. If child is null, then append node to parent’s children. * 7.2. Otherwise, insert node into parent’s children before child’s * index. */ node._parent = parent; parent._children.add(node); // assign siblings and children for quick lookups if (parent._firstChild === null) { node._previousSibling = null; node._nextSibling = null; parent._firstChild = node; parent._lastChild = node; } else { const prev = parent._lastChild; node._previousSibling = prev; node._nextSibling = null; if (prev) prev._nextSibling = node; if (!prev) parent._firstChild = node; parent._lastChild = node; } /** * 7.3. If parent is a shadow host and node is a slotable, then * assign a slot for node. */ if (DOMImpl_1.dom.features.slots) { if (parent._shadowRoot !== null && util_1.Guard.isSlotable(node)) { (0, ShadowTreeAlgorithm_1.shadowTree_assignASlot)(node); } } /** * 7.4. If node is a Text node, run the child text content change * steps for parent. */ if (DOMImpl_1.dom.features.steps) { if (util_1.Guard.isTextNode(node)) { (0, DOMAlgorithm_1.dom_runChildTextContentChangeSteps)(parent); } } /** * 7.5. If parent's root is a shadow root, and parent is a slot * whose assigned nodes is the empty list, then run signal * a slot change for parent. */ if (DOMImpl_1.dom.features.slots) { if (util_1.Guard.isShadowRoot((0, TreeAlgorithm_1.tree_rootNode)(parent)) && util_1.Guard.isSlot(parent) && (0, util_2.isEmpty)(parent._assignedNodes)) { (0, ShadowTreeAlgorithm_1.shadowTree_signalASlotChange)(parent); } } /** * 7.6. Run assign slotables for a tree with node's root. */ if (DOMImpl_1.dom.features.slots) { (0, ShadowTreeAlgorithm_1.shadowTree_assignSlotablesForATree)((0, TreeAlgorithm_1.tree_rootNode)(node)); } /** * 7.7. For each shadow-including inclusive descendant * inclusiveDescendant of node, in shadow-including tree * order: * 7.7.1. Run the insertion steps with inclusiveDescendant. */ if (DOMImpl_1.dom.features.steps) { (0, DOMAlgorithm_1.dom_runInsertionSteps)(node); } if (DOMImpl_1.dom.features.customElements) { /** * 7.7.2. If inclusiveDescendant is connected, then: */ if (util_1.Guard.isElementNode(node) && (0, ShadowTreeAlgorithm_1.shadowTree_isConnected)(node)) { if (util_1.Guard.isCustomElementNode(node)) { /** * 7.7.2.1. If inclusiveDescendant is custom, then enqueue a custom * element callback reaction with inclusiveDescendant, callback name * "connectedCallback", and an empty argument list. */ (0, CustomElementAlgorithm_1.customElement_enqueueACustomElementCallbackReaction)(node, "connectedCallback", []); } else { /** * 7.7.2.2. Otherwise, try to upgrade inclusiveDescendant. */ (0, CustomElementAlgorithm_1.customElement_tryToUpgrade)(node); } } } /** * 8. If suppress observers flag is unset, then queue a tree mutation record * for parent with nodes, « », previousSibling, and child. */ if (DOMImpl_1.dom.features.mutationObservers) { if (!suppressObservers) { (0, MutationObserverAlgorithm_1.observer_queueTreeMutationRecord)(parent, [node], [], previousSibling, null); } } } /** * Appends a node to the children of a parent node. * * @param node - a node * @param parent - the parent to receive node */ function mutation_append(node, parent) { /** * To append a node to a parent, pre-insert node into parent before null. */ return mutation_preInsert(node, parent, null); } /** * Replaces a node with another node. * * @param child - child node to remove * @param node - node to insert * @param parent - parent node to receive node */ function mutation_replace(child, node, parent) { /** * 1. If parent is not a Document, DocumentFragment, or Element node, * throw a "HierarchyRequestError" DOMException. */ if (parent._nodeType !== interfaces_1.NodeType.Document && parent._nodeType !== interfaces_1.NodeType.DocumentFragment && parent._nodeType !== interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`Only document, document fragment and element nodes can contain child nodes. Parent node is ${parent.nodeName}.`); /** * 2. If node is a host-including inclusive ancestor of parent, throw a * "HierarchyRequestError" DOMException. */ if ((0, TreeAlgorithm_1.tree_isHostIncludingAncestorOf)(parent, node, true)) throw new DOMException_1.HierarchyRequestError(`The node to be inserted cannot be an ancestor of parent node. Node is ${node.nodeName}, parent node is ${parent.nodeName}.`); /** * 3. If child’s parent is not parent, then throw a "NotFoundError" * DOMException. */ if (child._parent !== parent) throw new DOMException_1.NotFoundError(`The reference child node cannot be found under parent node. Child node is ${child.nodeName}, parent node is ${parent.nodeName}.`); /** * 4. If node is not a DocumentFragment, DocumentType, Element, Text, * ProcessingInstruction, or Comment node, throw a "HierarchyRequestError" * DOMException. */ if (node._nodeType !== interfaces_1.NodeType.DocumentFragment && node._nodeType !== interfaces_1.NodeType.DocumentType && node._nodeType !== interfaces_1.NodeType.Element && node._nodeType !== interfaces_1.NodeType.Text && node._nodeType !== interfaces_1.NodeType.ProcessingInstruction && node._nodeType !== interfaces_1.NodeType.CData && node._nodeType !== interfaces_1.NodeType.Comment) throw new DOMException_1.HierarchyRequestError(`Only document fragment, document type, element, text, processing instruction, cdata section or comment nodes can be inserted. Node is ${node.nodeName}.`); /** * 5. If either node is a Text node and parent is a document, or node is a * doctype and parent is not a document, throw a "HierarchyRequestError" * DOMException. */ if (node._nodeType === interfaces_1.NodeType.Text && parent._nodeType === interfaces_1.NodeType.Document) throw new DOMException_1.HierarchyRequestError(`Cannot insert a text node as a child of a document node. Node is ${node.nodeName}.`); if (node._nodeType === interfaces_1.NodeType.DocumentType && parent._nodeType !== interfaces_1.NodeType.Document) throw new DOMException_1.HierarchyRequestError(`A document type node can only be inserted under a document node. Parent node is ${parent.nodeName}.`); /** * 6. If parent is a document, and any of the statements below, switched on * node, are true, throw a "HierarchyRequestError" DOMException. * - DocumentFragment node * If node has more than one element child or has a Text node child. * Otherwise, if node has one element child and either parent has an element * child that is not child or a doctype is following child. * - element * parent has an element child that is not child or a doctype is * following child. * - doctype * parent has a doctype child that is not child, or an element is * preceding child. */ if (parent._nodeType === interfaces_1.NodeType.Document) { if (node._nodeType === interfaces_1.NodeType.DocumentFragment) { let eleCount = 0; for (const childNode of node._children) { if (childNode._nodeType === interfaces_1.NodeType.Element) eleCount++; else if (childNode._nodeType === interfaces_1.NodeType.Text) throw new DOMException_1.HierarchyRequestError(`Cannot insert text a node as a child of a document node. Node is ${childNode.nodeName}.`); } if (eleCount > 1) { throw new DOMException_1.HierarchyRequestError(`A document node can only have one document element node. Document fragment to be inserted has ${eleCount} element nodes.`); } else if (eleCount === 1) { for (const ele of parent._children) { if (ele._nodeType === interfaces_1.NodeType.Element && ele !== child) throw new DOMException_1.HierarchyRequestError(`The document node already has a document element node.`); } let doctypeChild = child._nextSibling; while (doctypeChild) { if (doctypeChild._nodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Cannot insert an element node before a document type node.`); doctypeChild = doctypeChild._nextSibling; } } } else if (node._nodeType === interfaces_1.NodeType.Element) { for (const ele of parent._children) { if (ele._nodeType === interfaces_1.NodeType.Element && ele !== child) throw new DOMException_1.HierarchyRequestError(`Document already has a document element node. Node is ${node.nodeName}.`); } let doctypeChild = child._nextSibling; while (doctypeChild) { if (doctypeChild._nodeType === interfaces_1.NodeType.DocumentType) throw new DOMException_1.HierarchyRequestError(`Cannot insert an element node before a document type node. Node is ${node.nodeName}.`); doctypeChild = doctypeChild._nextSibling; } } else if (node._nodeType === interfaces_1.NodeType.DocumentType) { for (const ele of parent._children) { if (ele._nodeType === interfaces_1.NodeType.DocumentType && ele !== child) throw new DOMException_1.HierarchyRequestError(`Document already has a document type node. Node is ${node.nodeName}.`); } let elementChild = child._previousSibling; while (elementChild) { if (elementChild._nodeType === interfaces_1.NodeType.Element) throw new DOMException_1.HierarchyRequestError(`Cannot insert a document type node before an element node. Node is ${node.nodeName}.`); elementChild = elementChild._previousSibling; } } } /** * 7. Let reference child be child’s next sibling. * 8. If reference child is node, set it to node’s next sibling. * 8. Let previousSibling be child’s previous sibling. */ let referenceChild = child._nextSibling; if (referenceChild === node) referenceChild = node._nextSibling; let previousSibling = child._previousSibling; /** * 10. Adopt node into parent’s node document. * 11. Let removedNodes be the empty list. */ (0, DocumentAlgorithm_1.document_adopt)(node, parent._nodeDocument); const removedNodes = []; /** * 12. If child’s parent is not null, then: */ if (child._parent !== null) { /** * 12.1. Set removedNodes to [child]. * 12.2. Remove child from its parent with the suppress observers flag * set. */ removedNodes.push(child); mutation_remove(child, child._parent, true); } /** * 13. Let nodes be node’s children if node is a DocumentFragment node; * otherwise [node]. */ let nodes = []; if (node._nodeType === interfaces_1.NodeType.DocumentFragment) { nodes = Array.from(node._children); } else { nodes.push(node); } /** * 14. Insert node into parent before reference child with the suppress * observers flag set. */ mutation_insert(node, parent, referenceChild, true); /** * 15. Queue a tree mutation record for parent with nodes, removedNodes, * previousSibling, and reference child. */ if (DOMImpl_1.dom.features.mutationObservers) { (0, MutationObserverAlgorithm_1.observer_queueTreeMutationRecord)(parent, nodes, removedNodes, previousSibling, referenceChild); } /** * 16. Return child. */ return child; } /** * Replaces all nodes of a parent with the given node. * * @param node - node to insert * @param parent - parent node to receive node */ function mutation_replaceAll(node, parent) { /** * 1. If node is not null, adopt node into parent’s node document. */ if (node !== null) { (0, DocumentAlgorithm_1.document_adopt)(node, parent._nodeDocument); } /** * 2. Let removedNodes be parent’s children. */ const removedNodes = Array.from(parent._children); /** * 3. Let addedNodes be the empty list. * 4. If node is DocumentFragment node, then set addedNodes to node’s * children. * 5. Otherwise, if node is non-null, set addedNodes to [node]. */ let addedNodes = []; if (node && node._nodeType === interfaces_1.NodeType.DocumentFragment) { addedNodes = Array.from(node._children); } else if (node !== null) { addedNodes.push(node); } /** * 6. Remove all parent’s children, in tree order, with the suppress * observers flag set. */ for (const childNode of removedNodes) { mutation_remove(childNode, parent, true); } /** * 7. If node is not null, then insert node into parent before null with the * suppress observers flag set. */ if (node !== null) { mutation_insert(node, parent, null, true); } /** * 8. Queue a tree mutation record for parent with addedNodes, removedNodes, * null, and null. */ if (DOMImpl_1.dom.features.mutationObservers) { (0, MutationObserverAlgorithm_1.observer_queueTreeMutationRecord)(parent, addedNodes, removedNodes, null, null); } } /** * Ensures pre-removal validity of a child node from a parent, then * removes it. * * @param child - child node to remove * @param parent - parent node */ function mutation_preRemove(child, parent) { /** * 1. If child’s parent is not parent, then throw a "NotFoundError" * DOMException. * 2. Remove child from parent. * 3. Return child. */ if (child._parent !== parent) throw new DOMException_1.NotFoundError(`The child node cannot be found under parent node. Child node is ${child.nodeName}, parent node is ${parent.nodeName}.`); mutation_remove(child, parent); return child; } /** * Removes a child node from its parent. * * @param node - node to remove * @param parent - parent node * @param suppressObservers - whether to notify observers */ function mutation_remove(node, parent, suppressObservers) { if (DOMImpl_1.dom.rangeList.size !== 0) { /** * 1. Let index be node’s index. */ const index = (0, TreeAlgorithm_1.tree_index)(node); /** * 2. For each live range whose start node is an inclusive descendant of * node, set its start to (parent, index). * 3. For each live range whose end node is an inclusive descendant of * node, set its end to (parent, index). */ for (const range of DOMImpl_1.dom.rangeList) { if ((0, TreeAlgorithm_1.tree_isDescendantOf)(node, range._start[0], true)) { range._start = [parent, index]; } if ((0, TreeAlgorithm_1.tree_isDescendantOf)(node, range._end[0], true)) { range._end = [parent, index]; } if (range._start[0] === parent && range._start[1] > index) { range._start[1]--; } if (range._end[0] === parent && range._end[1] > index) { range._end[1]--; } } /** * 4. For each live range whose start node is parent and start offset is * greater than index, decrease its start offset by 1. * 5. For each live range whose end node is parent and end offset is greater * than index, decrease its end offset by 1. */ for (const range of DOMImpl_1.dom.rangeList) { if (range._start[0] === parent && range._start[1] > index) { range._start[1] -= 1; } if (range._end[0] === parent && range._end[1] > index) { range._end[1] -= 1; } } } /** * 6. For each NodeIterator object iterator whose root’s node document is * node’s node document, run the NodeIterator pre-removing steps given node * and iterator. */ if (DOMImpl_1.dom.features.steps) { for (const iterator of (0, NodeIteratorAlgorithm_1.nodeIterator_iteratorList)()) { if (iterator._root._nodeDocument === node._nodeDocument) { (0, DOMAlgorithm_1.dom_runNodeIteratorPreRemovingSteps)(iterator, node); } } } /** * 7. Let oldPreviousSibling be node’s previous sibling. * 8. Let oldNextSibling be node’s next sibling. */ const oldPreviousSibling = node._previousSibling; const oldNextSibling = node._nextSibling; // set document element node if (util_1.Guard.isDocumentNode(parent) && util_1.Guard.isElementNode(node)) { parent._documentElement = null; } /** * 9. Remove node from its parent’s children. */ node._parent = null; parent._children.delete(node); // assign siblings and children for quick lookups const prev = node._previousSibling; const next = node._nextSibling; node._previousSibling = null; node._nextSibling = null; if (prev) prev._nextSibling = next; if (next) next._previousSibling = prev; if (!prev) parent._firstChild = next; if (!next) parent._lastChild = prev; /** * 10. If node is assigned, then run assign slotables for node’s assigned * slot. */ if (DOMImpl_1.dom.features.slots) { if (util_1.Guard.isSlotable(node) && node._assignedSlot !== null && (0, ShadowTreeAlgorithm_1.shadowTree_isAssigned)(node)) { (0, ShadowTreeAlgorithm_1.shadowTree_assignSlotables)(node._assignedSlot); } } /** * 11. If parent’s root is a shadow root, and parent is a slot whose * assigned nodes is the empty list, then run signal a slot change for * parent. */ if (DOMImpl_1.dom.features.slots) { if (util_1.Guard.isShadowRoot((0, TreeAlgorithm_1.tree_rootNode)(parent)) && util_1.Guard.isSlot(parent) && (0, util_2.isEmpty)(parent._assignedNodes)) { (0, ShadowTreeAlgorithm_1.shadowTree_signalASlotChange)(parent); } } /** * 12. If node has an inclusive descendant that is a slot, then: * 12.1. Run assign slotables for a tree with parent's root. * 12.2. Run assign slotables for a tree with node. */ if (DOMImpl_1.dom.features.slots) { const descendant = (0, TreeAlgorithm_1.tree_getFirstDescendantNode)(node, true, false, (e) => util_1.Guard.isSlot(e)); if (descendant !== null) { (0, ShadowTreeAlgorithm_1.shadowTree_assignSlotablesForATree)((0, TreeAlgorithm_1.tree_rootNode)(parent)); (0, ShadowTreeAlgorithm_1.shadowTree_assignSlotablesForATree)(node); } } /** * 13. Run the removing steps with node and parent. */ if (DOMImpl_1.dom.features.steps) { (0, DOMAlgorithm_1.dom_runRemovingSteps)(node, parent); } /** * 14. If node is custom, then enqueue a custom element callback * reaction with node, callback name "disconnectedCallback", * and an empty argument list. */ if (DOMImpl_1.dom.features.customElements) { if (util_1.Guard.isCustomElementNode(node)) { (0, CustomElementAlgorithm_1.customElement_enqueueACustomElementCallbackReaction)(node, "disconnectedCallback", []); } } /** * 15. For each shadow-including descendant descendant of node, * in shadow-including tree order, then: */ let descendant = (0, TreeAlgorithm_1.tree_getFirstDescendantNode)(node, false, true); while (descendant !== null) { /** * 15.1. Run the removing steps with descendant. */ if (DOMImpl_1.dom.features.steps) { (0, DOMAlgorithm_1.dom_runRemovingSteps)(descendant, node); } /** * 15.2. If descendant is custom, then enqueue a custom element * callback reaction with descendant, callback name * "disconnectedCallback", and an empty argument list. */ if (DOMImpl_1.dom.features.customElements) { if (util_1.Guard.isCustomElementNode(descendant)) { (0, CustomElementAlgorithm_1.customElement_enqueueACustomElementCallbackReaction)(descendant, "disconnectedCallback", []); } } descendant = (0, TreeAlgorithm_1.tree_getNextDescendantNode)(node, descendant, false, true); } /** * 16. For each inclusive ancestor inclusiveAncestor of parent, and * then for each registered of inclusiveAncestor's registered * observer list, if registered's options's subtree is true, * then append a new transient registered observer whose * observer is registered's observer, options is registered's * options, and source is registered to node's registered * observer list. */ if (DOMImpl_1.dom.features.mutationObservers) { let inclusiveAncestor = (0, TreeAlgorithm_1.tree_getFirstAncestorNode)(parent, true); while (inclusiveAncestor !== null) { for (const registered of inclusiveAncestor._registeredObserverList) { if (registered.options.subtree) { node._registeredObserverList.push({ observer: registered.observer, options: registered.options, source: registered }); } } inclusiveAncestor = (0, TreeAlgorithm_1.tree_getNextAncestorNode)(parent, inclusiveAncestor, true); } } /** * 17. If suppress observers flag is unset, then queue a tree mutation * record for parent with « », « node », oldPreviousSibling, and * oldNextSibling. */ if (DOMImpl_1.dom.features.mutationObservers) { if (!suppressObservers) { (0, MutationObserverAlgorithm_1.observer_queueTreeMutationRecord)(parent, [], [node], oldPreviousSibling, oldNextSibling); } } /** * 18. If node is a Text node, then run the child text content change steps * for parent. */ if (DOMImpl_1.dom.features.steps) { if (util_1.Guard.isTextNode(node)) { (0, DOMAlgorithm_1.dom_runChildTextContentChangeSteps)(parent); } } } //# sourceMappingURL=MutationAlgorithm.js.map