• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const Tokenizer = require('../tokenizer');
4const OpenElementStack = require('./open-element-stack');
5const FormattingElementList = require('./formatting-element-list');
6const LocationInfoParserMixin = require('../extensions/location-info/parser-mixin');
7const ErrorReportingParserMixin = require('../extensions/error-reporting/parser-mixin');
8const Mixin = require('../utils/mixin');
9const defaultTreeAdapter = require('../tree-adapters/default');
10const mergeOptions = require('../utils/merge-options');
11const doctype = require('../common/doctype');
12const foreignContent = require('../common/foreign-content');
13const ERR = require('../common/error-codes');
14const unicode = require('../common/unicode');
15const HTML = require('../common/html');
16
17//Aliases
18const $ = HTML.TAG_NAMES;
19const NS = HTML.NAMESPACES;
20const ATTRS = HTML.ATTRS;
21
22const DEFAULT_OPTIONS = {
23    scriptingEnabled: true,
24    sourceCodeLocationInfo: false,
25    onParseError: null,
26    treeAdapter: defaultTreeAdapter
27};
28
29//Misc constants
30const HIDDEN_INPUT_TYPE = 'hidden';
31
32//Adoption agency loops iteration count
33const AA_OUTER_LOOP_ITER = 8;
34const AA_INNER_LOOP_ITER = 3;
35
36//Insertion modes
37const INITIAL_MODE = 'INITIAL_MODE';
38const BEFORE_HTML_MODE = 'BEFORE_HTML_MODE';
39const BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE';
40const IN_HEAD_MODE = 'IN_HEAD_MODE';
41const IN_HEAD_NO_SCRIPT_MODE = 'IN_HEAD_NO_SCRIPT_MODE';
42const AFTER_HEAD_MODE = 'AFTER_HEAD_MODE';
43const IN_BODY_MODE = 'IN_BODY_MODE';
44const TEXT_MODE = 'TEXT_MODE';
45const IN_TABLE_MODE = 'IN_TABLE_MODE';
46const IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE';
47const IN_CAPTION_MODE = 'IN_CAPTION_MODE';
48const IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE';
49const IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE';
50const IN_ROW_MODE = 'IN_ROW_MODE';
51const IN_CELL_MODE = 'IN_CELL_MODE';
52const IN_SELECT_MODE = 'IN_SELECT_MODE';
53const IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE';
54const IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE';
55const AFTER_BODY_MODE = 'AFTER_BODY_MODE';
56const IN_FRAMESET_MODE = 'IN_FRAMESET_MODE';
57const AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE';
58const AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE';
59const AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE';
60
61//Insertion mode reset map
62const INSERTION_MODE_RESET_MAP = {
63    [$.TR]: IN_ROW_MODE,
64    [$.TBODY]: IN_TABLE_BODY_MODE,
65    [$.THEAD]: IN_TABLE_BODY_MODE,
66    [$.TFOOT]: IN_TABLE_BODY_MODE,
67    [$.CAPTION]: IN_CAPTION_MODE,
68    [$.COLGROUP]: IN_COLUMN_GROUP_MODE,
69    [$.TABLE]: IN_TABLE_MODE,
70    [$.BODY]: IN_BODY_MODE,
71    [$.FRAMESET]: IN_FRAMESET_MODE
72};
73
74//Template insertion mode switch map
75const TEMPLATE_INSERTION_MODE_SWITCH_MAP = {
76    [$.CAPTION]: IN_TABLE_MODE,
77    [$.COLGROUP]: IN_TABLE_MODE,
78    [$.TBODY]: IN_TABLE_MODE,
79    [$.TFOOT]: IN_TABLE_MODE,
80    [$.THEAD]: IN_TABLE_MODE,
81    [$.COL]: IN_COLUMN_GROUP_MODE,
82    [$.TR]: IN_TABLE_BODY_MODE,
83    [$.TD]: IN_ROW_MODE,
84    [$.TH]: IN_ROW_MODE
85};
86
87//Token handlers map for insertion modes
88const TOKEN_HANDLERS = {
89    [INITIAL_MODE]: {
90        [Tokenizer.CHARACTER_TOKEN]: tokenInInitialMode,
91        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInInitialMode,
92        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
93        [Tokenizer.COMMENT_TOKEN]: appendComment,
94        [Tokenizer.DOCTYPE_TOKEN]: doctypeInInitialMode,
95        [Tokenizer.START_TAG_TOKEN]: tokenInInitialMode,
96        [Tokenizer.END_TAG_TOKEN]: tokenInInitialMode,
97        [Tokenizer.EOF_TOKEN]: tokenInInitialMode
98    },
99    [BEFORE_HTML_MODE]: {
100        [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHtml,
101        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHtml,
102        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
103        [Tokenizer.COMMENT_TOKEN]: appendComment,
104        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
105        [Tokenizer.START_TAG_TOKEN]: startTagBeforeHtml,
106        [Tokenizer.END_TAG_TOKEN]: endTagBeforeHtml,
107        [Tokenizer.EOF_TOKEN]: tokenBeforeHtml
108    },
109    [BEFORE_HEAD_MODE]: {
110        [Tokenizer.CHARACTER_TOKEN]: tokenBeforeHead,
111        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHead,
112        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken,
113        [Tokenizer.COMMENT_TOKEN]: appendComment,
114        [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
115        [Tokenizer.START_TAG_TOKEN]: startTagBeforeHead,
116        [Tokenizer.END_TAG_TOKEN]: endTagBeforeHead,
117        [Tokenizer.EOF_TOKEN]: tokenBeforeHead
118    },
119    [IN_HEAD_MODE]: {
120        [Tokenizer.CHARACTER_TOKEN]: tokenInHead,
121        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHead,
122        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
123        [Tokenizer.COMMENT_TOKEN]: appendComment,
124        [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
125        [Tokenizer.START_TAG_TOKEN]: startTagInHead,
126        [Tokenizer.END_TAG_TOKEN]: endTagInHead,
127        [Tokenizer.EOF_TOKEN]: tokenInHead
128    },
129    [IN_HEAD_NO_SCRIPT_MODE]: {
130        [Tokenizer.CHARACTER_TOKEN]: tokenInHeadNoScript,
131        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHeadNoScript,
132        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
133        [Tokenizer.COMMENT_TOKEN]: appendComment,
134        [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
135        [Tokenizer.START_TAG_TOKEN]: startTagInHeadNoScript,
136        [Tokenizer.END_TAG_TOKEN]: endTagInHeadNoScript,
137        [Tokenizer.EOF_TOKEN]: tokenInHeadNoScript
138    },
139    [AFTER_HEAD_MODE]: {
140        [Tokenizer.CHARACTER_TOKEN]: tokenAfterHead,
141        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterHead,
142        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
143        [Tokenizer.COMMENT_TOKEN]: appendComment,
144        [Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype,
145        [Tokenizer.START_TAG_TOKEN]: startTagAfterHead,
146        [Tokenizer.END_TAG_TOKEN]: endTagAfterHead,
147        [Tokenizer.EOF_TOKEN]: tokenAfterHead
148    },
149    [IN_BODY_MODE]: {
150        [Tokenizer.CHARACTER_TOKEN]: characterInBody,
151        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
152        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
153        [Tokenizer.COMMENT_TOKEN]: appendComment,
154        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
155        [Tokenizer.START_TAG_TOKEN]: startTagInBody,
156        [Tokenizer.END_TAG_TOKEN]: endTagInBody,
157        [Tokenizer.EOF_TOKEN]: eofInBody
158    },
159    [TEXT_MODE]: {
160        [Tokenizer.CHARACTER_TOKEN]: insertCharacters,
161        [Tokenizer.NULL_CHARACTER_TOKEN]: insertCharacters,
162        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
163        [Tokenizer.COMMENT_TOKEN]: ignoreToken,
164        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
165        [Tokenizer.START_TAG_TOKEN]: ignoreToken,
166        [Tokenizer.END_TAG_TOKEN]: endTagInText,
167        [Tokenizer.EOF_TOKEN]: eofInText
168    },
169    [IN_TABLE_MODE]: {
170        [Tokenizer.CHARACTER_TOKEN]: characterInTable,
171        [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
172        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
173        [Tokenizer.COMMENT_TOKEN]: appendComment,
174        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
175        [Tokenizer.START_TAG_TOKEN]: startTagInTable,
176        [Tokenizer.END_TAG_TOKEN]: endTagInTable,
177        [Tokenizer.EOF_TOKEN]: eofInBody
178    },
179    [IN_TABLE_TEXT_MODE]: {
180        [Tokenizer.CHARACTER_TOKEN]: characterInTableText,
181        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
182        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInTableText,
183        [Tokenizer.COMMENT_TOKEN]: tokenInTableText,
184        [Tokenizer.DOCTYPE_TOKEN]: tokenInTableText,
185        [Tokenizer.START_TAG_TOKEN]: tokenInTableText,
186        [Tokenizer.END_TAG_TOKEN]: tokenInTableText,
187        [Tokenizer.EOF_TOKEN]: tokenInTableText
188    },
189    [IN_CAPTION_MODE]: {
190        [Tokenizer.CHARACTER_TOKEN]: characterInBody,
191        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
192        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
193        [Tokenizer.COMMENT_TOKEN]: appendComment,
194        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
195        [Tokenizer.START_TAG_TOKEN]: startTagInCaption,
196        [Tokenizer.END_TAG_TOKEN]: endTagInCaption,
197        [Tokenizer.EOF_TOKEN]: eofInBody
198    },
199    [IN_COLUMN_GROUP_MODE]: {
200        [Tokenizer.CHARACTER_TOKEN]: tokenInColumnGroup,
201        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenInColumnGroup,
202        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
203        [Tokenizer.COMMENT_TOKEN]: appendComment,
204        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
205        [Tokenizer.START_TAG_TOKEN]: startTagInColumnGroup,
206        [Tokenizer.END_TAG_TOKEN]: endTagInColumnGroup,
207        [Tokenizer.EOF_TOKEN]: eofInBody
208    },
209    [IN_TABLE_BODY_MODE]: {
210        [Tokenizer.CHARACTER_TOKEN]: characterInTable,
211        [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
212        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
213        [Tokenizer.COMMENT_TOKEN]: appendComment,
214        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
215        [Tokenizer.START_TAG_TOKEN]: startTagInTableBody,
216        [Tokenizer.END_TAG_TOKEN]: endTagInTableBody,
217        [Tokenizer.EOF_TOKEN]: eofInBody
218    },
219    [IN_ROW_MODE]: {
220        [Tokenizer.CHARACTER_TOKEN]: characterInTable,
221        [Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable,
222        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable,
223        [Tokenizer.COMMENT_TOKEN]: appendComment,
224        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
225        [Tokenizer.START_TAG_TOKEN]: startTagInRow,
226        [Tokenizer.END_TAG_TOKEN]: endTagInRow,
227        [Tokenizer.EOF_TOKEN]: eofInBody
228    },
229    [IN_CELL_MODE]: {
230        [Tokenizer.CHARACTER_TOKEN]: characterInBody,
231        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
232        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
233        [Tokenizer.COMMENT_TOKEN]: appendComment,
234        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
235        [Tokenizer.START_TAG_TOKEN]: startTagInCell,
236        [Tokenizer.END_TAG_TOKEN]: endTagInCell,
237        [Tokenizer.EOF_TOKEN]: eofInBody
238    },
239    [IN_SELECT_MODE]: {
240        [Tokenizer.CHARACTER_TOKEN]: insertCharacters,
241        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
242        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
243        [Tokenizer.COMMENT_TOKEN]: appendComment,
244        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
245        [Tokenizer.START_TAG_TOKEN]: startTagInSelect,
246        [Tokenizer.END_TAG_TOKEN]: endTagInSelect,
247        [Tokenizer.EOF_TOKEN]: eofInBody
248    },
249    [IN_SELECT_IN_TABLE_MODE]: {
250        [Tokenizer.CHARACTER_TOKEN]: insertCharacters,
251        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
252        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
253        [Tokenizer.COMMENT_TOKEN]: appendComment,
254        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
255        [Tokenizer.START_TAG_TOKEN]: startTagInSelectInTable,
256        [Tokenizer.END_TAG_TOKEN]: endTagInSelectInTable,
257        [Tokenizer.EOF_TOKEN]: eofInBody
258    },
259    [IN_TEMPLATE_MODE]: {
260        [Tokenizer.CHARACTER_TOKEN]: characterInBody,
261        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
262        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
263        [Tokenizer.COMMENT_TOKEN]: appendComment,
264        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
265        [Tokenizer.START_TAG_TOKEN]: startTagInTemplate,
266        [Tokenizer.END_TAG_TOKEN]: endTagInTemplate,
267        [Tokenizer.EOF_TOKEN]: eofInTemplate
268    },
269    [AFTER_BODY_MODE]: {
270        [Tokenizer.CHARACTER_TOKEN]: tokenAfterBody,
271        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterBody,
272        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
273        [Tokenizer.COMMENT_TOKEN]: appendCommentToRootHtmlElement,
274        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
275        [Tokenizer.START_TAG_TOKEN]: startTagAfterBody,
276        [Tokenizer.END_TAG_TOKEN]: endTagAfterBody,
277        [Tokenizer.EOF_TOKEN]: stopParsing
278    },
279    [IN_FRAMESET_MODE]: {
280        [Tokenizer.CHARACTER_TOKEN]: ignoreToken,
281        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
282        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
283        [Tokenizer.COMMENT_TOKEN]: appendComment,
284        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
285        [Tokenizer.START_TAG_TOKEN]: startTagInFrameset,
286        [Tokenizer.END_TAG_TOKEN]: endTagInFrameset,
287        [Tokenizer.EOF_TOKEN]: stopParsing
288    },
289    [AFTER_FRAMESET_MODE]: {
290        [Tokenizer.CHARACTER_TOKEN]: ignoreToken,
291        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
292        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters,
293        [Tokenizer.COMMENT_TOKEN]: appendComment,
294        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
295        [Tokenizer.START_TAG_TOKEN]: startTagAfterFrameset,
296        [Tokenizer.END_TAG_TOKEN]: endTagAfterFrameset,
297        [Tokenizer.EOF_TOKEN]: stopParsing
298    },
299    [AFTER_AFTER_BODY_MODE]: {
300        [Tokenizer.CHARACTER_TOKEN]: tokenAfterAfterBody,
301        [Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterAfterBody,
302        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
303        [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument,
304        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
305        [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterBody,
306        [Tokenizer.END_TAG_TOKEN]: tokenAfterAfterBody,
307        [Tokenizer.EOF_TOKEN]: stopParsing
308    },
309    [AFTER_AFTER_FRAMESET_MODE]: {
310        [Tokenizer.CHARACTER_TOKEN]: ignoreToken,
311        [Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken,
312        [Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody,
313        [Tokenizer.COMMENT_TOKEN]: appendCommentToDocument,
314        [Tokenizer.DOCTYPE_TOKEN]: ignoreToken,
315        [Tokenizer.START_TAG_TOKEN]: startTagAfterAfterFrameset,
316        [Tokenizer.END_TAG_TOKEN]: ignoreToken,
317        [Tokenizer.EOF_TOKEN]: stopParsing
318    }
319};
320
321//Parser
322class Parser {
323    constructor(options) {
324        this.options = mergeOptions(DEFAULT_OPTIONS, options);
325
326        this.treeAdapter = this.options.treeAdapter;
327        this.pendingScript = null;
328        this.nodeInfo = {};
329
330        if(this.options.componentValidator){
331            this.validator = this.options.componentValidator;
332        }
333
334        if(this.options.compileResult){
335            this.compileResult = this.options.compileResult;
336        }
337
338        if (this.options.sourceCodeLocationInfo) {
339            Mixin.install(this, LocationInfoParserMixin);
340        }
341
342        if (this.options.onParseError) {
343            Mixin.install(this, ErrorReportingParserMixin, { onParseError: this.options.onParseError });
344        }
345    }
346
347    // API
348    parse(html) {
349        const document = this.treeAdapter.createDocument();
350
351        this._bootstrap(document, null);
352        this.tokenizer.write(html, true);
353        this._runParsingLoop(null);
354
355        return document;
356    }
357
358    parseFragment(html, fragmentContext) {
359        //NOTE: use <template> element as a fragment context if context element was not provided,
360        //so we will parse in "forgiving" manner
361        if (!fragmentContext) {
362            fragmentContext = this.treeAdapter.createElement($.TEMPLATE, NS.HTML, []);
363        }
364
365        //NOTE: create fake element which will be used as 'document' for fragment parsing.
366        //This is important for jsdom there 'document' can't be recreated, therefore
367        //fragment parsing causes messing of the main `document`.
368        const documentMock = this.treeAdapter.createElement('documentmock', NS.HTML, []);
369
370        this._bootstrap(documentMock, fragmentContext);
371
372        if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE) {
373            this._pushTmplInsertionMode(IN_TEMPLATE_MODE);
374        }
375
376        this._initTokenizerForFragmentParsing();
377        this._insertFakeRootElement();
378        this._resetInsertionMode();
379        this._findFormInFragmentContext();
380        this.tokenizer.write(html, true);
381        this._runParsingLoop(null);
382
383        const rootElement = this.treeAdapter.getFirstChild(documentMock);
384        const fragment = this.treeAdapter.createDocumentFragment();
385
386        this._adoptNodes(rootElement, fragment);
387
388        return fragment;
389    }
390
391    //Bootstrap parser
392    _bootstrap(document, fragmentContext) {
393        this.tokenizer = new Tokenizer(this.options);
394
395        this.stopped = false;
396
397        this.insertionMode = INITIAL_MODE;
398        this.originalInsertionMode = '';
399
400        this.document = document;
401        this.fragmentContext = fragmentContext;
402
403        this.headElement = null;
404        this.formElement = null;
405
406        this.openElements = new OpenElementStack(this.document, this.treeAdapter);
407        this.activeFormattingElements = new FormattingElementList(this.treeAdapter);
408
409        this.tmplInsertionModeStack = [];
410        this.tmplInsertionModeStackTop = -1;
411        this.currentTmplInsertionMode = null;
412
413        this.pendingCharacterTokens = [];
414        this.hasNonWhitespacePendingCharacterToken = false;
415
416        this.framesetOk = true;
417        this.skipNextNewLine = false;
418        this.fosterParentingEnabled = false;
419    }
420
421    //Errors
422    _err() {
423        // NOTE: err reporting is noop by default. Enabled by mixin.
424    }
425
426    //Parsing loop
427    _runParsingLoop(scriptHandler) {
428        let lastToken = {};
429        while (!this.stopped) {
430          this._setupTokenizerCDATAMode();
431          const token = this.tokenizer.getNextToken();
432          if (token.type === Tokenizer.HIBERNATION_TOKEN) {
433            break;
434          }
435          if (token.type !== Tokenizer.EOF_TOKEN && token.type !== Tokenizer.WHITESPACE_CHARACTER_TOKEN) {
436            lastToken =token;
437          }
438          checkselfClosingNode(this, token);
439          if (this.skipNextNewLine) {
440            this.skipNextNewLine = false;
441            if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') {
442              if (token.chars.length === 1) {
443                continue;
444              }
445              token.chars = token.chars.substr(1);
446            }
447          }
448          this._processInputToken(token);
449          if (scriptHandler && this.pendingScript) {
450            break;
451          }
452        }
453        checkInvalid(this, lastToken);
454    }
455
456    runParsingLoopForCurrentChunk(writeCallback, scriptHandler) {
457        this._runParsingLoop(scriptHandler);
458
459        if (scriptHandler && this.pendingScript) {
460            const script = this.pendingScript;
461
462            this.pendingScript = null;
463
464            scriptHandler(script);
465
466            return;
467        }
468
469        if (writeCallback) {
470            writeCallback();
471        }
472    }
473
474    //Text parsing
475    _setupTokenizerCDATAMode() {
476        const current = this._getAdjustedCurrentElement();
477
478        this.tokenizer.allowCDATA =
479            current &&
480            current !== this.document &&
481            this.treeAdapter.getNamespaceURI(current) !== NS.HTML &&
482            !this._isIntegrationPoint(current);
483    }
484
485    _switchToTextParsing(currentToken, nextTokenizerState) {
486        this._insertElement(currentToken, NS.HTML);
487        this.tokenizer.state = nextTokenizerState;
488        this.originalInsertionMode = this.insertionMode;
489        this.insertionMode = TEXT_MODE;
490    }
491
492    switchToPlaintextParsing() {
493        this.insertionMode = TEXT_MODE;
494        this.originalInsertionMode = IN_BODY_MODE;
495        this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
496    }
497
498    //Fragment parsing
499    _getAdjustedCurrentElement() {
500        return this.openElements.stackTop === 0 && this.fragmentContext
501            ? this.fragmentContext
502            : this.openElements.current;
503    }
504
505    _findFormInFragmentContext() {
506        let node = this.fragmentContext;
507
508        do {
509            if (this.treeAdapter.getTagName(node) === $.FORM) {
510                this.formElement = node;
511                break;
512            }
513
514            node = this.treeAdapter.getParentNode(node);
515        } while (node);
516    }
517
518    _initTokenizerForFragmentParsing() {
519        if (this.treeAdapter.getNamespaceURI(this.fragmentContext) === NS.HTML) {
520            const tn = this.treeAdapter.getTagName(this.fragmentContext);
521
522            if (tn === $.TITLE || tn === $.TEXTAREA) {
523                this.tokenizer.state = Tokenizer.MODE.RCDATA;
524            } else if (
525                tn === $.STYLE ||
526                tn === $.XMP ||
527                tn === $.IFRAME ||
528                tn === $.NOEMBED ||
529                tn === $.NOFRAMES ||
530                tn === $.NOSCRIPT
531            ) {
532                this.tokenizer.state = Tokenizer.MODE.RAWTEXT;
533            } else if (tn === $.SCRIPT) {
534                this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA;
535            } else if (tn === $.PLAINTEXT) {
536                this.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
537            }
538        }
539    }
540
541    //Tree mutation
542    _setDocumentType(token) {
543        const name = token.name || '';
544        const publicId = token.publicId || '';
545        const systemId = token.systemId || '';
546
547        this.treeAdapter.setDocumentType(this.document, name, publicId, systemId);
548    }
549
550    _attachElementToTree(element) {
551        if (this._shouldFosterParentOnInsertion()) {
552            this._fosterParentElement(element);
553        } else {
554            const parent = this.openElements.currentTmplContent || this.openElements.current;
555
556            this.treeAdapter.appendChild(parent, element);
557        }
558    }
559
560    _appendElement(token, namespaceURI) {
561        const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
562
563        this._attachElementToTree(element);
564    }
565
566    _insertElement(token, namespaceURI) {
567        const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs);
568
569        this._attachElementToTree(element);
570        this.openElements.push(element);
571    }
572
573    _insertFakeElement(tagName) {
574        const element = this.treeAdapter.createElement(tagName, NS.HTML, []);
575
576        this._attachElementToTree(element);
577        this.openElements.push(element);
578    }
579
580    _insertTemplate(token) {
581        const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs);
582        const content = this.treeAdapter.createDocumentFragment();
583
584        this.treeAdapter.setTemplateContent(tmpl, content);
585        this._attachElementToTree(tmpl);
586        this.openElements.push(tmpl);
587    }
588
589    _insertFakeRootElement() {
590        const element = this.treeAdapter.createElement($.HTML, NS.HTML, []);
591
592        this.treeAdapter.appendChild(this.openElements.current, element);
593        this.openElements.push(element);
594    }
595
596    _appendCommentNode(token, parent) {
597        const commentNode = this.treeAdapter.createCommentNode(token.data);
598
599        this.treeAdapter.appendChild(parent, commentNode);
600    }
601
602    _insertCharacters(token) {
603        if (this._shouldFosterParentOnInsertion()) {
604            this._fosterParentText(token.chars);
605        } else {
606            const parent = this.openElements.currentTmplContent || this.openElements.current;
607
608            this.treeAdapter.insertText(parent, token.chars);
609        }
610    }
611
612    _adoptNodes(donor, recipient) {
613        for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) {
614            this.treeAdapter.detachNode(child);
615            this.treeAdapter.appendChild(recipient, child);
616        }
617    }
618
619    //Token processing
620    _shouldProcessTokenInForeignContent(token) {
621        const current = this._getAdjustedCurrentElement();
622
623        if (!current || current === this.document) {
624            return false;
625        }
626
627        const ns = this.treeAdapter.getNamespaceURI(current);
628
629        if (ns === NS.HTML) {
630            return false;
631        }
632
633        if (
634            this.treeAdapter.getTagName(current) === $.ANNOTATION_XML &&
635            ns === NS.MATHML &&
636            token.type === Tokenizer.START_TAG_TOKEN &&
637            token.tagName === $.SVG
638        ) {
639            return false;
640        }
641
642        const isCharacterToken =
643            token.type === Tokenizer.CHARACTER_TOKEN ||
644            token.type === Tokenizer.NULL_CHARACTER_TOKEN ||
645            token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN;
646
647        const isMathMLTextStartTag =
648            token.type === Tokenizer.START_TAG_TOKEN && token.tagName !== $.MGLYPH && token.tagName !== $.MALIGNMARK;
649
650        if ((isMathMLTextStartTag || isCharacterToken) && this._isIntegrationPoint(current, NS.MATHML)) {
651            return false;
652        }
653
654        if (
655            (token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) &&
656            this._isIntegrationPoint(current, NS.HTML)
657        ) {
658            return false;
659        }
660
661        return token.type !== Tokenizer.EOF_TOKEN;
662    }
663
664    _processToken(token) {
665        TOKEN_HANDLERS[this.insertionMode][token.type](this, token);
666    }
667
668    _processTokenInBodyMode(token) {
669        TOKEN_HANDLERS[IN_BODY_MODE][token.type](this, token);
670    }
671
672    _processTokenInForeignContent(token) {
673        if (token.type === Tokenizer.CHARACTER_TOKEN) {
674            characterInForeignContent(this, token);
675        } else if (token.type === Tokenizer.NULL_CHARACTER_TOKEN) {
676            nullCharacterInForeignContent(this, token);
677        } else if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN) {
678            insertCharacters(this, token);
679        } else if (token.type === Tokenizer.COMMENT_TOKEN) {
680            appendComment(this, token);
681        } else if (token.type === Tokenizer.START_TAG_TOKEN) {
682            startTagInForeignContent(this, token);
683        } else if (token.type === Tokenizer.END_TAG_TOKEN) {
684            endTagInForeignContent(this, token);
685        }
686    }
687
688    _processInputToken(token) {
689        if (this._shouldProcessTokenInForeignContent(token)) {
690            this._processTokenInForeignContent(token);
691        } else {
692            this._processToken(token);
693        }
694
695        if (token.type === Tokenizer.START_TAG_TOKEN && token.selfClosing && !token.ackSelfClosing) {
696            this._err(ERR.nonVoidHtmlElementStartTagWithTrailingSolidus);
697        }
698    }
699
700    //Integration points
701    _isIntegrationPoint(element, foreignNS) {
702        const tn = this.treeAdapter.getTagName(element);
703        const ns = this.treeAdapter.getNamespaceURI(element);
704        const attrs = this.treeAdapter.getAttrList(element);
705
706        return foreignContent.isIntegrationPoint(tn, ns, attrs, foreignNS);
707    }
708
709    //Active formatting elements reconstruction
710    _reconstructActiveFormattingElements() {
711        const listLength = this.activeFormattingElements.length;
712
713        if (listLength) {
714            let unopenIdx = listLength;
715            let entry = null;
716
717            do {
718                unopenIdx--;
719                entry = this.activeFormattingElements.entries[unopenIdx];
720
721                if (entry.type === FormattingElementList.MARKER_ENTRY || this.openElements.contains(entry.element)) {
722                    unopenIdx++;
723                    break;
724                }
725            } while (unopenIdx > 0);
726
727            for (let i = unopenIdx; i < listLength; i++) {
728                entry = this.activeFormattingElements.entries[i];
729                this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element));
730                entry.element = this.openElements.current;
731            }
732        }
733    }
734
735    //Close elements
736    _closeTableCell() {
737        this.openElements.generateImpliedEndTags();
738        this.openElements.popUntilTableCellPopped();
739        this.activeFormattingElements.clearToLastMarker();
740        this.insertionMode = IN_ROW_MODE;
741    }
742
743    _closePElement() {
744        this.openElements.generateImpliedEndTagsWithExclusion($.P);
745        this.openElements.popUntilTagNamePopped($.P);
746    }
747
748    //Insertion modes
749    _resetInsertionMode() {
750        for (let i = this.openElements.stackTop, last = false; i >= 0; i--) {
751            let element = this.openElements.items[i];
752
753            if (i === 0) {
754                last = true;
755
756                if (this.fragmentContext) {
757                    element = this.fragmentContext;
758                }
759            }
760
761            const tn = this.treeAdapter.getTagName(element);
762            const newInsertionMode = INSERTION_MODE_RESET_MAP[tn];
763
764            if (newInsertionMode) {
765                this.insertionMode = newInsertionMode;
766                break;
767            } else if (!last && (tn === $.TD || tn === $.TH)) {
768                this.insertionMode = IN_CELL_MODE;
769                break;
770            } else if (!last && tn === $.HEAD) {
771                this.insertionMode = IN_HEAD_MODE;
772                break;
773            } else if (tn === $.SELECT) {
774                this._resetInsertionModeForSelect(i);
775                break;
776            } else if (tn === $.TEMPLATE) {
777                this.insertionMode = this.currentTmplInsertionMode;
778                break;
779            } else if (tn === $.HTML) {
780                this.insertionMode = this.headElement ? AFTER_HEAD_MODE : BEFORE_HEAD_MODE;
781                break;
782            } else if (last) {
783                this.insertionMode = IN_BODY_MODE;
784                break;
785            }
786        }
787    }
788
789    _resetInsertionModeForSelect(selectIdx) {
790        if (selectIdx > 0) {
791            for (let i = selectIdx - 1; i > 0; i--) {
792                const ancestor = this.openElements.items[i];
793                const tn = this.treeAdapter.getTagName(ancestor);
794
795                if (tn === $.TEMPLATE) {
796                    break;
797                } else if (tn === $.TABLE) {
798                    this.insertionMode = IN_SELECT_IN_TABLE_MODE;
799                    return;
800                }
801            }
802        }
803
804        this.insertionMode = IN_SELECT_MODE;
805    }
806
807    _pushTmplInsertionMode(mode) {
808        this.tmplInsertionModeStack.push(mode);
809        this.tmplInsertionModeStackTop++;
810        this.currentTmplInsertionMode = mode;
811    }
812
813    _popTmplInsertionMode() {
814        this.tmplInsertionModeStack.pop();
815        this.tmplInsertionModeStackTop--;
816        this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop];
817    }
818
819    //Foster parenting
820    _isElementCausesFosterParenting(element) {
821        const tn = this.treeAdapter.getTagName(element);
822
823        return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR;
824    }
825
826    _shouldFosterParentOnInsertion() {
827        return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current);
828    }
829
830    _findFosterParentingLocation() {
831        const location = {
832            parent: null,
833            beforeElement: null
834        };
835
836        for (let i = this.openElements.stackTop; i >= 0; i--) {
837            const openElement = this.openElements.items[i];
838            const tn = this.treeAdapter.getTagName(openElement);
839            const ns = this.treeAdapter.getNamespaceURI(openElement);
840
841            if (tn === $.TEMPLATE && ns === NS.HTML) {
842                location.parent = this.treeAdapter.getTemplateContent(openElement);
843                break;
844            } else if (tn === $.TABLE) {
845                location.parent = this.treeAdapter.getParentNode(openElement);
846
847                if (location.parent) {
848                    location.beforeElement = openElement;
849                } else {
850                    location.parent = this.openElements.items[i - 1];
851                }
852
853                break;
854            }
855        }
856
857        if (!location.parent) {
858            location.parent = this.openElements.items[0];
859        }
860
861        return location;
862    }
863
864    _fosterParentElement(element) {
865        const location = this._findFosterParentingLocation();
866
867        if (location.beforeElement) {
868            this.treeAdapter.insertBefore(location.parent, element, location.beforeElement);
869        } else {
870            this.treeAdapter.appendChild(location.parent, element);
871        }
872    }
873
874    _fosterParentText(chars) {
875        const location = this._findFosterParentingLocation();
876
877        if (location.beforeElement) {
878            this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement);
879        } else {
880            this.treeAdapter.insertText(location.parent, chars);
881        }
882    }
883
884    //Special elements
885    _isSpecialElement(element) {
886        const tn = this.treeAdapter.getTagName(element);
887        const ns = this.treeAdapter.getNamespaceURI(element);
888
889        return HTML.SPECIAL_ELEMENTS[ns][tn];
890    }
891}
892
893/**
894 * Check if the node is self closing.
895 * @param {Object} parse parse5 object.
896 * @param {Object} token Hml text token information.
897 */
898function checkselfClosingNode(parse, token) {
899  const tagName = (token.tagName || "").toLowerCase();
900  const selfClosing = token.selfClosing;
901  const flag = parse.validator.isSupportedSelfClosing(tagName);
902  if (parse.nodeInfo.tn && tagName && !parse.nodeInfo.sc) {
903    const loc =
904      String(token.location.startLine) + String(token.location.startCol);
905    if (
906      !flag ||
907      (loc !== parse.nodeInfo.pos && token.type === Tokenizer.START_TAG_TOKEN)
908    ) {
909      parse.compileResult.log.push({
910        line: String(token.location.startLine) || 1,
911        column: String(token.location.startCol) || 1,
912        reason: 'ERROR: tag `' + parse.nodeInfo.tn + '` must be closed, please follow norm',
913      });
914      parse.nodeInfo = {};
915    }
916  }
917  if (tagName && flag) {
918    if (token.type === Tokenizer.START_TAG_TOKEN && !selfClosing) {
919      parse.nodeInfo.tn = tagName;
920      parse.nodeInfo.sc = false;
921      parse.nodeInfo.pos =
922        String(token.location.line) + String(token.location.col);
923    }
924    if (
925      token.type === Tokenizer.END_TAG_TOKEN &&
926      tagName === parse.nodeInfo.tn
927    ) {
928      parse.nodeInfo.sc = true;
929    }
930  }
931  if (!flag && selfClosing && token.type === Tokenizer.START_TAG_TOKEN) {
932    parse.compileResult.log.push({
933      line: token.location.startLine || 1,
934      column: token.location.startCol || 1,
935      reason: "ERROR: tag `" + tagName + "` can not use selfClosing",
936    });
937  }
938}
939
940/**
941 * Check if the html text is legal.
942 * @param {Object} lastToken Hml text last token information.
943 */
944function checkInvalid(lastToken) {
945  if (
946    lastToken.type && lastToken.type !== Tokenizer.END_TAG_TOKEN &&
947    lastToken.type !== Tokenizer.COMMENT_TOKEN
948  ) {
949    compileResult.log.push({
950    line: lastToken.location.startLine || 1,
951    column: lastToken.location.startCol || 1,
952    reason: "ERROR: hml content is invalid. Please check it.",
953    });
954  }
955}
956
957module.exports = Parser;
958
959//Adoption agency algorithm
960//(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency)
961//------------------------------------------------------------------
962
963//Steps 5-8 of the algorithm
964function aaObtainFormattingElementEntry(p, token) {
965    let formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName);
966
967    if (formattingElementEntry) {
968        if (!p.openElements.contains(formattingElementEntry.element)) {
969            p.activeFormattingElements.removeEntry(formattingElementEntry);
970            formattingElementEntry = null;
971        } else if (!p.openElements.hasInScope(token.tagName)) {
972            formattingElementEntry = null;
973        }
974    } else {
975        genericEndTagInBody(p, token);
976    }
977
978    return formattingElementEntry;
979}
980
981//Steps 9 and 10 of the algorithm
982function aaObtainFurthestBlock(p, formattingElementEntry) {
983    let furthestBlock = null;
984
985    for (let i = p.openElements.stackTop; i >= 0; i--) {
986        const element = p.openElements.items[i];
987
988        if (element === formattingElementEntry.element) {
989            break;
990        }
991
992        if (p._isSpecialElement(element)) {
993            furthestBlock = element;
994        }
995    }
996
997    if (!furthestBlock) {
998        p.openElements.popUntilElementPopped(formattingElementEntry.element);
999        p.activeFormattingElements.removeEntry(formattingElementEntry);
1000    }
1001
1002    return furthestBlock;
1003}
1004
1005//Step 13 of the algorithm
1006function aaInnerLoop(p, furthestBlock, formattingElement) {
1007    let lastElement = furthestBlock;
1008    let nextElement = p.openElements.getCommonAncestor(furthestBlock);
1009
1010    for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) {
1011        //NOTE: store next element for the next loop iteration (it may be deleted from the stack by step 9.5)
1012        nextElement = p.openElements.getCommonAncestor(element);
1013
1014        const elementEntry = p.activeFormattingElements.getElementEntry(element);
1015        const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER;
1016        const shouldRemoveFromOpenElements = !elementEntry || counterOverflow;
1017
1018        if (shouldRemoveFromOpenElements) {
1019            if (counterOverflow) {
1020                p.activeFormattingElements.removeEntry(elementEntry);
1021            }
1022
1023            p.openElements.remove(element);
1024        } else {
1025            element = aaRecreateElementFromEntry(p, elementEntry);
1026
1027            if (lastElement === furthestBlock) {
1028                p.activeFormattingElements.bookmark = elementEntry;
1029            }
1030
1031            p.treeAdapter.detachNode(lastElement);
1032            p.treeAdapter.appendChild(element, lastElement);
1033            lastElement = element;
1034        }
1035    }
1036
1037    return lastElement;
1038}
1039
1040//Step 13.7 of the algorithm
1041function aaRecreateElementFromEntry(p, elementEntry) {
1042    const ns = p.treeAdapter.getNamespaceURI(elementEntry.element);
1043    const newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs);
1044
1045    p.openElements.replace(elementEntry.element, newElement);
1046    elementEntry.element = newElement;
1047
1048    return newElement;
1049}
1050
1051//Step 14 of the algorithm
1052function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) {
1053    if (p._isElementCausesFosterParenting(commonAncestor)) {
1054        p._fosterParentElement(lastElement);
1055    } else {
1056        const tn = p.treeAdapter.getTagName(commonAncestor);
1057        const ns = p.treeAdapter.getNamespaceURI(commonAncestor);
1058
1059        if (tn === $.TEMPLATE && ns === NS.HTML) {
1060            commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor);
1061        }
1062
1063        p.treeAdapter.appendChild(commonAncestor, lastElement);
1064    }
1065}
1066
1067//Steps 15-19 of the algorithm
1068function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) {
1069    const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element);
1070    const token = formattingElementEntry.token;
1071    const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs);
1072
1073    p._adoptNodes(furthestBlock, newElement);
1074    p.treeAdapter.appendChild(furthestBlock, newElement);
1075
1076    p.activeFormattingElements.insertElementAfterBookmark(newElement, formattingElementEntry.token);
1077    p.activeFormattingElements.removeEntry(formattingElementEntry);
1078
1079    p.openElements.remove(formattingElementEntry.element);
1080    p.openElements.insertAfter(furthestBlock, newElement);
1081}
1082
1083//Algorithm entry point
1084function callAdoptionAgency(p, token) {
1085    let formattingElementEntry;
1086
1087    for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) {
1088        formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry);
1089
1090        if (!formattingElementEntry) {
1091            break;
1092        }
1093
1094        const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry);
1095
1096        if (!furthestBlock) {
1097            break;
1098        }
1099
1100        p.activeFormattingElements.bookmark = formattingElementEntry;
1101
1102        const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element);
1103        const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element);
1104
1105        p.treeAdapter.detachNode(lastElement);
1106        aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement);
1107        aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry);
1108    }
1109}
1110
1111//Generic token handlers
1112//------------------------------------------------------------------
1113function ignoreToken() {
1114    //NOTE: do nothing =)
1115}
1116
1117function misplacedDoctype(p) {
1118    p._err(ERR.misplacedDoctype);
1119}
1120
1121function appendComment(p, token) {
1122    p._appendCommentNode(token, p.openElements.currentTmplContent || p.openElements.current);
1123}
1124
1125function appendCommentToRootHtmlElement(p, token) {
1126    p._appendCommentNode(token, p.openElements.items[0]);
1127}
1128
1129function appendCommentToDocument(p, token) {
1130    p._appendCommentNode(token, p.document);
1131}
1132
1133function insertCharacters(p, token) {
1134    p._insertCharacters(token);
1135}
1136
1137function stopParsing(p) {
1138    p.stopped = true;
1139}
1140
1141// The "initial" insertion mode
1142//------------------------------------------------------------------
1143function doctypeInInitialMode(p, token) {
1144    p._setDocumentType(token);
1145
1146    const mode = token.forceQuirks ? HTML.DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token);
1147
1148    if (!doctype.isConforming(token)) {
1149        p._err(ERR.nonConformingDoctype);
1150    }
1151
1152    p.treeAdapter.setDocumentMode(p.document, mode);
1153
1154    p.insertionMode = BEFORE_HTML_MODE;
1155}
1156
1157function tokenInInitialMode(p, token) {
1158    p._err(ERR.missingDoctype, { beforeToken: true });
1159    p.treeAdapter.setDocumentMode(p.document, HTML.DOCUMENT_MODE.QUIRKS);
1160    p.insertionMode = BEFORE_HTML_MODE;
1161    p._processToken(token);
1162}
1163
1164// The "before html" insertion mode
1165//------------------------------------------------------------------
1166function startTagBeforeHtml(p, token) {
1167    if (token.tagName === $.HTML) {
1168        p._insertElement(token, NS.HTML);
1169        p.insertionMode = BEFORE_HEAD_MODE;
1170    } else {
1171        tokenBeforeHtml(p, token);
1172    }
1173}
1174
1175function endTagBeforeHtml(p, token) {
1176    const tn = token.tagName;
1177
1178    if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) {
1179        tokenBeforeHtml(p, token);
1180    }
1181}
1182
1183function tokenBeforeHtml(p, token) {
1184    p._insertFakeRootElement();
1185    p.insertionMode = BEFORE_HEAD_MODE;
1186    p._processToken(token);
1187}
1188
1189// The "before head" insertion mode
1190//------------------------------------------------------------------
1191function startTagBeforeHead(p, token) {
1192    const tn = token.tagName;
1193
1194    if (tn === $.HTML) {
1195        startTagInBody(p, token);
1196    } else if (tn === $.HEAD) {
1197        p._insertElement(token, NS.HTML);
1198        p.headElement = p.openElements.current;
1199        p.insertionMode = IN_HEAD_MODE;
1200    } else {
1201        tokenBeforeHead(p, token);
1202    }
1203}
1204
1205function endTagBeforeHead(p, token) {
1206    const tn = token.tagName;
1207
1208    if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) {
1209        tokenBeforeHead(p, token);
1210    } else {
1211        p._err(ERR.endTagWithoutMatchingOpenElement);
1212    }
1213}
1214
1215function tokenBeforeHead(p, token) {
1216    p._insertFakeElement($.HEAD);
1217    p.headElement = p.openElements.current;
1218    p.insertionMode = IN_HEAD_MODE;
1219    p._processToken(token);
1220}
1221
1222// The "in head" insertion mode
1223//------------------------------------------------------------------
1224function startTagInHead(p, token) {
1225    const tn = token.tagName;
1226
1227    if (tn === $.HTML) {
1228        startTagInBody(p, token);
1229    } else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META) {
1230        p._appendElement(token, NS.HTML);
1231        token.ackSelfClosing = true;
1232    } else if (tn === $.TITLE) {
1233        p._switchToTextParsing(token, Tokenizer.MODE.RCDATA);
1234    } else if (tn === $.NOSCRIPT) {
1235        if (p.options.scriptingEnabled) {
1236            p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1237        } else {
1238            p._insertElement(token, NS.HTML);
1239            p.insertionMode = IN_HEAD_NO_SCRIPT_MODE;
1240        }
1241    } else if (tn === $.NOFRAMES || tn === $.STYLE) {
1242        p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1243    } else if (tn === $.SCRIPT) {
1244        p._switchToTextParsing(token, Tokenizer.MODE.SCRIPT_DATA);
1245    } else if (tn === $.TEMPLATE) {
1246        p._insertTemplate(token, NS.HTML);
1247        p.activeFormattingElements.insertMarker();
1248        p.framesetOk = false;
1249        p.insertionMode = IN_TEMPLATE_MODE;
1250        p._pushTmplInsertionMode(IN_TEMPLATE_MODE);
1251    } else if (tn === $.HEAD) {
1252        p._err(ERR.misplacedStartTagForHeadElement);
1253    } else {
1254        tokenInHead(p, token);
1255    }
1256}
1257
1258function endTagInHead(p, token) {
1259    const tn = token.tagName;
1260
1261    if (tn === $.HEAD) {
1262        p.openElements.pop();
1263        p.insertionMode = AFTER_HEAD_MODE;
1264    } else if (tn === $.BODY || tn === $.BR || tn === $.HTML) {
1265        tokenInHead(p, token);
1266    } else if (tn === $.TEMPLATE) {
1267        if (p.openElements.tmplCount > 0) {
1268            p.openElements.generateImpliedEndTagsThoroughly();
1269
1270            if (p.openElements.currentTagName !== $.TEMPLATE) {
1271                p._err(ERR.closingOfElementWithOpenChildElements);
1272            }
1273
1274            p.openElements.popUntilTagNamePopped($.TEMPLATE);
1275            p.activeFormattingElements.clearToLastMarker();
1276            p._popTmplInsertionMode();
1277            p._resetInsertionMode();
1278        } else {
1279            p._err(ERR.endTagWithoutMatchingOpenElement);
1280        }
1281    } else {
1282        p._err(ERR.endTagWithoutMatchingOpenElement);
1283    }
1284}
1285
1286function tokenInHead(p, token) {
1287    p.openElements.pop();
1288    p.insertionMode = AFTER_HEAD_MODE;
1289    p._processToken(token);
1290}
1291
1292// The "in head no script" insertion mode
1293//------------------------------------------------------------------
1294function startTagInHeadNoScript(p, token) {
1295    const tn = token.tagName;
1296
1297    if (tn === $.HTML) {
1298        startTagInBody(p, token);
1299    } else if (
1300        tn === $.BASEFONT ||
1301        tn === $.BGSOUND ||
1302        tn === $.HEAD ||
1303        tn === $.LINK ||
1304        tn === $.META ||
1305        tn === $.NOFRAMES ||
1306        tn === $.STYLE
1307    ) {
1308        startTagInHead(p, token);
1309    } else if (tn === $.NOSCRIPT) {
1310        p._err(ERR.nestedNoscriptInHead);
1311    } else {
1312        tokenInHeadNoScript(p, token);
1313    }
1314}
1315
1316function endTagInHeadNoScript(p, token) {
1317    const tn = token.tagName;
1318
1319    if (tn === $.NOSCRIPT) {
1320        p.openElements.pop();
1321        p.insertionMode = IN_HEAD_MODE;
1322    } else if (tn === $.BR) {
1323        tokenInHeadNoScript(p, token);
1324    } else {
1325        p._err(ERR.endTagWithoutMatchingOpenElement);
1326    }
1327}
1328
1329function tokenInHeadNoScript(p, token) {
1330    const errCode =
1331        token.type === Tokenizer.EOF_TOKEN ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead;
1332
1333    p._err(errCode);
1334    p.openElements.pop();
1335    p.insertionMode = IN_HEAD_MODE;
1336    p._processToken(token);
1337}
1338
1339// The "after head" insertion mode
1340//------------------------------------------------------------------
1341function startTagAfterHead(p, token) {
1342    const tn = token.tagName;
1343
1344    if (tn === $.HTML) {
1345        startTagInBody(p, token);
1346    } else if (tn === $.BODY) {
1347        p._insertElement(token, NS.HTML);
1348        p.framesetOk = false;
1349        p.insertionMode = IN_BODY_MODE;
1350    } else if (tn === $.FRAMESET) {
1351        p._insertElement(token, NS.HTML);
1352        p.insertionMode = IN_FRAMESET_MODE;
1353    } else if (
1354        tn === $.BASE ||
1355        tn === $.BASEFONT ||
1356        tn === $.BGSOUND ||
1357        tn === $.LINK ||
1358        tn === $.META ||
1359        tn === $.NOFRAMES ||
1360        tn === $.SCRIPT ||
1361        tn === $.STYLE ||
1362        tn === $.TEMPLATE ||
1363        tn === $.TITLE
1364    ) {
1365        p._err(ERR.abandonedHeadElementChild);
1366        p.openElements.push(p.headElement);
1367        startTagInHead(p, token);
1368        p.openElements.remove(p.headElement);
1369    } else if (tn === $.HEAD) {
1370        p._err(ERR.misplacedStartTagForHeadElement);
1371    } else {
1372        tokenAfterHead(p, token);
1373    }
1374}
1375
1376function endTagAfterHead(p, token) {
1377    const tn = token.tagName;
1378
1379    if (tn === $.BODY || tn === $.HTML || tn === $.BR) {
1380        tokenAfterHead(p, token);
1381    } else if (tn === $.TEMPLATE) {
1382        endTagInHead(p, token);
1383    } else {
1384        p._err(ERR.endTagWithoutMatchingOpenElement);
1385    }
1386}
1387
1388function tokenAfterHead(p, token) {
1389    p._insertFakeElement($.BODY);
1390    p.insertionMode = IN_BODY_MODE;
1391    p._processToken(token);
1392}
1393
1394// The "in body" insertion mode
1395//------------------------------------------------------------------
1396function whitespaceCharacterInBody(p, token) {
1397    p._reconstructActiveFormattingElements();
1398    p._insertCharacters(token);
1399}
1400
1401function characterInBody(p, token) {
1402    p._reconstructActiveFormattingElements();
1403    p._insertCharacters(token);
1404    p.framesetOk = false;
1405}
1406
1407function htmlStartTagInBody(p, token) {
1408    if (p.openElements.tmplCount === 0) {
1409        p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs);
1410    }
1411}
1412
1413function bodyStartTagInBody(p, token) {
1414    const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
1415
1416    if (bodyElement && p.openElements.tmplCount === 0) {
1417        p.framesetOk = false;
1418        p.treeAdapter.adoptAttributes(bodyElement, token.attrs);
1419    }
1420}
1421
1422function framesetStartTagInBody(p, token) {
1423    const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement();
1424
1425    if (p.framesetOk && bodyElement) {
1426        p.treeAdapter.detachNode(bodyElement);
1427        p.openElements.popAllUpToHtmlElement();
1428        p._insertElement(token, NS.HTML);
1429        p.insertionMode = IN_FRAMESET_MODE;
1430    }
1431}
1432
1433function addressStartTagInBody(p, token) {
1434    if (p.openElements.hasInButtonScope($.P)) {
1435        p._closePElement();
1436    }
1437
1438    p._insertElement(token, NS.HTML);
1439}
1440
1441function numberedHeaderStartTagInBody(p, token) {
1442    if (p.openElements.hasInButtonScope($.P)) {
1443        p._closePElement();
1444    }
1445
1446    const tn = p.openElements.currentTagName;
1447
1448    if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
1449        p.openElements.pop();
1450    }
1451
1452    p._insertElement(token, NS.HTML);
1453}
1454
1455function preStartTagInBody(p, token) {
1456    if (p.openElements.hasInButtonScope($.P)) {
1457        p._closePElement();
1458    }
1459
1460    p._insertElement(token, NS.HTML);
1461    //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
1462    //on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.)
1463    p.skipNextNewLine = true;
1464    p.framesetOk = false;
1465}
1466
1467function formStartTagInBody(p, token) {
1468    const inTemplate = p.openElements.tmplCount > 0;
1469
1470    if (!p.formElement || inTemplate) {
1471        if (p.openElements.hasInButtonScope($.P)) {
1472            p._closePElement();
1473        }
1474
1475        p._insertElement(token, NS.HTML);
1476
1477        if (!inTemplate) {
1478            p.formElement = p.openElements.current;
1479        }
1480    }
1481}
1482
1483function listItemStartTagInBody(p, token) {
1484    p.framesetOk = false;
1485
1486    const tn = token.tagName;
1487
1488    for (let i = p.openElements.stackTop; i >= 0; i--) {
1489        const element = p.openElements.items[i];
1490        const elementTn = p.treeAdapter.getTagName(element);
1491        let closeTn = null;
1492
1493        if (tn === $.LI && elementTn === $.LI) {
1494            closeTn = $.LI;
1495        } else if ((tn === $.DD || tn === $.DT) && (elementTn === $.DD || elementTn === $.DT)) {
1496            closeTn = elementTn;
1497        }
1498
1499        if (closeTn) {
1500            p.openElements.generateImpliedEndTagsWithExclusion(closeTn);
1501            p.openElements.popUntilTagNamePopped(closeTn);
1502            break;
1503        }
1504
1505        if (elementTn !== $.ADDRESS && elementTn !== $.DIV && elementTn !== $.P && p._isSpecialElement(element)) {
1506            break;
1507        }
1508    }
1509
1510    if (p.openElements.hasInButtonScope($.P)) {
1511        p._closePElement();
1512    }
1513
1514    p._insertElement(token, NS.HTML);
1515}
1516
1517function plaintextStartTagInBody(p, token) {
1518    if (p.openElements.hasInButtonScope($.P)) {
1519        p._closePElement();
1520    }
1521
1522    p._insertElement(token, NS.HTML);
1523    p.tokenizer.state = Tokenizer.MODE.PLAINTEXT;
1524}
1525
1526function buttonStartTagInBody(p, token) {
1527    if (p.openElements.hasInScope($.BUTTON)) {
1528        p.openElements.generateImpliedEndTags();
1529        p.openElements.popUntilTagNamePopped($.BUTTON);
1530    }
1531
1532    p._reconstructActiveFormattingElements();
1533    p._insertElement(token, NS.HTML);
1534    p.framesetOk = false;
1535}
1536
1537function aStartTagInBody(p, token) {
1538    const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName($.A);
1539
1540    if (activeElementEntry) {
1541        callAdoptionAgency(p, token);
1542        p.openElements.remove(activeElementEntry.element);
1543        p.activeFormattingElements.removeEntry(activeElementEntry);
1544    }
1545
1546    p._reconstructActiveFormattingElements();
1547    p._insertElement(token, NS.HTML);
1548    p.activeFormattingElements.pushElement(p.openElements.current, token);
1549}
1550
1551function bStartTagInBody(p, token) {
1552    p._reconstructActiveFormattingElements();
1553    p._insertElement(token, NS.HTML);
1554    p.activeFormattingElements.pushElement(p.openElements.current, token);
1555}
1556
1557function nobrStartTagInBody(p, token) {
1558    p._reconstructActiveFormattingElements();
1559
1560    if (p.openElements.hasInScope($.NOBR)) {
1561        callAdoptionAgency(p, token);
1562        p._reconstructActiveFormattingElements();
1563    }
1564
1565    p._insertElement(token, NS.HTML);
1566    p.activeFormattingElements.pushElement(p.openElements.current, token);
1567}
1568
1569function appletStartTagInBody(p, token) {
1570    p._reconstructActiveFormattingElements();
1571    p._insertElement(token, NS.HTML);
1572    p.activeFormattingElements.insertMarker();
1573    p.framesetOk = false;
1574}
1575
1576function tableStartTagInBody(p, token) {
1577    if (
1578        p.treeAdapter.getDocumentMode(p.document) !== HTML.DOCUMENT_MODE.QUIRKS &&
1579        p.openElements.hasInButtonScope($.P)
1580    ) {
1581        p._closePElement();
1582    }
1583
1584    p._insertElement(token, NS.HTML);
1585    p.framesetOk = false;
1586    p.insertionMode = IN_TABLE_MODE;
1587}
1588
1589function areaStartTagInBody(p, token) {
1590    p._reconstructActiveFormattingElements();
1591    p._appendElement(token, NS.HTML);
1592    p.framesetOk = false;
1593    token.ackSelfClosing = true;
1594}
1595
1596function inputStartTagInBody(p, token) {
1597    p._reconstructActiveFormattingElements();
1598    p._appendElement(token, NS.HTML);
1599
1600    const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
1601
1602    if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE) {
1603        p.framesetOk = false;
1604    }
1605
1606    token.ackSelfClosing = true;
1607}
1608
1609function paramStartTagInBody(p, token) {
1610    p._appendElement(token, NS.HTML);
1611    token.ackSelfClosing = true;
1612}
1613
1614function hrStartTagInBody(p, token) {
1615    if (p.openElements.hasInButtonScope($.P)) {
1616        p._closePElement();
1617    }
1618
1619    p._appendElement(token, NS.HTML);
1620    p.framesetOk = false;
1621    p.ackSelfClosing = true;
1622}
1623
1624function imageStartTagInBody(p, token) {
1625    token.tagName = $.IMG;
1626    areaStartTagInBody(p, token);
1627}
1628
1629function textareaStartTagInBody(p, token) {
1630    p._insertElement(token, NS.HTML);
1631    //NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move
1632    //on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.)
1633    p.skipNextNewLine = true;
1634    p.tokenizer.state = Tokenizer.MODE.RCDATA;
1635    p.originalInsertionMode = p.insertionMode;
1636    p.framesetOk = false;
1637    p.insertionMode = TEXT_MODE;
1638}
1639
1640function xmpStartTagInBody(p, token) {
1641    if (p.openElements.hasInButtonScope($.P)) {
1642        p._closePElement();
1643    }
1644
1645    p._reconstructActiveFormattingElements();
1646    p.framesetOk = false;
1647    p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1648}
1649
1650function iframeStartTagInBody(p, token) {
1651    p.framesetOk = false;
1652    p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1653}
1654
1655//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse
1656//<noembed> as a rawtext.
1657function noembedStartTagInBody(p, token) {
1658    p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT);
1659}
1660
1661function selectStartTagInBody(p, token) {
1662    p._reconstructActiveFormattingElements();
1663    p._insertElement(token, NS.HTML);
1664    p.framesetOk = false;
1665
1666    if (
1667        p.insertionMode === IN_TABLE_MODE ||
1668        p.insertionMode === IN_CAPTION_MODE ||
1669        p.insertionMode === IN_TABLE_BODY_MODE ||
1670        p.insertionMode === IN_ROW_MODE ||
1671        p.insertionMode === IN_CELL_MODE
1672    ) {
1673        p.insertionMode = IN_SELECT_IN_TABLE_MODE;
1674    } else {
1675        p.insertionMode = IN_SELECT_MODE;
1676    }
1677}
1678
1679function optgroupStartTagInBody(p, token) {
1680    if (p.openElements.currentTagName === $.OPTION) {
1681        p.openElements.pop();
1682    }
1683
1684    p._reconstructActiveFormattingElements();
1685    p._insertElement(token, NS.HTML);
1686}
1687
1688function rbStartTagInBody(p, token) {
1689    if (p.openElements.hasInScope($.RUBY)) {
1690        p.openElements.generateImpliedEndTags();
1691    }
1692
1693    p._insertElement(token, NS.HTML);
1694}
1695
1696function rtStartTagInBody(p, token) {
1697    if (p.openElements.hasInScope($.RUBY)) {
1698        p.openElements.generateImpliedEndTagsWithExclusion($.RTC);
1699    }
1700
1701    p._insertElement(token, NS.HTML);
1702}
1703
1704function menuStartTagInBody(p, token) {
1705    if (p.openElements.hasInButtonScope($.P)) {
1706        p._closePElement();
1707    }
1708
1709    p._insertElement(token, NS.HTML);
1710}
1711
1712function mathStartTagInBody(p, token) {
1713    p._reconstructActiveFormattingElements();
1714
1715    foreignContent.adjustTokenMathMLAttrs(token);
1716    foreignContent.adjustTokenXMLAttrs(token);
1717
1718    if (token.selfClosing) {
1719        p._appendElement(token, NS.MATHML);
1720    } else {
1721        p._insertElement(token, NS.MATHML);
1722    }
1723
1724    token.ackSelfClosing = true;
1725}
1726
1727function svgStartTagInBody(p, token) {
1728    p._reconstructActiveFormattingElements();
1729
1730    foreignContent.adjustTokenSVGAttrs(token);
1731    foreignContent.adjustTokenXMLAttrs(token);
1732
1733    if (token.selfClosing) {
1734        p._appendElement(token, NS.SVG);
1735    } else {
1736        p._insertElement(token, NS.SVG);
1737    }
1738
1739    token.ackSelfClosing = true;
1740}
1741
1742function genericStartTagInBody(p, token) {
1743    p._reconstructActiveFormattingElements();
1744    p._insertElement(token, NS.HTML);
1745}
1746
1747//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
1748//It's faster than using dictionary.
1749function startTagInBody(p, token) {
1750    const tn = token.tagName;
1751
1752    switch (tn.length) {
1753        case 1:
1754            if (tn === $.I || tn === $.S || tn === $.B || tn === $.U) {
1755                bStartTagInBody(p, token);
1756            } else if (tn === $.P) {
1757                addressStartTagInBody(p, token);
1758            } else if (tn === $.A) {
1759                aStartTagInBody(p, token);
1760            } else {
1761                genericStartTagInBody(p, token);
1762            }
1763
1764            break;
1765
1766        case 2:
1767            if (tn === $.DL || tn === $.OL || tn === $.UL) {
1768                addressStartTagInBody(p, token);
1769            } else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
1770                numberedHeaderStartTagInBody(p, token);
1771            } else if (tn === $.LI || tn === $.DD || tn === $.DT) {
1772                listItemStartTagInBody(p, token);
1773            } else if (tn === $.EM || tn === $.TT) {
1774                bStartTagInBody(p, token);
1775            } else if (tn === $.BR) {
1776                areaStartTagInBody(p, token);
1777            } else if (tn === $.HR) {
1778                hrStartTagInBody(p, token);
1779            } else if (tn === $.RB) {
1780                rbStartTagInBody(p, token);
1781            } else if (tn === $.RT || tn === $.RP) {
1782                rtStartTagInBody(p, token);
1783            } else if (tn !== $.TH && tn !== $.TD && tn !== $.TR) {
1784                genericStartTagInBody(p, token);
1785            }
1786
1787            break;
1788
1789        case 3:
1790            if (tn === $.DIV || tn === $.DIR || tn === $.NAV) {
1791                addressStartTagInBody(p, token);
1792            } else if (tn === $.PRE) {
1793                preStartTagInBody(p, token);
1794            } else if (tn === $.BIG) {
1795                bStartTagInBody(p, token);
1796            } else if (tn === $.IMG || tn === $.WBR) {
1797                areaStartTagInBody(p, token);
1798            } else if (tn === $.XMP) {
1799                xmpStartTagInBody(p, token);
1800            } else if (tn === $.SVG) {
1801                svgStartTagInBody(p, token);
1802            } else if (tn === $.RTC) {
1803                rbStartTagInBody(p, token);
1804            } else if (tn !== $.COL) {
1805                genericStartTagInBody(p, token);
1806            }
1807
1808            break;
1809
1810        case 4:
1811            if (tn === $.HTML) {
1812                htmlStartTagInBody(p, token);
1813            } else if (tn === $.BASE || tn === $.LINK || tn === $.META) {
1814                startTagInHead(p, token);
1815            } else if (tn === $.BODY) {
1816                bodyStartTagInBody(p, token);
1817            } else if (tn === $.MAIN || tn === $.MENU) {
1818                addressStartTagInBody(p, token);
1819            } else if (tn === $.FORM) {
1820                formStartTagInBody(p, token);
1821            } else if (tn === $.CODE || tn === $.FONT) {
1822                bStartTagInBody(p, token);
1823            } else if (tn === $.NOBR) {
1824                nobrStartTagInBody(p, token);
1825            } else if (tn === $.AREA) {
1826                areaStartTagInBody(p, token);
1827            } else if (tn === $.MATH) {
1828                mathStartTagInBody(p, token);
1829            } else if (tn === $.MENU) {
1830                menuStartTagInBody(p, token);
1831            } else if (tn !== $.HEAD) {
1832                genericStartTagInBody(p, token);
1833            }
1834
1835            break;
1836
1837        case 5:
1838            if (tn === $.STYLE || tn === $.TITLE) {
1839                startTagInHead(p, token);
1840            } else if (tn === $.ASIDE) {
1841                addressStartTagInBody(p, token);
1842            } else if (tn === $.SMALL) {
1843                bStartTagInBody(p, token);
1844            } else if (tn === $.TABLE) {
1845                tableStartTagInBody(p, token);
1846            } else if (tn === $.EMBED) {
1847                areaStartTagInBody(p, token);
1848            } else if (tn === $.INPUT) {
1849                inputStartTagInBody(p, token);
1850            } else if (tn === $.PARAM || tn === $.TRACK) {
1851                paramStartTagInBody(p, token);
1852            } else if (tn === $.IMAGE) {
1853                imageStartTagInBody(p, token);
1854            } else if (tn !== $.FRAME && tn !== $.TBODY && tn !== $.TFOOT && tn !== $.THEAD) {
1855                genericStartTagInBody(p, token);
1856            }
1857
1858            break;
1859
1860        case 6:
1861            if (tn === $.SCRIPT) {
1862                startTagInHead(p, token);
1863            } else if (
1864                tn === $.CENTER ||
1865                tn === $.FIGURE ||
1866                tn === $.FOOTER ||
1867                tn === $.HEADER ||
1868                tn === $.HGROUP ||
1869                tn === $.DIALOG
1870            ) {
1871                addressStartTagInBody(p, token);
1872            } else if (tn === $.BUTTON) {
1873                buttonStartTagInBody(p, token);
1874            } else if (tn === $.STRIKE || tn === $.STRONG) {
1875                bStartTagInBody(p, token);
1876            } else if (tn === $.APPLET || tn === $.OBJECT) {
1877                appletStartTagInBody(p, token);
1878            } else if (tn === $.KEYGEN) {
1879                areaStartTagInBody(p, token);
1880            } else if (tn === $.SOURCE) {
1881                paramStartTagInBody(p, token);
1882            } else if (tn === $.IFRAME) {
1883                iframeStartTagInBody(p, token);
1884            } else if (tn === $.SELECT) {
1885                selectStartTagInBody(p, token);
1886            } else if (tn === $.OPTION) {
1887                optgroupStartTagInBody(p, token);
1888            } else {
1889                genericStartTagInBody(p, token);
1890            }
1891
1892            break;
1893
1894        case 7:
1895            if (tn === $.BGSOUND) {
1896                startTagInHead(p, token);
1897            } else if (
1898                tn === $.DETAILS ||
1899                tn === $.ADDRESS ||
1900                tn === $.ARTICLE ||
1901                tn === $.SECTION ||
1902                tn === $.SUMMARY
1903            ) {
1904                addressStartTagInBody(p, token);
1905            } else if (tn === $.LISTING) {
1906                preStartTagInBody(p, token);
1907            } else if (tn === $.MARQUEE) {
1908                appletStartTagInBody(p, token);
1909            } else if (tn === $.NOEMBED) {
1910                noembedStartTagInBody(p, token);
1911            } else if (tn !== $.CAPTION) {
1912                genericStartTagInBody(p, token);
1913            }
1914
1915            break;
1916
1917        case 8:
1918            if (tn === $.BASEFONT) {
1919                startTagInHead(p, token);
1920            } else if (tn === $.FRAMESET) {
1921                framesetStartTagInBody(p, token);
1922            } else if (tn === $.FIELDSET) {
1923                addressStartTagInBody(p, token);
1924            } else if (tn === $.TEXTAREA) {
1925                textareaStartTagInBody(p, token);
1926            } else if (tn === $.TEMPLATE) {
1927                startTagInHead(p, token);
1928            } else if (tn === $.NOSCRIPT) {
1929                if (p.options.scriptingEnabled) {
1930                    noembedStartTagInBody(p, token);
1931                } else {
1932                    genericStartTagInBody(p, token);
1933                }
1934            } else if (tn === $.OPTGROUP) {
1935                optgroupStartTagInBody(p, token);
1936            } else if (tn !== $.COLGROUP) {
1937                genericStartTagInBody(p, token);
1938            }
1939
1940            break;
1941
1942        case 9:
1943            if (tn === $.PLAINTEXT) {
1944                plaintextStartTagInBody(p, token);
1945            } else {
1946                genericStartTagInBody(p, token);
1947            }
1948
1949            break;
1950
1951        case 10:
1952            if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) {
1953                addressStartTagInBody(p, token);
1954            } else {
1955                genericStartTagInBody(p, token);
1956            }
1957
1958            break;
1959
1960        default:
1961            genericStartTagInBody(p, token);
1962    }
1963}
1964
1965function bodyEndTagInBody(p) {
1966    if (p.openElements.hasInScope($.BODY)) {
1967        p.insertionMode = AFTER_BODY_MODE;
1968    }
1969}
1970
1971function htmlEndTagInBody(p, token) {
1972    if (p.openElements.hasInScope($.BODY)) {
1973        p.insertionMode = AFTER_BODY_MODE;
1974        p._processToken(token);
1975    }
1976}
1977
1978function addressEndTagInBody(p, token) {
1979    const tn = token.tagName;
1980
1981    if (p.openElements.hasInScope(tn)) {
1982        p.openElements.generateImpliedEndTags();
1983        p.openElements.popUntilTagNamePopped(tn);
1984    }
1985}
1986
1987function formEndTagInBody(p) {
1988    const inTemplate = p.openElements.tmplCount > 0;
1989    const formElement = p.formElement;
1990
1991    if (!inTemplate) {
1992        p.formElement = null;
1993    }
1994
1995    if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) {
1996        p.openElements.generateImpliedEndTags();
1997
1998        if (inTemplate) {
1999            p.openElements.popUntilTagNamePopped($.FORM);
2000        } else {
2001            p.openElements.remove(formElement);
2002        }
2003    }
2004}
2005
2006function pEndTagInBody(p) {
2007    if (!p.openElements.hasInButtonScope($.P)) {
2008        p._insertFakeElement($.P);
2009    }
2010
2011    p._closePElement();
2012}
2013
2014function liEndTagInBody(p) {
2015    if (p.openElements.hasInListItemScope($.LI)) {
2016        p.openElements.generateImpliedEndTagsWithExclusion($.LI);
2017        p.openElements.popUntilTagNamePopped($.LI);
2018    }
2019}
2020
2021function ddEndTagInBody(p, token) {
2022    const tn = token.tagName;
2023
2024    if (p.openElements.hasInScope(tn)) {
2025        p.openElements.generateImpliedEndTagsWithExclusion(tn);
2026        p.openElements.popUntilTagNamePopped(tn);
2027    }
2028}
2029
2030function numberedHeaderEndTagInBody(p) {
2031    if (p.openElements.hasNumberedHeaderInScope()) {
2032        p.openElements.generateImpliedEndTags();
2033        p.openElements.popUntilNumberedHeaderPopped();
2034    }
2035}
2036
2037function appletEndTagInBody(p, token) {
2038    const tn = token.tagName;
2039
2040    if (p.openElements.hasInScope(tn)) {
2041        p.openElements.generateImpliedEndTags();
2042        p.openElements.popUntilTagNamePopped(tn);
2043        p.activeFormattingElements.clearToLastMarker();
2044    }
2045}
2046
2047function brEndTagInBody(p) {
2048    p._reconstructActiveFormattingElements();
2049    p._insertFakeElement($.BR);
2050    p.openElements.pop();
2051    p.framesetOk = false;
2052}
2053
2054function genericEndTagInBody(p, token) {
2055    const tn = token.tagName;
2056
2057    for (let i = p.openElements.stackTop; i > 0; i--) {
2058        const element = p.openElements.items[i];
2059
2060        if (p.treeAdapter.getTagName(element) === tn) {
2061            p.openElements.generateImpliedEndTagsWithExclusion(tn);
2062            p.openElements.popUntilElementPopped(element);
2063            break;
2064        }
2065
2066        if (p._isSpecialElement(element)) {
2067            break;
2068        }
2069    }
2070}
2071
2072//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here.
2073//It's faster than using dictionary.
2074function endTagInBody(p, token) {
2075    const tn = token.tagName;
2076
2077    switch (tn.length) {
2078        case 1:
2079            if (tn === $.A || tn === $.B || tn === $.I || tn === $.S || tn === $.U) {
2080                callAdoptionAgency(p, token);
2081            } else if (tn === $.P) {
2082                pEndTagInBody(p, token);
2083            } else {
2084                genericEndTagInBody(p, token);
2085            }
2086
2087            break;
2088
2089        case 2:
2090            if (tn === $.DL || tn === $.UL || tn === $.OL) {
2091                addressEndTagInBody(p, token);
2092            } else if (tn === $.LI) {
2093                liEndTagInBody(p, token);
2094            } else if (tn === $.DD || tn === $.DT) {
2095                ddEndTagInBody(p, token);
2096            } else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) {
2097                numberedHeaderEndTagInBody(p, token);
2098            } else if (tn === $.BR) {
2099                brEndTagInBody(p, token);
2100            } else if (tn === $.EM || tn === $.TT) {
2101                callAdoptionAgency(p, token);
2102            } else {
2103                genericEndTagInBody(p, token);
2104            }
2105
2106            break;
2107
2108        case 3:
2109            if (tn === $.BIG) {
2110                callAdoptionAgency(p, token);
2111            } else if (tn === $.DIR || tn === $.DIV || tn === $.NAV || tn === $.PRE) {
2112                addressEndTagInBody(p, token);
2113            } else {
2114                genericEndTagInBody(p, token);
2115            }
2116
2117            break;
2118
2119        case 4:
2120            if (tn === $.BODY) {
2121                bodyEndTagInBody(p, token);
2122            } else if (tn === $.HTML) {
2123                htmlEndTagInBody(p, token);
2124            } else if (tn === $.FORM) {
2125                formEndTagInBody(p, token);
2126            } else if (tn === $.CODE || tn === $.FONT || tn === $.NOBR) {
2127                callAdoptionAgency(p, token);
2128            } else if (tn === $.MAIN || tn === $.MENU) {
2129                addressEndTagInBody(p, token);
2130            } else {
2131                genericEndTagInBody(p, token);
2132            }
2133
2134            break;
2135
2136        case 5:
2137            if (tn === $.ASIDE) {
2138                addressEndTagInBody(p, token);
2139            } else if (tn === $.SMALL) {
2140                callAdoptionAgency(p, token);
2141            } else {
2142                genericEndTagInBody(p, token);
2143            }
2144
2145            break;
2146
2147        case 6:
2148            if (
2149                tn === $.CENTER ||
2150                tn === $.FIGURE ||
2151                tn === $.FOOTER ||
2152                tn === $.HEADER ||
2153                tn === $.HGROUP ||
2154                tn === $.DIALOG
2155            ) {
2156                addressEndTagInBody(p, token);
2157            } else if (tn === $.APPLET || tn === $.OBJECT) {
2158                appletEndTagInBody(p, token);
2159            } else if (tn === $.STRIKE || tn === $.STRONG) {
2160                callAdoptionAgency(p, token);
2161            } else {
2162                genericEndTagInBody(p, token);
2163            }
2164
2165            break;
2166
2167        case 7:
2168            if (
2169                tn === $.ADDRESS ||
2170                tn === $.ARTICLE ||
2171                tn === $.DETAILS ||
2172                tn === $.SECTION ||
2173                tn === $.SUMMARY ||
2174                tn === $.LISTING
2175            ) {
2176                addressEndTagInBody(p, token);
2177            } else if (tn === $.MARQUEE) {
2178                appletEndTagInBody(p, token);
2179            } else {
2180                genericEndTagInBody(p, token);
2181            }
2182
2183            break;
2184
2185        case 8:
2186            if (tn === $.FIELDSET) {
2187                addressEndTagInBody(p, token);
2188            } else if (tn === $.TEMPLATE) {
2189                endTagInHead(p, token);
2190            } else {
2191                genericEndTagInBody(p, token);
2192            }
2193
2194            break;
2195
2196        case 10:
2197            if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) {
2198                addressEndTagInBody(p, token);
2199            } else {
2200                genericEndTagInBody(p, token);
2201            }
2202
2203            break;
2204
2205        default:
2206            genericEndTagInBody(p, token);
2207    }
2208}
2209
2210function eofInBody(p, token) {
2211    if (p.tmplInsertionModeStackTop > -1) {
2212        eofInTemplate(p, token);
2213    } else {
2214        p.stopped = true;
2215    }
2216}
2217
2218// The "text" insertion mode
2219//------------------------------------------------------------------
2220function endTagInText(p, token) {
2221    if (token.tagName === $.SCRIPT) {
2222        p.pendingScript = p.openElements.current;
2223    }
2224
2225    p.openElements.pop();
2226    p.insertionMode = p.originalInsertionMode;
2227}
2228
2229function eofInText(p, token) {
2230    p._err(ERR.eofInElementThatCanContainOnlyText);
2231    p.openElements.pop();
2232    p.insertionMode = p.originalInsertionMode;
2233    p._processToken(token);
2234}
2235
2236// The "in table" insertion mode
2237//------------------------------------------------------------------
2238function characterInTable(p, token) {
2239    const curTn = p.openElements.currentTagName;
2240
2241    if (curTn === $.TABLE || curTn === $.TBODY || curTn === $.TFOOT || curTn === $.THEAD || curTn === $.TR) {
2242        p.pendingCharacterTokens = [];
2243        p.hasNonWhitespacePendingCharacterToken = false;
2244        p.originalInsertionMode = p.insertionMode;
2245        p.insertionMode = IN_TABLE_TEXT_MODE;
2246        p._processToken(token);
2247    } else {
2248        tokenInTable(p, token);
2249    }
2250}
2251
2252function captionStartTagInTable(p, token) {
2253    p.openElements.clearBackToTableContext();
2254    p.activeFormattingElements.insertMarker();
2255    p._insertElement(token, NS.HTML);
2256    p.insertionMode = IN_CAPTION_MODE;
2257}
2258
2259function colgroupStartTagInTable(p, token) {
2260    p.openElements.clearBackToTableContext();
2261    p._insertElement(token, NS.HTML);
2262    p.insertionMode = IN_COLUMN_GROUP_MODE;
2263}
2264
2265function colStartTagInTable(p, token) {
2266    p.openElements.clearBackToTableContext();
2267    p._insertFakeElement($.COLGROUP);
2268    p.insertionMode = IN_COLUMN_GROUP_MODE;
2269    p._processToken(token);
2270}
2271
2272function tbodyStartTagInTable(p, token) {
2273    p.openElements.clearBackToTableContext();
2274    p._insertElement(token, NS.HTML);
2275    p.insertionMode = IN_TABLE_BODY_MODE;
2276}
2277
2278function tdStartTagInTable(p, token) {
2279    p.openElements.clearBackToTableContext();
2280    p._insertFakeElement($.TBODY);
2281    p.insertionMode = IN_TABLE_BODY_MODE;
2282    p._processToken(token);
2283}
2284
2285function tableStartTagInTable(p, token) {
2286    if (p.openElements.hasInTableScope($.TABLE)) {
2287        p.openElements.popUntilTagNamePopped($.TABLE);
2288        p._resetInsertionMode();
2289        p._processToken(token);
2290    }
2291}
2292
2293function inputStartTagInTable(p, token) {
2294    const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE);
2295
2296    if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE) {
2297        p._appendElement(token, NS.HTML);
2298    } else {
2299        tokenInTable(p, token);
2300    }
2301
2302    token.ackSelfClosing = true;
2303}
2304
2305function formStartTagInTable(p, token) {
2306    if (!p.formElement && p.openElements.tmplCount === 0) {
2307        p._insertElement(token, NS.HTML);
2308        p.formElement = p.openElements.current;
2309        p.openElements.pop();
2310    }
2311}
2312
2313function startTagInTable(p, token) {
2314    const tn = token.tagName;
2315
2316    switch (tn.length) {
2317        case 2:
2318            if (tn === $.TD || tn === $.TH || tn === $.TR) {
2319                tdStartTagInTable(p, token);
2320            } else {
2321                tokenInTable(p, token);
2322            }
2323
2324            break;
2325
2326        case 3:
2327            if (tn === $.COL) {
2328                colStartTagInTable(p, token);
2329            } else {
2330                tokenInTable(p, token);
2331            }
2332
2333            break;
2334
2335        case 4:
2336            if (tn === $.FORM) {
2337                formStartTagInTable(p, token);
2338            } else {
2339                tokenInTable(p, token);
2340            }
2341
2342            break;
2343
2344        case 5:
2345            if (tn === $.TABLE) {
2346                tableStartTagInTable(p, token);
2347            } else if (tn === $.STYLE) {
2348                startTagInHead(p, token);
2349            } else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
2350                tbodyStartTagInTable(p, token);
2351            } else if (tn === $.INPUT) {
2352                inputStartTagInTable(p, token);
2353            } else {
2354                tokenInTable(p, token);
2355            }
2356
2357            break;
2358
2359        case 6:
2360            if (tn === $.SCRIPT) {
2361                startTagInHead(p, token);
2362            } else {
2363                tokenInTable(p, token);
2364            }
2365
2366            break;
2367
2368        case 7:
2369            if (tn === $.CAPTION) {
2370                captionStartTagInTable(p, token);
2371            } else {
2372                tokenInTable(p, token);
2373            }
2374
2375            break;
2376
2377        case 8:
2378            if (tn === $.COLGROUP) {
2379                colgroupStartTagInTable(p, token);
2380            } else if (tn === $.TEMPLATE) {
2381                startTagInHead(p, token);
2382            } else {
2383                tokenInTable(p, token);
2384            }
2385
2386            break;
2387
2388        default:
2389            tokenInTable(p, token);
2390    }
2391}
2392
2393function endTagInTable(p, token) {
2394    const tn = token.tagName;
2395
2396    if (tn === $.TABLE) {
2397        if (p.openElements.hasInTableScope($.TABLE)) {
2398            p.openElements.popUntilTagNamePopped($.TABLE);
2399            p._resetInsertionMode();
2400        }
2401    } else if (tn === $.TEMPLATE) {
2402        endTagInHead(p, token);
2403    } else if (
2404        tn !== $.BODY &&
2405        tn !== $.CAPTION &&
2406        tn !== $.COL &&
2407        tn !== $.COLGROUP &&
2408        tn !== $.HTML &&
2409        tn !== $.TBODY &&
2410        tn !== $.TD &&
2411        tn !== $.TFOOT &&
2412        tn !== $.TH &&
2413        tn !== $.THEAD &&
2414        tn !== $.TR
2415    ) {
2416        tokenInTable(p, token);
2417    }
2418}
2419
2420function tokenInTable(p, token) {
2421    const savedFosterParentingState = p.fosterParentingEnabled;
2422
2423    p.fosterParentingEnabled = true;
2424    p._processTokenInBodyMode(token);
2425    p.fosterParentingEnabled = savedFosterParentingState;
2426}
2427
2428// The "in table text" insertion mode
2429//------------------------------------------------------------------
2430function whitespaceCharacterInTableText(p, token) {
2431    p.pendingCharacterTokens.push(token);
2432}
2433
2434function characterInTableText(p, token) {
2435    p.pendingCharacterTokens.push(token);
2436    p.hasNonWhitespacePendingCharacterToken = true;
2437}
2438
2439function tokenInTableText(p, token) {
2440    let i = 0;
2441
2442    if (p.hasNonWhitespacePendingCharacterToken) {
2443        for (; i < p.pendingCharacterTokens.length; i++) {
2444            tokenInTable(p, p.pendingCharacterTokens[i]);
2445        }
2446    } else {
2447        for (; i < p.pendingCharacterTokens.length; i++) {
2448            p._insertCharacters(p.pendingCharacterTokens[i]);
2449        }
2450    }
2451
2452    p.insertionMode = p.originalInsertionMode;
2453    p._processToken(token);
2454}
2455
2456// The "in caption" insertion mode
2457//------------------------------------------------------------------
2458function startTagInCaption(p, token) {
2459    const tn = token.tagName;
2460
2461    if (
2462        tn === $.CAPTION ||
2463        tn === $.COL ||
2464        tn === $.COLGROUP ||
2465        tn === $.TBODY ||
2466        tn === $.TD ||
2467        tn === $.TFOOT ||
2468        tn === $.TH ||
2469        tn === $.THEAD ||
2470        tn === $.TR
2471    ) {
2472        if (p.openElements.hasInTableScope($.CAPTION)) {
2473            p.openElements.generateImpliedEndTags();
2474            p.openElements.popUntilTagNamePopped($.CAPTION);
2475            p.activeFormattingElements.clearToLastMarker();
2476            p.insertionMode = IN_TABLE_MODE;
2477            p._processToken(token);
2478        }
2479    } else {
2480        startTagInBody(p, token);
2481    }
2482}
2483
2484function endTagInCaption(p, token) {
2485    const tn = token.tagName;
2486
2487    if (tn === $.CAPTION || tn === $.TABLE) {
2488        if (p.openElements.hasInTableScope($.CAPTION)) {
2489            p.openElements.generateImpliedEndTags();
2490            p.openElements.popUntilTagNamePopped($.CAPTION);
2491            p.activeFormattingElements.clearToLastMarker();
2492            p.insertionMode = IN_TABLE_MODE;
2493
2494            if (tn === $.TABLE) {
2495                p._processToken(token);
2496            }
2497        }
2498    } else if (
2499        tn !== $.BODY &&
2500        tn !== $.COL &&
2501        tn !== $.COLGROUP &&
2502        tn !== $.HTML &&
2503        tn !== $.TBODY &&
2504        tn !== $.TD &&
2505        tn !== $.TFOOT &&
2506        tn !== $.TH &&
2507        tn !== $.THEAD &&
2508        tn !== $.TR
2509    ) {
2510        endTagInBody(p, token);
2511    }
2512}
2513
2514// The "in column group" insertion mode
2515//------------------------------------------------------------------
2516function startTagInColumnGroup(p, token) {
2517    const tn = token.tagName;
2518
2519    if (tn === $.HTML) {
2520        startTagInBody(p, token);
2521    } else if (tn === $.COL) {
2522        p._appendElement(token, NS.HTML);
2523        token.ackSelfClosing = true;
2524    } else if (tn === $.TEMPLATE) {
2525        startTagInHead(p, token);
2526    } else {
2527        tokenInColumnGroup(p, token);
2528    }
2529}
2530
2531function endTagInColumnGroup(p, token) {
2532    const tn = token.tagName;
2533
2534    if (tn === $.COLGROUP) {
2535        if (p.openElements.currentTagName === $.COLGROUP) {
2536            p.openElements.pop();
2537            p.insertionMode = IN_TABLE_MODE;
2538        }
2539    } else if (tn === $.TEMPLATE) {
2540        endTagInHead(p, token);
2541    } else if (tn !== $.COL) {
2542        tokenInColumnGroup(p, token);
2543    }
2544}
2545
2546function tokenInColumnGroup(p, token) {
2547    if (p.openElements.currentTagName === $.COLGROUP) {
2548        p.openElements.pop();
2549        p.insertionMode = IN_TABLE_MODE;
2550        p._processToken(token);
2551    }
2552}
2553
2554// The "in table body" insertion mode
2555//------------------------------------------------------------------
2556function startTagInTableBody(p, token) {
2557    const tn = token.tagName;
2558
2559    if (tn === $.TR) {
2560        p.openElements.clearBackToTableBodyContext();
2561        p._insertElement(token, NS.HTML);
2562        p.insertionMode = IN_ROW_MODE;
2563    } else if (tn === $.TH || tn === $.TD) {
2564        p.openElements.clearBackToTableBodyContext();
2565        p._insertFakeElement($.TR);
2566        p.insertionMode = IN_ROW_MODE;
2567        p._processToken(token);
2568    } else if (
2569        tn === $.CAPTION ||
2570        tn === $.COL ||
2571        tn === $.COLGROUP ||
2572        tn === $.TBODY ||
2573        tn === $.TFOOT ||
2574        tn === $.THEAD
2575    ) {
2576        if (p.openElements.hasTableBodyContextInTableScope()) {
2577            p.openElements.clearBackToTableBodyContext();
2578            p.openElements.pop();
2579            p.insertionMode = IN_TABLE_MODE;
2580            p._processToken(token);
2581        }
2582    } else {
2583        startTagInTable(p, token);
2584    }
2585}
2586
2587function endTagInTableBody(p, token) {
2588    const tn = token.tagName;
2589
2590    if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
2591        if (p.openElements.hasInTableScope(tn)) {
2592            p.openElements.clearBackToTableBodyContext();
2593            p.openElements.pop();
2594            p.insertionMode = IN_TABLE_MODE;
2595        }
2596    } else if (tn === $.TABLE) {
2597        if (p.openElements.hasTableBodyContextInTableScope()) {
2598            p.openElements.clearBackToTableBodyContext();
2599            p.openElements.pop();
2600            p.insertionMode = IN_TABLE_MODE;
2601            p._processToken(token);
2602        }
2603    } else if (
2604        (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) ||
2605        (tn !== $.HTML && tn !== $.TD && tn !== $.TH && tn !== $.TR)
2606    ) {
2607        endTagInTable(p, token);
2608    }
2609}
2610
2611// The "in row" insertion mode
2612//------------------------------------------------------------------
2613function startTagInRow(p, token) {
2614    const tn = token.tagName;
2615
2616    if (tn === $.TH || tn === $.TD) {
2617        p.openElements.clearBackToTableRowContext();
2618        p._insertElement(token, NS.HTML);
2619        p.insertionMode = IN_CELL_MODE;
2620        p.activeFormattingElements.insertMarker();
2621    } else if (
2622        tn === $.CAPTION ||
2623        tn === $.COL ||
2624        tn === $.COLGROUP ||
2625        tn === $.TBODY ||
2626        tn === $.TFOOT ||
2627        tn === $.THEAD ||
2628        tn === $.TR
2629    ) {
2630        if (p.openElements.hasInTableScope($.TR)) {
2631            p.openElements.clearBackToTableRowContext();
2632            p.openElements.pop();
2633            p.insertionMode = IN_TABLE_BODY_MODE;
2634            p._processToken(token);
2635        }
2636    } else {
2637        startTagInTable(p, token);
2638    }
2639}
2640
2641function endTagInRow(p, token) {
2642    const tn = token.tagName;
2643
2644    if (tn === $.TR) {
2645        if (p.openElements.hasInTableScope($.TR)) {
2646            p.openElements.clearBackToTableRowContext();
2647            p.openElements.pop();
2648            p.insertionMode = IN_TABLE_BODY_MODE;
2649        }
2650    } else if (tn === $.TABLE) {
2651        if (p.openElements.hasInTableScope($.TR)) {
2652            p.openElements.clearBackToTableRowContext();
2653            p.openElements.pop();
2654            p.insertionMode = IN_TABLE_BODY_MODE;
2655            p._processToken(token);
2656        }
2657    } else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) {
2658        if (p.openElements.hasInTableScope(tn) || p.openElements.hasInTableScope($.TR)) {
2659            p.openElements.clearBackToTableRowContext();
2660            p.openElements.pop();
2661            p.insertionMode = IN_TABLE_BODY_MODE;
2662            p._processToken(token);
2663        }
2664    } else if (
2665        (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) ||
2666        (tn !== $.HTML && tn !== $.TD && tn !== $.TH)
2667    ) {
2668        endTagInTable(p, token);
2669    }
2670}
2671
2672// The "in cell" insertion mode
2673//------------------------------------------------------------------
2674function startTagInCell(p, token) {
2675    const tn = token.tagName;
2676
2677    if (
2678        tn === $.CAPTION ||
2679        tn === $.COL ||
2680        tn === $.COLGROUP ||
2681        tn === $.TBODY ||
2682        tn === $.TD ||
2683        tn === $.TFOOT ||
2684        tn === $.TH ||
2685        tn === $.THEAD ||
2686        tn === $.TR
2687    ) {
2688        if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) {
2689            p._closeTableCell();
2690            p._processToken(token);
2691        }
2692    } else {
2693        startTagInBody(p, token);
2694    }
2695}
2696
2697function endTagInCell(p, token) {
2698    const tn = token.tagName;
2699
2700    if (tn === $.TD || tn === $.TH) {
2701        if (p.openElements.hasInTableScope(tn)) {
2702            p.openElements.generateImpliedEndTags();
2703            p.openElements.popUntilTagNamePopped(tn);
2704            p.activeFormattingElements.clearToLastMarker();
2705            p.insertionMode = IN_ROW_MODE;
2706        }
2707    } else if (tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR) {
2708        if (p.openElements.hasInTableScope(tn)) {
2709            p._closeTableCell();
2710            p._processToken(token);
2711        }
2712    } else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML) {
2713        endTagInBody(p, token);
2714    }
2715}
2716
2717// The "in select" insertion mode
2718//------------------------------------------------------------------
2719function startTagInSelect(p, token) {
2720    const tn = token.tagName;
2721
2722    if (tn === $.HTML) {
2723        startTagInBody(p, token);
2724    } else if (tn === $.OPTION) {
2725        if (p.openElements.currentTagName === $.OPTION) {
2726            p.openElements.pop();
2727        }
2728
2729        p._insertElement(token, NS.HTML);
2730    } else if (tn === $.OPTGROUP) {
2731        if (p.openElements.currentTagName === $.OPTION) {
2732            p.openElements.pop();
2733        }
2734
2735        if (p.openElements.currentTagName === $.OPTGROUP) {
2736            p.openElements.pop();
2737        }
2738
2739        p._insertElement(token, NS.HTML);
2740    } else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA || tn === $.SELECT) {
2741        if (p.openElements.hasInSelectScope($.SELECT)) {
2742            p.openElements.popUntilTagNamePopped($.SELECT);
2743            p._resetInsertionMode();
2744
2745            if (tn !== $.SELECT) {
2746                p._processToken(token);
2747            }
2748        }
2749    } else if (tn === $.SCRIPT || tn === $.TEMPLATE) {
2750        startTagInHead(p, token);
2751    }
2752}
2753
2754function endTagInSelect(p, token) {
2755    const tn = token.tagName;
2756
2757    if (tn === $.OPTGROUP) {
2758        const prevOpenElement = p.openElements.items[p.openElements.stackTop - 1];
2759        const prevOpenElementTn = prevOpenElement && p.treeAdapter.getTagName(prevOpenElement);
2760
2761        if (p.openElements.currentTagName === $.OPTION && prevOpenElementTn === $.OPTGROUP) {
2762            p.openElements.pop();
2763        }
2764
2765        if (p.openElements.currentTagName === $.OPTGROUP) {
2766            p.openElements.pop();
2767        }
2768    } else if (tn === $.OPTION) {
2769        if (p.openElements.currentTagName === $.OPTION) {
2770            p.openElements.pop();
2771        }
2772    } else if (tn === $.SELECT && p.openElements.hasInSelectScope($.SELECT)) {
2773        p.openElements.popUntilTagNamePopped($.SELECT);
2774        p._resetInsertionMode();
2775    } else if (tn === $.TEMPLATE) {
2776        endTagInHead(p, token);
2777    }
2778}
2779
2780//12.2.5.4.17 The "in select in table" insertion mode
2781//------------------------------------------------------------------
2782function startTagInSelectInTable(p, token) {
2783    const tn = token.tagName;
2784
2785    if (
2786        tn === $.CAPTION ||
2787        tn === $.TABLE ||
2788        tn === $.TBODY ||
2789        tn === $.TFOOT ||
2790        tn === $.THEAD ||
2791        tn === $.TR ||
2792        tn === $.TD ||
2793        tn === $.TH
2794    ) {
2795        p.openElements.popUntilTagNamePopped($.SELECT);
2796        p._resetInsertionMode();
2797        p._processToken(token);
2798    } else {
2799        startTagInSelect(p, token);
2800    }
2801}
2802
2803function endTagInSelectInTable(p, token) {
2804    const tn = token.tagName;
2805
2806    if (
2807        tn === $.CAPTION ||
2808        tn === $.TABLE ||
2809        tn === $.TBODY ||
2810        tn === $.TFOOT ||
2811        tn === $.THEAD ||
2812        tn === $.TR ||
2813        tn === $.TD ||
2814        tn === $.TH
2815    ) {
2816        if (p.openElements.hasInTableScope(tn)) {
2817            p.openElements.popUntilTagNamePopped($.SELECT);
2818            p._resetInsertionMode();
2819            p._processToken(token);
2820        }
2821    } else {
2822        endTagInSelect(p, token);
2823    }
2824}
2825
2826// The "in template" insertion mode
2827//------------------------------------------------------------------
2828function startTagInTemplate(p, token) {
2829    const tn = token.tagName;
2830
2831    if (
2832        tn === $.BASE ||
2833        tn === $.BASEFONT ||
2834        tn === $.BGSOUND ||
2835        tn === $.LINK ||
2836        tn === $.META ||
2837        tn === $.NOFRAMES ||
2838        tn === $.SCRIPT ||
2839        tn === $.STYLE ||
2840        tn === $.TEMPLATE ||
2841        tn === $.TITLE
2842    ) {
2843        startTagInHead(p, token);
2844    } else {
2845        const newInsertionMode = TEMPLATE_INSERTION_MODE_SWITCH_MAP[tn] || IN_BODY_MODE;
2846
2847        p._popTmplInsertionMode();
2848        p._pushTmplInsertionMode(newInsertionMode);
2849        p.insertionMode = newInsertionMode;
2850        p._processToken(token);
2851    }
2852}
2853
2854function endTagInTemplate(p, token) {
2855    if (token.tagName === $.TEMPLATE) {
2856        endTagInHead(p, token);
2857    }
2858}
2859
2860function eofInTemplate(p, token) {
2861    if (p.openElements.tmplCount > 0) {
2862        p.openElements.popUntilTagNamePopped($.TEMPLATE);
2863        p.activeFormattingElements.clearToLastMarker();
2864        p._popTmplInsertionMode();
2865        p._resetInsertionMode();
2866        p._processToken(token);
2867    } else {
2868        p.stopped = true;
2869    }
2870}
2871
2872// The "after body" insertion mode
2873//------------------------------------------------------------------
2874function startTagAfterBody(p, token) {
2875    if (token.tagName === $.HTML) {
2876        startTagInBody(p, token);
2877    } else {
2878        tokenAfterBody(p, token);
2879    }
2880}
2881
2882function endTagAfterBody(p, token) {
2883    if (token.tagName === $.HTML) {
2884        if (!p.fragmentContext) {
2885            p.insertionMode = AFTER_AFTER_BODY_MODE;
2886        }
2887    } else {
2888        tokenAfterBody(p, token);
2889    }
2890}
2891
2892function tokenAfterBody(p, token) {
2893    p.insertionMode = IN_BODY_MODE;
2894    p._processToken(token);
2895}
2896
2897// The "in frameset" insertion mode
2898//------------------------------------------------------------------
2899function startTagInFrameset(p, token) {
2900    const tn = token.tagName;
2901
2902    if (tn === $.HTML) {
2903        startTagInBody(p, token);
2904    } else if (tn === $.FRAMESET) {
2905        p._insertElement(token, NS.HTML);
2906    } else if (tn === $.FRAME) {
2907        p._appendElement(token, NS.HTML);
2908        token.ackSelfClosing = true;
2909    } else if (tn === $.NOFRAMES) {
2910        startTagInHead(p, token);
2911    }
2912}
2913
2914function endTagInFrameset(p, token) {
2915    if (token.tagName === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) {
2916        p.openElements.pop();
2917
2918        if (!p.fragmentContext && p.openElements.currentTagName !== $.FRAMESET) {
2919            p.insertionMode = AFTER_FRAMESET_MODE;
2920        }
2921    }
2922}
2923
2924// The "after frameset" insertion mode
2925//------------------------------------------------------------------
2926function startTagAfterFrameset(p, token) {
2927    const tn = token.tagName;
2928
2929    if (tn === $.HTML) {
2930        startTagInBody(p, token);
2931    } else if (tn === $.NOFRAMES) {
2932        startTagInHead(p, token);
2933    }
2934}
2935
2936function endTagAfterFrameset(p, token) {
2937    if (token.tagName === $.HTML) {
2938        p.insertionMode = AFTER_AFTER_FRAMESET_MODE;
2939    }
2940}
2941
2942// The "after after body" insertion mode
2943//------------------------------------------------------------------
2944function startTagAfterAfterBody(p, token) {
2945    if (token.tagName === $.HTML) {
2946        startTagInBody(p, token);
2947    } else {
2948        tokenAfterAfterBody(p, token);
2949    }
2950}
2951
2952function tokenAfterAfterBody(p, token) {
2953    p.insertionMode = IN_BODY_MODE;
2954    p._processToken(token);
2955}
2956
2957// The "after after frameset" insertion mode
2958//------------------------------------------------------------------
2959function startTagAfterAfterFrameset(p, token) {
2960    const tn = token.tagName;
2961
2962    if (tn === $.HTML) {
2963        startTagInBody(p, token);
2964    } else if (tn === $.NOFRAMES) {
2965        startTagInHead(p, token);
2966    }
2967}
2968
2969// The rules for parsing tokens in foreign content
2970//------------------------------------------------------------------
2971function nullCharacterInForeignContent(p, token) {
2972    token.chars = unicode.REPLACEMENT_CHARACTER;
2973    p._insertCharacters(token);
2974}
2975
2976function characterInForeignContent(p, token) {
2977    p._insertCharacters(token);
2978    p.framesetOk = false;
2979}
2980
2981function startTagInForeignContent(p, token) {
2982    if (foreignContent.causesExit(token) && !p.fragmentContext) {
2983        while (
2984            p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML &&
2985            !p._isIntegrationPoint(p.openElements.current)
2986        ) {
2987            p.openElements.pop();
2988        }
2989
2990        p._processToken(token);
2991    } else {
2992        const current = p._getAdjustedCurrentElement();
2993        const currentNs = p.treeAdapter.getNamespaceURI(current);
2994
2995        if (currentNs === NS.MATHML) {
2996            foreignContent.adjustTokenMathMLAttrs(token);
2997        } else if (currentNs === NS.SVG) {
2998            foreignContent.adjustTokenSVGTagName(token);
2999            foreignContent.adjustTokenSVGAttrs(token);
3000        }
3001
3002        foreignContent.adjustTokenXMLAttrs(token);
3003
3004        if (token.selfClosing) {
3005            p._appendElement(token, currentNs);
3006        } else {
3007            p._insertElement(token, currentNs);
3008        }
3009
3010        token.ackSelfClosing = true;
3011    }
3012}
3013
3014function endTagInForeignContent(p, token) {
3015    for (let i = p.openElements.stackTop; i > 0; i--) {
3016        const element = p.openElements.items[i];
3017
3018        if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) {
3019            p._processToken(token);
3020            break;
3021        }
3022
3023        if (p.treeAdapter.getTagName(element).toLowerCase() === token.tagName) {
3024            p.openElements.popUntilElementPopped(element);
3025            break;
3026        }
3027    }
3028}
3029