You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2956 lines
91 KiB

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