HTMLTreeBuilder.cpp [plain text]
#include "config.h"
#include "HTMLTreeBuilder.h"
#include "DocumentFragment.h"
#include "HTMLDocument.h"
#include "HTMLDocumentParser.h"
#include "HTMLFormElement.h"
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLParserIdioms.h"
#include "HTMLTableElement.h"
#include "HTMLTemplateElement.h"
#include "LocalizedStrings.h"
#include "NotImplemented.h"
#include "XLinkNames.h"
#include "XMLNSNames.h"
#include "XMLNames.h"
#include <wtf/MainThread.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/unicode/CharacterNames.h>
#if ENABLE(TELEPHONE_NUMBER_DETECTION)
#include "TelephoneNumberDetector.h"
#endif
namespace WebCore {
using namespace HTMLNames;
namespace {
inline bool isHTMLSpaceOrReplacementCharacter(UChar character)
{
return isHTMLSpace(character) || character == replacementCharacter;
}
}
static TextPosition uninitializedPositionValue1()
{
return TextPosition(OrdinalNumber::fromOneBasedInt(-1), OrdinalNumber::first());
}
static inline bool isAllWhitespace(const String& string)
{
return string.isAllSpecialCharacters<isHTMLSpace>();
}
static inline bool isAllWhitespaceOrReplacementCharacters(const String& string)
{
return string.isAllSpecialCharacters<isHTMLSpaceOrReplacementCharacter>();
}
static bool isNumberedHeaderTag(const AtomicString& tagName)
{
return tagName == h1Tag
|| tagName == h2Tag
|| tagName == h3Tag
|| tagName == h4Tag
|| tagName == h5Tag
|| tagName == h6Tag;
}
static bool isCaptionColOrColgroupTag(const AtomicString& tagName)
{
return tagName == captionTag
|| tagName == colTag
|| tagName == colgroupTag;
}
static bool isTableCellContextTag(const AtomicString& tagName)
{
return tagName == thTag || tagName == tdTag;
}
static bool isTableBodyContextTag(const AtomicString& tagName)
{
return tagName == tbodyTag
|| tagName == tfootTag
|| tagName == theadTag;
}
static bool isNonAnchorNonNobrFormattingTag(const AtomicString& tagName)
{
return tagName == bTag
|| tagName == bigTag
|| tagName == codeTag
|| tagName == emTag
|| tagName == fontTag
|| tagName == iTag
|| tagName == sTag
|| tagName == smallTag
|| tagName == strikeTag
|| tagName == strongTag
|| tagName == ttTag
|| tagName == uTag;
}
static bool isNonAnchorFormattingTag(const AtomicString& tagName)
{
return tagName == nobrTag
|| isNonAnchorNonNobrFormattingTag(tagName);
}
static bool isFormattingTag(const AtomicString& tagName)
{
return tagName == aTag || isNonAnchorFormattingTag(tagName);
}
class HTMLTreeBuilder::ExternalCharacterTokenBuffer {
WTF_MAKE_NONCOPYABLE(ExternalCharacterTokenBuffer);
public:
explicit ExternalCharacterTokenBuffer(AtomicHTMLToken* token)
: m_text(token->characters(), token->charactersLength())
, m_isAll8BitData(token->isAll8BitData())
{
ASSERT(!isEmpty());
}
explicit ExternalCharacterTokenBuffer(const String& string)
: m_text(string)
, m_isAll8BitData(m_text.length() && m_text.is8Bit())
{
ASSERT(!isEmpty());
}
~ExternalCharacterTokenBuffer()
{
ASSERT(isEmpty());
}
bool isEmpty() const { return m_text.isEmpty(); }
bool isAll8BitData() const { return m_isAll8BitData; }
void skipAtMostOneLeadingNewline()
{
ASSERT(!isEmpty());
if (m_text[0] == '\n')
m_text = m_text.substring(1);
}
void skipLeadingWhitespace()
{
skipLeading<isHTMLSpace>();
}
String takeLeadingWhitespace()
{
return takeLeading<isHTMLSpace>();
}
void skipLeadingNonWhitespace()
{
skipLeading<isNotHTMLSpace>();
}
String takeRemaining()
{
String result;
if (m_text.is8Bit() || !isAll8BitData())
result = m_text.toString();
else
result = String::make8BitFrom16BitSource(m_text.characters16(), m_text.length());
m_text = StringView();
return result;
}
void giveRemainingTo(StringBuilder& recipient)
{
recipient.append(m_text);
m_text = StringView();
}
String takeRemainingWhitespace()
{
ASSERT(!isEmpty());
Vector<LChar, 8> whitespace;
do {
UChar character = m_text[0];
if (isHTMLSpace(character))
whitespace.append(character);
m_text = m_text.substring(1);
} while (!m_text.isEmpty());
if (whitespace.isEmpty())
return String();
return String::adopt(whitespace);
}
private:
template<bool characterPredicate(UChar)>
void skipLeading()
{
ASSERT(!isEmpty());
while (characterPredicate(m_text[0])) {
m_text = m_text.substring(1);
if (m_text.isEmpty())
return;
}
}
template<bool characterPredicate(UChar)>
String takeLeading()
{
ASSERT(!isEmpty());
StringView start = m_text;
skipLeading<characterPredicate>();
if (start.length() == m_text.length())
return String();
StringView leading = start.substring(0, start.length() - m_text.length());
if (leading.is8Bit() || !isAll8BitData())
return leading.toString();
return String::make8BitFrom16BitSource(leading.characters16(), leading.length());
}
StringView m_text;
bool m_isAll8BitData;
};
HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser& parser, HTMLDocument& document, ParserContentPolicy parserContentPolicy, const HTMLParserOptions& options)
: m_framesetOk(true)
#ifndef NDEBUG
, m_isAttached(true)
#endif
, m_tree(document, parserContentPolicy, options.maximumDOMTreeDepth)
, m_insertionMode(InsertionMode::Initial)
, m_originalInsertionMode(InsertionMode::Initial)
, m_shouldSkipLeadingNewline(false)
, m_parser(parser)
, m_scriptToProcessStartPosition(uninitializedPositionValue1())
, m_options(options)
{
}
HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser& parser, DocumentFragment& fragment, Element* contextElement, ParserContentPolicy parserContentPolicy, const HTMLParserOptions& options)
: m_framesetOk(true)
#ifndef NDEBUG
, m_isAttached(true)
#endif
, m_fragmentContext(fragment, contextElement)
, m_tree(fragment, parserContentPolicy, options.maximumDOMTreeDepth)
, m_insertionMode(InsertionMode::Initial)
, m_originalInsertionMode(InsertionMode::Initial)
, m_shouldSkipLeadingNewline(false)
, m_parser(parser)
, m_scriptToProcessStartPosition(uninitializedPositionValue1())
, m_options(options)
{
ASSERT(isMainThread());
ASSERT(contextElement);
if (contextElement) {
m_tree.openElements()->pushRootNode(HTMLStackItem::create(&fragment, HTMLStackItem::ItemForDocumentFragmentNode));
#if ENABLE(TEMPLATE_ELEMENT)
if (contextElement->hasTagName(templateTag))
m_templateInsertionModes.append(InsertionMode::TemplateContents);
#endif
resetInsertionModeAppropriately();
m_tree.setForm(!contextElement || isHTMLFormElement(contextElement) ? toHTMLFormElement(contextElement) : HTMLFormElement::findClosestFormAncestor(*contextElement));
}
}
HTMLTreeBuilder::~HTMLTreeBuilder()
{
}
void HTMLTreeBuilder::detach()
{
#ifndef NDEBUG
m_isAttached = false;
#endif
m_tree.detach();
}
HTMLTreeBuilder::FragmentParsingContext::FragmentParsingContext()
: m_fragment(0)
, m_contextElement(0)
{
}
HTMLTreeBuilder::FragmentParsingContext::FragmentParsingContext(DocumentFragment& fragment, Element* contextElement)
: m_fragment(&fragment)
, m_contextElement(contextElement)
{
ASSERT(!fragment.hasChildNodes());
}
HTMLTreeBuilder::FragmentParsingContext::~FragmentParsingContext()
{
}
PassRefPtr<Element> HTMLTreeBuilder::takeScriptToProcess(TextPosition& scriptStartPosition)
{
ASSERT(m_scriptToProcess);
scriptStartPosition = m_scriptToProcessStartPosition;
m_scriptToProcessStartPosition = uninitializedPositionValue1();
return m_scriptToProcess.release();
}
void HTMLTreeBuilder::constructTree(AtomicHTMLToken* token)
{
if (shouldProcessTokenInForeignContent(token))
processTokenInForeignContent(token);
else
processToken(token);
if (m_parser.tokenizer()) {
bool inForeignContent = !m_tree.isEmpty()
&& !m_tree.currentStackItem()->isInHTMLNamespace()
&& !HTMLElementStack::isHTMLIntegrationPoint(m_tree.currentStackItem())
&& !HTMLElementStack::isMathMLTextIntegrationPoint(m_tree.currentStackItem());
m_parser.tokenizer()->setForceNullCharacterReplacement(m_insertionMode == InsertionMode::Text || inForeignContent);
m_parser.tokenizer()->setShouldAllowCDATA(inForeignContent);
}
m_tree.executeQueuedTasks();
}
void HTMLTreeBuilder::processToken(AtomicHTMLToken* token)
{
switch (token->type()) {
case HTMLToken::Uninitialized:
ASSERT_NOT_REACHED();
break;
case HTMLToken::DOCTYPE:
m_shouldSkipLeadingNewline = false;
processDoctypeToken(token);
break;
case HTMLToken::StartTag:
m_shouldSkipLeadingNewline = false;
processStartTag(token);
break;
case HTMLToken::EndTag:
m_shouldSkipLeadingNewline = false;
processEndTag(token);
break;
case HTMLToken::Comment:
m_shouldSkipLeadingNewline = false;
processComment(token);
return;
case HTMLToken::Character:
processCharacter(token);
break;
case HTMLToken::EndOfFile:
m_shouldSkipLeadingNewline = false;
processEndOfFile(token);
break;
}
}
void HTMLTreeBuilder::processDoctypeToken(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::DOCTYPE);
if (m_insertionMode == InsertionMode::Initial) {
m_tree.insertDoctype(token);
setInsertionMode(InsertionMode::BeforeHTML);
return;
}
if (m_insertionMode == InsertionMode::InTableText) {
defaultForInTableText();
processDoctypeToken(token);
return;
}
parseError(token);
}
void HTMLTreeBuilder::processFakeStartTag(const QualifiedName& tagName, const Vector<Attribute>& attributes)
{
AtomicHTMLToken fakeToken(HTMLToken::StartTag, tagName.localName(), attributes);
processStartTag(&fakeToken);
}
void HTMLTreeBuilder::processFakeEndTag(const AtomicString& tagName)
{
AtomicHTMLToken fakeToken(HTMLToken::EndTag, tagName);
processEndTag(&fakeToken);
}
void HTMLTreeBuilder::processFakeEndTag(const QualifiedName& tagName)
{
processFakeEndTag(tagName.localName());
}
void HTMLTreeBuilder::processFakeCharacters(const String& characters)
{
ASSERT(!characters.isEmpty());
ExternalCharacterTokenBuffer buffer(characters);
processCharacterBuffer(buffer);
}
void HTMLTreeBuilder::processFakePEndTagIfPInButtonScope()
{
if (!m_tree.openElements()->inButtonScope(pTag.localName()))
return;
AtomicHTMLToken endP(HTMLToken::EndTag, pTag.localName());
processEndTag(&endP);
}
Vector<Attribute> HTMLTreeBuilder::attributesForIsindexInput(AtomicHTMLToken* token)
{
Vector<Attribute> attributes = token->attributes();
for (int i = attributes.size() - 1; i >= 0; --i) {
const QualifiedName& name = attributes.at(i).name();
if (name.matches(nameAttr) || name.matches(actionAttr) || name.matches(promptAttr))
attributes.remove(i);
}
attributes.append(Attribute(nameAttr, isindexTag.localName()));
return attributes;
}
void HTMLTreeBuilder::processIsindexStartTagForInBody(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
ASSERT(token->name() == isindexTag);
parseError(token);
if (m_tree.form() && !isParsingTemplateContents())
return;
notImplemented(); processFakeStartTag(formTag);
Attribute* actionAttribute = token->getAttributeItem(actionAttr);
if (actionAttribute)
m_tree.form()->setAttribute(actionAttr, actionAttribute->value());
processFakeStartTag(hrTag);
processFakeStartTag(labelTag);
Attribute* promptAttribute = token->getAttributeItem(promptAttr);
if (promptAttribute)
processFakeCharacters(promptAttribute->value());
else
processFakeCharacters(searchableIndexIntroduction());
processFakeStartTag(inputTag, attributesForIsindexInput(token));
notImplemented(); processFakeEndTag(labelTag);
processFakeStartTag(hrTag);
processFakeEndTag(formTag);
}
namespace {
bool isLi(const HTMLStackItem* item)
{
return item->hasTagName(liTag);
}
bool isDdOrDt(const HTMLStackItem* item)
{
return item->hasTagName(ddTag)
|| item->hasTagName(dtTag);
}
}
template <bool shouldClose(const HTMLStackItem*)>
void HTMLTreeBuilder::processCloseWhenNestedTag(AtomicHTMLToken* token)
{
m_framesetOk = false;
HTMLElementStack::ElementRecord* nodeRecord = m_tree.openElements()->topRecord();
while (1) {
RefPtr<HTMLStackItem> item = nodeRecord->stackItem();
if (shouldClose(item.get())) {
ASSERT(item->isElementNode());
processFakeEndTag(item->localName());
break;
}
if (item->isSpecialNode() && !item->hasTagName(addressTag) && !item->hasTagName(divTag) && !item->hasTagName(pTag))
break;
nodeRecord = nodeRecord->next();
}
processFakePEndTagIfPInButtonScope();
m_tree.insertHTMLElement(token);
}
template <typename TableQualifiedName>
static HashMap<AtomicString, QualifiedName> createCaseMap(const TableQualifiedName* const names[], unsigned length)
{
HashMap<AtomicString, QualifiedName> map;
for (unsigned i = 0; i < length; ++i) {
const QualifiedName& name = *names[i];
const AtomicString& localName = name.localName();
AtomicString loweredLocalName = localName.lower();
if (loweredLocalName != localName)
map.add(loweredLocalName, name);
}
return map;
}
static void adjustSVGTagNameCase(AtomicHTMLToken& token)
{
static NeverDestroyed<HashMap<AtomicString, QualifiedName>> map = createCaseMap(SVGNames::getSVGTags(), SVGNames::SVGTagsCount);
const QualifiedName& casedName = map.get().get(token.name());
if (casedName.localName().isNull())
return;
token.setName(casedName.localName());
}
static inline void adjustAttributes(HashMap<AtomicString, QualifiedName>& map, AtomicHTMLToken& token)
{
for (auto& attribute : token.attributes()) {
const QualifiedName& casedName = map.get(attribute.localName());
if (!casedName.localName().isNull())
attribute.parserSetName(casedName);
}
}
template<const QualifiedName* const* attributesTable(), unsigned attributesTableLength>
static void adjustAttributes(AtomicHTMLToken& token)
{
static NeverDestroyed<HashMap<AtomicString, QualifiedName>> map = createCaseMap(attributesTable(), attributesTableLength);
adjustAttributes(map, token);
}
static inline void adjustSVGAttributes(AtomicHTMLToken& token)
{
adjustAttributes<SVGNames::getSVGAttrs, SVGNames::SVGAttrsCount>(token);
}
static inline void adjustMathMLAttributes(AtomicHTMLToken& token)
{
adjustAttributes<MathMLNames::getMathMLAttrs, MathMLNames::MathMLAttrsCount>(token);
}
static void addNamesWithPrefix(HashMap<AtomicString, QualifiedName>& map, const AtomicString& prefix, const QualifiedName* const names[], unsigned length)
{
for (unsigned i = 0; i < length; ++i) {
const QualifiedName& name = *names[i];
const AtomicString& localName = name.localName();
map.add(prefix + ':' + localName, QualifiedName(prefix, localName, name.namespaceURI()));
}
}
static HashMap<AtomicString, QualifiedName> createForeignAttributesMap()
{
HashMap<AtomicString, QualifiedName> map;
addNamesWithPrefix(map, xlinkAtom, XLinkNames::getXLinkAttrs(), XLinkNames::XLinkAttrsCount);
addNamesWithPrefix(map, xmlAtom, XMLNames::getXMLAttrs(), XMLNames::XMLAttrsCount);
map.add(WTF::xmlnsAtom, XMLNSNames::xmlnsAttr);
map.add("xmlns:xlink", QualifiedName(xmlnsAtom, xlinkAtom, XMLNSNames::xmlnsNamespaceURI));
return map;
}
static void adjustForeignAttributes(AtomicHTMLToken& token)
{
static NeverDestroyed<HashMap<AtomicString, QualifiedName>> map = createForeignAttributesMap();
adjustAttributes(map, token);
}
void HTMLTreeBuilder::processStartTagForInBody(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == baseTag
|| token->name() == basefontTag
|| token->name() == bgsoundTag
|| token->name() == commandTag
|| token->name() == linkTag
|| token->name() == metaTag
|| token->name() == noframesTag
|| token->name() == scriptTag
|| token->name() == styleTag
|| token->name() == titleTag) {
bool didProcess = processStartTagForInHead(token);
ASSERT_UNUSED(didProcess, didProcess);
return;
}
if (token->name() == bodyTag) {
parseError(token);
bool fragmentOrTemplateCase = !m_tree.openElements()->secondElementIsHTMLBodyElement() || m_tree.openElements()->hasOnlyOneElement();
#if ENABLE(TEMPLATE_ELEMENT)
fragmentOrTemplateCase = fragmentOrTemplateCase || m_tree.openElements()->hasTemplateInHTMLScope();
#endif
if (fragmentOrTemplateCase) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
m_framesetOk = false;
m_tree.insertHTMLBodyStartTagInBody(token);
return;
}
if (token->name() == framesetTag) {
parseError(token);
if (!m_tree.openElements()->secondElementIsHTMLBodyElement() || m_tree.openElements()->hasOnlyOneElement()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
if (!m_framesetOk)
return;
m_tree.openElements()->bodyElement()->remove(ASSERT_NO_EXCEPTION);
m_tree.openElements()->popUntil(m_tree.openElements()->bodyElement());
m_tree.openElements()->popHTMLBodyElement();
ASSERT(m_tree.openElements()->top() == m_tree.openElements()->htmlElement());
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InFrameset);
return;
}
if (token->name() == addressTag
|| token->name() == articleTag
|| token->name() == asideTag
|| token->name() == blockquoteTag
|| token->name() == centerTag
|| token->name() == detailsTag
|| token->name() == dirTag
|| token->name() == divTag
|| token->name() == dlTag
|| token->name() == fieldsetTag
|| token->name() == figcaptionTag
|| token->name() == figureTag
|| token->name() == footerTag
|| token->name() == headerTag
|| token->name() == hgroupTag
|| token->name() == mainTag
|| token->name() == menuTag
|| token->name() == navTag
|| token->name() == olTag
|| token->name() == pTag
|| token->name() == sectionTag
|| token->name() == summaryTag
|| token->name() == ulTag) {
processFakePEndTagIfPInButtonScope();
m_tree.insertHTMLElement(token);
return;
}
if (isNumberedHeaderTag(token->name())) {
processFakePEndTagIfPInButtonScope();
if (m_tree.currentStackItem()->isNumberedHeaderElement()) {
parseError(token);
m_tree.openElements()->pop();
}
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == preTag || token->name() == listingTag) {
processFakePEndTagIfPInButtonScope();
m_tree.insertHTMLElement(token);
m_shouldSkipLeadingNewline = true;
m_framesetOk = false;
return;
}
if (token->name() == formTag) {
if (m_tree.form() && !isParsingTemplateContents()) {
parseError(token);
return;
}
processFakePEndTagIfPInButtonScope();
m_tree.insertHTMLFormElement(token);
return;
}
if (token->name() == liTag) {
processCloseWhenNestedTag<isLi>(token);
return;
}
if (token->name() == ddTag || token->name() == dtTag) {
processCloseWhenNestedTag<isDdOrDt>(token);
return;
}
if (token->name() == plaintextTag) {
processFakePEndTagIfPInButtonScope();
m_tree.insertHTMLElement(token);
if (m_parser.tokenizer())
m_parser.tokenizer()->setState(HTMLTokenizer::PLAINTEXTState);
return;
}
if (token->name() == buttonTag) {
if (m_tree.openElements()->inScope(buttonTag)) {
parseError(token);
processFakeEndTag(buttonTag);
processStartTag(token); return;
}
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
m_framesetOk = false;
return;
}
if (token->name() == aTag) {
Element* activeATag = m_tree.activeFormattingElements()->closestElementInScopeWithName(aTag.localName());
if (activeATag) {
parseError(token);
processFakeEndTag(aTag);
m_tree.activeFormattingElements()->remove(activeATag);
if (m_tree.openElements()->contains(activeATag))
m_tree.openElements()->remove(activeATag);
}
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertFormattingElement(token);
return;
}
if (isNonAnchorNonNobrFormattingTag(token->name())) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertFormattingElement(token);
return;
}
if (token->name() == nobrTag) {
m_tree.reconstructTheActiveFormattingElements();
if (m_tree.openElements()->inScope(nobrTag)) {
parseError(token);
processFakeEndTag(nobrTag);
m_tree.reconstructTheActiveFormattingElements();
}
m_tree.insertFormattingElement(token);
return;
}
if (token->name() == appletTag
|| token->name() == embedTag
|| token->name() == objectTag) {
if (!pluginContentIsAllowed(m_tree.parserContentPolicy()))
return;
}
if (token->name() == appletTag
|| token->name() == marqueeTag
|| token->name() == objectTag) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
m_tree.activeFormattingElements()->appendMarker();
m_framesetOk = false;
return;
}
if (token->name() == tableTag) {
if (!m_tree.inQuirksMode() && m_tree.openElements()->inButtonScope(pTag))
processFakeEndTag(pTag);
m_tree.insertHTMLElement(token);
m_framesetOk = false;
setInsertionMode(InsertionMode::InTable);
return;
}
if (token->name() == imageTag) {
parseError(token);
token->setName(imgTag.localName());
}
if (token->name() == areaTag
|| token->name() == brTag
|| token->name() == embedTag
|| token->name() == imgTag
|| token->name() == keygenTag
|| token->name() == wbrTag) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertSelfClosingHTMLElement(token);
m_framesetOk = false;
return;
}
if (token->name() == inputTag) {
Attribute* typeAttribute = token->getAttributeItem(typeAttr);
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertSelfClosingHTMLElement(token);
if (!typeAttribute || !equalIgnoringCase(typeAttribute->value(), "hidden"))
m_framesetOk = false;
return;
}
if (token->name() == paramTag
|| token->name() == sourceTag
|| token->name() == trackTag) {
m_tree.insertSelfClosingHTMLElement(token);
return;
}
if (token->name() == hrTag) {
processFakePEndTagIfPInButtonScope();
m_tree.insertSelfClosingHTMLElement(token);
m_framesetOk = false;
return;
}
if (token->name() == isindexTag) {
processIsindexStartTagForInBody(token);
return;
}
if (token->name() == textareaTag) {
m_tree.insertHTMLElement(token);
m_shouldSkipLeadingNewline = true;
if (m_parser.tokenizer())
m_parser.tokenizer()->setState(HTMLTokenizer::RCDATAState);
m_originalInsertionMode = m_insertionMode;
m_framesetOk = false;
setInsertionMode(InsertionMode::Text);
return;
}
if (token->name() == xmpTag) {
processFakePEndTagIfPInButtonScope();
m_tree.reconstructTheActiveFormattingElements();
m_framesetOk = false;
processGenericRawTextStartTag(token);
return;
}
if (token->name() == iframeTag) {
m_framesetOk = false;
processGenericRawTextStartTag(token);
return;
}
if (token->name() == noembedTag && m_options.pluginsEnabled) {
processGenericRawTextStartTag(token);
return;
}
if (token->name() == noscriptTag && m_options.scriptEnabled) {
processGenericRawTextStartTag(token);
return;
}
if (token->name() == selectTag) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
m_framesetOk = false;
if (m_insertionMode == InsertionMode::InTable
|| m_insertionMode == InsertionMode::InCaption
|| m_insertionMode == InsertionMode::InColumnGroup
|| m_insertionMode == InsertionMode::InTableBody
|| m_insertionMode == InsertionMode::InRow
|| m_insertionMode == InsertionMode::InCell)
setInsertionMode(InsertionMode::InSelectInTable);
else
setInsertionMode(InsertionMode::InSelect);
return;
}
if (token->name() == optgroupTag || token->name() == optionTag) {
if (isHTMLOptionElement(m_tree.currentStackItem()->node())) {
AtomicHTMLToken endOption(HTMLToken::EndTag, optionTag.localName());
processEndTag(&endOption);
}
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == rbTag || token->name() == rpTag || token->name() == rtcTag) {
if (m_tree.openElements()->inScope(rubyTag.localName())) {
m_tree.generateImpliedEndTags();
if (!m_tree.currentStackItem()->hasTagName(rubyTag))
parseError(token);
}
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == rtTag) {
if (m_tree.openElements()->inScope(rubyTag.localName())) {
m_tree.generateImpliedEndTagsWithExclusion(rtcTag.localName());
if (!m_tree.currentStackItem()->hasTagName(rubyTag) && !m_tree.currentStackItem()->hasTagName(rtcTag))
parseError(token);
}
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == MathMLNames::mathTag.localName()) {
m_tree.reconstructTheActiveFormattingElements();
adjustMathMLAttributes(*token);
adjustForeignAttributes(*token);
m_tree.insertForeignElement(token, MathMLNames::mathmlNamespaceURI);
return;
}
if (token->name() == SVGNames::svgTag.localName()) {
m_tree.reconstructTheActiveFormattingElements();
adjustSVGAttributes(*token);
adjustForeignAttributes(*token);
m_tree.insertForeignElement(token, SVGNames::svgNamespaceURI);
return;
}
if (isCaptionColOrColgroupTag(token->name())
|| token->name() == frameTag
|| token->name() == headTag
|| isTableBodyContextTag(token->name())
|| isTableCellContextTag(token->name())
|| token->name() == trTag) {
parseError(token);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
#endif
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
}
#if ENABLE(TEMPLATE_ELEMENT)
void HTMLTreeBuilder::processTemplateStartTag(AtomicHTMLToken* token)
{
m_tree.activeFormattingElements()->appendMarker();
m_tree.insertHTMLElement(token);
m_templateInsertionModes.append(InsertionMode::TemplateContents);
setInsertionMode(InsertionMode::TemplateContents);
}
bool HTMLTreeBuilder::processTemplateEndTag(AtomicHTMLToken* token)
{
ASSERT(token->name() == templateTag.localName());
if (!m_tree.openElements()->hasTemplateInHTMLScope()) {
ASSERT(m_templateInsertionModes.isEmpty() || (m_templateInsertionModes.size() == 1 && m_fragmentContext.contextElement()->hasTagName(templateTag)));
parseError(token);
return false;
}
m_tree.generateImpliedEndTags();
if (!m_tree.currentStackItem()->hasTagName(templateTag))
parseError(token);
m_tree.openElements()->popUntilPopped(templateTag);
m_tree.activeFormattingElements()->clearToLastMarker();
m_templateInsertionModes.removeLast();
resetInsertionModeAppropriately();
return true;
}
bool HTMLTreeBuilder::processEndOfFileForInTemplateContents(AtomicHTMLToken* token)
{
AtomicHTMLToken endTemplate(HTMLToken::EndTag, templateTag.localName());
if (!processTemplateEndTag(&endTemplate))
return false;
processEndOfFile(token);
return true;
}
#endif
bool HTMLTreeBuilder::processColgroupEndTagForInColumnGroup()
{
bool ignoreFakeEndTag = m_tree.currentIsRootNode();
#if ENABLE(TEMPLATE_ELEMENT)
ignoreFakeEndTag = ignoreFakeEndTag || m_tree.currentNode()->hasTagName(templateTag);
#endif
if (ignoreFakeEndTag) {
ASSERT(isParsingFragmentOrTemplateContents());
return false;
}
m_tree.openElements()->pop();
setInsertionMode(InsertionMode::InTable);
return true;
}
void HTMLTreeBuilder::closeTheCell()
{
ASSERT(insertionMode() == InsertionMode::InCell);
if (m_tree.openElements()->inTableScope(tdTag)) {
ASSERT(!m_tree.openElements()->inTableScope(thTag));
processFakeEndTag(tdTag);
return;
}
ASSERT(m_tree.openElements()->inTableScope(thTag));
processFakeEndTag(thTag);
ASSERT(insertionMode() == InsertionMode::InRow);
}
void HTMLTreeBuilder::processStartTagForInTable(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
if (token->name() == captionTag) {
m_tree.openElements()->popUntilTableScopeMarker();
m_tree.activeFormattingElements()->appendMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InCaption);
return;
}
if (token->name() == colgroupTag) {
m_tree.openElements()->popUntilTableScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InColumnGroup);
return;
}
if (token->name() == colTag) {
processFakeStartTag(colgroupTag);
ASSERT(insertionMode() == InsertionMode::InColumnGroup);
processStartTag(token);
return;
}
if (isTableBodyContextTag(token->name())) {
m_tree.openElements()->popUntilTableScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InTableBody);
return;
}
if (isTableCellContextTag(token->name())
|| token->name() == trTag) {
processFakeStartTag(tbodyTag);
ASSERT(insertionMode() == InsertionMode::InTableBody);
processStartTag(token);
return;
}
if (token->name() == tableTag) {
parseError(token);
if (!processTableEndTagForInTable()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
processStartTag(token);
return;
}
if (token->name() == styleTag || token->name() == scriptTag) {
processStartTagForInHead(token);
return;
}
if (token->name() == inputTag) {
Attribute* typeAttribute = token->getAttributeItem(typeAttr);
if (typeAttribute && equalIgnoringCase(typeAttribute->value(), "hidden")) {
parseError(token);
m_tree.insertSelfClosingHTMLElement(token);
return;
}
}
if (token->name() == formTag) {
parseError(token);
if (m_tree.form() && !isParsingTemplateContents())
return;
m_tree.insertHTMLFormElement(token, true);
m_tree.openElements()->pop();
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
#endif
parseError(token);
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
processStartTagForInBody(token);
}
void HTMLTreeBuilder::processStartTag(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
switch (insertionMode()) {
case InsertionMode::Initial:
ASSERT(insertionMode() == InsertionMode::Initial);
defaultForInitial();
FALLTHROUGH;
case InsertionMode::BeforeHTML:
ASSERT(insertionMode() == InsertionMode::BeforeHTML);
if (token->name() == htmlTag) {
m_tree.insertHTMLHtmlStartTagBeforeHTML(token);
setInsertionMode(InsertionMode::BeforeHead);
return;
}
defaultForBeforeHTML();
FALLTHROUGH;
case InsertionMode::BeforeHead:
ASSERT(insertionMode() == InsertionMode::BeforeHead);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == headTag) {
m_tree.insertHTMLHeadElement(token);
setInsertionMode(InsertionMode::InHead);
return;
}
defaultForBeforeHead();
FALLTHROUGH;
case InsertionMode::InHead:
ASSERT(insertionMode() == InsertionMode::InHead);
if (processStartTagForInHead(token))
return;
defaultForInHead();
FALLTHROUGH;
case InsertionMode::AfterHead:
ASSERT(insertionMode() == InsertionMode::AfterHead);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == bodyTag) {
m_framesetOk = false;
m_tree.insertHTMLBodyElement(token);
setInsertionMode(InsertionMode::InBody);
return;
}
if (token->name() == framesetTag) {
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InFrameset);
return;
}
if (token->name() == baseTag
|| token->name() == basefontTag
|| token->name() == bgsoundTag
|| token->name() == linkTag
|| token->name() == metaTag
|| token->name() == noframesTag
|| token->name() == scriptTag
|| token->name() == styleTag
#if ENABLE(TEMPLATE_ELEMENT)
|| token->name() == templateTag
#endif
|| token->name() == titleTag) {
parseError(token);
ASSERT(m_tree.head());
m_tree.openElements()->pushHTMLHeadElement(m_tree.headStackItem());
processStartTagForInHead(token);
m_tree.openElements()->removeHTMLHeadElement(m_tree.head());
return;
}
if (token->name() == headTag) {
parseError(token);
return;
}
defaultForAfterHead();
FALLTHROUGH;
case InsertionMode::InBody:
ASSERT(insertionMode() == InsertionMode::InBody);
processStartTagForInBody(token);
break;
case InsertionMode::InTable:
ASSERT(insertionMode() == InsertionMode::InTable);
processStartTagForInTable(token);
break;
case InsertionMode::InCaption:
ASSERT(insertionMode() == InsertionMode::InCaption);
if (isCaptionColOrColgroupTag(token->name())
|| isTableBodyContextTag(token->name())
|| isTableCellContextTag(token->name())
|| token->name() == trTag) {
parseError(token);
if (!processCaptionEndTagForInCaption()) {
ASSERT(isParsingFragment());
return;
}
processStartTag(token);
return;
}
processStartTagForInBody(token);
break;
case InsertionMode::InColumnGroup:
ASSERT(insertionMode() == InsertionMode::InColumnGroup);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == colTag) {
m_tree.insertSelfClosingHTMLElement(token);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
#endif
if (!processColgroupEndTagForInColumnGroup()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
processStartTag(token);
break;
case InsertionMode::InTableBody:
ASSERT(insertionMode() == InsertionMode::InTableBody);
if (token->name() == trTag) {
m_tree.openElements()->popUntilTableBodyScopeMarker(); m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InRow);
return;
}
if (isTableCellContextTag(token->name())) {
parseError(token);
processFakeStartTag(trTag);
ASSERT(insertionMode() == InsertionMode::InRow);
processStartTag(token);
return;
}
if (isCaptionColOrColgroupTag(token->name()) || isTableBodyContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(tbodyTag) && !m_tree.openElements()->inTableScope(theadTag) && !m_tree.openElements()->inTableScope(tfootTag)) {
ASSERT(isParsingFragmentOrTemplateContents());
parseError(token);
return;
}
m_tree.openElements()->popUntilTableBodyScopeMarker();
ASSERT(isTableBodyContextTag(m_tree.currentStackItem()->localName()));
processFakeEndTag(m_tree.currentStackItem()->localName());
processStartTag(token);
return;
}
processStartTagForInTable(token);
break;
case InsertionMode::InRow:
ASSERT(insertionMode() == InsertionMode::InRow);
if (isTableCellContextTag(token->name())) {
m_tree.openElements()->popUntilTableRowScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InCell);
m_tree.activeFormattingElements()->appendMarker();
return;
}
if (token->name() == trTag
|| isCaptionColOrColgroupTag(token->name())
|| isTableBodyContextTag(token->name())) {
if (!processTrEndTagForInRow()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
ASSERT(insertionMode() == InsertionMode::InTableBody);
processStartTag(token);
return;
}
processStartTagForInTable(token);
break;
case InsertionMode::InCell:
ASSERT(insertionMode() == InsertionMode::InCell);
if (isCaptionColOrColgroupTag(token->name())
|| isTableCellContextTag(token->name())
|| token->name() == trTag
|| isTableBodyContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(tdTag) && !m_tree.openElements()->inTableScope(thTag)) {
ASSERT(isParsingFragment());
parseError(token);
return;
}
closeTheCell();
processStartTag(token);
return;
}
processStartTagForInBody(token);
break;
case InsertionMode::AfterBody:
case InsertionMode::AfterAfterBody:
ASSERT(insertionMode() == InsertionMode::AfterBody || insertionMode() == InsertionMode::AfterAfterBody);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
setInsertionMode(InsertionMode::InBody);
processStartTag(token);
break;
case InsertionMode::InHeadNoscript:
ASSERT(insertionMode() == InsertionMode::InHeadNoscript);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == basefontTag
|| token->name() == bgsoundTag
|| token->name() == linkTag
|| token->name() == metaTag
|| token->name() == noframesTag
|| token->name() == styleTag) {
bool didProcess = processStartTagForInHead(token);
ASSERT_UNUSED(didProcess, didProcess);
return;
}
if (token->name() == htmlTag || token->name() == noscriptTag) {
parseError(token);
return;
}
defaultForInHeadNoscript();
processToken(token);
break;
case InsertionMode::InFrameset:
ASSERT(insertionMode() == InsertionMode::InFrameset);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == framesetTag) {
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == frameTag) {
m_tree.insertSelfClosingHTMLElement(token);
return;
}
if (token->name() == noframesTag) {
processStartTagForInHead(token);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
#endif
parseError(token);
break;
case InsertionMode::AfterFrameset:
case InsertionMode::AfterAfterFrameset:
ASSERT(insertionMode() == InsertionMode::AfterFrameset || insertionMode() == InsertionMode::AfterAfterFrameset);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == noframesTag) {
processStartTagForInHead(token);
return;
}
parseError(token);
break;
case InsertionMode::InSelectInTable:
ASSERT(insertionMode() == InsertionMode::InSelectInTable);
if (token->name() == captionTag
|| token->name() == tableTag
|| isTableBodyContextTag(token->name())
|| token->name() == trTag
|| isTableCellContextTag(token->name())) {
parseError(token);
AtomicHTMLToken endSelect(HTMLToken::EndTag, selectTag.localName());
processEndTag(&endSelect);
processStartTag(token);
return;
}
FALLTHROUGH;
case InsertionMode::InSelect:
ASSERT(insertionMode() == InsertionMode::InSelect || insertionMode() == InsertionMode::InSelectInTable);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == optionTag) {
if (isHTMLOptionElement(m_tree.currentStackItem()->node())) {
AtomicHTMLToken endOption(HTMLToken::EndTag, optionTag.localName());
processEndTag(&endOption);
}
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == optgroupTag) {
if (isHTMLOptionElement(m_tree.currentStackItem()->node())) {
AtomicHTMLToken endOption(HTMLToken::EndTag, optionTag.localName());
processEndTag(&endOption);
}
if (isHTMLOptGroupElement(m_tree.currentStackItem()->node())) {
AtomicHTMLToken endOptgroup(HTMLToken::EndTag, optgroupTag.localName());
processEndTag(&endOptgroup);
}
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == selectTag) {
parseError(token);
AtomicHTMLToken endSelect(HTMLToken::EndTag, selectTag.localName());
processEndTag(&endSelect);
return;
}
if (token->name() == inputTag
|| token->name() == keygenTag
|| token->name() == textareaTag) {
parseError(token);
if (!m_tree.openElements()->inSelectScope(selectTag)) {
ASSERT(isParsingFragment());
return;
}
AtomicHTMLToken endSelect(HTMLToken::EndTag, selectTag.localName());
processEndTag(&endSelect);
processStartTag(token);
return;
}
if (token->name() == scriptTag) {
bool didProcess = processStartTagForInHead(token);
ASSERT_UNUSED(didProcess, didProcess);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
#endif
break;
case InsertionMode::InTableText:
defaultForInTableText();
processStartTag(token);
break;
case InsertionMode::Text:
ASSERT_NOT_REACHED();
break;
case InsertionMode::TemplateContents:
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
if (token->name() == linkTag
|| token->name() == scriptTag
|| token->name() == styleTag
|| token->name() == metaTag) {
processStartTagForInHead(token);
return;
}
InsertionMode insertionMode = InsertionMode::TemplateContents;
if (token->name() == frameTag)
insertionMode = InsertionMode::InFrameset;
else if (token->name() == colTag)
insertionMode = InsertionMode::InColumnGroup;
else if (isCaptionColOrColgroupTag(token->name()) || isTableBodyContextTag(token->name()))
insertionMode = InsertionMode::InTable;
else if (token->name() == trTag)
insertionMode = InsertionMode::InTableBody;
else if (isTableCellContextTag(token->name()))
insertionMode = InsertionMode::InRow;
else
insertionMode = InsertionMode::InBody;
ASSERT(insertionMode != InsertionMode::TemplateContents);
ASSERT(m_templateInsertionModes.last() == InsertionMode::TemplateContents);
m_templateInsertionModes.last() = insertionMode;
setInsertionMode(insertionMode);
processStartTag(token);
#else
ASSERT_NOT_REACHED();
#endif
break;
}
}
void HTMLTreeBuilder::processHtmlStartTagForInBody(AtomicHTMLToken* token)
{
parseError(token);
#if ENABLE(TEMPLATE_ELEMENT)
if (m_tree.openElements()->hasTemplateInHTMLScope()) {
ASSERT(isParsingTemplateContents());
return;
}
#endif
m_tree.insertHTMLHtmlStartTagInBody(token);
}
bool HTMLTreeBuilder::processBodyEndTagForInBody(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
ASSERT(token->name() == bodyTag);
if (!m_tree.openElements()->inScope(bodyTag.localName())) {
parseError(token);
return false;
}
notImplemented(); setInsertionMode(InsertionMode::AfterBody);
return true;
}
void HTMLTreeBuilder::processAnyOtherEndTagForInBody(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
HTMLElementStack::ElementRecord* record = m_tree.openElements()->topRecord();
while (1) {
RefPtr<HTMLStackItem> item = record->stackItem();
if (item->matchesHTMLTag(token->name())) {
m_tree.generateImpliedEndTagsWithExclusion(token->name());
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(item->element());
return;
}
if (item->isSpecialNode()) {
parseError(token);
return;
}
record = record->next();
}
}
void HTMLTreeBuilder::callTheAdoptionAgency(AtomicHTMLToken* token)
{
static const int outerIterationLimit = 8;
static const int innerIterationLimit = 3;
for (int i = 0; i < outerIterationLimit; ++i) {
Element* formattingElement = m_tree.activeFormattingElements()->closestElementInScopeWithName(token->name());
if (!formattingElement)
return processAnyOtherEndTagForInBody(token);
if ((m_tree.openElements()->contains(formattingElement)) && !m_tree.openElements()->inScope(formattingElement)) {
parseError(token);
notImplemented(); return;
}
HTMLElementStack::ElementRecord* formattingElementRecord = m_tree.openElements()->find(formattingElement);
if (!formattingElementRecord) {
parseError(token);
m_tree.activeFormattingElements()->remove(formattingElement);
return;
}
if (formattingElement != m_tree.currentElement())
parseError(token);
HTMLElementStack::ElementRecord* furthestBlock = m_tree.openElements()->furthestBlockForFormattingElement(formattingElement);
if (!furthestBlock) {
m_tree.openElements()->popUntilPopped(formattingElement);
m_tree.activeFormattingElements()->remove(formattingElement);
return;
}
ASSERT(furthestBlock->isAbove(formattingElementRecord));
RefPtr<HTMLStackItem> commonAncestor = formattingElementRecord->next()->stackItem();
HTMLFormattingElementList::Bookmark bookmark = m_tree.activeFormattingElements()->bookmarkFor(formattingElement);
HTMLElementStack::ElementRecord* node = furthestBlock;
HTMLElementStack::ElementRecord* nextNode = node->next();
HTMLElementStack::ElementRecord* lastNode = furthestBlock;
for (int i = 0; i < innerIterationLimit; ++i) {
node = nextNode;
ASSERT(node);
nextNode = node->next(); if (!m_tree.activeFormattingElements()->contains(node->element())) {
m_tree.openElements()->remove(node->element());
node = 0;
continue;
}
if (node == formattingElementRecord)
break;
RefPtr<HTMLStackItem> newItem = m_tree.createElementFromSavedToken(node->stackItem().get());
HTMLFormattingElementList::Entry* nodeEntry = m_tree.activeFormattingElements()->find(node->element());
nodeEntry->replaceElement(newItem);
node->replaceElement(newItem.release());
if (lastNode == furthestBlock)
bookmark.moveToAfter(nodeEntry);
m_tree.reparent(*node, *lastNode);
lastNode = node;
}
m_tree.insertAlreadyParsedChild(*commonAncestor, *lastNode);
RefPtr<HTMLStackItem> newItem = m_tree.createElementFromSavedToken(formattingElementRecord->stackItem().get());
m_tree.takeAllChildren(*newItem, *furthestBlock);
m_tree.reparent(*furthestBlock, *newItem);
m_tree.activeFormattingElements()->swapTo(formattingElement, newItem, bookmark);
m_tree.openElements()->remove(formattingElement);
m_tree.openElements()->insertAbove(newItem, furthestBlock);
}
}
void HTMLTreeBuilder::resetInsertionModeAppropriately()
{
bool last = false;
HTMLElementStack::ElementRecord* nodeRecord = m_tree.openElements()->topRecord();
while (1) {
RefPtr<HTMLStackItem> item = nodeRecord->stackItem();
if (item->node() == m_tree.openElements()->rootNode()) {
last = true;
#if ENABLE(TEMPLATE_ELEMENT)
bool shouldCreateItem = isParsingFragment();
#else
ASSERT(isParsingFragment());
bool shouldCreateItem = true;
#endif
if (shouldCreateItem)
item = HTMLStackItem::create(m_fragmentContext.contextElement(), HTMLStackItem::ItemForContextElement);
}
#if ENABLE(TEMPLATE_ELEMENT)
if (item->hasTagName(templateTag))
return setInsertionMode(m_templateInsertionModes.last());
#endif
if (item->hasTagName(selectTag)) {
#if ENABLE(TEMPLATE_ELEMENT)
if (!last) {
while (item->node() != m_tree.openElements()->rootNode() && !item->hasTagName(templateTag)) {
nodeRecord = nodeRecord->next();
item = nodeRecord->stackItem();
if (isHTMLTableElement(item->node()))
return setInsertionMode(InsertionMode::InSelectInTable);
}
}
#endif
return setInsertionMode(InsertionMode::InSelect);
}
if (item->hasTagName(tdTag) || item->hasTagName(thTag))
return setInsertionMode(InsertionMode::InCell);
if (item->hasTagName(trTag))
return setInsertionMode(InsertionMode::InRow);
if (item->hasTagName(tbodyTag) || item->hasTagName(theadTag) || item->hasTagName(tfootTag))
return setInsertionMode(InsertionMode::InTableBody);
if (item->hasTagName(captionTag))
return setInsertionMode(InsertionMode::InCaption);
if (item->hasTagName(colgroupTag)) {
return setInsertionMode(InsertionMode::InColumnGroup);
}
if (isHTMLTableElement(item->node()))
return setInsertionMode(InsertionMode::InTable);
if (item->hasTagName(headTag)) {
#if ENABLE(TEMPLATE_ELEMENT)
if (!m_fragmentContext.fragment() || m_fragmentContext.contextElement() != item->node())
return setInsertionMode(InsertionMode::InHead);
#endif
return setInsertionMode(InsertionMode::InBody);
}
if (item->hasTagName(bodyTag))
return setInsertionMode(InsertionMode::InBody);
if (item->hasTagName(framesetTag)) {
return setInsertionMode(InsertionMode::InFrameset);
}
if (item->hasTagName(htmlTag)) {
if (m_tree.headStackItem())
return setInsertionMode(InsertionMode::AfterHead);
ASSERT(isParsingFragment());
return setInsertionMode(InsertionMode::BeforeHead);
}
if (last) {
ASSERT(isParsingFragment());
return setInsertionMode(InsertionMode::InBody);
}
nodeRecord = nodeRecord->next();
}
}
void HTMLTreeBuilder::processEndTagForInTableBody(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
if (isTableBodyContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(token->name())) {
parseError(token);
return;
}
m_tree.openElements()->popUntilTableBodyScopeMarker();
m_tree.openElements()->pop();
setInsertionMode(InsertionMode::InTable);
return;
}
if (token->name() == tableTag) {
if (!m_tree.openElements()->inTableScope(tbodyTag) && !m_tree.openElements()->inTableScope(theadTag) && !m_tree.openElements()->inTableScope(tfootTag)) {
ASSERT(isParsingFragmentOrTemplateContents());
parseError(token);
return;
}
m_tree.openElements()->popUntilTableBodyScopeMarker();
ASSERT(isTableBodyContextTag(m_tree.currentStackItem()->localName()));
processFakeEndTag(m_tree.currentStackItem()->localName());
processEndTag(token);
return;
}
if (token->name() == bodyTag
|| isCaptionColOrColgroupTag(token->name())
|| token->name() == htmlTag
|| isTableCellContextTag(token->name())
|| token->name() == trTag) {
parseError(token);
return;
}
processEndTagForInTable(token);
}
void HTMLTreeBuilder::processEndTagForInRow(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
if (token->name() == trTag) {
processTrEndTagForInRow();
return;
}
if (token->name() == tableTag) {
if (!processTrEndTagForInRow()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
ASSERT(insertionMode() == InsertionMode::InTableBody);
processEndTag(token);
return;
}
if (isTableBodyContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(token->name())) {
parseError(token);
return;
}
processFakeEndTag(trTag);
ASSERT(insertionMode() == InsertionMode::InTableBody);
processEndTag(token);
return;
}
if (token->name() == bodyTag
|| isCaptionColOrColgroupTag(token->name())
|| token->name() == htmlTag
|| isTableCellContextTag(token->name())) {
parseError(token);
return;
}
processEndTagForInTable(token);
}
void HTMLTreeBuilder::processEndTagForInCell(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
if (isTableCellContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(token->name())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
m_tree.activeFormattingElements()->clearToLastMarker();
setInsertionMode(InsertionMode::InRow);
return;
}
if (token->name() == bodyTag
|| isCaptionColOrColgroupTag(token->name())
|| token->name() == htmlTag) {
parseError(token);
return;
}
if (token->name() == tableTag
|| token->name() == trTag
|| isTableBodyContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(token->name())) {
#if ENABLE(TEMPLATE_ELEMENT)
ASSERT(isTableBodyContextTag(token->name()) || m_tree.openElements()->inTableScope(templateTag) || isParsingFragment());
#else
ASSERT(isTableBodyContextTag(token->name()) || isParsingFragment());
#endif
parseError(token);
return;
}
closeTheCell();
processEndTag(token);
return;
}
processEndTagForInBody(token);
}
void HTMLTreeBuilder::processEndTagForInBody(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
if (token->name() == bodyTag) {
processBodyEndTagForInBody(token);
return;
}
if (token->name() == htmlTag) {
AtomicHTMLToken endBody(HTMLToken::EndTag, bodyTag.localName());
if (processBodyEndTagForInBody(&endBody))
processEndTag(token);
return;
}
if (token->name() == addressTag
|| token->name() == articleTag
|| token->name() == asideTag
|| token->name() == blockquoteTag
|| token->name() == buttonTag
|| token->name() == centerTag
|| token->name() == detailsTag
|| token->name() == dirTag
|| token->name() == divTag
|| token->name() == dlTag
|| token->name() == fieldsetTag
|| token->name() == figcaptionTag
|| token->name() == figureTag
|| token->name() == footerTag
|| token->name() == headerTag
|| token->name() == hgroupTag
|| token->name() == listingTag
|| token->name() == mainTag
|| token->name() == menuTag
|| token->name() == navTag
|| token->name() == olTag
|| token->name() == preTag
|| token->name() == sectionTag
|| token->name() == summaryTag
|| token->name() == ulTag) {
if (!m_tree.openElements()->inScope(token->name())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
return;
}
if (token->name() == formTag) {
if (!isParsingTemplateContents()) {
RefPtr<Element> node = m_tree.takeForm();
if (!node || !m_tree.openElements()->inScope(node.get())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (m_tree.currentNode() != node.get())
parseError(token);
m_tree.openElements()->remove(node.get());
} else {
if (!m_tree.openElements()->inScope(token->name())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (!m_tree.currentNode()->hasTagName(formTag))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
}
}
if (token->name() == pTag) {
if (!m_tree.openElements()->inButtonScope(token->name())) {
parseError(token);
processFakeStartTag(pTag);
ASSERT(m_tree.openElements()->inScope(token->name()));
processEndTag(token);
return;
}
m_tree.generateImpliedEndTagsWithExclusion(token->name());
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
return;
}
if (token->name() == liTag) {
if (!m_tree.openElements()->inListItemScope(token->name())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTagsWithExclusion(token->name());
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
return;
}
if (token->name() == ddTag
|| token->name() == dtTag) {
if (!m_tree.openElements()->inScope(token->name())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTagsWithExclusion(token->name());
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
return;
}
if (isNumberedHeaderTag(token->name())) {
if (!m_tree.openElements()->hasNumberedHeaderElementInScope()) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilNumberedHeaderElementPopped();
return;
}
if (isFormattingTag(token->name())) {
callTheAdoptionAgency(token);
return;
}
if (token->name() == appletTag
|| token->name() == marqueeTag
|| token->name() == objectTag) {
if (!m_tree.openElements()->inScope(token->name())) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (!m_tree.currentStackItem()->matchesHTMLTag(token->name()))
parseError(token);
m_tree.openElements()->popUntilPopped(token->name());
m_tree.activeFormattingElements()->clearToLastMarker();
return;
}
if (token->name() == brTag) {
parseError(token);
processFakeStartTag(brTag);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
#endif
processAnyOtherEndTagForInBody(token);
}
bool HTMLTreeBuilder::processCaptionEndTagForInCaption()
{
if (!m_tree.openElements()->inTableScope(captionTag.localName())) {
ASSERT(isParsingFragment());
return false;
}
m_tree.generateImpliedEndTags();
m_tree.openElements()->popUntilPopped(captionTag.localName());
m_tree.activeFormattingElements()->clearToLastMarker();
setInsertionMode(InsertionMode::InTable);
return true;
}
bool HTMLTreeBuilder::processTrEndTagForInRow()
{
if (!m_tree.openElements()->inTableScope(trTag)) {
ASSERT(isParsingFragmentOrTemplateContents());
return false;
}
m_tree.openElements()->popUntilTableRowScopeMarker();
ASSERT(m_tree.currentStackItem()->hasTagName(trTag));
m_tree.openElements()->pop();
setInsertionMode(InsertionMode::InTableBody);
return true;
}
bool HTMLTreeBuilder::processTableEndTagForInTable()
{
if (!m_tree.openElements()->inTableScope(tableTag)) {
ASSERT(isParsingFragmentOrTemplateContents());
return false;
}
m_tree.openElements()->popUntilPopped(tableTag.localName());
resetInsertionModeAppropriately();
return true;
}
void HTMLTreeBuilder::processEndTagForInTable(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
if (token->name() == tableTag) {
processTableEndTagForInTable();
return;
}
if (token->name() == bodyTag
|| isCaptionColOrColgroupTag(token->name())
|| token->name() == htmlTag
|| isTableBodyContextTag(token->name())
|| isTableCellContextTag(token->name())
|| token->name() == trTag) {
parseError(token);
return;
}
parseError(token);
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
processEndTagForInBody(token);
}
void HTMLTreeBuilder::processEndTag(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndTag);
switch (insertionMode()) {
case InsertionMode::Initial:
ASSERT(insertionMode() == InsertionMode::Initial);
defaultForInitial();
FALLTHROUGH;
case InsertionMode::BeforeHTML:
ASSERT(insertionMode() == InsertionMode::BeforeHTML);
if (token->name() != headTag && token->name() != bodyTag && token->name() != htmlTag && token->name() != brTag) {
parseError(token);
return;
}
defaultForBeforeHTML();
FALLTHROUGH;
case InsertionMode::BeforeHead:
ASSERT(insertionMode() == InsertionMode::BeforeHead);
if (token->name() != headTag && token->name() != bodyTag && token->name() != htmlTag && token->name() != brTag) {
parseError(token);
return;
}
defaultForBeforeHead();
FALLTHROUGH;
case InsertionMode::InHead:
ASSERT(insertionMode() == InsertionMode::InHead);
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
#endif
if (token->name() == headTag) {
m_tree.openElements()->popHTMLHeadElement();
setInsertionMode(InsertionMode::AfterHead);
return;
}
if (token->name() != bodyTag && token->name() != htmlTag && token->name() != brTag) {
parseError(token);
return;
}
defaultForInHead();
FALLTHROUGH;
case InsertionMode::AfterHead:
ASSERT(insertionMode() == InsertionMode::AfterHead);
if (token->name() != bodyTag && token->name() != htmlTag && token->name() != brTag) {
parseError(token);
return;
}
defaultForAfterHead();
FALLTHROUGH;
case InsertionMode::InBody:
ASSERT(insertionMode() == InsertionMode::InBody);
processEndTagForInBody(token);
break;
case InsertionMode::InTable:
ASSERT(insertionMode() == InsertionMode::InTable);
processEndTagForInTable(token);
break;
case InsertionMode::InCaption:
ASSERT(insertionMode() == InsertionMode::InCaption);
if (token->name() == captionTag) {
processCaptionEndTagForInCaption();
return;
}
if (token->name() == tableTag) {
parseError(token);
if (!processCaptionEndTagForInCaption()) {
ASSERT(isParsingFragment());
return;
}
processEndTag(token);
return;
}
if (token->name() == bodyTag
|| token->name() == colTag
|| token->name() == colgroupTag
|| token->name() == htmlTag
|| isTableBodyContextTag(token->name())
|| isTableCellContextTag(token->name())
|| token->name() == trTag) {
parseError(token);
return;
}
processEndTagForInBody(token);
break;
case InsertionMode::InColumnGroup:
ASSERT(insertionMode() == InsertionMode::InColumnGroup);
if (token->name() == colgroupTag) {
processColgroupEndTagForInColumnGroup();
return;
}
if (token->name() == colTag) {
parseError(token);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
#endif
if (!processColgroupEndTagForInColumnGroup()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
processEndTag(token);
break;
case InsertionMode::InRow:
ASSERT(insertionMode() == InsertionMode::InRow);
processEndTagForInRow(token);
break;
case InsertionMode::InCell:
ASSERT(insertionMode() == InsertionMode::InCell);
processEndTagForInCell(token);
break;
case InsertionMode::InTableBody:
ASSERT(insertionMode() == InsertionMode::InTableBody);
processEndTagForInTableBody(token);
break;
case InsertionMode::AfterBody:
ASSERT(insertionMode() == InsertionMode::AfterBody);
if (token->name() == htmlTag) {
if (isParsingFragment()) {
parseError(token);
return;
}
setInsertionMode(InsertionMode::AfterAfterBody);
return;
}
FALLTHROUGH;
case InsertionMode::AfterAfterBody:
ASSERT(insertionMode() == InsertionMode::AfterBody || insertionMode() == InsertionMode::AfterAfterBody);
parseError(token);
setInsertionMode(InsertionMode::InBody);
processEndTag(token);
break;
case InsertionMode::InHeadNoscript:
ASSERT(insertionMode() == InsertionMode::InHeadNoscript);
if (token->name() == noscriptTag) {
ASSERT(m_tree.currentStackItem()->hasTagName(noscriptTag));
m_tree.openElements()->pop();
ASSERT(m_tree.currentStackItem()->hasTagName(headTag));
setInsertionMode(InsertionMode::InHead);
return;
}
if (token->name() != brTag) {
parseError(token);
return;
}
defaultForInHeadNoscript();
processToken(token);
break;
case InsertionMode::Text:
if (token->name() == scriptTag) {
ASSERT(m_tree.currentStackItem()->hasTagName(scriptTag));
if (scriptingContentIsAllowed(m_tree.parserContentPolicy()))
m_scriptToProcess = m_tree.currentElement();
m_tree.openElements()->pop();
setInsertionMode(m_originalInsertionMode);
if (m_parser.tokenizer()) {
ASSERT(m_parser.tokenizer()->state() == HTMLTokenizer::DataState || m_options.usePreHTML5ParserQuirks);
m_parser.tokenizer()->setState(HTMLTokenizer::DataState);
}
return;
}
m_tree.openElements()->pop();
setInsertionMode(m_originalInsertionMode);
break;
case InsertionMode::InFrameset:
ASSERT(insertionMode() == InsertionMode::InFrameset);
if (token->name() == framesetTag) {
bool ignoreFramesetForFragmentParsing = m_tree.currentIsRootNode();
#if ENABLE(TEMPLATE_ELEMENT)
ignoreFramesetForFragmentParsing = ignoreFramesetForFragmentParsing || m_tree.openElements()->hasTemplateInHTMLScope();
#endif
if (ignoreFramesetForFragmentParsing) {
ASSERT(isParsingFragmentOrTemplateContents());
parseError(token);
return;
}
m_tree.openElements()->pop();
if (!isParsingFragment() && !m_tree.currentStackItem()->hasTagName(framesetTag))
setInsertionMode(InsertionMode::AfterFrameset);
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
#endif
break;
case InsertionMode::AfterFrameset:
ASSERT(insertionMode() == InsertionMode::AfterFrameset);
if (token->name() == htmlTag) {
setInsertionMode(InsertionMode::AfterAfterFrameset);
return;
}
FALLTHROUGH;
case InsertionMode::AfterAfterFrameset:
ASSERT(insertionMode() == InsertionMode::AfterFrameset || insertionMode() == InsertionMode::AfterAfterFrameset);
parseError(token);
break;
case InsertionMode::InSelectInTable:
ASSERT(insertionMode() == InsertionMode::InSelectInTable);
if (token->name() == captionTag
|| token->name() == tableTag
|| isTableBodyContextTag(token->name())
|| token->name() == trTag
|| isTableCellContextTag(token->name())) {
parseError(token);
if (m_tree.openElements()->inTableScope(token->name())) {
AtomicHTMLToken endSelect(HTMLToken::EndTag, selectTag.localName());
processEndTag(&endSelect);
processEndTag(token);
}
return;
}
FALLTHROUGH;
case InsertionMode::InSelect:
ASSERT(insertionMode() == InsertionMode::InSelect || insertionMode() == InsertionMode::InSelectInTable);
if (token->name() == optgroupTag) {
if (isHTMLOptionElement(m_tree.currentStackItem()->node()) && m_tree.oneBelowTop() && isHTMLOptGroupElement(m_tree.oneBelowTop()->node()))
processFakeEndTag(optionTag);
if (isHTMLOptGroupElement(m_tree.currentStackItem()->node())) {
m_tree.openElements()->pop();
return;
}
parseError(token);
return;
}
if (token->name() == optionTag) {
if (isHTMLOptionElement(m_tree.currentStackItem()->node())) {
m_tree.openElements()->pop();
return;
}
parseError(token);
return;
}
if (token->name() == selectTag) {
if (!m_tree.openElements()->inSelectScope(token->name())) {
ASSERT(isParsingFragment());
parseError(token);
return;
}
m_tree.openElements()->popUntilPopped(selectTag.localName());
resetInsertionModeAppropriately();
return;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
#endif
break;
case InsertionMode::InTableText:
defaultForInTableText();
processEndTag(token);
break;
case InsertionMode::TemplateContents:
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
#else
ASSERT_NOT_REACHED();
#endif
break;
}
}
void HTMLTreeBuilder::processComment(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::Comment);
if (m_insertionMode == InsertionMode::Initial
|| m_insertionMode == InsertionMode::BeforeHTML
|| m_insertionMode == InsertionMode::AfterAfterBody
|| m_insertionMode == InsertionMode::AfterAfterFrameset) {
m_tree.insertCommentOnDocument(token);
return;
}
if (m_insertionMode == InsertionMode::AfterBody) {
m_tree.insertCommentOnHTMLHtmlElement(token);
return;
}
if (m_insertionMode == InsertionMode::InTableText) {
defaultForInTableText();
processComment(token);
return;
}
m_tree.insertComment(token);
}
void HTMLTreeBuilder::processCharacter(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::Character);
ExternalCharacterTokenBuffer buffer(token);
processCharacterBuffer(buffer);
}
#if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS)
void HTMLTreeBuilder::insertPhoneNumberLink(const String& string)
{
Vector<Attribute> attributes;
attributes.append(Attribute(HTMLNames::hrefAttr, ASCIILiteral("tel:") + string));
const AtomicString& aTagLocalName = aTag.localName();
AtomicHTMLToken aStartToken(HTMLToken::StartTag, aTagLocalName, attributes);
AtomicHTMLToken aEndToken(HTMLToken::EndTag, aTagLocalName);
processStartTag(&aStartToken);
m_tree.executeQueuedTasks();
m_tree.insertTextNode(string);
processEndTag(&aEndToken);
}
void HTMLTreeBuilder::linkifyPhoneNumbers(const String& string)
{
ASSERT(TelephoneNumberDetector::isSupported());
unsigned length = string.length();
unsigned scannerPosition = 0;
int relativeStartPosition = 0;
int relativeEndPosition = 0;
auto characters = StringView(string).upconvertedCharacters();
while (scannerPosition < length && TelephoneNumberDetector::find(&characters[scannerPosition], length - scannerPosition, &relativeStartPosition, &relativeEndPosition)) {
--relativeEndPosition;
ASSERT(scannerPosition + relativeEndPosition < length);
m_tree.insertTextNode(string.substring(scannerPosition, relativeStartPosition));
insertPhoneNumberLink(string.substring(scannerPosition + relativeStartPosition, relativeEndPosition - relativeStartPosition + 1));
scannerPosition += relativeEndPosition + 1;
}
if (scannerPosition > 0) {
if (scannerPosition < length) {
String after = string.substring(scannerPosition, length - scannerPosition);
m_tree.insertTextNode(after);
}
} else
m_tree.insertTextNode(string);
}
static inline bool disallowTelephoneNumberParsing(const Node& node)
{
return node.isLink()
|| node.nodeType() == Node::COMMENT_NODE
|| node.hasTagName(scriptTag)
|| (node.isHTMLElement() && toHTMLElement(node).isFormControlElement())
|| node.hasTagName(styleTag)
|| node.hasTagName(ttTag)
|| node.hasTagName(preTag)
|| node.hasTagName(codeTag);
}
static inline bool shouldParseTelephoneNumbersInNode(const ContainerNode& node)
{
const ContainerNode* currentNode = &node;
do {
if (currentNode->isElementNode() && disallowTelephoneNumberParsing(*currentNode))
return false;
currentNode = currentNode->parentNode();
} while (currentNode);
return true;
}
#endif // ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS)
void HTMLTreeBuilder::processCharacterBuffer(ExternalCharacterTokenBuffer& buffer)
{
ReprocessBuffer:
if (m_shouldSkipLeadingNewline) {
m_shouldSkipLeadingNewline = false;
buffer.skipAtMostOneLeadingNewline();
if (buffer.isEmpty())
return;
}
switch (insertionMode()) {
case InsertionMode::Initial: {
ASSERT(insertionMode() == InsertionMode::Initial);
buffer.skipLeadingWhitespace();
if (buffer.isEmpty())
return;
defaultForInitial();
FALLTHROUGH;
}
case InsertionMode::BeforeHTML: {
ASSERT(insertionMode() == InsertionMode::BeforeHTML);
buffer.skipLeadingWhitespace();
if (buffer.isEmpty())
return;
defaultForBeforeHTML();
FALLTHROUGH;
}
case InsertionMode::BeforeHead: {
ASSERT(insertionMode() == InsertionMode::BeforeHead);
buffer.skipLeadingWhitespace();
if (buffer.isEmpty())
return;
defaultForBeforeHead();
FALLTHROUGH;
}
case InsertionMode::InHead: {
ASSERT(insertionMode() == InsertionMode::InHead);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
defaultForInHead();
FALLTHROUGH;
}
case InsertionMode::AfterHead: {
ASSERT(insertionMode() == InsertionMode::AfterHead);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
defaultForAfterHead();
FALLTHROUGH;
}
case InsertionMode::InBody:
case InsertionMode::InCaption:
case InsertionMode::TemplateContents:
case InsertionMode::InCell: {
#if ENABLE(TEMPLATE_ELEMENT)
ASSERT(insertionMode() == InsertionMode::InBody || insertionMode() == InsertionMode::InCaption || insertionMode() == InsertionMode::InCell || insertionMode() == InsertionMode::TemplateContents);
#else
ASSERT(insertionMode() != InsertionMode::TemplateContents);
ASSERT(insertionMode() == InsertionMode::InBody || insertionMode() == InsertionMode::InCaption || insertionMode() == InsertionMode::InCell);
#endif
processCharacterBufferForInBody(buffer);
break;
}
case InsertionMode::InTable:
case InsertionMode::InTableBody:
case InsertionMode::InRow: {
ASSERT(insertionMode() == InsertionMode::InTable || insertionMode() == InsertionMode::InTableBody || insertionMode() == InsertionMode::InRow);
ASSERT(m_pendingTableCharacters.isEmpty());
if (m_tree.currentStackItem()->isElementNode()
&& (isHTMLTableElement(m_tree.currentStackItem()->node())
|| m_tree.currentStackItem()->hasTagName(HTMLNames::tbodyTag)
|| m_tree.currentStackItem()->hasTagName(HTMLNames::tfootTag)
|| m_tree.currentStackItem()->hasTagName(HTMLNames::theadTag)
|| m_tree.currentStackItem()->hasTagName(HTMLNames::trTag))) {
m_originalInsertionMode = m_insertionMode;
setInsertionMode(InsertionMode::InTableText);
} else {
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
processCharacterBufferForInBody(buffer);
break;
}
FALLTHROUGH;
}
case InsertionMode::InTableText: {
buffer.giveRemainingTo(m_pendingTableCharacters);
break;
}
case InsertionMode::InColumnGroup: {
ASSERT(insertionMode() == InsertionMode::InColumnGroup);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
if (!processColgroupEndTagForInColumnGroup()) {
ASSERT(isParsingFragmentOrTemplateContents());
buffer.skipLeadingNonWhitespace();
if (buffer.isEmpty())
return;
}
goto ReprocessBuffer;
}
case InsertionMode::AfterBody:
case InsertionMode::AfterAfterBody: {
ASSERT(insertionMode() == InsertionMode::AfterBody || insertionMode() == InsertionMode::AfterAfterBody);
setInsertionMode(InsertionMode::InBody);
goto ReprocessBuffer;
}
case InsertionMode::Text: {
ASSERT(insertionMode() == InsertionMode::Text);
m_tree.insertTextNode(buffer.takeRemaining());
break;
}
case InsertionMode::InHeadNoscript: {
ASSERT(insertionMode() == InsertionMode::InHeadNoscript);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
defaultForInHeadNoscript();
goto ReprocessBuffer;
}
case InsertionMode::InFrameset:
case InsertionMode::AfterFrameset: {
ASSERT(insertionMode() == InsertionMode::InFrameset || insertionMode() == InsertionMode::AfterFrameset || insertionMode() == InsertionMode::AfterAfterFrameset);
String leadingWhitespace = buffer.takeRemainingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
break;
}
case InsertionMode::InSelectInTable:
case InsertionMode::InSelect: {
ASSERT(insertionMode() == InsertionMode::InSelect || insertionMode() == InsertionMode::InSelectInTable);
m_tree.insertTextNode(buffer.takeRemaining());
break;
}
case InsertionMode::AfterAfterFrameset: {
String leadingWhitespace = buffer.takeRemainingWhitespace();
if (!leadingWhitespace.isEmpty()) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
}
break;
}
}
}
void HTMLTreeBuilder::processCharacterBufferForInBody(ExternalCharacterTokenBuffer& buffer)
{
m_tree.reconstructTheActiveFormattingElements();
String characters = buffer.takeRemaining();
#if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS)
if (!isParsingFragment() && m_tree.isTelephoneNumberParsingEnabled() && shouldParseTelephoneNumbersInNode(*m_tree.currentNode()) && TelephoneNumberDetector::isSupported())
linkifyPhoneNumbers(characters);
else
m_tree.insertTextNode(characters);
#else
m_tree.insertTextNode(characters);
#endif
if (m_framesetOk && !isAllWhitespaceOrReplacementCharacters(characters))
m_framesetOk = false;
}
void HTMLTreeBuilder::processEndOfFile(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::EndOfFile);
switch (insertionMode()) {
case InsertionMode::Initial:
ASSERT(insertionMode() == InsertionMode::Initial);
defaultForInitial();
FALLTHROUGH;
case InsertionMode::BeforeHTML:
ASSERT(insertionMode() == InsertionMode::BeforeHTML);
defaultForBeforeHTML();
FALLTHROUGH;
case InsertionMode::BeforeHead:
ASSERT(insertionMode() == InsertionMode::BeforeHead);
defaultForBeforeHead();
FALLTHROUGH;
case InsertionMode::InHead:
ASSERT(insertionMode() == InsertionMode::InHead);
defaultForInHead();
FALLTHROUGH;
case InsertionMode::AfterHead:
ASSERT(insertionMode() == InsertionMode::AfterHead);
defaultForAfterHead();
FALLTHROUGH;
case InsertionMode::InBody:
case InsertionMode::InCell:
case InsertionMode::InCaption:
case InsertionMode::InRow:
#if ENABLE(TEMPLATE_ELEMENT)
ASSERT(insertionMode() == InsertionMode::InBody || insertionMode() == InsertionMode::InCell || insertionMode() == InsertionMode::InCaption || insertionMode() == InsertionMode::InRow || insertionMode() == InsertionMode::TemplateContents);
#else
ASSERT(insertionMode() != InsertionMode::TemplateContents);
ASSERT(insertionMode() == InsertionMode::InBody || insertionMode() == InsertionMode::InCell || insertionMode() == InsertionMode::InCaption || insertionMode() == InsertionMode::InRow);
#endif
notImplemented(); #if ENABLE(TEMPLATE_ELEMENT)
if (!m_templateInsertionModes.isEmpty())
if (processEndOfFileForInTemplateContents(token))
return;
#endif
break;
case InsertionMode::AfterBody:
case InsertionMode::AfterAfterBody:
ASSERT(insertionMode() == InsertionMode::AfterBody || insertionMode() == InsertionMode::AfterAfterBody);
break;
case InsertionMode::InHeadNoscript:
ASSERT(insertionMode() == InsertionMode::InHeadNoscript);
defaultForInHeadNoscript();
processEndOfFile(token);
return;
case InsertionMode::AfterFrameset:
case InsertionMode::AfterAfterFrameset:
ASSERT(insertionMode() == InsertionMode::AfterFrameset || insertionMode() == InsertionMode::AfterAfterFrameset);
break;
case InsertionMode::InColumnGroup:
if (m_tree.currentIsRootNode()) {
ASSERT(isParsingFragment());
return; }
#if ENABLE(TEMPLATE_ELEMENT)
ASSERT(m_tree.currentNode()->hasTagName(colgroupTag) || m_tree.currentNode()->hasTagName(templateTag));
#else
ASSERT(m_tree.currentNode()->hasTagName(colgroupTag));
#endif
processColgroupEndTagForInColumnGroup();
FALLTHROUGH;
case InsertionMode::InFrameset:
case InsertionMode::InTable:
case InsertionMode::InTableBody:
case InsertionMode::InSelectInTable:
case InsertionMode::InSelect:
ASSERT(insertionMode() == InsertionMode::InSelect || insertionMode() == InsertionMode::InSelectInTable || insertionMode() == InsertionMode::InTable || insertionMode() == InsertionMode::InFrameset || insertionMode() == InsertionMode::InTableBody || insertionMode() == InsertionMode::InColumnGroup);
if (m_tree.currentNode() != m_tree.openElements()->rootNode())
parseError(token);
#if ENABLE(TEMPLATE_ELEMENT)
if (!m_templateInsertionModes.isEmpty())
if (processEndOfFileForInTemplateContents(token))
return;
#endif
break;
case InsertionMode::InTableText:
defaultForInTableText();
processEndOfFile(token);
return;
case InsertionMode::Text:
parseError(token);
if (m_tree.currentStackItem()->hasTagName(scriptTag))
notImplemented(); m_tree.openElements()->pop();
ASSERT(m_originalInsertionMode != InsertionMode::Text);
setInsertionMode(m_originalInsertionMode);
processEndOfFile(token);
return;
case InsertionMode::TemplateContents:
#if ENABLE(TEMPLATE_ELEMENT)
if (processEndOfFileForInTemplateContents(token))
return;
break;
#else
ASSERT_NOT_REACHED();
#endif
}
ASSERT(m_tree.currentNode());
m_tree.openElements()->popAll();
}
void HTMLTreeBuilder::defaultForInitial()
{
notImplemented();
m_tree.setDefaultCompatibilityMode();
setInsertionMode(InsertionMode::BeforeHTML);
}
void HTMLTreeBuilder::defaultForBeforeHTML()
{
AtomicHTMLToken startHTML(HTMLToken::StartTag, htmlTag.localName());
m_tree.insertHTMLHtmlStartTagBeforeHTML(&startHTML);
setInsertionMode(InsertionMode::BeforeHead);
}
void HTMLTreeBuilder::defaultForBeforeHead()
{
AtomicHTMLToken startHead(HTMLToken::StartTag, headTag.localName());
processStartTag(&startHead);
}
void HTMLTreeBuilder::defaultForInHead()
{
AtomicHTMLToken endHead(HTMLToken::EndTag, headTag.localName());
processEndTag(&endHead);
}
void HTMLTreeBuilder::defaultForInHeadNoscript()
{
AtomicHTMLToken endNoscript(HTMLToken::EndTag, noscriptTag.localName());
processEndTag(&endNoscript);
}
void HTMLTreeBuilder::defaultForAfterHead()
{
AtomicHTMLToken startBody(HTMLToken::StartTag, bodyTag.localName());
processStartTag(&startBody);
m_framesetOk = true;
}
void HTMLTreeBuilder::defaultForInTableText()
{
String characters = m_pendingTableCharacters.toString();
m_pendingTableCharacters.clear();
if (!isAllWhitespace(characters)) {
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertTextNode(characters, NotAllWhitespace);
m_framesetOk = false;
setInsertionMode(m_originalInsertionMode);
return;
}
m_tree.insertTextNode(characters);
setInsertionMode(m_originalInsertionMode);
}
bool HTMLTreeBuilder::processStartTagForInHead(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return true;
}
if (token->name() == baseTag
|| token->name() == basefontTag
|| token->name() == bgsoundTag
|| token->name() == commandTag
|| token->name() == linkTag
|| token->name() == metaTag) {
m_tree.insertSelfClosingHTMLElement(token);
return true;
}
if (token->name() == titleTag) {
processGenericRCDATAStartTag(token);
return true;
}
if (token->name() == noscriptTag) {
if (m_options.scriptEnabled) {
processGenericRawTextStartTag(token);
return true;
}
m_tree.insertHTMLElement(token);
setInsertionMode(InsertionMode::InHeadNoscript);
return true;
}
if (token->name() == noframesTag || token->name() == styleTag) {
processGenericRawTextStartTag(token);
return true;
}
if (token->name() == scriptTag) {
processScriptStartTag(token);
if (m_options.usePreHTML5ParserQuirks && token->selfClosing())
processFakeEndTag(scriptTag);
return true;
}
#if ENABLE(TEMPLATE_ELEMENT)
if (token->name() == templateTag) {
processTemplateStartTag(token);
return true;
}
#endif
if (token->name() == headTag) {
parseError(token);
return true;
}
return false;
}
void HTMLTreeBuilder::processGenericRCDATAStartTag(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
m_tree.insertHTMLElement(token);
if (m_parser.tokenizer())
m_parser.tokenizer()->setState(HTMLTokenizer::RCDATAState);
m_originalInsertionMode = m_insertionMode;
setInsertionMode(InsertionMode::Text);
}
void HTMLTreeBuilder::processGenericRawTextStartTag(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
m_tree.insertHTMLElement(token);
if (m_parser.tokenizer())
m_parser.tokenizer()->setState(HTMLTokenizer::RAWTEXTState);
m_originalInsertionMode = m_insertionMode;
setInsertionMode(InsertionMode::Text);
}
void HTMLTreeBuilder::processScriptStartTag(AtomicHTMLToken* token)
{
ASSERT(token->type() == HTMLToken::StartTag);
m_tree.insertScriptElement(token);
if (m_parser.tokenizer())
m_parser.tokenizer()->setState(HTMLTokenizer::ScriptDataState);
m_originalInsertionMode = m_insertionMode;
TextPosition position = m_parser.textPosition();
m_scriptToProcessStartPosition = position;
setInsertionMode(InsertionMode::Text);
}
bool HTMLTreeBuilder::shouldProcessTokenInForeignContent(AtomicHTMLToken* token)
{
if (m_tree.isEmpty())
return false;
HTMLStackItem* item = m_tree.currentStackItem();
if (item->isInHTMLNamespace())
return false;
if (HTMLElementStack::isMathMLTextIntegrationPoint(item)) {
if (token->type() == HTMLToken::StartTag
&& token->name() != MathMLNames::mglyphTag
&& token->name() != MathMLNames::malignmarkTag)
return false;
if (token->type() == HTMLToken::Character)
return false;
}
if (item->hasTagName(MathMLNames::annotation_xmlTag)
&& token->type() == HTMLToken::StartTag
&& token->name() == SVGNames::svgTag)
return false;
if (HTMLElementStack::isHTMLIntegrationPoint(item)) {
if (token->type() == HTMLToken::StartTag)
return false;
if (token->type() == HTMLToken::Character)
return false;
}
if (token->type() == HTMLToken::EndOfFile)
return false;
return true;
}
void HTMLTreeBuilder::processTokenInForeignContent(AtomicHTMLToken* token)
{
switch (token->type()) {
case HTMLToken::Uninitialized:
ASSERT_NOT_REACHED();
break;
case HTMLToken::DOCTYPE:
parseError(token);
break;
case HTMLToken::StartTag: {
if (token->name() == bTag
|| token->name() == bigTag
|| token->name() == blockquoteTag
|| token->name() == bodyTag
|| token->name() == brTag
|| token->name() == centerTag
|| token->name() == codeTag
|| token->name() == ddTag
|| token->name() == divTag
|| token->name() == dlTag
|| token->name() == dtTag
|| token->name() == emTag
|| token->name() == embedTag
|| isNumberedHeaderTag(token->name())
|| token->name() == headTag
|| token->name() == hrTag
|| token->name() == iTag
|| token->name() == imgTag
|| token->name() == liTag
|| token->name() == listingTag
|| token->name() == menuTag
|| token->name() == metaTag
|| token->name() == nobrTag
|| token->name() == olTag
|| token->name() == pTag
|| token->name() == preTag
|| token->name() == rubyTag
|| token->name() == sTag
|| token->name() == smallTag
|| token->name() == spanTag
|| token->name() == strongTag
|| token->name() == strikeTag
|| token->name() == subTag
|| token->name() == supTag
|| token->name() == tableTag
|| token->name() == ttTag
|| token->name() == uTag
|| token->name() == ulTag
|| token->name() == varTag
|| (token->name() == fontTag && (token->getAttributeItem(colorAttr) || token->getAttributeItem(faceAttr) || token->getAttributeItem(sizeAttr)))) {
parseError(token);
m_tree.openElements()->popUntilForeignContentScopeMarker();
processStartTag(token);
return;
}
const AtomicString& currentNamespace = m_tree.currentStackItem()->namespaceURI();
if (currentNamespace == MathMLNames::mathmlNamespaceURI)
adjustMathMLAttributes(*token);
if (currentNamespace == SVGNames::svgNamespaceURI) {
adjustSVGTagNameCase(*token);
adjustSVGAttributes(*token);
}
adjustForeignAttributes(*token);
m_tree.insertForeignElement(token, currentNamespace);
break;
}
case HTMLToken::EndTag: {
if (m_tree.currentStackItem()->namespaceURI() == SVGNames::svgNamespaceURI)
adjustSVGTagNameCase(*token);
if (token->name() == SVGNames::scriptTag && m_tree.currentStackItem()->hasTagName(SVGNames::scriptTag)) {
if (scriptingContentIsAllowed(m_tree.parserContentPolicy()))
m_scriptToProcess = m_tree.currentElement();
m_tree.openElements()->pop();
return;
}
if (!m_tree.currentStackItem()->isInHTMLNamespace()) {
HTMLElementStack::ElementRecord* nodeRecord = m_tree.openElements()->topRecord();
if (!nodeRecord->stackItem()->hasLocalName(token->name()))
parseError(token);
while (1) {
if (nodeRecord->stackItem()->hasLocalName(token->name())) {
m_tree.openElements()->popUntilPopped(nodeRecord->element());
return;
}
nodeRecord = nodeRecord->next();
if (nodeRecord->stackItem()->isInHTMLNamespace())
break;
}
}
processEndTag(token);
break;
}
case HTMLToken::Comment:
m_tree.insertComment(token);
return;
case HTMLToken::Character: {
String characters = String(token->characters(), token->charactersLength());
m_tree.insertTextNode(characters);
if (m_framesetOk && !isAllWhitespaceOrReplacementCharacters(characters))
m_framesetOk = false;
break;
}
case HTMLToken::EndOfFile:
ASSERT_NOT_REACHED();
break;
}
}
void HTMLTreeBuilder::finished()
{
if (isParsingFragment())
return;
#if ENABLE(TEMPLATE_ELEMENT)
ASSERT(m_templateInsertionModes.isEmpty());
#endif
ASSERT(m_isAttached);
m_tree.finishedParsing();
}
void HTMLTreeBuilder::parseError(AtomicHTMLToken*)
{
}
}