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