blob: 4f7b921895488e3075b9d5764cfe5026f104f1ea [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 "third_party/blink/renderer/core/html/parser/html_tree_builder.h"
#include <memory>
#include "base/macros.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/element_traversal.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/html_template_element.h"
#include "third_party/blink/renderer/core/html/parser/atomic_html_token.h"
#include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/html/parser/html_stack_item.h"
#include "third_party/blink/renderer/core/html/parser/html_token.h"
#include "third_party/blink/renderer/core/html/parser/html_tokenizer.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/mathml_names.h"
#include "third_party/blink/renderer/core/svg_names.h"
#include "third_party/blink/renderer/core/xlink_names.h"
#include "third_party/blink/renderer/core/xml_names.h"
#include "third_party/blink/renderer/core/xmlns_names.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/wtf/text/character_names.h"
#include "third_party/blink/renderer/platform/wtf/text/character_visitor.h"
#include "third_party/blink/renderer/platform/wtf/text/string_buffer.h"
namespace blink {
namespace {
inline bool IsHTMLSpaceOrReplacementCharacter(UChar character) {
return IsHTMLSpace<UChar>(character) || character == kReplacementCharacter;
}
}
static TextPosition UninitializedPositionValue1() {
return TextPosition(OrdinalNumber::FromOneBasedInt(-1),
OrdinalNumber::First());
}
static inline bool IsAllWhitespace(const StringView& string_view) {
return string_view.IsAllSpecialCharacters<IsHTMLSpace<UChar>>();
}
static inline bool IsAllWhitespaceOrReplacementCharacters(
const StringView& string_view) {
return string_view
.IsAllSpecialCharacters<IsHTMLSpaceOrReplacementCharacter>();
}
static bool IsNumberedHeaderTag(const AtomicString& tag_name) {
return tag_name == html_names::kH1Tag || tag_name == html_names::kH2Tag ||
tag_name == html_names::kH3Tag || tag_name == html_names::kH4Tag ||
tag_name == html_names::kH5Tag || tag_name == html_names::kH6Tag;
}
static bool IsCaptionColOrColgroupTag(const AtomicString& tag_name) {
return tag_name == html_names::kCaptionTag ||
tag_name == html_names::kColTag ||
tag_name == html_names::kColgroupTag;
}
static bool IsTableCellContextTag(const AtomicString& tag_name) {
return tag_name == html_names::kThTag || tag_name == html_names::kTdTag;
}
static bool IsTableBodyContextTag(const AtomicString& tag_name) {
return tag_name == html_names::kTbodyTag ||
tag_name == html_names::kTfootTag || tag_name == html_names::kTheadTag;
}
static bool IsNonAnchorNonNobrFormattingTag(const AtomicString& tag_name) {
return tag_name == html_names::kBTag || tag_name == html_names::kBigTag ||
tag_name == html_names::kCodeTag || tag_name == html_names::kEmTag ||
tag_name == html_names::kFontTag || tag_name == html_names::kITag ||
tag_name == html_names::kSTag || tag_name == html_names::kSmallTag ||
tag_name == html_names::kStrikeTag ||
tag_name == html_names::kStrongTag || tag_name == html_names::kTtTag ||
tag_name == html_names::kUTag;
}
static bool IsNonAnchorFormattingTag(const AtomicString& tag_name) {
return tag_name == html_names::kNobrTag ||
IsNonAnchorNonNobrFormattingTag(tag_name);
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#formatting
static bool IsFormattingTag(const AtomicString& tag_name) {
return tag_name == html_names::kATag || IsNonAnchorFormattingTag(tag_name);
}
class HTMLTreeBuilder::CharacterTokenBuffer {
public:
explicit CharacterTokenBuffer(AtomicHTMLToken* token)
: characters_(token->Characters().Impl()),
current_(0),
end_(token->Characters().length()) {
DCHECK(!IsEmpty());
}
explicit CharacterTokenBuffer(const String& characters)
: characters_(characters.Impl()), current_(0), end_(characters.length()) {
DCHECK(!IsEmpty());
}
~CharacterTokenBuffer() { DCHECK(IsEmpty()); }
bool IsEmpty() const { return current_ == end_; }
void SkipAtMostOneLeadingNewline() {
DCHECK(!IsEmpty());
if ((*characters_)[current_] == '\n')
++current_;
}
void SkipLeadingWhitespace() { SkipLeading<IsHTMLSpace<UChar>>(); }
StringView TakeLeadingWhitespace() {
return TakeLeading<IsHTMLSpace<UChar>>();
}
void SkipLeadingNonWhitespace() { SkipLeading<IsNotHTMLSpace<UChar>>(); }
void SkipRemaining() { current_ = end_; }
StringView TakeRemaining() {
DCHECK(!IsEmpty());
unsigned start = current_;
current_ = end_;
return StringView(characters_.get(), start, end_ - start);
}
void GiveRemainingTo(StringBuilder& recipient) {
WTF::VisitCharacters(*characters_, [&](const auto* chars, unsigned length) {
recipient.Append(chars + current_, end_ - current_);
});
current_ = end_;
}
String TakeRemainingWhitespace() {
DCHECK(!IsEmpty());
const unsigned start = current_;
current_ = end_; // One way or another, we're taking everything!
unsigned length = 0;
for (unsigned i = start; i < end_; ++i) {
if (IsHTMLSpace<UChar>((*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 - end_) // It's all whitespace.
return String(characters_->Substring(start, start - end_));
// All HTML spaces are ASCII.
StringBuffer<LChar> result(length);
unsigned j = 0;
for (unsigned i = start; i < end_; ++i) {
UChar c = (*characters_)[i];
if (IsHTMLSpace(c))
result[j++] = static_cast<LChar>(c);
}
DCHECK_EQ(j, length);
return String::Adopt(result);
}
private:
template <bool characterPredicate(UChar)>
void SkipLeading() {
DCHECK(!IsEmpty());
while (characterPredicate((*characters_)[current_])) {
if (++current_ == end_)
return;
}
}
template <bool characterPredicate(UChar)>
StringView TakeLeading() {
DCHECK(!IsEmpty());
const unsigned start = current_;
SkipLeading<characterPredicate>();
return StringView(characters_.get(), start, current_ - start);
}
scoped_refptr<StringImpl> characters_;
unsigned current_;
unsigned end_;
DISALLOW_COPY_AND_ASSIGN(CharacterTokenBuffer);
};
HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser* parser,
Document& document,
ParserContentPolicy parser_content_policy,
const HTMLParserOptions& options,
bool include_shadow_roots)
: frameset_ok_(true),
tree_(parser->ReentryPermit(), document, parser_content_policy),
insertion_mode_(kInitialMode),
original_insertion_mode_(kInitialMode),
should_skip_leading_newline_(false),
include_shadow_roots_(include_shadow_roots),
parser_(parser),
script_to_process_start_position_(UninitializedPositionValue1()),
options_(options) {}
HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser* parser,
DocumentFragment* fragment,
Element* context_element,
ParserContentPolicy parser_content_policy,
const HTMLParserOptions& options,
bool include_shadow_roots)
: HTMLTreeBuilder(parser,
fragment->GetDocument(),
parser_content_policy,
options,
include_shadow_roots) {
DCHECK(IsMainThread());
DCHECK(context_element);
tree_.InitFragmentParsing(fragment, context_element);
fragment_context_.Init(fragment, context_element);
// 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.
tree_.OpenElements()->PushRootNode(MakeGarbageCollected<HTMLStackItem>(
fragment, HTMLStackItem::kItemForDocumentFragmentNode));
if (IsA<HTMLTemplateElement>(*context_element))
template_insertion_modes_.push_back(kTemplateContentsMode);
ResetInsertionModeAppropriately();
}
HTMLTreeBuilder::~HTMLTreeBuilder() = default;
void HTMLTreeBuilder::FragmentParsingContext::Init(DocumentFragment* fragment,
Element* context_element) {
DCHECK(fragment);
DCHECK(!fragment->HasChildren());
fragment_ = fragment;
context_element_stack_item_ = MakeGarbageCollected<HTMLStackItem>(
context_element, HTMLStackItem::kItemForContextElement);
}
void HTMLTreeBuilder::FragmentParsingContext::Trace(Visitor* visitor) const {
visitor->Trace(fragment_);
visitor->Trace(context_element_stack_item_);
}
void HTMLTreeBuilder::Trace(Visitor* visitor) const {
visitor->Trace(fragment_context_);
visitor->Trace(tree_);
visitor->Trace(parser_);
visitor->Trace(script_to_process_);
}
void HTMLTreeBuilder::Detach() {
#if DCHECK_IS_ON()
// This call makes little sense in fragment mode, but for consistency
// DocumentParser expects Detach() to always be called before it's destroyed.
is_attached_ = false;
#endif
// HTMLConstructionSite might be on the callstack when Detach() is called
// otherwise we'd just call tree_.Clear() here instead.
tree_.Detach();
}
Element* HTMLTreeBuilder::TakeScriptToProcess(
TextPosition& script_start_position) {
DCHECK(script_to_process_);
DCHECK(!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.
script_start_position = script_to_process_start_position_;
script_to_process_start_position_ = UninitializedPositionValue1();
return script_to_process_.Release();
}
void HTMLTreeBuilder::ConstructTree(AtomicHTMLToken* token) {
RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(),
RuntimeCallStats::CounterId::kConstructTree);
if (ShouldProcessTokenInForeignContent(token))
ProcessTokenInForeignContent(token);
else
ProcessToken(token);
if (parser_->Tokenizer()) {
bool in_foreign_content = false;
if (!tree_.IsEmpty()) {
HTMLStackItem* adjusted_current_node = AdjustedCurrentStackItem();
in_foreign_content =
!adjusted_current_node->IsInHTMLNamespace() &&
!HTMLElementStack::IsHTMLIntegrationPoint(adjusted_current_node) &&
!HTMLElementStack::IsMathMLTextIntegrationPoint(
adjusted_current_node);
}
parser_->Tokenizer()->SetForceNullCharacterReplacement(
insertion_mode_ == kTextMode || in_foreign_content);
parser_->Tokenizer()->SetShouldAllowCDATA(in_foreign_content);
}
tree_.ExecuteQueuedTasks();
// We might be detached now.
}
void HTMLTreeBuilder::ProcessToken(AtomicHTMLToken* token) {
if (token->GetType() == HTMLToken::kCharacter) {
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.
tree_.Flush(kFlushAlways);
should_skip_leading_newline_ = false;
switch (token->GetType()) {
case HTMLToken::kUninitialized:
case HTMLToken::kCharacter:
NOTREACHED();
break;
case HTMLToken::DOCTYPE:
ProcessDoctypeToken(token);
break;
case HTMLToken::kStartTag:
ProcessStartTag(token);
break;
case HTMLToken::kEndTag:
ProcessEndTag(token);
break;
case HTMLToken::kComment:
ProcessComment(token);
break;
case HTMLToken::kEndOfFile:
ProcessEndOfFile(token);
break;
}
}
void HTMLTreeBuilder::ProcessDoctypeToken(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::DOCTYPE);
if (insertion_mode_ == kInitialMode) {
tree_.InsertDoctype(token);
SetInsertionMode(kBeforeHTMLMode);
return;
}
if (insertion_mode_ == kInTableTextMode) {
DefaultForInTableText();
ProcessDoctypeToken(token);
return;
}
ParseError(token);
}
void HTMLTreeBuilder::ProcessFakeStartTag(const QualifiedName& tag_name,
const Vector<Attribute>& attributes) {
// FIXME: We'll need a fancier conversion than just "localName" for SVG/MathML
// tags.
AtomicHTMLToken fake_token(HTMLToken::kStartTag, tag_name.LocalName(),
attributes);
ProcessStartTag(&fake_token);
}
void HTMLTreeBuilder::ProcessFakeEndTag(const AtomicString& tag_name) {
AtomicHTMLToken fake_token(HTMLToken::kEndTag, tag_name);
ProcessEndTag(&fake_token);
}
void HTMLTreeBuilder::ProcessFakeEndTag(const QualifiedName& tag_name) {
// FIXME: We'll need a fancier conversion than just "localName" for SVG/MathML
// tags.
ProcessFakeEndTag(tag_name.LocalName());
}
void HTMLTreeBuilder::ProcessFakePEndTagIfPInButtonScope() {
if (!tree_.OpenElements()->InButtonScope(html_names::kPTag.LocalName()))
return;
AtomicHTMLToken end_p(HTMLToken::kEndTag, html_names::kPTag.LocalName());
ProcessEndTag(&end_p);
}
namespace {
bool IsLi(const HTMLStackItem* item) {
return item->HasTagName(html_names::kLiTag);
}
bool IsDdOrDt(const HTMLStackItem* item) {
return item->HasTagName(html_names::kDdTag) ||
item->HasTagName(html_names::kDtTag);
}
} // namespace
template <bool shouldClose(const HTMLStackItem*)>
void HTMLTreeBuilder::ProcessCloseWhenNestedTag(AtomicHTMLToken* token) {
frameset_ok_ = false;
HTMLElementStack::ElementRecord* node_record =
tree_.OpenElements()->TopRecord();
while (1) {
HTMLStackItem* item = node_record->StackItem();
if (shouldClose(item)) {
DCHECK(item->IsElementNode());
ProcessFakeEndTag(item->LocalName());
break;
}
if (item->IsSpecialNode() && !item->HasTagName(html_names::kAddressTag) &&
!item->HasTagName(html_names::kDivTag) &&
!item->HasTagName(html_names::kPTag))
break;
node_record = node_record->Next();
}
ProcessFakePEndTagIfPInButtonScope();
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& local_name = name.LocalName();
AtomicString lowered_local_name = local_name.LowerASCII();
if (lowered_local_name != local_name)
map->insert(lowered_local_name, name);
}
}
// "Any other start tag" bullet in
// https://html.spec.whatwg.org/C/#parsing-main-inforeign
static void AdjustSVGTagNameCase(AtomicHTMLToken* token) {
static PrefixedNameToQualifiedNameMap* case_map = nullptr;
if (!case_map) {
case_map = new PrefixedNameToQualifiedNameMap;
std::unique_ptr<const SVGQualifiedName* []> svg_tags = svg_names::GetTags();
MapLoweredLocalNameToName(case_map, svg_tags.get(), svg_names::kTagsCount);
}
const QualifiedName& cased_name = case_map->at(token->GetName());
if (cased_name.LocalName().IsNull())
return;
token->SetName(cased_name.LocalName());
}
template <std::unique_ptr<const QualifiedName* []> getAttrs(), unsigned length>
static void AdjustAttributes(AtomicHTMLToken* token) {
static PrefixedNameToQualifiedNameMap* case_map = nullptr;
if (!case_map) {
case_map = new PrefixedNameToQualifiedNameMap;
std::unique_ptr<const QualifiedName* []> attrs = getAttrs();
MapLoweredLocalNameToName(case_map, attrs.get(), length);
}
for (auto& token_attribute : token->Attributes()) {
const QualifiedName& cased_name = case_map->at(token_attribute.LocalName());
if (!cased_name.LocalName().IsNull())
token_attribute.ParserSetName(cased_name);
}
}
// https://html.spec.whatwg.org/C/#adjust-svg-attributes
static void AdjustSVGAttributes(AtomicHTMLToken* token) {
AdjustAttributes<svg_names::GetAttrs, svg_names::kAttrsCount>(token);
}
// https://html.spec.whatwg.org/C/#adjust-mathml-attributes
static void AdjustMathMLAttributes(AtomicHTMLToken* token) {
AdjustAttributes<mathml_names::GetAttrs, mathml_names::kAttrsCount>(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& local_name = name->LocalName();
AtomicString prefix_colon_local_name = prefix + ':' + local_name;
QualifiedName name_with_prefix(prefix, local_name, name->NamespaceURI());
map->insert(prefix_colon_local_name, name_with_prefix);
}
}
static void AdjustForeignAttributes(AtomicHTMLToken* token) {
static PrefixedNameToQualifiedNameMap* map = nullptr;
if (!map) {
map = new PrefixedNameToQualifiedNameMap;
std::unique_ptr<const QualifiedName* []> attrs = xlink_names::GetAttrs();
AddNamesWithPrefix(map, g_xlink_atom, attrs.get(),
xlink_names::kAttrsCount);
std::unique_ptr<const QualifiedName* []> xml_attrs = xml_names::GetAttrs();
AddNamesWithPrefix(map, g_xml_atom, xml_attrs.get(),
xml_names::kAttrsCount);
map->insert(WTF::g_xmlns_atom, xmlns_names::kXmlnsAttr);
map->insert("xmlns:xlink", QualifiedName(g_xmlns_atom, g_xlink_atom,
xmlns_names::kNamespaceURI));
}
for (unsigned i = 0; i < token->Attributes().size(); ++i) {
Attribute& token_attribute = token->Attributes().at(i);
const QualifiedName& name = map->at(token_attribute.LocalName());
if (!name.LocalName().IsNull())
token_attribute.ParserSetName(name);
}
}
void HTMLTreeBuilder::ProcessStartTagForInBody(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kBaseTag ||
token->GetName() == html_names::kBasefontTag ||
token->GetName() == html_names::kBgsoundTag ||
token->GetName() == html_names::kCommandTag ||
token->GetName() == html_names::kLinkTag ||
token->GetName() == html_names::kMetaTag ||
token->GetName() == html_names::kNoframesTag ||
token->GetName() == html_names::kScriptTag ||
token->GetName() == html_names::kStyleTag ||
token->GetName() == html_names::kTitleTag ||
token->GetName() == html_names::kTemplateTag) {
bool did_process = ProcessStartTagForInHead(token);
DCHECK(did_process);
return;
}
if (token->GetName() == html_names::kBodyTag) {
ParseError(token);
if (!tree_.OpenElements()->SecondElementIsHTMLBodyElement() ||
tree_.OpenElements()->HasOnlyOneElement() ||
tree_.OpenElements()->HasTemplateInHTMLScope()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
frameset_ok_ = false;
tree_.InsertHTMLBodyStartTagInBody(token);
return;
}
if (token->GetName() == html_names::kFramesetTag) {
ParseError(token);
if (!tree_.OpenElements()->SecondElementIsHTMLBodyElement() ||
tree_.OpenElements()->HasOnlyOneElement()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
if (!frameset_ok_)
return;
tree_.OpenElements()->BodyElement()->remove(ASSERT_NO_EXCEPTION);
tree_.OpenElements()->PopUntil(tree_.OpenElements()->BodyElement());
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() ||
To<DocumentFragment>(tree_.OpenElements()->TopNode()));
DCHECK(IsParsingFragment() ||
tree_.OpenElements()->Top() == tree_.OpenElements()->HtmlElement());
tree_.InsertHTMLElement(token);
SetInsertionMode(kInFramesetMode);
return;
}
if (token->GetName() == html_names::kAddressTag ||
token->GetName() == html_names::kArticleTag ||
token->GetName() == html_names::kAsideTag ||
token->GetName() == html_names::kBlockquoteTag ||
token->GetName() == html_names::kCenterTag ||
token->GetName() == html_names::kDetailsTag ||
token->GetName() == html_names::kDirTag ||
token->GetName() == html_names::kDivTag ||
token->GetName() == html_names::kDlTag ||
token->GetName() == html_names::kFieldsetTag ||
token->GetName() == html_names::kFigcaptionTag ||
token->GetName() == html_names::kFigureTag ||
token->GetName() == html_names::kFooterTag ||
token->GetName() == html_names::kHeaderTag ||
token->GetName() == html_names::kHgroupTag ||
token->GetName() == html_names::kMainTag ||
token->GetName() == html_names::kMenuTag ||
token->GetName() == html_names::kNavTag ||
token->GetName() == html_names::kOlTag ||
token->GetName() == html_names::kPTag ||
token->GetName() == html_names::kSectionTag ||
token->GetName() == html_names::kSummaryTag ||
token->GetName() == html_names::kUlTag) {
ProcessFakePEndTagIfPInButtonScope();
tree_.InsertHTMLElement(token);
return;
}
if (IsNumberedHeaderTag(token->GetName())) {
ProcessFakePEndTagIfPInButtonScope();
if (tree_.CurrentStackItem()->IsNumberedHeaderElement()) {
ParseError(token);
tree_.OpenElements()->Pop();
}
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == html_names::kPreTag ||
token->GetName() == html_names::kListingTag) {
ProcessFakePEndTagIfPInButtonScope();
tree_.InsertHTMLElement(token);
should_skip_leading_newline_ = true;
frameset_ok_ = false;
return;
}
if (token->GetName() == html_names::kFormTag) {
if (tree_.IsFormElementPointerNonNull() && !IsParsingTemplateContents()) {
ParseError(token);
UseCounter::Count(tree_.CurrentNode()->GetDocument(),
WebFeature::kHTMLParseErrorNestedForm);
return;
}
ProcessFakePEndTagIfPInButtonScope();
tree_.InsertHTMLFormElement(token);
return;
}
if (token->GetName() == html_names::kLiTag) {
ProcessCloseWhenNestedTag<IsLi>(token);
return;
}
if (token->GetName() == html_names::kDdTag ||
token->GetName() == html_names::kDtTag) {
ProcessCloseWhenNestedTag<IsDdOrDt>(token);
return;
}
if (token->GetName() == html_names::kPlaintextTag) {
ProcessFakePEndTagIfPInButtonScope();
tree_.InsertHTMLElement(token);
if (parser_->Tokenizer())
parser_->Tokenizer()->SetState(HTMLTokenizer::kPLAINTEXTState);
return;
}
if (token->GetName() == html_names::kButtonTag) {
if (tree_.OpenElements()->InScope(html_names::kButtonTag)) {
ParseError(token);
ProcessFakeEndTag(html_names::kButtonTag);
ProcessStartTag(token); // FIXME: Could we just fall through here?
return;
}
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertHTMLElement(token);
frameset_ok_ = false;
return;
}
if (token->GetName() == html_names::kATag) {
Element* active_a_tag =
tree_.ActiveFormattingElements()->ClosestElementInScopeWithName(
html_names::kATag.LocalName());
if (active_a_tag) {
ParseError(token);
ProcessFakeEndTag(html_names::kATag);
tree_.ActiveFormattingElements()->Remove(active_a_tag);
if (tree_.OpenElements()->Contains(active_a_tag))
tree_.OpenElements()->Remove(active_a_tag);
}
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertFormattingElement(token);
return;
}
if (IsNonAnchorNonNobrFormattingTag(token->GetName())) {
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertFormattingElement(token);
return;
}
if (token->GetName() == html_names::kNobrTag) {
tree_.ReconstructTheActiveFormattingElements();
if (tree_.OpenElements()->InScope(html_names::kNobrTag)) {
ParseError(token);
ProcessFakeEndTag(html_names::kNobrTag);
tree_.ReconstructTheActiveFormattingElements();
}
tree_.InsertFormattingElement(token);
return;
}
if (token->GetName() == html_names::kAppletTag ||
token->GetName() == html_names::kEmbedTag ||
token->GetName() == html_names::kObjectTag) {
if (!PluginContentIsAllowed(tree_.GetParserContentPolicy()))
return;
}
if (token->GetName() == html_names::kAppletTag ||
token->GetName() == html_names::kMarqueeTag ||
token->GetName() == html_names::kObjectTag) {
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertHTMLElement(token);
tree_.ActiveFormattingElements()->AppendMarker();
frameset_ok_ = false;
return;
}
if (token->GetName() == html_names::kTableTag) {
if (!tree_.InQuirksMode() &&
tree_.OpenElements()->InButtonScope(html_names::kPTag))
ProcessFakeEndTag(html_names::kPTag);
tree_.InsertHTMLElement(token);
frameset_ok_ = false;
SetInsertionMode(kInTableMode);
return;
}
if (token->GetName() == html_names::kImageTag) {
ParseError(token);
// Apparently we're not supposed to ask.
token->SetName(html_names::kImgTag.LocalName());
// Note the fall through to the kImgTag handling below!
}
if (token->GetName() == html_names::kAreaTag ||
token->GetName() == html_names::kBrTag ||
token->GetName() == html_names::kEmbedTag ||
token->GetName() == html_names::kImgTag ||
token->GetName() == html_names::kKeygenTag ||
token->GetName() == html_names::kWbrTag) {
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
frameset_ok_ = false;
return;
}
if (token->GetName() == html_names::kInputTag) {
// Per spec https://html.spec.whatwg.org/C/#parsing-main-inbody,
// section "A start tag whose tag name is "input""
Attribute* type_attribute = token->GetAttributeItem(html_names::kTypeAttr);
bool disable_frameset =
!type_attribute ||
!EqualIgnoringASCIICase(type_attribute->Value(), "hidden");
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
if (disable_frameset)
frameset_ok_ = false;
return;
}
if (token->GetName() == html_names::kParamTag ||
token->GetName() == html_names::kSourceTag ||
token->GetName() == html_names::kTrackTag) {
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
return;
}
if (token->GetName() == html_names::kHrTag) {
ProcessFakePEndTagIfPInButtonScope();
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
frameset_ok_ = false;
return;
}
if (token->GetName() == html_names::kTextareaTag) {
tree_.InsertHTMLElement(token);
should_skip_leading_newline_ = true;
if (parser_->Tokenizer())
parser_->Tokenizer()->SetState(HTMLTokenizer::kRCDATAState);
original_insertion_mode_ = insertion_mode_;
frameset_ok_ = false;
SetInsertionMode(kTextMode);
return;
}
if (token->GetName() == html_names::kXmpTag) {
ProcessFakePEndTagIfPInButtonScope();
tree_.ReconstructTheActiveFormattingElements();
frameset_ok_ = false;
ProcessGenericRawTextStartTag(token);
return;
}
if (token->GetName() == html_names::kIFrameTag) {
frameset_ok_ = false;
ProcessGenericRawTextStartTag(token);
return;
}
if (token->GetName() == html_names::kNoembedTag) {
ProcessGenericRawTextStartTag(token);
return;
}
if (token->GetName() == html_names::kNoscriptTag && options_.scripting_flag) {
ProcessGenericRawTextStartTag(token);
return;
}
if (token->GetName() == html_names::kSelectTag) {
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertHTMLElement(token);
frameset_ok_ = false;
if (insertion_mode_ == kInTableMode || insertion_mode_ == kInCaptionMode ||
insertion_mode_ == kInColumnGroupMode ||
insertion_mode_ == kInTableBodyMode || insertion_mode_ == kInRowMode ||
insertion_mode_ == kInCellMode)
SetInsertionMode(kInSelectInTableMode);
else
SetInsertionMode(kInSelectMode);
return;
}
if (token->GetName() == html_names::kOptgroupTag ||
token->GetName() == html_names::kOptionTag) {
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptionTag)) {
AtomicHTMLToken end_option(HTMLToken::kEndTag,
html_names::kOptionTag.LocalName());
ProcessEndTag(&end_option);
}
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == html_names::kRbTag ||
token->GetName() == html_names::kRTCTag) {
if (tree_.OpenElements()->InScope(html_names::kRubyTag.LocalName())) {
tree_.GenerateImpliedEndTags();
if (!tree_.CurrentStackItem()->HasTagName(html_names::kRubyTag))
ParseError(token);
}
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == html_names::kRtTag ||
token->GetName() == html_names::kRpTag) {
if (tree_.OpenElements()->InScope(html_names::kRubyTag.LocalName())) {
tree_.GenerateImpliedEndTagsWithExclusion(
html_names::kRTCTag.LocalName());
if (!tree_.CurrentStackItem()->HasTagName(html_names::kRubyTag) &&
!tree_.CurrentStackItem()->HasTagName(html_names::kRTCTag))
ParseError(token);
}
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == mathml_names::kMathTag.LocalName()) {
tree_.ReconstructTheActiveFormattingElements();
AdjustMathMLAttributes(token);
AdjustForeignAttributes(token);
tree_.InsertForeignElement(token, mathml_names::kNamespaceURI);
return;
}
if (token->GetName() == svg_names::kSVGTag.LocalName()) {
tree_.ReconstructTheActiveFormattingElements();
AdjustSVGAttributes(token);
AdjustForeignAttributes(token);
tree_.InsertForeignElement(token, svg_names::kNamespaceURI);
return;
}
if (IsCaptionColOrColgroupTag(token->GetName()) ||
token->GetName() == html_names::kFrameTag ||
token->GetName() == html_names::kHeadTag ||
IsTableBodyContextTag(token->GetName()) ||
IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag) {
ParseError(token);
return;
}
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertHTMLElement(token);
}
namespace {
DeclarativeShadowRootType DeclarativeShadowRootTypeFromToken(
AtomicHTMLToken* token,
const Document& document,
bool include_shadow_roots) {
if (!RuntimeEnabledFeatures::DeclarativeShadowDOMEnabled(
document.GetExecutionContext())) {
return DeclarativeShadowRootType::kNone;
}
Attribute* type_attribute =
token->GetAttributeItem(html_names::kShadowrootAttr);
if (!type_attribute)
return DeclarativeShadowRootType::kNone;
if (!include_shadow_roots) {
document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kWarning,
"Found declarative shadowroot attribute on a template, but declarative "
"Shadow DOM has not been enabled by includeShadowRoots."));
return DeclarativeShadowRootType::kNone;
}
String shadow_mode = type_attribute->Value();
if (EqualIgnoringASCIICase(shadow_mode, "open"))
return DeclarativeShadowRootType::kOpen;
if (EqualIgnoringASCIICase(shadow_mode, "closed"))
return DeclarativeShadowRootType::kClosed;
document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kWarning,
"Invalid declarative shadowroot attribute value \"" + shadow_mode +
"\". Valid values include \"open\" and \"closed\"."));
return DeclarativeShadowRootType::kNone;
}
} // namespace
void HTMLTreeBuilder::ProcessTemplateStartTag(AtomicHTMLToken* token) {
tree_.ActiveFormattingElements()->AppendMarker();
tree_.InsertHTMLTemplateElement(
token,
DeclarativeShadowRootTypeFromToken(
token, tree_.OwnerDocumentForCurrentNode(), include_shadow_roots_));
frameset_ok_ = false;
template_insertion_modes_.push_back(kTemplateContentsMode);
SetInsertionMode(kTemplateContentsMode);
}
bool HTMLTreeBuilder::ProcessTemplateEndTag(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetName(), html_names::kTemplateTag.LocalName());
if (!tree_.OpenElements()->HasTemplateInHTMLScope()) {
DCHECK(template_insertion_modes_.IsEmpty() ||
(template_insertion_modes_.size() == 1 &&
IsA<HTMLTemplateElement>(fragment_context_.ContextElement())));
ParseError(token);
return false;
}
tree_.GenerateImpliedEndTags();
if (!tree_.CurrentStackItem()->HasTagName(html_names::kTemplateTag))
ParseError(token);
tree_.OpenElements()->PopUntil(html_names::kTemplateTag.LocalName());
HTMLStackItem* template_stack_item =
tree_.OpenElements()->TopRecord()->StackItem();
tree_.OpenElements()->Pop();
HTMLStackItem* shadow_host_stack_item =
tree_.OpenElements()->TopRecord()->StackItem();
tree_.ActiveFormattingElements()->ClearToLastMarker();
template_insertion_modes_.pop_back();
ResetInsertionModeAppropriately();
if (RuntimeEnabledFeatures::DeclarativeShadowDOMEnabled(
shadow_host_stack_item->GetNode()->GetExecutionContext()) &&
template_stack_item) {
DCHECK(template_stack_item->IsElementNode());
HTMLTemplateElement* template_element =
DynamicTo<HTMLTemplateElement>(template_stack_item->GetElement());
// 9. If the start tag for the declarative template element did not have an
// attribute with the name "shadowroot" whose value was an ASCII
// case-insensitive match for the strings "open" or "closed", then stop this
// algorithm.
if (template_element->IsDeclarativeShadowRoot()) {
if (shadow_host_stack_item->GetNode() ==
tree_.OpenElements()->RootNode()) {
// 10. If the adjusted current node is the topmost element in the stack
// of open elements, then stop this algorithm.
template_element->SetDeclarativeShadowRootType(
DeclarativeShadowRootType::kNone);
} else {
DCHECK(shadow_host_stack_item);
DCHECK(shadow_host_stack_item->IsElementNode());
bool delegates_focus = template_stack_item->GetAttributeItem(
html_names::kShadowrootdelegatesfocusAttr);
// TODO(crbug.com/1063157): Add an attribute for imperative slot
// assignment.
bool manual_slotting = false;
shadow_host_stack_item->GetElement()->AttachDeclarativeShadowRoot(
template_element,
template_element->GetDeclarativeShadowRootType() ==
DeclarativeShadowRootType::kOpen
? ShadowRootType::kOpen
: ShadowRootType::kClosed,
delegates_focus ? FocusDelegation::kDelegateFocus
: FocusDelegation::kNone,
manual_slotting ? SlotAssignmentMode::kManual
: SlotAssignmentMode::kAuto);
}
}
}
return true;
}
bool HTMLTreeBuilder::ProcessEndOfFileForInTemplateContents(
AtomicHTMLToken* token) {
AtomicHTMLToken end_template(HTMLToken::kEndTag,
html_names::kTemplateTag.LocalName());
if (!ProcessTemplateEndTag(&end_template))
return false;
ProcessEndOfFile(token);
return true;
}
bool HTMLTreeBuilder::ProcessColgroupEndTagForInColumnGroup() {
if (tree_.CurrentIsRootNode() ||
IsA<HTMLTemplateElement>(*tree_.CurrentNode())) {
DCHECK(IsParsingFragmentOrTemplateContents());
// FIXME: parse error
return false;
}
tree_.OpenElements()->Pop();
SetInsertionMode(kInTableMode);
return true;
}
// http://www.whatwg.org/specs/web-apps/current-work/#adjusted-current-node
HTMLStackItem* HTMLTreeBuilder::AdjustedCurrentStackItem() const {
DCHECK(!tree_.IsEmpty());
if (IsParsingFragment() && tree_.OpenElements()->HasOnlyOneElement())
return fragment_context_.ContextElementStackItem();
return tree_.CurrentStackItem();
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#close-the-cell
void HTMLTreeBuilder::CloseTheCell() {
DCHECK_EQ(GetInsertionMode(), kInCellMode);
if (tree_.OpenElements()->InTableScope(html_names::kTdTag)) {
DCHECK(!tree_.OpenElements()->InTableScope(html_names::kThTag));
ProcessFakeEndTag(html_names::kTdTag);
return;
}
DCHECK(tree_.OpenElements()->InTableScope(html_names::kThTag));
ProcessFakeEndTag(html_names::kThTag);
DCHECK_EQ(GetInsertionMode(), kInRowMode);
}
void HTMLTreeBuilder::ProcessStartTagForInTable(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
if (token->GetName() == html_names::kCaptionTag) {
tree_.OpenElements()->PopUntilTableScopeMarker();
tree_.ActiveFormattingElements()->AppendMarker();
tree_.InsertHTMLElement(token);
SetInsertionMode(kInCaptionMode);
return;
}
if (token->GetName() == html_names::kColgroupTag) {
tree_.OpenElements()->PopUntilTableScopeMarker();
tree_.InsertHTMLElement(token);
SetInsertionMode(kInColumnGroupMode);
return;
}
if (token->GetName() == html_names::kColTag) {
ProcessFakeStartTag(html_names::kColgroupTag);
DCHECK(kInColumnGroupMode);
ProcessStartTag(token);
return;
}
if (IsTableBodyContextTag(token->GetName())) {
tree_.OpenElements()->PopUntilTableScopeMarker();
tree_.InsertHTMLElement(token);
SetInsertionMode(kInTableBodyMode);
return;
}
if (IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag) {
ProcessFakeStartTag(html_names::kTbodyTag);
DCHECK_EQ(GetInsertionMode(), kInTableBodyMode);
ProcessStartTag(token);
return;
}
if (token->GetName() == html_names::kTableTag) {
ParseError(token);
if (!ProcessTableEndTagForInTable()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
ProcessStartTag(token);
return;
}
if (token->GetName() == html_names::kStyleTag ||
token->GetName() == html_names::kScriptTag) {
ProcessStartTagForInHead(token);
return;
}
if (token->GetName() == html_names::kInputTag) {
Attribute* type_attribute = token->GetAttributeItem(html_names::kTypeAttr);
if (type_attribute &&
EqualIgnoringASCIICase(type_attribute->Value(), "hidden")) {
ParseError(token);
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
return;
}
// Fall through to "anything else" case.
}
if (token->GetName() == html_names::kFormTag) {
ParseError(token);
if (tree_.IsFormElementPointerNonNull() && !IsParsingTemplateContents())
return;
tree_.InsertHTMLFormElement(token, true);
tree_.OpenElements()->Pop();
return;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateStartTag(token);
return;
}
ParseError(token);
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(tree_);
ProcessStartTagForInBody(token);
}
void HTMLTreeBuilder::ProcessStartTag(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
switch (GetInsertionMode()) {
case kInitialMode:
DCHECK_EQ(GetInsertionMode(), kInitialMode);
DefaultForInitial();
FALLTHROUGH;
case kBeforeHTMLMode:
DCHECK_EQ(GetInsertionMode(), kBeforeHTMLMode);
if (token->GetName() == html_names::kHTMLTag) {
tree_.InsertHTMLHtmlStartTagBeforeHTML(token);
SetInsertionMode(kBeforeHeadMode);
return;
}
DefaultForBeforeHTML();
FALLTHROUGH;
case kBeforeHeadMode:
DCHECK_EQ(GetInsertionMode(), kBeforeHeadMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kHeadTag) {
tree_.InsertHTMLHeadElement(token);
SetInsertionMode(kInHeadMode);
return;
}
DefaultForBeforeHead();
FALLTHROUGH;
case kInHeadMode:
DCHECK_EQ(GetInsertionMode(), kInHeadMode);
if (ProcessStartTagForInHead(token))
return;
DefaultForInHead();
FALLTHROUGH;
case kAfterHeadMode:
DCHECK_EQ(GetInsertionMode(), kAfterHeadMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kBodyTag) {
frameset_ok_ = false;
tree_.InsertHTMLBodyElement(token);
SetInsertionMode(kInBodyMode);
return;
}
if (token->GetName() == html_names::kFramesetTag) {
tree_.InsertHTMLElement(token);
SetInsertionMode(kInFramesetMode);
return;
}
if (token->GetName() == html_names::kBaseTag ||
token->GetName() == html_names::kBasefontTag ||
token->GetName() == html_names::kBgsoundTag ||
token->GetName() == html_names::kLinkTag ||
token->GetName() == html_names::kMetaTag ||
token->GetName() == html_names::kNoframesTag ||
token->GetName() == html_names::kScriptTag ||
token->GetName() == html_names::kStyleTag ||
token->GetName() == html_names::kTemplateTag ||
token->GetName() == html_names::kTitleTag) {
ParseError(token);
DCHECK(tree_.Head());
tree_.OpenElements()->PushHTMLHeadElement(tree_.HeadStackItem());
ProcessStartTagForInHead(token);
tree_.OpenElements()->RemoveHTMLHeadElement(tree_.Head());
return;
}
if (token->GetName() == html_names::kHeadTag) {
ParseError(token);
return;
}
DefaultForAfterHead();
FALLTHROUGH;
case kInBodyMode:
DCHECK_EQ(GetInsertionMode(), kInBodyMode);
ProcessStartTagForInBody(token);
break;
case kInTableMode:
DCHECK_EQ(GetInsertionMode(), kInTableMode);
ProcessStartTagForInTable(token);
break;
case kInCaptionMode:
DCHECK_EQ(GetInsertionMode(), kInCaptionMode);
if (IsCaptionColOrColgroupTag(token->GetName()) ||
IsTableBodyContextTag(token->GetName()) ||
IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag) {
ParseError(token);
if (!ProcessCaptionEndTagForInCaption()) {
DCHECK(IsParsingFragment());
return;
}
ProcessStartTag(token);
return;
}
ProcessStartTagForInBody(token);
break;
case kInColumnGroupMode:
DCHECK_EQ(GetInsertionMode(), kInColumnGroupMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kColTag) {
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
return;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateStartTag(token);
return;
}
if (!ProcessColgroupEndTagForInColumnGroup()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
ProcessStartTag(token);
break;
case kInTableBodyMode:
DCHECK_EQ(GetInsertionMode(), kInTableBodyMode);
if (token->GetName() == html_names::kTrTag) {
// How is there ever anything to pop?
tree_.OpenElements()->PopUntilTableBodyScopeMarker();
tree_.InsertHTMLElement(token);
SetInsertionMode(kInRowMode);
return;
}
if (IsTableCellContextTag(token->GetName())) {
ParseError(token);
ProcessFakeStartTag(html_names::kTrTag);
DCHECK_EQ(GetInsertionMode(), kInRowMode);
ProcessStartTag(token);
return;
}
if (IsCaptionColOrColgroupTag(token->GetName()) ||
IsTableBodyContextTag(token->GetName())) {
// FIXME: This is slow.
if (!tree_.OpenElements()->InTableScope(html_names::kTbodyTag) &&
!tree_.OpenElements()->InTableScope(html_names::kTheadTag) &&
!tree_.OpenElements()->InTableScope(html_names::kTfootTag)) {
DCHECK(IsParsingFragmentOrTemplateContents());
ParseError(token);
return;
}
tree_.OpenElements()->PopUntilTableBodyScopeMarker();
DCHECK(IsTableBodyContextTag(tree_.CurrentStackItem()->LocalName()));
ProcessFakeEndTag(tree_.CurrentStackItem()->LocalName());
ProcessStartTag(token);
return;
}
ProcessStartTagForInTable(token);
break;
case kInRowMode:
DCHECK_EQ(GetInsertionMode(), kInRowMode);
if (IsTableCellContextTag(token->GetName())) {
tree_.OpenElements()->PopUntilTableRowScopeMarker();
tree_.InsertHTMLElement(token);
SetInsertionMode(kInCellMode);
tree_.ActiveFormattingElements()->AppendMarker();
return;
}
if (token->GetName() == html_names::kTrTag ||
IsCaptionColOrColgroupTag(token->GetName()) ||
IsTableBodyContextTag(token->GetName())) {
if (!ProcessTrEndTagForInRow()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
DCHECK_EQ(GetInsertionMode(), kInTableBodyMode);
ProcessStartTag(token);
return;
}
ProcessStartTagForInTable(token);
break;
case kInCellMode:
DCHECK_EQ(GetInsertionMode(), kInCellMode);
if (IsCaptionColOrColgroupTag(token->GetName()) ||
IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag ||
IsTableBodyContextTag(token->GetName())) {
// FIXME: This could be more efficient.
if (!tree_.OpenElements()->InTableScope(html_names::kTdTag) &&
!tree_.OpenElements()->InTableScope(html_names::kThTag)) {
DCHECK(IsParsingFragment());
ParseError(token);
return;
}
CloseTheCell();
ProcessStartTag(token);
return;
}
ProcessStartTagForInBody(token);
break;
case kAfterBodyMode:
case kAfterAfterBodyMode:
DCHECK(GetInsertionMode() == kAfterBodyMode ||
GetInsertionMode() == kAfterAfterBodyMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
SetInsertionMode(kInBodyMode);
ProcessStartTag(token);
break;
case kInHeadNoscriptMode:
DCHECK_EQ(GetInsertionMode(), kInHeadNoscriptMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kBasefontTag ||
token->GetName() == html_names::kBgsoundTag ||
token->GetName() == html_names::kLinkTag ||
token->GetName() == html_names::kMetaTag ||
token->GetName() == html_names::kNoframesTag ||
token->GetName() == html_names::kStyleTag) {
bool did_process = ProcessStartTagForInHead(token);
DCHECK(did_process);
return;
}
if (token->GetName() == html_names::kHTMLTag ||
token->GetName() == html_names::kNoscriptTag) {
ParseError(token);
return;
}
DefaultForInHeadNoscript();
ProcessToken(token);
break;
case kInFramesetMode:
DCHECK_EQ(GetInsertionMode(), kInFramesetMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kFramesetTag) {
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == html_names::kFrameTag) {
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
return;
}
if (token->GetName() == html_names::kNoframesTag) {
ProcessStartTagForInHead(token);
return;
}
ParseError(token);
break;
case kAfterFramesetMode:
case kAfterAfterFramesetMode:
DCHECK(GetInsertionMode() == kAfterFramesetMode ||
GetInsertionMode() == kAfterAfterFramesetMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kNoframesTag) {
ProcessStartTagForInHead(token);
return;
}
ParseError(token);
break;
case kInSelectInTableMode:
DCHECK_EQ(GetInsertionMode(), kInSelectInTableMode);
if (token->GetName() == html_names::kCaptionTag ||
token->GetName() == html_names::kTableTag ||
IsTableBodyContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag ||
IsTableCellContextTag(token->GetName())) {
ParseError(token);
AtomicHTMLToken end_select(HTMLToken::kEndTag,
html_names::kSelectTag.LocalName());
ProcessEndTag(&end_select);
ProcessStartTag(token);
return;
}
FALLTHROUGH;
case kInSelectMode:
DCHECK(GetInsertionMode() == kInSelectMode ||
GetInsertionMode() == kInSelectInTableMode);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return;
}
if (token->GetName() == html_names::kOptionTag) {
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptionTag)) {
AtomicHTMLToken end_option(HTMLToken::kEndTag,
html_names::kOptionTag.LocalName());
ProcessEndTag(&end_option);
}
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == html_names::kOptgroupTag) {
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptionTag)) {
AtomicHTMLToken end_option(HTMLToken::kEndTag,
html_names::kOptionTag.LocalName());
ProcessEndTag(&end_option);
}
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptgroupTag)) {
AtomicHTMLToken end_optgroup(HTMLToken::kEndTag,
html_names::kOptgroupTag.LocalName());
ProcessEndTag(&end_optgroup);
}
tree_.InsertHTMLElement(token);
return;
}
if (token->GetName() == html_names::kSelectTag) {
ParseError(token);
AtomicHTMLToken end_select(HTMLToken::kEndTag,
html_names::kSelectTag.LocalName());
ProcessEndTag(&end_select);
return;
}
if (token->GetName() == html_names::kInputTag ||
token->GetName() == html_names::kKeygenTag ||
token->GetName() == html_names::kTextareaTag) {
ParseError(token);
if (!tree_.OpenElements()->InSelectScope(html_names::kSelectTag)) {
DCHECK(IsParsingFragment());
return;
}
AtomicHTMLToken end_select(HTMLToken::kEndTag,
html_names::kSelectTag.LocalName());
ProcessEndTag(&end_select);
ProcessStartTag(token);
return;
}
if (token->GetName() == html_names::kScriptTag) {
bool did_process = ProcessStartTagForInHead(token);
DCHECK(did_process);
return;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateStartTag(token);
return;
}
break;
case kInTableTextMode:
DefaultForInTableText();
ProcessStartTag(token);
break;
case kTextMode:
NOTREACHED();
break;
case kTemplateContentsMode:
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateStartTag(token);
return;
}
if (token->GetName() == html_names::kLinkTag ||
token->GetName() == html_names::kScriptTag ||
token->GetName() == html_names::kStyleTag ||
token->GetName() == html_names::kMetaTag) {
ProcessStartTagForInHead(token);
return;
}
InsertionMode insertion_mode = kTemplateContentsMode;
if (token->GetName() == html_names::kColTag)
insertion_mode = kInColumnGroupMode;
else if (IsCaptionColOrColgroupTag(token->GetName()) ||
IsTableBodyContextTag(token->GetName()))
insertion_mode = kInTableMode;
else if (token->GetName() == html_names::kTrTag)
insertion_mode = kInTableBodyMode;
else if (IsTableCellContextTag(token->GetName()))
insertion_mode = kInRowMode;
else
insertion_mode = kInBodyMode;
DCHECK_NE(insertion_mode, kTemplateContentsMode);
DCHECK_EQ(template_insertion_modes_.back(), kTemplateContentsMode);
template_insertion_modes_.back() = insertion_mode;
SetInsertionMode(insertion_mode);
ProcessStartTag(token);
break;
}
}
void HTMLTreeBuilder::ProcessHtmlStartTagForInBody(AtomicHTMLToken* token) {
ParseError(token);
if (tree_.OpenElements()->HasTemplateInHTMLScope()) {
DCHECK(IsParsingTemplateContents());
return;
}
tree_.InsertHTMLHtmlStartTagInBody(token);
}
bool HTMLTreeBuilder::ProcessBodyEndTagForInBody(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
DCHECK(token->GetName() == html_names::kBodyTag);
if (!tree_.OpenElements()->InScope(html_names::kBodyTag.LocalName())) {
ParseError(token);
return false;
}
// Emit a more specific parse error based on stack contents.
DVLOG(1) << "Not implmeneted.";
SetInsertionMode(kAfterBodyMode);
return true;
}
void HTMLTreeBuilder::ProcessAnyOtherEndTagForInBody(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
HTMLElementStack::ElementRecord* record = tree_.OpenElements()->TopRecord();
while (1) {
HTMLStackItem* item = record->StackItem();
if (item->MatchesHTMLTag(token->GetName())) {
tree_.GenerateImpliedEndTagsWithExclusion(token->GetName());
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(item->GetElement());
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 kOuterIterationLimit = 8;
static const int kInnerIterationLimit = 3;
// 1, 2, 3 and 16 are covered by the for() loop.
for (int i = 0; i < kOuterIterationLimit; ++i) {
// 4.
Element* formatting_element =
tree_.ActiveFormattingElements()->ClosestElementInScopeWithName(
token->GetName());
// 4.a
if (!formatting_element)
return ProcessAnyOtherEndTagForInBody(token);
// 4.c
if ((tree_.OpenElements()->Contains(formatting_element)) &&
!tree_.OpenElements()->InScope(formatting_element)) {
ParseError(token);
// Check the stack of open elements for a more specific parse error.
DVLOG(1) << "Not implemented.";
return;
}
// 4.b
HTMLElementStack::ElementRecord* formatting_element_record =
tree_.OpenElements()->Find(formatting_element);
if (!formatting_element_record) {
ParseError(token);
tree_.ActiveFormattingElements()->Remove(formatting_element);
return;
}
// 4.d
if (formatting_element != tree_.CurrentElement())
ParseError(token);
// 5.
HTMLElementStack::ElementRecord* furthest_block =
tree_.OpenElements()->FurthestBlockForFormattingElement(
formatting_element);
// 6.
if (!furthest_block) {
tree_.OpenElements()->PopUntilPopped(formatting_element);
tree_.ActiveFormattingElements()->Remove(formatting_element);
return;
}
// 7.
DCHECK(furthest_block->IsAbove(formatting_element_record));
HTMLStackItem* common_ancestor =
formatting_element_record->Next()->StackItem();
// 8.
HTMLFormattingElementList::Bookmark bookmark =
tree_.ActiveFormattingElements()->BookmarkFor(formatting_element);
// 9.
HTMLElementStack::ElementRecord* node = furthest_block;
HTMLElementStack::ElementRecord* next_node = node->Next();
HTMLElementStack::ElementRecord* last_node = furthest_block;
// 9.1, 9.2, 9.3 and 9.11 are covered by the for() loop.
for (int j = 0; j < kInnerIterationLimit; ++j) {
// 9.4
node = next_node;
DCHECK(node);
// Save node->next() for the next iteration in case node is deleted in
// 9.5.
next_node = node->Next();
// 9.5
if (!tree_.ActiveFormattingElements()->Contains(node->GetElement())) {
tree_.OpenElements()->Remove(node->GetElement());
node = nullptr;
continue;
}
// 9.6
if (node == formatting_element_record)
break;
// 9.7
HTMLStackItem* new_item =
tree_.CreateElementFromSavedToken(node->StackItem());
HTMLFormattingElementList::Entry* node_entry =
tree_.ActiveFormattingElements()->Find(node->GetElement());
node_entry->ReplaceElement(new_item);
node->ReplaceElement(new_item);
// 9.8
if (last_node == furthest_block)
bookmark.MoveToAfter(node_entry);
// 9.9
tree_.Reparent(node, last_node);
// 9.10
last_node = node;
}
// 10.
tree_.InsertAlreadyParsedChild(common_ancestor, last_node);
// 11.
HTMLStackItem* new_item = tree_.CreateElementFromSavedToken(
formatting_element_record->StackItem());
// 12.
tree_.TakeAllChildren(new_item, furthest_block);
// 13.
tree_.Reparent(furthest_block, new_item);
// 14.
tree_.ActiveFormattingElements()->SwapTo(formatting_element, new_item,
bookmark);
// 15.
tree_.OpenElements()->Remove(formatting_element);
tree_.OpenElements()->InsertAbove(new_item, furthest_block);
}
}
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* node_record =
tree_.OpenElements()->TopRecord();
while (1) {
HTMLStackItem* item = node_record->StackItem();
if (item->GetNode() == tree_.OpenElements()->RootNode()) {
last = true;
if (IsParsingFragment())
item = fragment_context_.ContextElementStackItem();
}
if (item->HasTagName(html_names::kTemplateTag))
return SetInsertionMode(template_insertion_modes_.back());
if (item->HasTagName(html_names::kSelectTag)) {
if (!last) {
while (item->GetNode() != tree_.OpenElements()->RootNode() &&
!item->HasTagName(html_names::kTemplateTag)) {
node_record = node_record->Next();
item = node_record->StackItem();
if (item->HasTagName(html_names::kTableTag))
return SetInsertionMode(kInSelectInTableMode);
}
}
return SetInsertionMode(kInSelectMode);
}
if (item->HasTagName(html_names::kTdTag) ||
item->HasTagName(html_names::kThTag))
return SetInsertionMode(kInCellMode);
if (item->HasTagName(html_names::kTrTag))
return SetInsertionMode(kInRowMode);
if (item->HasTagName(html_names::kTbodyTag) ||
item->HasTagName(html_names::kTheadTag) ||
item->HasTagName(html_names::kTfootTag))
return SetInsertionMode(kInTableBodyMode);
if (item->HasTagName(html_names::kCaptionTag))
return SetInsertionMode(kInCaptionMode);
if (item->HasTagName(html_names::kColgroupTag)) {
return SetInsertionMode(kInColumnGroupMode);
}
if (item->HasTagName(html_names::kTableTag))
return SetInsertionMode(kInTableMode);
if (item->HasTagName(html_names::kHeadTag)) {
if (!fragment_context_.Fragment() ||
fragment_context_.ContextElement() != item->GetNode())
return SetInsertionMode(kInHeadMode);
return SetInsertionMode(kInBodyMode);
}
if (item->HasTagName(html_names::kBodyTag))
return SetInsertionMode(kInBodyMode);
if (item->HasTagName(html_names::kFramesetTag)) {
return SetInsertionMode(kInFramesetMode);
}
if (item->HasTagName(html_names::kHTMLTag)) {
if (tree_.HeadStackItem())
return SetInsertionMode(kAfterHeadMode);
DCHECK(IsParsingFragment());
return SetInsertionMode(kBeforeHeadMode);
}
if (last) {
DCHECK(IsParsingFragment());
return SetInsertionMode(kInBodyMode);
}
node_record = node_record->Next();
}
}
void HTMLTreeBuilder::ProcessEndTagForInTableBody(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
if (IsTableBodyContextTag(token->GetName())) {
if (!tree_.OpenElements()->InTableScope(token->GetName())) {
ParseError(token);
return;
}
tree_.OpenElements()->PopUntilTableBodyScopeMarker();
tree_.OpenElements()->Pop();
SetInsertionMode(kInTableMode);
return;
}
if (token->GetName() == html_names::kTableTag) {
// FIXME: This is slow.
if (!tree_.OpenElements()->InTableScope(html_names::kTbodyTag) &&
!tree_.OpenElements()->InTableScope(html_names::kTheadTag) &&
!tree_.OpenElements()->InTableScope(html_names::kTfootTag)) {
DCHECK(IsParsingFragmentOrTemplateContents());
ParseError(token);
return;
}
tree_.OpenElements()->PopUntilTableBodyScopeMarker();
DCHECK(IsTableBodyContextTag(tree_.CurrentStackItem()->LocalName()));
ProcessFakeEndTag(tree_.CurrentStackItem()->LocalName());
ProcessEndTag(token);
return;
}
if (token->GetName() == html_names::kBodyTag ||
IsCaptionColOrColgroupTag(token->GetName()) ||
token->GetName() == html_names::kHTMLTag ||
IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag) {
ParseError(token);
return;
}
ProcessEndTagForInTable(token);
}
void HTMLTreeBuilder::ProcessEndTagForInRow(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
if (token->GetName() == html_names::kTrTag) {
ProcessTrEndTagForInRow();
return;
}
if (token->GetName() == html_names::kTableTag) {
if (!ProcessTrEndTagForInRow()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
DCHECK_EQ(GetInsertionMode(), kInTableBodyMode);
ProcessEndTag(token);
return;
}
if (IsTableBodyContextTag(token->GetName())) {
if (!tree_.OpenElements()->InTableScope(token->GetName())) {
ParseError(token);
return;
}
ProcessFakeEndTag(html_names::kTrTag);
DCHECK_EQ(GetInsertionMode(), kInTableBodyMode);
ProcessEndTag(token);
return;
}
if (token->GetName() == html_names::kBodyTag ||
IsCaptionColOrColgroupTag(token->GetName()) ||
token->GetName() == html_names::kHTMLTag ||
IsTableCellContextTag(token->GetName())) {
ParseError(token);
return;
}
ProcessEndTagForInTable(token);
}
void HTMLTreeBuilder::ProcessEndTagForInCell(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
if (IsTableCellContextTag(token->GetName())) {
if (!tree_.OpenElements()->InTableScope(token->GetName())) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTags();
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(token->GetName());
tree_.ActiveFormattingElements()->ClearToLastMarker();
SetInsertionMode(kInRowMode);
return;
}
if (token->GetName() == html_names::kBodyTag ||
IsCaptionColOrColgroupTag(token->GetName()) ||
token->GetName() == html_names::kHTMLTag) {
ParseError(token);
return;
}
if (token->GetName() == html_names::kTableTag ||
token->GetName() == html_names::kTrTag ||
IsTableBodyContextTag(token->GetName())) {
if (!tree_.OpenElements()->InTableScope(token->GetName())) {
DCHECK(IsTableBodyContextTag(token->GetName()) ||
tree_.OpenElements()->InTableScope(html_names::kTemplateTag) ||
IsParsingFragment());
ParseError(token);
return;
}
CloseTheCell();
ProcessEndTag(token);
return;
}
ProcessEndTagForInBody(token);
}
void HTMLTreeBuilder::ProcessEndTagForInBody(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
if (token->GetName() == html_names::kBodyTag) {
ProcessBodyEndTagForInBody(token);
return;
}
if (token->GetName() == html_names::kHTMLTag) {
AtomicHTMLToken end_body(HTMLToken::kEndTag,
html_names::kBodyTag.LocalName());
if (ProcessBodyEndTagForInBody(&end_body))
ProcessEndTag(token);
return;
}
if (token->GetName() == html_names::kAddressTag ||
token->GetName() == html_names::kArticleTag ||
token->GetName() == html_names::kAsideTag ||
token->GetName() == html_names::kBlockquoteTag ||
token->GetName() == html_names::kButtonTag ||
token->GetName() == html_names::kCenterTag ||
token->GetName() == html_names::kDetailsTag ||
token->GetName() == html_names::kDirTag ||
token->GetName() == html_names::kDivTag ||
token->GetName() == html_names::kDlTag ||
token->GetName() == html_names::kFieldsetTag ||
token->GetName() == html_names::kFigcaptionTag ||
token->GetName() == html_names::kFigureTag ||
token->GetName() == html_names::kFooterTag ||
token->GetName() == html_names::kHeaderTag ||
token->GetName() == html_names::kHgroupTag ||
token->GetName() == html_names::kListingTag ||
token->GetName() == html_names::kMainTag ||
token->GetName() == html_names::kMenuTag ||
token->GetName() == html_names::kNavTag ||
token->GetName() == html_names::kOlTag ||
token->GetName() == html_names::kPreTag ||
token->GetName() == html_names::kSectionTag ||
token->GetName() == html_names::kSummaryTag ||
token->GetName() == html_names::kUlTag) {
if (!tree_.OpenElements()->InScope(token->GetName())) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTags();
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(token->GetName());
return;
}
if (token->GetName() == html_names::kFormTag &&
!IsParsingTemplateContents()) {
Element* node = tree_.TakeForm();
if (!node || !tree_.OpenElements()->InScope(node)) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTags();
if (tree_.CurrentElement() != node)
ParseError(token);
tree_.OpenElements()->Remove(node);
}
if (token->GetName() == html_names::kPTag) {
if (!tree_.OpenElements()->InButtonScope(token->GetName())) {
ParseError(token);
ProcessFakeStartTag(html_names::kPTag);
DCHECK(tree_.OpenElements()->InScope(token->GetName()));
ProcessEndTag(token);
return;
}
tree_.GenerateImpliedEndTagsWithExclusion(token->GetName());
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(token->GetName());
return;
}
if (token->GetName() == html_names::kLiTag) {
if (!tree_.OpenElements()->InListItemScope(token->GetName())) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTagsWithExclusion(token->GetName());
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(token->GetName());
return;
}
if (token->GetName() == html_names::kDdTag ||
token->GetName() == html_names::kDtTag) {
if (!tree_.OpenElements()->InScope(token->GetName())) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTagsWithExclusion(token->GetName());
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(token->GetName());
return;
}
if (IsNumberedHeaderTag(token->GetName())) {
if (!tree_.OpenElements()->HasNumberedHeaderElementInScope()) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTags();
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilNumberedHeaderElementPopped();
return;
}
if (IsFormattingTag(token->GetName())) {
CallTheAdoptionAgency(token);
return;
}
if (token->GetName() == html_names::kAppletTag ||
token->GetName() == html_names::kMarqueeTag ||
token->GetName() == html_names::kObjectTag) {
if (!tree_.OpenElements()->InScope(token->GetName())) {
ParseError(token);
return;
}
tree_.GenerateImpliedEndTags();
if (!tree_.CurrentStackItem()->MatchesHTMLTag(token->GetName()))
ParseError(token);
tree_.OpenElements()->PopUntilPopped(token->GetName());
tree_.ActiveFormattingElements()->ClearToLastMarker();
return;
}
if (token->GetName() == html_names::kBrTag) {
ParseError(token);
ProcessFakeStartTag(html_names::kBrTag);
return;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateEndTag(token);
return;
}
ProcessAnyOtherEndTagForInBody(token);
}
bool HTMLTreeBuilder::ProcessCaptionEndTagForInCaption() {
if (!tree_.OpenElements()->InTableScope(
html_names::kCaptionTag.LocalName())) {
DCHECK(IsParsingFragment());
// FIXME: parse error
return false;
}
tree_.GenerateImpliedEndTags();
// FIXME: parse error if
// (!tree_.CurrentStackItem()->HasTagName(html_names::kCaptionTag))
tree_.OpenElements()->PopUntilPopped(html_names::kCaptionTag.LocalName());
tree_.ActiveFormattingElements()->ClearToLastMarker();
SetInsertionMode(kInTableMode);
return true;
}
bool HTMLTreeBuilder::ProcessTrEndTagForInRow() {
if (!tree_.OpenElements()->InTableScope(html_names::kTrTag)) {
DCHECK(IsParsingFragmentOrTemplateContents());
// FIXME: parse error
return false;
}
tree_.OpenElements()->PopUntilTableRowScopeMarker();
DCHECK(tree_.CurrentStackItem()->HasTagName(html_names::kTrTag));
tree_.OpenElements()->Pop();
SetInsertionMode(kInTableBodyMode);
return true;
}
bool HTMLTreeBuilder::ProcessTableEndTagForInTable() {
if (!tree_.OpenElements()->InTableScope(html_names::kTableTag)) {
DCHECK(IsParsingFragmentOrTemplateContents());
// FIXME: parse error.
return false;
}
tree_.OpenElements()->PopUntilPopped(html_names::kTableTag.LocalName());
ResetInsertionModeAppropriately();
return true;
}
void HTMLTreeBuilder::ProcessEndTagForInTable(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
if (token->GetName() == html_names::kTableTag) {
ProcessTableEndTagForInTable();
return;
}
if (token->GetName() == html_names::kBodyTag ||
IsCaptionColOrColgroupTag(token->GetName()) ||
token->GetName() == html_names::kHTMLTag ||
IsTableBodyContextTag(token->GetName()) ||
IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag) {
ParseError(token);
return;
}
ParseError(token);
// Is this redirection necessary here?
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(tree_);
ProcessEndTagForInBody(token);
}
void HTMLTreeBuilder::ProcessEndTag(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndTag);
switch (GetInsertionMode()) {
case kInitialMode:
DCHECK_EQ(GetInsertionMode(), kInitialMode);
DefaultForInitial();
FALLTHROUGH;
case kBeforeHTMLMode:
DCHECK_EQ(GetInsertionMode(), kBeforeHTMLMode);
if (token->GetName() != html_names::kHeadTag &&
token->GetName() != html_names::kBodyTag &&
token->GetName() != html_names::kHTMLTag &&
token->GetName() != html_names::kBrTag) {
ParseError(token);
return;
}
DefaultForBeforeHTML();
FALLTHROUGH;
case kBeforeHeadMode:
DCHECK_EQ(GetInsertionMode(), kBeforeHeadMode);
if (token->GetName() != html_names::kHeadTag &&
token->GetName() != html_names::kBodyTag &&
token->GetName() != html_names::kHTMLTag &&
token->GetName() != html_names::kBrTag) {
ParseError(token);
return;
}
DefaultForBeforeHead();
FALLTHROUGH;
case kInHeadMode:
DCHECK_EQ(GetInsertionMode(), kInHeadMode);
// 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->GetName() == html_names::kTemplateTag) {
ProcessTemplateEndTag(token);
return;
}
if (token->GetName() == html_names::kHeadTag) {
tree_.OpenElements()->PopHTMLHeadElement();
SetInsertionMode(kAfterHeadMode);
return;
}
if (token->GetName() != html_names::kBodyTag &&
token->GetName() != html_names::kHTMLTag &&
token->GetName() != html_names::kBrTag) {
ParseError(token);
return;
}
DefaultForInHead();
FALLTHROUGH;
case kAfterHeadMode:
DCHECK_EQ(GetInsertionMode(), kAfterHeadMode);
if (token->GetName() != html_names::kBodyTag &&
token->GetName() != html_names::kHTMLTag &&
token->GetName() != html_names::kBrTag) {
ParseError(token);
return;
}
DefaultForAfterHead();
FALLTHROUGH;
case kInBodyMode:
DCHECK_EQ(GetInsertionMode(), kInBodyMode);
ProcessEndTagForInBody(token);
break;
case kInTableMode:
DCHECK_EQ(GetInsertionMode(), kInTableMode);
ProcessEndTagForInTable(token);
break;
case kInCaptionMode:
DCHECK_EQ(GetInsertionMode(), kInCaptionMode);
if (token->GetName() == html_names::kCaptionTag) {
ProcessCaptionEndTagForInCaption();
return;
}
if (token->GetName() == html_names::kTableTag) {
ParseError(token);
if (!ProcessCaptionEndTagForInCaption()) {
DCHECK(IsParsingFragment());
return;
}
ProcessEndTag(token);
return;
}
if (token->GetName() == html_names::kBodyTag ||
token->GetName() == html_names::kColTag ||
token->GetName() == html_names::kColgroupTag ||
token->GetName() == html_names::kHTMLTag ||
IsTableBodyContextTag(token->GetName()) ||
IsTableCellContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag) {
ParseError(token);
return;
}
ProcessEndTagForInBody(token);
break;
case kInColumnGroupMode:
DCHECK_EQ(GetInsertionMode(), kInColumnGroupMode);
if (token->GetName() == html_names::kColgroupTag) {
ProcessColgroupEndTagForInColumnGroup();
return;
}
if (token->GetName() == html_names::kColTag) {
ParseError(token);
return;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateEndTag(token);
return;
}
if (!ProcessColgroupEndTagForInColumnGroup()) {
DCHECK(IsParsingFragmentOrTemplateContents());
return;
}
ProcessEndTag(token);
break;
case kInRowMode:
DCHECK_EQ(GetInsertionMode(), kInRowMode);
ProcessEndTagForInRow(token);
break;
case kInCellMode:
DCHECK_EQ(GetInsertionMode(), kInCellMode);
ProcessEndTagForInCell(token);
break;
case kInTableBodyMode:
DCHECK_EQ(GetInsertionMode(), kInTableBodyMode);
ProcessEndTagForInTableBody(token);
break;
case kAfterBodyMode:
DCHECK_EQ(GetInsertionMode(), kAfterBodyMode);
if (token->GetName() == html_names::kHTMLTag) {
if (IsParsingFragment()) {
ParseError(token);
return;
}
SetInsertionMode(kAfterAfterBodyMode);
return;
}
FALLTHROUGH;
case kAfterAfterBodyMode:
DCHECK(GetInsertionMode() == kAfterBodyMode ||
GetInsertionMode() == kAfterAfterBodyMode);
ParseError(token);
SetInsertionMode(kInBodyMode);
ProcessEndTag(token);
break;
case kInHeadNoscriptMode:
DCHECK_EQ(GetInsertionMode(), kInHeadNoscriptMode);
if (token->GetName() == html_names::kNoscriptTag) {
DCHECK(tree_.CurrentStackItem()->HasTagName(html_names::kNoscriptTag));
tree_.OpenElements()->Pop();
DCHECK(tree_.CurrentStackItem()->HasTagName(html_names::kHeadTag));
SetInsertionMode(kInHeadMode);
return;
}
if (token->GetName() != html_names::kBrTag) {
ParseError(token);
return;
}
DefaultForInHeadNoscript();
ProcessToken(token);
break;
case kTextMode:
if (token->GetName() == html_names::kScriptTag &&
tree_.CurrentStackItem()->HasTagName(html_names::kScriptTag)) {
// Pause ourselves so that parsing stops until the script can be
// processed by the caller.
if (ScriptingContentIsAllowed(tree_.GetParserContentPolicy()))
script_to_process_ = tree_.CurrentElement();
tree_.OpenElements()->Pop();
SetInsertionMode(original_insertion_mode_);
if (parser_->Tokenizer()) {
// We must set the tokenizer's state to DataState explicitly if the
// tokenizer didn't have a chance to.
parser_->Tokenizer()->SetState(HTMLTokenizer::kDataState);
}
return;
}
tree_.OpenElements()->Pop();
SetInsertionMode(original_insertion_mode_);
break;
case kInFramesetMode:
DCHECK_EQ(GetInsertionMode(), kInFramesetMode);
if (token->GetName() == html_names::kFramesetTag) {
bool ignore_frameset_for_fragment_parsing = tree_.CurrentIsRootNode();
ignore_frameset_for_fragment_parsing =
ignore_frameset_for_fragment_parsing ||
tree_.OpenElements()->HasTemplateInHTMLScope();
if (ignore_frameset_for_fragment_parsing) {
DCHECK(IsParsingFragmentOrTemplateContents());
ParseError(token);
return;
}
tree_.OpenElements()->Pop();
if (!IsParsingFragment() &&
!tree_.CurrentStackItem()->HasTagName(html_names::kFramesetTag))
SetInsertionMode(kAfterFramesetMode);
return;
}
break;
case kAfterFramesetMode:
DCHECK_EQ(GetInsertionMode(), kAfterFramesetMode);
if (token->GetName() == html_names::kHTMLTag) {
SetInsertionMode(kAfterAfterFramesetMode);
return;
}
FALLTHROUGH;
case kAfterAfterFramesetMode:
DCHECK(GetInsertionMode() == kAfterFramesetMode ||
GetInsertionMode() == kAfterAfterFramesetMode);
ParseError(token);
break;
case kInSelectInTableMode:
DCHECK(GetInsertionMode() == kInSelectInTableMode);
if (token->GetName() == html_names::kCaptionTag ||
token->GetName() == html_names::kTableTag ||
IsTableBodyContextTag(token->GetName()) ||
token->GetName() == html_names::kTrTag ||
IsTableCellContextTag(token->GetName())) {
ParseError(token);
if (tree_.OpenElements()->InTableScope(token->GetName())) {
AtomicHTMLToken end_select(HTMLToken::kEndTag,
html_names::kSelectTag.LocalName());
ProcessEndTag(&end_select);
ProcessEndTag(token);
}
return;
}
FALLTHROUGH;
case kInSelectMode:
DCHECK(GetInsertionMode() == kInSelectMode ||
GetInsertionMode() == kInSelectInTableMode);
if (token->GetName() == html_names::kOptgroupTag) {
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptionTag) &&
tree_.OneBelowTop() &&
tree_.OneBelowTop()->HasTagName(html_names::kOptgroupTag))
ProcessFakeEndTag(html_names::kOptionTag);
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptgroupTag)) {
tree_.OpenElements()->Pop();
return;
}
ParseError(token);
return;
}
if (token->GetName() == html_names::kOptionTag) {
if (tree_.CurrentStackItem()->HasTagName(html_names::kOptionTag)) {
tree_.OpenElements()->Pop();
return;
}
ParseError(token);
return;
}
if (token->GetName() == html_names::kSelectTag) {
if (!tree_.OpenElements()->InSelectScope(token->GetName())) {
DCHECK(IsParsingFragment());
ParseError(token);
return;
}
tree_.OpenElements()->PopUntilPopped(
html_names::kSelectTag.LocalName());
ResetInsertionModeAppropriately();
return;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateEndTag(token);
return;
}
break;
case kInTableTextMode:
DefaultForInTableText();
ProcessEndTag(token);
break;
case kTemplateContentsMode:
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateEndTag(token);
return;
}
break;
}
}
void HTMLTreeBuilder::ProcessComment(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kComment);
if (insertion_mode_ == kInitialMode || insertion_mode_ == kBeforeHTMLMode ||
insertion_mode_ == kAfterAfterBodyMode ||
insertion_mode_ == kAfterAfterFramesetMode) {
tree_.InsertCommentOnDocument(token);
return;
}
if (insertion_mode_ == kAfterBodyMode) {
tree_.InsertCommentOnHTMLHtmlElement(token);
return;
}
if (insertion_mode_ == kInTableTextMode) {
DefaultForInTableText();
ProcessComment(token);
return;
}
tree_.InsertComment(token);
}
void HTMLTreeBuilder::ProcessCharacter(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kCharacter);
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 (should_skip_leading_newline_) {
should_skip_leading_newline_ = false;
buffer.SkipAtMostOneLeadingNewline();
if (buffer.IsEmpty())
return;
}
switch (GetInsertionMode()) {
case kInitialMode: {
DCHECK_EQ(GetInsertionMode(), kInitialMode);
buffer.SkipLeadingWhitespace();
if (buffer.IsEmpty())
return;
DefaultForInitial();
FALLTHROUGH;
}
case kBeforeHTMLMode: {
DCHECK_EQ(GetInsertionMode(), kBeforeHTMLMode);
buffer.SkipLeadingWhitespace();
if (buffer.IsEmpty())
return;
DefaultForBeforeHTML();
if (parser_->IsStopped()) {
buffer.SkipRemaining();
return;
}
FALLTHROUGH;
}
case kBeforeHeadMode: {
DCHECK_EQ(GetInsertionMode(), kBeforeHeadMode);
buffer.SkipLeadingWhitespace();
if (buffer.IsEmpty())
return;
DefaultForBeforeHead();
FALLTHROUGH;
}
case kInHeadMode: {
DCHECK_EQ(GetInsertionMode(), kInHeadMode);
StringView leading_whitespace = buffer.TakeLeadingWhitespace();
if (!leading_whitespace.IsEmpty())
tree_.InsertTextNode(leading_whitespace, kAllWhitespace);
if (buffer.IsEmpty())
return;
DefaultForInHead();
FALLTHROUGH;
}
case kAfterHeadMode: {
DCHECK_EQ(GetInsertionMode(), kAfterHeadMode);
StringView leading_whitespace = buffer.TakeLeadingWhitespace();
if (!leading_whitespace.IsEmpty())
tree_.InsertTextNode(leading_whitespace, kAllWhitespace);
if (buffer.IsEmpty())
return;
DefaultForAfterHead();
FALLTHROUGH;
}
case kInBodyMode:
case kInCaptionMode:
case kTemplateContentsMode:
case kInCellMode: {
DCHECK(GetInsertionMode() == kInBodyMode ||
GetInsertionMode() == kInCaptionMode ||
GetInsertionMode() == kInCellMode ||
GetInsertionMode() == kTemplateContentsMode);
ProcessCharacterBufferForInBody(buffer);
break;
}
case kInTableMode:
case kInTableBodyMode:
case kInRowMode: {
DCHECK(GetInsertionMode() == kInTableMode ||
GetInsertionMode() == kInTableBodyMode ||
GetInsertionMode() == kInRowMode);
DCHECK(pending_table_characters_.IsEmpty());
if (tree_.CurrentStackItem()->IsElementNode() &&
(tree_.CurrentStackItem()->HasTagName(html_names::kTableTag) ||
tree_.CurrentStackItem()->HasTagName(html_names::kTbodyTag) ||
tree_.CurrentStackItem()->HasTagName(html_names::kTfootTag) ||
tree_.CurrentStackItem()->HasTagName(html_names::kTheadTag) ||
tree_.CurrentStackItem()->HasTagName(html_names::kTrTag))) {
original_insertion_mode_ = insertion_mode_;
SetInsertionMode(kInTableTextMode);
// Note that we fall through to the InTableTextMode case below.
} else {
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(tree_);
ProcessCharacterBufferForInBody(buffer);
break;
}
FALLTHROUGH;
}
case kInTableTextMode: {
buffer.GiveRemainingTo(pending_table_characters_);
break;
}
case kInColumnGroupMode: {
DCHECK_EQ(GetInsertionMode(), kInColumnGroupMode);
StringView leading_whitespace = buffer.TakeLeadingWhitespace();
if (!leading_whitespace.IsEmpty())
tree_.InsertTextNode(leading_whitespace, kAllWhitespace);
if (buffer.IsEmpty())
return;
if (!ProcessColgroupEndTagForInColumnGroup()) {
DCHECK(IsParsingFragmentOrTemplateContents());
// The spec tells us to drop these characters on the floor.
buffer.SkipLeadingNonWhitespace();
if (buffer.IsEmpty())
return;
}
goto ReprocessBuffer;
}
case kAfterBodyMode:
case kAfterAfterBodyMode: {
DCHECK(GetInsertionMode() == kAfterBodyMode ||
GetInsertionMode() == kAfterAfterBodyMode);
// FIXME: parse error
SetInsertionMode(kInBodyMode);
goto ReprocessBuffer;
}
case kTextMode: {
DCHECK_EQ(GetInsertionMode(), kTextMode);
tree_.InsertTextNode(buffer.TakeRemaining());
break;
}
case kInHeadNoscriptMode: {
DCHECK_EQ(GetInsertionMode(), kInHeadNoscriptMode);
StringView leading_whitespace = buffer.TakeLeadingWhitespace();
if (!leading_whitespace.IsEmpty())
tree_.InsertTextNode(leading_whitespace, kAllWhitespace);
if (buffer.IsEmpty())
return;
DefaultForInHeadNoscript();
goto ReprocessBuffer;
}
case kInFramesetMode:
case kAfterFramesetMode: {
DCHECK(GetInsertionMode() == kInFramesetMode ||
GetInsertionMode() == kAfterFramesetMode ||
GetInsertionMode() == kAfterAfterFramesetMode);
String leading_whitespace = buffer.TakeRemainingWhitespace();
if (!leading_whitespace.IsEmpty())
tree_.InsertTextNode(leading_whitespace, kAllWhitespace);
// FIXME: We should generate a parse error if we skipped over any
// non-whitespace characters.
break;
}
case kInSelectInTableMode:
case kInSelectMode: {
DCHECK(GetInsertionMode() == kInSelectMode ||
GetInsertionMode() == kInSelectInTableMode);
tree_.InsertTextNode(buffer.TakeRemaining());
break;
}
case kAfterAfterFramesetMode: {
String leading_whitespace = buffer.TakeRemainingWhitespace();
if (!leading_whitespace.IsEmpty()) {
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertTextNode(leading_whitespace, kAllWhitespace);
}
// FIXME: We should generate a parse error if we skipped over any
// non-whitespace characters.
break;
}
}
}
void HTMLTreeBuilder::ProcessCharacterBufferForInBody(
CharacterTokenBuffer& buffer) {
tree_.ReconstructTheActiveFormattingElements();
StringView characters = buffer.TakeRemaining();
tree_.InsertTextNode(characters);
if (frameset_ok_ && !IsAllWhitespaceOrReplacementCharacters(characters))
frameset_ok_ = false;
}
void HTMLTreeBuilder::ProcessEndOfFile(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kEndOfFile);
switch (GetInsertionMode()) {
case kInitialMode:
DCHECK_EQ(GetInsertionMode(), kInitialMode);
DefaultForInitial();
FALLTHROUGH;
case kBeforeHTMLMode:
DCHECK_EQ(GetInsertionMode(), kBeforeHTMLMode);
DefaultForBeforeHTML();
FALLTHROUGH;
case kBeforeHeadMode:
DCHECK_EQ(GetInsertionMode(), kBeforeHeadMode);
DefaultForBeforeHead();
FALLTHROUGH;
case kInHeadMode:
DCHECK_EQ(GetInsertionMode(), kInHeadMode);
DefaultForInHead();
FALLTHROUGH;
case kAfterHeadMode:
DCHECK_EQ(GetInsertionMode(), kAfterHeadMode);
DefaultForAfterHead();
FALLTHROUGH;
case kInBodyMode:
case kInCellMode:
case kInCaptionMode:
case kInRowMode:
DCHECK(GetInsertionMode() == kInBodyMode ||
GetInsertionMode() == kInCellMode ||
GetInsertionMode() == kInCaptionMode ||
GetInsertionMode() == kInRowMode ||
GetInsertionMode() == kTemplateContentsMode);
// Emit parse error based on what elements are still open.
DVLOG(1) << "Not implemented.";
if (!template_insertion_modes_.IsEmpty() &&
ProcessEndOfFileForInTemplateContents(token))
return;
break;
case kAfterBodyMode:
case kAfterAfterBodyMode:
DCHECK(GetInsertionMode() == kAfterBodyMode ||
GetInsertionMode() == kAfterAfterBodyMode);
break;
case kInHeadNoscriptMode:
DCHECK_EQ(GetInsertionMode(), kInHeadNoscriptMode);
DefaultForInHeadNoscript();
ProcessEndOfFile(token);
return;
case kAfterFramesetMode:
case kAfterAfterFramesetMode:
DCHECK(GetInsertionMode() == kAfterFramesetMode ||
GetInsertionMode() == kAfterAfterFramesetMode);
break;
case kInColumnGroupMode:
if (tree_.CurrentIsRootNode()) {
DCHECK(IsParsingFragment());
return; // FIXME: Should we break here instead of returning?
}
DCHECK(tree_.CurrentNode()->HasTagName(html_names::kColgroupTag) ||
IsA<HTMLTemplateElement>(tree_.CurrentNode()));
ProcessColgroupEndTagForInColumnGroup();
FALLTHROUGH;
case kInFramesetMode:
case kInTableMode:
case kInTableBodyMode:
case kInSelectInTableMode:
case kInSelectMode:
DCHECK(GetInsertionMode() == kInSelectMode ||
GetInsertionMode() == kInSelectInTableMode ||
GetInsertionMode() == kInTableMode ||
GetInsertionMode() == kInFramesetMode ||
GetInsertionMode() == kInTableBodyMode ||
GetInsertionMode() == kInColumnGroupMode);
if (tree_.CurrentNode() != tree_.OpenElements()->RootNode())
ParseError(token);
if (!template_insertion_modes_.IsEmpty() &&
ProcessEndOfFileForInTemplateContents(token))
return;
break;
case kInTableTextMode:
DefaultForInTableText();
ProcessEndOfFile(token);
return;
case kTextMode: {
ParseError(token);
if (tree_.CurrentStackItem()->HasTagName(html_names::kScriptTag)) {
// Mark the script element as "already started".
DVLOG(1) << "Not implemented.";
}
Element* el = tree_.OpenElements()->Top();
if (IsA<HTMLTextAreaElement>(el))
To<HTMLFormControlElement>(el)->SetBlocksFormSubmission(true);
tree_.OpenElements()->Pop();
DCHECK_NE(original_insertion_mode_, kTextMode);
SetInsertionMode(original_insertion_mode_);
ProcessEndOfFile(token);
return;
}
case kTemplateContentsMode:
if (ProcessEndOfFileForInTemplateContents(token))
return;
break;
}
tree_.ProcessEndOfFile();
}
void HTMLTreeBuilder::DefaultForInitial() {
DVLOG(1) << "Not implemented.";
tree_.SetDefaultCompatibilityMode();
// FIXME: parse error
SetInsertionMode(kBeforeHTMLMode);
}
void HTMLTreeBuilder::DefaultForBeforeHTML() {
AtomicHTMLToken start_html(HTMLToken::kStartTag,
html_names::kHTMLTag.LocalName());
tree_.InsertHTMLHtmlStartTagBeforeHTML(&start_html);
SetInsertionMode(kBeforeHeadMode);
}
void HTMLTreeBuilder::DefaultForBeforeHead() {
AtomicHTMLToken start_head(HTMLToken::kStartTag,
html_names::kHeadTag.LocalName());
ProcessStartTag(&start_head);
}
void HTMLTreeBuilder::DefaultForInHead() {
AtomicHTMLToken end_head(HTMLToken::kEndTag,
html_names::kHeadTag.LocalName());
ProcessEndTag(&end_head);
}
void HTMLTreeBuilder::DefaultForInHeadNoscript() {
AtomicHTMLToken end_noscript(HTMLToken::kEndTag,
html_names::kNoscriptTag.LocalName());
ProcessEndTag(&end_noscript);
}
void HTMLTreeBuilder::DefaultForAfterHead() {
AtomicHTMLToken start_body(HTMLToken::kStartTag,
html_names::kBodyTag.LocalName());
ProcessStartTag(&start_body);
frameset_ok_ = true;
}
void HTMLTreeBuilder::DefaultForInTableText() {
String characters = pending_table_characters_.ToString();
pending_table_characters_.Clear();
if (!IsAllWhitespace(characters)) {
// FIXME: parse error
HTMLConstructionSite::RedirectToFosterParentGuard redirecter(tree_);
tree_.ReconstructTheActiveFormattingElements();
tree_.InsertTextNode(characters, kNotAllWhitespace);
frameset_ok_ = false;
SetInsertionMode(original_insertion_mode_);
return;
}
tree_.InsertTextNode(characters);
SetInsertionMode(original_insertion_mode_);
}
bool HTMLTreeBuilder::ProcessStartTagForInHead(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
if (token->GetName() == html_names::kHTMLTag) {
ProcessHtmlStartTagForInBody(token);
return true;
}
if (token->GetName() == html_names::kBaseTag ||
token->GetName() == html_names::kBasefontTag ||
token->GetName() == html_names::kBgsoundTag ||
token->GetName() == html_names::kCommandTag ||
token->GetName() == html_names::kLinkTag ||
token->GetName() == html_names::kMetaTag) {
tree_.InsertSelfClosingHTMLElementDestroyingToken(token);
// Note: The custom processing for the <meta> tag is done in
// HTMLMetaElement::process().
return true;
}
if (token->GetName() == html_names::kTitleTag) {
ProcessGenericRCDATAStartTag(token);
return true;
}
if (token->GetName() == html_names::kNoscriptTag) {
if (options_.scripting_flag) {
ProcessGenericRawTextStartTag(token);
return true;
}
tree_.InsertHTMLElement(token);
SetInsertionMode(kInHeadNoscriptMode);
return true;
}
if (token->GetName() == html_names::kNoframesTag ||
token->GetName() == html_names::kStyleTag) {
ProcessGenericRawTextStartTag(token);
return true;
}
if (token->GetName() == html_names::kScriptTag) {
ProcessScriptStartTag(token);
return true;
}
if (token->GetName() == html_names::kTemplateTag) {
ProcessTemplateStartTag(token);
return true;
}
if (token->GetName() == html_names::kHeadTag) {
ParseError(token);
return true;
}
return false;
}
void HTMLTreeBuilder::ProcessGenericRCDATAStartTag(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
tree_.InsertHTMLElement(token);
if (parser_->Tokenizer())
parser_->Tokenizer()->SetState(HTMLTokenizer::kRCDATAState);
original_insertion_mode_ = insertion_mode_;
SetInsertionMode(kTextMode);
}
void HTMLTreeBuilder::ProcessGenericRawTextStartTag(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
tree_.InsertHTMLElement(token);
if (parser_->Tokenizer())
parser_->Tokenizer()->SetState(HTMLTokenizer::kRAWTEXTState);
original_insertion_mode_ = insertion_mode_;
SetInsertionMode(kTextMode);
}
void HTMLTreeBuilder::ProcessScriptStartTag(AtomicHTMLToken* token) {
DCHECK_EQ(token->GetType(), HTMLToken::kStartTag);
tree_.InsertScriptElement(token);
if (parser_->Tokenizer())
parser_->Tokenizer()->SetState(HTMLTokenizer::kScriptDataState);
original_insertion_mode_ = insertion_mode_;
TextPosition position = parser_->GetTextPosition();
script_to_process_start_position_ = position;
SetInsertionMode(kTextMode);
}
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#tree-construction
bool HTMLTreeBuilder::ShouldProcessTokenInForeignContent(
AtomicHTMLToken* token) {
if (tree_.IsEmpty())
return false;
HTMLStackItem* adjusted_current_node = AdjustedCurrentStackItem();
if (adjusted_current_node->IsInHTMLNamespace())
return false;
if (HTMLElementStack::IsMathMLTextIntegrationPoint(adjusted_current_node)) {
if (token->GetType() == HTMLToken::kStartTag &&
token->GetName() != mathml_names::kMglyphTag &&
token->GetName() != mathml_names::kMalignmarkTag)
return false;
if (token->GetType() == HTMLToken::kCharacter)
return false;
}
if (adjusted_current_node->HasTagName(mathml_names::kAnnotationXmlTag) &&
token->GetType() == HTMLToken::kStartTag &&
token->GetName() == svg_names::kSVGTag)
return false;
if (HTMLElementStack::IsHTMLIntegrationPoint(adjusted_current_node)) {
if (token->GetType() == HTMLToken::kStartTag)
return false;
if (token->GetType() == HTMLToken::kCharacter)
return false;
}
if (token->GetType() == HTMLToken::kEndOfFile)
return false;
return true;
}
void HTMLTreeBuilder::ProcessTokenInForeignContent(AtomicHTMLToken* token) {
if (token->GetType() == HTMLToken::kCharacter) {
const String& characters = token->Characters();
tree_.InsertTextNode(characters);
if (frameset_ok_ && !IsAllWhitespaceOrReplacementCharacters(characters))
frameset_ok_ = false;
return;
}
tree_.Flush(kFlushAlways);
HTMLStackItem* adjusted_current_node = AdjustedCurrentStackItem();
switch (token->GetType()) {
case HTMLToken::kUninitialized:
NOTREACHED();
break;
case HTMLToken::DOCTYPE:
ParseError(token);
break;
case HTMLToken::kStartTag: {
if (token->GetName() == html_names::kBTag ||
token->GetName() == html_names::kBigTag ||
token->GetName() == html_names::kBlockquoteTag ||
token->GetName() == html_names::kBodyTag ||
token->GetName() == html_names::kBrTag ||
token->GetName() == html_names::kCenterTag ||
token->GetName() == html_names::kCodeTag ||
token->GetName() == html_names::kDdTag ||
token->GetName() == html_names::kDivTag ||
token->GetName() == html_names::kDlTag ||
token->GetName() == html_names::kDtTag ||
token->GetName() == html_names::kEmTag ||
token->GetName() == html_names::kEmbedTag ||
IsNumberedHeaderTag(token->GetName()) ||
token->GetName() == html_names::kHeadTag ||
token->GetName() == html_names::kHrTag ||
token->GetName() == html_names::kITag ||
token->GetName() == html_names::kImgTag ||
token->GetName() == html_names::kLiTag ||
token->GetName() == html_names::kListingTag ||
token->GetName() == html_names::kMenuTag ||
token->GetName() == html_names::kMetaTag ||
token->GetName() == html_names::kNobrTag ||
token->GetName() == html_names::kOlTag ||
token->GetName() == html_names::kPTag ||
token->GetName() == html_names::kPreTag ||
token->GetName() == html_names::kRubyTag ||
token->GetName() == html_names::kSTag ||
token->GetName() == html_names::kSmallTag ||
token->GetName() == html_names::kSpanTag ||
token->GetName() == html_names::kStrongTag ||
token->GetName() == html_names::kStrikeTag ||
token->GetName() == html_names::kSubTag ||
token->GetName() == html_names::kSupTag ||
token->GetName() == html_names::kTableTag ||
token->GetName() == html_names::kTtTag ||
token->GetName() == html_names::kUTag ||
token->GetName() == html_names::kUlTag ||
token->GetName() == html_names::kVarTag ||
(token->GetName() == html_names::kFontTag &&
(token->GetAttributeItem(html_names::kColorAttr) ||
token->GetAttributeItem(html_names::kFaceAttr) ||
token->GetAttributeItem(html_names::kSizeAttr)))) {
ParseError(token);
tree_.OpenElements()->PopUntilForeignContentScopeMarker();
ProcessStartTag(token);
return;
}
if (token->GetName() == html_names::kScriptTag)
script_to_process_start_position_ = parser_->GetTextPosition();
const AtomicString& current_namespace =
adjusted_current_node->NamespaceURI();
if (current_namespace == mathml_names::kNamespaceURI)
AdjustMathMLAttributes(token);
if (current_namespace == svg_names::kNamespaceURI) {
AdjustSVGTagNameCase(token);
AdjustSVGAttributes(token);
}
AdjustForeignAttributes(token);
tree_.InsertForeignElement(token, current_namespace);
break;
}
case HTMLToken::kEndTag: {
if (adjusted_current_node->NamespaceURI() == svg_names::kNamespaceURI)
AdjustSVGTagNameCase(token);
if (token->GetName() == svg_names::kScriptTag &&
tree_.CurrentStackItem()->HasTagName(svg_names::kScriptTag)) {
if (ScriptingContentIsAllowed(tree_.GetParserContentPolicy()))
script_to_process_ = tree_.CurrentElement();
tree_.OpenElements()->Pop();
return;
}
if (token->GetName() == html_names::kBrTag ||
token->GetName() == html_names::kPTag) {
ParseError(token);
tree_.OpenElements()->PopUntilForeignContentScopeMarker();
ProcessEndTag(token);
return;
}
if (!tree_.CurrentStackItem()->IsInHTMLNamespace()) {
// FIXME: This code just wants an Element* iterator, instead of an
// ElementRecord*
HTMLElementStack::ElementRecord* node_record =
tree_.OpenElements()->TopRecord();
if (!node_record->StackItem()->HasLocalName(token->GetName()))
ParseError(token);
while (1) {
if (node_record->StackItem()->HasLocalName(token->GetName())) {
tree_.OpenElements()->PopUntilPopped(node_record->GetElement());
return;
}
node_record = node_record->Next();
if (node_record->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::kComment:
tree_.InsertComment(token);
break;
case HTMLToken::kCharacter:
case HTMLToken::kEndOfFile:
NOTREACHED();
break;
}
}
void HTMLTreeBuilder::Finished() {
if (IsParsingFragment())
return;
DCHECK(template_insertion_modes_.IsEmpty());
#if DCHECK_IS_ON()
DCHECK(is_attached_);
#endif
// Warning, this may detach the parser. Do not do anything else after this.
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(kInitialMode)
DEFINE_STRINGIFY(kBeforeHTMLMode)
DEFINE_STRINGIFY(kBeforeHeadMode)
DEFINE_STRINGIFY(kInHeadMode)
DEFINE_STRINGIFY(kInHeadNoscriptMode)
DEFINE_STRINGIFY(kAfterHeadMode)
DEFINE_STRINGIFY(kTemplateContentsMode)
DEFINE_STRINGIFY(kInBodyMode)
DEFINE_STRINGIFY(kTextMode)
DEFINE_STRINGIFY(kInTableMode)
DEFINE_STRINGIFY(kInTableTextMode)
DEFINE_STRINGIFY(kInCaptionMode)
DEFINE_STRINGIFY(kInColumnGroupMode)
DEFINE_STRINGIFY(kInTableBodyMode)
DEFINE_STRINGIFY(kInRowMode)
DEFINE_STRINGIFY(kInCellMode)
DEFINE_STRINGIFY(kInSelectMode)
DEFINE_STRINGIFY(kInSelectInTableMode)
DEFINE_STRINGIFY(kAfterBodyMode)
DEFINE_STRINGIFY(kInFramesetMode)
DEFINE_STRINGIFY(kAfterFramesetMode)
DEFINE_STRINGIFY(kAfterAfterBodyMode)
DEFINE_STRINGIFY(kAfterAfterFramesetMode)
#undef DEFINE_STRINGIFY
}
return "<unknown>";
}
#endif
} // namespace blink