Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/parse5/lib/parser/index.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

2956 lines (2508 sloc)
91.1 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
const Tokenizer = require('../tokenizer'); | |
const OpenElementStack = require('./open-element-stack'); | |
const FormattingElementList = require('./formatting-element-list'); | |
const LocationInfoParserMixin = require('../extensions/location-info/parser-mixin'); | |
const ErrorReportingParserMixin = require('../extensions/error-reporting/parser-mixin'); | |
const Mixin = require('../utils/mixin'); | |
const defaultTreeAdapter = require('../tree-adapters/default'); | |
const mergeOptions = require('../utils/merge-options'); | |
const doctype = require('../common/doctype'); | |
const foreignContent = require('../common/foreign-content'); | |
const ERR = require('../common/error-codes'); | |
const unicode = require('../common/unicode'); | |
const HTML = require('../common/html'); | |
//Aliases | |
const $ = HTML.TAG_NAMES; | |
const NS = HTML.NAMESPACES; | |
const ATTRS = HTML.ATTRS; | |
const DEFAULT_OPTIONS = { | |
scriptingEnabled: true, | |
sourceCodeLocationInfo: false, | |
onParseError: null, | |
treeAdapter: defaultTreeAdapter | |
}; | |
//Misc constants | |
const HIDDEN_INPUT_TYPE = 'hidden'; | |
//Adoption agency loops iteration count | |
const AA_OUTER_LOOP_ITER = 8; | |
const AA_INNER_LOOP_ITER = 3; | |
//Insertion modes | |
const INITIAL_MODE = 'INITIAL_MODE'; | |
const BEFORE_HTML_MODE = 'BEFORE_HTML_MODE'; | |
const BEFORE_HEAD_MODE = 'BEFORE_HEAD_MODE'; | |
const IN_HEAD_MODE = 'IN_HEAD_MODE'; | |
const IN_HEAD_NO_SCRIPT_MODE = 'IN_HEAD_NO_SCRIPT_MODE'; | |
const AFTER_HEAD_MODE = 'AFTER_HEAD_MODE'; | |
const IN_BODY_MODE = 'IN_BODY_MODE'; | |
const TEXT_MODE = 'TEXT_MODE'; | |
const IN_TABLE_MODE = 'IN_TABLE_MODE'; | |
const IN_TABLE_TEXT_MODE = 'IN_TABLE_TEXT_MODE'; | |
const IN_CAPTION_MODE = 'IN_CAPTION_MODE'; | |
const IN_COLUMN_GROUP_MODE = 'IN_COLUMN_GROUP_MODE'; | |
const IN_TABLE_BODY_MODE = 'IN_TABLE_BODY_MODE'; | |
const IN_ROW_MODE = 'IN_ROW_MODE'; | |
const IN_CELL_MODE = 'IN_CELL_MODE'; | |
const IN_SELECT_MODE = 'IN_SELECT_MODE'; | |
const IN_SELECT_IN_TABLE_MODE = 'IN_SELECT_IN_TABLE_MODE'; | |
const IN_TEMPLATE_MODE = 'IN_TEMPLATE_MODE'; | |
const AFTER_BODY_MODE = 'AFTER_BODY_MODE'; | |
const IN_FRAMESET_MODE = 'IN_FRAMESET_MODE'; | |
const AFTER_FRAMESET_MODE = 'AFTER_FRAMESET_MODE'; | |
const AFTER_AFTER_BODY_MODE = 'AFTER_AFTER_BODY_MODE'; | |
const AFTER_AFTER_FRAMESET_MODE = 'AFTER_AFTER_FRAMESET_MODE'; | |
//Insertion mode reset map | |
const INSERTION_MODE_RESET_MAP = { | |
[$.TR]: IN_ROW_MODE, | |
[$.TBODY]: IN_TABLE_BODY_MODE, | |
[$.THEAD]: IN_TABLE_BODY_MODE, | |
[$.TFOOT]: IN_TABLE_BODY_MODE, | |
[$.CAPTION]: IN_CAPTION_MODE, | |
[$.COLGROUP]: IN_COLUMN_GROUP_MODE, | |
[$.TABLE]: IN_TABLE_MODE, | |
[$.BODY]: IN_BODY_MODE, | |
[$.FRAMESET]: IN_FRAMESET_MODE | |
}; | |
//Template insertion mode switch map | |
const TEMPLATE_INSERTION_MODE_SWITCH_MAP = { | |
[$.CAPTION]: IN_TABLE_MODE, | |
[$.COLGROUP]: IN_TABLE_MODE, | |
[$.TBODY]: IN_TABLE_MODE, | |
[$.TFOOT]: IN_TABLE_MODE, | |
[$.THEAD]: IN_TABLE_MODE, | |
[$.COL]: IN_COLUMN_GROUP_MODE, | |
[$.TR]: IN_TABLE_BODY_MODE, | |
[$.TD]: IN_ROW_MODE, | |
[$.TH]: IN_ROW_MODE | |
}; | |
//Token handlers map for insertion modes | |
const TOKEN_HANDLERS = { | |
[INITIAL_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenInInitialMode, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInInitialMode, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: doctypeInInitialMode, | |
[Tokenizer.START_TAG_TOKEN]: tokenInInitialMode, | |
[Tokenizer.END_TAG_TOKEN]: tokenInInitialMode, | |
[Tokenizer.EOF_TOKEN]: tokenInInitialMode | |
}, | |
[BEFORE_HTML_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenBeforeHtml, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHtml, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagBeforeHtml, | |
[Tokenizer.END_TAG_TOKEN]: endTagBeforeHtml, | |
[Tokenizer.EOF_TOKEN]: tokenBeforeHtml | |
}, | |
[BEFORE_HEAD_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenBeforeHead, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenBeforeHead, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, | |
[Tokenizer.START_TAG_TOKEN]: startTagBeforeHead, | |
[Tokenizer.END_TAG_TOKEN]: endTagBeforeHead, | |
[Tokenizer.EOF_TOKEN]: tokenBeforeHead | |
}, | |
[IN_HEAD_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenInHead, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHead, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, | |
[Tokenizer.START_TAG_TOKEN]: startTagInHead, | |
[Tokenizer.END_TAG_TOKEN]: endTagInHead, | |
[Tokenizer.EOF_TOKEN]: tokenInHead | |
}, | |
[IN_HEAD_NO_SCRIPT_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenInHeadNoScript, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInHeadNoScript, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, | |
[Tokenizer.START_TAG_TOKEN]: startTagInHeadNoScript, | |
[Tokenizer.END_TAG_TOKEN]: endTagInHeadNoScript, | |
[Tokenizer.EOF_TOKEN]: tokenInHeadNoScript | |
}, | |
[AFTER_HEAD_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenAfterHead, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterHead, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: misplacedDoctype, | |
[Tokenizer.START_TAG_TOKEN]: startTagAfterHead, | |
[Tokenizer.END_TAG_TOKEN]: endTagAfterHead, | |
[Tokenizer.EOF_TOKEN]: tokenAfterHead | |
}, | |
[IN_BODY_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInBody, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInBody, | |
[Tokenizer.END_TAG_TOKEN]: endTagInBody, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[TEXT_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: ignoreToken, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: ignoreToken, | |
[Tokenizer.END_TAG_TOKEN]: endTagInText, | |
[Tokenizer.EOF_TOKEN]: eofInText | |
}, | |
[IN_TABLE_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInTable, | |
[Tokenizer.END_TAG_TOKEN]: endTagInTable, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_TABLE_TEXT_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInTableText, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInTableText, | |
[Tokenizer.COMMENT_TOKEN]: tokenInTableText, | |
[Tokenizer.DOCTYPE_TOKEN]: tokenInTableText, | |
[Tokenizer.START_TAG_TOKEN]: tokenInTableText, | |
[Tokenizer.END_TAG_TOKEN]: tokenInTableText, | |
[Tokenizer.EOF_TOKEN]: tokenInTableText | |
}, | |
[IN_CAPTION_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInBody, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInCaption, | |
[Tokenizer.END_TAG_TOKEN]: endTagInCaption, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_COLUMN_GROUP_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenInColumnGroup, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenInColumnGroup, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInColumnGroup, | |
[Tokenizer.END_TAG_TOKEN]: endTagInColumnGroup, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_TABLE_BODY_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInTableBody, | |
[Tokenizer.END_TAG_TOKEN]: endTagInTableBody, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_ROW_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: characterInTable, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInRow, | |
[Tokenizer.END_TAG_TOKEN]: endTagInRow, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_CELL_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInBody, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInCell, | |
[Tokenizer.END_TAG_TOKEN]: endTagInCell, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_SELECT_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInSelect, | |
[Tokenizer.END_TAG_TOKEN]: endTagInSelect, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_SELECT_IN_TABLE_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInSelectInTable, | |
[Tokenizer.END_TAG_TOKEN]: endTagInSelectInTable, | |
[Tokenizer.EOF_TOKEN]: eofInBody | |
}, | |
[IN_TEMPLATE_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: characterInBody, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInTemplate, | |
[Tokenizer.END_TAG_TOKEN]: endTagInTemplate, | |
[Tokenizer.EOF_TOKEN]: eofInTemplate | |
}, | |
[AFTER_BODY_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenAfterBody, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterBody, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendCommentToRootHtmlElement, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagAfterBody, | |
[Tokenizer.END_TAG_TOKEN]: endTagAfterBody, | |
[Tokenizer.EOF_TOKEN]: stopParsing | |
}, | |
[IN_FRAMESET_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagInFrameset, | |
[Tokenizer.END_TAG_TOKEN]: endTagInFrameset, | |
[Tokenizer.EOF_TOKEN]: stopParsing | |
}, | |
[AFTER_FRAMESET_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: insertCharacters, | |
[Tokenizer.COMMENT_TOKEN]: appendComment, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagAfterFrameset, | |
[Tokenizer.END_TAG_TOKEN]: endTagAfterFrameset, | |
[Tokenizer.EOF_TOKEN]: stopParsing | |
}, | |
[AFTER_AFTER_BODY_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: tokenAfterAfterBody, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: tokenAfterAfterBody, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendCommentToDocument, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagAfterAfterBody, | |
[Tokenizer.END_TAG_TOKEN]: tokenAfterAfterBody, | |
[Tokenizer.EOF_TOKEN]: stopParsing | |
}, | |
[AFTER_AFTER_FRAMESET_MODE]: { | |
[Tokenizer.CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.NULL_CHARACTER_TOKEN]: ignoreToken, | |
[Tokenizer.WHITESPACE_CHARACTER_TOKEN]: whitespaceCharacterInBody, | |
[Tokenizer.COMMENT_TOKEN]: appendCommentToDocument, | |
[Tokenizer.DOCTYPE_TOKEN]: ignoreToken, | |
[Tokenizer.START_TAG_TOKEN]: startTagAfterAfterFrameset, | |
[Tokenizer.END_TAG_TOKEN]: ignoreToken, | |
[Tokenizer.EOF_TOKEN]: stopParsing | |
} | |
}; | |
//Parser | |
class Parser { | |
constructor(options) { | |
this.options = mergeOptions(DEFAULT_OPTIONS, options); | |
this.treeAdapter = this.options.treeAdapter; | |
this.pendingScript = null; | |
if (this.options.sourceCodeLocationInfo) { | |
Mixin.install(this, LocationInfoParserMixin); | |
} | |
if (this.options.onParseError) { | |
Mixin.install(this, ErrorReportingParserMixin, { onParseError: this.options.onParseError }); | |
} | |
} | |
// API | |
parse(html) { | |
const document = this.treeAdapter.createDocument(); | |
this._bootstrap(document, null); | |
this.tokenizer.write(html, true); | |
this._runParsingLoop(null); | |
return document; | |
} | |
parseFragment(html, fragmentContext) { | |
//NOTE: use <template> element as a fragment context if context element was not provided, | |
//so we will parse in "forgiving" manner | |
if (!fragmentContext) { | |
fragmentContext = this.treeAdapter.createElement($.TEMPLATE, NS.HTML, []); | |
} | |
//NOTE: create fake element which will be used as 'document' for fragment parsing. | |
//This is important for jsdom there 'document' can't be recreated, therefore | |
//fragment parsing causes messing of the main `document`. | |
const documentMock = this.treeAdapter.createElement('documentmock', NS.HTML, []); | |
this._bootstrap(documentMock, fragmentContext); | |
if (this.treeAdapter.getTagName(fragmentContext) === $.TEMPLATE) { | |
this._pushTmplInsertionMode(IN_TEMPLATE_MODE); | |
} | |
this._initTokenizerForFragmentParsing(); | |
this._insertFakeRootElement(); | |
this._resetInsertionMode(); | |
this._findFormInFragmentContext(); | |
this.tokenizer.write(html, true); | |
this._runParsingLoop(null); | |
const rootElement = this.treeAdapter.getFirstChild(documentMock); | |
const fragment = this.treeAdapter.createDocumentFragment(); | |
this._adoptNodes(rootElement, fragment); | |
return fragment; | |
} | |
//Bootstrap parser | |
_bootstrap(document, fragmentContext) { | |
this.tokenizer = new Tokenizer(this.options); | |
this.stopped = false; | |
this.insertionMode = INITIAL_MODE; | |
this.originalInsertionMode = ''; | |
this.document = document; | |
this.fragmentContext = fragmentContext; | |
this.headElement = null; | |
this.formElement = null; | |
this.openElements = new OpenElementStack(this.document, this.treeAdapter); | |
this.activeFormattingElements = new FormattingElementList(this.treeAdapter); | |
this.tmplInsertionModeStack = []; | |
this.tmplInsertionModeStackTop = -1; | |
this.currentTmplInsertionMode = null; | |
this.pendingCharacterTokens = []; | |
this.hasNonWhitespacePendingCharacterToken = false; | |
this.framesetOk = true; | |
this.skipNextNewLine = false; | |
this.fosterParentingEnabled = false; | |
} | |
//Errors | |
_err() { | |
// NOTE: err reporting is noop by default. Enabled by mixin. | |
} | |
//Parsing loop | |
_runParsingLoop(scriptHandler) { | |
while (!this.stopped) { | |
this._setupTokenizerCDATAMode(); | |
const token = this.tokenizer.getNextToken(); | |
if (token.type === Tokenizer.HIBERNATION_TOKEN) { | |
break; | |
} | |
if (this.skipNextNewLine) { | |
this.skipNextNewLine = false; | |
if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN && token.chars[0] === '\n') { | |
if (token.chars.length === 1) { | |
continue; | |
} | |
token.chars = token.chars.substr(1); | |
} | |
} | |
this._processInputToken(token); | |
if (scriptHandler && this.pendingScript) { | |
break; | |
} | |
} | |
} | |
runParsingLoopForCurrentChunk(writeCallback, scriptHandler) { | |
this._runParsingLoop(scriptHandler); | |
if (scriptHandler && this.pendingScript) { | |
const script = this.pendingScript; | |
this.pendingScript = null; | |
scriptHandler(script); | |
return; | |
} | |
if (writeCallback) { | |
writeCallback(); | |
} | |
} | |
//Text parsing | |
_setupTokenizerCDATAMode() { | |
const current = this._getAdjustedCurrentElement(); | |
this.tokenizer.allowCDATA = | |
current && | |
current !== this.document && | |
this.treeAdapter.getNamespaceURI(current) !== NS.HTML && | |
!this._isIntegrationPoint(current); | |
} | |
_switchToTextParsing(currentToken, nextTokenizerState) { | |
this._insertElement(currentToken, NS.HTML); | |
this.tokenizer.state = nextTokenizerState; | |
this.originalInsertionMode = this.insertionMode; | |
this.insertionMode = TEXT_MODE; | |
} | |
switchToPlaintextParsing() { | |
this.insertionMode = TEXT_MODE; | |
this.originalInsertionMode = IN_BODY_MODE; | |
this.tokenizer.state = Tokenizer.MODE.PLAINTEXT; | |
} | |
//Fragment parsing | |
_getAdjustedCurrentElement() { | |
return this.openElements.stackTop === 0 && this.fragmentContext | |
? this.fragmentContext | |
: this.openElements.current; | |
} | |
_findFormInFragmentContext() { | |
let node = this.fragmentContext; | |
do { | |
if (this.treeAdapter.getTagName(node) === $.FORM) { | |
this.formElement = node; | |
break; | |
} | |
node = this.treeAdapter.getParentNode(node); | |
} while (node); | |
} | |
_initTokenizerForFragmentParsing() { | |
if (this.treeAdapter.getNamespaceURI(this.fragmentContext) === NS.HTML) { | |
const tn = this.treeAdapter.getTagName(this.fragmentContext); | |
if (tn === $.TITLE || tn === $.TEXTAREA) { | |
this.tokenizer.state = Tokenizer.MODE.RCDATA; | |
} else if ( | |
tn === $.STYLE || | |
tn === $.XMP || | |
tn === $.IFRAME || | |
tn === $.NOEMBED || | |
tn === $.NOFRAMES || | |
tn === $.NOSCRIPT | |
) { | |
this.tokenizer.state = Tokenizer.MODE.RAWTEXT; | |
} else if (tn === $.SCRIPT) { | |
this.tokenizer.state = Tokenizer.MODE.SCRIPT_DATA; | |
} else if (tn === $.PLAINTEXT) { | |
this.tokenizer.state = Tokenizer.MODE.PLAINTEXT; | |
} | |
} | |
} | |
//Tree mutation | |
_setDocumentType(token) { | |
const name = token.name || ''; | |
const publicId = token.publicId || ''; | |
const systemId = token.systemId || ''; | |
this.treeAdapter.setDocumentType(this.document, name, publicId, systemId); | |
} | |
_attachElementToTree(element) { | |
if (this._shouldFosterParentOnInsertion()) { | |
this._fosterParentElement(element); | |
} else { | |
const parent = this.openElements.currentTmplContent || this.openElements.current; | |
this.treeAdapter.appendChild(parent, element); | |
} | |
} | |
_appendElement(token, namespaceURI) { | |
const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); | |
this._attachElementToTree(element); | |
} | |
_insertElement(token, namespaceURI) { | |
const element = this.treeAdapter.createElement(token.tagName, namespaceURI, token.attrs); | |
this._attachElementToTree(element); | |
this.openElements.push(element); | |
} | |
_insertFakeElement(tagName) { | |
const element = this.treeAdapter.createElement(tagName, NS.HTML, []); | |
this._attachElementToTree(element); | |
this.openElements.push(element); | |
} | |
_insertTemplate(token) { | |
const tmpl = this.treeAdapter.createElement(token.tagName, NS.HTML, token.attrs); | |
const content = this.treeAdapter.createDocumentFragment(); | |
this.treeAdapter.setTemplateContent(tmpl, content); | |
this._attachElementToTree(tmpl); | |
this.openElements.push(tmpl); | |
} | |
_insertFakeRootElement() { | |
const element = this.treeAdapter.createElement($.HTML, NS.HTML, []); | |
this.treeAdapter.appendChild(this.openElements.current, element); | |
this.openElements.push(element); | |
} | |
_appendCommentNode(token, parent) { | |
const commentNode = this.treeAdapter.createCommentNode(token.data); | |
this.treeAdapter.appendChild(parent, commentNode); | |
} | |
_insertCharacters(token) { | |
if (this._shouldFosterParentOnInsertion()) { | |
this._fosterParentText(token.chars); | |
} else { | |
const parent = this.openElements.currentTmplContent || this.openElements.current; | |
this.treeAdapter.insertText(parent, token.chars); | |
} | |
} | |
_adoptNodes(donor, recipient) { | |
for (let child = this.treeAdapter.getFirstChild(donor); child; child = this.treeAdapter.getFirstChild(donor)) { | |
this.treeAdapter.detachNode(child); | |
this.treeAdapter.appendChild(recipient, child); | |
} | |
} | |
//Token processing | |
_shouldProcessTokenInForeignContent(token) { | |
const current = this._getAdjustedCurrentElement(); | |
if (!current || current === this.document) { | |
return false; | |
} | |
const ns = this.treeAdapter.getNamespaceURI(current); | |
if (ns === NS.HTML) { | |
return false; | |
} | |
if ( | |
this.treeAdapter.getTagName(current) === $.ANNOTATION_XML && | |
ns === NS.MATHML && | |
token.type === Tokenizer.START_TAG_TOKEN && | |
token.tagName === $.SVG | |
) { | |
return false; | |
} | |
const isCharacterToken = | |
token.type === Tokenizer.CHARACTER_TOKEN || | |
token.type === Tokenizer.NULL_CHARACTER_TOKEN || | |
token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN; | |
const isMathMLTextStartTag = | |
token.type === Tokenizer.START_TAG_TOKEN && token.tagName !== $.MGLYPH && token.tagName !== $.MALIGNMARK; | |
if ((isMathMLTextStartTag || isCharacterToken) && this._isIntegrationPoint(current, NS.MATHML)) { | |
return false; | |
} | |
if ( | |
(token.type === Tokenizer.START_TAG_TOKEN || isCharacterToken) && | |
this._isIntegrationPoint(current, NS.HTML) | |
) { | |
return false; | |
} | |
return token.type !== Tokenizer.EOF_TOKEN; | |
} | |
_processToken(token) { | |
TOKEN_HANDLERS[this.insertionMode][token.type](this, token); | |
} | |
_processTokenInBodyMode(token) { | |
TOKEN_HANDLERS[IN_BODY_MODE][token.type](this, token); | |
} | |
_processTokenInForeignContent(token) { | |
if (token.type === Tokenizer.CHARACTER_TOKEN) { | |
characterInForeignContent(this, token); | |
} else if (token.type === Tokenizer.NULL_CHARACTER_TOKEN) { | |
nullCharacterInForeignContent(this, token); | |
} else if (token.type === Tokenizer.WHITESPACE_CHARACTER_TOKEN) { | |
insertCharacters(this, token); | |
} else if (token.type === Tokenizer.COMMENT_TOKEN) { | |
appendComment(this, token); | |
} else if (token.type === Tokenizer.START_TAG_TOKEN) { | |
startTagInForeignContent(this, token); | |
} else if (token.type === Tokenizer.END_TAG_TOKEN) { | |
endTagInForeignContent(this, token); | |
} | |
} | |
_processInputToken(token) { | |
if (this._shouldProcessTokenInForeignContent(token)) { | |
this._processTokenInForeignContent(token); | |
} else { | |
this._processToken(token); | |
} | |
if (token.type === Tokenizer.START_TAG_TOKEN && token.selfClosing && !token.ackSelfClosing) { | |
this._err(ERR.nonVoidHtmlElementStartTagWithTrailingSolidus); | |
} | |
} | |
//Integration points | |
_isIntegrationPoint(element, foreignNS) { | |
const tn = this.treeAdapter.getTagName(element); | |
const ns = this.treeAdapter.getNamespaceURI(element); | |
const attrs = this.treeAdapter.getAttrList(element); | |
return foreignContent.isIntegrationPoint(tn, ns, attrs, foreignNS); | |
} | |
//Active formatting elements reconstruction | |
_reconstructActiveFormattingElements() { | |
const listLength = this.activeFormattingElements.length; | |
if (listLength) { | |
let unopenIdx = listLength; | |
let entry = null; | |
do { | |
unopenIdx--; | |
entry = this.activeFormattingElements.entries[unopenIdx]; | |
if (entry.type === FormattingElementList.MARKER_ENTRY || this.openElements.contains(entry.element)) { | |
unopenIdx++; | |
break; | |
} | |
} while (unopenIdx > 0); | |
for (let i = unopenIdx; i < listLength; i++) { | |
entry = this.activeFormattingElements.entries[i]; | |
this._insertElement(entry.token, this.treeAdapter.getNamespaceURI(entry.element)); | |
entry.element = this.openElements.current; | |
} | |
} | |
} | |
//Close elements | |
_closeTableCell() { | |
this.openElements.generateImpliedEndTags(); | |
this.openElements.popUntilTableCellPopped(); | |
this.activeFormattingElements.clearToLastMarker(); | |
this.insertionMode = IN_ROW_MODE; | |
} | |
_closePElement() { | |
this.openElements.generateImpliedEndTagsWithExclusion($.P); | |
this.openElements.popUntilTagNamePopped($.P); | |
} | |
//Insertion modes | |
_resetInsertionMode() { | |
for (let i = this.openElements.stackTop, last = false; i >= 0; i--) { | |
let element = this.openElements.items[i]; | |
if (i === 0) { | |
last = true; | |
if (this.fragmentContext) { | |
element = this.fragmentContext; | |
} | |
} | |
const tn = this.treeAdapter.getTagName(element); | |
const newInsertionMode = INSERTION_MODE_RESET_MAP[tn]; | |
if (newInsertionMode) { | |
this.insertionMode = newInsertionMode; | |
break; | |
} else if (!last && (tn === $.TD || tn === $.TH)) { | |
this.insertionMode = IN_CELL_MODE; | |
break; | |
} else if (!last && tn === $.HEAD) { | |
this.insertionMode = IN_HEAD_MODE; | |
break; | |
} else if (tn === $.SELECT) { | |
this._resetInsertionModeForSelect(i); | |
break; | |
} else if (tn === $.TEMPLATE) { | |
this.insertionMode = this.currentTmplInsertionMode; | |
break; | |
} else if (tn === $.HTML) { | |
this.insertionMode = this.headElement ? AFTER_HEAD_MODE : BEFORE_HEAD_MODE; | |
break; | |
} else if (last) { | |
this.insertionMode = IN_BODY_MODE; | |
break; | |
} | |
} | |
} | |
_resetInsertionModeForSelect(selectIdx) { | |
if (selectIdx > 0) { | |
for (let i = selectIdx - 1; i > 0; i--) { | |
const ancestor = this.openElements.items[i]; | |
const tn = this.treeAdapter.getTagName(ancestor); | |
if (tn === $.TEMPLATE) { | |
break; | |
} else if (tn === $.TABLE) { | |
this.insertionMode = IN_SELECT_IN_TABLE_MODE; | |
return; | |
} | |
} | |
} | |
this.insertionMode = IN_SELECT_MODE; | |
} | |
_pushTmplInsertionMode(mode) { | |
this.tmplInsertionModeStack.push(mode); | |
this.tmplInsertionModeStackTop++; | |
this.currentTmplInsertionMode = mode; | |
} | |
_popTmplInsertionMode() { | |
this.tmplInsertionModeStack.pop(); | |
this.tmplInsertionModeStackTop--; | |
this.currentTmplInsertionMode = this.tmplInsertionModeStack[this.tmplInsertionModeStackTop]; | |
} | |
//Foster parenting | |
_isElementCausesFosterParenting(element) { | |
const tn = this.treeAdapter.getTagName(element); | |
return tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR; | |
} | |
_shouldFosterParentOnInsertion() { | |
return this.fosterParentingEnabled && this._isElementCausesFosterParenting(this.openElements.current); | |
} | |
_findFosterParentingLocation() { | |
const location = { | |
parent: null, | |
beforeElement: null | |
}; | |
for (let i = this.openElements.stackTop; i >= 0; i--) { | |
const openElement = this.openElements.items[i]; | |
const tn = this.treeAdapter.getTagName(openElement); | |
const ns = this.treeAdapter.getNamespaceURI(openElement); | |
if (tn === $.TEMPLATE && ns === NS.HTML) { | |
location.parent = this.treeAdapter.getTemplateContent(openElement); | |
break; | |
} else if (tn === $.TABLE) { | |
location.parent = this.treeAdapter.getParentNode(openElement); | |
if (location.parent) { | |
location.beforeElement = openElement; | |
} else { | |
location.parent = this.openElements.items[i - 1]; | |
} | |
break; | |
} | |
} | |
if (!location.parent) { | |
location.parent = this.openElements.items[0]; | |
} | |
return location; | |
} | |
_fosterParentElement(element) { | |
const location = this._findFosterParentingLocation(); | |
if (location.beforeElement) { | |
this.treeAdapter.insertBefore(location.parent, element, location.beforeElement); | |
} else { | |
this.treeAdapter.appendChild(location.parent, element); | |
} | |
} | |
_fosterParentText(chars) { | |
const location = this._findFosterParentingLocation(); | |
if (location.beforeElement) { | |
this.treeAdapter.insertTextBefore(location.parent, chars, location.beforeElement); | |
} else { | |
this.treeAdapter.insertText(location.parent, chars); | |
} | |
} | |
//Special elements | |
_isSpecialElement(element) { | |
const tn = this.treeAdapter.getTagName(element); | |
const ns = this.treeAdapter.getNamespaceURI(element); | |
return HTML.SPECIAL_ELEMENTS[ns][tn]; | |
} | |
} | |
module.exports = Parser; | |
//Adoption agency algorithm | |
//(see: http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#adoptionAgency) | |
//------------------------------------------------------------------ | |
//Steps 5-8 of the algorithm | |
function aaObtainFormattingElementEntry(p, token) { | |
let formattingElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName(token.tagName); | |
if (formattingElementEntry) { | |
if (!p.openElements.contains(formattingElementEntry.element)) { | |
p.activeFormattingElements.removeEntry(formattingElementEntry); | |
formattingElementEntry = null; | |
} else if (!p.openElements.hasInScope(token.tagName)) { | |
formattingElementEntry = null; | |
} | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
return formattingElementEntry; | |
} | |
//Steps 9 and 10 of the algorithm | |
function aaObtainFurthestBlock(p, formattingElementEntry) { | |
let furthestBlock = null; | |
for (let i = p.openElements.stackTop; i >= 0; i--) { | |
const element = p.openElements.items[i]; | |
if (element === formattingElementEntry.element) { | |
break; | |
} | |
if (p._isSpecialElement(element)) { | |
furthestBlock = element; | |
} | |
} | |
if (!furthestBlock) { | |
p.openElements.popUntilElementPopped(formattingElementEntry.element); | |
p.activeFormattingElements.removeEntry(formattingElementEntry); | |
} | |
return furthestBlock; | |
} | |
//Step 13 of the algorithm | |
function aaInnerLoop(p, furthestBlock, formattingElement) { | |
let lastElement = furthestBlock; | |
let nextElement = p.openElements.getCommonAncestor(furthestBlock); | |
for (let i = 0, element = nextElement; element !== formattingElement; i++, element = nextElement) { | |
//NOTE: store next element for the next loop iteration (it may be deleted from the stack by step 9.5) | |
nextElement = p.openElements.getCommonAncestor(element); | |
const elementEntry = p.activeFormattingElements.getElementEntry(element); | |
const counterOverflow = elementEntry && i >= AA_INNER_LOOP_ITER; | |
const shouldRemoveFromOpenElements = !elementEntry || counterOverflow; | |
if (shouldRemoveFromOpenElements) { | |
if (counterOverflow) { | |
p.activeFormattingElements.removeEntry(elementEntry); | |
} | |
p.openElements.remove(element); | |
} else { | |
element = aaRecreateElementFromEntry(p, elementEntry); | |
if (lastElement === furthestBlock) { | |
p.activeFormattingElements.bookmark = elementEntry; | |
} | |
p.treeAdapter.detachNode(lastElement); | |
p.treeAdapter.appendChild(element, lastElement); | |
lastElement = element; | |
} | |
} | |
return lastElement; | |
} | |
//Step 13.7 of the algorithm | |
function aaRecreateElementFromEntry(p, elementEntry) { | |
const ns = p.treeAdapter.getNamespaceURI(elementEntry.element); | |
const newElement = p.treeAdapter.createElement(elementEntry.token.tagName, ns, elementEntry.token.attrs); | |
p.openElements.replace(elementEntry.element, newElement); | |
elementEntry.element = newElement; | |
return newElement; | |
} | |
//Step 14 of the algorithm | |
function aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement) { | |
if (p._isElementCausesFosterParenting(commonAncestor)) { | |
p._fosterParentElement(lastElement); | |
} else { | |
const tn = p.treeAdapter.getTagName(commonAncestor); | |
const ns = p.treeAdapter.getNamespaceURI(commonAncestor); | |
if (tn === $.TEMPLATE && ns === NS.HTML) { | |
commonAncestor = p.treeAdapter.getTemplateContent(commonAncestor); | |
} | |
p.treeAdapter.appendChild(commonAncestor, lastElement); | |
} | |
} | |
//Steps 15-19 of the algorithm | |
function aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry) { | |
const ns = p.treeAdapter.getNamespaceURI(formattingElementEntry.element); | |
const token = formattingElementEntry.token; | |
const newElement = p.treeAdapter.createElement(token.tagName, ns, token.attrs); | |
p._adoptNodes(furthestBlock, newElement); | |
p.treeAdapter.appendChild(furthestBlock, newElement); | |
p.activeFormattingElements.insertElementAfterBookmark(newElement, formattingElementEntry.token); | |
p.activeFormattingElements.removeEntry(formattingElementEntry); | |
p.openElements.remove(formattingElementEntry.element); | |
p.openElements.insertAfter(furthestBlock, newElement); | |
} | |
//Algorithm entry point | |
function callAdoptionAgency(p, token) { | |
let formattingElementEntry; | |
for (let i = 0; i < AA_OUTER_LOOP_ITER; i++) { | |
formattingElementEntry = aaObtainFormattingElementEntry(p, token, formattingElementEntry); | |
if (!formattingElementEntry) { | |
break; | |
} | |
const furthestBlock = aaObtainFurthestBlock(p, formattingElementEntry); | |
if (!furthestBlock) { | |
break; | |
} | |
p.activeFormattingElements.bookmark = formattingElementEntry; | |
const lastElement = aaInnerLoop(p, furthestBlock, formattingElementEntry.element); | |
const commonAncestor = p.openElements.getCommonAncestor(formattingElementEntry.element); | |
p.treeAdapter.detachNode(lastElement); | |
aaInsertLastNodeInCommonAncestor(p, commonAncestor, lastElement); | |
aaReplaceFormattingElement(p, furthestBlock, formattingElementEntry); | |
} | |
} | |
//Generic token handlers | |
//------------------------------------------------------------------ | |
function ignoreToken() { | |
//NOTE: do nothing =) | |
} | |
function misplacedDoctype(p) { | |
p._err(ERR.misplacedDoctype); | |
} | |
function appendComment(p, token) { | |
p._appendCommentNode(token, p.openElements.currentTmplContent || p.openElements.current); | |
} | |
function appendCommentToRootHtmlElement(p, token) { | |
p._appendCommentNode(token, p.openElements.items[0]); | |
} | |
function appendCommentToDocument(p, token) { | |
p._appendCommentNode(token, p.document); | |
} | |
function insertCharacters(p, token) { | |
p._insertCharacters(token); | |
} | |
function stopParsing(p) { | |
p.stopped = true; | |
} | |
// The "initial" insertion mode | |
//------------------------------------------------------------------ | |
function doctypeInInitialMode(p, token) { | |
p._setDocumentType(token); | |
const mode = token.forceQuirks ? HTML.DOCUMENT_MODE.QUIRKS : doctype.getDocumentMode(token); | |
if (!doctype.isConforming(token)) { | |
p._err(ERR.nonConformingDoctype); | |
} | |
p.treeAdapter.setDocumentMode(p.document, mode); | |
p.insertionMode = BEFORE_HTML_MODE; | |
} | |
function tokenInInitialMode(p, token) { | |
p._err(ERR.missingDoctype, { beforeToken: true }); | |
p.treeAdapter.setDocumentMode(p.document, HTML.DOCUMENT_MODE.QUIRKS); | |
p.insertionMode = BEFORE_HTML_MODE; | |
p._processToken(token); | |
} | |
// The "before html" insertion mode | |
//------------------------------------------------------------------ | |
function startTagBeforeHtml(p, token) { | |
if (token.tagName === $.HTML) { | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = BEFORE_HEAD_MODE; | |
} else { | |
tokenBeforeHtml(p, token); | |
} | |
} | |
function endTagBeforeHtml(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML || tn === $.HEAD || tn === $.BODY || tn === $.BR) { | |
tokenBeforeHtml(p, token); | |
} | |
} | |
function tokenBeforeHtml(p, token) { | |
p._insertFakeRootElement(); | |
p.insertionMode = BEFORE_HEAD_MODE; | |
p._processToken(token); | |
} | |
// The "before head" insertion mode | |
//------------------------------------------------------------------ | |
function startTagBeforeHead(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.HEAD) { | |
p._insertElement(token, NS.HTML); | |
p.headElement = p.openElements.current; | |
p.insertionMode = IN_HEAD_MODE; | |
} else { | |
tokenBeforeHead(p, token); | |
} | |
} | |
function endTagBeforeHead(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HEAD || tn === $.BODY || tn === $.HTML || tn === $.BR) { | |
tokenBeforeHead(p, token); | |
} else { | |
p._err(ERR.endTagWithoutMatchingOpenElement); | |
} | |
} | |
function tokenBeforeHead(p, token) { | |
p._insertFakeElement($.HEAD); | |
p.headElement = p.openElements.current; | |
p.insertionMode = IN_HEAD_MODE; | |
p._processToken(token); | |
} | |
// The "in head" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInHead(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.BASE || tn === $.BASEFONT || tn === $.BGSOUND || tn === $.LINK || tn === $.META) { | |
p._appendElement(token, NS.HTML); | |
token.ackSelfClosing = true; | |
} else if (tn === $.TITLE) { | |
p._switchToTextParsing(token, Tokenizer.MODE.RCDATA); | |
} else if (tn === $.NOSCRIPT) { | |
if (p.options.scriptingEnabled) { | |
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); | |
} else { | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_HEAD_NO_SCRIPT_MODE; | |
} | |
} else if (tn === $.NOFRAMES || tn === $.STYLE) { | |
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); | |
} else if (tn === $.SCRIPT) { | |
p._switchToTextParsing(token, Tokenizer.MODE.SCRIPT_DATA); | |
} else if (tn === $.TEMPLATE) { | |
p._insertTemplate(token, NS.HTML); | |
p.activeFormattingElements.insertMarker(); | |
p.framesetOk = false; | |
p.insertionMode = IN_TEMPLATE_MODE; | |
p._pushTmplInsertionMode(IN_TEMPLATE_MODE); | |
} else if (tn === $.HEAD) { | |
p._err(ERR.misplacedStartTagForHeadElement); | |
} else { | |
tokenInHead(p, token); | |
} | |
} | |
function endTagInHead(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HEAD) { | |
p.openElements.pop(); | |
p.insertionMode = AFTER_HEAD_MODE; | |
} else if (tn === $.BODY || tn === $.BR || tn === $.HTML) { | |
tokenInHead(p, token); | |
} else if (tn === $.TEMPLATE) { | |
if (p.openElements.tmplCount > 0) { | |
p.openElements.generateImpliedEndTagsThoroughly(); | |
if (p.openElements.currentTagName !== $.TEMPLATE) { | |
p._err(ERR.closingOfElementWithOpenChildElements); | |
} | |
p.openElements.popUntilTagNamePopped($.TEMPLATE); | |
p.activeFormattingElements.clearToLastMarker(); | |
p._popTmplInsertionMode(); | |
p._resetInsertionMode(); | |
} else { | |
p._err(ERR.endTagWithoutMatchingOpenElement); | |
} | |
} else { | |
p._err(ERR.endTagWithoutMatchingOpenElement); | |
} | |
} | |
function tokenInHead(p, token) { | |
p.openElements.pop(); | |
p.insertionMode = AFTER_HEAD_MODE; | |
p._processToken(token); | |
} | |
// The "in head no script" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInHeadNoScript(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if ( | |
tn === $.BASEFONT || | |
tn === $.BGSOUND || | |
tn === $.HEAD || | |
tn === $.LINK || | |
tn === $.META || | |
tn === $.NOFRAMES || | |
tn === $.STYLE | |
) { | |
startTagInHead(p, token); | |
} else if (tn === $.NOSCRIPT) { | |
p._err(ERR.nestedNoscriptInHead); | |
} else { | |
tokenInHeadNoScript(p, token); | |
} | |
} | |
function endTagInHeadNoScript(p, token) { | |
const tn = token.tagName; | |
if (tn === $.NOSCRIPT) { | |
p.openElements.pop(); | |
p.insertionMode = IN_HEAD_MODE; | |
} else if (tn === $.BR) { | |
tokenInHeadNoScript(p, token); | |
} else { | |
p._err(ERR.endTagWithoutMatchingOpenElement); | |
} | |
} | |
function tokenInHeadNoScript(p, token) { | |
const errCode = | |
token.type === Tokenizer.EOF_TOKEN ? ERR.openElementsLeftAfterEof : ERR.disallowedContentInNoscriptInHead; | |
p._err(errCode); | |
p.openElements.pop(); | |
p.insertionMode = IN_HEAD_MODE; | |
p._processToken(token); | |
} | |
// The "after head" insertion mode | |
//------------------------------------------------------------------ | |
function startTagAfterHead(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.BODY) { | |
p._insertElement(token, NS.HTML); | |
p.framesetOk = false; | |
p.insertionMode = IN_BODY_MODE; | |
} else if (tn === $.FRAMESET) { | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_FRAMESET_MODE; | |
} else if ( | |
tn === $.BASE || | |
tn === $.BASEFONT || | |
tn === $.BGSOUND || | |
tn === $.LINK || | |
tn === $.META || | |
tn === $.NOFRAMES || | |
tn === $.SCRIPT || | |
tn === $.STYLE || | |
tn === $.TEMPLATE || | |
tn === $.TITLE | |
) { | |
p._err(ERR.abandonedHeadElementChild); | |
p.openElements.push(p.headElement); | |
startTagInHead(p, token); | |
p.openElements.remove(p.headElement); | |
} else if (tn === $.HEAD) { | |
p._err(ERR.misplacedStartTagForHeadElement); | |
} else { | |
tokenAfterHead(p, token); | |
} | |
} | |
function endTagAfterHead(p, token) { | |
const tn = token.tagName; | |
if (tn === $.BODY || tn === $.HTML || tn === $.BR) { | |
tokenAfterHead(p, token); | |
} else if (tn === $.TEMPLATE) { | |
endTagInHead(p, token); | |
} else { | |
p._err(ERR.endTagWithoutMatchingOpenElement); | |
} | |
} | |
function tokenAfterHead(p, token) { | |
p._insertFakeElement($.BODY); | |
p.insertionMode = IN_BODY_MODE; | |
p._processToken(token); | |
} | |
// The "in body" insertion mode | |
//------------------------------------------------------------------ | |
function whitespaceCharacterInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._insertCharacters(token); | |
} | |
function characterInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._insertCharacters(token); | |
p.framesetOk = false; | |
} | |
function htmlStartTagInBody(p, token) { | |
if (p.openElements.tmplCount === 0) { | |
p.treeAdapter.adoptAttributes(p.openElements.items[0], token.attrs); | |
} | |
} | |
function bodyStartTagInBody(p, token) { | |
const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); | |
if (bodyElement && p.openElements.tmplCount === 0) { | |
p.framesetOk = false; | |
p.treeAdapter.adoptAttributes(bodyElement, token.attrs); | |
} | |
} | |
function framesetStartTagInBody(p, token) { | |
const bodyElement = p.openElements.tryPeekProperlyNestedBodyElement(); | |
if (p.framesetOk && bodyElement) { | |
p.treeAdapter.detachNode(bodyElement); | |
p.openElements.popAllUpToHtmlElement(); | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_FRAMESET_MODE; | |
} | |
} | |
function addressStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
} | |
function numberedHeaderStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
const tn = p.openElements.currentTagName; | |
if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) { | |
p.openElements.pop(); | |
} | |
p._insertElement(token, NS.HTML); | |
} | |
function preStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
//NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move | |
//on to the next one. (Newlines at the start of pre blocks are ignored as an authoring convenience.) | |
p.skipNextNewLine = true; | |
p.framesetOk = false; | |
} | |
function formStartTagInBody(p, token) { | |
const inTemplate = p.openElements.tmplCount > 0; | |
if (!p.formElement || inTemplate) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
if (!inTemplate) { | |
p.formElement = p.openElements.current; | |
} | |
} | |
} | |
function listItemStartTagInBody(p, token) { | |
p.framesetOk = false; | |
const tn = token.tagName; | |
for (let i = p.openElements.stackTop; i >= 0; i--) { | |
const element = p.openElements.items[i]; | |
const elementTn = p.treeAdapter.getTagName(element); | |
let closeTn = null; | |
if (tn === $.LI && elementTn === $.LI) { | |
closeTn = $.LI; | |
} else if ((tn === $.DD || tn === $.DT) && (elementTn === $.DD || elementTn === $.DT)) { | |
closeTn = elementTn; | |
} | |
if (closeTn) { | |
p.openElements.generateImpliedEndTagsWithExclusion(closeTn); | |
p.openElements.popUntilTagNamePopped(closeTn); | |
break; | |
} | |
if (elementTn !== $.ADDRESS && elementTn !== $.DIV && elementTn !== $.P && p._isSpecialElement(element)) { | |
break; | |
} | |
} | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
} | |
function plaintextStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
p.tokenizer.state = Tokenizer.MODE.PLAINTEXT; | |
} | |
function buttonStartTagInBody(p, token) { | |
if (p.openElements.hasInScope($.BUTTON)) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilTagNamePopped($.BUTTON); | |
} | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
p.framesetOk = false; | |
} | |
function aStartTagInBody(p, token) { | |
const activeElementEntry = p.activeFormattingElements.getElementEntryInScopeWithTagName($.A); | |
if (activeElementEntry) { | |
callAdoptionAgency(p, token); | |
p.openElements.remove(activeElementEntry.element); | |
p.activeFormattingElements.removeEntry(activeElementEntry); | |
} | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
p.activeFormattingElements.pushElement(p.openElements.current, token); | |
} | |
function bStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
p.activeFormattingElements.pushElement(p.openElements.current, token); | |
} | |
function nobrStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
if (p.openElements.hasInScope($.NOBR)) { | |
callAdoptionAgency(p, token); | |
p._reconstructActiveFormattingElements(); | |
} | |
p._insertElement(token, NS.HTML); | |
p.activeFormattingElements.pushElement(p.openElements.current, token); | |
} | |
function appletStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
p.activeFormattingElements.insertMarker(); | |
p.framesetOk = false; | |
} | |
function tableStartTagInBody(p, token) { | |
if ( | |
p.treeAdapter.getDocumentMode(p.document) !== HTML.DOCUMENT_MODE.QUIRKS && | |
p.openElements.hasInButtonScope($.P) | |
) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
p.framesetOk = false; | |
p.insertionMode = IN_TABLE_MODE; | |
} | |
function areaStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._appendElement(token, NS.HTML); | |
p.framesetOk = false; | |
token.ackSelfClosing = true; | |
} | |
function inputStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._appendElement(token, NS.HTML); | |
const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE); | |
if (!inputType || inputType.toLowerCase() !== HIDDEN_INPUT_TYPE) { | |
p.framesetOk = false; | |
} | |
token.ackSelfClosing = true; | |
} | |
function paramStartTagInBody(p, token) { | |
p._appendElement(token, NS.HTML); | |
token.ackSelfClosing = true; | |
} | |
function hrStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._appendElement(token, NS.HTML); | |
p.framesetOk = false; | |
p.ackSelfClosing = true; | |
} | |
function imageStartTagInBody(p, token) { | |
token.tagName = $.IMG; | |
areaStartTagInBody(p, token); | |
} | |
function textareaStartTagInBody(p, token) { | |
p._insertElement(token, NS.HTML); | |
//NOTE: If the next token is a U+000A LINE FEED (LF) character token, then ignore that token and move | |
//on to the next one. (Newlines at the start of textarea elements are ignored as an authoring convenience.) | |
p.skipNextNewLine = true; | |
p.tokenizer.state = Tokenizer.MODE.RCDATA; | |
p.originalInsertionMode = p.insertionMode; | |
p.framesetOk = false; | |
p.insertionMode = TEXT_MODE; | |
} | |
function xmpStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._reconstructActiveFormattingElements(); | |
p.framesetOk = false; | |
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); | |
} | |
function iframeStartTagInBody(p, token) { | |
p.framesetOk = false; | |
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); | |
} | |
//NOTE: here we assume that we always act as an user agent with enabled plugins, so we parse | |
//<noembed> as a rawtext. | |
function noembedStartTagInBody(p, token) { | |
p._switchToTextParsing(token, Tokenizer.MODE.RAWTEXT); | |
} | |
function selectStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
p.framesetOk = false; | |
if ( | |
p.insertionMode === IN_TABLE_MODE || | |
p.insertionMode === IN_CAPTION_MODE || | |
p.insertionMode === IN_TABLE_BODY_MODE || | |
p.insertionMode === IN_ROW_MODE || | |
p.insertionMode === IN_CELL_MODE | |
) { | |
p.insertionMode = IN_SELECT_IN_TABLE_MODE; | |
} else { | |
p.insertionMode = IN_SELECT_MODE; | |
} | |
} | |
function optgroupStartTagInBody(p, token) { | |
if (p.openElements.currentTagName === $.OPTION) { | |
p.openElements.pop(); | |
} | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
} | |
function rbStartTagInBody(p, token) { | |
if (p.openElements.hasInScope($.RUBY)) { | |
p.openElements.generateImpliedEndTags(); | |
} | |
p._insertElement(token, NS.HTML); | |
} | |
function rtStartTagInBody(p, token) { | |
if (p.openElements.hasInScope($.RUBY)) { | |
p.openElements.generateImpliedEndTagsWithExclusion($.RTC); | |
} | |
p._insertElement(token, NS.HTML); | |
} | |
function menuStartTagInBody(p, token) { | |
if (p.openElements.hasInButtonScope($.P)) { | |
p._closePElement(); | |
} | |
p._insertElement(token, NS.HTML); | |
} | |
function mathStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
foreignContent.adjustTokenMathMLAttrs(token); | |
foreignContent.adjustTokenXMLAttrs(token); | |
if (token.selfClosing) { | |
p._appendElement(token, NS.MATHML); | |
} else { | |
p._insertElement(token, NS.MATHML); | |
} | |
token.ackSelfClosing = true; | |
} | |
function svgStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
foreignContent.adjustTokenSVGAttrs(token); | |
foreignContent.adjustTokenXMLAttrs(token); | |
if (token.selfClosing) { | |
p._appendElement(token, NS.SVG); | |
} else { | |
p._insertElement(token, NS.SVG); | |
} | |
token.ackSelfClosing = true; | |
} | |
function genericStartTagInBody(p, token) { | |
p._reconstructActiveFormattingElements(); | |
p._insertElement(token, NS.HTML); | |
} | |
//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here. | |
//It's faster than using dictionary. | |
function startTagInBody(p, token) { | |
const tn = token.tagName; | |
switch (tn.length) { | |
case 1: | |
if (tn === $.I || tn === $.S || tn === $.B || tn === $.U) { | |
bStartTagInBody(p, token); | |
} else if (tn === $.P) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.A) { | |
aStartTagInBody(p, token); | |
} else { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 2: | |
if (tn === $.DL || tn === $.OL || tn === $.UL) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) { | |
numberedHeaderStartTagInBody(p, token); | |
} else if (tn === $.LI || tn === $.DD || tn === $.DT) { | |
listItemStartTagInBody(p, token); | |
} else if (tn === $.EM || tn === $.TT) { | |
bStartTagInBody(p, token); | |
} else if (tn === $.BR) { | |
areaStartTagInBody(p, token); | |
} else if (tn === $.HR) { | |
hrStartTagInBody(p, token); | |
} else if (tn === $.RB) { | |
rbStartTagInBody(p, token); | |
} else if (tn === $.RT || tn === $.RP) { | |
rtStartTagInBody(p, token); | |
} else if (tn !== $.TH && tn !== $.TD && tn !== $.TR) { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 3: | |
if (tn === $.DIV || tn === $.DIR || tn === $.NAV) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.PRE) { | |
preStartTagInBody(p, token); | |
} else if (tn === $.BIG) { | |
bStartTagInBody(p, token); | |
} else if (tn === $.IMG || tn === $.WBR) { | |
areaStartTagInBody(p, token); | |
} else if (tn === $.XMP) { | |
xmpStartTagInBody(p, token); | |
} else if (tn === $.SVG) { | |
svgStartTagInBody(p, token); | |
} else if (tn === $.RTC) { | |
rbStartTagInBody(p, token); | |
} else if (tn !== $.COL) { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 4: | |
if (tn === $.HTML) { | |
htmlStartTagInBody(p, token); | |
} else if (tn === $.BASE || tn === $.LINK || tn === $.META) { | |
startTagInHead(p, token); | |
} else if (tn === $.BODY) { | |
bodyStartTagInBody(p, token); | |
} else if (tn === $.MAIN || tn === $.MENU) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.FORM) { | |
formStartTagInBody(p, token); | |
} else if (tn === $.CODE || tn === $.FONT) { | |
bStartTagInBody(p, token); | |
} else if (tn === $.NOBR) { | |
nobrStartTagInBody(p, token); | |
} else if (tn === $.AREA) { | |
areaStartTagInBody(p, token); | |
} else if (tn === $.MATH) { | |
mathStartTagInBody(p, token); | |
} else if (tn === $.MENU) { | |
menuStartTagInBody(p, token); | |
} else if (tn !== $.HEAD) { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 5: | |
if (tn === $.STYLE || tn === $.TITLE) { | |
startTagInHead(p, token); | |
} else if (tn === $.ASIDE) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.SMALL) { | |
bStartTagInBody(p, token); | |
} else if (tn === $.TABLE) { | |
tableStartTagInBody(p, token); | |
} else if (tn === $.EMBED) { | |
areaStartTagInBody(p, token); | |
} else if (tn === $.INPUT) { | |
inputStartTagInBody(p, token); | |
} else if (tn === $.PARAM || tn === $.TRACK) { | |
paramStartTagInBody(p, token); | |
} else if (tn === $.IMAGE) { | |
imageStartTagInBody(p, token); | |
} else if (tn !== $.FRAME && tn !== $.TBODY && tn !== $.TFOOT && tn !== $.THEAD) { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 6: | |
if (tn === $.SCRIPT) { | |
startTagInHead(p, token); | |
} else if ( | |
tn === $.CENTER || | |
tn === $.FIGURE || | |
tn === $.FOOTER || | |
tn === $.HEADER || | |
tn === $.HGROUP || | |
tn === $.DIALOG | |
) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.BUTTON) { | |
buttonStartTagInBody(p, token); | |
} else if (tn === $.STRIKE || tn === $.STRONG) { | |
bStartTagInBody(p, token); | |
} else if (tn === $.APPLET || tn === $.OBJECT) { | |
appletStartTagInBody(p, token); | |
} else if (tn === $.KEYGEN) { | |
areaStartTagInBody(p, token); | |
} else if (tn === $.SOURCE) { | |
paramStartTagInBody(p, token); | |
} else if (tn === $.IFRAME) { | |
iframeStartTagInBody(p, token); | |
} else if (tn === $.SELECT) { | |
selectStartTagInBody(p, token); | |
} else if (tn === $.OPTION) { | |
optgroupStartTagInBody(p, token); | |
} else { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 7: | |
if (tn === $.BGSOUND) { | |
startTagInHead(p, token); | |
} else if ( | |
tn === $.DETAILS || | |
tn === $.ADDRESS || | |
tn === $.ARTICLE || | |
tn === $.SECTION || | |
tn === $.SUMMARY | |
) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.LISTING) { | |
preStartTagInBody(p, token); | |
} else if (tn === $.MARQUEE) { | |
appletStartTagInBody(p, token); | |
} else if (tn === $.NOEMBED) { | |
noembedStartTagInBody(p, token); | |
} else if (tn !== $.CAPTION) { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 8: | |
if (tn === $.BASEFONT) { | |
startTagInHead(p, token); | |
} else if (tn === $.FRAMESET) { | |
framesetStartTagInBody(p, token); | |
} else if (tn === $.FIELDSET) { | |
addressStartTagInBody(p, token); | |
} else if (tn === $.TEXTAREA) { | |
textareaStartTagInBody(p, token); | |
} else if (tn === $.TEMPLATE) { | |
startTagInHead(p, token); | |
} else if (tn === $.NOSCRIPT) { | |
if (p.options.scriptingEnabled) { | |
noembedStartTagInBody(p, token); | |
} else { | |
genericStartTagInBody(p, token); | |
} | |
} else if (tn === $.OPTGROUP) { | |
optgroupStartTagInBody(p, token); | |
} else if (tn !== $.COLGROUP) { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 9: | |
if (tn === $.PLAINTEXT) { | |
plaintextStartTagInBody(p, token); | |
} else { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
case 10: | |
if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) { | |
addressStartTagInBody(p, token); | |
} else { | |
genericStartTagInBody(p, token); | |
} | |
break; | |
default: | |
genericStartTagInBody(p, token); | |
} | |
} | |
function bodyEndTagInBody(p) { | |
if (p.openElements.hasInScope($.BODY)) { | |
p.insertionMode = AFTER_BODY_MODE; | |
} | |
} | |
function htmlEndTagInBody(p, token) { | |
if (p.openElements.hasInScope($.BODY)) { | |
p.insertionMode = AFTER_BODY_MODE; | |
p._processToken(token); | |
} | |
} | |
function addressEndTagInBody(p, token) { | |
const tn = token.tagName; | |
if (p.openElements.hasInScope(tn)) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilTagNamePopped(tn); | |
} | |
} | |
function formEndTagInBody(p) { | |
const inTemplate = p.openElements.tmplCount > 0; | |
const formElement = p.formElement; | |
if (!inTemplate) { | |
p.formElement = null; | |
} | |
if ((formElement || inTemplate) && p.openElements.hasInScope($.FORM)) { | |
p.openElements.generateImpliedEndTags(); | |
if (inTemplate) { | |
p.openElements.popUntilTagNamePopped($.FORM); | |
} else { | |
p.openElements.remove(formElement); | |
} | |
} | |
} | |
function pEndTagInBody(p) { | |
if (!p.openElements.hasInButtonScope($.P)) { | |
p._insertFakeElement($.P); | |
} | |
p._closePElement(); | |
} | |
function liEndTagInBody(p) { | |
if (p.openElements.hasInListItemScope($.LI)) { | |
p.openElements.generateImpliedEndTagsWithExclusion($.LI); | |
p.openElements.popUntilTagNamePopped($.LI); | |
} | |
} | |
function ddEndTagInBody(p, token) { | |
const tn = token.tagName; | |
if (p.openElements.hasInScope(tn)) { | |
p.openElements.generateImpliedEndTagsWithExclusion(tn); | |
p.openElements.popUntilTagNamePopped(tn); | |
} | |
} | |
function numberedHeaderEndTagInBody(p) { | |
if (p.openElements.hasNumberedHeaderInScope()) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilNumberedHeaderPopped(); | |
} | |
} | |
function appletEndTagInBody(p, token) { | |
const tn = token.tagName; | |
if (p.openElements.hasInScope(tn)) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilTagNamePopped(tn); | |
p.activeFormattingElements.clearToLastMarker(); | |
} | |
} | |
function brEndTagInBody(p) { | |
p._reconstructActiveFormattingElements(); | |
p._insertFakeElement($.BR); | |
p.openElements.pop(); | |
p.framesetOk = false; | |
} | |
function genericEndTagInBody(p, token) { | |
const tn = token.tagName; | |
for (let i = p.openElements.stackTop; i > 0; i--) { | |
const element = p.openElements.items[i]; | |
if (p.treeAdapter.getTagName(element) === tn) { | |
p.openElements.generateImpliedEndTagsWithExclusion(tn); | |
p.openElements.popUntilElementPopped(element); | |
break; | |
} | |
if (p._isSpecialElement(element)) { | |
break; | |
} | |
} | |
} | |
//OPTIMIZATION: Integer comparisons are low-cost, so we can use very fast tag name length filters here. | |
//It's faster than using dictionary. | |
function endTagInBody(p, token) { | |
const tn = token.tagName; | |
switch (tn.length) { | |
case 1: | |
if (tn === $.A || tn === $.B || tn === $.I || tn === $.S || tn === $.U) { | |
callAdoptionAgency(p, token); | |
} else if (tn === $.P) { | |
pEndTagInBody(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 2: | |
if (tn === $.DL || tn === $.UL || tn === $.OL) { | |
addressEndTagInBody(p, token); | |
} else if (tn === $.LI) { | |
liEndTagInBody(p, token); | |
} else if (tn === $.DD || tn === $.DT) { | |
ddEndTagInBody(p, token); | |
} else if (tn === $.H1 || tn === $.H2 || tn === $.H3 || tn === $.H4 || tn === $.H5 || tn === $.H6) { | |
numberedHeaderEndTagInBody(p, token); | |
} else if (tn === $.BR) { | |
brEndTagInBody(p, token); | |
} else if (tn === $.EM || tn === $.TT) { | |
callAdoptionAgency(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 3: | |
if (tn === $.BIG) { | |
callAdoptionAgency(p, token); | |
} else if (tn === $.DIR || tn === $.DIV || tn === $.NAV || tn === $.PRE) { | |
addressEndTagInBody(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 4: | |
if (tn === $.BODY) { | |
bodyEndTagInBody(p, token); | |
} else if (tn === $.HTML) { | |
htmlEndTagInBody(p, token); | |
} else if (tn === $.FORM) { | |
formEndTagInBody(p, token); | |
} else if (tn === $.CODE || tn === $.FONT || tn === $.NOBR) { | |
callAdoptionAgency(p, token); | |
} else if (tn === $.MAIN || tn === $.MENU) { | |
addressEndTagInBody(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 5: | |
if (tn === $.ASIDE) { | |
addressEndTagInBody(p, token); | |
} else if (tn === $.SMALL) { | |
callAdoptionAgency(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 6: | |
if ( | |
tn === $.CENTER || | |
tn === $.FIGURE || | |
tn === $.FOOTER || | |
tn === $.HEADER || | |
tn === $.HGROUP || | |
tn === $.DIALOG | |
) { | |
addressEndTagInBody(p, token); | |
} else if (tn === $.APPLET || tn === $.OBJECT) { | |
appletEndTagInBody(p, token); | |
} else if (tn === $.STRIKE || tn === $.STRONG) { | |
callAdoptionAgency(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 7: | |
if ( | |
tn === $.ADDRESS || | |
tn === $.ARTICLE || | |
tn === $.DETAILS || | |
tn === $.SECTION || | |
tn === $.SUMMARY || | |
tn === $.LISTING | |
) { | |
addressEndTagInBody(p, token); | |
} else if (tn === $.MARQUEE) { | |
appletEndTagInBody(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 8: | |
if (tn === $.FIELDSET) { | |
addressEndTagInBody(p, token); | |
} else if (tn === $.TEMPLATE) { | |
endTagInHead(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
case 10: | |
if (tn === $.BLOCKQUOTE || tn === $.FIGCAPTION) { | |
addressEndTagInBody(p, token); | |
} else { | |
genericEndTagInBody(p, token); | |
} | |
break; | |
default: | |
genericEndTagInBody(p, token); | |
} | |
} | |
function eofInBody(p, token) { | |
if (p.tmplInsertionModeStackTop > -1) { | |
eofInTemplate(p, token); | |
} else { | |
p.stopped = true; | |
} | |
} | |
// The "text" insertion mode | |
//------------------------------------------------------------------ | |
function endTagInText(p, token) { | |
if (token.tagName === $.SCRIPT) { | |
p.pendingScript = p.openElements.current; | |
} | |
p.openElements.pop(); | |
p.insertionMode = p.originalInsertionMode; | |
} | |
function eofInText(p, token) { | |
p._err(ERR.eofInElementThatCanContainOnlyText); | |
p.openElements.pop(); | |
p.insertionMode = p.originalInsertionMode; | |
p._processToken(token); | |
} | |
// The "in table" insertion mode | |
//------------------------------------------------------------------ | |
function characterInTable(p, token) { | |
const curTn = p.openElements.currentTagName; | |
if (curTn === $.TABLE || curTn === $.TBODY || curTn === $.TFOOT || curTn === $.THEAD || curTn === $.TR) { | |
p.pendingCharacterTokens = []; | |
p.hasNonWhitespacePendingCharacterToken = false; | |
p.originalInsertionMode = p.insertionMode; | |
p.insertionMode = IN_TABLE_TEXT_MODE; | |
p._processToken(token); | |
} else { | |
tokenInTable(p, token); | |
} | |
} | |
function captionStartTagInTable(p, token) { | |
p.openElements.clearBackToTableContext(); | |
p.activeFormattingElements.insertMarker(); | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_CAPTION_MODE; | |
} | |
function colgroupStartTagInTable(p, token) { | |
p.openElements.clearBackToTableContext(); | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_COLUMN_GROUP_MODE; | |
} | |
function colStartTagInTable(p, token) { | |
p.openElements.clearBackToTableContext(); | |
p._insertFakeElement($.COLGROUP); | |
p.insertionMode = IN_COLUMN_GROUP_MODE; | |
p._processToken(token); | |
} | |
function tbodyStartTagInTable(p, token) { | |
p.openElements.clearBackToTableContext(); | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_TABLE_BODY_MODE; | |
} | |
function tdStartTagInTable(p, token) { | |
p.openElements.clearBackToTableContext(); | |
p._insertFakeElement($.TBODY); | |
p.insertionMode = IN_TABLE_BODY_MODE; | |
p._processToken(token); | |
} | |
function tableStartTagInTable(p, token) { | |
if (p.openElements.hasInTableScope($.TABLE)) { | |
p.openElements.popUntilTagNamePopped($.TABLE); | |
p._resetInsertionMode(); | |
p._processToken(token); | |
} | |
} | |
function inputStartTagInTable(p, token) { | |
const inputType = Tokenizer.getTokenAttr(token, ATTRS.TYPE); | |
if (inputType && inputType.toLowerCase() === HIDDEN_INPUT_TYPE) { | |
p._appendElement(token, NS.HTML); | |
} else { | |
tokenInTable(p, token); | |
} | |
token.ackSelfClosing = true; | |
} | |
function formStartTagInTable(p, token) { | |
if (!p.formElement && p.openElements.tmplCount === 0) { | |
p._insertElement(token, NS.HTML); | |
p.formElement = p.openElements.current; | |
p.openElements.pop(); | |
} | |
} | |
function startTagInTable(p, token) { | |
const tn = token.tagName; | |
switch (tn.length) { | |
case 2: | |
if (tn === $.TD || tn === $.TH || tn === $.TR) { | |
tdStartTagInTable(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
case 3: | |
if (tn === $.COL) { | |
colStartTagInTable(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
case 4: | |
if (tn === $.FORM) { | |
formStartTagInTable(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
case 5: | |
if (tn === $.TABLE) { | |
tableStartTagInTable(p, token); | |
} else if (tn === $.STYLE) { | |
startTagInHead(p, token); | |
} else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) { | |
tbodyStartTagInTable(p, token); | |
} else if (tn === $.INPUT) { | |
inputStartTagInTable(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
case 6: | |
if (tn === $.SCRIPT) { | |
startTagInHead(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
case 7: | |
if (tn === $.CAPTION) { | |
captionStartTagInTable(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
case 8: | |
if (tn === $.COLGROUP) { | |
colgroupStartTagInTable(p, token); | |
} else if (tn === $.TEMPLATE) { | |
startTagInHead(p, token); | |
} else { | |
tokenInTable(p, token); | |
} | |
break; | |
default: | |
tokenInTable(p, token); | |
} | |
} | |
function endTagInTable(p, token) { | |
const tn = token.tagName; | |
if (tn === $.TABLE) { | |
if (p.openElements.hasInTableScope($.TABLE)) { | |
p.openElements.popUntilTagNamePopped($.TABLE); | |
p._resetInsertionMode(); | |
} | |
} else if (tn === $.TEMPLATE) { | |
endTagInHead(p, token); | |
} else if ( | |
tn !== $.BODY && | |
tn !== $.CAPTION && | |
tn !== $.COL && | |
tn !== $.COLGROUP && | |
tn !== $.HTML && | |
tn !== $.TBODY && | |
tn !== $.TD && | |
tn !== $.TFOOT && | |
tn !== $.TH && | |
tn !== $.THEAD && | |
tn !== $.TR | |
) { | |
tokenInTable(p, token); | |
} | |
} | |
function tokenInTable(p, token) { | |
const savedFosterParentingState = p.fosterParentingEnabled; | |
p.fosterParentingEnabled = true; | |
p._processTokenInBodyMode(token); | |
p.fosterParentingEnabled = savedFosterParentingState; | |
} | |
// The "in table text" insertion mode | |
//------------------------------------------------------------------ | |
function whitespaceCharacterInTableText(p, token) { | |
p.pendingCharacterTokens.push(token); | |
} | |
function characterInTableText(p, token) { | |
p.pendingCharacterTokens.push(token); | |
p.hasNonWhitespacePendingCharacterToken = true; | |
} | |
function tokenInTableText(p, token) { | |
let i = 0; | |
if (p.hasNonWhitespacePendingCharacterToken) { | |
for (; i < p.pendingCharacterTokens.length; i++) { | |
tokenInTable(p, p.pendingCharacterTokens[i]); | |
} | |
} else { | |
for (; i < p.pendingCharacterTokens.length; i++) { | |
p._insertCharacters(p.pendingCharacterTokens[i]); | |
} | |
} | |
p.insertionMode = p.originalInsertionMode; | |
p._processToken(token); | |
} | |
// The "in caption" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInCaption(p, token) { | |
const tn = token.tagName; | |
if ( | |
tn === $.CAPTION || | |
tn === $.COL || | |
tn === $.COLGROUP || | |
tn === $.TBODY || | |
tn === $.TD || | |
tn === $.TFOOT || | |
tn === $.TH || | |
tn === $.THEAD || | |
tn === $.TR | |
) { | |
if (p.openElements.hasInTableScope($.CAPTION)) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilTagNamePopped($.CAPTION); | |
p.activeFormattingElements.clearToLastMarker(); | |
p.insertionMode = IN_TABLE_MODE; | |
p._processToken(token); | |
} | |
} else { | |
startTagInBody(p, token); | |
} | |
} | |
function endTagInCaption(p, token) { | |
const tn = token.tagName; | |
if (tn === $.CAPTION || tn === $.TABLE) { | |
if (p.openElements.hasInTableScope($.CAPTION)) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilTagNamePopped($.CAPTION); | |
p.activeFormattingElements.clearToLastMarker(); | |
p.insertionMode = IN_TABLE_MODE; | |
if (tn === $.TABLE) { | |
p._processToken(token); | |
} | |
} | |
} else if ( | |
tn !== $.BODY && | |
tn !== $.COL && | |
tn !== $.COLGROUP && | |
tn !== $.HTML && | |
tn !== $.TBODY && | |
tn !== $.TD && | |
tn !== $.TFOOT && | |
tn !== $.TH && | |
tn !== $.THEAD && | |
tn !== $.TR | |
) { | |
endTagInBody(p, token); | |
} | |
} | |
// The "in column group" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInColumnGroup(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.COL) { | |
p._appendElement(token, NS.HTML); | |
token.ackSelfClosing = true; | |
} else if (tn === $.TEMPLATE) { | |
startTagInHead(p, token); | |
} else { | |
tokenInColumnGroup(p, token); | |
} | |
} | |
function endTagInColumnGroup(p, token) { | |
const tn = token.tagName; | |
if (tn === $.COLGROUP) { | |
if (p.openElements.currentTagName === $.COLGROUP) { | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_MODE; | |
} | |
} else if (tn === $.TEMPLATE) { | |
endTagInHead(p, token); | |
} else if (tn !== $.COL) { | |
tokenInColumnGroup(p, token); | |
} | |
} | |
function tokenInColumnGroup(p, token) { | |
if (p.openElements.currentTagName === $.COLGROUP) { | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_MODE; | |
p._processToken(token); | |
} | |
} | |
// The "in table body" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInTableBody(p, token) { | |
const tn = token.tagName; | |
if (tn === $.TR) { | |
p.openElements.clearBackToTableBodyContext(); | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_ROW_MODE; | |
} else if (tn === $.TH || tn === $.TD) { | |
p.openElements.clearBackToTableBodyContext(); | |
p._insertFakeElement($.TR); | |
p.insertionMode = IN_ROW_MODE; | |
p._processToken(token); | |
} else if ( | |
tn === $.CAPTION || | |
tn === $.COL || | |
tn === $.COLGROUP || | |
tn === $.TBODY || | |
tn === $.TFOOT || | |
tn === $.THEAD | |
) { | |
if (p.openElements.hasTableBodyContextInTableScope()) { | |
p.openElements.clearBackToTableBodyContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_MODE; | |
p._processToken(token); | |
} | |
} else { | |
startTagInTable(p, token); | |
} | |
} | |
function endTagInTableBody(p, token) { | |
const tn = token.tagName; | |
if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) { | |
if (p.openElements.hasInTableScope(tn)) { | |
p.openElements.clearBackToTableBodyContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_MODE; | |
} | |
} else if (tn === $.TABLE) { | |
if (p.openElements.hasTableBodyContextInTableScope()) { | |
p.openElements.clearBackToTableBodyContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_MODE; | |
p._processToken(token); | |
} | |
} else if ( | |
(tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) || | |
(tn !== $.HTML && tn !== $.TD && tn !== $.TH && tn !== $.TR) | |
) { | |
endTagInTable(p, token); | |
} | |
} | |
// The "in row" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInRow(p, token) { | |
const tn = token.tagName; | |
if (tn === $.TH || tn === $.TD) { | |
p.openElements.clearBackToTableRowContext(); | |
p._insertElement(token, NS.HTML); | |
p.insertionMode = IN_CELL_MODE; | |
p.activeFormattingElements.insertMarker(); | |
} else if ( | |
tn === $.CAPTION || | |
tn === $.COL || | |
tn === $.COLGROUP || | |
tn === $.TBODY || | |
tn === $.TFOOT || | |
tn === $.THEAD || | |
tn === $.TR | |
) { | |
if (p.openElements.hasInTableScope($.TR)) { | |
p.openElements.clearBackToTableRowContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_BODY_MODE; | |
p._processToken(token); | |
} | |
} else { | |
startTagInTable(p, token); | |
} | |
} | |
function endTagInRow(p, token) { | |
const tn = token.tagName; | |
if (tn === $.TR) { | |
if (p.openElements.hasInTableScope($.TR)) { | |
p.openElements.clearBackToTableRowContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_BODY_MODE; | |
} | |
} else if (tn === $.TABLE) { | |
if (p.openElements.hasInTableScope($.TR)) { | |
p.openElements.clearBackToTableRowContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_BODY_MODE; | |
p._processToken(token); | |
} | |
} else if (tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD) { | |
if (p.openElements.hasInTableScope(tn) || p.openElements.hasInTableScope($.TR)) { | |
p.openElements.clearBackToTableRowContext(); | |
p.openElements.pop(); | |
p.insertionMode = IN_TABLE_BODY_MODE; | |
p._processToken(token); | |
} | |
} else if ( | |
(tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP) || | |
(tn !== $.HTML && tn !== $.TD && tn !== $.TH) | |
) { | |
endTagInTable(p, token); | |
} | |
} | |
// The "in cell" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInCell(p, token) { | |
const tn = token.tagName; | |
if ( | |
tn === $.CAPTION || | |
tn === $.COL || | |
tn === $.COLGROUP || | |
tn === $.TBODY || | |
tn === $.TD || | |
tn === $.TFOOT || | |
tn === $.TH || | |
tn === $.THEAD || | |
tn === $.TR | |
) { | |
if (p.openElements.hasInTableScope($.TD) || p.openElements.hasInTableScope($.TH)) { | |
p._closeTableCell(); | |
p._processToken(token); | |
} | |
} else { | |
startTagInBody(p, token); | |
} | |
} | |
function endTagInCell(p, token) { | |
const tn = token.tagName; | |
if (tn === $.TD || tn === $.TH) { | |
if (p.openElements.hasInTableScope(tn)) { | |
p.openElements.generateImpliedEndTags(); | |
p.openElements.popUntilTagNamePopped(tn); | |
p.activeFormattingElements.clearToLastMarker(); | |
p.insertionMode = IN_ROW_MODE; | |
} | |
} else if (tn === $.TABLE || tn === $.TBODY || tn === $.TFOOT || tn === $.THEAD || tn === $.TR) { | |
if (p.openElements.hasInTableScope(tn)) { | |
p._closeTableCell(); | |
p._processToken(token); | |
} | |
} else if (tn !== $.BODY && tn !== $.CAPTION && tn !== $.COL && tn !== $.COLGROUP && tn !== $.HTML) { | |
endTagInBody(p, token); | |
} | |
} | |
// The "in select" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInSelect(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.OPTION) { | |
if (p.openElements.currentTagName === $.OPTION) { | |
p.openElements.pop(); | |
} | |
p._insertElement(token, NS.HTML); | |
} else if (tn === $.OPTGROUP) { | |
if (p.openElements.currentTagName === $.OPTION) { | |
p.openElements.pop(); | |
} | |
if (p.openElements.currentTagName === $.OPTGROUP) { | |
p.openElements.pop(); | |
} | |
p._insertElement(token, NS.HTML); | |
} else if (tn === $.INPUT || tn === $.KEYGEN || tn === $.TEXTAREA || tn === $.SELECT) { | |
if (p.openElements.hasInSelectScope($.SELECT)) { | |
p.openElements.popUntilTagNamePopped($.SELECT); | |
p._resetInsertionMode(); | |
if (tn !== $.SELECT) { | |
p._processToken(token); | |
} | |
} | |
} else if (tn === $.SCRIPT || tn === $.TEMPLATE) { | |
startTagInHead(p, token); | |
} | |
} | |
function endTagInSelect(p, token) { | |
const tn = token.tagName; | |
if (tn === $.OPTGROUP) { | |
const prevOpenElement = p.openElements.items[p.openElements.stackTop - 1]; | |
const prevOpenElementTn = prevOpenElement && p.treeAdapter.getTagName(prevOpenElement); | |
if (p.openElements.currentTagName === $.OPTION && prevOpenElementTn === $.OPTGROUP) { | |
p.openElements.pop(); | |
} | |
if (p.openElements.currentTagName === $.OPTGROUP) { | |
p.openElements.pop(); | |
} | |
} else if (tn === $.OPTION) { | |
if (p.openElements.currentTagName === $.OPTION) { | |
p.openElements.pop(); | |
} | |
} else if (tn === $.SELECT && p.openElements.hasInSelectScope($.SELECT)) { | |
p.openElements.popUntilTagNamePopped($.SELECT); | |
p._resetInsertionMode(); | |
} else if (tn === $.TEMPLATE) { | |
endTagInHead(p, token); | |
} | |
} | |
//12.2.5.4.17 The "in select in table" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInSelectInTable(p, token) { | |
const tn = token.tagName; | |
if ( | |
tn === $.CAPTION || | |
tn === $.TABLE || | |
tn === $.TBODY || | |
tn === $.TFOOT || | |
tn === $.THEAD || | |
tn === $.TR || | |
tn === $.TD || | |
tn === $.TH | |
) { | |
p.openElements.popUntilTagNamePopped($.SELECT); | |
p._resetInsertionMode(); | |
p._processToken(token); | |
} else { | |
startTagInSelect(p, token); | |
} | |
} | |
function endTagInSelectInTable(p, token) { | |
const tn = token.tagName; | |
if ( | |
tn === $.CAPTION || | |
tn === $.TABLE || | |
tn === $.TBODY || | |
tn === $.TFOOT || | |
tn === $.THEAD || | |
tn === $.TR || | |
tn === $.TD || | |
tn === $.TH | |
) { | |
if (p.openElements.hasInTableScope(tn)) { | |
p.openElements.popUntilTagNamePopped($.SELECT); | |
p._resetInsertionMode(); | |
p._processToken(token); | |
} | |
} else { | |
endTagInSelect(p, token); | |
} | |
} | |
// The "in template" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInTemplate(p, token) { | |
const tn = token.tagName; | |
if ( | |
tn === $.BASE || | |
tn === $.BASEFONT || | |
tn === $.BGSOUND || | |
tn === $.LINK || | |
tn === $.META || | |
tn === $.NOFRAMES || | |
tn === $.SCRIPT || | |
tn === $.STYLE || | |
tn === $.TEMPLATE || | |
tn === $.TITLE | |
) { | |
startTagInHead(p, token); | |
} else { | |
const newInsertionMode = TEMPLATE_INSERTION_MODE_SWITCH_MAP[tn] || IN_BODY_MODE; | |
p._popTmplInsertionMode(); | |
p._pushTmplInsertionMode(newInsertionMode); | |
p.insertionMode = newInsertionMode; | |
p._processToken(token); | |
} | |
} | |
function endTagInTemplate(p, token) { | |
if (token.tagName === $.TEMPLATE) { | |
endTagInHead(p, token); | |
} | |
} | |
function eofInTemplate(p, token) { | |
if (p.openElements.tmplCount > 0) { | |
p.openElements.popUntilTagNamePopped($.TEMPLATE); | |
p.activeFormattingElements.clearToLastMarker(); | |
p._popTmplInsertionMode(); | |
p._resetInsertionMode(); | |
p._processToken(token); | |
} else { | |
p.stopped = true; | |
} | |
} | |
// The "after body" insertion mode | |
//------------------------------------------------------------------ | |
function startTagAfterBody(p, token) { | |
if (token.tagName === $.HTML) { | |
startTagInBody(p, token); | |
} else { | |
tokenAfterBody(p, token); | |
} | |
} | |
function endTagAfterBody(p, token) { | |
if (token.tagName === $.HTML) { | |
if (!p.fragmentContext) { | |
p.insertionMode = AFTER_AFTER_BODY_MODE; | |
} | |
} else { | |
tokenAfterBody(p, token); | |
} | |
} | |
function tokenAfterBody(p, token) { | |
p.insertionMode = IN_BODY_MODE; | |
p._processToken(token); | |
} | |
// The "in frameset" insertion mode | |
//------------------------------------------------------------------ | |
function startTagInFrameset(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.FRAMESET) { | |
p._insertElement(token, NS.HTML); | |
} else if (tn === $.FRAME) { | |
p._appendElement(token, NS.HTML); | |
token.ackSelfClosing = true; | |
} else if (tn === $.NOFRAMES) { | |
startTagInHead(p, token); | |
} | |
} | |
function endTagInFrameset(p, token) { | |
if (token.tagName === $.FRAMESET && !p.openElements.isRootHtmlElementCurrent()) { | |
p.openElements.pop(); | |
if (!p.fragmentContext && p.openElements.currentTagName !== $.FRAMESET) { | |
p.insertionMode = AFTER_FRAMESET_MODE; | |
} | |
} | |
} | |
// The "after frameset" insertion mode | |
//------------------------------------------------------------------ | |
function startTagAfterFrameset(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.NOFRAMES) { | |
startTagInHead(p, token); | |
} | |
} | |
function endTagAfterFrameset(p, token) { | |
if (token.tagName === $.HTML) { | |
p.insertionMode = AFTER_AFTER_FRAMESET_MODE; | |
} | |
} | |
// The "after after body" insertion mode | |
//------------------------------------------------------------------ | |
function startTagAfterAfterBody(p, token) { | |
if (token.tagName === $.HTML) { | |
startTagInBody(p, token); | |
} else { | |
tokenAfterAfterBody(p, token); | |
} | |
} | |
function tokenAfterAfterBody(p, token) { | |
p.insertionMode = IN_BODY_MODE; | |
p._processToken(token); | |
} | |
// The "after after frameset" insertion mode | |
//------------------------------------------------------------------ | |
function startTagAfterAfterFrameset(p, token) { | |
const tn = token.tagName; | |
if (tn === $.HTML) { | |
startTagInBody(p, token); | |
} else if (tn === $.NOFRAMES) { | |
startTagInHead(p, token); | |
} | |
} | |
// The rules for parsing tokens in foreign content | |
//------------------------------------------------------------------ | |
function nullCharacterInForeignContent(p, token) { | |
token.chars = unicode.REPLACEMENT_CHARACTER; | |
p._insertCharacters(token); | |
} | |
function characterInForeignContent(p, token) { | |
p._insertCharacters(token); | |
p.framesetOk = false; | |
} | |
function startTagInForeignContent(p, token) { | |
if (foreignContent.causesExit(token) && !p.fragmentContext) { | |
while ( | |
p.treeAdapter.getNamespaceURI(p.openElements.current) !== NS.HTML && | |
!p._isIntegrationPoint(p.openElements.current) | |
) { | |
p.openElements.pop(); | |
} | |
p._processToken(token); | |
} else { | |
const current = p._getAdjustedCurrentElement(); | |
const currentNs = p.treeAdapter.getNamespaceURI(current); | |
if (currentNs === NS.MATHML) { | |
foreignContent.adjustTokenMathMLAttrs(token); | |
} else if (currentNs === NS.SVG) { | |
foreignContent.adjustTokenSVGTagName(token); | |
foreignContent.adjustTokenSVGAttrs(token); | |
} | |
foreignContent.adjustTokenXMLAttrs(token); | |
if (token.selfClosing) { | |
p._appendElement(token, currentNs); | |
} else { | |
p._insertElement(token, currentNs); | |
} | |
token.ackSelfClosing = true; | |
} | |
} | |
function endTagInForeignContent(p, token) { | |
for (let i = p.openElements.stackTop; i > 0; i--) { | |
const element = p.openElements.items[i]; | |
if (p.treeAdapter.getNamespaceURI(element) === NS.HTML) { | |
p._processToken(token); | |
break; | |
} | |
if (p.treeAdapter.getTagName(element).toLowerCase() === token.tagName) { | |
p.openElements.popUntilElementPopped(element); | |
break; | |
} | |
} | |
} |