| /* |
| * 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 "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(const CharacterTokenBuffer&) = delete; |
| CharacterTokenBuffer& operator=(const CharacterTokenBuffer&) = delete; |
| |
| ~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_; |
| }; |
| |
| 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::kNamed); |
| } |
| } |
| } |
| 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()) |
|