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