blob: 684d43b720516c61c0b0c99f6f3047644a3e1046 [file] [log] [blame]
/*
* Copyright (C) 2010 Google, Inc. All Rights Reserved.
* Copyright (C) 2011, 2014 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "core/html/parser/HTMLTreeBuilder.h"
#include "bindings/core/v8/ExceptionStatePlaceholder.h"
#include "core/HTMLNames.h"
#include "core/MathMLNames.h"
#include "core/SVGNames.h"
#include "core/XLinkNames.h"
#include "core/XMLNSNames.h"
#include "core/XMLNames.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentFragment.h"
#include "core/dom/ElementTraversal.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLFormElement.h"
#include "core/html/HTMLTemplateElement.h"
#include "core/html/parser/AtomicHTMLToken.h"
#include "core/html/parser/HTMLDocumentParser.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html/parser/HTMLStackItem.h"
#include "core/html/parser/HTMLToken.h"
#include "core/html/parser/HTMLTokenizer.h"
#include "platform/text/PlatformLocale.h"
#include "wtf/text/CharacterNames.h"
#include <memory>
namespace blink {
using namespace HTMLNames;
namespace {
inline bool isHTMLSpaceOrReplacementCharacter(UChar character) {
return isHTMLSpace<UChar>(character) || character == replacementCharacter;
}
}
static TextPosition uninitializedPositionValue1() {
return TextPosition(OrdinalNumber::fromOneBasedInt(-1),
OrdinalNumber::first());
}
static inline bool isAllWhitespace(const String& string) {
return string.isAllSpecialCharacters<isHTMLSpace<UChar>>();
}
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);
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#formatting
static bool isFormattingTag(const AtomicString& tagName) {
return tagName == aTag || isNonAnchorFormattingTag(tagName);
}
class HTMLTreeBuilder::CharacterTokenBuffer {
WTF_MAKE_NONCOPYABLE(CharacterTokenBuffer);
public:
explicit CharacterTokenBuffer(AtomicHTMLToken* token)
: m_characters(token->characters().impl()),
m_current(0),
m_end(token->characters().length()) {
ASSERT(!isEmpty());
}
explicit CharacterTokenBuffer(const String& characters)
: m_characters(characters.impl()),
m_current(0),
m_end(characters.length()) {
ASSERT(!isEmpty());
}
~CharacterTokenBuffer() { ASSERT(isEmpty()); }
bool isEmpty() const { return m_current == m_end; }
void skipAtMostOneLeadingNewline() {
ASSERT(!isEmpty());
if ((*m_characters)[m_current] == '\n')
++m_current;
}
void skipLeadingWhitespace() { skipLeading<isHTMLSpace<UChar>>(); }
String takeLeadingWhitespace() { return takeLeading<isHTMLSpace<UChar>>(); }
void skipLeadingNonWhitespace() { skipLeading<isNotHTMLSpace<UChar>>(); }
void skipRemaining() { m_current = m_end; }
String takeRemaining() {
ASSERT(!isEmpty());
unsigned start = m_current;
m_current = m_end;
// Notice that substring is smart enough to return *this when start == 0.
return String(m_characters->substring(start, m_end - start));
}
void giveRemainingTo(StringBuilder& recipient) {
if (m_characters->is8Bit())
recipient.append(m_characters->characters8() + m_current,
m_end - m_current);
else
recipient.append(m_characters->characters16() + m_current,
m_end - m_current);
m_current = m_end;
}
String takeRemainingWhitespace() {
ASSERT(!isEmpty());
const unsigned start = m_current;
m_current = m_end; // One way or another, we're taking everything!
unsigned length = 0;
for (unsigned i = start; i < m_end; ++i) {
if (isHTMLSpace<UChar>((*m_characters)[i]))
++length;
}
// Returning the null string when there aren't any whitespace
// characters is slightly cleaner semantically because we don't want
// to insert a text node (as opposed to inserting an empty text node).
if (!length)
return String();
if (length == start - m_end) // It's all whitespace.
return String(m_characters->substring(start, start - m_end));
StringBuilder result;
result.reserveCapacity(length);
for (unsigned i = start; i < m_end; ++i) {
UChar c = (*m_characters)[i];
if (isHTMLSpace<UChar>(c))
result.append(c);
}
return result.toString();
}
private:
template <bool characterPredicate(UChar)>
void skipLeading() {
ASSERT(!isEmpty());
while (characterPredicate((*m_characters)[m_current])) {
if (++m_current == m_end)
return;
}
}
template <bool characterPredicate(UChar)>
String takeLeading() {
ASSERT(!isEmpty());
const unsigned start = m_current;
skipLeading<characterPredicate>();
if (start == m_current)
return String();
return String(m_characters->substring(start, m_current - start));
}
RefPtr<StringImpl> m_characters;
unsigned m_current;
unsigned m_end;
};
HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser* parser,
Document& document,
ParserContentPolicy parserContentPolicy,
const HTMLParserOptions& options)
: m_framesetOk(true),
#if ENABLE(ASSERT)
m_isAttached(true),
#endif
m_tree(parser->reentryPermit(), document, parserContentPolicy),
m_insertionMode(InitialMode),
m_originalInsertionMode(InitialMode),
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)
: HTMLTreeBuilder(parser,
fragment->document(),
parserContentPolicy,
options) {
ASSERT(isMainThread());
ASSERT(contextElement);
m_tree.initFragmentParsing(fragment, contextElement);
m_fragmentContext.init(fragment, contextElement);
// Steps 4.2-4.6 of the HTML5 Fragment Case parsing algorithm:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/the-end.html#fragment-case
// For efficiency, we skip step 4.2 ("Let root be a new html element with no
// attributes") and instead use the DocumentFragment as a root node.
m_tree.openElements()->pushRootNode(HTMLStackItem::create(
fragment, HTMLStackItem::ItemForDocumentFragmentNode));
if (isHTMLTemplateElement(*contextElement))
m_templateInsertionModes.append(TemplateContentsMode);
resetInsertionModeAppropriately();
}
HTMLTreeBuilder::~HTMLTreeBuilder() {}
void HTMLTreeBuilder::FragmentParsingContext::init(DocumentFragment* fragment,
Element* contextElement) {
DCHECK(fragment);
DCHECK(!fragment->hasChildren());
m_fragment = fragment;
m_contextElementStackItem = HTMLStackItem::create(
contextElement, HTMLStackItem::ItemForContextElement);
}
DEFINE_TRACE(HTMLTreeBuilder::FragmentParsingContext) {
visitor->trace(m_fragment);
visitor->trace(m_contextElementStackItem);
}
DEFINE_TRACE(HTMLTreeBuilder) {
visitor->trace(m_fragmentContext);
visitor->trace(m_tree);
visitor->trace(m_parser);
visitor->trace(m_scriptToProcess);
}
void HTMLTreeBuilder::detach() {
#if ENABLE(ASSERT)
// This call makes little sense in fragment mode, but for consistency
// DocumentParser expects detach() to always be called before it's destroyed.
m_isAttached = false;
#endif
// HTMLConstructionSite might be on the callstack when detach() is called
// otherwise we'd just call m_tree.clear() here instead.
m_tree.detach();
}
Element* HTMLTreeBuilder::takeScriptToProcess(
TextPosition& scriptStartPosition) {
ASSERT(m_scriptToProcess);
ASSERT(!m_tree.hasPendingTasks());
// Unpause ourselves, callers may pause us again when processing the script.
// The HTML5 spec is written as though scripts are executed inside the tree
// builder. We pause the parser to exit the tree builder, and then resume
// before running scripts.
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 = false;
if (!m_tree.isEmpty()) {
HTMLStackItem* adjustedCurrentNode = adjustedCurrentStackItem();
inForeignContent =
!adjustedCurrentNode->isInHTMLNamespace() &&
!HTMLElementStack::isHTMLIntegrationPoint(adjustedCurrentNode) &&
!HTMLElementStack::isMathMLTextIntegrationPoint(adjustedCurrentNode);
}
m_parser->tokenizer()->setForceNullCharacterReplacement(
m_insertionMode == TextMode || inForeignContent);
m_parser->tokenizer()->setShouldAllowCDATA(inForeignContent);
}
m_tree.executeQueuedTasks();
// We might be detached now.
}
void HTMLTreeBuilder::processToken(AtomicHTMLToken* token) {
if (token->type() == HTMLToken::Character) {
processCharacter(token);
return;
}
// Any non-character token needs to cause us to flush any pending text
// immediately. NOTE: flush() can cause any queued tasks to execute, possibly
// re-entering the parser.
m_tree.flush(FlushAlways);
m_shouldSkipLeadingNewline = false;
switch (token->type()) {
case HTMLToken::Uninitialized:
case HTMLToken::Character:
ASSERT_NOT_REACHED();
break;
case HTMLToken::DOCTYPE:
processDoctypeToken(token);
break;
case HTMLToken::StartTag:
processStartTag(token);
break;
case HTMLToken::EndTag:
processEndTag(token);
break;
case HTMLToken::Comment:
processComment(token);
break;
case HTMLToken::EndOfFile:
processEndOfFile(token);
break;
}
}
void HTMLTreeBuilder::processDoctypeToken(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::DOCTYPE);
if (m_insertionMode == InitialMode) {
m_tree.insertDoctype(token);
setInsertionMode(BeforeHTMLMode);
return;
}
if (m_insertionMode == InTableTextMode) {
defaultForInTableText();
processDoctypeToken(token);
return;
}
parseError(token);
}
void HTMLTreeBuilder::processFakeStartTag(const QualifiedName& tagName,
const Vector<Attribute>& attributes) {
// FIXME: We'll need a fancier conversion than just "localName" for SVG/MathML
// tags.
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) {
// FIXME: We'll need a fancier conversion than just "localName" for SVG/MathML
// tags.
processFakeEndTag(tagName.localName());
}
void HTMLTreeBuilder::processFakePEndTagIfPInButtonScope() {
if (!m_tree.openElements()->inButtonScope(pTag.localName()))
return;
AtomicHTMLToken endP(HTMLToken::EndTag, pTag.localName());
processEndTag(&endP);
}
namespace {
bool isLi(const HTMLStackItem* item) {
return item->hasTagName(liTag);
}
bool isDdOrDt(const HTMLStackItem* item) {
return item->hasTagName(ddTag) || item->hasTagName(dtTag);
}
} // namespace
template <bool shouldClose(const HTMLStackItem*)>
void HTMLTreeBuilder::processCloseWhenNestedTag(AtomicHTMLToken* token) {
m_framesetOk = false;
HTMLElementStack::ElementRecord* nodeRecord =
m_tree.openElements()->topRecord();
while (1) {
HTMLStackItem* item = nodeRecord->stackItem();
if (shouldClose(item)) {
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);
}
typedef HashMap<AtomicString, QualifiedName> PrefixedNameToQualifiedNameMap;
template <typename TableQualifiedName>
static void mapLoweredLocalNameToName(PrefixedNameToQualifiedNameMap* map,
const TableQualifiedName* const* names,
size_t length) {
for (size_t 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);
}
}
static void adjustSVGTagNameCase(AtomicHTMLToken* token) {
static PrefixedNameToQualifiedNameMap* caseMap = 0;
if (!caseMap) {
caseMap = new PrefixedNameToQualifiedNameMap;
std::unique_ptr<const SVGQualifiedName* []> svgTags =
SVGNames::getSVGTags();
mapLoweredLocalNameToName(caseMap, svgTags.get(), SVGNames::SVGTagsCount);
}
const QualifiedName& casedName = caseMap->get(token->name());
if (casedName.localName().isNull())
return;
token->setName(casedName.localName());
}
template <std::unique_ptr<const QualifiedName* []> getAttrs(), unsigned length>
static void adjustAttributes(AtomicHTMLToken* token) {
static PrefixedNameToQualifiedNameMap* caseMap = 0;
if (!caseMap) {
caseMap = new PrefixedNameToQualifiedNameMap;
std::unique_ptr<const QualifiedName* []> attrs = getAttrs();
mapLoweredLocalNameToName(caseMap, attrs.get(), length);
}
for (unsigned i = 0; i < token->attributes().size(); ++i) {
Attribute& tokenAttribute = token->attributes().at(i);
const QualifiedName& casedName = caseMap->get(tokenAttribute.localName());
if (!casedName.localName().isNull())
tokenAttribute.parserSetName(casedName);
}
}
static void adjustSVGAttributes(AtomicHTMLToken* token) {
adjustAttributes<SVGNames::getSVGAttrs, SVGNames::SVGAttrsCount>(token);
}
static void adjustMathMLAttributes(AtomicHTMLToken* token) {
adjustAttributes<MathMLNames::getMathMLAttrs, MathMLNames::MathMLAttrsCount>(
token);
}
static void addNamesWithPrefix(PrefixedNameToQualifiedNameMap* map,
const AtomicString& prefix,
const QualifiedName* const* names,
size_t length) {
for (size_t i = 0; i < length; ++i) {
const QualifiedName* name = names[i];
const AtomicString& localName = name->localName();
AtomicString prefixColonLocalName = prefix + ':' + localName;
QualifiedName nameWithPrefix(prefix, localName, name->namespaceURI());
map->add(prefixColonLocalName, nameWithPrefix);
}
}
static void adjustForeignAttributes(AtomicHTMLToken* token) {
static PrefixedNameToQualifiedNameMap* map = 0;
if (!map) {
map = new PrefixedNameToQualifiedNameMap;
std::unique_ptr<const QualifiedName* []> attrs =
XLinkNames::getXLinkAttrs();
addNamesWithPrefix(map, xlinkAtom, attrs.get(),
XLinkNames::XLinkAttrsCount);
std::unique_ptr<const QualifiedName* []> xmlAttrs = XMLNames::getXMLAttrs();
addNamesWithPrefix(map, xmlAtom, xmlAttrs.get(), XMLNames::XMLAttrsCount);
map->add(WTF::xmlnsAtom, XMLNSNames::xmlnsAttr);
map->add("xmlns:xlink", QualifiedName(xmlnsAtom, xlinkAtom,
XMLNSNames::xmlnsNamespaceURI));
}
for (unsigned i = 0; i < token->attributes().size(); ++i) {
Attribute& tokenAttribute = token->attributes().at(i);
const QualifiedName& name = map->get(tokenAttribute.localName());
if (!name.localName().isNull())
tokenAttribute.parserSetName(name);
}
}
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);
if (!m_tree.openElements()->secondElementIsHTMLBodyElement() ||
m_tree.openElements()->hasOnlyOneElement() ||
m_tree.openElements()->hasTemplateInHTMLScope()) {
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();
// Note: in the fragment case the root is a DocumentFragment instead of
// a proper html element which is a quirk in Blink's implementation.
DCHECK(!isParsingTemplateContents());
DCHECK(!isParsingFragment() ||
toDocumentFragment(m_tree.openElements()->topNode()));
DCHECK(isParsingFragment() ||
m_tree.openElements()->top() ==
m_tree.openElements()->htmlElement());
m_tree.insertHTMLElement(token);
setInsertionMode(InFramesetMode);
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.isFormElementPointerNonNull() && !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); // FIXME: Could we just fall through here?
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.getParserContentPolicy()))
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(InTableMode);
return;
}
if (token->name() == imageTag) {
parseError(token);
// Apparently we're not supposed to ask.
token->setName(imgTag.localName());
// Note the fall through to the imgTag handling below!
}
if (token->name() == areaTag || token->name() == brTag ||
token->name() == embedTag || token->name() == imgTag ||
token->name() == keygenTag || token->name() == wbrTag) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertSelfClosingHTMLElementDestroyingToken(token);
m_framesetOk = false;
return;
}
if (token->name() == inputTag) {
// Per spec https://html.spec.whatwg.org/#parsing-main-inbody,
// section "A start tag whose tag name is "input""
Attribute* typeAttribute = token->getAttributeItem(typeAttr);
bool disableFrameset =
!typeAttribute || !equalIgnoringCase(typeAttribute->value(), "hidden");
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertSelfClosingHTMLElementDestroyingToken(token);
if (disableFrameset)
m_framesetOk = false;
return;
}
if ((RuntimeEnabledFeatures::contextMenuEnabled() &&
token->name() == menuitemTag) ||
token->name() == paramTag || token->name() == sourceTag ||
token->name() == trackTag) {
m_tree.insertSelfClosingHTMLElementDestroyingToken(token);
return;
}
if (token->name() == hrTag) {
processFakePEndTagIfPInButtonScope();
m_tree.insertSelfClosingHTMLElementDestroyingToken(token);
m_framesetOk = false;
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(TextMode);
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 == InTableMode || m_insertionMode == InCaptionMode ||
m_insertionMode == InColumnGroupMode ||
m_insertionMode == InTableBodyMode || m_insertionMode == InRowMode ||
m_insertionMode == InCellMode)
setInsertionMode(InSelectInTableMode);
else
setInsertionMode(InSelectMode);
return;
}
if (token->name() == optgroupTag || token->name() == optionTag) {
if (m_tree.currentStackItem()->hasTagName(optionTag)) {
AtomicHTMLToken endOption(HTMLToken::EndTag, optionTag.localName());
processEndTag(&endOption);
}
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == rbTag || 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 || token->name() == rpTag) {
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 (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertHTMLElement(token);
}
void HTMLTreeBuilder::processTemplateStartTag(AtomicHTMLToken* token) {
m_tree.activeFormattingElements()->appendMarker();
m_tree.insertHTMLElement(token);
m_framesetOk = false;
m_templateInsertionModes.append(TemplateContentsMode);
setInsertionMode(TemplateContentsMode);
}
bool HTMLTreeBuilder::processTemplateEndTag(AtomicHTMLToken* token) {
ASSERT(token->name() == templateTag.localName());
if (!m_tree.openElements()->hasTemplateInHTMLScope()) {
ASSERT(m_templateInsertionModes.isEmpty() ||
(m_templateInsertionModes.size() == 1 &&
isHTMLTemplateElement(m_fragmentContext.contextElement())));
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;
}
bool HTMLTreeBuilder::processColgroupEndTagForInColumnGroup() {
if (m_tree.currentIsRootNode() ||
isHTMLTemplateElement(*m_tree.currentNode())) {
ASSERT(isParsingFragmentOrTemplateContents());
// FIXME: parse error
return false;
}
m_tree.openElements()->pop();
setInsertionMode(InTableMode);
return true;
}
// http://www.whatwg.org/specs/web-apps/current-work/#adjusted-current-node
HTMLStackItem* HTMLTreeBuilder::adjustedCurrentStackItem() const {
ASSERT(!m_tree.isEmpty());
if (isParsingFragment() && m_tree.openElements()->hasOnlyOneElement())
return m_fragmentContext.contextElementStackItem();
return m_tree.currentStackItem();
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#close-the-cell
void HTMLTreeBuilder::closeTheCell() {
ASSERT(getInsertionMode() == InCellMode);
if (m_tree.openElements()->inTableScope(tdTag)) {
ASSERT(!m_tree.openElements()->inTableScope(thTag));
processFakeEndTag(tdTag);
return;
}
ASSERT(m_tree.openElements()->inTableScope(thTag));
processFakeEndTag(thTag);
ASSERT(getInsertionMode() == InRowMode);
}
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(InCaptionMode);
return;
}
if (token->name() == colgroupTag) {
m_tree.openElements()->popUntilTableScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InColumnGroupMode);
return;
}
if (token->name() == colTag) {
processFakeStartTag(colgroupTag);
ASSERT(InColumnGroupMode);
processStartTag(token);
return;
}
if (isTableBodyContextTag(token->name())) {
m_tree.openElements()->popUntilTableScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InTableBodyMode);
return;
}
if (isTableCellContextTag(token->name()) || token->name() == trTag) {
processFakeStartTag(tbodyTag);
ASSERT(getInsertionMode() == InTableBodyMode);
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.insertSelfClosingHTMLElementDestroyingToken(token);
return;
}
// Fall through to "anything else" case.
}
if (token->name() == formTag) {
parseError(token);
if (m_tree.isFormElementPointerNonNull() && !isParsingTemplateContents())
return;
m_tree.insertHTMLFormElement(token, true);
m_tree.openElements()->pop();
return;
}
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
parseError(token);
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
processStartTagForInBody(token);
}
void HTMLTreeBuilder::processStartTag(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::StartTag);
switch (getInsertionMode()) {
case InitialMode:
ASSERT(getInsertionMode() == InitialMode);
defaultForInitial();
// Fall through.
case BeforeHTMLMode:
ASSERT(getInsertionMode() == BeforeHTMLMode);
if (token->name() == htmlTag) {
m_tree.insertHTMLHtmlStartTagBeforeHTML(token);
setInsertionMode(BeforeHeadMode);
return;
}
defaultForBeforeHTML();
// Fall through.
case BeforeHeadMode:
ASSERT(getInsertionMode() == BeforeHeadMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == headTag) {
m_tree.insertHTMLHeadElement(token);
setInsertionMode(InHeadMode);
return;
}
defaultForBeforeHead();
// Fall through.
case InHeadMode:
ASSERT(getInsertionMode() == InHeadMode);
if (processStartTagForInHead(token))
return;
defaultForInHead();
// Fall through.
case AfterHeadMode:
ASSERT(getInsertionMode() == AfterHeadMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == bodyTag) {
m_framesetOk = false;
m_tree.insertHTMLBodyElement(token);
setInsertionMode(InBodyMode);
return;
}
if (token->name() == framesetTag) {
m_tree.insertHTMLElement(token);
setInsertionMode(InFramesetMode);
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 ||
token->name() == templateTag || 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();
// Fall through
case InBodyMode:
ASSERT(getInsertionMode() == InBodyMode);
processStartTagForInBody(token);
break;
case InTableMode:
ASSERT(getInsertionMode() == InTableMode);
processStartTagForInTable(token);
break;
case InCaptionMode:
ASSERT(getInsertionMode() == InCaptionMode);
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 InColumnGroupMode:
ASSERT(getInsertionMode() == InColumnGroupMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == colTag) {
m_tree.insertSelfClosingHTMLElementDestroyingToken(token);
return;
}
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
if (!processColgroupEndTagForInColumnGroup()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
processStartTag(token);
break;
case InTableBodyMode:
ASSERT(getInsertionMode() == InTableBodyMode);
if (token->name() == trTag) {
// How is there ever anything to pop?
m_tree.openElements()->popUntilTableBodyScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InRowMode);
return;
}
if (isTableCellContextTag(token->name())) {
parseError(token);
processFakeStartTag(trTag);
ASSERT(getInsertionMode() == InRowMode);
processStartTag(token);
return;
}
if (isCaptionColOrColgroupTag(token->name()) ||
isTableBodyContextTag(token->name())) {
// FIXME: This is slow.
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 InRowMode:
ASSERT(getInsertionMode() == InRowMode);
if (isTableCellContextTag(token->name())) {
m_tree.openElements()->popUntilTableRowScopeMarker();
m_tree.insertHTMLElement(token);
setInsertionMode(InCellMode);
m_tree.activeFormattingElements()->appendMarker();
return;
}
if (token->name() == trTag || isCaptionColOrColgroupTag(token->name()) ||
isTableBodyContextTag(token->name())) {
if (!processTrEndTagForInRow()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
ASSERT(getInsertionMode() == InTableBodyMode);
processStartTag(token);
return;
}
processStartTagForInTable(token);
break;
case InCellMode:
ASSERT(getInsertionMode() == InCellMode);
if (isCaptionColOrColgroupTag(token->name()) ||
isTableCellContextTag(token->name()) || token->name() == trTag ||
isTableBodyContextTag(token->name())) {
// FIXME: This could be more efficient.
if (!m_tree.openElements()->inTableScope(tdTag) &&
!m_tree.openElements()->inTableScope(thTag)) {
ASSERT(isParsingFragment());
parseError(token);
return;
}
closeTheCell();
processStartTag(token);
return;
}
processStartTagForInBody(token);
break;
case AfterBodyMode:
case AfterAfterBodyMode:
ASSERT(getInsertionMode() == AfterBodyMode ||
getInsertionMode() == AfterAfterBodyMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
setInsertionMode(InBodyMode);
processStartTag(token);
break;
case InHeadNoscriptMode:
ASSERT(getInsertionMode() == InHeadNoscriptMode);
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 InFramesetMode:
ASSERT(getInsertionMode() == InFramesetMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == framesetTag) {
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == frameTag) {
m_tree.insertSelfClosingHTMLElementDestroyingToken(token);
return;
}
if (token->name() == noframesTag) {
processStartTagForInHead(token);
return;
}
if (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
parseError(token);
break;
case AfterFramesetMode:
case AfterAfterFramesetMode:
ASSERT(getInsertionMode() == AfterFramesetMode ||
getInsertionMode() == AfterAfterFramesetMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == noframesTag) {
processStartTagForInHead(token);
return;
}
parseError(token);
break;
case InSelectInTableMode:
ASSERT(getInsertionMode() == InSelectInTableMode);
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;
}
// Fall through
case InSelectMode:
ASSERT(getInsertionMode() == InSelectMode ||
getInsertionMode() == InSelectInTableMode);
if (token->name() == htmlTag) {
processHtmlStartTagForInBody(token);
return;
}
if (token->name() == optionTag) {
if (m_tree.currentStackItem()->hasTagName(optionTag)) {
AtomicHTMLToken endOption(HTMLToken::EndTag, optionTag.localName());
processEndTag(&endOption);
}
m_tree.insertHTMLElement(token);
return;
}
if (token->name() == optgroupTag) {
if (m_tree.currentStackItem()->hasTagName(optionTag)) {
AtomicHTMLToken endOption(HTMLToken::EndTag, optionTag.localName());
processEndTag(&endOption);
}
if (m_tree.currentStackItem()->hasTagName(optgroupTag)) {
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 (token->name() == templateTag) {
processTemplateStartTag(token);
return;
}
break;
case InTableTextMode:
defaultForInTableText();
processStartTag(token);
break;
case TextMode:
ASSERT_NOT_REACHED();
break;
case TemplateContentsMode:
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 = TemplateContentsMode;
if (token->name() == frameTag)
insertionMode = InFramesetMode;
else if (token->name() == colTag)
insertionMode = InColumnGroupMode;
else if (isCaptionColOrColgroupTag(token->name()) ||
isTableBodyContextTag(token->name()))
insertionMode = InTableMode;
else if (token->name() == trTag)
insertionMode = InTableBodyMode;
else if (isTableCellContextTag(token->name()))
insertionMode = InRowMode;
else
insertionMode = InBodyMode;
ASSERT(insertionMode != TemplateContentsMode);
ASSERT(m_templateInsertionModes.last() == TemplateContentsMode);
m_templateInsertionModes.last() = insertionMode;
setInsertionMode(insertionMode);
processStartTag(token);
break;
}
}
void HTMLTreeBuilder::processHtmlStartTagForInBody(AtomicHTMLToken* token) {
parseError(token);
if (m_tree.openElements()->hasTemplateInHTMLScope()) {
ASSERT(isParsingTemplateContents());
return;
}
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;
}
// Emit a more specific parse error based on stack contents.
DVLOG(1) << "Not implmeneted.";
setInsertionMode(AfterBodyMode);
return true;
}
void HTMLTreeBuilder::processAnyOtherEndTagForInBody(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::EndTag);
if (token->name() == menuitemTag)
UseCounter::count(m_tree.currentNode()->document(),
UseCounter::MenuItemCloseTag);
HTMLElementStack::ElementRecord* record = m_tree.openElements()->topRecord();
while (1) {
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();
}
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#parsing-main-inbody
void HTMLTreeBuilder::callTheAdoptionAgency(AtomicHTMLToken* token) {
// The adoption agency algorithm is N^2. We limit the number of iterations
// to stop from hanging the whole browser. This limit is specified in the
// adoption agency algorithm:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inbody
static const int outerIterationLimit = 8;
static const int innerIterationLimit = 3;
// 1, 2, 3 and 16 are covered by the for() loop.
for (int i = 0; i < outerIterationLimit; ++i) {
// 4.
Element* formattingElement =
m_tree.activeFormattingElements()->closestElementInScopeWithName(
token->name());
// 4.a
if (!formattingElement)
return processAnyOtherEndTagForInBody(token);
// 4.c
if ((m_tree.openElements()->contains(formattingElement)) &&
!m_tree.openElements()->inScope(formattingElement)) {
parseError(token);
// Check the stack of open elements for a more specific parse error.
DVLOG(1) << "Not implemented.";
return;
}
// 4.b
HTMLElementStack::ElementRecord* formattingElementRecord =
m_tree.openElements()->find(formattingElement);
if (!formattingElementRecord) {
parseError(token);
m_tree.activeFormattingElements()->remove(formattingElement);
return;
}
// 4.d
if (formattingElement != m_tree.currentElement())
parseError(token);
// 5.
HTMLElementStack::ElementRecord* furthestBlock =
m_tree.openElements()->furthestBlockForFormattingElement(
formattingElement);
// 6.
if (!furthestBlock) {
m_tree.openElements()->popUntilPopped(formattingElement);
m_tree.activeFormattingElements()->remove(formattingElement);
return;
}
// 7.
ASSERT(furthestBlock->isAbove(formattingElementRecord));
HTMLStackItem* commonAncestor =
formattingElementRecord->next()->stackItem();
// 8.
HTMLFormattingElementList::Bookmark bookmark =
m_tree.activeFormattingElements()->bookmarkFor(formattingElement);
// 9.
HTMLElementStack::ElementRecord* node = furthestBlock;
HTMLElementStack::ElementRecord* nextNode = node->next();
HTMLElementStack::ElementRecord* lastNode = furthestBlock;
// 9.1, 9.2, 9.3 and 9.11 are covered by the for() loop.
for (int i = 0; i < innerIterationLimit; ++i) {
// 9.4
node = nextNode;
ASSERT(node);
// Save node->next() for the next iteration in case node is deleted in
// 9.5.
nextNode = node->next();
// 9.5
if (!m_tree.activeFormattingElements()->contains(node->element())) {
m_tree.openElements()->remove(node->element());
node = 0;
continue;
}
// 9.6
if (node == formattingElementRecord)
break;
// 9.7
HTMLStackItem* newItem =
m_tree.createElementFromSavedToken(node->stackItem());
HTMLFormattingElementList::Entry* nodeEntry =
m_tree.activeFormattingElements()->find(node->element());
nodeEntry->replaceElement(newItem);
node->replaceElement(newItem);
// 9.8
if (lastNode == furthestBlock)
bookmark.moveToAfter(nodeEntry);
// 9.9
m_tree.reparent(node, lastNode);
// 9.10
lastNode = node;
}
// 10.
m_tree.insertAlreadyParsedChild(commonAncestor, lastNode);
// 11.
HTMLStackItem* newItem = m_tree.createElementFromSavedToken(
formattingElementRecord->stackItem());
// 12.
m_tree.takeAllChildren(newItem, furthestBlock);
// 13.
m_tree.reparent(furthestBlock, newItem);
// 14.
m_tree.activeFormattingElements()->swapTo(formattingElement, newItem,
bookmark);
// 15.
m_tree.openElements()->remove(formattingElement);
m_tree.openElements()->insertAbove(newItem, furthestBlock);
}
}
void HTMLTreeBuilder::resetInsertionModeAppropriately() {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#reset-the-insertion-mode-appropriately
bool last = false;
HTMLElementStack::ElementRecord* nodeRecord =
m_tree.openElements()->topRecord();
while (1) {
HTMLStackItem* item = nodeRecord->stackItem();
if (item->node() == m_tree.openElements()->rootNode()) {
last = true;
if (isParsingFragment())
item = m_fragmentContext.contextElementStackItem();
}
if (item->hasTagName(templateTag))
return setInsertionMode(m_templateInsertionModes.last());
if (item->hasTagName(selectTag)) {
if (!last) {
while (item->node() != m_tree.openElements()->rootNode() &&
!item->hasTagName(templateTag)) {
nodeRecord = nodeRecord->next();
item = nodeRecord->stackItem();
if (item->hasTagName(tableTag))
return setInsertionMode(InSelectInTableMode);
}
}
return setInsertionMode(InSelectMode);
}
if (item->hasTagName(tdTag) || item->hasTagName(thTag))
return setInsertionMode(InCellMode);
if (item->hasTagName(trTag))
return setInsertionMode(InRowMode);
if (item->hasTagName(tbodyTag) || item->hasTagName(theadTag) ||
item->hasTagName(tfootTag))
return setInsertionMode(InTableBodyMode);
if (item->hasTagName(captionTag))
return setInsertionMode(InCaptionMode);
if (item->hasTagName(colgroupTag)) {
return setInsertionMode(InColumnGroupMode);
}
if (item->hasTagName(tableTag))
return setInsertionMode(InTableMode);
if (item->hasTagName(headTag)) {
if (!m_fragmentContext.fragment() ||
m_fragmentContext.contextElement() != item->node())
return setInsertionMode(InHeadMode);
return setInsertionMode(InBodyMode);
}
if (item->hasTagName(bodyTag))
return setInsertionMode(InBodyMode);
if (item->hasTagName(framesetTag)) {
return setInsertionMode(InFramesetMode);
}
if (item->hasTagName(htmlTag)) {
if (m_tree.headStackItem())
return setInsertionMode(AfterHeadMode);
ASSERT(isParsingFragment());
return setInsertionMode(BeforeHeadMode);
}
if (last) {
ASSERT(isParsingFragment());
return setInsertionMode(InBodyMode);
}
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(InTableMode);
return;
}
if (token->name() == tableTag) {
// FIXME: This is slow.
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(getInsertionMode() == InTableBodyMode);
processEndTag(token);
return;
}
if (isTableBodyContextTag(token->name())) {
if (!m_tree.openElements()->inTableScope(token->name())) {
parseError(token);
return;
}
processFakeEndTag(trTag);
ASSERT(getInsertionMode() == InTableBodyMode);
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(InRowMode);
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())) {
ASSERT(isTableBodyContextTag(token->name()) ||
m_tree.openElements()->inTableScope(templateTag) ||
isParsingFragment());
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 && !isParsingTemplateContents()) {
Element* node = m_tree.takeForm();
if (!node || !m_tree.openElements()->inScope(node)) {
parseError(token);
return;
}
m_tree.generateImpliedEndTags();
if (m_tree.currentElement() != node)
parseError(token);
m_tree.openElements()->remove(node);
}
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 (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
processAnyOtherEndTagForInBody(token);
}
bool HTMLTreeBuilder::processCaptionEndTagForInCaption() {
if (!m_tree.openElements()->inTableScope(captionTag.localName())) {
ASSERT(isParsingFragment());
// FIXME: parse error
return false;
}
m_tree.generateImpliedEndTags();
// FIXME: parse error if (!m_tree.currentStackItem()->hasTagName(captionTag))
m_tree.openElements()->popUntilPopped(captionTag.localName());
m_tree.activeFormattingElements()->clearToLastMarker();
setInsertionMode(InTableMode);
return true;
}
bool HTMLTreeBuilder::processTrEndTagForInRow() {
if (!m_tree.openElements()->inTableScope(trTag)) {
ASSERT(isParsingFragmentOrTemplateContents());
// FIXME: parse error
return false;
}
m_tree.openElements()->popUntilTableRowScopeMarker();
ASSERT(m_tree.currentStackItem()->hasTagName(trTag));
m_tree.openElements()->pop();
setInsertionMode(InTableBodyMode);
return true;
}
bool HTMLTreeBuilder::processTableEndTagForInTable() {
if (!m_tree.openElements()->inTableScope(tableTag)) {
ASSERT(isParsingFragmentOrTemplateContents());
// FIXME: parse error.
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);
// Is this redirection necessary here?
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
processEndTagForInBody(token);
}
void HTMLTreeBuilder::processEndTag(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::EndTag);
switch (getInsertionMode()) {
case InitialMode:
ASSERT(getInsertionMode() == InitialMode);
defaultForInitial();
// Fall through.
case BeforeHTMLMode:
ASSERT(getInsertionMode() == BeforeHTMLMode);
if (token->name() != headTag && token->name() != bodyTag &&
token->name() != htmlTag && token->name() != brTag) {
parseError(token);
return;
}
defaultForBeforeHTML();
// Fall through.
case BeforeHeadMode:
ASSERT(getInsertionMode() == BeforeHeadMode);
if (token->name() != headTag && token->name() != bodyTag &&
token->name() != htmlTag && token->name() != brTag) {
parseError(token);
return;
}
defaultForBeforeHead();
// Fall through.
case InHeadMode:
ASSERT(getInsertionMode() == InHeadMode);
// FIXME: This case should be broken out into processEndTagForInHead,
// because other end tag cases now refer to it ("process the token for
// using the rules of the "in head" insertion mode"). but because the
// logic falls through to AfterHeadMode, that gets a little messy.
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
if (token->name() == headTag) {
m_tree.openElements()->popHTMLHeadElement();
setInsertionMode(AfterHeadMode);
return;
}
if (token->name() != bodyTag && token->name() != htmlTag &&
token->name() != brTag) {
parseError(token);
return;
}
defaultForInHead();
// Fall through.
case AfterHeadMode:
ASSERT(getInsertionMode() == AfterHeadMode);
if (token->name() != bodyTag && token->name() != htmlTag &&
token->name() != brTag) {
parseError(token);
return;
}
defaultForAfterHead();
// Fall through
case InBodyMode:
ASSERT(getInsertionMode() == InBodyMode);
processEndTagForInBody(token);
break;
case InTableMode:
ASSERT(getInsertionMode() == InTableMode);
processEndTagForInTable(token);
break;
case InCaptionMode:
ASSERT(getInsertionMode() == InCaptionMode);
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 InColumnGroupMode:
ASSERT(getInsertionMode() == InColumnGroupMode);
if (token->name() == colgroupTag) {
processColgroupEndTagForInColumnGroup();
return;
}
if (token->name() == colTag) {
parseError(token);
return;
}
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
if (!processColgroupEndTagForInColumnGroup()) {
ASSERT(isParsingFragmentOrTemplateContents());
return;
}
processEndTag(token);
break;
case InRowMode:
ASSERT(getInsertionMode() == InRowMode);
processEndTagForInRow(token);
break;
case InCellMode:
ASSERT(getInsertionMode() == InCellMode);
processEndTagForInCell(token);
break;
case InTableBodyMode:
ASSERT(getInsertionMode() == InTableBodyMode);
processEndTagForInTableBody(token);
break;
case AfterBodyMode:
ASSERT(getInsertionMode() == AfterBodyMode);
if (token->name() == htmlTag) {
if (isParsingFragment()) {
parseError(token);
return;
}
setInsertionMode(AfterAfterBodyMode);
return;
}
// Fall through.
case AfterAfterBodyMode:
ASSERT(getInsertionMode() == AfterBodyMode ||
getInsertionMode() == AfterAfterBodyMode);
parseError(token);
setInsertionMode(InBodyMode);
processEndTag(token);
break;
case InHeadNoscriptMode:
ASSERT(getInsertionMode() == InHeadNoscriptMode);
if (token->name() == noscriptTag) {
ASSERT(m_tree.currentStackItem()->hasTagName(noscriptTag));
m_tree.openElements()->pop();
ASSERT(m_tree.currentStackItem()->hasTagName(headTag));
setInsertionMode(InHeadMode);
return;
}
if (token->name() != brTag) {
parseError(token);
return;
}
defaultForInHeadNoscript();
processToken(token);
break;
case TextMode:
if (token->name() == scriptTag &&
m_tree.currentStackItem()->hasTagName(scriptTag)) {
// Pause ourselves so that parsing stops until the script can be
// processed by the caller.
if (scriptingContentIsAllowed(m_tree.getParserContentPolicy()))
m_scriptToProcess = m_tree.currentElement();
m_tree.openElements()->pop();
setInsertionMode(m_originalInsertionMode);
if (m_parser->tokenizer()) {
// We must set the tokenizer's state to DataState explicitly if the
// tokenizer didn't have a chance to.
m_parser->tokenizer()->setState(HTMLTokenizer::DataState);
}
return;
}
m_tree.openElements()->pop();
setInsertionMode(m_originalInsertionMode);
break;
case InFramesetMode:
ASSERT(getInsertionMode() == InFramesetMode);
if (token->name() == framesetTag) {
bool ignoreFramesetForFragmentParsing = m_tree.currentIsRootNode();
ignoreFramesetForFragmentParsing =
ignoreFramesetForFragmentParsing ||
m_tree.openElements()->hasTemplateInHTMLScope();
if (ignoreFramesetForFragmentParsing) {
ASSERT(isParsingFragmentOrTemplateContents());
parseError(token);
return;
}
m_tree.openElements()->pop();
if (!isParsingFragment() &&
!m_tree.currentStackItem()->hasTagName(framesetTag))
setInsertionMode(AfterFramesetMode);
return;
}
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
break;
case AfterFramesetMode:
ASSERT(getInsertionMode() == AfterFramesetMode);
if (token->name() == htmlTag) {
setInsertionMode(AfterAfterFramesetMode);
return;
}
// Fall through.
case AfterAfterFramesetMode:
ASSERT(getInsertionMode() == AfterFramesetMode ||
getInsertionMode() == AfterAfterFramesetMode);
parseError(token);
break;
case InSelectInTableMode:
ASSERT(getInsertionMode() == InSelectInTableMode);
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;
}
// Fall through.
case InSelectMode:
ASSERT(getInsertionMode() == InSelectMode ||
getInsertionMode() == InSelectInTableMode);
if (token->name() == optgroupTag) {
if (m_tree.currentStackItem()->hasTagName(optionTag) &&
m_tree.oneBelowTop() &&
m_tree.oneBelowTop()->hasTagName(optgroupTag))
processFakeEndTag(optionTag);
if (m_tree.currentStackItem()->hasTagName(optgroupTag)) {
m_tree.openElements()->pop();
return;
}
parseError(token);
return;
}
if (token->name() == optionTag) {
if (m_tree.currentStackItem()->hasTagName(optionTag)) {
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 (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
break;
case InTableTextMode:
defaultForInTableText();
processEndTag(token);
break;
case TemplateContentsMode:
if (token->name() == templateTag) {
processTemplateEndTag(token);
return;
}
break;
}
}
void HTMLTreeBuilder::processComment(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::Comment);
if (m_insertionMode == InitialMode || m_insertionMode == BeforeHTMLMode ||
m_insertionMode == AfterAfterBodyMode ||
m_insertionMode == AfterAfterFramesetMode) {
m_tree.insertCommentOnDocument(token);
return;
}
if (m_insertionMode == AfterBodyMode) {
m_tree.insertCommentOnHTMLHtmlElement(token);
return;
}
if (m_insertionMode == InTableTextMode) {
defaultForInTableText();
processComment(token);
return;
}
m_tree.insertComment(token);
}
void HTMLTreeBuilder::processCharacter(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::Character);
CharacterTokenBuffer buffer(token);
processCharacterBuffer(buffer);
}
void HTMLTreeBuilder::processCharacterBuffer(CharacterTokenBuffer& buffer) {
ReprocessBuffer:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#parsing-main-inbody
// Note that this logic is different than the generic \r\n collapsing
// handled in the input stream preprocessor. This logic is here as an
// "authoring convenience" so folks can write:
//
// <pre>
// lorem ipsum
// lorem ipsum
// </pre>
//
// without getting an extra newline at the start of their <pre> element.
if (m_shouldSkipLeadingNewline) {
m_shouldSkipLeadingNewline = false;
buffer.skipAtMostOneLeadingNewline();
if (buffer.isEmpty())
return;
}
switch (getInsertionMode()) {
case InitialMode: {
ASSERT(getInsertionMode() == InitialMode);
buffer.skipLeadingWhitespace();
if (buffer.isEmpty())
return;
defaultForInitial();
// Fall through.
}
case BeforeHTMLMode: {
ASSERT(getInsertionMode() == BeforeHTMLMode);
buffer.skipLeadingWhitespace();
if (buffer.isEmpty())
return;
defaultForBeforeHTML();
if (m_parser->isStopped()) {
buffer.skipRemaining();
return;
}
// Fall through.
}
case BeforeHeadMode: {
ASSERT(getInsertionMode() == BeforeHeadMode);
buffer.skipLeadingWhitespace();
if (buffer.isEmpty())
return;
defaultForBeforeHead();
// Fall through.
}
case InHeadMode: {
ASSERT(getInsertionMode() == InHeadMode);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
defaultForInHead();
// Fall through.
}
case AfterHeadMode: {
ASSERT(getInsertionMode() == AfterHeadMode);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
defaultForAfterHead();
// Fall through.
}
case InBodyMode:
case InCaptionMode:
case TemplateContentsMode:
case InCellMode: {
ASSERT(getInsertionMode() == InBodyMode ||
getInsertionMode() == InCaptionMode ||
getInsertionMode() == InCellMode ||
getInsertionMode() == TemplateContentsMode);
processCharacterBufferForInBody(buffer);
break;
}
case InTableMode:
case InTableBodyMode:
case InRowMode: {
ASSERT(getInsertionMode() == InTableMode ||
getInsertionMode() == InTableBodyMode ||
getInsertionMode() == InRowMode);
ASSERT(m_pendingTableCharacters.isEmpty());
if (m_tree.currentStackItem()->isElementNode() &&
(m_tree.currentStackItem()->hasTagName(tableTag) ||
m_tree.currentStackItem()->hasTagName(tbodyTag) ||
m_tree.currentStackItem()->hasTagName(tfootTag) ||
m_tree.currentStackItem()->hasTagName(theadTag) ||
m_tree.currentStackItem()->hasTagName(trTag))) {
m_originalInsertionMode = m_insertionMode;
setInsertionMode(InTableTextMode);
// Note that we fall through to the InTableTextMode case below.
} else {
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree);
processCharacterBufferForInBody(buffer);
break;
}
// Fall through.
}
case InTableTextMode: {
buffer.giveRemainingTo(m_pendingTableCharacters);
break;
}
case InColumnGroupMode: {
ASSERT(getInsertionMode() == InColumnGroupMode);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
if (!processColgroupEndTagForInColumnGroup()) {
ASSERT(isParsingFragmentOrTemplateContents());
// The spec tells us to drop these characters on the floor.
buffer.skipLeadingNonWhitespace();
if (buffer.isEmpty())
return;
}
goto ReprocessBuffer;
}
case AfterBodyMode:
case AfterAfterBodyMode: {
ASSERT(getInsertionMode() == AfterBodyMode ||
getInsertionMode() == AfterAfterBodyMode);
// FIXME: parse error
setInsertionMode(InBodyMode);
goto ReprocessBuffer;
}
case TextMode: {
ASSERT(getInsertionMode() == TextMode);
m_tree.insertTextNode(buffer.takeRemaining());
break;
}
case InHeadNoscriptMode: {
ASSERT(getInsertionMode() == InHeadNoscriptMode);
String leadingWhitespace = buffer.takeLeadingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
if (buffer.isEmpty())
return;
defaultForInHeadNoscript();
goto ReprocessBuffer;
}
case InFramesetMode:
case AfterFramesetMode: {
ASSERT(getInsertionMode() == InFramesetMode ||
getInsertionMode() == AfterFramesetMode ||
getInsertionMode() == AfterAfterFramesetMode);
String leadingWhitespace = buffer.takeRemainingWhitespace();
if (!leadingWhitespace.isEmpty())
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
// FIXME: We should generate a parse error if we skipped over any
// non-whitespace characters.
break;
}
case InSelectInTableMode:
case InSelectMode: {
ASSERT(getInsertionMode() == InSelectMode ||
getInsertionMode() == InSelectInTableMode);
m_tree.insertTextNode(buffer.takeRemaining());
break;
}
case AfterAfterFramesetMode: {
String leadingWhitespace = buffer.takeRemainingWhitespace();
if (!leadingWhitespace.isEmpty()) {
m_tree.reconstructTheActiveFormattingElements();
m_tree.insertTextNode(leadingWhitespace, AllWhitespace);
}
// FIXME: We should generate a parse error if we skipped over any
// non-whitespace characters.
break;
}
}
}
void HTMLTreeBuilder::processCharacterBufferForInBody(
CharacterTokenBuffer& buffer) {
m_tree.reconstructTheActiveFormattingElements();
const String& characters = buffer.takeRemaining();
m_tree.insertTextNode(characters);
if (m_framesetOk && !isAllWhitespaceOrReplacementCharacters(characters))
m_framesetOk = false;
}
void HTMLTreeBuilder::processEndOfFile(AtomicHTMLToken* token) {
ASSERT(token->type() == HTMLToken::EndOfFile);
switch (getInsertionMode()) {
case InitialMode:
ASSERT(getInsertionMode() == InitialMode);
defaultForInitial();
// Fall through.
case BeforeHTMLMode:
ASSERT(getInsertionMode() == BeforeHTMLMode);
defaultForBeforeHTML();
// Fall through.
case BeforeHeadMode:
ASSERT(getInsertionMode() == BeforeHeadMode);
defaultForBeforeHead();
// Fall through.
case InHeadMode:
ASSERT(getInsertionMode() == InHeadMode);
defaultForInHead();
// Fall through.
case AfterHeadMode:
ASSERT(getInsertionMode() == AfterHeadMode);
defaultForAfterHead();
// Fall through
case InBodyMode:
case InCellMode:
case InCaptionMode:
case InRowMode:
ASSERT(getInsertionMode() == InBodyMode ||
getInsertionMode() == InCellMode ||
getInsertionMode() == InCaptionMode ||
getInsertionMode() == InRowMode ||
getInsertionMode() == TemplateContentsMode);
// Emit parse error based on what elements are still open.
DVLOG(1) << "Not implemented.";
if (!m_templateInsertionModes.isEmpty() &&
processEndOfFileForInTemplateContents(token))
return;
break;
case AfterBodyMode:
case AfterAfterBodyMode:
ASSERT(getInsertionMode() == AfterBodyMode ||
getInsertionMode() == AfterAfterBodyMode);
break;
case InHeadNoscriptMode:
ASSERT(getInsertionMode() == InHeadNoscriptMode);
defaultForInHeadNoscript();
processEndOfFile(token);
return;
case AfterFramesetMode:
case AfterAfterFramesetMode:
ASSERT(getInsertionMode() == AfterFramesetMode ||
getInsertionMode() == AfterAfterFramesetMode);
break;
case InColumnGroupMode:
if (m_tree.currentIsRootNode()) {
ASSERT(isParsingFragment());
return; // FIXME: Should we break here instead of returning?
}
ASSERT(m_tree.currentNode()->hasTagName(colgroupTag) ||
isHTMLTemplateElement(m_tree.currentNode()));
processColgroupEndTagForInColumnGroup();
// Fall through
case InFramesetMode:
case InTableMode:
case InTableBodyMode:
case InSelectInTableMode:
case InSelectMode:
ASSERT(getInsertionMode() == InSelectMode ||
getInsertionMode() == InSelectInTableMode ||
getInsertionMode() == InTableMode ||
getInsertionMode() == InFramesetMode ||
getInsertionMode() == InTableBodyMode ||
getInsertionMode() == InColumnGroupMode);
if (m_tree.currentNode() != m_tree.openElements()->rootNode())
parseError(token);
if (!m_templateInsertionModes.isEmpty() &&
processEndOfFileForInTemplateContents(token))
return;
break;
case InTableTextMode:
defaultForInTableText();
processEndOfFile(token);
return;
case TextMode:
parseError(token);
if (m_tree.currentStackItem()->hasTagName(scriptTag)) {
// Mark the script element as "already started".
DVLOG(1) << "Not implemented.";
}
m_tree.openElements()->pop();
ASSERT(m_originalInsertionMode != TextMode);
setInsertionMode(m_originalInsertionMode);
processEndOfFile(token);
return;
case TemplateContentsMode:
if (processEndOfFileForInTemplateContents(token))
return;
break;
}
m_tree.processEndOfFile();
}
void HTMLTreeBuilder::defaultForInitial() {
DVLOG(1) << "Not implemented.";
m_tree.setDefaultCompatibilityMode();
// FIXME: parse error
setInsertionMode(BeforeHTMLMode);
}
void HTMLTreeBuilder::defaultForBeforeHTML() {
AtomicHTMLToken startHTML(HTMLToken::StartTag, htmlTag.localName());
m_tree.insertHTMLHtmlStartTagBeforeHTML(&startHTML);
setInsertionMode(BeforeHeadMode);
}
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)) {
// FIXME: parse error
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.insertSelfClosingHTMLElementDestroyingToken(token);
// Note: The custom processing for the <meta> tag is done in
// HTMLMetaElement::process().
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(InHeadNoscriptMode);
return true;
}
if (token->name() == noframesTag || token->name() == styleTag) {
processGenericRawTextStartTag(token);
return true;
}
if (token->name() == scriptTag) {
processScriptStartTag(token);
return true;
}
if (token->name() == templateTag) {
processTemplateStartTag(token);
return true;
}
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(TextMode);
}
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(TextMode);
}
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(TextMode);
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#tree-construction
bool HTMLTreeBuilder::shouldProcessTokenInForeignContent(
AtomicHTMLToken* token) {
if (m_tree.isEmpty())
return false;
HTMLStackItem* adjustedCurrentNode = adjustedCurrentStackItem();
if (adjustedCurrentNode->isInHTMLNamespace())
return false;
if (HTMLElementStack::isMathMLTextIntegrationPoint(adjustedCurrentNode)) {
if (token->type() == HTMLToken::StartTag &&
token->name() != MathMLNames::mglyphTag &&
token->name() != MathMLNames::malignmarkTag)
return false;
if (token->type() == HTMLToken::Character)
return false;
}
if (adjustedCurrentNode->hasTagName(MathMLNames::annotation_xmlTag) &&
token->type() == HTMLToken::StartTag && token->name() == SVGNames::svgTag)
return false;
if (HTMLElementStack::isHTMLIntegrationPoint(adjustedCurrentNode)) {
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) {
if (token->type() == HTMLToken::Character) {
const String& characters = token->characters();
m_tree.insertTextNode(characters);
if (m_framesetOk && !isAllWhitespaceOrReplacementCharacters(characters))
m_framesetOk = false;
return;
}
m_tree.flush(FlushAlways);
HTMLStackItem* adjustedCurrentNode = adjustedCurrentStackItem();
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 =
adjustedCurrentNode->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 (adjustedCurrentNode->namespaceURI() == SVGNames::svgNamespaceURI)
adjustSVGTagNameCase(token);
if (token->name() == SVGNames::scriptTag &&
m_tree.currentStackItem()->hasTagName(SVGNames::scriptTag)) {
if (scriptingContentIsAllowed(m_tree.getParserContentPolicy()))
m_scriptToProcess = m_tree.currentElement();
m_tree.openElements()->pop();
return;
}
if (!m_tree.currentStackItem()->isInHTMLNamespace()) {
// FIXME: This code just wants an Element* iterator, instead of an
// ElementRecord*
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;
}
}
// Otherwise, process the token according to the rules given in the
// section corresponding to the current insertion mode in HTML content.
processEndTag(token);
break;
}
case HTMLToken::Comment:
m_tree.insertComment(token);
break;
case HTMLToken::Character:
case HTMLToken::EndOfFile:
ASSERT_NOT_REACHED();
break;
}
}
void HTMLTreeBuilder::finished() {
if (isParsingFragment())
return;
ASSERT(m_templateInsertionModes.isEmpty());
ASSERT(m_isAttached);
// Warning, this may detach the parser. Do not do anything else after this.
m_tree.finishedParsing();
}
void HTMLTreeBuilder::parseError(AtomicHTMLToken*) {}
#ifndef NDEBUG
const char* HTMLTreeBuilder::toString(HTMLTreeBuilder::InsertionMode mode) {
switch (mode) {
#define DEFINE_STRINGIFY(mode) \
case mode: \
return #mode;
DEFINE_STRINGIFY(InitialMode)
DEFINE_STRINGIFY(BeforeHTMLMode)
DEFINE_STRINGIFY(BeforeHeadMode)
DEFINE_STRINGIFY(InHeadMode)
DEFINE_STRINGIFY(InHeadNoscriptMode)
DEFINE_STRINGIFY(AfterHeadMode)
DEFINE_STRINGIFY(TemplateContentsMode)
DEFINE_STRINGIFY(InBodyMode)
DEFINE_STRINGIFY(TextMode)
DEFINE_STRINGIFY(InTableMode)
DEFINE_STRINGIFY(InTableTextMode)
DEFINE_STRINGIFY(InCaptionMode)
DEFINE_STRINGIFY(InColumnGroupMode)
DEFINE_STRINGIFY(InTableBodyMode)
DEFINE_STRINGIFY(InRowMode)
DEFINE_STRINGIFY(InCellMode)
DEFINE_STRINGIFY(InSelectMode)
DEFINE_STRINGIFY(InSelectInTableMode)
DEFINE_STRINGIFY(AfterBodyMode)
DEFINE_STRINGIFY(InFramesetMode)
DEFINE_STRINGIFY(AfterFramesetMode)
DEFINE_STRINGIFY(AfterAfterBodyMode)
DEFINE_STRINGIFY(AfterAfterFramesetMode)
#undef DEFINE_STRINGIFY
}
return "<unknown>";
}
#endif
} // namespace blink