 aacb45156b
			
		
	
	aacb45156b
	
	
	
		
			
			- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			3246 lines
		
	
	
		
			105 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			3246 lines
		
	
	
		
			105 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { Tokenizer, TokenizerMode } from '../tokenizer/index.js';
 | |
| import { OpenElementStack } from './open-element-stack.js';
 | |
| import { FormattingElementList, EntryType } from './formatting-element-list.js';
 | |
| import { defaultTreeAdapter } from '../tree-adapters/default.js';
 | |
| import * as doctype from '../common/doctype.js';
 | |
| import * as foreignContent from '../common/foreign-content.js';
 | |
| import { ERR } from '../common/error-codes.js';
 | |
| import * as unicode from '../common/unicode.js';
 | |
| import { TAG_ID as $, TAG_NAMES as TN, NS, ATTRS, SPECIAL_ELEMENTS, DOCUMENT_MODE, NUMBERED_HEADERS, getTagID, } from '../common/html.js';
 | |
| import { TokenType, getTokenAttr, } from '../common/token.js';
 | |
| //Misc constants
 | |
| const HIDDEN_INPUT_TYPE = 'hidden';
 | |
| //Adoption agency loops iteration count
 | |
| const AA_OUTER_LOOP_ITER = 8;
 | |
| const AA_INNER_LOOP_ITER = 3;
 | |
| //Insertion modes
 | |
| var InsertionMode;
 | |
| (function (InsertionMode) {
 | |
|     InsertionMode[InsertionMode["INITIAL"] = 0] = "INITIAL";
 | |
|     InsertionMode[InsertionMode["BEFORE_HTML"] = 1] = "BEFORE_HTML";
 | |
|     InsertionMode[InsertionMode["BEFORE_HEAD"] = 2] = "BEFORE_HEAD";
 | |
|     InsertionMode[InsertionMode["IN_HEAD"] = 3] = "IN_HEAD";
 | |
|     InsertionMode[InsertionMode["IN_HEAD_NO_SCRIPT"] = 4] = "IN_HEAD_NO_SCRIPT";
 | |
|     InsertionMode[InsertionMode["AFTER_HEAD"] = 5] = "AFTER_HEAD";
 | |
|     InsertionMode[InsertionMode["IN_BODY"] = 6] = "IN_BODY";
 | |
|     InsertionMode[InsertionMode["TEXT"] = 7] = "TEXT";
 | |
|     InsertionMode[InsertionMode["IN_TABLE"] = 8] = "IN_TABLE";
 | |
|     InsertionMode[InsertionMode["IN_TABLE_TEXT"] = 9] = "IN_TABLE_TEXT";
 | |
|     InsertionMode[InsertionMode["IN_CAPTION"] = 10] = "IN_CAPTION";
 | |
|     InsertionMode[InsertionMode["IN_COLUMN_GROUP"] = 11] = "IN_COLUMN_GROUP";
 | |
|     InsertionMode[InsertionMode["IN_TABLE_BODY"] = 12] = "IN_TABLE_BODY";
 | |
|     InsertionMode[InsertionMode["IN_ROW"] = 13] = "IN_ROW";
 | |
|     InsertionMode[InsertionMode["IN_CELL"] = 14] = "IN_CELL";
 | |
|     InsertionMode[InsertionMode["IN_SELECT"] = 15] = "IN_SELECT";
 | |
|     InsertionMode[InsertionMode["IN_SELECT_IN_TABLE"] = 16] = "IN_SELECT_IN_TABLE";
 | |
|     InsertionMode[InsertionMode["IN_TEMPLATE"] = 17] = "IN_TEMPLATE";
 | |
|     InsertionMode[InsertionMode["AFTER_BODY"] = 18] = "AFTER_BODY";
 | |
|     InsertionMode[InsertionMode["IN_FRAMESET"] = 19] = "IN_FRAMESET";
 | |
|     InsertionMode[InsertionMode["AFTER_FRAMESET"] = 20] = "AFTER_FRAMESET";
 | |
|     InsertionMode[InsertionMode["AFTER_AFTER_BODY"] = 21] = "AFTER_AFTER_BODY";
 | |
|     InsertionMode[InsertionMode["AFTER_AFTER_FRAMESET"] = 22] = "AFTER_AFTER_FRAMESET";
 | |
| })(InsertionMode || (InsertionMode = {}));
 | |
| const BASE_LOC = {
 | |
|     startLine: -1,
 | |
|     startCol: -1,
 | |
|     startOffset: -1,
 | |
|     endLine: -1,
 | |
|     endCol: -1,
 | |
|     endOffset: -1,
 | |
| };
 | |
| const TABLE_STRUCTURE_TAGS = new Set([$.TABLE, $.TBODY, $.TFOOT, $.THEAD, $.TR]);
 | |
| const defaultParserOptions = {
 | |
|     scriptingEnabled: true,
 | |
|     sourceCodeLocationInfo: false,
 | |
|     treeAdapter: defaultTreeAdapter,
 | |
|     onParseError: null,
 | |
| };
 | |
| //Parser
 | |
| export class Parser {
 | |
|     constructor(options, document, 
 | |
|     /** @internal */
 | |
|     fragmentContext = null, 
 | |
|     /** @internal */
 | |
|     scriptHandler = null) {
 | |
|         this.fragmentContext = fragmentContext;
 | |
|         this.scriptHandler = scriptHandler;
 | |
|         this.currentToken = null;
 | |
|         this.stopped = false;
 | |
|         /** @internal */
 | |
|         this.insertionMode = InsertionMode.INITIAL;
 | |
|         /** @internal */
 | |
|         this.originalInsertionMode = InsertionMode.INITIAL;
 | |
|         /** @internal */
 | |
|         this.headElement = null;
 | |
|         /** @internal */
 | |
|         this.formElement = null;
 | |
|         /** Indicates that the current node is not an element in the HTML namespace */
 | |
|         this.currentNotInHTML = false;
 | |
|         /**
 | |
|          * The template insertion mode stack is maintained from the left.
 | |
|          * Ie. the topmost element will always have index 0.
 | |
|          *
 | |
|          * @internal
 | |
|          */
 | |
|         this.tmplInsertionModeStack = [];
 | |
|         /** @internal */
 | |
|         this.pendingCharacterTokens = [];
 | |
|         /** @internal */
 | |
|         this.hasNonWhitespacePendingCharacterToken = false;
 | |
|         /** @internal */
 | |
|         this.framesetOk = true;
 | |
|         /** @internal */
 | |
|         this.skipNextNewLine = false;
 | |
|         /** @internal */
 | |
|         this.fosterParentingEnabled = false;
 | |
|         this.options = {
 | |
|             ...defaultParserOptions,
 | |
|             ...options,
 | |
|         };
 | |
|         this.treeAdapter = this.options.treeAdapter;
 | |
|         this.onParseError = this.options.onParseError;
 | |
|         // Always enable location info if we report parse errors.
 | |
|         if (this.onParseError) {
 | |
|             this.options.sourceCodeLocationInfo = true;
 | |
|         }
 | |
|         this.document = document !== null && document !== void 0 ? document : this.treeAdapter.createDocument();
 | |
|         this.tokenizer = new Tokenizer(this.options, this);
 | |
|         this.activeFormattingElements = new FormattingElementList(this.treeAdapter);
 | |
|         this.fragmentContextID = fragmentContext ? getTagID(this.treeAdapter.getTagName(fragmentContext)) : $.UNKNOWN;
 | |
|         this._setContextModes(fragmentContext !== null && fragmentContext !== void 0 ? fragmentContext : this.document, this.fragmentContextID);
 | |
|         this.openElements = new OpenElementStack(this.document, this.treeAdapter, this);
 | |
|     }
 | |
|     // API
 | |
|     static parse(html, options) {
 | |
|         const parser = new this(options);
 | |
|         parser.tokenizer.write(html, true);
 | |
|         return parser.document;
 | |
|     }
 | |
|     static getFragmentParser(fragmentContext, options) {
 | |
|         const opts = {
 | |
|             ...defaultParserOptions,
 | |
|             ...options,
 | |
|         };
 | |
|         //NOTE: use a <template> element as the fragment context if no context element was provided,
 | |
|         //so we will parse in a "forgiving" manner
 | |
|         fragmentContext !== null && fragmentContext !== void 0 ? fragmentContext : (fragmentContext = opts.treeAdapter.createElement(TN.TEMPLATE, NS.HTML, []));
 | |
|         //NOTE: create a fake element which will be used as the `document` for fragment parsing.
 | |
|         //This is important for jsdom, where a new `document` cannot be created. This led to
 | |
|         //fragment parsing messing with the main `document`.
 | |
|         const documentMock = opts.treeAdapter.createElement('documentmock', NS.HTML, []);
 | |
|         const parser = new this(opts, documentMock, fragmentContext);
 | |
|         if (parser.fragmentContextID === $.TEMPLATE) {
 | |
|             parser.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE);
 | |
|         }
 | |
|         parser._initTokenizerForFragmentParsing();
 | |
|         parser._insertFakeRootElement();
 | |
|         parser._resetInsertionMode();
 | |
|         parser._findFormInFragmentContext();
 | |
|         return parser;
 | |
|     }
 | |
|     getFragment() {
 | |
|         const rootElement = this.treeAdapter.getFirstChild(this.document);
 | |
|         const fragment = this.treeAdapter.createDocumentFragment();
 | |
|         this._adoptNodes(rootElement, fragment);
 | |
|         return fragment;
 | |
|     }
 | |
|     //Errors
 | |
|     /** @internal */
 | |
|     _err(token, code, beforeToken) {
 | |
|         var _a;
 | |
|         if (!this.onParseError)
 | |
|             return;
 | |
|         const loc = (_a = token.location) !== null && _a !== void 0 ? _a : BASE_LOC;
 | |
|         const err = {
 | |
|             code,
 | |
|             startLine: loc.startLine,
 | |
|             startCol: loc.startCol,
 | |
|             startOffset: loc.startOffset,
 | |
|             endLine: beforeToken ? loc.startLine : loc.endLine,
 | |
|             endCol: beforeToken ? loc.startCol : loc.endCol,
 | |
|             endOffset: beforeToken ? loc.startOffset : loc.endOffset,
 | |
|         };
 | |
|         this.onParseError(err);
 | |
|     }
 | |
|     //Stack events
 | |
|     /** @internal */
 | |
|     onItemPush(node, tid, isTop) {
 | |
|         var _a, _b;
 | |
|         (_b = (_a = this.treeAdapter).onItemPush) === null || _b === void 0 ? void 0 : _b.call(_a, node);
 | |
|         if (isTop && this.openElements.stackTop > 0)
 | |
|             this._setContextModes(node, tid);
 | |
|     }
 | |
|     /** @internal */
 | |
|     onItemPop(node, isTop) {
 | |
|         var _a, _b;
 | |
|         if (this.options.sourceCodeLocationInfo) {
 | |
|             this._setEndLocation(node, this.currentToken);
 | |
|         }
 | |
|         (_b = (_a = this.treeAdapter).onItemPop) === null || _b === void 0 ? void 0 : _b.call(_a, node, this.openElements.current);
 | |
|         if (isTop) {
 | |
|             let current;
 | |
|             let currentTagId;
 | |
|             if (this.openElements.stackTop === 0 && this.fragmentContext) {
 | |
|                 current = this.fragmentContext;
 | |
|                 currentTagId = this.fragmentContextID;
 | |
|             }
 | |
|             else {
 | |
|                 ({ current, currentTagId } = this.openElements);
 | |
|             }
 | |
|             this._setContextModes(current, currentTagId);
 | |
|         }
 | |
|     }
 | |
|     _setContextModes(current, tid) {
 | |
|         const isHTML = current === this.document || (current && this.treeAdapter.getNamespaceURI(current) === NS.HTML);
 | |
|         this.currentNotInHTML = !isHTML;
 | |
|         this.tokenizer.inForeignNode =
 | |
|             !isHTML && current !== undefined && tid !== undefined && !this._isIntegrationPoint(tid, current);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _switchToTextParsing(currentToken, nextTokenizerState) {
 | |
|         this._insertElement(currentToken, NS.HTML);
 | |
|         this.tokenizer.state = nextTokenizerState;
 | |
|         this.originalInsertionMode = this.insertionMode;
 | |
|         this.insertionMode = InsertionMode.TEXT;
 | |
|     }
 | |
|     switchToPlaintextParsing() {
 | |
|         this.insertionMode = InsertionMode.TEXT;
 | |
|         this.originalInsertionMode = InsertionMode.IN_BODY;
 | |
|         this.tokenizer.state = TokenizerMode.PLAINTEXT;
 | |
|     }
 | |
|     //Fragment parsing
 | |
|     /** @protected */
 | |
|     _getAdjustedCurrentElement() {
 | |
|         return this.openElements.stackTop === 0 && this.fragmentContext
 | |
|             ? this.fragmentContext
 | |
|             : this.openElements.current;
 | |
|     }
 | |
|     /** @protected */
 | |
|     _findFormInFragmentContext() {
 | |
|         let node = this.fragmentContext;
 | |
|         while (node) {
 | |
|             if (this.treeAdapter.getTagName(node) === TN.FORM) {
 | |
|                 this.formElement = node;
 | |
|                 break;
 | |
|             }
 | |
|             node = this.treeAdapter.getParentNode(node);
 | |
|         }
 | |
|     }
 | |
|     _initTokenizerForFragmentParsing() {
 | |
|         if (!this.fragmentContext || this.treeAdapter.getNamespaceURI(this.fragmentContext) !== NS.HTML) {
 | |
|             return;
 | |
|         }
 | |
|         switch (this.fragmentContextID) {
 | |
|             case $.TITLE:
 | |
|             case $.TEXTAREA: {
 | |
|                 this.tokenizer.state = TokenizerMode.RCDATA;
 | |
|                 break;
 | |
|             }
 | |
|             case $.STYLE:
 | |
|             case $.XMP:
 | |
|             case $.IFRAME:
 | |
|             case $.NOEMBED:
 | |
|             case $.NOFRAMES:
 | |
|             case $.NOSCRIPT: {
 | |
|                 this.tokenizer.state = TokenizerMode.RAWTEXT;
 | |
|                 break;
 | |
|             }
 | |
|             case $.SCRIPT: {
 | |
|                 this.tokenizer.state = TokenizerMode.SCRIPT_DATA;
 | |
|                 break;
 | |
|             }
 | |
|             case $.PLAINTEXT: {
 | |
|                 this.tokenizer.state = TokenizerMode.PLAINTEXT;
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     //Tree mutation
 | |
|     /** @protected */
 | |
|     _setDocumentType(token) {
 | |
|         const name = token.name || '';
 | |
|         const publicId = token.publicId || '';
 | |
|         const systemId = token.systemId || '';
 | |
|         this.treeAdapter.setDocumentType(this.document, name, publicId, systemId);
 | |
|         if (token.location) {
 | |
|             const documentChildren = this.treeAdapter.getChildNodes(this.document);
 | |
|             const docTypeNode = documentChildren.find((node) => this.treeAdapter.isDocumentTypeNode(node));
 | |
|             if (docTypeNode) {
 | |
|                 this.treeAdapter.setNodeSourceCodeLocation(docTypeNode, token.location);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     /** @protected */
 | |
|     _attachElementToTree(element, location) {
 | |
|         if (this.options.sourceCodeLocationInfo) {
 | |
|             const loc = location && {
 | |
|                 ...location,
 | |
|                 startTag: location,
 | |
|             };
 | |
|             this.treeAdapter.setNodeSourceCodeLocation(element, loc);
 | |
|         }
 | |
|         if (this._shouldFosterParentOnInsertion()) {
 | |
|             this._fosterParentElement(element);
 | |
|         }
 | |
|         else {
 | |
|             const parent = this.openElements.currentTmplContentOrNode;
 | |
|             this.treeAdapter.appendChild(parent !== null && parent !== void 0 ? parent : this.document, element);
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|      * For self-closing tags. Add an element to the tree, but skip adding it
 | |
|      * to the stack.
 | |
|      */
 | |
|     /** @protected */
 | |
|     _appendElement(token, namespaceURI) {
 | |
|         const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
 | |
|         this._attachElementToTree(element, token.location);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _insertElement(token, namespaceURI) {
 | |
|         const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
 | |
|         this._attachElementToTree(element, token.location);
 | |
|         this.openElements.push(element, token.tagID);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _insertFakeElement(tagName, tagID) {
 | |
|         const element = this.treeAdapter.createElement(tagName, NS.HTML, []);
 | |
|         this._attachElementToTree(element, null);
 | |
|         this.openElements.push(element, tagID);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _insertTemplate(token) {
 | |
|         const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs);
 | |
|         const content = this.treeAdapter.createDocumentFragment();
 | |
|         this.treeAdapter.setTemplateContent(tmpl, content);
 | |
|         this._attachElementToTree(tmpl, token.location);
 | |
|         this.openElements.push(tmpl, token.tagID);
 | |
|         if (this.options.sourceCodeLocationInfo)
 | |
|             this.treeAdapter.setNodeSourceCodeLocation(content, null);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _insertFakeRootElement() {
 | |
|         const element = this.treeAdapter.createElement(TN.HTML, NS.HTML, []);
 | |
|         if (this.options.sourceCodeLocationInfo)
 | |
|             this.treeAdapter.setNodeSourceCodeLocation(element, null);
 | |
|         this.treeAdapter.appendChild(this.openElements.current, element);
 | |
|         this.openElements.push(element, $.HTML);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _appendCommentNode(token, parent) {
 | |
|         const commentNode = this.treeAdapter.createCommentNode(token.data);
 | |
|         this.treeAdapter.appendChild(parent, commentNode);
 | |
|         if (this.options.sourceCodeLocationInfo) {
 | |
|             this.treeAdapter.setNodeSourceCodeLocation(commentNode, token.location);
 | |
|         }
 | |
|     }
 | |
|     /** @protected */
 | |
|     _insertCharacters(token) {
 | |
|         let parent;
 | |
|         let beforeElement;
 | |
|         if (this._shouldFosterParentOnInsertion()) {
 | |
|             ({ parent, beforeElement } = this._findFosterParentingLocation());
 | |
|             if (beforeElement) {
 | |
|                 this.treeAdapter.insertTextBefore(parent, token.chars, beforeElement);
 | |
|             }
 | |
|             else {
 | |
|                 this.treeAdapter.insertText(parent, token.chars);
 | |
|             }
 | |
|         }
 | |
|         else {
 | |
|             parent = this.openElements.currentTmplContentOrNode;
 | |
|             this.treeAdapter.insertText(parent, token.chars);
 | |
|         }
 | |
|         if (!token.location)
 | |
|             return;
 | |
|         const siblings = this.treeAdapter.getChildNodes(parent);
 | |
|         const textNodeIdx = beforeElement ? siblings.lastIndexOf(beforeElement) : siblings.length;
 | |
|         const textNode = siblings[textNodeIdx - 1];
 | |
|         //NOTE: if we have a location assigned by another token, then just update the end position
 | |
|         const tnLoc = this.treeAdapter.getNodeSourceCodeLocation(textNode);
 | |
|         if (tnLoc) {
 | |
|             const { endLine, endCol, endOffset } = token.location;
 | |
|             this.treeAdapter.updateNodeSourceCodeLocation(textNode, { endLine, endCol, endOffset });
 | |
|         }
 | |
|         else if (this.options.sourceCodeLocationInfo) {
 | |
|             this.treeAdapter.setNodeSourceCodeLocation(textNode, token.location);
 | |
|         }
 | |
|     }
 | |
|     /** @protected */
 | |
|     _adoptNodes(donor, recipient) {
 | |
|         for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) {
 | |
|             this.treeAdapter.detachNode(child);
 | |
|             this.treeAdapter.appendChild(recipient, child);
 | |
|         }
 | |
|     }
 | |
|     /** @protected */
 | |
|     _setEndLocation(element, closingToken) {
 | |
|         if (this.treeAdapter.getNodeSourceCodeLocation(element) && closingToken.location) {
 | |
|             const ctLoc = closingToken.location;
 | |
|             const tn = this.treeAdapter.getTagName(element);
 | |
|             const endLoc = 
 | |
|             // NOTE: For cases like <p> <p> </p> - First 'p' closes without a closing
 | |
|             // tag and for cases like <td> <p> </td> - 'p' closes without a closing tag.
 | |
|             closingToken.type === TokenType.END_TAG && tn === closingToken.tagName
 | |
|                 ? {
 | |
|                     endTag: { ...ctLoc },
 | |
|                     endLine: ctLoc.endLine,
 | |
|                     endCol: ctLoc.endCol,
 | |
|                     endOffset: ctLoc.endOffset,
 | |
|                 }
 | |
|                 : {
 | |
|                     endLine: ctLoc.startLine,
 | |
|                     endCol: ctLoc.startCol,
 | |
|                     endOffset: ctLoc.startOffset,
 | |
|                 };
 | |
|             this.treeAdapter.updateNodeSourceCodeLocation(element, endLoc);
 | |
|         }
 | |
|     }
 | |
|     //Token processing
 | |
|     shouldProcessStartTagTokenInForeignContent(token) {
 | |
|         // Check that neither current === document, or ns === NS.HTML
 | |
|         if (!this.currentNotInHTML)
 | |
|             return false;
 | |
|         let current;
 | |
|         let currentTagId;
 | |
|         if (this.openElements.stackTop === 0 && this.fragmentContext) {
 | |
|             current = this.fragmentContext;
 | |
|             currentTagId = this.fragmentContextID;
 | |
|         }
 | |
|         else {
 | |
|             ({ current, currentTagId } = this.openElements);
 | |
|         }
 | |
|         if (token.tagID === $.SVG &&
 | |
|             this.treeAdapter.getTagName(current) === TN.ANNOTATION_XML &&
 | |
|             this.treeAdapter.getNamespaceURI(current) === NS.MATHML) {
 | |
|             return false;
 | |
|         }
 | |
|         return (
 | |
|         // Check that `current` is not an integration point for HTML or MathML elements.
 | |
|         this.tokenizer.inForeignNode ||
 | |
|             // If it _is_ an integration point, then we might have to check that it is not an HTML
 | |
|             // integration point.
 | |
|             ((token.tagID === $.MGLYPH || token.tagID === $.MALIGNMARK) &&
 | |
|                 currentTagId !== undefined &&
 | |
|                 !this._isIntegrationPoint(currentTagId, current, NS.HTML)));
 | |
|     }
 | |
|     /** @protected */
 | |
|     _processToken(token) {
 | |
|         switch (token.type) {
 | |
|             case TokenType.CHARACTER: {
 | |
|                 this.onCharacter(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.NULL_CHARACTER: {
 | |
|                 this.onNullCharacter(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.COMMENT: {
 | |
|                 this.onComment(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.DOCTYPE: {
 | |
|                 this.onDoctype(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.START_TAG: {
 | |
|                 this._processStartTag(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.END_TAG: {
 | |
|                 this.onEndTag(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.EOF: {
 | |
|                 this.onEof(token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.WHITESPACE_CHARACTER: {
 | |
|                 this.onWhitespaceCharacter(token);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     //Integration points
 | |
|     /** @protected */
 | |
|     _isIntegrationPoint(tid, element, foreignNS) {
 | |
|         const ns = this.treeAdapter.getNamespaceURI(element);
 | |
|         const attrs = this.treeAdapter.getAttrList(element);
 | |
|         return foreignContent.isIntegrationPoint(tid, ns, attrs, foreignNS);
 | |
|     }
 | |
|     //Active formatting elements reconstruction
 | |
|     /** @protected */
 | |
|     _reconstructActiveFormattingElements() {
 | |
|         const listLength = this.activeFormattingElements.entries.length;
 | |
|         if (listLength) {
 | |
|             const endIndex = this.activeFormattingElements.entries.findIndex((entry) => entry.type === EntryType.Marker || this.openElements.contains(entry.element));
 | |
|             const unopenIdx = endIndex === -1 ? listLength - 1 : endIndex - 1;
 | |
|             for (let i = unopenIdx; i >= 0; i--) {
 | |
|                 const entry = this.activeFormattingElements.entries[i];
 | |
|                 this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element));
 | |
|                 entry.element = this.openElements.current;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     //Close elements
 | |
|     /** @protected */
 | |
|     _closeTableCell() {
 | |
|         this.openElements.generateImpliedEndTags();
 | |
|         this.openElements.popUntilTableCellPopped();
 | |
|         this.activeFormattingElements.clearToLastMarker();
 | |
|         this.insertionMode = InsertionMode.IN_ROW;
 | |
|     }
 | |
|     /** @protected */
 | |
|     _closePElement() {
 | |
|         this.openElements.generateImpliedEndTagsWithExclusion($.P);
 | |
|         this.openElements.popUntilTagNamePopped($.P);
 | |
|     }
 | |
|     //Insertion modes
 | |
|     /** @protected */
 | |
|     _resetInsertionMode() {
 | |
|         for (let i = this.openElements.stackTop; i >= 0; i--) {
 | |
|             //Insertion mode reset map
 | |
|             switch (i === 0 && this.fragmentContext ? this.fragmentContextID : this.openElements.tagIDs[i]) {
 | |
|                 case $.TR: {
 | |
|                     this.insertionMode = InsertionMode.IN_ROW;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.TBODY:
 | |
|                 case $.THEAD:
 | |
|                 case $.TFOOT: {
 | |
|                     this.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.CAPTION: {
 | |
|                     this.insertionMode = InsertionMode.IN_CAPTION;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.COLGROUP: {
 | |
|                     this.insertionMode = InsertionMode.IN_COLUMN_GROUP;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.TABLE: {
 | |
|                     this.insertionMode = InsertionMode.IN_TABLE;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.BODY: {
 | |
|                     this.insertionMode = InsertionMode.IN_BODY;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.FRAMESET: {
 | |
|                     this.insertionMode = InsertionMode.IN_FRAMESET;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.SELECT: {
 | |
|                     this._resetInsertionModeForSelect(i);
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.TEMPLATE: {
 | |
|                     this.insertionMode = this.tmplInsertionModeStack[0];
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.HTML: {
 | |
|                     this.insertionMode = this.headElement ? InsertionMode.AFTER_HEAD : InsertionMode.BEFORE_HEAD;
 | |
|                     return;
 | |
|                 }
 | |
|                 case $.TD:
 | |
|                 case $.TH: {
 | |
|                     if (i > 0) {
 | |
|                         this.insertionMode = InsertionMode.IN_CELL;
 | |
|                         return;
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
|                 case $.HEAD: {
 | |
|                     if (i > 0) {
 | |
|                         this.insertionMode = InsertionMode.IN_HEAD;
 | |
|                         return;
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         this.insertionMode = InsertionMode.IN_BODY;
 | |
|     }
 | |
|     /** @protected */
 | |
|     _resetInsertionModeForSelect(selectIdx) {
 | |
|         if (selectIdx > 0) {
 | |
|             for (let i = selectIdx - 1; i > 0; i--) {
 | |
|                 const tn = this.openElements.tagIDs[i];
 | |
|                 if (tn === $.TEMPLATE) {
 | |
|                     break;
 | |
|                 }
 | |
|                 else if (tn === $.TABLE) {
 | |
|                     this.insertionMode = InsertionMode.IN_SELECT_IN_TABLE;
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         this.insertionMode = InsertionMode.IN_SELECT;
 | |
|     }
 | |
|     //Foster parenting
 | |
|     /** @protected */
 | |
|     _isElementCausesFosterParenting(tn) {
 | |
|         return TABLE_STRUCTURE_TAGS.has(tn);
 | |
|     }
 | |
|     /** @protected */
 | |
|     _shouldFosterParentOnInsertion() {
 | |
|         return (this.fosterParentingEnabled &&
 | |
|             this.openElements.currentTagId !== undefined &&
 | |
|             this._isElementCausesFosterParenting(this.openElements.currentTagId));
 | |
|     }
 | |
|     /** @protected */
 | |
|     _findFosterParentingLocation() {
 | |
|         for (let i = this.openElements.stackTop; i >= 0; i--) {
 | |
|             const openElement = this.openElements.items[i];
 | |
|             switch (this.openElements.tagIDs[i]) {
 | |
|                 case $.TEMPLATE: {
 | |
|                     if (this.treeAdapter.getNamespaceURI(openElement) === NS.HTML) {
 | |
|                         return { parent: this.treeAdapter.getTemplateContent(openElement), beforeElement: null };
 | |
|                     }
 | |
|                     break;
 | |
|                 }
 | |
|                 case $.TABLE: {
 | |
|                     const parent = this.treeAdapter.getParentNode(openElement);
 | |
|                     if (parent) {
 | |
|                         return { parent, beforeElement: openElement };
 | |
|                     }
 | |
|                     return { parent: this.openElements.items[i - 1], beforeElement: null };
 | |
|                 }
 | |
|                 default:
 | |
|                 // Do nothing
 | |
|             }
 | |
|         }
 | |
|         return { parent: this.openElements.items[0], beforeElement: null };
 | |
|     }
 | |
|     /** @protected */
 | |
|     _fosterParentElement(element) {
 | |
|         const location = this._findFosterParentingLocation();
 | |
|         if (location.beforeElement) {
 | |
|             this.treeAdapter.insertBefore(location.parent, element, location.beforeElement);
 | |
|         }
 | |
|         else {
 | |
|             this.treeAdapter.appendChild(location.parent, element);
 | |
|         }
 | |
|     }
 | |
|     //Special elements
 | |
|     /** @protected */
 | |
|     _isSpecialElement(element, id) {
 | |
|         const ns = this.treeAdapter.getNamespaceURI(element);
 | |
|         return SPECIAL_ELEMENTS[ns].has(id);
 | |
|     }
 | |
|     /** @internal */
 | |
|     onCharacter(token) {
 | |
|         this.skipNextNewLine = false;
 | |
|         if (this.tokenizer.inForeignNode) {
 | |
|             characterInForeignContent(this, token);
 | |
|             return;
 | |
|         }
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL: {
 | |
|                 tokenInInitialMode(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HTML: {
 | |
|                 tokenBeforeHtml(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HEAD: {
 | |
|                 tokenBeforeHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD: {
 | |
|                 tokenInHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT: {
 | |
|                 tokenInHeadNoScript(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_HEAD: {
 | |
|                 tokenAfterHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_BODY:
 | |
|             case InsertionMode.IN_CAPTION:
 | |
|             case InsertionMode.IN_CELL:
 | |
|             case InsertionMode.IN_TEMPLATE: {
 | |
|                 characterInBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.TEXT:
 | |
|             case InsertionMode.IN_SELECT:
 | |
|             case InsertionMode.IN_SELECT_IN_TABLE: {
 | |
|                 this._insertCharacters(token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE:
 | |
|             case InsertionMode.IN_TABLE_BODY:
 | |
|             case InsertionMode.IN_ROW: {
 | |
|                 characterInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 characterInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_COLUMN_GROUP: {
 | |
|                 tokenInColumnGroup(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_BODY: {
 | |
|                 tokenAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_AFTER_BODY: {
 | |
|                 tokenAfterAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onNullCharacter(token) {
 | |
|         this.skipNextNewLine = false;
 | |
|         if (this.tokenizer.inForeignNode) {
 | |
|             nullCharacterInForeignContent(this, token);
 | |
|             return;
 | |
|         }
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL: {
 | |
|                 tokenInInitialMode(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HTML: {
 | |
|                 tokenBeforeHtml(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HEAD: {
 | |
|                 tokenBeforeHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD: {
 | |
|                 tokenInHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT: {
 | |
|                 tokenInHeadNoScript(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_HEAD: {
 | |
|                 tokenAfterHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.TEXT: {
 | |
|                 this._insertCharacters(token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE:
 | |
|             case InsertionMode.IN_TABLE_BODY:
 | |
|             case InsertionMode.IN_ROW: {
 | |
|                 characterInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_COLUMN_GROUP: {
 | |
|                 tokenInColumnGroup(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_BODY: {
 | |
|                 tokenAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_AFTER_BODY: {
 | |
|                 tokenAfterAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onComment(token) {
 | |
|         this.skipNextNewLine = false;
 | |
|         if (this.currentNotInHTML) {
 | |
|             appendComment(this, token);
 | |
|             return;
 | |
|         }
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL:
 | |
|             case InsertionMode.BEFORE_HTML:
 | |
|             case InsertionMode.BEFORE_HEAD:
 | |
|             case InsertionMode.IN_HEAD:
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT:
 | |
|             case InsertionMode.AFTER_HEAD:
 | |
|             case InsertionMode.IN_BODY:
 | |
|             case InsertionMode.IN_TABLE:
 | |
|             case InsertionMode.IN_CAPTION:
 | |
|             case InsertionMode.IN_COLUMN_GROUP:
 | |
|             case InsertionMode.IN_TABLE_BODY:
 | |
|             case InsertionMode.IN_ROW:
 | |
|             case InsertionMode.IN_CELL:
 | |
|             case InsertionMode.IN_SELECT:
 | |
|             case InsertionMode.IN_SELECT_IN_TABLE:
 | |
|             case InsertionMode.IN_TEMPLATE:
 | |
|             case InsertionMode.IN_FRAMESET:
 | |
|             case InsertionMode.AFTER_FRAMESET: {
 | |
|                 appendComment(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 tokenInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_BODY: {
 | |
|                 appendCommentToRootHtmlElement(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_AFTER_BODY:
 | |
|             case InsertionMode.AFTER_AFTER_FRAMESET: {
 | |
|                 appendCommentToDocument(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onDoctype(token) {
 | |
|         this.skipNextNewLine = false;
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL: {
 | |
|                 doctypeInInitialMode(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HEAD:
 | |
|             case InsertionMode.IN_HEAD:
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT:
 | |
|             case InsertionMode.AFTER_HEAD: {
 | |
|                 this._err(token, ERR.misplacedDoctype);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 tokenInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onStartTag(token) {
 | |
|         this.skipNextNewLine = false;
 | |
|         this.currentToken = token;
 | |
|         this._processStartTag(token);
 | |
|         if (token.selfClosing && !token.ackSelfClosing) {
 | |
|             this._err(token, ERR.nonVoidHtmlElementStartTagWithTrailingSolidus);
 | |
|         }
 | |
|     }
 | |
|     /**
 | |
|      * Processes a given start tag.
 | |
|      *
 | |
|      * `onStartTag` checks if a self-closing tag was recognized. When a token
 | |
|      * is moved inbetween multiple insertion modes, this check for self-closing
 | |
|      * could lead to false positives. To avoid this, `_processStartTag` is used
 | |
|      * for nested calls.
 | |
|      *
 | |
|      * @param token The token to process.
 | |
|      * @protected
 | |
|      */
 | |
|     _processStartTag(token) {
 | |
|         if (this.shouldProcessStartTagTokenInForeignContent(token)) {
 | |
|             startTagInForeignContent(this, token);
 | |
|         }
 | |
|         else {
 | |
|             this._startTagOutsideForeignContent(token);
 | |
|         }
 | |
|     }
 | |
|     /** @protected */
 | |
|     _startTagOutsideForeignContent(token) {
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL: {
 | |
|                 tokenInInitialMode(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HTML: {
 | |
|                 startTagBeforeHtml(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HEAD: {
 | |
|                 startTagBeforeHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD: {
 | |
|                 startTagInHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT: {
 | |
|                 startTagInHeadNoScript(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_HEAD: {
 | |
|                 startTagAfterHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_BODY: {
 | |
|                 startTagInBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE: {
 | |
|                 startTagInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 tokenInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_CAPTION: {
 | |
|                 startTagInCaption(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_COLUMN_GROUP: {
 | |
|                 startTagInColumnGroup(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_BODY: {
 | |
|                 startTagInTableBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_ROW: {
 | |
|                 startTagInRow(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_CELL: {
 | |
|                 startTagInCell(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_SELECT: {
 | |
|                 startTagInSelect(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_SELECT_IN_TABLE: {
 | |
|                 startTagInSelectInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TEMPLATE: {
 | |
|                 startTagInTemplate(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_BODY: {
 | |
|                 startTagAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_FRAMESET: {
 | |
|                 startTagInFrameset(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_FRAMESET: {
 | |
|                 startTagAfterFrameset(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_AFTER_BODY: {
 | |
|                 startTagAfterAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_AFTER_FRAMESET: {
 | |
|                 startTagAfterAfterFrameset(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onEndTag(token) {
 | |
|         this.skipNextNewLine = false;
 | |
|         this.currentToken = token;
 | |
|         if (this.currentNotInHTML) {
 | |
|             endTagInForeignContent(this, token);
 | |
|         }
 | |
|         else {
 | |
|             this._endTagOutsideForeignContent(token);
 | |
|         }
 | |
|     }
 | |
|     /** @protected */
 | |
|     _endTagOutsideForeignContent(token) {
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL: {
 | |
|                 tokenInInitialMode(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HTML: {
 | |
|                 endTagBeforeHtml(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HEAD: {
 | |
|                 endTagBeforeHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD: {
 | |
|                 endTagInHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT: {
 | |
|                 endTagInHeadNoScript(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_HEAD: {
 | |
|                 endTagAfterHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_BODY: {
 | |
|                 endTagInBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.TEXT: {
 | |
|                 endTagInText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE: {
 | |
|                 endTagInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 tokenInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_CAPTION: {
 | |
|                 endTagInCaption(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_COLUMN_GROUP: {
 | |
|                 endTagInColumnGroup(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_BODY: {
 | |
|                 endTagInTableBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_ROW: {
 | |
|                 endTagInRow(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_CELL: {
 | |
|                 endTagInCell(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_SELECT: {
 | |
|                 endTagInSelect(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_SELECT_IN_TABLE: {
 | |
|                 endTagInSelectInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TEMPLATE: {
 | |
|                 endTagInTemplate(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_BODY: {
 | |
|                 endTagAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_FRAMESET: {
 | |
|                 endTagInFrameset(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_FRAMESET: {
 | |
|                 endTagAfterFrameset(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_AFTER_BODY: {
 | |
|                 tokenAfterAfterBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onEof(token) {
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.INITIAL: {
 | |
|                 tokenInInitialMode(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HTML: {
 | |
|                 tokenBeforeHtml(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.BEFORE_HEAD: {
 | |
|                 tokenBeforeHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD: {
 | |
|                 tokenInHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT: {
 | |
|                 tokenInHeadNoScript(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_HEAD: {
 | |
|                 tokenAfterHead(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_BODY:
 | |
|             case InsertionMode.IN_TABLE:
 | |
|             case InsertionMode.IN_CAPTION:
 | |
|             case InsertionMode.IN_COLUMN_GROUP:
 | |
|             case InsertionMode.IN_TABLE_BODY:
 | |
|             case InsertionMode.IN_ROW:
 | |
|             case InsertionMode.IN_CELL:
 | |
|             case InsertionMode.IN_SELECT:
 | |
|             case InsertionMode.IN_SELECT_IN_TABLE: {
 | |
|                 eofInBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.TEXT: {
 | |
|                 eofInText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 tokenInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TEMPLATE: {
 | |
|                 eofInTemplate(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.AFTER_BODY:
 | |
|             case InsertionMode.IN_FRAMESET:
 | |
|             case InsertionMode.AFTER_FRAMESET:
 | |
|             case InsertionMode.AFTER_AFTER_BODY:
 | |
|             case InsertionMode.AFTER_AFTER_FRAMESET: {
 | |
|                 stopParsing(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
|     /** @internal */
 | |
|     onWhitespaceCharacter(token) {
 | |
|         if (this.skipNextNewLine) {
 | |
|             this.skipNextNewLine = false;
 | |
|             if (token.chars.charCodeAt(0) === unicode.CODE_POINTS.LINE_FEED) {
 | |
|                 if (token.chars.length === 1) {
 | |
|                     return;
 | |
|                 }
 | |
|                 token.chars = token.chars.substr(1);
 | |
|             }
 | |
|         }
 | |
|         if (this.tokenizer.inForeignNode) {
 | |
|             this._insertCharacters(token);
 | |
|             return;
 | |
|         }
 | |
|         switch (this.insertionMode) {
 | |
|             case InsertionMode.IN_HEAD:
 | |
|             case InsertionMode.IN_HEAD_NO_SCRIPT:
 | |
|             case InsertionMode.AFTER_HEAD:
 | |
|             case InsertionMode.TEXT:
 | |
|             case InsertionMode.IN_COLUMN_GROUP:
 | |
|             case InsertionMode.IN_SELECT:
 | |
|             case InsertionMode.IN_SELECT_IN_TABLE:
 | |
|             case InsertionMode.IN_FRAMESET:
 | |
|             case InsertionMode.AFTER_FRAMESET: {
 | |
|                 this._insertCharacters(token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_BODY:
 | |
|             case InsertionMode.IN_CAPTION:
 | |
|             case InsertionMode.IN_CELL:
 | |
|             case InsertionMode.IN_TEMPLATE:
 | |
|             case InsertionMode.AFTER_BODY:
 | |
|             case InsertionMode.AFTER_AFTER_BODY:
 | |
|             case InsertionMode.AFTER_AFTER_FRAMESET: {
 | |
|                 whitespaceCharacterInBody(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE:
 | |
|             case InsertionMode.IN_TABLE_BODY:
 | |
|             case InsertionMode.IN_ROW: {
 | |
|                 characterInTable(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             case InsertionMode.IN_TABLE_TEXT: {
 | |
|                 whitespaceCharacterInTableText(this, token);
 | |
|                 break;
 | |
|             }
 | |
|             default:
 | |
|             // Do nothing
 | |
|         }
 | |
|     }
 | |
| }
 | |
| //Adoption agency algorithm
 | |
| //(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency)
 | |
| //------------------------------------------------------------------
 | |
| //Steps 5-8 of the algorithm
 | |
| function aaObtainFormattingElementEntry(p, token) {
 | |
|     let formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName);
 | |
|     if (formattingElementEntry) {
 | |
|         if (!p.openElements.contains(formattingElementEntry.element)) {
 | |
|             p.activeFormattingElements.removeEntry(formattingElementEntry);
 | |
|             formattingElementEntry = null;
 | |
|         }
 | |
|         else if (!p.openElements.hasInScope(token.tagID)) {
 | |
|             formattingElementEntry = null;
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         genericEndTagInBody(p, token);
 | |
|     }
 | |
|     return formattingElementEntry;
 | |
| }
 | |
| //Steps 9 and 10 of the algorithm
 | |
| function aaObtainFurthestBlock(p, formattingElementEntry) {
 | |
|     let furthestBlock = null;
 | |
|     let idx = p.openElements.stackTop;
 | |
|     for (; idx >= 0; idx--) {
 | |
|         const element = p.openElements.items[idx];
 | |
|         if (element === formattingElementEntry.element) {
 | |
|             break;
 | |
|         }
 | |
|         if (p._isSpecialElement(element, p.openElements.tagIDs[idx])) {
 | |
|             furthestBlock = element;
 | |
|         }
 | |
|     }
 | |
|     if (!furthestBlock) {
 | |
|         p.openElements.shortenToLength(Math.max(idx, 0));
 | |
|         p.activeFormattingElements.removeEntry(formattingElementEntry);
 | |
|     }
 | |
|     return furthestBlock;
 | |
| }
 | |
| //Step 13 of the algorithm
 | |
| function aaInnerLoop(p, furthestBlock, formattingElement) {
 | |
|     let lastElement = furthestBlock;
 | |
|     let nextElement = p.openElements.getCommonAncestor(furthestBlock);
 | |
|     for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) {
 | |
|         //NOTE: store the next element for the next loop iteration (it may be deleted from the stack by step 9.5)
 | |
|         nextElement = p.openElements.getCommonAncestor(element);
 | |
|         const elementEntry = p.activeFormattingElements.getElementEntry(element);
 | |
|         const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER;
 | |
|         const shouldRemoveFromOpenElements = !elementEntry || counterOverflow;
 | |
|         if (shouldRemoveFromOpenElements) {
 | |
|             if (counterOverflow) {
 | |
|                 p.activeFormattingElements.removeEntry(elementEntry);
 | |
|             }
 | |
|             p.openElements.remove(element);
 | |
|         }
 | |
|         else {
 | |
|             element = aaRecreateElementFromEntry(p, elementEntry);
 | |
|             if (lastElement === furthestBlock) {
 | |
|                 p.activeFormattingElements.bookmark = elementEntry;
 | |
|             }
 | |
|             p.treeAdapter.detachNode(lastElement);
 | |
|             p.treeAdapter.appendChild(element, lastElement);
 | |
|             lastElement = element;
 | |
|         }
 | |
|     }
 | |
|     return lastElement;
 | |
| }
 | |
| //Step 13.7 of the algorithm
 | |
| function aaRecreateElementFromEntry(p, elementEntry) {
 | |
|     const ns = p.treeAdapter.getNamespaceURI(elementEntry.element);
 | |
|     const newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs);
 | |
|     p.openElements.replace(elementEntry.element, newElement);
 | |
|     elementEntry.element = newElement;
 | |
|     return newElement;
 | |
| }
 | |
| //Step 14 of the algorithm
 | |
| function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) {
 | |
|     const tn = p.treeAdapter.getTagName(commonAncestor);
 | |
|     const tid = getTagID(tn);
 | |
|     if (p._isElementCausesFosterParenting(tid)) {
 | |
|         p._fosterParentElement(lastElement);
 | |
|     }
 | |
|     else {
 | |
|         const ns = p.treeAdapter.getNamespaceURI(commonAncestor);
 | |
|         if (tid === $.TEMPLATE && ns === NS.HTML) {
 | |
|             commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor);
 | |
|         }
 | |
|         p.treeAdapter.appendChild(commonAncestor, lastElement);
 | |
|     }
 | |
| }
 | |
| //Steps 15-19 of the algorithm
 | |
| function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {
 | |
|     const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element);
 | |
|     const { token } = formattingElementEntry;
 | |
|     const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs);
 | |
|     p._adoptNodes(furthestBlock, newElement);
 | |
|     p.treeAdapter.appendChild(furthestBlock, newElement);
 | |
|     p.activeFormattingElements.insertElementAfterBookmark(newElement, token);
 | |
|     p.activeFormattingElements.removeEntry(formattingElementEntry);
 | |
|     p.openElements.remove(formattingElementEntry.element);
 | |
|     p.openElements.insertAfter(furthestBlock, newElement, token.tagID);
 | |
| }
 | |
| //Algorithm entry point
 | |
| function callAdoptionAgency(p, token) {
 | |
|     for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) {
 | |
|         const formattingElementEntry = aaObtainFormattingElementEntry(p, token);
 | |
|         if (!formattingElementEntry) {
 | |
|             break;
 | |
|         }
 | |
|         const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry);
 | |
|         if (!furthestBlock) {
 | |
|             break;
 | |
|         }
 | |
|         p.activeFormattingElements.bookmark = formattingElementEntry;
 | |
|         const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element);
 | |
|         const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element);
 | |
|         p.treeAdapter.detachNode(lastElement);
 | |
|         if (commonAncestor)
 | |
|             aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement);
 | |
|         aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry);
 | |
|     }
 | |
| }
 | |
| //Generic token handlers
 | |
| //------------------------------------------------------------------
 | |
| function appendComment(p, token) {
 | |
|     p._appendCommentNode(token, p.openElements.currentTmplContentOrNode);
 | |
| }
 | |
| function appendCommentToRootHtmlElement(p, token) {
 | |
|     p._appendCommentNode(token, p.openElements.items[0]);
 | |
| }
 | |
| function appendCommentToDocument(p, token) {
 | |
|     p._appendCommentNode(token, p.document);
 | |
| }
 | |
| function stopParsing(p, token) {
 | |
|     p.stopped = true;
 | |
|     // NOTE: Set end locations for elements that remain on the open element stack.
 | |
|     if (token.location) {
 | |
|         // NOTE: If we are not in a fragment, `html` and `body` will stay on the stack.
 | |
|         // This is a problem, as we might overwrite their end position here.
 | |
|         const target = p.fragmentContext ? 0 : 2;
 | |
|         for (let i = p.openElements.stackTop; i >= target; i--) {
 | |
|             p._setEndLocation(p.openElements.items[i], token);
 | |
|         }
 | |
|         // Handle `html` and `body`
 | |
|         if (!p.fragmentContext && p.openElements.stackTop >= 0) {
 | |
|             const htmlElement = p.openElements.items[0];
 | |
|             const htmlLocation = p.treeAdapter.getNodeSourceCodeLocation(htmlElement);
 | |
|             if (htmlLocation && !htmlLocation.endTag) {
 | |
|                 p._setEndLocation(htmlElement, token);
 | |
|                 if (p.openElements.stackTop >= 1) {
 | |
|                     const bodyElement = p.openElements.items[1];
 | |
|                     const bodyLocation = p.treeAdapter.getNodeSourceCodeLocation(bodyElement);
 | |
|                     if (bodyLocation && !bodyLocation.endTag) {
 | |
|                         p._setEndLocation(bodyElement, token);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // The "initial" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function doctypeInInitialMode(p, token) {
 | |
|     p._setDocumentType(token);
 | |
|     const mode = token.forceQuirks ? DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token);
 | |
|     if (!doctype.isConforming(token)) {
 | |
|         p._err(token, ERR.nonConformingDoctype);
 | |
|     }
 | |
|     p.treeAdapter.setDocumentMode(p.document, mode);
 | |
|     p.insertionMode = InsertionMode.BEFORE_HTML;
 | |
| }
 | |
| function tokenInInitialMode(p, token) {
 | |
|     p._err(token, ERR.missingDoctype, true);
 | |
|     p.treeAdapter.setDocumentMode(p.document, DOCUMENT_MODE.QUIRKS);
 | |
|     p.insertionMode = InsertionMode.BEFORE_HTML;
 | |
|     p._processToken(token);
 | |
| }
 | |
| // The "before html" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagBeforeHtml(p, token) {
 | |
|     if (token.tagID === $.HTML) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.insertionMode = InsertionMode.BEFORE_HEAD;
 | |
|     }
 | |
|     else {
 | |
|         tokenBeforeHtml(p, token);
 | |
|     }
 | |
| }
 | |
| function endTagBeforeHtml(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) {
 | |
|         tokenBeforeHtml(p, token);
 | |
|     }
 | |
| }
 | |
| function tokenBeforeHtml(p, token) {
 | |
|     p._insertFakeRootElement();
 | |
|     p.insertionMode = InsertionMode.BEFORE_HEAD;
 | |
|     p._processToken(token);
 | |
| }
 | |
| // The "before head" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagBeforeHead(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.HEAD: {
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             p.headElement = p.openElements.current;
 | |
|             p.insertionMode = InsertionMode.IN_HEAD;
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenBeforeHead(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagBeforeHead(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) {
 | |
|         tokenBeforeHead(p, token);
 | |
|     }
 | |
|     else {
 | |
|         p._err(token, ERR.endTagWithoutMatchingOpenElement);
 | |
|     }
 | |
| }
 | |
| function tokenBeforeHead(p, token) {
 | |
|     p._insertFakeElement(TN.HEAD, $.HEAD);
 | |
|     p.headElement = p.openElements.current;
 | |
|     p.insertionMode = InsertionMode.IN_HEAD;
 | |
|     p._processToken(token);
 | |
| }
 | |
| // The "in head" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInHead(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BASE:
 | |
|         case $.BASEFONT:
 | |
|         case $.BGSOUND:
 | |
|         case $.LINK:
 | |
|         case $.META: {
 | |
|             p._appendElement(token, NS.HTML);
 | |
|             token.ackSelfClosing = true;
 | |
|             break;
 | |
|         }
 | |
|         case $.TITLE: {
 | |
|             p._switchToTextParsing(token, TokenizerMode.RCDATA);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOSCRIPT: {
 | |
|             if (p.options.scriptingEnabled) {
 | |
|                 p._switchToTextParsing(token, TokenizerMode.RAWTEXT);
 | |
|             }
 | |
|             else {
 | |
|                 p._insertElement(token, NS.HTML);
 | |
|                 p.insertionMode = InsertionMode.IN_HEAD_NO_SCRIPT;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.NOFRAMES:
 | |
|         case $.STYLE: {
 | |
|             p._switchToTextParsing(token, TokenizerMode.RAWTEXT);
 | |
|             break;
 | |
|         }
 | |
|         case $.SCRIPT: {
 | |
|             p._switchToTextParsing(token, TokenizerMode.SCRIPT_DATA);
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             p._insertTemplate(token);
 | |
|             p.activeFormattingElements.insertMarker();
 | |
|             p.framesetOk = false;
 | |
|             p.insertionMode = InsertionMode.IN_TEMPLATE;
 | |
|             p.tmplInsertionModeStack.unshift(InsertionMode.IN_TEMPLATE);
 | |
|             break;
 | |
|         }
 | |
|         case $.HEAD: {
 | |
|             p._err(token, ERR.misplacedStartTagForHeadElement);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenInHead(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInHead(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HEAD: {
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = InsertionMode.AFTER_HEAD;
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY:
 | |
|         case $.BR:
 | |
|         case $.HTML: {
 | |
|             tokenInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             templateEndTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             p._err(token, ERR.endTagWithoutMatchingOpenElement);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function templateEndTagInHead(p, token) {
 | |
|     if (p.openElements.tmplCount > 0) {
 | |
|         p.openElements.generateImpliedEndTagsThoroughly();
 | |
|         if (p.openElements.currentTagId !== $.TEMPLATE) {
 | |
|             p._err(token, ERR.closingOfElementWithOpenChildElements);
 | |
|         }
 | |
|         p.openElements.popUntilTagNamePopped($.TEMPLATE);
 | |
|         p.activeFormattingElements.clearToLastMarker();
 | |
|         p.tmplInsertionModeStack.shift();
 | |
|         p._resetInsertionMode();
 | |
|     }
 | |
|     else {
 | |
|         p._err(token, ERR.endTagWithoutMatchingOpenElement);
 | |
|     }
 | |
| }
 | |
| function tokenInHead(p, token) {
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = InsertionMode.AFTER_HEAD;
 | |
|     p._processToken(token);
 | |
| }
 | |
| // The "in head no script" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInHeadNoScript(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BASEFONT:
 | |
|         case $.BGSOUND:
 | |
|         case $.HEAD:
 | |
|         case $.LINK:
 | |
|         case $.META:
 | |
|         case $.NOFRAMES:
 | |
|         case $.STYLE: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOSCRIPT: {
 | |
|             p._err(token, ERR.nestedNoscriptInHead);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenInHeadNoScript(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInHeadNoScript(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.NOSCRIPT: {
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = InsertionMode.IN_HEAD;
 | |
|             break;
 | |
|         }
 | |
|         case $.BR: {
 | |
|             tokenInHeadNoScript(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             p._err(token, ERR.endTagWithoutMatchingOpenElement);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function tokenInHeadNoScript(p, token) {
 | |
|     const errCode = token.type === TokenType.EOF ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead;
 | |
|     p._err(token, errCode);
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = InsertionMode.IN_HEAD;
 | |
|     p._processToken(token);
 | |
| }
 | |
| // The "after head" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterHead(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY: {
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             p.framesetOk = false;
 | |
|             p.insertionMode = InsertionMode.IN_BODY;
 | |
|             break;
 | |
|         }
 | |
|         case $.FRAMESET: {
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             p.insertionMode = InsertionMode.IN_FRAMESET;
 | |
|             break;
 | |
|         }
 | |
|         case $.BASE:
 | |
|         case $.BASEFONT:
 | |
|         case $.BGSOUND:
 | |
|         case $.LINK:
 | |
|         case $.META:
 | |
|         case $.NOFRAMES:
 | |
|         case $.SCRIPT:
 | |
|         case $.STYLE:
 | |
|         case $.TEMPLATE:
 | |
|         case $.TITLE: {
 | |
|             p._err(token, ERR.abandonedHeadElementChild);
 | |
|             p.openElements.push(p.headElement, $.HEAD);
 | |
|             startTagInHead(p, token);
 | |
|             p.openElements.remove(p.headElement);
 | |
|             break;
 | |
|         }
 | |
|         case $.HEAD: {
 | |
|             p._err(token, ERR.misplacedStartTagForHeadElement);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenAfterHead(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagAfterHead(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.BODY:
 | |
|         case $.HTML:
 | |
|         case $.BR: {
 | |
|             tokenAfterHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             templateEndTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             p._err(token, ERR.endTagWithoutMatchingOpenElement);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function tokenAfterHead(p, token) {
 | |
|     p._insertFakeElement(TN.BODY, $.BODY);
 | |
|     p.insertionMode = InsertionMode.IN_BODY;
 | |
|     modeInBody(p, token);
 | |
| }
 | |
| // The "in body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function modeInBody(p, token) {
 | |
|     switch (token.type) {
 | |
|         case TokenType.CHARACTER: {
 | |
|             characterInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case TokenType.WHITESPACE_CHARACTER: {
 | |
|             whitespaceCharacterInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case TokenType.COMMENT: {
 | |
|             appendComment(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case TokenType.START_TAG: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case TokenType.END_TAG: {
 | |
|             endTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case TokenType.EOF: {
 | |
|             eofInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|         // Do nothing
 | |
|     }
 | |
| }
 | |
| function whitespaceCharacterInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertCharacters(token);
 | |
| }
 | |
| function characterInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertCharacters(token);
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| function htmlStartTagInBody(p, token) {
 | |
|     if (p.openElements.tmplCount === 0) {
 | |
|         p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs);
 | |
|     }
 | |
| }
 | |
| function bodyStartTagInBody(p, token) {
 | |
|     const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
 | |
|     if (bodyElement && p.openElements.tmplCount === 0) {
 | |
|         p.framesetOk = false;
 | |
|         p.treeAdapter.adoptAttributes(bodyElement, token.attrs);
 | |
|     }
 | |
| }
 | |
| function framesetStartTagInBody(p, token) {
 | |
|     const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
 | |
|     if (p.framesetOk && bodyElement) {
 | |
|         p.treeAdapter.detachNode(bodyElement);
 | |
|         p.openElements.popAllUpToHtmlElement();
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.insertionMode = InsertionMode.IN_FRAMESET;
 | |
|     }
 | |
| }
 | |
| function addressStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function numberedHeaderStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     if (p.openElements.currentTagId !== undefined && NUMBERED_HEADERS.has(p.openElements.currentTagId)) {
 | |
|         p.openElements.pop();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function preStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
 | |
|     //on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.)
 | |
|     p.skipNextNewLine = true;
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| function formStartTagInBody(p, token) {
 | |
|     const inTemplate = p.openElements.tmplCount > 0;
 | |
|     if (!p.formElement || inTemplate) {
 | |
|         if (p.openElements.hasInButtonScope($.P)) {
 | |
|             p._closePElement();
 | |
|         }
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         if (!inTemplate) {
 | |
|             p.formElement = p.openElements.current;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function listItemStartTagInBody(p, token) {
 | |
|     p.framesetOk = false;
 | |
|     const tn = token.tagID;
 | |
|     for (let i = p.openElements.stackTop; i >= 0; i--) {
 | |
|         const elementId = p.openElements.tagIDs[i];
 | |
|         if ((tn === $.LI && elementId === $.LI) ||
 | |
|             ((tn === $.DD || tn === $.DT) && (elementId === $.DD || elementId === $.DT))) {
 | |
|             p.openElements.generateImpliedEndTagsWithExclusion(elementId);
 | |
|             p.openElements.popUntilTagNamePopped(elementId);
 | |
|             break;
 | |
|         }
 | |
|         if (elementId !== $.ADDRESS &&
 | |
|             elementId !== $.DIV &&
 | |
|             elementId !== $.P &&
 | |
|             p._isSpecialElement(p.openElements.items[i], elementId)) {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function plaintextStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.tokenizer.state = TokenizerMode.PLAINTEXT;
 | |
| }
 | |
| function buttonStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInScope($.BUTTON)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         p.openElements.popUntilTagNamePopped($.BUTTON);
 | |
|     }
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| function aStartTagInBody(p, token) {
 | |
|     const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(TN.A);
 | |
|     if (activeElementEntry) {
 | |
|         callAdoptionAgency(p, token);
 | |
|         p.openElements.remove(activeElementEntry.element);
 | |
|         p.activeFormattingElements.removeEntry(activeElementEntry);
 | |
|     }
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.activeFormattingElements.pushElement(p.openElements.current, token);
 | |
| }
 | |
| function bStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.activeFormattingElements.pushElement(p.openElements.current, token);
 | |
| }
 | |
| function nobrStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     if (p.openElements.hasInScope($.NOBR)) {
 | |
|         callAdoptionAgency(p, token);
 | |
|         p._reconstructActiveFormattingElements();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.activeFormattingElements.pushElement(p.openElements.current, token);
 | |
| }
 | |
| function appletStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.activeFormattingElements.insertMarker();
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| function tableStartTagInBody(p, token) {
 | |
|     if (p.treeAdapter.getDocumentMode(p.document) !== DOCUMENT_MODE.QUIRKS && p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
|     p.insertionMode = InsertionMode.IN_TABLE;
 | |
| }
 | |
| function areaStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._appendElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function isHiddenInput(token) {
 | |
|     const inputType = getTokenAttr(token, ATTRS.TYPE);
 | |
|     return inputType != null && inputType.toLowerCase() === HIDDEN_INPUT_TYPE;
 | |
| }
 | |
| function inputStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._appendElement(token, NS.HTML);
 | |
|     if (!isHiddenInput(token)) {
 | |
|         p.framesetOk = false;
 | |
|     }
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function paramStartTagInBody(p, token) {
 | |
|     p._appendElement(token, NS.HTML);
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function hrStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._appendElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function imageStartTagInBody(p, token) {
 | |
|     token.tagName = TN.IMG;
 | |
|     token.tagID = $.IMG;
 | |
|     areaStartTagInBody(p, token);
 | |
| }
 | |
| function textareaStartTagInBody(p, token) {
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
 | |
|     //on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.)
 | |
|     p.skipNextNewLine = true;
 | |
|     p.tokenizer.state = TokenizerMode.RCDATA;
 | |
|     p.originalInsertionMode = p.insertionMode;
 | |
|     p.framesetOk = false;
 | |
|     p.insertionMode = InsertionMode.TEXT;
 | |
| }
 | |
| function xmpStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p.framesetOk = false;
 | |
|     p._switchToTextParsing(token, TokenizerMode.RAWTEXT);
 | |
| }
 | |
| function iframeStartTagInBody(p, token) {
 | |
|     p.framesetOk = false;
 | |
|     p._switchToTextParsing(token, TokenizerMode.RAWTEXT);
 | |
| }
 | |
| //NOTE: here we assume that we always act as a user agent with enabled plugins/frames, so we parse
 | |
| //<noembed>/<noframes> as rawtext.
 | |
| function rawTextStartTagInBody(p, token) {
 | |
|     p._switchToTextParsing(token, TokenizerMode.RAWTEXT);
 | |
| }
 | |
| function selectStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
|     p.insertionMode =
 | |
|         p.insertionMode === InsertionMode.IN_TABLE ||
 | |
|             p.insertionMode === InsertionMode.IN_CAPTION ||
 | |
|             p.insertionMode === InsertionMode.IN_TABLE_BODY ||
 | |
|             p.insertionMode === InsertionMode.IN_ROW ||
 | |
|             p.insertionMode === InsertionMode.IN_CELL
 | |
|             ? InsertionMode.IN_SELECT_IN_TABLE
 | |
|             : InsertionMode.IN_SELECT;
 | |
| }
 | |
| function optgroupStartTagInBody(p, token) {
 | |
|     if (p.openElements.currentTagId === $.OPTION) {
 | |
|         p.openElements.pop();
 | |
|     }
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function rbStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInScope($.RUBY)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function rtStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInScope($.RUBY)) {
 | |
|         p.openElements.generateImpliedEndTagsWithExclusion($.RTC);
 | |
|     }
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function mathStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     foreignContent.adjustTokenMathMLAttrs(token);
 | |
|     foreignContent.adjustTokenXMLAttrs(token);
 | |
|     if (token.selfClosing) {
 | |
|         p._appendElement(token, NS.MATHML);
 | |
|     }
 | |
|     else {
 | |
|         p._insertElement(token, NS.MATHML);
 | |
|     }
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function svgStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     foreignContent.adjustTokenSVGAttrs(token);
 | |
|     foreignContent.adjustTokenXMLAttrs(token);
 | |
|     if (token.selfClosing) {
 | |
|         p._appendElement(token, NS.SVG);
 | |
|     }
 | |
|     else {
 | |
|         p._insertElement(token, NS.SVG);
 | |
|     }
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function genericStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
| }
 | |
| function startTagInBody(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.I:
 | |
|         case $.S:
 | |
|         case $.B:
 | |
|         case $.U:
 | |
|         case $.EM:
 | |
|         case $.TT:
 | |
|         case $.BIG:
 | |
|         case $.CODE:
 | |
|         case $.FONT:
 | |
|         case $.SMALL:
 | |
|         case $.STRIKE:
 | |
|         case $.STRONG: {
 | |
|             bStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.A: {
 | |
|             aStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.H1:
 | |
|         case $.H2:
 | |
|         case $.H3:
 | |
|         case $.H4:
 | |
|         case $.H5:
 | |
|         case $.H6: {
 | |
|             numberedHeaderStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.P:
 | |
|         case $.DL:
 | |
|         case $.OL:
 | |
|         case $.UL:
 | |
|         case $.DIV:
 | |
|         case $.DIR:
 | |
|         case $.NAV:
 | |
|         case $.MAIN:
 | |
|         case $.MENU:
 | |
|         case $.ASIDE:
 | |
|         case $.CENTER:
 | |
|         case $.FIGURE:
 | |
|         case $.FOOTER:
 | |
|         case $.HEADER:
 | |
|         case $.HGROUP:
 | |
|         case $.DIALOG:
 | |
|         case $.DETAILS:
 | |
|         case $.ADDRESS:
 | |
|         case $.ARTICLE:
 | |
|         case $.SEARCH:
 | |
|         case $.SECTION:
 | |
|         case $.SUMMARY:
 | |
|         case $.FIELDSET:
 | |
|         case $.BLOCKQUOTE:
 | |
|         case $.FIGCAPTION: {
 | |
|             addressStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.LI:
 | |
|         case $.DD:
 | |
|         case $.DT: {
 | |
|             listItemStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BR:
 | |
|         case $.IMG:
 | |
|         case $.WBR:
 | |
|         case $.AREA:
 | |
|         case $.EMBED:
 | |
|         case $.KEYGEN: {
 | |
|             areaStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.HR: {
 | |
|             hrStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.RB:
 | |
|         case $.RTC: {
 | |
|             rbStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.RT:
 | |
|         case $.RP: {
 | |
|             rtStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.PRE:
 | |
|         case $.LISTING: {
 | |
|             preStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.XMP: {
 | |
|             xmpStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.SVG: {
 | |
|             svgStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.HTML: {
 | |
|             htmlStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BASE:
 | |
|         case $.LINK:
 | |
|         case $.META:
 | |
|         case $.STYLE:
 | |
|         case $.TITLE:
 | |
|         case $.SCRIPT:
 | |
|         case $.BGSOUND:
 | |
|         case $.BASEFONT:
 | |
|         case $.TEMPLATE: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY: {
 | |
|             bodyStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.FORM: {
 | |
|             formStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOBR: {
 | |
|             nobrStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.MATH: {
 | |
|             mathStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TABLE: {
 | |
|             tableStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.INPUT: {
 | |
|             inputStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.PARAM:
 | |
|         case $.TRACK:
 | |
|         case $.SOURCE: {
 | |
|             paramStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.IMAGE: {
 | |
|             imageStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BUTTON: {
 | |
|             buttonStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.APPLET:
 | |
|         case $.OBJECT:
 | |
|         case $.MARQUEE: {
 | |
|             appletStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.IFRAME: {
 | |
|             iframeStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.SELECT: {
 | |
|             selectStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.OPTION:
 | |
|         case $.OPTGROUP: {
 | |
|             optgroupStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOEMBED:
 | |
|         case $.NOFRAMES: {
 | |
|             rawTextStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.FRAMESET: {
 | |
|             framesetStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TEXTAREA: {
 | |
|             textareaStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOSCRIPT: {
 | |
|             if (p.options.scriptingEnabled) {
 | |
|                 rawTextStartTagInBody(p, token);
 | |
|             }
 | |
|             else {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.PLAINTEXT: {
 | |
|             plaintextStartTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.COL:
 | |
|         case $.TH:
 | |
|         case $.TD:
 | |
|         case $.TR:
 | |
|         case $.HEAD:
 | |
|         case $.FRAME:
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD:
 | |
|         case $.CAPTION:
 | |
|         case $.COLGROUP: {
 | |
|             // Ignore token
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             genericStartTagInBody(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function bodyEndTagInBody(p, token) {
 | |
|     if (p.openElements.hasInScope($.BODY)) {
 | |
|         p.insertionMode = InsertionMode.AFTER_BODY;
 | |
|         //NOTE: <body> is never popped from the stack, so we need to updated
 | |
|         //the end location explicitly.
 | |
|         if (p.options.sourceCodeLocationInfo) {
 | |
|             const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
 | |
|             if (bodyElement) {
 | |
|                 p._setEndLocation(bodyElement, token);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function htmlEndTagInBody(p, token) {
 | |
|     if (p.openElements.hasInScope($.BODY)) {
 | |
|         p.insertionMode = InsertionMode.AFTER_BODY;
 | |
|         endTagAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| function addressEndTagInBody(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (p.openElements.hasInScope(tn)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         p.openElements.popUntilTagNamePopped(tn);
 | |
|     }
 | |
| }
 | |
| function formEndTagInBody(p) {
 | |
|     const inTemplate = p.openElements.tmplCount > 0;
 | |
|     const { formElement } = p;
 | |
|     if (!inTemplate) {
 | |
|         p.formElement = null;
 | |
|     }
 | |
|     if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         if (inTemplate) {
 | |
|             p.openElements.popUntilTagNamePopped($.FORM);
 | |
|         }
 | |
|         else if (formElement) {
 | |
|             p.openElements.remove(formElement);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function pEndTagInBody(p) {
 | |
|     if (!p.openElements.hasInButtonScope($.P)) {
 | |
|         p._insertFakeElement(TN.P, $.P);
 | |
|     }
 | |
|     p._closePElement();
 | |
| }
 | |
| function liEndTagInBody(p) {
 | |
|     if (p.openElements.hasInListItemScope($.LI)) {
 | |
|         p.openElements.generateImpliedEndTagsWithExclusion($.LI);
 | |
|         p.openElements.popUntilTagNamePopped($.LI);
 | |
|     }
 | |
| }
 | |
| function ddEndTagInBody(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (p.openElements.hasInScope(tn)) {
 | |
|         p.openElements.generateImpliedEndTagsWithExclusion(tn);
 | |
|         p.openElements.popUntilTagNamePopped(tn);
 | |
|     }
 | |
| }
 | |
| function numberedHeaderEndTagInBody(p) {
 | |
|     if (p.openElements.hasNumberedHeaderInScope()) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         p.openElements.popUntilNumberedHeaderPopped();
 | |
|     }
 | |
| }
 | |
| function appletEndTagInBody(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (p.openElements.hasInScope(tn)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         p.openElements.popUntilTagNamePopped(tn);
 | |
|         p.activeFormattingElements.clearToLastMarker();
 | |
|     }
 | |
| }
 | |
| function brEndTagInBody(p) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertFakeElement(TN.BR, $.BR);
 | |
|     p.openElements.pop();
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| function genericEndTagInBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
|     const tid = token.tagID;
 | |
|     for (let i = p.openElements.stackTop; i > 0; i--) {
 | |
|         const element = p.openElements.items[i];
 | |
|         const elementId = p.openElements.tagIDs[i];
 | |
|         // Compare the tag name here, as the tag might not be a known tag with an ID.
 | |
|         if (tid === elementId && (tid !== $.UNKNOWN || p.treeAdapter.getTagName(element) === tn)) {
 | |
|             p.openElements.generateImpliedEndTagsWithExclusion(tid);
 | |
|             if (p.openElements.stackTop >= i)
 | |
|                 p.openElements.shortenToLength(i);
 | |
|             break;
 | |
|         }
 | |
|         if (p._isSpecialElement(element, elementId)) {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInBody(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.A:
 | |
|         case $.B:
 | |
|         case $.I:
 | |
|         case $.S:
 | |
|         case $.U:
 | |
|         case $.EM:
 | |
|         case $.TT:
 | |
|         case $.BIG:
 | |
|         case $.CODE:
 | |
|         case $.FONT:
 | |
|         case $.NOBR:
 | |
|         case $.SMALL:
 | |
|         case $.STRIKE:
 | |
|         case $.STRONG: {
 | |
|             callAdoptionAgency(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.P: {
 | |
|             pEndTagInBody(p);
 | |
|             break;
 | |
|         }
 | |
|         case $.DL:
 | |
|         case $.UL:
 | |
|         case $.OL:
 | |
|         case $.DIR:
 | |
|         case $.DIV:
 | |
|         case $.NAV:
 | |
|         case $.PRE:
 | |
|         case $.MAIN:
 | |
|         case $.MENU:
 | |
|         case $.ASIDE:
 | |
|         case $.BUTTON:
 | |
|         case $.CENTER:
 | |
|         case $.FIGURE:
 | |
|         case $.FOOTER:
 | |
|         case $.HEADER:
 | |
|         case $.HGROUP:
 | |
|         case $.DIALOG:
 | |
|         case $.ADDRESS:
 | |
|         case $.ARTICLE:
 | |
|         case $.DETAILS:
 | |
|         case $.SEARCH:
 | |
|         case $.SECTION:
 | |
|         case $.SUMMARY:
 | |
|         case $.LISTING:
 | |
|         case $.FIELDSET:
 | |
|         case $.BLOCKQUOTE:
 | |
|         case $.FIGCAPTION: {
 | |
|             addressEndTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.LI: {
 | |
|             liEndTagInBody(p);
 | |
|             break;
 | |
|         }
 | |
|         case $.DD:
 | |
|         case $.DT: {
 | |
|             ddEndTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.H1:
 | |
|         case $.H2:
 | |
|         case $.H3:
 | |
|         case $.H4:
 | |
|         case $.H5:
 | |
|         case $.H6: {
 | |
|             numberedHeaderEndTagInBody(p);
 | |
|             break;
 | |
|         }
 | |
|         case $.BR: {
 | |
|             brEndTagInBody(p);
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY: {
 | |
|             bodyEndTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.HTML: {
 | |
|             htmlEndTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.FORM: {
 | |
|             formEndTagInBody(p);
 | |
|             break;
 | |
|         }
 | |
|         case $.APPLET:
 | |
|         case $.OBJECT:
 | |
|         case $.MARQUEE: {
 | |
|             appletEndTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             templateEndTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             genericEndTagInBody(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function eofInBody(p, token) {
 | |
|     if (p.tmplInsertionModeStack.length > 0) {
 | |
|         eofInTemplate(p, token);
 | |
|     }
 | |
|     else {
 | |
|         stopParsing(p, token);
 | |
|     }
 | |
| }
 | |
| // The "text" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function endTagInText(p, token) {
 | |
|     var _a;
 | |
|     if (token.tagID === $.SCRIPT) {
 | |
|         (_a = p.scriptHandler) === null || _a === void 0 ? void 0 : _a.call(p, p.openElements.current);
 | |
|     }
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = p.originalInsertionMode;
 | |
| }
 | |
| function eofInText(p, token) {
 | |
|     p._err(token, ERR.eofInElementThatCanContainOnlyText);
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = p.originalInsertionMode;
 | |
|     p.onEof(token);
 | |
| }
 | |
| // The "in table" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function characterInTable(p, token) {
 | |
|     if (p.openElements.currentTagId !== undefined && TABLE_STRUCTURE_TAGS.has(p.openElements.currentTagId)) {
 | |
|         p.pendingCharacterTokens.length = 0;
 | |
|         p.hasNonWhitespacePendingCharacterToken = false;
 | |
|         p.originalInsertionMode = p.insertionMode;
 | |
|         p.insertionMode = InsertionMode.IN_TABLE_TEXT;
 | |
|         switch (token.type) {
 | |
|             case TokenType.CHARACTER: {
 | |
|                 characterInTableText(p, token);
 | |
|                 break;
 | |
|             }
 | |
|             case TokenType.WHITESPACE_CHARACTER: {
 | |
|                 whitespaceCharacterInTableText(p, token);
 | |
|                 break;
 | |
|             }
 | |
|             // Ignore null
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         tokenInTable(p, token);
 | |
|     }
 | |
| }
 | |
| function captionStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p.activeFormattingElements.insertMarker();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.insertionMode = InsertionMode.IN_CAPTION;
 | |
| }
 | |
| function colgroupStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.insertionMode = InsertionMode.IN_COLUMN_GROUP;
 | |
| }
 | |
| function colStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertFakeElement(TN.COLGROUP, $.COLGROUP);
 | |
|     p.insertionMode = InsertionMode.IN_COLUMN_GROUP;
 | |
|     startTagInColumnGroup(p, token);
 | |
| }
 | |
| function tbodyStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
| }
 | |
| function tdStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertFakeElement(TN.TBODY, $.TBODY);
 | |
|     p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|     startTagInTableBody(p, token);
 | |
| }
 | |
| function tableStartTagInTable(p, token) {
 | |
|     if (p.openElements.hasInTableScope($.TABLE)) {
 | |
|         p.openElements.popUntilTagNamePopped($.TABLE);
 | |
|         p._resetInsertionMode();
 | |
|         p._processStartTag(token);
 | |
|     }
 | |
| }
 | |
| function inputStartTagInTable(p, token) {
 | |
|     if (isHiddenInput(token)) {
 | |
|         p._appendElement(token, NS.HTML);
 | |
|     }
 | |
|     else {
 | |
|         tokenInTable(p, token);
 | |
|     }
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| function formStartTagInTable(p, token) {
 | |
|     if (!p.formElement && p.openElements.tmplCount === 0) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.formElement = p.openElements.current;
 | |
|         p.openElements.pop();
 | |
|     }
 | |
| }
 | |
| function startTagInTable(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.TD:
 | |
|         case $.TH:
 | |
|         case $.TR: {
 | |
|             tdStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.STYLE:
 | |
|         case $.SCRIPT:
 | |
|         case $.TEMPLATE: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.COL: {
 | |
|             colStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.FORM: {
 | |
|             formStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TABLE: {
 | |
|             tableStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD: {
 | |
|             tbodyStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.INPUT: {
 | |
|             inputStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.CAPTION: {
 | |
|             captionStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.COLGROUP: {
 | |
|             colgroupStartTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenInTable(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInTable(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.TABLE: {
 | |
|             if (p.openElements.hasInTableScope($.TABLE)) {
 | |
|                 p.openElements.popUntilTagNamePopped($.TABLE);
 | |
|                 p._resetInsertionMode();
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             templateEndTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY:
 | |
|         case $.CAPTION:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.HTML:
 | |
|         case $.TBODY:
 | |
|         case $.TD:
 | |
|         case $.TFOOT:
 | |
|         case $.TH:
 | |
|         case $.THEAD:
 | |
|         case $.TR: {
 | |
|             // Ignore token
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenInTable(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function tokenInTable(p, token) {
 | |
|     const savedFosterParentingState = p.fosterParentingEnabled;
 | |
|     p.fosterParentingEnabled = true;
 | |
|     // Process token in `In Body` mode
 | |
|     modeInBody(p, token);
 | |
|     p.fosterParentingEnabled = savedFosterParentingState;
 | |
| }
 | |
| // The "in table text" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function whitespaceCharacterInTableText(p, token) {
 | |
|     p.pendingCharacterTokens.push(token);
 | |
| }
 | |
| function characterInTableText(p, token) {
 | |
|     p.pendingCharacterTokens.push(token);
 | |
|     p.hasNonWhitespacePendingCharacterToken = true;
 | |
| }
 | |
| function tokenInTableText(p, token) {
 | |
|     let i = 0;
 | |
|     if (p.hasNonWhitespacePendingCharacterToken) {
 | |
|         for (; i < p.pendingCharacterTokens.length; i++) {
 | |
|             tokenInTable(p, p.pendingCharacterTokens[i]);
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         for (; i < p.pendingCharacterTokens.length; i++) {
 | |
|             p._insertCharacters(p.pendingCharacterTokens[i]);
 | |
|         }
 | |
|     }
 | |
|     p.insertionMode = p.originalInsertionMode;
 | |
|     p._processToken(token);
 | |
| }
 | |
| // The "in caption" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| const TABLE_VOID_ELEMENTS = new Set([$.CAPTION, $.COL, $.COLGROUP, $.TBODY, $.TD, $.TFOOT, $.TH, $.THEAD, $.TR]);
 | |
| function startTagInCaption(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (TABLE_VOID_ELEMENTS.has(tn)) {
 | |
|         if (p.openElements.hasInTableScope($.CAPTION)) {
 | |
|             p.openElements.generateImpliedEndTags();
 | |
|             p.openElements.popUntilTagNamePopped($.CAPTION);
 | |
|             p.activeFormattingElements.clearToLastMarker();
 | |
|             p.insertionMode = InsertionMode.IN_TABLE;
 | |
|             startTagInTable(p, token);
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         startTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| function endTagInCaption(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     switch (tn) {
 | |
|         case $.CAPTION:
 | |
|         case $.TABLE: {
 | |
|             if (p.openElements.hasInTableScope($.CAPTION)) {
 | |
|                 p.openElements.generateImpliedEndTags();
 | |
|                 p.openElements.popUntilTagNamePopped($.CAPTION);
 | |
|                 p.activeFormattingElements.clearToLastMarker();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE;
 | |
|                 if (tn === $.TABLE) {
 | |
|                     endTagInTable(p, token);
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.HTML:
 | |
|         case $.TBODY:
 | |
|         case $.TD:
 | |
|         case $.TFOOT:
 | |
|         case $.TH:
 | |
|         case $.THEAD:
 | |
|         case $.TR: {
 | |
|             // Ignore token
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             endTagInBody(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // The "in column group" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInColumnGroup(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.COL: {
 | |
|             p._appendElement(token, NS.HTML);
 | |
|             token.ackSelfClosing = true;
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenInColumnGroup(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInColumnGroup(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.COLGROUP: {
 | |
|             if (p.openElements.currentTagId === $.COLGROUP) {
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             templateEndTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.COL: {
 | |
|             // Ignore token
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             tokenInColumnGroup(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function tokenInColumnGroup(p, token) {
 | |
|     if (p.openElements.currentTagId === $.COLGROUP) {
 | |
|         p.openElements.pop();
 | |
|         p.insertionMode = InsertionMode.IN_TABLE;
 | |
|         p._processToken(token);
 | |
|     }
 | |
| }
 | |
| // The "in table body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInTableBody(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.TR: {
 | |
|             p.openElements.clearBackToTableBodyContext();
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             p.insertionMode = InsertionMode.IN_ROW;
 | |
|             break;
 | |
|         }
 | |
|         case $.TH:
 | |
|         case $.TD: {
 | |
|             p.openElements.clearBackToTableBodyContext();
 | |
|             p._insertFakeElement(TN.TR, $.TR);
 | |
|             p.insertionMode = InsertionMode.IN_ROW;
 | |
|             startTagInRow(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.CAPTION:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD: {
 | |
|             if (p.openElements.hasTableBodyContextInTableScope()) {
 | |
|                 p.openElements.clearBackToTableBodyContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE;
 | |
|                 startTagInTable(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             startTagInTable(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInTableBody(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     switch (token.tagID) {
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD: {
 | |
|             if (p.openElements.hasInTableScope(tn)) {
 | |
|                 p.openElements.clearBackToTableBodyContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TABLE: {
 | |
|             if (p.openElements.hasTableBodyContextInTableScope()) {
 | |
|                 p.openElements.clearBackToTableBodyContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE;
 | |
|                 endTagInTable(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY:
 | |
|         case $.CAPTION:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.HTML:
 | |
|         case $.TD:
 | |
|         case $.TH:
 | |
|         case $.TR: {
 | |
|             // Ignore token
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             endTagInTable(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // The "in row" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInRow(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.TH:
 | |
|         case $.TD: {
 | |
|             p.openElements.clearBackToTableRowContext();
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             p.insertionMode = InsertionMode.IN_CELL;
 | |
|             p.activeFormattingElements.insertMarker();
 | |
|             break;
 | |
|         }
 | |
|         case $.CAPTION:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD:
 | |
|         case $.TR: {
 | |
|             if (p.openElements.hasInTableScope($.TR)) {
 | |
|                 p.openElements.clearBackToTableRowContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|                 startTagInTableBody(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             startTagInTable(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInRow(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.TR: {
 | |
|             if (p.openElements.hasInTableScope($.TR)) {
 | |
|                 p.openElements.clearBackToTableRowContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TABLE: {
 | |
|             if (p.openElements.hasInTableScope($.TR)) {
 | |
|                 p.openElements.clearBackToTableRowContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|                 endTagInTableBody(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD: {
 | |
|             if (p.openElements.hasInTableScope(token.tagID) || p.openElements.hasInTableScope($.TR)) {
 | |
|                 p.openElements.clearBackToTableRowContext();
 | |
|                 p.openElements.pop();
 | |
|                 p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|                 endTagInTableBody(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY:
 | |
|         case $.CAPTION:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.HTML:
 | |
|         case $.TD:
 | |
|         case $.TH: {
 | |
|             // Ignore end tag
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             endTagInTable(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // The "in cell" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInCell(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (TABLE_VOID_ELEMENTS.has(tn)) {
 | |
|         if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) {
 | |
|             p._closeTableCell();
 | |
|             startTagInRow(p, token);
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         startTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| function endTagInCell(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     switch (tn) {
 | |
|         case $.TD:
 | |
|         case $.TH: {
 | |
|             if (p.openElements.hasInTableScope(tn)) {
 | |
|                 p.openElements.generateImpliedEndTags();
 | |
|                 p.openElements.popUntilTagNamePopped(tn);
 | |
|                 p.activeFormattingElements.clearToLastMarker();
 | |
|                 p.insertionMode = InsertionMode.IN_ROW;
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TABLE:
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD:
 | |
|         case $.TR: {
 | |
|             if (p.openElements.hasInTableScope(tn)) {
 | |
|                 p._closeTableCell();
 | |
|                 endTagInRow(p, token);
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.BODY:
 | |
|         case $.CAPTION:
 | |
|         case $.COL:
 | |
|         case $.COLGROUP:
 | |
|         case $.HTML: {
 | |
|             // Ignore token
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             endTagInBody(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // The "in select" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInSelect(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.OPTION: {
 | |
|             if (p.openElements.currentTagId === $.OPTION) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             break;
 | |
|         }
 | |
|         case $.OPTGROUP: {
 | |
|             if (p.openElements.currentTagId === $.OPTION) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             if (p.openElements.currentTagId === $.OPTGROUP) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             break;
 | |
|         }
 | |
|         case $.HR: {
 | |
|             if (p.openElements.currentTagId === $.OPTION) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             if (p.openElements.currentTagId === $.OPTGROUP) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             p._appendElement(token, NS.HTML);
 | |
|             token.ackSelfClosing = true;
 | |
|             break;
 | |
|         }
 | |
|         case $.INPUT:
 | |
|         case $.KEYGEN:
 | |
|         case $.TEXTAREA:
 | |
|         case $.SELECT: {
 | |
|             if (p.openElements.hasInSelectScope($.SELECT)) {
 | |
|                 p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|                 p._resetInsertionMode();
 | |
|                 if (token.tagID !== $.SELECT) {
 | |
|                     p._processStartTag(token);
 | |
|                 }
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.SCRIPT:
 | |
|         case $.TEMPLATE: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|         // Do nothing
 | |
|     }
 | |
| }
 | |
| function endTagInSelect(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.OPTGROUP: {
 | |
|             if (p.openElements.stackTop > 0 &&
 | |
|                 p.openElements.currentTagId === $.OPTION &&
 | |
|                 p.openElements.tagIDs[p.openElements.stackTop - 1] === $.OPTGROUP) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             if (p.openElements.currentTagId === $.OPTGROUP) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.OPTION: {
 | |
|             if (p.openElements.currentTagId === $.OPTION) {
 | |
|                 p.openElements.pop();
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.SELECT: {
 | |
|             if (p.openElements.hasInSelectScope($.SELECT)) {
 | |
|                 p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|                 p._resetInsertionMode();
 | |
|             }
 | |
|             break;
 | |
|         }
 | |
|         case $.TEMPLATE: {
 | |
|             templateEndTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|         // Do nothing
 | |
|     }
 | |
| }
 | |
| // The "in select in table" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInSelectInTable(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (tn === $.CAPTION ||
 | |
|         tn === $.TABLE ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.THEAD ||
 | |
|         tn === $.TR ||
 | |
|         tn === $.TD ||
 | |
|         tn === $.TH) {
 | |
|         p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|         p._resetInsertionMode();
 | |
|         p._processStartTag(token);
 | |
|     }
 | |
|     else {
 | |
|         startTagInSelect(p, token);
 | |
|     }
 | |
| }
 | |
| function endTagInSelectInTable(p, token) {
 | |
|     const tn = token.tagID;
 | |
|     if (tn === $.CAPTION ||
 | |
|         tn === $.TABLE ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.THEAD ||
 | |
|         tn === $.TR ||
 | |
|         tn === $.TD ||
 | |
|         tn === $.TH) {
 | |
|         if (p.openElements.hasInTableScope(tn)) {
 | |
|             p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|             p._resetInsertionMode();
 | |
|             p.onEndTag(token);
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         endTagInSelect(p, token);
 | |
|     }
 | |
| }
 | |
| // The "in template" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInTemplate(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         // First, handle tags that can start without a mode change
 | |
|         case $.BASE:
 | |
|         case $.BASEFONT:
 | |
|         case $.BGSOUND:
 | |
|         case $.LINK:
 | |
|         case $.META:
 | |
|         case $.NOFRAMES:
 | |
|         case $.SCRIPT:
 | |
|         case $.STYLE:
 | |
|         case $.TEMPLATE:
 | |
|         case $.TITLE: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         // Re-process the token in the appropriate mode
 | |
|         case $.CAPTION:
 | |
|         case $.COLGROUP:
 | |
|         case $.TBODY:
 | |
|         case $.TFOOT:
 | |
|         case $.THEAD: {
 | |
|             p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE;
 | |
|             p.insertionMode = InsertionMode.IN_TABLE;
 | |
|             startTagInTable(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.COL: {
 | |
|             p.tmplInsertionModeStack[0] = InsertionMode.IN_COLUMN_GROUP;
 | |
|             p.insertionMode = InsertionMode.IN_COLUMN_GROUP;
 | |
|             startTagInColumnGroup(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TR: {
 | |
|             p.tmplInsertionModeStack[0] = InsertionMode.IN_TABLE_BODY;
 | |
|             p.insertionMode = InsertionMode.IN_TABLE_BODY;
 | |
|             startTagInTableBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.TD:
 | |
|         case $.TH: {
 | |
|             p.tmplInsertionModeStack[0] = InsertionMode.IN_ROW;
 | |
|             p.insertionMode = InsertionMode.IN_ROW;
 | |
|             startTagInRow(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default: {
 | |
|             p.tmplInsertionModeStack[0] = InsertionMode.IN_BODY;
 | |
|             p.insertionMode = InsertionMode.IN_BODY;
 | |
|             startTagInBody(p, token);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| function endTagInTemplate(p, token) {
 | |
|     if (token.tagID === $.TEMPLATE) {
 | |
|         templateEndTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| function eofInTemplate(p, token) {
 | |
|     if (p.openElements.tmplCount > 0) {
 | |
|         p.openElements.popUntilTagNamePopped($.TEMPLATE);
 | |
|         p.activeFormattingElements.clearToLastMarker();
 | |
|         p.tmplInsertionModeStack.shift();
 | |
|         p._resetInsertionMode();
 | |
|         p.onEof(token);
 | |
|     }
 | |
|     else {
 | |
|         stopParsing(p, token);
 | |
|     }
 | |
| }
 | |
| // The "after body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterBody(p, token) {
 | |
|     if (token.tagID === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     }
 | |
|     else {
 | |
|         tokenAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| function endTagAfterBody(p, token) {
 | |
|     var _a;
 | |
|     if (token.tagID === $.HTML) {
 | |
|         if (!p.fragmentContext) {
 | |
|             p.insertionMode = InsertionMode.AFTER_AFTER_BODY;
 | |
|         }
 | |
|         //NOTE: <html> is never popped from the stack, so we need to updated
 | |
|         //the end location explicitly.
 | |
|         if (p.options.sourceCodeLocationInfo && p.openElements.tagIDs[0] === $.HTML) {
 | |
|             p._setEndLocation(p.openElements.items[0], token);
 | |
|             // Update the body element, if it doesn't have an end tag
 | |
|             const bodyElement = p.openElements.items[1];
 | |
|             if (bodyElement && !((_a = p.treeAdapter.getNodeSourceCodeLocation(bodyElement)) === null || _a === void 0 ? void 0 : _a.endTag)) {
 | |
|                 p._setEndLocation(bodyElement, token);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     else {
 | |
|         tokenAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| function tokenAfterBody(p, token) {
 | |
|     p.insertionMode = InsertionMode.IN_BODY;
 | |
|     modeInBody(p, token);
 | |
| }
 | |
| // The "in frameset" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInFrameset(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.FRAMESET: {
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             break;
 | |
|         }
 | |
|         case $.FRAME: {
 | |
|             p._appendElement(token, NS.HTML);
 | |
|             token.ackSelfClosing = true;
 | |
|             break;
 | |
|         }
 | |
|         case $.NOFRAMES: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|         // Do nothing
 | |
|     }
 | |
| }
 | |
| function endTagInFrameset(p, token) {
 | |
|     if (token.tagID === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) {
 | |
|         p.openElements.pop();
 | |
|         if (!p.fragmentContext && p.openElements.currentTagId !== $.FRAMESET) {
 | |
|             p.insertionMode = InsertionMode.AFTER_FRAMESET;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| // The "after frameset" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterFrameset(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOFRAMES: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|         // Do nothing
 | |
|     }
 | |
| }
 | |
| function endTagAfterFrameset(p, token) {
 | |
|     if (token.tagID === $.HTML) {
 | |
|         p.insertionMode = InsertionMode.AFTER_AFTER_FRAMESET;
 | |
|     }
 | |
| }
 | |
| // The "after after body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterAfterBody(p, token) {
 | |
|     if (token.tagID === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     }
 | |
|     else {
 | |
|         tokenAfterAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| function tokenAfterAfterBody(p, token) {
 | |
|     p.insertionMode = InsertionMode.IN_BODY;
 | |
|     modeInBody(p, token);
 | |
| }
 | |
| // The "after after frameset" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterAfterFrameset(p, token) {
 | |
|     switch (token.tagID) {
 | |
|         case $.HTML: {
 | |
|             startTagInBody(p, token);
 | |
|             break;
 | |
|         }
 | |
|         case $.NOFRAMES: {
 | |
|             startTagInHead(p, token);
 | |
|             break;
 | |
|         }
 | |
|         default:
 | |
|         // Do nothing
 | |
|     }
 | |
| }
 | |
| // The rules for parsing tokens in foreign content
 | |
| //------------------------------------------------------------------
 | |
| function nullCharacterInForeignContent(p, token) {
 | |
|     token.chars = unicode.REPLACEMENT_CHARACTER;
 | |
|     p._insertCharacters(token);
 | |
| }
 | |
| function characterInForeignContent(p, token) {
 | |
|     p._insertCharacters(token);
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| function popUntilHtmlOrIntegrationPoint(p) {
 | |
|     while (p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML &&
 | |
|         p.openElements.currentTagId !== undefined &&
 | |
|         !p._isIntegrationPoint(p.openElements.currentTagId, p.openElements.current)) {
 | |
|         p.openElements.pop();
 | |
|     }
 | |
| }
 | |
| function startTagInForeignContent(p, token) {
 | |
|     if (foreignContent.causesExit(token)) {
 | |
|         popUntilHtmlOrIntegrationPoint(p);
 | |
|         p._startTagOutsideForeignContent(token);
 | |
|     }
 | |
|     else {
 | |
|         const current = p._getAdjustedCurrentElement();
 | |
|         const currentNs = p.treeAdapter.getNamespaceURI(current);
 | |
|         if (currentNs === NS.MATHML) {
 | |
|             foreignContent.adjustTokenMathMLAttrs(token);
 | |
|         }
 | |
|         else if (currentNs === NS.SVG) {
 | |
|             foreignContent.adjustTokenSVGTagName(token);
 | |
|             foreignContent.adjustTokenSVGAttrs(token);
 | |
|         }
 | |
|         foreignContent.adjustTokenXMLAttrs(token);
 | |
|         if (token.selfClosing) {
 | |
|             p._appendElement(token, currentNs);
 | |
|         }
 | |
|         else {
 | |
|             p._insertElement(token, currentNs);
 | |
|         }
 | |
|         token.ackSelfClosing = true;
 | |
|     }
 | |
| }
 | |
| function endTagInForeignContent(p, token) {
 | |
|     if (token.tagID === $.P || token.tagID === $.BR) {
 | |
|         popUntilHtmlOrIntegrationPoint(p);
 | |
|         p._endTagOutsideForeignContent(token);
 | |
|         return;
 | |
|     }
 | |
|     for (let i = p.openElements.stackTop; i > 0; i--) {
 | |
|         const element = p.openElements.items[i];
 | |
|         if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) {
 | |
|             p._endTagOutsideForeignContent(token);
 | |
|             break;
 | |
|         }
 | |
|         const tagName = p.treeAdapter.getTagName(element);
 | |
|         if (tagName.toLowerCase() === token.tagName) {
 | |
|             //NOTE: update the token tag name for `_setEndLocation`.
 | |
|             token.tagName = tagName;
 | |
|             p.openElements.shortenToLength(i);
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 |