From 76cb9c2a39d477a64824a985ade40507e3bbade1 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Fri, 13 Feb 2026 21:34:48 -0800 Subject: feat(vanilla): add testing infrastructure and tests (NK-wjnczv) --- .../parse5/dist/parser/open-element-stack.js | 324 +++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 vanilla/node_modules/parse5/dist/parser/open-element-stack.js (limited to 'vanilla/node_modules/parse5/dist/parser/open-element-stack.js') diff --git a/vanilla/node_modules/parse5/dist/parser/open-element-stack.js b/vanilla/node_modules/parse5/dist/parser/open-element-stack.js new file mode 100644 index 0000000..b3f98e4 --- /dev/null +++ b/vanilla/node_modules/parse5/dist/parser/open-element-stack.js @@ -0,0 +1,324 @@ +import { TAG_ID as $, NS, NUMBERED_HEADERS } from '../common/html.js'; +//Element utils +const IMPLICIT_END_TAG_REQUIRED = new Set([$.DD, $.DT, $.LI, $.OPTGROUP, $.OPTION, $.P, $.RB, $.RP, $.RT, $.RTC]); +const IMPLICIT_END_TAG_REQUIRED_THOROUGHLY = new Set([ + ...IMPLICIT_END_TAG_REQUIRED, + $.CAPTION, + $.COLGROUP, + $.TBODY, + $.TD, + $.TFOOT, + $.TH, + $.THEAD, + $.TR, +]); +const SCOPING_ELEMENTS_HTML = new Set([ + $.APPLET, + $.CAPTION, + $.HTML, + $.MARQUEE, + $.OBJECT, + $.TABLE, + $.TD, + $.TEMPLATE, + $.TH, +]); +const SCOPING_ELEMENTS_HTML_LIST = new Set([...SCOPING_ELEMENTS_HTML, $.OL, $.UL]); +const SCOPING_ELEMENTS_HTML_BUTTON = new Set([...SCOPING_ELEMENTS_HTML, $.BUTTON]); +const SCOPING_ELEMENTS_MATHML = new Set([$.ANNOTATION_XML, $.MI, $.MN, $.MO, $.MS, $.MTEXT]); +const SCOPING_ELEMENTS_SVG = new Set([$.DESC, $.FOREIGN_OBJECT, $.TITLE]); +const TABLE_ROW_CONTEXT = new Set([$.TR, $.TEMPLATE, $.HTML]); +const TABLE_BODY_CONTEXT = new Set([$.TBODY, $.TFOOT, $.THEAD, $.TEMPLATE, $.HTML]); +const TABLE_CONTEXT = new Set([$.TABLE, $.TEMPLATE, $.HTML]); +const TABLE_CELLS = new Set([$.TD, $.TH]); +//Stack of open elements +export class OpenElementStack { + get currentTmplContentOrNode() { + return this._isInTemplate() ? this.treeAdapter.getTemplateContent(this.current) : this.current; + } + constructor(document, treeAdapter, handler) { + this.treeAdapter = treeAdapter; + this.handler = handler; + this.items = []; + this.tagIDs = []; + this.stackTop = -1; + this.tmplCount = 0; + this.currentTagId = $.UNKNOWN; + this.current = document; + } + //Index of element + _indexOf(element) { + return this.items.lastIndexOf(element, this.stackTop); + } + //Update current element + _isInTemplate() { + return this.currentTagId === $.TEMPLATE && this.treeAdapter.getNamespaceURI(this.current) === NS.HTML; + } + _updateCurrentElement() { + this.current = this.items[this.stackTop]; + this.currentTagId = this.tagIDs[this.stackTop]; + } + //Mutations + push(element, tagID) { + this.stackTop++; + this.items[this.stackTop] = element; + this.current = element; + this.tagIDs[this.stackTop] = tagID; + this.currentTagId = tagID; + if (this._isInTemplate()) { + this.tmplCount++; + } + this.handler.onItemPush(element, tagID, true); + } + pop() { + const popped = this.current; + if (this.tmplCount > 0 && this._isInTemplate()) { + this.tmplCount--; + } + this.stackTop--; + this._updateCurrentElement(); + this.handler.onItemPop(popped, true); + } + replace(oldElement, newElement) { + const idx = this._indexOf(oldElement); + this.items[idx] = newElement; + if (idx === this.stackTop) { + this.current = newElement; + } + } + insertAfter(referenceElement, newElement, newElementID) { + const insertionIdx = this._indexOf(referenceElement) + 1; + this.items.splice(insertionIdx, 0, newElement); + this.tagIDs.splice(insertionIdx, 0, newElementID); + this.stackTop++; + if (insertionIdx === this.stackTop) { + this._updateCurrentElement(); + } + if (this.current && this.currentTagId !== undefined) { + this.handler.onItemPush(this.current, this.currentTagId, insertionIdx === this.stackTop); + } + } + popUntilTagNamePopped(tagName) { + let targetIdx = this.stackTop + 1; + do { + targetIdx = this.tagIDs.lastIndexOf(tagName, targetIdx - 1); + } while (targetIdx > 0 && this.treeAdapter.getNamespaceURI(this.items[targetIdx]) !== NS.HTML); + this.shortenToLength(Math.max(targetIdx, 0)); + } + shortenToLength(idx) { + while (this.stackTop >= idx) { + const popped = this.current; + if (this.tmplCount > 0 && this._isInTemplate()) { + this.tmplCount -= 1; + } + this.stackTop--; + this._updateCurrentElement(); + this.handler.onItemPop(popped, this.stackTop < idx); + } + } + popUntilElementPopped(element) { + const idx = this._indexOf(element); + this.shortenToLength(Math.max(idx, 0)); + } + popUntilPopped(tagNames, targetNS) { + const idx = this._indexOfTagNames(tagNames, targetNS); + this.shortenToLength(Math.max(idx, 0)); + } + popUntilNumberedHeaderPopped() { + this.popUntilPopped(NUMBERED_HEADERS, NS.HTML); + } + popUntilTableCellPopped() { + this.popUntilPopped(TABLE_CELLS, NS.HTML); + } + popAllUpToHtmlElement() { + //NOTE: here we assume that the root element is always first in the open element stack, so + //we perform this fast stack clean up. + this.tmplCount = 0; + this.shortenToLength(1); + } + _indexOfTagNames(tagNames, namespace) { + for (let i = this.stackTop; i >= 0; i--) { + if (tagNames.has(this.tagIDs[i]) && this.treeAdapter.getNamespaceURI(this.items[i]) === namespace) { + return i; + } + } + return -1; + } + clearBackTo(tagNames, targetNS) { + const idx = this._indexOfTagNames(tagNames, targetNS); + this.shortenToLength(idx + 1); + } + clearBackToTableContext() { + this.clearBackTo(TABLE_CONTEXT, NS.HTML); + } + clearBackToTableBodyContext() { + this.clearBackTo(TABLE_BODY_CONTEXT, NS.HTML); + } + clearBackToTableRowContext() { + this.clearBackTo(TABLE_ROW_CONTEXT, NS.HTML); + } + remove(element) { + const idx = this._indexOf(element); + if (idx >= 0) { + if (idx === this.stackTop) { + this.pop(); + } + else { + this.items.splice(idx, 1); + this.tagIDs.splice(idx, 1); + this.stackTop--; + this._updateCurrentElement(); + this.handler.onItemPop(element, false); + } + } + } + //Search + tryPeekProperlyNestedBodyElement() { + //Properly nested element (should be second element in stack). + return this.stackTop >= 1 && this.tagIDs[1] === $.BODY ? this.items[1] : null; + } + contains(element) { + return this._indexOf(element) > -1; + } + getCommonAncestor(element) { + const elementIdx = this._indexOf(element) - 1; + return elementIdx >= 0 ? this.items[elementIdx] : null; + } + isRootHtmlElementCurrent() { + return this.stackTop === 0 && this.tagIDs[0] === $.HTML; + } + //Element in scope + hasInDynamicScope(tagName, htmlScope) { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.tagIDs[i]; + switch (this.treeAdapter.getNamespaceURI(this.items[i])) { + case NS.HTML: { + if (tn === tagName) + return true; + if (htmlScope.has(tn)) + return false; + break; + } + case NS.SVG: { + if (SCOPING_ELEMENTS_SVG.has(tn)) + return false; + break; + } + case NS.MATHML: { + if (SCOPING_ELEMENTS_MATHML.has(tn)) + return false; + break; + } + } + } + return true; + } + hasInScope(tagName) { + return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML); + } + hasInListItemScope(tagName) { + return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML_LIST); + } + hasInButtonScope(tagName) { + return this.hasInDynamicScope(tagName, SCOPING_ELEMENTS_HTML_BUTTON); + } + hasNumberedHeaderInScope() { + for (let i = this.stackTop; i >= 0; i--) { + const tn = this.tagIDs[i]; + switch (this.treeAdapter.getNamespaceURI(this.items[i])) { + case NS.HTML: { + if (NUMBERED_HEADERS.has(tn)) + return true; + if (SCOPING_ELEMENTS_HTML.has(tn)) + return false; + break; + } + case NS.SVG: { + if (SCOPING_ELEMENTS_SVG.has(tn)) + return false; + break; + } + case NS.MATHML: { + if (SCOPING_ELEMENTS_MATHML.has(tn)) + return false; + break; + } + } + } + return true; + } + hasInTableScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) { + continue; + } + switch (this.tagIDs[i]) { + case tagName: { + return true; + } + case $.TABLE: + case $.HTML: { + return false; + } + } + } + return true; + } + hasTableBodyContextInTableScope() { + for (let i = this.stackTop; i >= 0; i--) { + if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) { + continue; + } + switch (this.tagIDs[i]) { + case $.TBODY: + case $.THEAD: + case $.TFOOT: { + return true; + } + case $.TABLE: + case $.HTML: { + return false; + } + } + } + return true; + } + hasInSelectScope(tagName) { + for (let i = this.stackTop; i >= 0; i--) { + if (this.treeAdapter.getNamespaceURI(this.items[i]) !== NS.HTML) { + continue; + } + switch (this.tagIDs[i]) { + case tagName: { + return true; + } + case $.OPTION: + case $.OPTGROUP: { + break; + } + default: { + return false; + } + } + } + return true; + } + //Implied end tags + generateImpliedEndTags() { + while (this.currentTagId !== undefined && IMPLICIT_END_TAG_REQUIRED.has(this.currentTagId)) { + this.pop(); + } + } + generateImpliedEndTagsThoroughly() { + while (this.currentTagId !== undefined && IMPLICIT_END_TAG_REQUIRED_THOROUGHLY.has(this.currentTagId)) { + this.pop(); + } + } + generateImpliedEndTagsWithExclusion(exclusionId) { + while (this.currentTagId !== undefined && + this.currentTagId !== exclusionId && + IMPLICIT_END_TAG_REQUIRED_THOROUGHLY.has(this.currentTagId)) { + this.pop(); + } + } +} -- cgit v1.2.3