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