2957 lines
		
	
	
		
			91 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2957 lines
		
	
	
		
			91 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict';
 | |
| 
 | |
| const Tokenizer = require('../tokenizer');
 | |
| const OpenElementStack = require('./open-element-stack');
 | |
| const FormattingElementList = require('./formatting-element-list');
 | |
| const LocationInfoParserMixin = require('../extensions/location-info/parser-mixin');
 | |
| const ErrorReportingParserMixin = require('../extensions/error-reporting/parser-mixin');
 | |
| const Mixin = require('../utils/mixin');
 | |
| const defaultTreeAdapter = require('../tree-adapters/default');
 | |
| const mergeOptions = require('../utils/merge-options');
 | |
| const doctype = require('../common/doctype');
 | |
| const foreignContent = require('../common/foreign-content');
 | |
| const ERR = require('../common/error-codes');
 | |
| const unicode = require('../common/unicode');
 | |
| const HTML = require('../common/html');
 | |
| 
 | |
| //Aliases
 | |
| const $ = HTML.TAG_NAMES;
 | |
| const NS = HTML.NAMESPACES;
 | |
| const ATTRS = HTML.ATTRS;
 | |
| 
 | |
| const DEFAULT_OPTIONS = {
 | |
|     scriptingEnabled: true,
 | |
|     sourceCodeLocationInfo: false,
 | |
|     onParseError: null,
 | |
|     treeAdapter: defaultTreeAdapter
 | |
| };
 | |
| 
 | |
| //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
 | |
| const INITIAL_MODE = 'INITIAL_MODE';
 | |
| const BEFORE_HTML_MODE = 'BEFORE_HTML_MODE';
 | |
| const BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE';
 | |
| const IN_HEAD_MODE = 'IN_HEAD_MODE';
 | |
| const IN_HEAD_NO_SCRIPT_MODE = 'IN_HEAD_NO_SCRIPT_MODE';
 | |
| const AFTER_HEAD_MODE = 'AFTER_HEAD_MODE';
 | |
| const IN_BODY_MODE = 'IN_BODY_MODE';
 | |
| const TEXT_MODE = 'TEXT_MODE';
 | |
| const IN_TABLE_MODE = 'IN_TABLE_MODE';
 | |
| const IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE';
 | |
| const IN_CAPTION_MODE = 'IN_CAPTION_MODE';
 | |
| const IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE';
 | |
| const IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE';
 | |
| const IN_ROW_MODE = 'IN_ROW_MODE';
 | |
| const IN_CELL_MODE = 'IN_CELL_MODE';
 | |
| const IN_SELECT_MODE = 'IN_SELECT_MODE';
 | |
| const IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE';
 | |
| const IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE';
 | |
| const AFTER_BODY_MODE = 'AFTER_BODY_MODE';
 | |
| const IN_FRAMESET_MODE = 'IN_FRAMESET_MODE';
 | |
| const AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE';
 | |
| const AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE';
 | |
| const AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE';
 | |
| 
 | |
| //Insertion mode reset map
 | |
| const INSERTION_MODE_RESET_MAP = {
 | |
|     [$.TR]: IN_ROW_MODE,
 | |
|     [$.TBODY]: IN_TABLE_BODY_MODE,
 | |
|     [$.THEAD]: IN_TABLE_BODY_MODE,
 | |
|     [$.TFOOT]: IN_TABLE_BODY_MODE,
 | |
|     [$.CAPTION]: IN_CAPTION_MODE,
 | |
|     [$.COLGROUP]: IN_COLUMN_GROUP_MODE,
 | |
|     [$.TABLE]: IN_TABLE_MODE,
 | |
|     [$.BODY]: IN_BODY_MODE,
 | |
|     [$.FRAMESET]: IN_FRAMESET_MODE
 | |
| };
 | |
| 
 | |
| //Template insertion mode switch map
 | |
| const TEMPLATE_INSERTION_MODE_SWITCH_MAP = {
 | |
|     [$.CAPTION]: IN_TABLE_MODE,
 | |
|     [$.COLGROUP]: IN_TABLE_MODE,
 | |
|     [$.TBODY]: IN_TABLE_MODE,
 | |
|     [$.TFOOT]: IN_TABLE_MODE,
 | |
|     [$.THEAD]: IN_TABLE_MODE,
 | |
|     [$.COL]: IN_COLUMN_GROUP_MODE,
 | |
|     [$.TR]: IN_TABLE_BODY_MODE,
 | |
|     [$.TD]: IN_ROW_MODE,
 | |
|     [$.TH]: IN_ROW_MODE
 | |
| };
 | |
| 
 | |
| //Token handlers map for insertion modes
 | |
| const TOKEN_HANDLERS = {
 | |
|     [INITIAL_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenInInitialMode,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInInitialMode,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: doctypeInInitialMode,
 | |
|         [Tokenizer.START_TAG_TOKEN]: tokenInInitialMode,
 | |
|         [Tokenizer.END_TAG_TOKEN]: tokenInInitialMode,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenInInitialMode
 | |
|     },
 | |
|     [BEFORE_HTML_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHtml,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHtml,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagBeforeHtml,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagBeforeHtml,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenBeforeHtml
 | |
|     },
 | |
|     [BEFORE_HEAD_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHead,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHead,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagBeforeHead,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagBeforeHead,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenBeforeHead
 | |
|     },
 | |
|     [IN_HEAD_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenInHead,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHead,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInHead,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInHead,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenInHead
 | |
|     },
 | |
|     [IN_HEAD_NO_SCRIPT_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenInHeadNoScript,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHeadNoScript,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInHeadNoScript,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInHeadNoScript,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenInHeadNoScript
 | |
|     },
 | |
|     [AFTER_HEAD_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenAfterHead,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterHead,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagAfterHead,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagAfterHead,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenAfterHead
 | |
|     },
 | |
|     [IN_BODY_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInBody,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInBody,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInBody,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [TEXT_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInText,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInText
 | |
|     },
 | |
|     [IN_TABLE_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInTable,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInTable,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_TABLE_TEXT_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInTableText,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInTableText,
 | |
|         [Tokenizer.COMMENT_TOKEN]: tokenInTableText,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: tokenInTableText,
 | |
|         [Tokenizer.START_TAG_TOKEN]: tokenInTableText,
 | |
|         [Tokenizer.END_TAG_TOKEN]: tokenInTableText,
 | |
|         [Tokenizer.EOF_TOKEN]: tokenInTableText
 | |
|     },
 | |
|     [IN_CAPTION_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInBody,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInCaption,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInCaption,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_COLUMN_GROUP_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenInColumnGroup,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInColumnGroup,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInColumnGroup,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInColumnGroup,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_TABLE_BODY_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInTableBody,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInTableBody,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_ROW_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInRow,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInRow,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_CELL_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInBody,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInCell,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInCell,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_SELECT_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInSelect,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInSelect,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_SELECT_IN_TABLE_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInSelectInTable,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInSelectInTable,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInBody
 | |
|     },
 | |
|     [IN_TEMPLATE_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: characterInBody,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInTemplate,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInTemplate,
 | |
|         [Tokenizer.EOF_TOKEN]: eofInTemplate
 | |
|     },
 | |
|     [AFTER_BODY_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenAfterBody,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterBody,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendCommentToRootHtmlElement,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagAfterBody,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagAfterBody,
 | |
|         [Tokenizer.EOF_TOKEN]: stopParsing
 | |
|     },
 | |
|     [IN_FRAMESET_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagInFrameset,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagInFrameset,
 | |
|         [Tokenizer.EOF_TOKEN]: stopParsing
 | |
|     },
 | |
|     [AFTER_FRAMESET_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendComment,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagAfterFrameset,
 | |
|         [Tokenizer.END_TAG_TOKEN]: endTagAfterFrameset,
 | |
|         [Tokenizer.EOF_TOKEN]: stopParsing
 | |
|     },
 | |
|     [AFTER_AFTER_BODY_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: tokenAfterAfterBody,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterAfterBody,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterBody,
 | |
|         [Tokenizer.END_TAG_TOKEN]: tokenAfterAfterBody,
 | |
|         [Tokenizer.EOF_TOKEN]: stopParsing
 | |
|     },
 | |
|     [AFTER_AFTER_FRAMESET_MODE]: {
 | |
|         [Tokenizer.CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
 | |
|         [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument,
 | |
|         [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterFrameset,
 | |
|         [Tokenizer.END_TAG_TOKEN]: ignoreToken,
 | |
|         [Tokenizer.EOF_TOKEN]: stopParsing
 | |
|     }
 | |
| };
 | |
| 
 | |
| //Parser
 | |
| class Parser {
 | |
|     constructor(options) {
 | |
|         this.options = mergeOptions(DEFAULT_OPTIONS, options);
 | |
| 
 | |
|         this.treeAdapter = this.options.treeAdapter;
 | |
|         this.pendingScript = null;
 | |
| 
 | |
|         if (this.options.sourceCodeLocationInfo) {
 | |
|             Mixin.install(this, LocationInfoParserMixin);
 | |
|         }
 | |
| 
 | |
|         if (this.options.onParseError) {
 | |
|             Mixin.install(this, ErrorReportingParserMixin, { onParseError: this.options.onParseError });
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // API
 | |
|     parse(html) {
 | |
|         const document = this.treeAdapter.createDocument();
 | |
| 
 | |
|         this._bootstrap(document, null);
 | |
|         this.tokenizer.write(html, true);
 | |
|         this._runParsingLoop(null);
 | |
| 
 | |
|         return document;
 | |
|     }
 | |
| 
 | |
|     parseFragment(html, fragmentContext) {
 | |
|         //NOTE: use <template> element as a fragment context if context element was not provided,
 | |
|         //so we will parse in "forgiving" manner
 | |
|         if (!fragmentContext) {
 | |
|             fragmentContext = this.treeAdapter.createElement($.TEMPLATE, NS.HTML, []);
 | |
|         }
 | |
| 
 | |
|         //NOTE: create fake element which will be used as 'document' for fragment parsing.
 | |
|         //This is important for jsdom there 'document' can't be recreated, therefore
 | |
|         //fragment parsing causes messing of the main `document`.
 | |
|         const documentMock = this.treeAdapter.createElement('documentmock', NS.HTML, []);
 | |
| 
 | |
|         this._bootstrap(documentMock, fragmentContext);
 | |
| 
 | |
|         if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE) {
 | |
|             this._pushTmplInsertionMode(IN_TEMPLATE_MODE);
 | |
|         }
 | |
| 
 | |
|         this._initTokenizerForFragmentParsing();
 | |
|         this._insertFakeRootElement();
 | |
|         this._resetInsertionMode();
 | |
|         this._findFormInFragmentContext();
 | |
|         this.tokenizer.write(html, true);
 | |
|         this._runParsingLoop(null);
 | |
| 
 | |
|         const rootElement = this.treeAdapter.getFirstChild(documentMock);
 | |
|         const fragment = this.treeAdapter.createDocumentFragment();
 | |
| 
 | |
|         this._adoptNodes(rootElement, fragment);
 | |
| 
 | |
|         return fragment;
 | |
|     }
 | |
| 
 | |
|     //Bootstrap parser
 | |
|     _bootstrap(document, fragmentContext) {
 | |
|         this.tokenizer = new Tokenizer(this.options);
 | |
| 
 | |
|         this.stopped = false;
 | |
| 
 | |
|         this.insertionMode = INITIAL_MODE;
 | |
|         this.originalInsertionMode = '';
 | |
| 
 | |
|         this.document = document;
 | |
|         this.fragmentContext = fragmentContext;
 | |
| 
 | |
|         this.headElement = null;
 | |
|         this.formElement = null;
 | |
| 
 | |
|         this.openElements = new OpenElementStack(this.document, this.treeAdapter);
 | |
|         this.activeFormattingElements = new FormattingElementList(this.treeAdapter);
 | |
| 
 | |
|         this.tmplInsertionModeStack = [];
 | |
|         this.tmplInsertionModeStackTop = -1;
 | |
|         this.currentTmplInsertionMode = null;
 | |
| 
 | |
|         this.pendingCharacterTokens = [];
 | |
|         this.hasNonWhitespacePendingCharacterToken = false;
 | |
| 
 | |
|         this.framesetOk = true;
 | |
|         this.skipNextNewLine = false;
 | |
|         this.fosterParentingEnabled = false;
 | |
|     }
 | |
| 
 | |
|     //Errors
 | |
|     _err() {
 | |
|         // NOTE: err reporting is noop by default. Enabled by mixin.
 | |
|     }
 | |
| 
 | |
|     //Parsing loop
 | |
|     _runParsingLoop(scriptHandler) {
 | |
|         while (!this.stopped) {
 | |
|             this._setupTokenizerCDATAMode();
 | |
| 
 | |
|             const token = this.tokenizer.getNextToken();
 | |
| 
 | |
|             if (token.type === Tokenizer.HIBERNATION_TOKEN) {
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if (this.skipNextNewLine) {
 | |
|                 this.skipNextNewLine = false;
 | |
| 
 | |
|                 if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') {
 | |
|                     if (token.chars.length === 1) {
 | |
|                         continue;
 | |
|                     }
 | |
| 
 | |
|                     token.chars = token.chars.substr(1);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             this._processInputToken(token);
 | |
| 
 | |
|             if (scriptHandler && this.pendingScript) {
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     runParsingLoopForCurrentChunk(writeCallback, scriptHandler) {
 | |
|         this._runParsingLoop(scriptHandler);
 | |
| 
 | |
|         if (scriptHandler && this.pendingScript) {
 | |
|             const script = this.pendingScript;
 | |
| 
 | |
|             this.pendingScript = null;
 | |
| 
 | |
|             scriptHandler(script);
 | |
| 
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if (writeCallback) {
 | |
|             writeCallback();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //Text parsing
 | |
|     _setupTokenizerCDATAMode() {
 | |
|         const current = this._getAdjustedCurrentElement();
 | |
| 
 | |
|         this.tokenizer.allowCDATA =
 | |
|             current &&
 | |
|             current !== this.document &&
 | |
|             this.treeAdapter.getNamespaceURI(current) !== NS.HTML &&
 | |
|             !this._isIntegrationPoint(current);
 | |
|     }
 | |
| 
 | |
|     _switchToTextParsing(currentToken, nextTokenizerState) {
 | |
|         this._insertElement(currentToken, NS.HTML);
 | |
|         this.tokenizer.state = nextTokenizerState;
 | |
|         this.originalInsertionMode = this.insertionMode;
 | |
|         this.insertionMode = TEXT_MODE;
 | |
|     }
 | |
| 
 | |
|     switchToPlaintextParsing() {
 | |
|         this.insertionMode = TEXT_MODE;
 | |
|         this.originalInsertionMode = IN_BODY_MODE;
 | |
|         this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
 | |
|     }
 | |
| 
 | |
|     //Fragment parsing
 | |
|     _getAdjustedCurrentElement() {
 | |
|         return this.openElements.stackTop === 0 && this.fragmentContext
 | |
|             ? this.fragmentContext
 | |
|             : this.openElements.current;
 | |
|     }
 | |
| 
 | |
|     _findFormInFragmentContext() {
 | |
|         let node = this.fragmentContext;
 | |
| 
 | |
|         do {
 | |
|             if (this.treeAdapter.getTagName(node) === $.FORM) {
 | |
|                 this.formElement = node;
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             node = this.treeAdapter.getParentNode(node);
 | |
|         } while (node);
 | |
|     }
 | |
| 
 | |
|     _initTokenizerForFragmentParsing() {
 | |
|         if (this.treeAdapter.getNamespaceURI(this.fragmentContext) === NS.HTML) {
 | |
|             const tn = this.treeAdapter.getTagName(this.fragmentContext);
 | |
| 
 | |
|             if (tn === $.TITLE || tn === $.TEXTAREA) {
 | |
|                 this.tokenizer.state = Tokenizer.MODE.RCDATA;
 | |
|             } else if (
 | |
|                 tn === $.STYLE ||
 | |
|                 tn === $.XMP ||
 | |
|                 tn === $.IFRAME ||
 | |
|                 tn === $.NOEMBED ||
 | |
|                 tn === $.NOFRAMES ||
 | |
|                 tn === $.NOSCRIPT
 | |
|             ) {
 | |
|                 this.tokenizer.state = Tokenizer.MODE.RAWTEXT;
 | |
|             } else if (tn === $.SCRIPT) {
 | |
|                 this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA;
 | |
|             } else if (tn === $.PLAINTEXT) {
 | |
|                 this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //Tree mutation
 | |
|     _setDocumentType(token) {
 | |
|         const name = token.name || '';
 | |
|         const publicId = token.publicId || '';
 | |
|         const systemId = token.systemId || '';
 | |
| 
 | |
|         this.treeAdapter.setDocumentType(this.document, name, publicId, systemId);
 | |
|     }
 | |
| 
 | |
|     _attachElementToTree(element) {
 | |
|         if (this._shouldFosterParentOnInsertion()) {
 | |
|             this._fosterParentElement(element);
 | |
|         } else {
 | |
|             const parent = this.openElements.currentTmplContent || this.openElements.current;
 | |
| 
 | |
|             this.treeAdapter.appendChild(parent, element);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _appendElement(token, namespaceURI) {
 | |
|         const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
 | |
| 
 | |
|         this._attachElementToTree(element);
 | |
|     }
 | |
| 
 | |
|     _insertElement(token, namespaceURI) {
 | |
|         const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
 | |
| 
 | |
|         this._attachElementToTree(element);
 | |
|         this.openElements.push(element);
 | |
|     }
 | |
| 
 | |
|     _insertFakeElement(tagName) {
 | |
|         const element = this.treeAdapter.createElement(tagName, NS.HTML, []);
 | |
| 
 | |
|         this._attachElementToTree(element);
 | |
|         this.openElements.push(element);
 | |
|     }
 | |
| 
 | |
|     _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);
 | |
|         this.openElements.push(tmpl);
 | |
|     }
 | |
| 
 | |
|     _insertFakeRootElement() {
 | |
|         const element = this.treeAdapter.createElement($.HTML, NS.HTML, []);
 | |
| 
 | |
|         this.treeAdapter.appendChild(this.openElements.current, element);
 | |
|         this.openElements.push(element);
 | |
|     }
 | |
| 
 | |
|     _appendCommentNode(token, parent) {
 | |
|         const commentNode = this.treeAdapter.createCommentNode(token.data);
 | |
| 
 | |
|         this.treeAdapter.appendChild(parent, commentNode);
 | |
|     }
 | |
| 
 | |
|     _insertCharacters(token) {
 | |
|         if (this._shouldFosterParentOnInsertion()) {
 | |
|             this._fosterParentText(token.chars);
 | |
|         } else {
 | |
|             const parent = this.openElements.currentTmplContent || this.openElements.current;
 | |
| 
 | |
|             this.treeAdapter.insertText(parent, token.chars);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _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);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //Token processing
 | |
|     _shouldProcessTokenInForeignContent(token) {
 | |
|         const current = this._getAdjustedCurrentElement();
 | |
| 
 | |
|         if (!current || current === this.document) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         const ns = this.treeAdapter.getNamespaceURI(current);
 | |
| 
 | |
|         if (ns === NS.HTML) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         if (
 | |
|             this.treeAdapter.getTagName(current) === $.ANNOTATION_XML &&
 | |
|             ns === NS.MATHML &&
 | |
|             token.type === Tokenizer.START_TAG_TOKEN &&
 | |
|             token.tagName === $.SVG
 | |
|         ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         const isCharacterToken =
 | |
|             token.type === Tokenizer.CHARACTER_TOKEN ||
 | |
|             token.type === Tokenizer.NULL_CHARACTER_TOKEN ||
 | |
|             token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN;
 | |
| 
 | |
|         const isMathMLTextStartTag =
 | |
|             token.type === Tokenizer.START_TAG_TOKEN && token.tagName !== $.MGLYPH && token.tagName !== $.MALIGNMARK;
 | |
| 
 | |
|         if ((isMathMLTextStartTag || isCharacterToken) && this._isIntegrationPoint(current, NS.MATHML)) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         if (
 | |
|             (token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) &&
 | |
|             this._isIntegrationPoint(current, NS.HTML)
 | |
|         ) {
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         return token.type !== Tokenizer.EOF_TOKEN;
 | |
|     }
 | |
| 
 | |
|     _processToken(token) {
 | |
|         TOKEN_HANDLERS[this.insertionMode][token.type](this, token);
 | |
|     }
 | |
| 
 | |
|     _processTokenInBodyMode(token) {
 | |
|         TOKEN_HANDLERS[IN_BODY_MODE][token.type](this, token);
 | |
|     }
 | |
| 
 | |
|     _processTokenInForeignContent(token) {
 | |
|         if (token.type === Tokenizer.CHARACTER_TOKEN) {
 | |
|             characterInForeignContent(this, token);
 | |
|         } else if (token.type === Tokenizer.NULL_CHARACTER_TOKEN) {
 | |
|             nullCharacterInForeignContent(this, token);
 | |
|         } else if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN) {
 | |
|             insertCharacters(this, token);
 | |
|         } else if (token.type === Tokenizer.COMMENT_TOKEN) {
 | |
|             appendComment(this, token);
 | |
|         } else if (token.type === Tokenizer.START_TAG_TOKEN) {
 | |
|             startTagInForeignContent(this, token);
 | |
|         } else if (token.type === Tokenizer.END_TAG_TOKEN) {
 | |
|             endTagInForeignContent(this, token);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _processInputToken(token) {
 | |
|         if (this._shouldProcessTokenInForeignContent(token)) {
 | |
|             this._processTokenInForeignContent(token);
 | |
|         } else {
 | |
|             this._processToken(token);
 | |
|         }
 | |
| 
 | |
|         if (token.type === Tokenizer.START_TAG_TOKEN && token.selfClosing && !token.ackSelfClosing) {
 | |
|             this._err(ERR.nonVoidHtmlElementStartTagWithTrailingSolidus);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //Integration points
 | |
|     _isIntegrationPoint(element, foreignNS) {
 | |
|         const tn = this.treeAdapter.getTagName(element);
 | |
|         const ns = this.treeAdapter.getNamespaceURI(element);
 | |
|         const attrs = this.treeAdapter.getAttrList(element);
 | |
| 
 | |
|         return foreignContent.isIntegrationPoint(tn, ns, attrs, foreignNS);
 | |
|     }
 | |
| 
 | |
|     //Active formatting elements reconstruction
 | |
|     _reconstructActiveFormattingElements() {
 | |
|         const listLength = this.activeFormattingElements.length;
 | |
| 
 | |
|         if (listLength) {
 | |
|             let unopenIdx = listLength;
 | |
|             let entry = null;
 | |
| 
 | |
|             do {
 | |
|                 unopenIdx--;
 | |
|                 entry = this.activeFormattingElements.entries[unopenIdx];
 | |
| 
 | |
|                 if (entry.type === FormattingElementList.MARKER_ENTRY || this.openElements.contains(entry.element)) {
 | |
|                     unopenIdx++;
 | |
|                     break;
 | |
|                 }
 | |
|             } while (unopenIdx > 0);
 | |
| 
 | |
|             for (let i = unopenIdx; i < listLength; i++) {
 | |
|                 entry = this.activeFormattingElements.entries[i];
 | |
|                 this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element));
 | |
|                 entry.element = this.openElements.current;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //Close elements
 | |
|     _closeTableCell() {
 | |
|         this.openElements.generateImpliedEndTags();
 | |
|         this.openElements.popUntilTableCellPopped();
 | |
|         this.activeFormattingElements.clearToLastMarker();
 | |
|         this.insertionMode = IN_ROW_MODE;
 | |
|     }
 | |
| 
 | |
|     _closePElement() {
 | |
|         this.openElements.generateImpliedEndTagsWithExclusion($.P);
 | |
|         this.openElements.popUntilTagNamePopped($.P);
 | |
|     }
 | |
| 
 | |
|     //Insertion modes
 | |
|     _resetInsertionMode() {
 | |
|         for (let i = this.openElements.stackTop, last = false; i >= 0; i--) {
 | |
|             let element = this.openElements.items[i];
 | |
| 
 | |
|             if (i === 0) {
 | |
|                 last = true;
 | |
| 
 | |
|                 if (this.fragmentContext) {
 | |
|                     element = this.fragmentContext;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             const tn = this.treeAdapter.getTagName(element);
 | |
|             const newInsertionMode = INSERTION_MODE_RESET_MAP[tn];
 | |
| 
 | |
|             if (newInsertionMode) {
 | |
|                 this.insertionMode = newInsertionMode;
 | |
|                 break;
 | |
|             } else if (!last && (tn === $.TD || tn === $.TH)) {
 | |
|                 this.insertionMode = IN_CELL_MODE;
 | |
|                 break;
 | |
|             } else if (!last && tn === $.HEAD) {
 | |
|                 this.insertionMode = IN_HEAD_MODE;
 | |
|                 break;
 | |
|             } else if (tn === $.SELECT) {
 | |
|                 this._resetInsertionModeForSelect(i);
 | |
|                 break;
 | |
|             } else if (tn === $.TEMPLATE) {
 | |
|                 this.insertionMode = this.currentTmplInsertionMode;
 | |
|                 break;
 | |
|             } else if (tn === $.HTML) {
 | |
|                 this.insertionMode = this.headElement ? AFTER_HEAD_MODE : BEFORE_HEAD_MODE;
 | |
|                 break;
 | |
|             } else if (last) {
 | |
|                 this.insertionMode = IN_BODY_MODE;
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _resetInsertionModeForSelect(selectIdx) {
 | |
|         if (selectIdx > 0) {
 | |
|             for (let i = selectIdx - 1; i > 0; i--) {
 | |
|                 const ancestor = this.openElements.items[i];
 | |
|                 const tn = this.treeAdapter.getTagName(ancestor);
 | |
| 
 | |
|                 if (tn === $.TEMPLATE) {
 | |
|                     break;
 | |
|                 } else if (tn === $.TABLE) {
 | |
|                     this.insertionMode = IN_SELECT_IN_TABLE_MODE;
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         this.insertionMode = IN_SELECT_MODE;
 | |
|     }
 | |
| 
 | |
|     _pushTmplInsertionMode(mode) {
 | |
|         this.tmplInsertionModeStack.push(mode);
 | |
|         this.tmplInsertionModeStackTop++;
 | |
|         this.currentTmplInsertionMode = mode;
 | |
|     }
 | |
| 
 | |
|     _popTmplInsertionMode() {
 | |
|         this.tmplInsertionModeStack.pop();
 | |
|         this.tmplInsertionModeStackTop--;
 | |
|         this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop];
 | |
|     }
 | |
| 
 | |
|     //Foster parenting
 | |
|     _isElementCausesFosterParenting(element) {
 | |
|         const tn = this.treeAdapter.getTagName(element);
 | |
| 
 | |
|         return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR;
 | |
|     }
 | |
| 
 | |
|     _shouldFosterParentOnInsertion() {
 | |
|         return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current);
 | |
|     }
 | |
| 
 | |
|     _findFosterParentingLocation() {
 | |
|         const location = {
 | |
|             parent: null,
 | |
|             beforeElement: null
 | |
|         };
 | |
| 
 | |
|         for (let i = this.openElements.stackTop; i >= 0; i--) {
 | |
|             const openElement = this.openElements.items[i];
 | |
|             const tn = this.treeAdapter.getTagName(openElement);
 | |
|             const ns = this.treeAdapter.getNamespaceURI(openElement);
 | |
| 
 | |
|             if (tn === $.TEMPLATE && ns === NS.HTML) {
 | |
|                 location.parent = this.treeAdapter.getTemplateContent(openElement);
 | |
|                 break;
 | |
|             } else if (tn === $.TABLE) {
 | |
|                 location.parent = this.treeAdapter.getParentNode(openElement);
 | |
| 
 | |
|                 if (location.parent) {
 | |
|                     location.beforeElement = openElement;
 | |
|                 } else {
 | |
|                     location.parent = this.openElements.items[i - 1];
 | |
|                 }
 | |
| 
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         if (!location.parent) {
 | |
|             location.parent = this.openElements.items[0];
 | |
|         }
 | |
| 
 | |
|         return location;
 | |
|     }
 | |
| 
 | |
|     _fosterParentElement(element) {
 | |
|         const location = this._findFosterParentingLocation();
 | |
| 
 | |
|         if (location.beforeElement) {
 | |
|             this.treeAdapter.insertBefore(location.parent, element, location.beforeElement);
 | |
|         } else {
 | |
|             this.treeAdapter.appendChild(location.parent, element);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     _fosterParentText(chars) {
 | |
|         const location = this._findFosterParentingLocation();
 | |
| 
 | |
|         if (location.beforeElement) {
 | |
|             this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement);
 | |
|         } else {
 | |
|             this.treeAdapter.insertText(location.parent, chars);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     //Special elements
 | |
|     _isSpecialElement(element) {
 | |
|         const tn = this.treeAdapter.getTagName(element);
 | |
|         const ns = this.treeAdapter.getNamespaceURI(element);
 | |
| 
 | |
|         return HTML.SPECIAL_ELEMENTS[ns][tn];
 | |
|     }
 | |
| }
 | |
| 
 | |
| module.exports = Parser;
 | |
| 
 | |
| //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.tagName)) {
 | |
|             formattingElementEntry = null;
 | |
|         }
 | |
|     } else {
 | |
|         genericEndTagInBody(p, token);
 | |
|     }
 | |
| 
 | |
|     return formattingElementEntry;
 | |
| }
 | |
| 
 | |
| //Steps 9 and 10 of the algorithm
 | |
| function aaObtainFurthestBlock(p, formattingElementEntry) {
 | |
|     let furthestBlock = null;
 | |
| 
 | |
|     for (let i = p.openElements.stackTop; i >= 0; i--) {
 | |
|         const element = p.openElements.items[i];
 | |
| 
 | |
|         if (element === formattingElementEntry.element) {
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         if (p._isSpecialElement(element)) {
 | |
|             furthestBlock = element;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!furthestBlock) {
 | |
|         p.openElements.popUntilElementPopped(formattingElementEntry.element);
 | |
|         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 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) {
 | |
|     if (p._isElementCausesFosterParenting(commonAncestor)) {
 | |
|         p._fosterParentElement(lastElement);
 | |
|     } else {
 | |
|         const tn = p.treeAdapter.getTagName(commonAncestor);
 | |
|         const ns = p.treeAdapter.getNamespaceURI(commonAncestor);
 | |
| 
 | |
|         if (tn === $.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.token;
 | |
|     const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs);
 | |
| 
 | |
|     p._adoptNodes(furthestBlock, newElement);
 | |
|     p.treeAdapter.appendChild(furthestBlock, newElement);
 | |
| 
 | |
|     p.activeFormattingElements.insertElementAfterBookmark(newElement, formattingElementEntry.token);
 | |
|     p.activeFormattingElements.removeEntry(formattingElementEntry);
 | |
| 
 | |
|     p.openElements.remove(formattingElementEntry.element);
 | |
|     p.openElements.insertAfter(furthestBlock, newElement);
 | |
| }
 | |
| 
 | |
| //Algorithm entry point
 | |
| function callAdoptionAgency(p, token) {
 | |
|     let formattingElementEntry;
 | |
| 
 | |
|     for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) {
 | |
|         formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry);
 | |
| 
 | |
|         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);
 | |
|         aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement);
 | |
|         aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry);
 | |
|     }
 | |
| }
 | |
| 
 | |
| //Generic token handlers
 | |
| //------------------------------------------------------------------
 | |
| function ignoreToken() {
 | |
|     //NOTE: do nothing =)
 | |
| }
 | |
| 
 | |
| function misplacedDoctype(p) {
 | |
|     p._err(ERR.misplacedDoctype);
 | |
| }
 | |
| 
 | |
| function appendComment(p, token) {
 | |
|     p._appendCommentNode(token, p.openElements.currentTmplContent || p.openElements.current);
 | |
| }
 | |
| 
 | |
| function appendCommentToRootHtmlElement(p, token) {
 | |
|     p._appendCommentNode(token, p.openElements.items[0]);
 | |
| }
 | |
| 
 | |
| function appendCommentToDocument(p, token) {
 | |
|     p._appendCommentNode(token, p.document);
 | |
| }
 | |
| 
 | |
| function insertCharacters(p, token) {
 | |
|     p._insertCharacters(token);
 | |
| }
 | |
| 
 | |
| function stopParsing(p) {
 | |
|     p.stopped = true;
 | |
| }
 | |
| 
 | |
| // The "initial" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function doctypeInInitialMode(p, token) {
 | |
|     p._setDocumentType(token);
 | |
| 
 | |
|     const mode = token.forceQuirks ? HTML.DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token);
 | |
| 
 | |
|     if (!doctype.isConforming(token)) {
 | |
|         p._err(ERR.nonConformingDoctype);
 | |
|     }
 | |
| 
 | |
|     p.treeAdapter.setDocumentMode(p.document, mode);
 | |
| 
 | |
|     p.insertionMode = BEFORE_HTML_MODE;
 | |
| }
 | |
| 
 | |
| function tokenInInitialMode(p, token) {
 | |
|     p._err(ERR.missingDoctype, { beforeToken: true });
 | |
|     p.treeAdapter.setDocumentMode(p.document, HTML.DOCUMENT_MODE.QUIRKS);
 | |
|     p.insertionMode = BEFORE_HTML_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "before html" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagBeforeHtml(p, token) {
 | |
|     if (token.tagName === $.HTML) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.insertionMode = BEFORE_HEAD_MODE;
 | |
|     } else {
 | |
|         tokenBeforeHtml(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagBeforeHtml(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) {
 | |
|         tokenBeforeHtml(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenBeforeHtml(p, token) {
 | |
|     p._insertFakeRootElement();
 | |
|     p.insertionMode = BEFORE_HEAD_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "before head" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagBeforeHead(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.HEAD) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.headElement = p.openElements.current;
 | |
|         p.insertionMode = IN_HEAD_MODE;
 | |
|     } else {
 | |
|         tokenBeforeHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagBeforeHead(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) {
 | |
|         tokenBeforeHead(p, token);
 | |
|     } else {
 | |
|         p._err(ERR.endTagWithoutMatchingOpenElement);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenBeforeHead(p, token) {
 | |
|     p._insertFakeElement($.HEAD);
 | |
|     p.headElement = p.openElements.current;
 | |
|     p.insertionMode = IN_HEAD_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "in head" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInHead(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META) {
 | |
|         p._appendElement(token, NS.HTML);
 | |
|         token.ackSelfClosing = true;
 | |
|     } else if (tn === $.TITLE) {
 | |
|         p._switchToTextParsing(token, Tokenizer.MODE.RCDATA);
 | |
|     } else if (tn === $.NOSCRIPT) {
 | |
|         if (p.options.scriptingEnabled) {
 | |
|             p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
 | |
|         } else {
 | |
|             p._insertElement(token, NS.HTML);
 | |
|             p.insertionMode = IN_HEAD_NO_SCRIPT_MODE;
 | |
|         }
 | |
|     } else if (tn === $.NOFRAMES || tn === $.STYLE) {
 | |
|         p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
 | |
|     } else if (tn === $.SCRIPT) {
 | |
|         p._switchToTextParsing(token, Tokenizer.MODE.SCRIPT_DATA);
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         p._insertTemplate(token, NS.HTML);
 | |
|         p.activeFormattingElements.insertMarker();
 | |
|         p.framesetOk = false;
 | |
|         p.insertionMode = IN_TEMPLATE_MODE;
 | |
|         p._pushTmplInsertionMode(IN_TEMPLATE_MODE);
 | |
|     } else if (tn === $.HEAD) {
 | |
|         p._err(ERR.misplacedStartTagForHeadElement);
 | |
|     } else {
 | |
|         tokenInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInHead(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HEAD) {
 | |
|         p.openElements.pop();
 | |
|         p.insertionMode = AFTER_HEAD_MODE;
 | |
|     } else if (tn === $.BODY || tn === $.BR || tn === $.HTML) {
 | |
|         tokenInHead(p, token);
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         if (p.openElements.tmplCount > 0) {
 | |
|             p.openElements.generateImpliedEndTagsThoroughly();
 | |
| 
 | |
|             if (p.openElements.currentTagName !== $.TEMPLATE) {
 | |
|                 p._err(ERR.closingOfElementWithOpenChildElements);
 | |
|             }
 | |
| 
 | |
|             p.openElements.popUntilTagNamePopped($.TEMPLATE);
 | |
|             p.activeFormattingElements.clearToLastMarker();
 | |
|             p._popTmplInsertionMode();
 | |
|             p._resetInsertionMode();
 | |
|         } else {
 | |
|             p._err(ERR.endTagWithoutMatchingOpenElement);
 | |
|         }
 | |
|     } else {
 | |
|         p._err(ERR.endTagWithoutMatchingOpenElement);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenInHead(p, token) {
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = AFTER_HEAD_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "in head no script" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInHeadNoScript(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (
 | |
|         tn === $.BASEFONT ||
 | |
|         tn === $.BGSOUND ||
 | |
|         tn === $.HEAD ||
 | |
|         tn === $.LINK ||
 | |
|         tn === $.META ||
 | |
|         tn === $.NOFRAMES ||
 | |
|         tn === $.STYLE
 | |
|     ) {
 | |
|         startTagInHead(p, token);
 | |
|     } else if (tn === $.NOSCRIPT) {
 | |
|         p._err(ERR.nestedNoscriptInHead);
 | |
|     } else {
 | |
|         tokenInHeadNoScript(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInHeadNoScript(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.NOSCRIPT) {
 | |
|         p.openElements.pop();
 | |
|         p.insertionMode = IN_HEAD_MODE;
 | |
|     } else if (tn === $.BR) {
 | |
|         tokenInHeadNoScript(p, token);
 | |
|     } else {
 | |
|         p._err(ERR.endTagWithoutMatchingOpenElement);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenInHeadNoScript(p, token) {
 | |
|     const errCode =
 | |
|         token.type === Tokenizer.EOF_TOKEN ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead;
 | |
| 
 | |
|     p._err(errCode);
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = IN_HEAD_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "after head" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterHead(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.BODY) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.framesetOk = false;
 | |
|         p.insertionMode = IN_BODY_MODE;
 | |
|     } else if (tn === $.FRAMESET) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.insertionMode = IN_FRAMESET_MODE;
 | |
|     } else if (
 | |
|         tn === $.BASE ||
 | |
|         tn === $.BASEFONT ||
 | |
|         tn === $.BGSOUND ||
 | |
|         tn === $.LINK ||
 | |
|         tn === $.META ||
 | |
|         tn === $.NOFRAMES ||
 | |
|         tn === $.SCRIPT ||
 | |
|         tn === $.STYLE ||
 | |
|         tn === $.TEMPLATE ||
 | |
|         tn === $.TITLE
 | |
|     ) {
 | |
|         p._err(ERR.abandonedHeadElementChild);
 | |
|         p.openElements.push(p.headElement);
 | |
|         startTagInHead(p, token);
 | |
|         p.openElements.remove(p.headElement);
 | |
|     } else if (tn === $.HEAD) {
 | |
|         p._err(ERR.misplacedStartTagForHeadElement);
 | |
|     } else {
 | |
|         tokenAfterHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagAfterHead(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.BODY || tn === $.HTML || tn === $.BR) {
 | |
|         tokenAfterHead(p, token);
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         endTagInHead(p, token);
 | |
|     } else {
 | |
|         p._err(ERR.endTagWithoutMatchingOpenElement);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenAfterHead(p, token) {
 | |
|     p._insertFakeElement($.BODY);
 | |
|     p.insertionMode = IN_BODY_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "in body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| 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 = IN_FRAMESET_MODE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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();
 | |
|     }
 | |
| 
 | |
|     const tn = p.openElements.currentTagName;
 | |
| 
 | |
|     if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
 | |
|         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.tagName;
 | |
| 
 | |
|     for (let i = p.openElements.stackTop; i >= 0; i--) {
 | |
|         const element = p.openElements.items[i];
 | |
|         const elementTn = p.treeAdapter.getTagName(element);
 | |
|         let closeTn = null;
 | |
| 
 | |
|         if (tn === $.LI && elementTn === $.LI) {
 | |
|             closeTn = $.LI;
 | |
|         } else if ((tn === $.DD || tn === $.DT) && (elementTn === $.DD || elementTn === $.DT)) {
 | |
|             closeTn = elementTn;
 | |
|         }
 | |
| 
 | |
|         if (closeTn) {
 | |
|             p.openElements.generateImpliedEndTagsWithExclusion(closeTn);
 | |
|             p.openElements.popUntilTagNamePopped(closeTn);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         if (elementTn !== $.ADDRESS && elementTn !== $.DIV && elementTn !== $.P && p._isSpecialElement(element)) {
 | |
|             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 = Tokenizer.MODE.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($.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) !== HTML.DOCUMENT_MODE.QUIRKS &&
 | |
|         p.openElements.hasInButtonScope($.P)
 | |
|     ) {
 | |
|         p._closePElement();
 | |
|     }
 | |
| 
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
|     p.insertionMode = IN_TABLE_MODE;
 | |
| }
 | |
| 
 | |
| function areaStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._appendElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
|     token.ackSelfClosing = true;
 | |
| }
 | |
| 
 | |
| function inputStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._appendElement(token, NS.HTML);
 | |
| 
 | |
|     const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
 | |
| 
 | |
|     if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE) {
 | |
|         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;
 | |
|     p.ackSelfClosing = true;
 | |
| }
 | |
| 
 | |
| function imageStartTagInBody(p, token) {
 | |
|     token.tagName = $.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 = Tokenizer.MODE.RCDATA;
 | |
|     p.originalInsertionMode = p.insertionMode;
 | |
|     p.framesetOk = false;
 | |
|     p.insertionMode = TEXT_MODE;
 | |
| }
 | |
| 
 | |
| function xmpStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
| 
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p.framesetOk = false;
 | |
|     p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
 | |
| }
 | |
| 
 | |
| function iframeStartTagInBody(p, token) {
 | |
|     p.framesetOk = false;
 | |
|     p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
 | |
| }
 | |
| 
 | |
| //NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse
 | |
| //<noembed> as a rawtext.
 | |
| function noembedStartTagInBody(p, token) {
 | |
|     p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
 | |
| }
 | |
| 
 | |
| function selectStartTagInBody(p, token) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.framesetOk = false;
 | |
| 
 | |
|     if (
 | |
|         p.insertionMode === IN_TABLE_MODE ||
 | |
|         p.insertionMode === IN_CAPTION_MODE ||
 | |
|         p.insertionMode === IN_TABLE_BODY_MODE ||
 | |
|         p.insertionMode === IN_ROW_MODE ||
 | |
|         p.insertionMode === IN_CELL_MODE
 | |
|     ) {
 | |
|         p.insertionMode = IN_SELECT_IN_TABLE_MODE;
 | |
|     } else {
 | |
|         p.insertionMode = IN_SELECT_MODE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function optgroupStartTagInBody(p, token) {
 | |
|     if (p.openElements.currentTagName === $.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 menuStartTagInBody(p, token) {
 | |
|     if (p.openElements.hasInButtonScope($.P)) {
 | |
|         p._closePElement();
 | |
|     }
 | |
| 
 | |
|     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);
 | |
| }
 | |
| 
 | |
| //OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
 | |
| //It's faster than using dictionary.
 | |
| function startTagInBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     switch (tn.length) {
 | |
|         case 1:
 | |
|             if (tn === $.I || tn === $.S || tn === $.B || tn === $.U) {
 | |
|                 bStartTagInBody(p, token);
 | |
|             } else if (tn === $.P) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.A) {
 | |
|                 aStartTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 2:
 | |
|             if (tn === $.DL || tn === $.OL || tn === $.UL) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
 | |
|                 numberedHeaderStartTagInBody(p, token);
 | |
|             } else if (tn === $.LI || tn === $.DD || tn === $.DT) {
 | |
|                 listItemStartTagInBody(p, token);
 | |
|             } else if (tn === $.EM || tn === $.TT) {
 | |
|                 bStartTagInBody(p, token);
 | |
|             } else if (tn === $.BR) {
 | |
|                 areaStartTagInBody(p, token);
 | |
|             } else if (tn === $.HR) {
 | |
|                 hrStartTagInBody(p, token);
 | |
|             } else if (tn === $.RB) {
 | |
|                 rbStartTagInBody(p, token);
 | |
|             } else if (tn === $.RT || tn === $.RP) {
 | |
|                 rtStartTagInBody(p, token);
 | |
|             } else if (tn !== $.TH && tn !== $.TD && tn !== $.TR) {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 3:
 | |
|             if (tn === $.DIV || tn === $.DIR || tn === $.NAV) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.PRE) {
 | |
|                 preStartTagInBody(p, token);
 | |
|             } else if (tn === $.BIG) {
 | |
|                 bStartTagInBody(p, token);
 | |
|             } else if (tn === $.IMG || tn === $.WBR) {
 | |
|                 areaStartTagInBody(p, token);
 | |
|             } else if (tn === $.XMP) {
 | |
|                 xmpStartTagInBody(p, token);
 | |
|             } else if (tn === $.SVG) {
 | |
|                 svgStartTagInBody(p, token);
 | |
|             } else if (tn === $.RTC) {
 | |
|                 rbStartTagInBody(p, token);
 | |
|             } else if (tn !== $.COL) {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 4:
 | |
|             if (tn === $.HTML) {
 | |
|                 htmlStartTagInBody(p, token);
 | |
|             } else if (tn === $.BASE || tn === $.LINK || tn === $.META) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (tn === $.BODY) {
 | |
|                 bodyStartTagInBody(p, token);
 | |
|             } else if (tn === $.MAIN || tn === $.MENU) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.FORM) {
 | |
|                 formStartTagInBody(p, token);
 | |
|             } else if (tn === $.CODE || tn === $.FONT) {
 | |
|                 bStartTagInBody(p, token);
 | |
|             } else if (tn === $.NOBR) {
 | |
|                 nobrStartTagInBody(p, token);
 | |
|             } else if (tn === $.AREA) {
 | |
|                 areaStartTagInBody(p, token);
 | |
|             } else if (tn === $.MATH) {
 | |
|                 mathStartTagInBody(p, token);
 | |
|             } else if (tn === $.MENU) {
 | |
|                 menuStartTagInBody(p, token);
 | |
|             } else if (tn !== $.HEAD) {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 5:
 | |
|             if (tn === $.STYLE || tn === $.TITLE) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (tn === $.ASIDE) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.SMALL) {
 | |
|                 bStartTagInBody(p, token);
 | |
|             } else if (tn === $.TABLE) {
 | |
|                 tableStartTagInBody(p, token);
 | |
|             } else if (tn === $.EMBED) {
 | |
|                 areaStartTagInBody(p, token);
 | |
|             } else if (tn === $.INPUT) {
 | |
|                 inputStartTagInBody(p, token);
 | |
|             } else if (tn === $.PARAM || tn === $.TRACK) {
 | |
|                 paramStartTagInBody(p, token);
 | |
|             } else if (tn === $.IMAGE) {
 | |
|                 imageStartTagInBody(p, token);
 | |
|             } else if (tn !== $.FRAME && tn !== $.TBODY && tn !== $.TFOOT && tn !== $.THEAD) {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 6:
 | |
|             if (tn === $.SCRIPT) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (
 | |
|                 tn === $.CENTER ||
 | |
|                 tn === $.FIGURE ||
 | |
|                 tn === $.FOOTER ||
 | |
|                 tn === $.HEADER ||
 | |
|                 tn === $.HGROUP ||
 | |
|                 tn === $.DIALOG
 | |
|             ) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.BUTTON) {
 | |
|                 buttonStartTagInBody(p, token);
 | |
|             } else if (tn === $.STRIKE || tn === $.STRONG) {
 | |
|                 bStartTagInBody(p, token);
 | |
|             } else if (tn === $.APPLET || tn === $.OBJECT) {
 | |
|                 appletStartTagInBody(p, token);
 | |
|             } else if (tn === $.KEYGEN) {
 | |
|                 areaStartTagInBody(p, token);
 | |
|             } else if (tn === $.SOURCE) {
 | |
|                 paramStartTagInBody(p, token);
 | |
|             } else if (tn === $.IFRAME) {
 | |
|                 iframeStartTagInBody(p, token);
 | |
|             } else if (tn === $.SELECT) {
 | |
|                 selectStartTagInBody(p, token);
 | |
|             } else if (tn === $.OPTION) {
 | |
|                 optgroupStartTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 7:
 | |
|             if (tn === $.BGSOUND) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (
 | |
|                 tn === $.DETAILS ||
 | |
|                 tn === $.ADDRESS ||
 | |
|                 tn === $.ARTICLE ||
 | |
|                 tn === $.SECTION ||
 | |
|                 tn === $.SUMMARY
 | |
|             ) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.LISTING) {
 | |
|                 preStartTagInBody(p, token);
 | |
|             } else if (tn === $.MARQUEE) {
 | |
|                 appletStartTagInBody(p, token);
 | |
|             } else if (tn === $.NOEMBED) {
 | |
|                 noembedStartTagInBody(p, token);
 | |
|             } else if (tn !== $.CAPTION) {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 8:
 | |
|             if (tn === $.BASEFONT) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (tn === $.FRAMESET) {
 | |
|                 framesetStartTagInBody(p, token);
 | |
|             } else if (tn === $.FIELDSET) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else if (tn === $.TEXTAREA) {
 | |
|                 textareaStartTagInBody(p, token);
 | |
|             } else if (tn === $.TEMPLATE) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (tn === $.NOSCRIPT) {
 | |
|                 if (p.options.scriptingEnabled) {
 | |
|                     noembedStartTagInBody(p, token);
 | |
|                 } else {
 | |
|                     genericStartTagInBody(p, token);
 | |
|                 }
 | |
|             } else if (tn === $.OPTGROUP) {
 | |
|                 optgroupStartTagInBody(p, token);
 | |
|             } else if (tn !== $.COLGROUP) {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 9:
 | |
|             if (tn === $.PLAINTEXT) {
 | |
|                 plaintextStartTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 10:
 | |
|             if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) {
 | |
|                 addressStartTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericStartTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             genericStartTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function bodyEndTagInBody(p) {
 | |
|     if (p.openElements.hasInScope($.BODY)) {
 | |
|         p.insertionMode = AFTER_BODY_MODE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| function htmlEndTagInBody(p, token) {
 | |
|     if (p.openElements.hasInScope($.BODY)) {
 | |
|         p.insertionMode = AFTER_BODY_MODE;
 | |
|         p._processToken(token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function addressEndTagInBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (p.openElements.hasInScope(tn)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         p.openElements.popUntilTagNamePopped(tn);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function formEndTagInBody(p) {
 | |
|     const inTemplate = p.openElements.tmplCount > 0;
 | |
|     const formElement = p.formElement;
 | |
| 
 | |
|     if (!inTemplate) {
 | |
|         p.formElement = null;
 | |
|     }
 | |
| 
 | |
|     if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
| 
 | |
|         if (inTemplate) {
 | |
|             p.openElements.popUntilTagNamePopped($.FORM);
 | |
|         } else {
 | |
|             p.openElements.remove(formElement);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| function pEndTagInBody(p) {
 | |
|     if (!p.openElements.hasInButtonScope($.P)) {
 | |
|         p._insertFakeElement($.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.tagName;
 | |
| 
 | |
|     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.tagName;
 | |
| 
 | |
|     if (p.openElements.hasInScope(tn)) {
 | |
|         p.openElements.generateImpliedEndTags();
 | |
|         p.openElements.popUntilTagNamePopped(tn);
 | |
|         p.activeFormattingElements.clearToLastMarker();
 | |
|     }
 | |
| }
 | |
| 
 | |
| function brEndTagInBody(p) {
 | |
|     p._reconstructActiveFormattingElements();
 | |
|     p._insertFakeElement($.BR);
 | |
|     p.openElements.pop();
 | |
|     p.framesetOk = false;
 | |
| }
 | |
| 
 | |
| function genericEndTagInBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     for (let i = p.openElements.stackTop; i > 0; i--) {
 | |
|         const element = p.openElements.items[i];
 | |
| 
 | |
|         if (p.treeAdapter.getTagName(element) === tn) {
 | |
|             p.openElements.generateImpliedEndTagsWithExclusion(tn);
 | |
|             p.openElements.popUntilElementPopped(element);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         if (p._isSpecialElement(element)) {
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| //OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
 | |
| //It's faster than using dictionary.
 | |
| function endTagInBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     switch (tn.length) {
 | |
|         case 1:
 | |
|             if (tn === $.A || tn === $.B || tn === $.I || tn === $.S || tn === $.U) {
 | |
|                 callAdoptionAgency(p, token);
 | |
|             } else if (tn === $.P) {
 | |
|                 pEndTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 2:
 | |
|             if (tn === $.DL || tn === $.UL || tn === $.OL) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else if (tn === $.LI) {
 | |
|                 liEndTagInBody(p, token);
 | |
|             } else if (tn === $.DD || tn === $.DT) {
 | |
|                 ddEndTagInBody(p, token);
 | |
|             } else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
 | |
|                 numberedHeaderEndTagInBody(p, token);
 | |
|             } else if (tn === $.BR) {
 | |
|                 brEndTagInBody(p, token);
 | |
|             } else if (tn === $.EM || tn === $.TT) {
 | |
|                 callAdoptionAgency(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 3:
 | |
|             if (tn === $.BIG) {
 | |
|                 callAdoptionAgency(p, token);
 | |
|             } else if (tn === $.DIR || tn === $.DIV || tn === $.NAV || tn === $.PRE) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 4:
 | |
|             if (tn === $.BODY) {
 | |
|                 bodyEndTagInBody(p, token);
 | |
|             } else if (tn === $.HTML) {
 | |
|                 htmlEndTagInBody(p, token);
 | |
|             } else if (tn === $.FORM) {
 | |
|                 formEndTagInBody(p, token);
 | |
|             } else if (tn === $.CODE || tn === $.FONT || tn === $.NOBR) {
 | |
|                 callAdoptionAgency(p, token);
 | |
|             } else if (tn === $.MAIN || tn === $.MENU) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 5:
 | |
|             if (tn === $.ASIDE) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else if (tn === $.SMALL) {
 | |
|                 callAdoptionAgency(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 6:
 | |
|             if (
 | |
|                 tn === $.CENTER ||
 | |
|                 tn === $.FIGURE ||
 | |
|                 tn === $.FOOTER ||
 | |
|                 tn === $.HEADER ||
 | |
|                 tn === $.HGROUP ||
 | |
|                 tn === $.DIALOG
 | |
|             ) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else if (tn === $.APPLET || tn === $.OBJECT) {
 | |
|                 appletEndTagInBody(p, token);
 | |
|             } else if (tn === $.STRIKE || tn === $.STRONG) {
 | |
|                 callAdoptionAgency(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 7:
 | |
|             if (
 | |
|                 tn === $.ADDRESS ||
 | |
|                 tn === $.ARTICLE ||
 | |
|                 tn === $.DETAILS ||
 | |
|                 tn === $.SECTION ||
 | |
|                 tn === $.SUMMARY ||
 | |
|                 tn === $.LISTING
 | |
|             ) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else if (tn === $.MARQUEE) {
 | |
|                 appletEndTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 8:
 | |
|             if (tn === $.FIELDSET) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else if (tn === $.TEMPLATE) {
 | |
|                 endTagInHead(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 10:
 | |
|             if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) {
 | |
|                 addressEndTagInBody(p, token);
 | |
|             } else {
 | |
|                 genericEndTagInBody(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             genericEndTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function eofInBody(p, token) {
 | |
|     if (p.tmplInsertionModeStackTop > -1) {
 | |
|         eofInTemplate(p, token);
 | |
|     } else {
 | |
|         p.stopped = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "text" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function endTagInText(p, token) {
 | |
|     if (token.tagName === $.SCRIPT) {
 | |
|         p.pendingScript = p.openElements.current;
 | |
|     }
 | |
| 
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = p.originalInsertionMode;
 | |
| }
 | |
| 
 | |
| function eofInText(p, token) {
 | |
|     p._err(ERR.eofInElementThatCanContainOnlyText);
 | |
|     p.openElements.pop();
 | |
|     p.insertionMode = p.originalInsertionMode;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "in table" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function characterInTable(p, token) {
 | |
|     const curTn = p.openElements.currentTagName;
 | |
| 
 | |
|     if (curTn === $.TABLE || curTn === $.TBODY || curTn === $.TFOOT || curTn === $.THEAD || curTn === $.TR) {
 | |
|         p.pendingCharacterTokens = [];
 | |
|         p.hasNonWhitespacePendingCharacterToken = false;
 | |
|         p.originalInsertionMode = p.insertionMode;
 | |
|         p.insertionMode = IN_TABLE_TEXT_MODE;
 | |
|         p._processToken(token);
 | |
|     } else {
 | |
|         tokenInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function captionStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p.activeFormattingElements.insertMarker();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.insertionMode = IN_CAPTION_MODE;
 | |
| }
 | |
| 
 | |
| function colgroupStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.insertionMode = IN_COLUMN_GROUP_MODE;
 | |
| }
 | |
| 
 | |
| function colStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertFakeElement($.COLGROUP);
 | |
|     p.insertionMode = IN_COLUMN_GROUP_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| function tbodyStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertElement(token, NS.HTML);
 | |
|     p.insertionMode = IN_TABLE_BODY_MODE;
 | |
| }
 | |
| 
 | |
| function tdStartTagInTable(p, token) {
 | |
|     p.openElements.clearBackToTableContext();
 | |
|     p._insertFakeElement($.TBODY);
 | |
|     p.insertionMode = IN_TABLE_BODY_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| function tableStartTagInTable(p, token) {
 | |
|     if (p.openElements.hasInTableScope($.TABLE)) {
 | |
|         p.openElements.popUntilTagNamePopped($.TABLE);
 | |
|         p._resetInsertionMode();
 | |
|         p._processToken(token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function inputStartTagInTable(p, token) {
 | |
|     const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
 | |
| 
 | |
|     if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE) {
 | |
|         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) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     switch (tn.length) {
 | |
|         case 2:
 | |
|             if (tn === $.TD || tn === $.TH || tn === $.TR) {
 | |
|                 tdStartTagInTable(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 3:
 | |
|             if (tn === $.COL) {
 | |
|                 colStartTagInTable(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 4:
 | |
|             if (tn === $.FORM) {
 | |
|                 formStartTagInTable(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 5:
 | |
|             if (tn === $.TABLE) {
 | |
|                 tableStartTagInTable(p, token);
 | |
|             } else if (tn === $.STYLE) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
 | |
|                 tbodyStartTagInTable(p, token);
 | |
|             } else if (tn === $.INPUT) {
 | |
|                 inputStartTagInTable(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 6:
 | |
|             if (tn === $.SCRIPT) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 7:
 | |
|             if (tn === $.CAPTION) {
 | |
|                 captionStartTagInTable(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         case 8:
 | |
|             if (tn === $.COLGROUP) {
 | |
|                 colgroupStartTagInTable(p, token);
 | |
|             } else if (tn === $.TEMPLATE) {
 | |
|                 startTagInHead(p, token);
 | |
|             } else {
 | |
|                 tokenInTable(p, token);
 | |
|             }
 | |
| 
 | |
|             break;
 | |
| 
 | |
|         default:
 | |
|             tokenInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInTable(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.TABLE) {
 | |
|         if (p.openElements.hasInTableScope($.TABLE)) {
 | |
|             p.openElements.popUntilTagNamePopped($.TABLE);
 | |
|             p._resetInsertionMode();
 | |
|         }
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         endTagInHead(p, token);
 | |
|     } else if (
 | |
|         tn !== $.BODY &&
 | |
|         tn !== $.CAPTION &&
 | |
|         tn !== $.COL &&
 | |
|         tn !== $.COLGROUP &&
 | |
|         tn !== $.HTML &&
 | |
|         tn !== $.TBODY &&
 | |
|         tn !== $.TD &&
 | |
|         tn !== $.TFOOT &&
 | |
|         tn !== $.TH &&
 | |
|         tn !== $.THEAD &&
 | |
|         tn !== $.TR
 | |
|     ) {
 | |
|         tokenInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenInTable(p, token) {
 | |
|     const savedFosterParentingState = p.fosterParentingEnabled;
 | |
| 
 | |
|     p.fosterParentingEnabled = true;
 | |
|     p._processTokenInBodyMode(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
 | |
| //------------------------------------------------------------------
 | |
| function startTagInCaption(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (
 | |
|         tn === $.CAPTION ||
 | |
|         tn === $.COL ||
 | |
|         tn === $.COLGROUP ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TD ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.TH ||
 | |
|         tn === $.THEAD ||
 | |
|         tn === $.TR
 | |
|     ) {
 | |
|         if (p.openElements.hasInTableScope($.CAPTION)) {
 | |
|             p.openElements.generateImpliedEndTags();
 | |
|             p.openElements.popUntilTagNamePopped($.CAPTION);
 | |
|             p.activeFormattingElements.clearToLastMarker();
 | |
|             p.insertionMode = IN_TABLE_MODE;
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else {
 | |
|         startTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInCaption(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.CAPTION || tn === $.TABLE) {
 | |
|         if (p.openElements.hasInTableScope($.CAPTION)) {
 | |
|             p.openElements.generateImpliedEndTags();
 | |
|             p.openElements.popUntilTagNamePopped($.CAPTION);
 | |
|             p.activeFormattingElements.clearToLastMarker();
 | |
|             p.insertionMode = IN_TABLE_MODE;
 | |
| 
 | |
|             if (tn === $.TABLE) {
 | |
|                 p._processToken(token);
 | |
|             }
 | |
|         }
 | |
|     } else if (
 | |
|         tn !== $.BODY &&
 | |
|         tn !== $.COL &&
 | |
|         tn !== $.COLGROUP &&
 | |
|         tn !== $.HTML &&
 | |
|         tn !== $.TBODY &&
 | |
|         tn !== $.TD &&
 | |
|         tn !== $.TFOOT &&
 | |
|         tn !== $.TH &&
 | |
|         tn !== $.THEAD &&
 | |
|         tn !== $.TR
 | |
|     ) {
 | |
|         endTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "in column group" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInColumnGroup(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.COL) {
 | |
|         p._appendElement(token, NS.HTML);
 | |
|         token.ackSelfClosing = true;
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         startTagInHead(p, token);
 | |
|     } else {
 | |
|         tokenInColumnGroup(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInColumnGroup(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.COLGROUP) {
 | |
|         if (p.openElements.currentTagName === $.COLGROUP) {
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_MODE;
 | |
|         }
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         endTagInHead(p, token);
 | |
|     } else if (tn !== $.COL) {
 | |
|         tokenInColumnGroup(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenInColumnGroup(p, token) {
 | |
|     if (p.openElements.currentTagName === $.COLGROUP) {
 | |
|         p.openElements.pop();
 | |
|         p.insertionMode = IN_TABLE_MODE;
 | |
|         p._processToken(token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "in table body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInTableBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.TR) {
 | |
|         p.openElements.clearBackToTableBodyContext();
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.insertionMode = IN_ROW_MODE;
 | |
|     } else if (tn === $.TH || tn === $.TD) {
 | |
|         p.openElements.clearBackToTableBodyContext();
 | |
|         p._insertFakeElement($.TR);
 | |
|         p.insertionMode = IN_ROW_MODE;
 | |
|         p._processToken(token);
 | |
|     } else if (
 | |
|         tn === $.CAPTION ||
 | |
|         tn === $.COL ||
 | |
|         tn === $.COLGROUP ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.THEAD
 | |
|     ) {
 | |
|         if (p.openElements.hasTableBodyContextInTableScope()) {
 | |
|             p.openElements.clearBackToTableBodyContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_MODE;
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else {
 | |
|         startTagInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInTableBody(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
 | |
|         if (p.openElements.hasInTableScope(tn)) {
 | |
|             p.openElements.clearBackToTableBodyContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_MODE;
 | |
|         }
 | |
|     } else if (tn === $.TABLE) {
 | |
|         if (p.openElements.hasTableBodyContextInTableScope()) {
 | |
|             p.openElements.clearBackToTableBodyContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_MODE;
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else if (
 | |
|         (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) ||
 | |
|         (tn !== $.HTML && tn !== $.TD && tn !== $.TH && tn !== $.TR)
 | |
|     ) {
 | |
|         endTagInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "in row" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInRow(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.TH || tn === $.TD) {
 | |
|         p.openElements.clearBackToTableRowContext();
 | |
|         p._insertElement(token, NS.HTML);
 | |
|         p.insertionMode = IN_CELL_MODE;
 | |
|         p.activeFormattingElements.insertMarker();
 | |
|     } else if (
 | |
|         tn === $.CAPTION ||
 | |
|         tn === $.COL ||
 | |
|         tn === $.COLGROUP ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.THEAD ||
 | |
|         tn === $.TR
 | |
|     ) {
 | |
|         if (p.openElements.hasInTableScope($.TR)) {
 | |
|             p.openElements.clearBackToTableRowContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_BODY_MODE;
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else {
 | |
|         startTagInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInRow(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.TR) {
 | |
|         if (p.openElements.hasInTableScope($.TR)) {
 | |
|             p.openElements.clearBackToTableRowContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_BODY_MODE;
 | |
|         }
 | |
|     } else if (tn === $.TABLE) {
 | |
|         if (p.openElements.hasInTableScope($.TR)) {
 | |
|             p.openElements.clearBackToTableRowContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_BODY_MODE;
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
 | |
|         if (p.openElements.hasInTableScope(tn) || p.openElements.hasInTableScope($.TR)) {
 | |
|             p.openElements.clearBackToTableRowContext();
 | |
|             p.openElements.pop();
 | |
|             p.insertionMode = IN_TABLE_BODY_MODE;
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else if (
 | |
|         (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) ||
 | |
|         (tn !== $.HTML && tn !== $.TD && tn !== $.TH)
 | |
|     ) {
 | |
|         endTagInTable(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "in cell" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInCell(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (
 | |
|         tn === $.CAPTION ||
 | |
|         tn === $.COL ||
 | |
|         tn === $.COLGROUP ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TD ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.TH ||
 | |
|         tn === $.THEAD ||
 | |
|         tn === $.TR
 | |
|     ) {
 | |
|         if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) {
 | |
|             p._closeTableCell();
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else {
 | |
|         startTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInCell(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.TD || tn === $.TH) {
 | |
|         if (p.openElements.hasInTableScope(tn)) {
 | |
|             p.openElements.generateImpliedEndTags();
 | |
|             p.openElements.popUntilTagNamePopped(tn);
 | |
|             p.activeFormattingElements.clearToLastMarker();
 | |
|             p.insertionMode = IN_ROW_MODE;
 | |
|         }
 | |
|     } else if (tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR) {
 | |
|         if (p.openElements.hasInTableScope(tn)) {
 | |
|             p._closeTableCell();
 | |
|             p._processToken(token);
 | |
|         }
 | |
|     } else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML) {
 | |
|         endTagInBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "in select" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInSelect(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.OPTION) {
 | |
|         if (p.openElements.currentTagName === $.OPTION) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
| 
 | |
|         p._insertElement(token, NS.HTML);
 | |
|     } else if (tn === $.OPTGROUP) {
 | |
|         if (p.openElements.currentTagName === $.OPTION) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
| 
 | |
|         if (p.openElements.currentTagName === $.OPTGROUP) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
| 
 | |
|         p._insertElement(token, NS.HTML);
 | |
|     } else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA || tn === $.SELECT) {
 | |
|         if (p.openElements.hasInSelectScope($.SELECT)) {
 | |
|             p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|             p._resetInsertionMode();
 | |
| 
 | |
|             if (tn !== $.SELECT) {
 | |
|                 p._processToken(token);
 | |
|             }
 | |
|         }
 | |
|     } else if (tn === $.SCRIPT || tn === $.TEMPLATE) {
 | |
|         startTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInSelect(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.OPTGROUP) {
 | |
|         const prevOpenElement = p.openElements.items[p.openElements.stackTop - 1];
 | |
|         const prevOpenElementTn = prevOpenElement && p.treeAdapter.getTagName(prevOpenElement);
 | |
| 
 | |
|         if (p.openElements.currentTagName === $.OPTION && prevOpenElementTn === $.OPTGROUP) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
| 
 | |
|         if (p.openElements.currentTagName === $.OPTGROUP) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
|     } else if (tn === $.OPTION) {
 | |
|         if (p.openElements.currentTagName === $.OPTION) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
|     } else if (tn === $.SELECT && p.openElements.hasInSelectScope($.SELECT)) {
 | |
|         p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|         p._resetInsertionMode();
 | |
|     } else if (tn === $.TEMPLATE) {
 | |
|         endTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| //12.2.5.4.17 The "in select in table" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInSelectInTable(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (
 | |
|         tn === $.CAPTION ||
 | |
|         tn === $.TABLE ||
 | |
|         tn === $.TBODY ||
 | |
|         tn === $.TFOOT ||
 | |
|         tn === $.THEAD ||
 | |
|         tn === $.TR ||
 | |
|         tn === $.TD ||
 | |
|         tn === $.TH
 | |
|     ) {
 | |
|         p.openElements.popUntilTagNamePopped($.SELECT);
 | |
|         p._resetInsertionMode();
 | |
|         p._processToken(token);
 | |
|     } else {
 | |
|         startTagInSelect(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInSelectInTable(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     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._processToken(token);
 | |
|         }
 | |
|     } else {
 | |
|         endTagInSelect(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "in template" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInTemplate(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (
 | |
|         tn === $.BASE ||
 | |
|         tn === $.BASEFONT ||
 | |
|         tn === $.BGSOUND ||
 | |
|         tn === $.LINK ||
 | |
|         tn === $.META ||
 | |
|         tn === $.NOFRAMES ||
 | |
|         tn === $.SCRIPT ||
 | |
|         tn === $.STYLE ||
 | |
|         tn === $.TEMPLATE ||
 | |
|         tn === $.TITLE
 | |
|     ) {
 | |
|         startTagInHead(p, token);
 | |
|     } else {
 | |
|         const newInsertionMode = TEMPLATE_INSERTION_MODE_SWITCH_MAP[tn] || IN_BODY_MODE;
 | |
| 
 | |
|         p._popTmplInsertionMode();
 | |
|         p._pushTmplInsertionMode(newInsertionMode);
 | |
|         p.insertionMode = newInsertionMode;
 | |
|         p._processToken(token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInTemplate(p, token) {
 | |
|     if (token.tagName === $.TEMPLATE) {
 | |
|         endTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function eofInTemplate(p, token) {
 | |
|     if (p.openElements.tmplCount > 0) {
 | |
|         p.openElements.popUntilTagNamePopped($.TEMPLATE);
 | |
|         p.activeFormattingElements.clearToLastMarker();
 | |
|         p._popTmplInsertionMode();
 | |
|         p._resetInsertionMode();
 | |
|         p._processToken(token);
 | |
|     } else {
 | |
|         p.stopped = true;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "after body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterBody(p, token) {
 | |
|     if (token.tagName === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else {
 | |
|         tokenAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagAfterBody(p, token) {
 | |
|     if (token.tagName === $.HTML) {
 | |
|         if (!p.fragmentContext) {
 | |
|             p.insertionMode = AFTER_AFTER_BODY_MODE;
 | |
|         }
 | |
|     } else {
 | |
|         tokenAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenAfterBody(p, token) {
 | |
|     p.insertionMode = IN_BODY_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "in frameset" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagInFrameset(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.FRAMESET) {
 | |
|         p._insertElement(token, NS.HTML);
 | |
|     } else if (tn === $.FRAME) {
 | |
|         p._appendElement(token, NS.HTML);
 | |
|         token.ackSelfClosing = true;
 | |
|     } else if (tn === $.NOFRAMES) {
 | |
|         startTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagInFrameset(p, token) {
 | |
|     if (token.tagName === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) {
 | |
|         p.openElements.pop();
 | |
| 
 | |
|         if (!p.fragmentContext && p.openElements.currentTagName !== $.FRAMESET) {
 | |
|             p.insertionMode = AFTER_FRAMESET_MODE;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "after frameset" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterFrameset(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.NOFRAMES) {
 | |
|         startTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function endTagAfterFrameset(p, token) {
 | |
|     if (token.tagName === $.HTML) {
 | |
|         p.insertionMode = AFTER_AFTER_FRAMESET_MODE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| // The "after after body" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterAfterBody(p, token) {
 | |
|     if (token.tagName === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else {
 | |
|         tokenAfterAfterBody(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| function tokenAfterAfterBody(p, token) {
 | |
|     p.insertionMode = IN_BODY_MODE;
 | |
|     p._processToken(token);
 | |
| }
 | |
| 
 | |
| // The "after after frameset" insertion mode
 | |
| //------------------------------------------------------------------
 | |
| function startTagAfterAfterFrameset(p, token) {
 | |
|     const tn = token.tagName;
 | |
| 
 | |
|     if (tn === $.HTML) {
 | |
|         startTagInBody(p, token);
 | |
|     } else if (tn === $.NOFRAMES) {
 | |
|         startTagInHead(p, token);
 | |
|     }
 | |
| }
 | |
| 
 | |
| // 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 startTagInForeignContent(p, token) {
 | |
|     if (foreignContent.causesExit(token) && !p.fragmentContext) {
 | |
|         while (
 | |
|             p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML &&
 | |
|             !p._isIntegrationPoint(p.openElements.current)
 | |
|         ) {
 | |
|             p.openElements.pop();
 | |
|         }
 | |
| 
 | |
|         p._processToken(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) {
 | |
|     for (let i = p.openElements.stackTop; i > 0; i--) {
 | |
|         const element = p.openElements.items[i];
 | |
| 
 | |
|         if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) {
 | |
|             p._processToken(token);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         if (p.treeAdapter.getTagName(element).toLowerCase() === token.tagName) {
 | |
|             p.openElements.popUntilElementPopped(element);
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| }
 |