UNPKG

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