| /* |
| * Copyright (C) 2010 Google, Inc. All Rights Reserved. |
| * Copyright (C) 2011-2017 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 "config.h" |
| #include "HTMLTreeBuilder.h" |
| |
| #include "CommonAtomStrings.h" |
| #include "DocumentFragment.h" |
| #include "HTMLDocument.h" |
| #include "HTMLDocumentParser.h" |
| #include "HTMLFormControlElement.h" |
| #include "HTMLFormElement.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLOptGroupElement.h" |
| #include "HTMLOptionElement.h" |
| #include "HTMLParserIdioms.h" |
| #include "HTMLScriptElement.h" |
| #include "HTMLTableElement.h" |
| #include "JSCustomElementInterface.h" |
| #include "LocalizedStrings.h" |
| #include "NotImplemented.h" |
| #include "SVGElementTypeHelpers.h" |
| #include "SVGScriptElement.h" |
| #include "XLinkNames.h" |
| #include "XMLNSNames.h" |
| #include "XMLNames.h" |
| #include <wtf/NeverDestroyed.h> |
| #include <wtf/RobinHoodHashMap.h> |
| #include <wtf/unicode/CharacterNames.h> |
| |
| #if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS_FAMILY) |
| #include "TelephoneNumberDetector.h" |
| #endif |
| |
| namespace WebCore { |
| |
| using namespace HTMLNames; |
| |
| CustomElementConstructionData::CustomElementConstructionData(Ref<JSCustomElementInterface>&& customElementInterface, const AtomString& name, Vector<Attribute>&& attributes) |
| : elementInterface(WTFMove(customElementInterface)) |
| , name(name) |
| , attributes(WTFMove(attributes)) |
| { |
| } |
| |
| CustomElementConstructionData::~CustomElementConstructionData() = default; |
| |
| namespace { |
| |
| inline bool isHTMLSpaceOrReplacementCharacter(UChar character) |
| { |
| return isHTMLSpace(character) || character == replacementCharacter; |
| } |
| |
| } |
| |
| static inline TextPosition uninitializedPositionValue1() |
| { |
| return TextPosition(OrdinalNumber::fromOneBasedInt(-1), OrdinalNumber()); |
| } |
| |
| static inline bool isAllWhitespace(const String& string) |
| { |
| return string.isAllSpecialCharacters<isHTMLSpace>(); |
| } |
| |
| static inline bool isAllWhitespaceOrReplacementCharacters(const String& string) |
| { |
| return string.isAllSpecialCharacters<isHTMLSpaceOrReplacementCharacter>(); |
| } |
| |
| static bool isNumberedHeaderTag(const AtomString& tagName) |
| { |
| return tagName == h1Tag |
| || tagName == h2Tag |
| || tagName == h3Tag |
| || tagName == h4Tag |
| || tagName == h5Tag |
| || tagName == h6Tag; |
| } |
| |
| static bool isCaptionColOrColgroupTag(const AtomString& tagName) |
| { |
| return tagName == captionTag || tagName == colTag || tagName == colgroupTag; |
| } |
| |
| static bool isTableCellContextTag(const AtomString& tagName) |
| { |
| return tagName == thTag || tagName == tdTag; |
| } |
| |
| static bool isTableBodyContextTag(const AtomString& tagName) |
| { |
| return tagName == tbodyTag || tagName == tfootTag || tagName == theadTag; |
| } |
| |
| static bool isNonAnchorNonNobrFormattingTag(const AtomString& tagName) |
| { |
| return tagName == bTag |
| || tagName == bigTag |
| || tagName == codeTag |
| || tagName == emTag |
| || tagName == fontTag |
| || tagName == iTag |
| || tagName == sTag |
| || tagName == smallTag |
| || tagName == strikeTag |
| || tagName == strongTag |
| || tagName == ttTag |
| || tagName == uTag; |
| } |
| |
| static bool isNonAnchorFormattingTag(const AtomString& tagName) |
| { |
| return tagName == nobrTag || isNonAnchorNonNobrFormattingTag(tagName); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/syntax.html#formatting |
| bool HTMLConstructionSite::isFormattingTag(const AtomString& tagName) |
| { |
| return tagName == aTag || isNonAnchorFormattingTag(tagName); |
| } |
| |
| class HTMLTreeBuilder::ExternalCharacterTokenBuffer { |
| public: |
| explicit ExternalCharacterTokenBuffer(AtomHTMLToken& token) |
| : m_text(token.characters()) |
| , m_isAll8BitData(token.charactersIsAll8BitData()) |
| { |
| ASSERT(!isEmpty()); |
| } |
| |
| explicit ExternalCharacterTokenBuffer(const String& string) |
| : m_text(string) |
| , m_isAll8BitData(m_text.is8Bit()) |
| { |
| ASSERT(!isEmpty()); |
| } |
| |
| ~ExternalCharacterTokenBuffer() |
| { |
| ASSERT(isEmpty()); |
| } |
| |
| bool isEmpty() const { return m_text.isEmpty(); } |
| |
| bool isAll8BitData() const { return m_isAll8BitData; } |
| |
| void skipAtMostOneLeadingNewline() |
| { |
| ASSERT(!isEmpty()); |
| if (m_text[0] == '\n') |
| m_text = m_text.substring(1); |
| } |
| |
| void skipLeadingWhitespace() |
| { |
| skipLeading<isHTMLSpace>(); |
| } |
| |
| String takeLeadingWhitespace() |
| { |
| return takeLeading<isHTMLSpace>(); |
| } |
| |
| void skipLeadingNonWhitespace() |
| { |
| skipLeading<isNotHTMLSpace>(); |
| } |
| |
| String takeRemaining() |
| { |
| auto result = makeString(m_text); |
| m_text = StringView(); |
| return result; |
| } |
| |
| void giveRemainingTo(StringBuilder& recipient) |
| { |
| recipient.append(m_text); |
| m_text = StringView(); |
| } |
| |
| String takeRemainingWhitespace() |
| { |
| ASSERT(!isEmpty()); |
| Vector<LChar, 8> whitespace; |
| do { |
| UChar character = m_text[0]; |
| if (isHTMLSpace(character)) |
| whitespace.append(character); |
| m_text = m_text.substring(1); |
| } while (!m_text.isEmpty()); |
| |
| // 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 (whitespace.isEmpty()) |
| return String(); |
| |
| return String::adopt(WTFMove(whitespace)); |
| } |
| |
| private: |
| template<bool characterPredicate(UChar)> void skipLeading() |
| { |
| ASSERT(!isEmpty()); |
| while (characterPredicate(m_text[0])) { |
| m_text = m_text.substring(1); |
| if (m_text.isEmpty()) |
| return; |
| } |
| } |
| |
| template<bool characterPredicate(UChar)> String takeLeading() |
| { |
| ASSERT(!isEmpty()); |
| StringView start = m_text; |
| skipLeading<characterPredicate>(); |
| if (start.length() == m_text.length()) |
| return String(); |
| return makeString(start.left(start.length() - m_text.length())); |
| } |
| |
| String makeString(StringView stringView) const |
| { |
| if (stringView.is8Bit() || !isAll8BitData()) |
| return stringView.toString(); |
| return String::make8BitFrom16BitSource(stringView.characters16(), stringView.length()); |
| } |
| |
| StringView m_text; |
| bool m_isAll8BitData; |
| }; |
| |
| inline bool HTMLTreeBuilder::isParsingTemplateContents() const |
| { |
| return m_tree.openElements().hasTemplateInHTMLScope(); |
| } |
| |
| inline bool HTMLTreeBuilder::isParsingFragmentOrTemplateContents() const |
| { |
| return isParsingFragment() || isParsingTemplateContents(); |
| } |
| |
| HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser& parser, HTMLDocument& document, ParserContentPolicy parserContentPolicy, const HTMLParserOptions& options) |
| : m_parser(parser) |
| , m_options(options) |
| , m_tree(document, parserContentPolicy, options.maximumDOMTreeDepth) |
| , m_scriptToProcessStartPosition(uninitializedPositionValue1()) |
| { |
| #if ASSERT_ENABLED |
| m_destructionProhibited = false; |
| #endif |
| } |
| |
| HTMLTreeBuilder::HTMLTreeBuilder(HTMLDocumentParser& parser, DocumentFragment& fragment, Element& contextElement, ParserContentPolicy parserContentPolicy, const HTMLParserOptions& options) |
| : m_parser(parser) |
| , m_options(options) |
| , m_fragmentContext(fragment, contextElement) |
| , m_tree(fragment, parserContentPolicy, options.maximumDOMTreeDepth) |
| , m_scriptToProcessStartPosition(uninitializedPositionValue1()) |
| { |
| ASSERT(isMainThread()); |
| |
| // https://html.spec.whatwg.org/multipage/syntax.html#parsing-html-fragments |
| // For efficiency, we skip step 5 ("Let root be a new html element with no attributes") and instead use the DocumentFragment as a root node. |
| m_tree.openElements().pushRootNode(HTMLStackItem(fragment)); |
| |
| if (contextElement.hasTagName(templateTag)) |
| m_templateInsertionModes.append(InsertionMode::TemplateContents); |
| |
| resetInsertionModeAppropriately(); |
| |
| m_tree.setForm(is<HTMLFormElement>(contextElement) ? &downcast<HTMLFormElement>(contextElement) : HTMLFormElement::findClosestFormAncestor(contextElement)); |
| |
| #if ASSERT_ENABLED |
| m_destructionProhibited = false; |
| #endif |
| } |
| |
| HTMLTreeBuilder::FragmentParsingContext::FragmentParsingContext() |
| { |
| } |
| |
| HTMLTreeBuilder::FragmentParsingContext::FragmentParsingContext(DocumentFragment& fragment, Element& contextElement) |
| : m_fragment(&fragment) |
| , m_contextElementStackItem(contextElement) |
| { |
| ASSERT(!fragment.hasChildNodes()); |
| } |
| |
| inline Element& HTMLTreeBuilder::FragmentParsingContext::contextElement() |
| { |
| return contextElementStackItem().element(); |
| } |
| |
| inline HTMLStackItem& HTMLTreeBuilder::FragmentParsingContext::contextElementStackItem() |
| { |
| ASSERT(m_fragment); |
| return m_contextElementStackItem; |
| } |
| |
| RefPtr<ScriptElement> HTMLTreeBuilder::takeScriptToProcess(TextPosition& scriptStartPosition) |
| { |
| ASSERT(!m_destroyed); |
| |
| if (!m_scriptToProcess) |
| return nullptr; |
| |
| // Unpause ourselves, callers may pause us again when processing the script. |
| // The HTML5 spec is written as though scripts are executed inside the tree builder. |
| // We pause the parser to exit the tree builder, and then resume before running scripts. |
| scriptStartPosition = m_scriptToProcessStartPosition; |
| m_scriptToProcessStartPosition = uninitializedPositionValue1(); |
| return WTFMove(m_scriptToProcess); |
| } |
| |
| void HTMLTreeBuilder::constructTree(AtomHTMLToken&& token) |
| { |
| #if ASSERT_ENABLED |
| ASSERT(!m_destroyed); |
| ASSERT(!m_destructionProhibited); |
| m_destructionProhibited = true; |
| #endif |
| |
| if (shouldProcessTokenInForeignContent(token)) |
| processTokenInForeignContent(WTFMove(token)); |
| else |
| processToken(WTFMove(token)); |
| |
| bool inForeignContent = !m_tree.isEmpty() |
| && !isInHTMLNamespace(adjustedCurrentStackItem()) |
| && !HTMLElementStack::isHTMLIntegrationPoint(m_tree.currentStackItem()) |
| && !HTMLElementStack::isMathMLTextIntegrationPoint(m_tree.currentStackItem()); |
| |
| m_parser.tokenizer().setForceNullCharacterReplacement(m_insertionMode == InsertionMode::Text || inForeignContent); |
| m_parser.tokenizer().setShouldAllowCDATA(inForeignContent); |
| |
| #if ASSERT_ENABLED |
| m_destructionProhibited = false; |
| #endif |
| |
| m_tree.executeQueuedTasks(); |
| // The tree builder might have been destroyed as an indirect result of executing the queued tasks. |
| } |
| |
| void HTMLTreeBuilder::processToken(AtomHTMLToken&& token) |
| { |
| switch (token.type()) { |
| case HTMLToken::Type::Uninitialized: |
| ASSERT_NOT_REACHED(); |
| break; |
| case HTMLToken::Type::DOCTYPE: |
| m_shouldSkipLeadingNewline = false; |
| processDoctypeToken(WTFMove(token)); |
| break; |
| case HTMLToken::Type::StartTag: |
| m_shouldSkipLeadingNewline = false; |
| processStartTag(WTFMove(token)); |
| break; |
| case HTMLToken::Type::EndTag: |
| m_shouldSkipLeadingNewline = false; |
| processEndTag(WTFMove(token)); |
| break; |
| case HTMLToken::Type::Comment: |
| m_shouldSkipLeadingNewline = false; |
| processComment(WTFMove(token)); |
| return; |
| case HTMLToken::Type::Character: |
| processCharacter(WTFMove(token)); |
| break; |
| case HTMLToken::Type::EndOfFile: |
| m_shouldSkipLeadingNewline = false; |
| processEndOfFile(WTFMove(token)); |
| break; |
| } |
| } |
| |
| void HTMLTreeBuilder::processDoctypeToken(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::DOCTYPE); |
| if (m_insertionMode == InsertionMode::Initial) { |
| m_tree.insertDoctype(WTFMove(token)); |
| m_insertionMode = InsertionMode::BeforeHTML; |
| return; |
| } |
| if (m_insertionMode == InsertionMode::InTableText) { |
| defaultForInTableText(); |
| processDoctypeToken(WTFMove(token)); |
| return; |
| } |
| parseError(token); |
| } |
| |
| void HTMLTreeBuilder::processFakeStartTag(const QualifiedName& tagName, Vector<Attribute>&& attributes) |
| { |
| // FIXME: We'll need a fancier conversion than just "localName" for SVG/MathML tags. |
| AtomHTMLToken fakeToken(HTMLToken::Type::StartTag, tagName.localName(), WTFMove(attributes)); |
| processStartTag(WTFMove(fakeToken)); |
| } |
| |
| void HTMLTreeBuilder::processFakeEndTag(const AtomString& tagName) |
| { |
| AtomHTMLToken fakeToken(HTMLToken::Type::EndTag, tagName); |
| processEndTag(WTFMove(fakeToken)); |
| } |
| |
| void HTMLTreeBuilder::processFakeEndTag(const QualifiedName& tagName) |
| { |
| // FIXME: We'll need a fancier conversion than just "localName" for SVG/MathML tags. |
| processFakeEndTag(tagName.localName()); |
| } |
| |
| void HTMLTreeBuilder::processFakeCharacters(const String& characters) |
| { |
| ASSERT(!characters.isEmpty()); |
| ExternalCharacterTokenBuffer buffer(characters); |
| processCharacterBuffer(buffer); |
| } |
| |
| void HTMLTreeBuilder::processFakePEndTagIfPInButtonScope() |
| { |
| if (!m_tree.openElements().inButtonScope(pTag->localName())) |
| return; |
| AtomHTMLToken endP(HTMLToken::Type::EndTag, pTag->localName()); |
| processEndTag(WTFMove(endP)); |
| } |
| |
| namespace { |
| |
| bool isLi(const HTMLStackItem& item) |
| { |
| return item.hasTagName(liTag); |
| } |
| |
| bool isDdOrDt(const HTMLStackItem& item) |
| { |
| return item.hasTagName(ddTag) || item.hasTagName(dtTag); |
| } |
| |
| } |
| |
| template <bool shouldClose(const HTMLStackItem&)> void HTMLTreeBuilder::processCloseWhenNestedTag(AtomHTMLToken&& token) |
| { |
| m_framesetOk = false; |
| for (auto* nodeRecord = &m_tree.openElements().topRecord(); ; nodeRecord = nodeRecord->next()) { |
| HTMLStackItem& item = nodeRecord->stackItem(); |
| if (shouldClose(item)) { |
| ASSERT(item.isElement()); |
| processFakeEndTag(item.localName()); |
| break; |
| } |
| if (isSpecialNode(item) && !item.hasTagName(addressTag) && !item.hasTagName(divTag) && !item.hasTagName(pTag)) |
| break; |
| } |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| } |
| |
| template <typename TableQualifiedName> static MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName> createCaseMap(const TableQualifiedName* const names[], unsigned length) |
| { |
| MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName> map; |
| for (unsigned i = 0; i < length; ++i) { |
| const QualifiedName& name = *names[i]; |
| const AtomString& localName = name.localName(); |
| AtomString loweredLocalName = localName.convertToASCIILowercase(); |
| if (loweredLocalName != localName) |
| map.add(loweredLocalName, name); |
| } |
| return map; |
| } |
| |
| static void adjustSVGTagNameCase(AtomHTMLToken& token) |
| { |
| static NeverDestroyed<MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName>> map = createCaseMap(SVGNames::getSVGTags(), SVGNames::SVGTagsCount); |
| const QualifiedName& casedName = map.get().get(token.name()); |
| if (casedName.localName().isNull()) |
| return; |
| token.setName(casedName.localName()); |
| } |
| |
| static inline void adjustAttributes(const MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName>& map, AtomHTMLToken& token) |
| { |
| for (auto& attribute : token.attributes()) { |
| const QualifiedName& casedName = map.get(attribute.localName()); |
| if (!casedName.localName().isNull()) |
| attribute.parserSetName(casedName); |
| } |
| } |
| |
| template<const QualifiedName* const* attributesTable(), unsigned attributesTableLength> static void adjustAttributes(AtomHTMLToken& token) |
| { |
| static NeverDestroyed<MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName>> map = createCaseMap(attributesTable(), attributesTableLength); |
| adjustAttributes(map, token); |
| } |
| |
| static inline void adjustSVGAttributes(AtomHTMLToken& token) |
| { |
| adjustAttributes<SVGNames::getSVGAttrs, SVGNames::SVGAttrsCount>(token); |
| } |
| |
| static inline void adjustMathMLAttributes(AtomHTMLToken& token) |
| { |
| adjustAttributes<MathMLNames::getMathMLAttrs, MathMLNames::MathMLAttrsCount>(token); |
| } |
| |
| static MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName> createForeignAttributesMap() |
| { |
| auto addNamesWithPrefix = [](MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName>& map, const AtomString& prefix, const QualifiedName* const names[], unsigned length) { |
| for (unsigned i = 0; i < length; ++i) { |
| const QualifiedName& name = *names[i]; |
| const AtomString& localName = name.localName(); |
| map.add(makeAtomString(prefix, ':', localName), QualifiedName(prefix, localName, name.namespaceURI())); |
| } |
| }; |
| |
| MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName> map; |
| |
| AtomString xlinkName("xlink"_s); |
| addNamesWithPrefix(map, xlinkName, XLinkNames::getXLinkAttrs(), XLinkNames::XLinkAttrsCount); |
| addNamesWithPrefix(map, xmlAtom(), XMLNames::getXMLAttrs(), XMLNames::XMLAttrsCount); |
| |
| map.add(xmlnsAtom(), XMLNSNames::xmlnsAttr); |
| map.add("xmlns:xlink"_s, QualifiedName(xmlnsAtom(), xlinkName, XMLNSNames::xmlnsNamespaceURI)); |
| |
| return map; |
| } |
| |
| static void adjustForeignAttributes(AtomHTMLToken& token) |
| { |
| static NeverDestroyed<MemoryCompactLookupOnlyRobinHoodHashMap<AtomString, QualifiedName>> map = createForeignAttributesMap(); |
| adjustAttributes(map, token); |
| } |
| |
| void HTMLTreeBuilder::processStartTagForInBody(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == baseTag |
| || token.name() == basefontTag |
| || token.name() == bgsoundTag |
| || token.name() == commandTag |
| || token.name() == linkTag |
| || token.name() == metaTag |
| || token.name() == noframesTag |
| || token.name() == scriptTag |
| || token.name() == styleTag |
| || token.name() == titleTag) { |
| bool didProcess = processStartTagForInHead(WTFMove(token)); |
| ASSERT_UNUSED(didProcess, didProcess); |
| return; |
| } |
| if (token.name() == bodyTag) { |
| parseError(token); |
| bool fragmentOrTemplateCase = !m_tree.openElements().secondElementIsHTMLBodyElement() || m_tree.openElements().hasOnlyOneElement() |
| || m_tree.openElements().hasTemplateInHTMLScope(); |
| if (fragmentOrTemplateCase) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| m_framesetOk = false; |
| m_tree.insertHTMLBodyStartTagInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == framesetTag) { |
| parseError(token); |
| if (!m_tree.openElements().secondElementIsHTMLBodyElement() || m_tree.openElements().hasOnlyOneElement()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| if (!m_framesetOk) |
| return; |
| m_tree.openElements().bodyElement().remove(); |
| m_tree.openElements().popUntil(m_tree.openElements().bodyElement()); |
| m_tree.openElements().popHTMLBodyElement(); |
| // Note: in the fragment case the root is a DocumentFragment instead of a proper html element which is a quirk / optimization in WebKit. |
| ASSERT(!isParsingFragment() || is<DocumentFragment>(m_tree.openElements().topNode())); |
| ASSERT(isParsingFragment() || &m_tree.openElements().top() == &m_tree.openElements().htmlElement()); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InFrameset; |
| return; |
| } |
| if (token.name() == addressTag |
| || token.name() == articleTag |
| || token.name() == asideTag |
| || token.name() == blockquoteTag |
| || token.name() == centerTag |
| || token.name() == detailsTag |
| || token.name() == dialogTag |
| || token.name() == dirTag |
| || token.name() == divTag |
| || token.name() == dlTag |
| || token.name() == fieldsetTag |
| || token.name() == figcaptionTag |
| || token.name() == figureTag |
| || token.name() == footerTag |
| || token.name() == headerTag |
| || token.name() == hgroupTag |
| || token.name() == mainTag |
| || token.name() == menuTag |
| || token.name() == navTag |
| || token.name() == olTag |
| || token.name() == pTag |
| || token.name() == sectionTag |
| || token.name() == summaryTag |
| || token.name() == ulTag) { |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (isNumberedHeaderTag(token.name())) { |
| processFakePEndTagIfPInButtonScope(); |
| if (isNumberedHeaderElement(m_tree.currentStackItem())) { |
| parseError(token); |
| m_tree.openElements().pop(); |
| } |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == preTag || token.name() == listingTag) { |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_shouldSkipLeadingNewline = true; |
| m_framesetOk = false; |
| return; |
| } |
| if (token.name() == formTag) { |
| if (m_tree.form() && !isParsingTemplateContents()) { |
| parseError(token); |
| return; |
| } |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.insertHTMLFormElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == liTag) { |
| processCloseWhenNestedTag<isLi>(WTFMove(token)); |
| return; |
| } |
| if (token.name() == ddTag || token.name() == dtTag) { |
| processCloseWhenNestedTag<isDdOrDt>(WTFMove(token)); |
| return; |
| } |
| if (token.name() == plaintextTag) { |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_parser.tokenizer().setPLAINTEXTState(); |
| return; |
| } |
| if (token.name() == buttonTag) { |
| if (m_tree.openElements().inScope(buttonTag)) { |
| parseError(token); |
| processFakeEndTag(buttonTag); |
| processStartTag(WTFMove(token)); // FIXME: Could we just fall through here? |
| return; |
| } |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_framesetOk = false; |
| return; |
| } |
| if (token.name() == aTag) { |
| RefPtr<Element> activeATag = m_tree.activeFormattingElements().closestElementInScopeWithName(aTag->localName()); |
| if (activeATag) { |
| parseError(token); |
| processFakeEndTag(aTag); |
| m_tree.activeFormattingElements().remove(*activeATag); |
| if (m_tree.openElements().contains(*activeATag)) |
| m_tree.openElements().remove(*activeATag); |
| } |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertFormattingElement(WTFMove(token)); |
| return; |
| } |
| if (isNonAnchorNonNobrFormattingTag(token.name())) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertFormattingElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == nobrTag) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| if (m_tree.openElements().inScope(nobrTag)) { |
| parseError(token); |
| processFakeEndTag(nobrTag); |
| m_tree.reconstructTheActiveFormattingElements(); |
| } |
| m_tree.insertFormattingElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == appletTag || token.name() == embedTag || token.name() == objectTag) { |
| if (!pluginContentIsAllowed(m_tree.parserContentPolicy())) |
| return; |
| } |
| if (token.name() == appletTag || token.name() == marqueeTag || token.name() == objectTag) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_tree.activeFormattingElements().appendMarker(); |
| m_framesetOk = false; |
| return; |
| } |
| if (token.name() == tableTag) { |
| if (!m_tree.inQuirksMode() && m_tree.openElements().inButtonScope(pTag)) |
| processFakeEndTag(pTag); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_framesetOk = false; |
| m_insertionMode = InsertionMode::InTable; |
| return; |
| } |
| if (token.name() == imageTag) { |
| parseError(token); |
| // Apparently we're not supposed to ask. |
| token.setName(imgTag->localName()); |
| // Note the fall through to the imgTag handling below! |
| } |
| if (token.name() == areaTag |
| || token.name() == brTag |
| || token.name() == embedTag |
| || token.name() == imgTag |
| || token.name() == keygenTag |
| || token.name() == wbrTag) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| m_framesetOk = false; |
| return; |
| } |
| if (token.name() == inputTag) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| auto* typeAttribute = findAttribute(token.attributes(), typeAttr); |
| bool shouldClearFramesetOK = !typeAttribute || !equalLettersIgnoringASCIICase(typeAttribute->value(), "hidden"_s); |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| if (shouldClearFramesetOK) |
| m_framesetOk = false; |
| return; |
| } |
| if (token.name() == paramTag || token.name() == sourceTag || token.name() == trackTag) { |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == hrTag) { |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| m_framesetOk = false; |
| return; |
| } |
| if (token.name() == textareaTag) { |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_shouldSkipLeadingNewline = true; |
| m_parser.tokenizer().setRCDATAState(); |
| m_originalInsertionMode = m_insertionMode; |
| m_framesetOk = false; |
| m_insertionMode = InsertionMode::Text; |
| return; |
| } |
| if (token.name() == xmpTag) { |
| processFakePEndTagIfPInButtonScope(); |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_framesetOk = false; |
| processGenericRawTextStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == iframeTag) { |
| m_framesetOk = false; |
| processGenericRawTextStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == noembedTag) { |
| processGenericRawTextStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == noscriptTag && m_options.scriptingFlag) { |
| processGenericRawTextStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == selectTag) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_framesetOk = false; |
| if (m_insertionMode == InsertionMode::InTable |
| || m_insertionMode == InsertionMode::InCaption |
| || m_insertionMode == InsertionMode::InColumnGroup |
| || m_insertionMode == InsertionMode::InTableBody |
| || m_insertionMode == InsertionMode::InRow |
| || m_insertionMode == InsertionMode::InCell) |
| m_insertionMode = InsertionMode::InSelectInTable; |
| else |
| m_insertionMode = InsertionMode::InSelect; |
| return; |
| } |
| if (token.name() == optgroupTag || token.name() == optionTag) { |
| if (is<HTMLOptionElement>(m_tree.currentStackItem().node())) { |
| AtomHTMLToken endOption(HTMLToken::Type::EndTag, optionTag->localName()); |
| processEndTag(WTFMove(endOption)); |
| } |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == rbTag || token.name() == rtcTag) { |
| if (m_tree.openElements().inScope(rubyTag->localName())) { |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentStackItem().hasTagName(rubyTag)) |
| parseError(token); |
| } |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == rtTag || token.name() == rpTag) { |
| if (m_tree.openElements().inScope(rubyTag->localName())) { |
| m_tree.generateImpliedEndTagsWithExclusion(rtcTag->localName()); |
| if (!m_tree.currentStackItem().hasTagName(rubyTag) && !m_tree.currentStackItem().hasTagName(rtcTag)) |
| parseError(token); |
| } |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == MathMLNames::mathTag->localName()) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| adjustMathMLAttributes(token); |
| adjustForeignAttributes(token); |
| m_tree.insertForeignElement(WTFMove(token), MathMLNames::mathmlNamespaceURI); |
| return; |
| } |
| if (token.name() == SVGNames::svgTag->localName()) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| adjustSVGAttributes(token); |
| adjustForeignAttributes(token); |
| m_tree.insertForeignElement(WTFMove(token), SVGNames::svgNamespaceURI); |
| return; |
| } |
| if (isCaptionColOrColgroupTag(token.name()) |
| || token.name() == frameTag |
| || token.name() == headTag |
| || isTableBodyContextTag(token.name()) |
| || isTableCellContextTag(token.name()) |
| || token.name() == trTag) { |
| parseError(token); |
| return; |
| } |
| if (token.name() == templateTag) { |
| m_framesetOk = false; |
| processTemplateStartTag(WTFMove(token)); |
| return; |
| } |
| m_tree.reconstructTheActiveFormattingElements(); |
| insertGenericHTMLElement(WTFMove(token)); |
| } |
| |
| inline void HTMLTreeBuilder::insertGenericHTMLElement(AtomHTMLToken&& token) |
| { |
| m_customElementToConstruct = m_tree.insertHTMLElementOrFindCustomElementInterface(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::didCreateCustomOrFallbackElement(Ref<Element>&& element, CustomElementConstructionData& data) |
| { |
| m_tree.insertCustomElement(WTFMove(element), data.name, WTFMove(data.attributes)); |
| } |
| |
| void HTMLTreeBuilder::processTemplateStartTag(AtomHTMLToken&& token) |
| { |
| m_tree.activeFormattingElements().appendMarker(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_templateInsertionModes.append(InsertionMode::TemplateContents); |
| m_insertionMode = InsertionMode::TemplateContents; |
| } |
| |
| bool HTMLTreeBuilder::processTemplateEndTag(AtomHTMLToken&& token) |
| { |
| ASSERT(token.name() == templateTag->localName()); |
| if (!m_tree.openElements().hasTemplateInHTMLScope()) { |
| ASSERT(m_templateInsertionModes.isEmpty() || (m_templateInsertionModes.size() == 1 && m_fragmentContext.contextElement().hasTagName(templateTag))); |
| parseError(token); |
| return false; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentStackItem().hasTagName(templateTag)) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(templateTag); |
| m_tree.activeFormattingElements().clearToLastMarker(); |
| m_templateInsertionModes.removeLast(); |
| resetInsertionModeAppropriately(); |
| return true; |
| } |
| |
| bool HTMLTreeBuilder::processEndOfFileForInTemplateContents(AtomHTMLToken&& token) |
| { |
| AtomHTMLToken endTemplate(HTMLToken::Type::EndTag, templateTag->localName()); |
| if (!processTemplateEndTag(WTFMove(endTemplate))) |
| return false; |
| |
| processEndOfFile(WTFMove(token)); |
| return true; |
| } |
| |
| bool HTMLTreeBuilder::processColgroupEndTagForInColumnGroup() |
| { |
| bool ignoreFakeEndTag = m_tree.currentIsRootNode() || m_tree.currentNode().hasTagName(templateTag); |
| |
| if (ignoreFakeEndTag) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| // FIXME: parse error |
| return false; |
| } |
| m_tree.openElements().pop(); |
| m_insertionMode = InsertionMode::InTable; |
| return true; |
| } |
| |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#close-the-cell |
| void HTMLTreeBuilder::closeTheCell() |
| { |
| ASSERT(m_insertionMode == InsertionMode::InCell); |
| if (m_tree.openElements().inTableScope(tdTag)) { |
| ASSERT(!m_tree.openElements().inTableScope(thTag)); |
| processFakeEndTag(tdTag); |
| return; |
| } |
| ASSERT(m_tree.openElements().inTableScope(thTag)); |
| processFakeEndTag(thTag); |
| ASSERT(m_insertionMode == InsertionMode::InRow); |
| } |
| |
| void HTMLTreeBuilder::processStartTagForInTable(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| if (token.name() == captionTag) { |
| m_tree.openElements().popUntilTableScopeMarker(); |
| m_tree.activeFormattingElements().appendMarker(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InCaption; |
| return; |
| } |
| if (token.name() == colgroupTag) { |
| m_tree.openElements().popUntilTableScopeMarker(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InColumnGroup; |
| return; |
| } |
| if (token.name() == colTag) { |
| processFakeStartTag(colgroupTag); |
| ASSERT(m_insertionMode == InsertionMode::InColumnGroup); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| if (isTableBodyContextTag(token.name())) { |
| m_tree.openElements().popUntilTableScopeMarker(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InTableBody; |
| return; |
| } |
| if (isTableCellContextTag(token.name()) || token.name() == trTag) { |
| processFakeStartTag(tbodyTag); |
| ASSERT(m_insertionMode == InsertionMode::InTableBody); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == tableTag) { |
| parseError(token); |
| if (!processTableEndTagForInTable()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == styleTag || token.name() == scriptTag) { |
| processStartTagForInHead(WTFMove(token)); |
| return; |
| } |
| if (token.name() == inputTag) { |
| auto* typeAttribute = findAttribute(token.attributes(), typeAttr); |
| if (typeAttribute && equalLettersIgnoringASCIICase(typeAttribute->value(), "hidden"_s)) { |
| parseError(token); |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| return; |
| } |
| // Fall through to "anything else" case. |
| } |
| if (token.name() == formTag) { |
| parseError(token); |
| if (m_tree.form() && !isParsingTemplateContents()) |
| return; |
| m_tree.insertHTMLFormElement(WTFMove(token), true); |
| m_tree.openElements().pop(); |
| return; |
| } |
| if (token.name() == templateTag) { |
| processTemplateStartTag(WTFMove(token)); |
| return; |
| } |
| parseError(token); |
| HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree); |
| processStartTagForInBody(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::processStartTag(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| switch (m_insertionMode) { |
| case InsertionMode::Initial: |
| defaultForInitial(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHTML); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHTML: |
| if (token.name() == htmlTag) { |
| m_tree.insertHTMLHtmlStartTagBeforeHTML(WTFMove(token)); |
| m_insertionMode = InsertionMode::BeforeHead; |
| return; |
| } |
| defaultForBeforeHTML(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHead); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHead: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == headTag) { |
| m_tree.insertHTMLHeadElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InHead; |
| return; |
| } |
| defaultForBeforeHead(); |
| ASSERT(m_insertionMode == InsertionMode::InHead); |
| FALLTHROUGH; |
| case InsertionMode::InHead: |
| if (processStartTagForInHead(WTFMove(token))) |
| return; |
| defaultForInHead(); |
| ASSERT(m_insertionMode == InsertionMode::AfterHead); |
| FALLTHROUGH; |
| case InsertionMode::AfterHead: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == bodyTag) { |
| m_framesetOk = false; |
| m_tree.insertHTMLBodyElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InBody; |
| return; |
| } |
| if (token.name() == framesetTag) { |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InFrameset; |
| return; |
| } |
| if (token.name() == baseTag |
| || token.name() == basefontTag |
| || token.name() == bgsoundTag |
| || token.name() == linkTag |
| || token.name() == metaTag |
| || token.name() == noframesTag |
| || token.name() == scriptTag |
| || token.name() == styleTag |
| || token.name() == templateTag |
| || token.name() == titleTag) { |
| parseError(token); |
| ASSERT(!m_tree.headStackItem().isNull()); |
| m_tree.openElements().pushHTMLHeadElement(HTMLStackItem(m_tree.headStackItem())); |
| processStartTagForInHead(WTFMove(token)); |
| m_tree.openElements().removeHTMLHeadElement(m_tree.head()); |
| return; |
| } |
| if (token.name() == headTag) { |
| parseError(token); |
| return; |
| } |
| defaultForAfterHead(); |
| ASSERT(m_insertionMode == InsertionMode::InBody); |
| FALLTHROUGH; |
| case InsertionMode::InBody: |
| processStartTagForInBody(WTFMove(token)); |
| break; |
| case InsertionMode::InTable: |
| processStartTagForInTable(WTFMove(token)); |
| break; |
| case InsertionMode::InCaption: |
| if (isCaptionColOrColgroupTag(token.name()) |
| || isTableBodyContextTag(token.name()) |
| || isTableCellContextTag(token.name()) |
| || token.name() == trTag) { |
| parseError(token); |
| if (!processCaptionEndTagForInCaption()) { |
| ASSERT(isParsingFragment()); |
| return; |
| } |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| processStartTagForInBody(WTFMove(token)); |
| break; |
| case InsertionMode::InColumnGroup: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == colTag) { |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == templateTag) { |
| processTemplateStartTag(WTFMove(token)); |
| return; |
| } |
| if (!processColgroupEndTagForInColumnGroup()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| processStartTag(WTFMove(token)); |
| break; |
| case InsertionMode::InTableBody: |
| if (token.name() == trTag) { |
| m_tree.openElements().popUntilTableBodyScopeMarker(); // How is there ever anything to pop? |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InRow; |
| return; |
| } |
| if (isTableCellContextTag(token.name())) { |
| parseError(token); |
| processFakeStartTag(trTag); |
| ASSERT(m_insertionMode == InsertionMode::InRow); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| if (isCaptionColOrColgroupTag(token.name()) || isTableBodyContextTag(token.name())) { |
| // FIXME: This is slow. |
| if (!m_tree.openElements().inTableScope(tbodyTag) && !m_tree.openElements().inTableScope(theadTag) && !m_tree.openElements().inTableScope(tfootTag)) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| parseError(token); |
| return; |
| } |
| m_tree.openElements().popUntilTableBodyScopeMarker(); |
| ASSERT(isTableBodyContextTag(m_tree.currentStackItem().localName())); |
| processFakeEndTag(m_tree.currentStackItem().localName()); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| processStartTagForInTable(WTFMove(token)); |
| break; |
| case InsertionMode::InRow: |
| if (isTableCellContextTag(token.name())) { |
| m_tree.openElements().popUntilTableRowScopeMarker(); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InCell; |
| m_tree.activeFormattingElements().appendMarker(); |
| return; |
| } |
| if (token.name() == trTag |
| || isCaptionColOrColgroupTag(token.name()) |
| || isTableBodyContextTag(token.name())) { |
| if (!processTrEndTagForInRow()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| ASSERT(m_insertionMode == InsertionMode::InTableBody); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| processStartTagForInTable(WTFMove(token)); |
| break; |
| case InsertionMode::InCell: |
| if (isCaptionColOrColgroupTag(token.name()) |
| || isTableCellContextTag(token.name()) |
| || token.name() == trTag |
| || isTableBodyContextTag(token.name())) { |
| // FIXME: This could be more efficient. |
| if (!m_tree.openElements().inTableScope(tdTag) && !m_tree.openElements().inTableScope(thTag)) { |
| ASSERT(isParsingFragment()); |
| parseError(token); |
| return; |
| } |
| closeTheCell(); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| processStartTagForInBody(WTFMove(token)); |
| break; |
| case InsertionMode::AfterBody: |
| case InsertionMode::AfterAfterBody: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| m_insertionMode = InsertionMode::InBody; |
| processStartTag(WTFMove(token)); |
| break; |
| case InsertionMode::InHeadNoscript: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == basefontTag |
| || token.name() == bgsoundTag |
| || token.name() == linkTag |
| || token.name() == metaTag |
| || token.name() == noframesTag |
| || token.name() == styleTag) { |
| bool didProcess = processStartTagForInHead(WTFMove(token)); |
| ASSERT_UNUSED(didProcess, didProcess); |
| return; |
| } |
| if (token.name() == htmlTag || token.name() == noscriptTag) { |
| parseError(token); |
| return; |
| } |
| defaultForInHeadNoscript(); |
| processToken(WTFMove(token)); |
| break; |
| case InsertionMode::InFrameset: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == framesetTag) { |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == frameTag) { |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == noframesTag) { |
| processStartTagForInHead(WTFMove(token)); |
| return; |
| } |
| parseError(token); |
| break; |
| case InsertionMode::AfterFrameset: |
| case InsertionMode::AfterAfterFrameset: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == noframesTag) { |
| processStartTagForInHead(WTFMove(token)); |
| return; |
| } |
| parseError(token); |
| break; |
| case InsertionMode::InSelectInTable: |
| if (token.name() == captionTag |
| || token.name() == tableTag |
| || isTableBodyContextTag(token.name()) |
| || token.name() == trTag |
| || isTableCellContextTag(token.name())) { |
| parseError(token); |
| AtomHTMLToken endSelect(HTMLToken::Type::EndTag, selectTag->localName()); |
| processEndTag(WTFMove(endSelect)); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| FALLTHROUGH; |
| case InsertionMode::InSelect: |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == optionTag) { |
| if (is<HTMLOptionElement>(m_tree.currentStackItem().node())) { |
| AtomHTMLToken endOption(HTMLToken::Type::EndTag, optionTag->localName()); |
| processEndTag(WTFMove(endOption)); |
| } |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == optgroupTag) { |
| if (is<HTMLOptionElement>(m_tree.currentStackItem().node())) { |
| AtomHTMLToken endOption(HTMLToken::Type::EndTag, optionTag->localName()); |
| processEndTag(WTFMove(endOption)); |
| } |
| if (is<HTMLOptGroupElement>(m_tree.currentStackItem().node())) { |
| AtomHTMLToken endOptgroup(HTMLToken::Type::EndTag, optgroupTag->localName()); |
| processEndTag(WTFMove(endOptgroup)); |
| } |
| m_tree.insertHTMLElement(WTFMove(token)); |
| return; |
| } |
| if (token.name() == selectTag) { |
| parseError(token); |
| AtomHTMLToken endSelect(HTMLToken::Type::EndTag, selectTag->localName()); |
| processEndTag(WTFMove(endSelect)); |
| return; |
| } |
| if (token.name() == inputTag || token.name() == keygenTag || token.name() == textareaTag) { |
| parseError(token); |
| if (!m_tree.openElements().inSelectScope(selectTag)) { |
| ASSERT(isParsingFragment()); |
| return; |
| } |
| AtomHTMLToken endSelect(HTMLToken::Type::EndTag, selectTag->localName()); |
| processEndTag(WTFMove(endSelect)); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == scriptTag) { |
| bool didProcess = processStartTagForInHead(WTFMove(token)); |
| ASSERT_UNUSED(didProcess, didProcess); |
| return; |
| } |
| if (token.name() == templateTag) { |
| processTemplateStartTag(WTFMove(token)); |
| return; |
| } |
| break; |
| case InsertionMode::InTableText: |
| defaultForInTableText(); |
| processStartTag(WTFMove(token)); |
| break; |
| case InsertionMode::Text: |
| ASSERT_NOT_REACHED(); |
| break; |
| case InsertionMode::TemplateContents: |
| if (token.name() == templateTag) { |
| processTemplateStartTag(WTFMove(token)); |
| return; |
| } |
| |
| if (token.name() == linkTag |
| || token.name() == scriptTag |
| || token.name() == styleTag |
| || token.name() == metaTag) { |
| processStartTagForInHead(WTFMove(token)); |
| return; |
| } |
| |
| InsertionMode insertionMode = InsertionMode::TemplateContents; |
| if (token.name() == colTag) |
| insertionMode = InsertionMode::InColumnGroup; |
| else if (isCaptionColOrColgroupTag(token.name()) || isTableBodyContextTag(token.name())) |
| insertionMode = InsertionMode::InTable; |
| else if (token.name() == trTag) |
| insertionMode = InsertionMode::InTableBody; |
| else if (isTableCellContextTag(token.name())) |
| insertionMode = InsertionMode::InRow; |
| else |
| insertionMode = InsertionMode::InBody; |
| |
| ASSERT(insertionMode != InsertionMode::TemplateContents); |
| ASSERT(m_templateInsertionModes.last() == InsertionMode::TemplateContents); |
| m_templateInsertionModes.last() = insertionMode; |
| m_insertionMode = insertionMode; |
| |
| processStartTag(WTFMove(token)); |
| break; |
| } |
| } |
| |
| void HTMLTreeBuilder::processHtmlStartTagForInBody(AtomHTMLToken&& token) |
| { |
| parseError(token); |
| if (m_tree.openElements().hasTemplateInHTMLScope()) { |
| ASSERT(isParsingTemplateContents()); |
| return; |
| } |
| m_tree.insertHTMLHtmlStartTagInBody(WTFMove(token)); |
| } |
| |
| bool HTMLTreeBuilder::processBodyEndTagForInBody(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| ASSERT(token.name() == bodyTag); |
| if (!m_tree.openElements().inScope(bodyTag->localName())) { |
| parseError(token); |
| return false; |
| } |
| notImplemented(); // Emit a more specific parse error based on stack contents. |
| m_insertionMode = InsertionMode::AfterBody; |
| return true; |
| } |
| |
| void HTMLTreeBuilder::processAnyOtherEndTagForInBody(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| for (auto* record = &m_tree.openElements().topRecord(); ; record = record->next()) { |
| HTMLStackItem& item = record->stackItem(); |
| if (item.matchesHTMLTag(token.name())) { |
| m_tree.generateImpliedEndTagsWithExclusion(token.name()); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(item.element()); |
| return; |
| } |
| if (isSpecialNode(item)) { |
| parseError(token); |
| return; |
| } |
| } |
| } |
| |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#parsing-main-inbody |
| void HTMLTreeBuilder::callTheAdoptionAgency(AtomHTMLToken& token) |
| { |
| // The adoption agency algorithm is N^2. We limit the number of iterations |
| // to stop from hanging the whole browser. This limit is specified in the |
| // adoption agency algorithm: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#parsing-main-inbody |
| static const int outerIterationLimit = 8; |
| static const int innerIterationLimit = 3; |
| |
| // 1, 2, 3 and 16 are covered by the for() loop. |
| for (int i = 0; i < outerIterationLimit; ++i) { |
| // 4. |
| RefPtr<Element> formattingElement = m_tree.activeFormattingElements().closestElementInScopeWithName(token.name()); |
| // 4.a |
| if (!formattingElement) |
| return processAnyOtherEndTagForInBody(WTFMove(token)); |
| // 4.c |
| if ((m_tree.openElements().contains(*formattingElement)) && !m_tree.openElements().inScope(*formattingElement)) { |
| parseError(token); |
| notImplemented(); // Check the stack of open elements for a more specific parse error. |
| return; |
| } |
| // 4.b |
| auto* formattingElementRecord = m_tree.openElements().find(*formattingElement); |
| if (!formattingElementRecord) { |
| parseError(token); |
| m_tree.activeFormattingElements().remove(*formattingElement); |
| return; |
| } |
| // 4.d |
| if (formattingElement != &m_tree.currentElement()) |
| parseError(token); |
| // 5. |
| auto* furthestBlock = m_tree.openElements().furthestBlockForFormattingElement(*formattingElement); |
| // 6. |
| if (!furthestBlock) { |
| m_tree.openElements().popUntilPopped(*formattingElement); |
| m_tree.activeFormattingElements().remove(*formattingElement); |
| return; |
| } |
| // 7. |
| ASSERT(furthestBlock->isAbove(*formattingElementRecord)); |
| auto& commonAncestor = formattingElementRecord->next()->stackItem(); |
| // 8. |
| HTMLFormattingElementList::Bookmark bookmark = m_tree.activeFormattingElements().bookmarkFor(*formattingElement); |
| // 9. |
| auto* node = furthestBlock; |
| auto* nextNode = node->next(); |
| auto* lastNode = furthestBlock; |
| // 9.1, 9.2, 9.3 and 9.11 are covered by the for() loop. |
| for (int i = 0; i < innerIterationLimit; ++i) { |
| // 9.4 |
| node = nextNode; |
| ASSERT(node); |
| nextNode = node->next(); // Save node->next() for the next iteration in case node is deleted in 9.5. |
| // 9.5 |
| if (!m_tree.activeFormattingElements().contains(node->element())) { |
| m_tree.openElements().remove(node->element()); |
| node = 0; |
| continue; |
| } |
| // 9.6 |
| if (node == formattingElementRecord) |
| break; |
| // 9.7 |
| auto newItem = m_tree.createElementFromSavedToken(node->stackItem()); |
| |
| HTMLFormattingElementList::Entry* nodeEntry = m_tree.activeFormattingElements().find(node->element()); |
| nodeEntry->replaceElement(HTMLStackItem(newItem)); |
| node->replaceElement(WTFMove(newItem)); |
| |
| // 9.8 |
| if (lastNode == furthestBlock) |
| bookmark.moveToAfter(*nodeEntry); |
| // 9.9 |
| m_tree.reparent(*node, *lastNode); |
| // 9.10 |
| lastNode = node; |
| } |
| // 10. |
| m_tree.insertAlreadyParsedChild(commonAncestor, *lastNode); |
| // 11. |
| auto newItem = m_tree.createElementFromSavedToken(formattingElementRecord->stackItem()); |
| // 12. & 13. |
| m_tree.takeAllChildrenAndReparent(newItem, *furthestBlock); |
| // 14. |
| m_tree.activeFormattingElements().swapTo(*formattingElement, HTMLStackItem(newItem), bookmark); |
| // 15. |
| m_tree.openElements().remove(*formattingElement); |
| m_tree.openElements().insertAbove(WTFMove(newItem), *furthestBlock); |
| } |
| } |
| |
| void HTMLTreeBuilder::resetInsertionModeAppropriately() |
| { |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#reset-the-insertion-mode-appropriately |
| bool last = false; |
| for (auto* record = &m_tree.openElements().topRecord(); ; record = record->next()) { |
| auto* item = &record->stackItem(); |
| if (&item->node() == &m_tree.openElements().rootNode()) { |
| last = true; |
| bool shouldCreateItem = isParsingFragment(); |
| if (shouldCreateItem) |
| item = &m_fragmentContext.contextElementStackItem(); |
| } |
| |
| if (item->hasTagName(templateTag)) { |
| m_insertionMode = m_templateInsertionModes.last(); |
| return; |
| } |
| |
| if (item->hasTagName(selectTag)) { |
| if (!last) { |
| while (&item->node() != &m_tree.openElements().rootNode() && !item->hasTagName(templateTag)) { |
| record = record->next(); |
| item = &record->stackItem(); |
| if (is<HTMLTableElement>(item->node())) { |
| m_insertionMode = InsertionMode::InSelectInTable; |
| return; |
| } |
| } |
| } |
| m_insertionMode = InsertionMode::InSelect; |
| return; |
| } |
| if (item->hasTagName(tdTag) || item->hasTagName(thTag)) { |
| m_insertionMode = InsertionMode::InCell; |
| return; |
| } |
| if (item->hasTagName(trTag)) { |
| m_insertionMode = InsertionMode::InRow; |
| return; |
| } |
| if (item->hasTagName(tbodyTag) || item->hasTagName(theadTag) || item->hasTagName(tfootTag)) { |
| m_insertionMode = InsertionMode::InTableBody; |
| return; |
| } |
| if (item->hasTagName(captionTag)) { |
| m_insertionMode = InsertionMode::InCaption; |
| return; |
| } |
| if (item->hasTagName(colgroupTag)) { |
| m_insertionMode = InsertionMode::InColumnGroup; |
| return; |
| } |
| if (is<HTMLTableElement>(item->node())) { |
| m_insertionMode = InsertionMode::InTable; |
| return; |
| } |
| if (item->hasTagName(headTag)) { |
| if (!m_fragmentContext.fragment() || &m_fragmentContext.contextElement() != &item->node()) { |
| m_insertionMode = InsertionMode::InHead; |
| return; |
| } |
| m_insertionMode = InsertionMode::InBody; |
| return; |
| } |
| if (item->hasTagName(bodyTag)) { |
| m_insertionMode = InsertionMode::InBody; |
| return; |
| } |
| if (item->hasTagName(framesetTag)) { |
| m_insertionMode = InsertionMode::InFrameset; |
| return; |
| } |
| if (item->hasTagName(htmlTag)) { |
| if (!m_tree.headStackItem().isNull()) { |
| m_insertionMode = InsertionMode::AfterHead; |
| return; |
| } |
| ASSERT(isParsingFragment()); |
| m_insertionMode = InsertionMode::BeforeHead; |
| return; |
| } |
| if (last) { |
| ASSERT(isParsingFragment()); |
| m_insertionMode = InsertionMode::InBody; |
| return; |
| } |
| } |
| } |
| |
| void HTMLTreeBuilder::processEndTagForInTableBody(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| if (isTableBodyContextTag(token.name())) { |
| if (!m_tree.openElements().inTableScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.openElements().popUntilTableBodyScopeMarker(); |
| m_tree.openElements().pop(); |
| m_insertionMode = InsertionMode::InTable; |
| return; |
| } |
| if (token.name() == tableTag) { |
| // FIXME: This is slow. |
| if (!m_tree.openElements().inTableScope(tbodyTag) && !m_tree.openElements().inTableScope(theadTag) && !m_tree.openElements().inTableScope(tfootTag)) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| parseError(token); |
| return; |
| } |
| m_tree.openElements().popUntilTableBodyScopeMarker(); |
| ASSERT(isTableBodyContextTag(m_tree.currentStackItem().localName())); |
| processFakeEndTag(m_tree.currentStackItem().localName()); |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == bodyTag |
| || isCaptionColOrColgroupTag(token.name()) |
| || token.name() == htmlTag |
| || isTableCellContextTag(token.name()) |
| || token.name() == trTag) { |
| parseError(token); |
| return; |
| } |
| processEndTagForInTable(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::processEndTagForInRow(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| if (token.name() == trTag) { |
| processTrEndTagForInRow(); |
| return; |
| } |
| if (token.name() == tableTag) { |
| if (!processTrEndTagForInRow()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| ASSERT(m_insertionMode == InsertionMode::InTableBody); |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| if (isTableBodyContextTag(token.name())) { |
| if (!m_tree.openElements().inTableScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| processFakeEndTag(trTag); |
| ASSERT(m_insertionMode == InsertionMode::InTableBody); |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == bodyTag |
| || isCaptionColOrColgroupTag(token.name()) |
| || token.name() == htmlTag |
| || isTableCellContextTag(token.name())) { |
| parseError(token); |
| return; |
| } |
| processEndTagForInTable(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::processEndTagForInCell(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| if (isTableCellContextTag(token.name())) { |
| if (!m_tree.openElements().inTableScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| m_tree.activeFormattingElements().clearToLastMarker(); |
| m_insertionMode = InsertionMode::InRow; |
| return; |
| } |
| if (token.name() == bodyTag |
| || isCaptionColOrColgroupTag(token.name()) |
| || token.name() == htmlTag) { |
| parseError(token); |
| return; |
| } |
| if (token.name() == tableTag |
| || token.name() == trTag |
| || isTableBodyContextTag(token.name())) { |
| if (!m_tree.openElements().inTableScope(token.name())) { |
| ASSERT(isTableBodyContextTag(token.name()) || m_tree.openElements().inTableScope(templateTag) || isParsingFragment()); |
| parseError(token); |
| return; |
| } |
| closeTheCell(); |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| processEndTagForInBody(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::processEndTagForInBody(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| if (token.name() == bodyTag) { |
| processBodyEndTagForInBody(WTFMove(token)); |
| return; |
| } |
| if (token.name() == htmlTag) { |
| AtomHTMLToken endBody(HTMLToken::Type::EndTag, bodyTag->localName()); |
| if (processBodyEndTagForInBody(WTFMove(endBody))) |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == addressTag |
| || token.name() == articleTag |
| || token.name() == asideTag |
| || token.name() == blockquoteTag |
| || token.name() == buttonTag |
| || token.name() == centerTag |
| || token.name() == detailsTag |
| || token.name() == dialogTag |
| || token.name() == dirTag |
| || token.name() == divTag |
| || token.name() == dlTag |
| || token.name() == fieldsetTag |
| || token.name() == figcaptionTag |
| || token.name() == figureTag |
| || token.name() == footerTag |
| || token.name() == headerTag |
| || token.name() == hgroupTag |
| || token.name() == listingTag |
| || token.name() == mainTag |
| || token.name() == menuTag |
| || token.name() == navTag |
| || token.name() == olTag |
| || token.name() == preTag |
| || token.name() == sectionTag |
| || token.name() == summaryTag |
| || token.name() == ulTag) { |
| if (!m_tree.openElements().inScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| return; |
| } |
| if (token.name() == formTag) { |
| if (!isParsingTemplateContents()) { |
| RefPtr<Element> formElement = m_tree.takeForm(); |
| if (!formElement || !m_tree.openElements().inScope(*formElement)) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (&m_tree.currentNode() != formElement.get()) |
| parseError(token); |
| m_tree.openElements().remove(*formElement); |
| } else { |
| if (!m_tree.openElements().inScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentNode().hasTagName(formTag)) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| } |
| } |
| if (token.name() == pTag) { |
| if (!m_tree.openElements().inButtonScope(token.name())) { |
| parseError(token); |
| processFakeStartTag(pTag); |
| ASSERT(m_tree.openElements().inScope(token.name())); |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| m_tree.generateImpliedEndTagsWithExclusion(token.name()); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| return; |
| } |
| if (token.name() == liTag) { |
| if (!m_tree.openElements().inListItemScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTagsWithExclusion(token.name()); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| return; |
| } |
| if (token.name() == ddTag || token.name() == dtTag) { |
| if (!m_tree.openElements().inScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTagsWithExclusion(token.name()); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| return; |
| } |
| if (isNumberedHeaderTag(token.name())) { |
| if (!m_tree.openElements().hasNumberedHeaderElementInScope()) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilNumberedHeaderElementPopped(); |
| return; |
| } |
| if (HTMLConstructionSite::isFormattingTag(token.name())) { |
| callTheAdoptionAgency(token); |
| return; |
| } |
| if (token.name() == appletTag || token.name() == marqueeTag || token.name() == objectTag) { |
| if (!m_tree.openElements().inScope(token.name())) { |
| parseError(token); |
| return; |
| } |
| m_tree.generateImpliedEndTags(); |
| if (!m_tree.currentStackItem().matchesHTMLTag(token.name())) |
| parseError(token); |
| m_tree.openElements().popUntilPopped(token.name()); |
| m_tree.activeFormattingElements().clearToLastMarker(); |
| return; |
| } |
| if (token.name() == brTag) { |
| parseError(token); |
| processFakeStartTag(brTag); |
| return; |
| } |
| if (token.name() == templateTag) { |
| processTemplateEndTag(WTFMove(token)); |
| return; |
| } |
| processAnyOtherEndTagForInBody(WTFMove(token)); |
| } |
| |
| bool HTMLTreeBuilder::processCaptionEndTagForInCaption() |
| { |
| if (!m_tree.openElements().inTableScope(captionTag->localName())) { |
| ASSERT(isParsingFragment()); |
| // FIXME: parse error |
| return false; |
| } |
| m_tree.generateImpliedEndTags(); |
| // FIXME: parse error if (!m_tree.currentStackItem().hasTagName(captionTag)) |
| m_tree.openElements().popUntilPopped(captionTag->localName()); |
| m_tree.activeFormattingElements().clearToLastMarker(); |
| m_insertionMode = InsertionMode::InTable; |
| return true; |
| } |
| |
| bool HTMLTreeBuilder::processTrEndTagForInRow() |
| { |
| if (!m_tree.openElements().inTableScope(trTag)) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| // FIXME: parse error |
| return false; |
| } |
| m_tree.openElements().popUntilTableRowScopeMarker(); |
| ASSERT(m_tree.currentStackItem().hasTagName(trTag)); |
| m_tree.openElements().pop(); |
| m_insertionMode = InsertionMode::InTableBody; |
| return true; |
| } |
| |
| bool HTMLTreeBuilder::processTableEndTagForInTable() |
| { |
| if (!m_tree.openElements().inTableScope(tableTag)) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| // FIXME: parse error. |
| return false; |
| } |
| m_tree.openElements().popUntilPopped(tableTag->localName()); |
| resetInsertionModeAppropriately(); |
| return true; |
| } |
| |
| void HTMLTreeBuilder::processEndTagForInTable(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| if (token.name() == tableTag) { |
| processTableEndTagForInTable(); |
| return; |
| } |
| if (token.name() == bodyTag |
| || isCaptionColOrColgroupTag(token.name()) |
| || token.name() == htmlTag |
| || isTableBodyContextTag(token.name()) |
| || isTableCellContextTag(token.name()) |
| || token.name() == trTag) { |
| parseError(token); |
| return; |
| } |
| parseError(token); |
| // Is this redirection necessary here? |
| HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree); |
| processEndTagForInBody(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::processEndTag(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndTag); |
| switch (m_insertionMode) { |
| case InsertionMode::Initial: |
| defaultForInitial(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHTML); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHTML: |
| if (token.name() != headTag && token.name() != bodyTag && token.name() != htmlTag && token.name() != brTag) { |
| parseError(token); |
| return; |
| } |
| defaultForBeforeHTML(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHead); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHead: |
| if (token.name() != headTag && token.name() != bodyTag && token.name() != htmlTag && token.name() != brTag) { |
| parseError(token); |
| return; |
| } |
| defaultForBeforeHead(); |
| ASSERT(m_insertionMode == InsertionMode::InHead); |
| FALLTHROUGH; |
| case InsertionMode::InHead: |
| // 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 InsertionMode::AfterHead, that gets a little messy. |
| if (token.name() == templateTag) { |
| processTemplateEndTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == headTag) { |
| m_tree.openElements().popHTMLHeadElement(); |
| m_insertionMode = InsertionMode::AfterHead; |
| return; |
| } |
| if (token.name() != bodyTag && token.name() != htmlTag && token.name() != brTag) { |
| parseError(token); |
| return; |
| } |
| defaultForInHead(); |
| ASSERT(m_insertionMode == InsertionMode::AfterHead); |
| FALLTHROUGH; |
| case InsertionMode::AfterHead: |
| if (token.name() != bodyTag && token.name() != htmlTag && token.name() != brTag) { |
| parseError(token); |
| return; |
| } |
| defaultForAfterHead(); |
| ASSERT(m_insertionMode == InsertionMode::InBody); |
| FALLTHROUGH; |
| case InsertionMode::InBody: |
| processEndTagForInBody(WTFMove(token)); |
| break; |
| case InsertionMode::InTable: |
| processEndTagForInTable(WTFMove(token)); |
| break; |
| case InsertionMode::InCaption: |
| if (token.name() == captionTag) { |
| processCaptionEndTagForInCaption(); |
| return; |
| } |
| if (token.name() == tableTag) { |
| parseError(token); |
| if (!processCaptionEndTagForInCaption()) { |
| ASSERT(isParsingFragment()); |
| return; |
| } |
| processEndTag(WTFMove(token)); |
| return; |
| } |
| if (token.name() == bodyTag |
| || token.name() == colTag |
| || token.name() == colgroupTag |
| || token.name() == htmlTag |
| || isTableBodyContextTag(token.name()) |
| || isTableCellContextTag(token.name()) |
| || token.name() == trTag) { |
| parseError(token); |
| return; |
| } |
| processEndTagForInBody(WTFMove(token)); |
| break; |
| case InsertionMode::InColumnGroup: |
| if (token.name() == colgroupTag) { |
| processColgroupEndTagForInColumnGroup(); |
| return; |
| } |
| if (token.name() == colTag) { |
| parseError(token); |
| return; |
| } |
| if (token.name() == templateTag) { |
| processTemplateEndTag(WTFMove(token)); |
| return; |
| } |
| if (!processColgroupEndTagForInColumnGroup()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| return; |
| } |
| processEndTag(WTFMove(token)); |
| break; |
| case InsertionMode::InRow: |
| processEndTagForInRow(WTFMove(token)); |
| break; |
| case InsertionMode::InCell: |
| processEndTagForInCell(WTFMove(token)); |
| break; |
| case InsertionMode::InTableBody: |
| processEndTagForInTableBody(WTFMove(token)); |
| break; |
| case InsertionMode::AfterBody: |
| if (token.name() == htmlTag) { |
| if (isParsingFragment()) { |
| parseError(token); |
| return; |
| } |
| m_insertionMode = InsertionMode::AfterAfterBody; |
| return; |
| } |
| FALLTHROUGH; |
| case InsertionMode::AfterAfterBody: |
| ASSERT(m_insertionMode == InsertionMode::AfterBody || m_insertionMode == InsertionMode::AfterAfterBody); |
| parseError(token); |
| m_insertionMode = InsertionMode::InBody; |
| processEndTag(WTFMove(token)); |
| break; |
| case InsertionMode::InHeadNoscript: |
| if (token.name() == noscriptTag) { |
| ASSERT(m_tree.currentStackItem().hasTagName(noscriptTag)); |
| m_tree.openElements().pop(); |
| ASSERT(m_tree.currentStackItem().hasTagName(headTag)); |
| m_insertionMode = InsertionMode::InHead; |
| return; |
| } |
| if (token.name() != brTag) { |
| parseError(token); |
| return; |
| } |
| defaultForInHeadNoscript(); |
| processToken(WTFMove(token)); |
| break; |
| case InsertionMode::Text: |
| if (token.name() == scriptTag) { |
| // Pause ourselves so that parsing stops until the script can be processed by the caller. |
| ASSERT(m_tree.currentStackItem().hasTagName(scriptTag)); |
| if (scriptingContentIsAllowed(m_tree.parserContentPolicy())) |
| m_scriptToProcess = &downcast<HTMLScriptElement>(m_tree.currentElement()); |
| m_tree.openElements().pop(); |
| m_insertionMode = m_originalInsertionMode; |
| |
| // This token will not have been created by the tokenizer if a |
| // self-closing script tag was encountered and pre-HTML5 parser |
| // quirks are enabled. We must set the tokenizer's state to |
| // DataState explicitly if the tokenizer didn't have a chance to. |
| ASSERT(m_parser.tokenizer().isInDataState() || m_options.usePreHTML5ParserQuirks); |
| m_parser.tokenizer().setDataState(); |
| return; |
| } |
| m_tree.openElements().pop(); |
| m_insertionMode = m_originalInsertionMode; |
| break; |
| case InsertionMode::InFrameset: |
| if (token.name() == framesetTag) { |
| bool ignoreFramesetForFragmentParsing = m_tree.currentIsRootNode() || m_tree.openElements().hasTemplateInHTMLScope(); |
| if (ignoreFramesetForFragmentParsing) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| parseError(token); |
| return; |
| } |
| m_tree.openElements().pop(); |
| if (!isParsingFragment() && !m_tree.currentStackItem().hasTagName(framesetTag)) |
| m_insertionMode = InsertionMode::AfterFrameset; |
| return; |
| } |
| break; |
| case InsertionMode::AfterFrameset: |
| if (token.name() == htmlTag) { |
| m_insertionMode = InsertionMode::AfterAfterFrameset; |
| return; |
| } |
| FALLTHROUGH; |
| case InsertionMode::AfterAfterFrameset: |
| ASSERT(m_insertionMode == InsertionMode::AfterFrameset || m_insertionMode == InsertionMode::AfterAfterFrameset); |
| parseError(token); |
| break; |
| case InsertionMode::InSelectInTable: |
| if (token.name() == captionTag |
| || token.name() == tableTag |
| || isTableBodyContextTag(token.name()) |
| || token.name() == trTag |
| || isTableCellContextTag(token.name())) { |
| parseError(token); |
| if (m_tree.openElements().inTableScope(token.name())) { |
| AtomHTMLToken endSelect(HTMLToken::Type::EndTag, selectTag->localName()); |
| processEndTag(WTFMove(endSelect)); |
| processEndTag(WTFMove(token)); |
| } |
| return; |
| } |
| FALLTHROUGH; |
| case InsertionMode::InSelect: |
| ASSERT(m_insertionMode == InsertionMode::InSelect || m_insertionMode == InsertionMode::InSelectInTable); |
| if (token.name() == optgroupTag) { |
| if (is<HTMLOptionElement>(m_tree.currentStackItem().node()) && m_tree.oneBelowTop() && is<HTMLOptGroupElement>(m_tree.oneBelowTop()->node())) |
| processFakeEndTag(optionTag); |
| if (is<HTMLOptGroupElement>(m_tree.currentStackItem().node())) { |
| m_tree.openElements().pop(); |
| return; |
| } |
| parseError(token); |
| return; |
| } |
| if (token.name() == optionTag) { |
| if (is<HTMLOptionElement>(m_tree.currentStackItem().node())) { |
| m_tree.openElements().pop(); |
| return; |
| } |
| parseError(token); |
| return; |
| } |
| if (token.name() == selectTag) { |
| if (!m_tree.openElements().inSelectScope(token.name())) { |
| ASSERT(isParsingFragment()); |
| parseError(token); |
| return; |
| } |
| m_tree.openElements().popUntilPopped(selectTag->localName()); |
| resetInsertionModeAppropriately(); |
| return; |
| } |
| if (token.name() == templateTag) { |
| processTemplateEndTag(WTFMove(token)); |
| return; |
| } |
| break; |
| case InsertionMode::InTableText: |
| defaultForInTableText(); |
| processEndTag(WTFMove(token)); |
| break; |
| case InsertionMode::TemplateContents: |
| if (token.name() == templateTag) { |
| processTemplateEndTag(WTFMove(token)); |
| return; |
| } |
| break; |
| } |
| } |
| |
| void HTMLTreeBuilder::processComment(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::Comment); |
| if (m_insertionMode == InsertionMode::Initial |
| || m_insertionMode == InsertionMode::BeforeHTML |
| || m_insertionMode == InsertionMode::AfterAfterBody |
| || m_insertionMode == InsertionMode::AfterAfterFrameset) { |
| m_tree.insertCommentOnDocument(WTFMove(token)); |
| return; |
| } |
| if (m_insertionMode == InsertionMode::AfterBody) { |
| m_tree.insertCommentOnHTMLHtmlElement(WTFMove(token)); |
| return; |
| } |
| if (m_insertionMode == InsertionMode::InTableText) { |
| defaultForInTableText(); |
| processComment(WTFMove(token)); |
| return; |
| } |
| m_tree.insertComment(WTFMove(token)); |
| } |
| |
| void HTMLTreeBuilder::processCharacter(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::Character); |
| ExternalCharacterTokenBuffer buffer(token); |
| processCharacterBuffer(buffer); |
| } |
| |
| #if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS_FAMILY) |
| |
| // FIXME: Extract the following iOS-specific code into a separate file. |
| // From the string 4089961010, creates a link of the form <a href="tel:4089961010">4089961010</a> and inserts it. |
| void HTMLTreeBuilder::insertPhoneNumberLink(const String& string) |
| { |
| Attribute attribute(HTMLNames::hrefAttr, makeAtomString("tel:"_s, string)); |
| |
| const AtomString& aTagLocalName = aTag->localName(); |
| AtomHTMLToken aStartToken(HTMLToken::Type::StartTag, aTagLocalName, { WTFMove(attribute) }); |
| AtomHTMLToken aEndToken(HTMLToken::Type::EndTag, aTagLocalName); |
| |
| processStartTag(WTFMove(aStartToken)); |
| m_tree.executeQueuedTasks(); |
| m_tree.insertTextNode(string, NotAllWhitespace); |
| processEndTag(WTFMove(aEndToken)); |
| } |
| |
| // Locates the phone numbers in the string and deals with it |
| // 1. Appends the text before the phone number as a text node. |
| // 2. Wraps the phone number in a tel: link. |
| // 3. Goes back to step 1 if a phone number is found in the rest of the string. |
| // 4. Appends the rest of the string as a text node. |
| void HTMLTreeBuilder::linkifyPhoneNumbers(const String& string, WhitespaceMode whitespaceMode) |
| { |
| ASSERT(TelephoneNumberDetector::isSupported()); |
| |
| // relativeStartPosition and relativeEndPosition are the endpoints of the phone number range, |
| // relative to the scannerPosition |
| unsigned length = string.length(); |
| unsigned scannerPosition = 0; |
| int relativeStartPosition = 0; |
| int relativeEndPosition = 0; |
| |
| auto characters = StringView(string).upconvertedCharacters(); |
| |
| // While there's a phone number in the rest of the string... |
| while (scannerPosition < length && TelephoneNumberDetector::find(&characters[scannerPosition], length - scannerPosition, &relativeStartPosition, &relativeEndPosition)) { |
| // The convention in the Data Detectors framework is that the end position is the first character NOT in the phone number |
| // (that is, the length of the range is relativeEndPosition - relativeStartPosition). So substract 1 to get the same |
| // convention as the old WebCore phone number parser (so that the rest of the code is still valid if we want to go back |
| // to the old parser). |
| --relativeEndPosition; |
| |
| ASSERT(scannerPosition + relativeEndPosition < length); |
| |
| m_tree.insertTextNode(string.substring(scannerPosition, relativeStartPosition), whitespaceMode); |
| insertPhoneNumberLink(string.substring(scannerPosition + relativeStartPosition, relativeEndPosition - relativeStartPosition + 1)); |
| |
| scannerPosition += relativeEndPosition + 1; |
| } |
| |
| // Append the rest as a text node. |
| if (scannerPosition > 0) { |
| if (scannerPosition < length) { |
| String after = string.substring(scannerPosition, length - scannerPosition); |
| m_tree.insertTextNode(after, whitespaceMode); |
| } |
| } else |
| m_tree.insertTextNode(string, whitespaceMode); |
| } |
| |
| // Looks at the ancestors of the element to determine whether we're inside an element which disallows parsing phone numbers. |
| static inline bool disallowTelephoneNumberParsing(const ContainerNode& node) |
| { |
| return node.isLink() |
| || node.hasTagName(aTag) |
| || node.hasTagName(scriptTag) |
| || is<HTMLFormControlElement>(node) |
| || node.hasTagName(styleTag) |
| || node.hasTagName(ttTag) |
| || node.hasTagName(preTag) |
| || node.hasTagName(codeTag); |
| } |
| |
| static inline bool shouldParseTelephoneNumbersInNode(const ContainerNode& node) |
| { |
| for (const ContainerNode* ancestor = &node; ancestor; ancestor = ancestor->parentNode()) { |
| if (disallowTelephoneNumberParsing(*ancestor)) |
| return false; |
| } |
| return true; |
| } |
| |
| #endif // ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS_FAMILY) |
| |
| void HTMLTreeBuilder::processCharacterBuffer(ExternalCharacterTokenBuffer& buffer) |
| { |
| ReprocessBuffer: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#parsing-main-inbody |
| // Note that this logic is different than the generic \r\n collapsing |
| // handled in the input stream preprocessor. This logic is here as an |
| // "authoring convenience" so folks can write: |
| // |
| // <pre> |
| // lorem ipsum |
| // lorem ipsum |
| // </pre> |
| // |
| // without getting an extra newline at the start of their <pre> element. |
| if (m_shouldSkipLeadingNewline) { |
| m_shouldSkipLeadingNewline = false; |
| buffer.skipAtMostOneLeadingNewline(); |
| if (buffer.isEmpty()) |
| return; |
| } |
| |
| switch (m_insertionMode) { |
| case InsertionMode::Initial: |
| buffer.skipLeadingWhitespace(); |
| if (buffer.isEmpty()) |
| return; |
| defaultForInitial(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHTML); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHTML: |
| buffer.skipLeadingWhitespace(); |
| if (buffer.isEmpty()) |
| return; |
| defaultForBeforeHTML(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHead); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHead: |
| buffer.skipLeadingWhitespace(); |
| if (buffer.isEmpty()) |
| return; |
| defaultForBeforeHead(); |
| ASSERT(m_insertionMode == InsertionMode::InHead); |
| FALLTHROUGH; |
| case InsertionMode::InHead: { |
| String leadingWhitespace = buffer.takeLeadingWhitespace(); |
| if (!leadingWhitespace.isEmpty()) |
| m_tree.insertTextNode(leadingWhitespace, AllWhitespace); |
| if (buffer.isEmpty()) |
| return; |
| defaultForInHead(); |
| ASSERT(m_insertionMode == InsertionMode::AfterHead); |
| FALLTHROUGH; |
| } |
| case InsertionMode::AfterHead: { |
| String leadingWhitespace = buffer.takeLeadingWhitespace(); |
| if (!leadingWhitespace.isEmpty()) |
| m_tree.insertTextNode(leadingWhitespace, AllWhitespace); |
| if (buffer.isEmpty()) |
| return; |
| defaultForAfterHead(); |
| ASSERT(m_insertionMode == InsertionMode::InBody); |
| FALLTHROUGH; |
| } |
| case InsertionMode::InBody: |
| case InsertionMode::InCaption: |
| case InsertionMode::InCell: |
| case InsertionMode::TemplateContents: |
| processCharacterBufferForInBody(buffer); |
| break; |
| case InsertionMode::InTable: |
| case InsertionMode::InTableBody: |
| case InsertionMode::InRow: |
| ASSERT(m_pendingTableCharacters.isEmpty()); |
| if (is<HTMLTableElement>(m_tree.currentStackItem().node()) |
| || m_tree.currentStackItem().hasTagName(HTMLNames::tbodyTag) |
| || m_tree.currentStackItem().hasTagName(HTMLNames::tfootTag) |
| || m_tree.currentStackItem().hasTagName(HTMLNames::theadTag) |
| || m_tree.currentStackItem().hasTagName(HTMLNames::trTag)) { |
| |
| m_originalInsertionMode = m_insertionMode; |
| m_insertionMode = InsertionMode::InTableText; |
| // Note that we fall through to the InsertionMode::InTableText case below. |
| } else { |
| HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree); |
| processCharacterBufferForInBody(buffer); |
| break; |
| } |
| FALLTHROUGH; |
| case InsertionMode::InTableText: |
| buffer.giveRemainingTo(m_pendingTableCharacters); |
| break; |
| case InsertionMode::InColumnGroup: { |
| String leadingWhitespace = buffer.takeLeadingWhitespace(); |
| if (!leadingWhitespace.isEmpty()) |
| m_tree.insertTextNode(leadingWhitespace, AllWhitespace); |
| if (buffer.isEmpty()) |
| return; |
| if (!processColgroupEndTagForInColumnGroup()) { |
| ASSERT(isParsingFragmentOrTemplateContents()); |
| // The spec tells us to drop these characters on the floor. |
| buffer.skipLeadingNonWhitespace(); |
| if (buffer.isEmpty()) |
| return; |
| } |
| goto ReprocessBuffer; |
| } |
| case InsertionMode::AfterBody: |
| case InsertionMode::AfterAfterBody: |
| // FIXME: parse error |
| m_insertionMode = InsertionMode::InBody; |
| goto ReprocessBuffer; |
| case InsertionMode::Text: |
| m_tree.insertTextNode(buffer.takeRemaining()); |
| break; |
| case InsertionMode::InHeadNoscript: { |
| String leadingWhitespace = buffer.takeLeadingWhitespace(); |
| if (!leadingWhitespace.isEmpty()) |
| m_tree.insertTextNode(leadingWhitespace, AllWhitespace); |
| if (buffer.isEmpty()) |
| return; |
| defaultForInHeadNoscript(); |
| goto ReprocessBuffer; |
| } |
| case InsertionMode::InFrameset: |
| case InsertionMode::AfterFrameset: { |
| String leadingWhitespace = buffer.takeRemainingWhitespace(); |
| if (!leadingWhitespace.isEmpty()) |
| m_tree.insertTextNode(leadingWhitespace, AllWhitespace); |
| // FIXME: We should generate a parse error if we skipped over any |
| // non-whitespace characters. |
| break; |
| } |
| case InsertionMode::InSelectInTable: |
| case InsertionMode::InSelect: |
| m_tree.insertTextNode(buffer.takeRemaining()); |
| break; |
| case InsertionMode::AfterAfterFrameset: { |
| String leadingWhitespace = buffer.takeRemainingWhitespace(); |
| if (!leadingWhitespace.isEmpty()) { |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertTextNode(leadingWhitespace, AllWhitespace); |
| } |
| // FIXME: We should generate a parse error if we skipped over any |
| // non-whitespace characters. |
| break; |
| } |
| } |
| } |
| |
| void HTMLTreeBuilder::processCharacterBufferForInBody(ExternalCharacterTokenBuffer& buffer) |
| { |
| m_tree.reconstructTheActiveFormattingElements(); |
| auto whitespaceMode = buffer.isAll8BitData() ? WhitespaceUnknown : NotAllWhitespace; |
| String characters = buffer.takeRemaining(); |
| #if ENABLE(TELEPHONE_NUMBER_DETECTION) && PLATFORM(IOS_FAMILY) |
| if (!isParsingFragment() && m_tree.isTelephoneNumberParsingEnabled() && shouldParseTelephoneNumbersInNode(m_tree.currentNode()) && TelephoneNumberDetector::isSupported()) |
| linkifyPhoneNumbers(characters, whitespaceMode); |
| else |
| m_tree.insertTextNode(characters, whitespaceMode); |
| #else |
| m_tree.insertTextNode(characters, whitespaceMode); |
| #endif |
| if (m_framesetOk && !isAllWhitespaceOrReplacementCharacters(characters)) |
| m_framesetOk = false; |
| } |
| |
| void HTMLTreeBuilder::processEndOfFile(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::EndOfFile); |
| switch (m_insertionMode) { |
| case InsertionMode::Initial: |
| defaultForInitial(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHTML); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHTML: |
| defaultForBeforeHTML(); |
| ASSERT(m_insertionMode == InsertionMode::BeforeHead); |
| FALLTHROUGH; |
| case InsertionMode::BeforeHead: |
| defaultForBeforeHead(); |
| ASSERT(m_insertionMode == InsertionMode::InHead); |
| FALLTHROUGH; |
| case InsertionMode::InHead: |
| defaultForInHead(); |
| ASSERT(m_insertionMode == InsertionMode::AfterHead); |
| FALLTHROUGH; |
| case InsertionMode::AfterHead: |
| defaultForAfterHead(); |
| ASSERT(m_insertionMode == InsertionMode::InBody); |
| FALLTHROUGH; |
| case InsertionMode::InBody: |
| case InsertionMode::InCell: |
| case InsertionMode::InCaption: |
| case InsertionMode::InRow: |
| notImplemented(); // Emit parse error based on what elements are still open. |
| if (!m_templateInsertionModes.isEmpty()) { |
| if (processEndOfFileForInTemplateContents(WTFMove(token))) |
| return; |
| } |
| break; |
| case InsertionMode::AfterBody: |
| case InsertionMode::AfterAfterBody: |
| break; |
| case InsertionMode::InHeadNoscript: |
| defaultForInHeadNoscript(); |
| processEndOfFile(WTFMove(token)); |
| return; |
| case InsertionMode::AfterFrameset: |
| case InsertionMode::AfterAfterFrameset: |
| break; |
| case InsertionMode::InColumnGroup: |
| if (m_tree.currentIsRootNode()) { |
| ASSERT(isParsingFragment()); |
| return; // FIXME: Should we break here instead of returning? |
| } |
| ASSERT(m_tree.currentNode().hasTagName(colgroupTag) || m_tree.currentNode().hasTagName(templateTag)); |
| processColgroupEndTagForInColumnGroup(); |
| FALLTHROUGH; |
| case InsertionMode::InFrameset: |
| case InsertionMode::InTable: |
| case InsertionMode::InTableBody: |
| case InsertionMode::InSelectInTable: |
| case InsertionMode::InSelect: |
| ASSERT(m_insertionMode == InsertionMode::InSelect || m_insertionMode == InsertionMode::InSelectInTable || m_insertionMode == InsertionMode::InTable || m_insertionMode == InsertionMode::InFrameset || m_insertionMode == InsertionMode::InTableBody || m_insertionMode == InsertionMode::InColumnGroup); |
| if (&m_tree.currentNode() != &m_tree.openElements().rootNode()) |
| parseError(token); |
| if (!m_templateInsertionModes.isEmpty()) { |
| if (processEndOfFileForInTemplateContents(WTFMove(token))) |
| return; |
| } |
| break; |
| case InsertionMode::InTableText: |
| defaultForInTableText(); |
| processEndOfFile(WTFMove(token)); |
| return; |
| case InsertionMode::Text: |
| parseError(token); |
| if (m_tree.currentStackItem().hasTagName(scriptTag)) |
| notImplemented(); // mark the script element as "already started". |
| m_tree.openElements().pop(); |
| ASSERT(m_originalInsertionMode != InsertionMode::Text); |
| m_insertionMode = m_originalInsertionMode; |
| processEndOfFile(WTFMove(token)); |
| return; |
| case InsertionMode::TemplateContents: |
| if (processEndOfFileForInTemplateContents(WTFMove(token))) |
| return; |
| break; |
| } |
| m_tree.openElements().popAll(); |
| } |
| |
| void HTMLTreeBuilder::defaultForInitial() |
| { |
| notImplemented(); |
| m_tree.setDefaultCompatibilityMode(); |
| // FIXME: parse error |
| m_insertionMode = InsertionMode::BeforeHTML; |
| } |
| |
| void HTMLTreeBuilder::defaultForBeforeHTML() |
| { |
| AtomHTMLToken startHTML(HTMLToken::Type::StartTag, htmlTag->localName()); |
| m_tree.insertHTMLHtmlStartTagBeforeHTML(WTFMove(startHTML)); |
| m_insertionMode = InsertionMode::BeforeHead; |
| } |
| |
| void HTMLTreeBuilder::defaultForBeforeHead() |
| { |
| AtomHTMLToken startHead(HTMLToken::Type::StartTag, headTag->localName()); |
| processStartTag(WTFMove(startHead)); |
| } |
| |
| void HTMLTreeBuilder::defaultForInHead() |
| { |
| AtomHTMLToken endHead(HTMLToken::Type::EndTag, headTag->localName()); |
| processEndTag(WTFMove(endHead)); |
| } |
| |
| void HTMLTreeBuilder::defaultForInHeadNoscript() |
| { |
| AtomHTMLToken endNoscript(HTMLToken::Type::EndTag, noscriptTag->localName()); |
| processEndTag(WTFMove(endNoscript)); |
| } |
| |
| void HTMLTreeBuilder::defaultForAfterHead() |
| { |
| AtomHTMLToken startBody(HTMLToken::Type::StartTag, bodyTag->localName()); |
| processStartTag(WTFMove(startBody)); |
| m_framesetOk = true; |
| } |
| |
| void HTMLTreeBuilder::defaultForInTableText() |
| { |
| String characters = m_pendingTableCharacters.toString(); |
| m_pendingTableCharacters.clear(); |
| if (!isAllWhitespace(characters)) { |
| // FIXME: parse error |
| HTMLConstructionSite::RedirectToFosterParentGuard redirecter(m_tree); |
| m_tree.reconstructTheActiveFormattingElements(); |
| m_tree.insertTextNode(characters, NotAllWhitespace); |
| m_framesetOk = false; |
| m_insertionMode = m_originalInsertionMode; |
| return; |
| } |
| m_tree.insertTextNode(characters); |
| m_insertionMode = m_originalInsertionMode; |
| } |
| |
| bool HTMLTreeBuilder::processStartTagForInHead(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| if (token.name() == htmlTag) { |
| processHtmlStartTagForInBody(WTFMove(token)); |
| return true; |
| } |
| if (token.name() == baseTag |
| || token.name() == basefontTag |
| || token.name() == bgsoundTag |
| || token.name() == commandTag |
| || token.name() == linkTag |
| || token.name() == metaTag) { |
| m_tree.insertSelfClosingHTMLElement(WTFMove(token)); |
| // Note: The custom processing for the <meta> tag is done in HTMLMetaElement::process(). |
| return true; |
| } |
| if (token.name() == titleTag) { |
| processGenericRCDATAStartTag(WTFMove(token)); |
| return true; |
| } |
| if (token.name() == noscriptTag) { |
| if (m_options.scriptingFlag) { |
| processGenericRawTextStartTag(WTFMove(token)); |
| return true; |
| } |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_insertionMode = InsertionMode::InHeadNoscript; |
| return true; |
| } |
| if (token.name() == noframesTag || token.name() == styleTag) { |
| processGenericRawTextStartTag(WTFMove(token)); |
| return true; |
| } |
| if (token.name() == scriptTag) { |
| bool isSelfClosing = token.selfClosing(); |
| processScriptStartTag(WTFMove(token)); |
| if (m_options.usePreHTML5ParserQuirks && isSelfClosing) |
| processFakeEndTag(scriptTag); |
| return true; |
| } |
| if (token.name() == templateTag) { |
| m_framesetOk = false; |
| processTemplateStartTag(WTFMove(token)); |
| return true; |
| } |
| if (token.name() == headTag) { |
| parseError(token); |
| return true; |
| } |
| return false; |
| } |
| |
| void HTMLTreeBuilder::processGenericRCDATAStartTag(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_parser.tokenizer().setRCDATAState(); |
| m_originalInsertionMode = m_insertionMode; |
| m_insertionMode = InsertionMode::Text; |
| } |
| |
| void HTMLTreeBuilder::processGenericRawTextStartTag(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| m_tree.insertHTMLElement(WTFMove(token)); |
| m_parser.tokenizer().setRAWTEXTState(); |
| m_originalInsertionMode = m_insertionMode; |
| m_insertionMode = InsertionMode::Text; |
| } |
| |
| void HTMLTreeBuilder::processScriptStartTag(AtomHTMLToken&& token) |
| { |
| ASSERT(token.type() == HTMLToken::Type::StartTag); |
| m_tree.insertScriptElement(WTFMove(token)); |
| m_parser.tokenizer().setScriptDataState(); |
| m_originalInsertionMode = m_insertionMode; |
| |
| TextPosition position = m_parser.textPosition(); |
| |
| m_scriptToProcessStartPosition = position; |
| |
| m_insertionMode = InsertionMode::Text; |
| } |
| |
| // http://www.whatwg.org/specs/web-apps/current-work/#adjusted-current-node |
| HTMLStackItem& HTMLTreeBuilder::adjustedCurrentStackItem() |
| { |
| ASSERT(!m_tree.isEmpty()); |
| if (isParsingFragment() && m_tree.openElements().hasOnlyOneElement()) |
| return m_fragmentContext.contextElementStackItem(); |
| |
| return m_tree.currentStackItem(); |
| } |
| |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#tree-construction |
| bool HTMLTreeBuilder::shouldProcessTokenInForeignContent(const AtomHTMLToken& token) |
| { |
| if (m_tree.isEmpty()) |
| return false; |
| HTMLStackItem& adjustedCurrentNode = adjustedCurrentStackItem(); |
| if (isInHTMLNamespace(adjustedCurrentNode)) |
| return false; |
| if (HTMLElementStack::isMathMLTextIntegrationPoint(adjustedCurrentNode)) { |
| if (token.type() == HTMLToken::Type::StartTag |
| && token.name() != MathMLNames::mglyphTag |
| && token.name() != MathMLNames::malignmarkTag) |
| return false; |
| if (token.type() == HTMLToken::Type::Character) |
| return false; |
| } |
| if (adjustedCurrentNode.hasTagName(MathMLNames::annotation_xmlTag) |
| && token.type() == HTMLToken::Type::StartTag |
| && token.name() == SVGNames::svgTag) |
| return false; |
| if (HTMLElementStack::isHTMLIntegrationPoint(adjustedCurrentNode)) { |
| if (token.type() == HTMLToken::Type::StartTag) |
| return false; |
| if (token.type() == HTMLToken::Type::Character) |
| return false; |
| } |
| if (token.type() == HTMLToken::Type::EndOfFile) |
| return false; |
| return true; |
| } |
| |
| static bool hasAttribute(const AtomHTMLToken& token, const QualifiedName& name) |
| { |
| return findAttribute(token.attributes(), name); |
| } |
| |
| void HTMLTreeBuilder::processTokenInForeignContent(AtomHTMLToken&& token) |
| { |
| HTMLStackItem& adjustedCurrentNode = adjustedCurrentStackItem(); |
| |
| switch (token.type()) { |
| case HTMLToken::Type::Uninitialized: |
| ASSERT_NOT_REACHED(); |
| break; |
| case HTMLToken::Type::DOCTYPE: |
| parseError(token); |
| break; |
| case HTMLToken::Type::StartTag: { |
| if (token.name() == bTag |
| || token.name() == bigTag |
| || token.name() == blockquoteTag |
| || token.name() == bodyTag |
| || token.name() == brTag |
| || token.name() == centerTag |
| || token.name() == codeTag |
| || token.name() == ddTag |
| || token.name() == divTag |
| || token.name() == dlTag |
| || token.name() == dtTag |
| || token.name() == emTag |
| || token.name() == embedTag |
| || isNumberedHeaderTag(token.name()) |
| || token.name() == headTag |
| || token.name() == hrTag |
| || token.name() == iTag |
| || token.name() == imgTag |
| || token.name() == liTag |
| || token.name() == listingTag |
| || token.name() == menuTag |
| || token.name() == metaTag |
| || token.name() == nobrTag |
| || token.name() == olTag |
| || token.name() == pTag |
| || token.name() == preTag |
| || token.name() == rubyTag |
| || token.name() == sTag |
| || token.name() == smallTag |
| || token.name() == spanTag |
| || token.name() == strongTag |
| || token.name() == strikeTag |
| || token.name() == subTag |
| || token.name() == supTag |
| || token.name() == tableTag |
| || token.name() == ttTag |
| || token.name() == uTag |
| || token.name() == ulTag |
| || token.name() == varTag |
| || (token.name() == fontTag && (hasAttribute(token, colorAttr) || hasAttribute(token, faceAttr) || hasAttribute(token, sizeAttr)))) { |
| parseError(token); |
| m_tree.openElements().popUntilForeignContentScopeMarker(); |
| processStartTag(WTFMove(token)); |
| return; |
| } |
| const AtomString& currentNamespace = adjustedCurrentNode.namespaceURI(); |
| if (currentNamespace == MathMLNames::mathmlNamespaceURI) |
| adjustMathMLAttributes(token); |
| if (currentNamespace == SVGNames::svgNamespaceURI) { |
| adjustSVGTagNameCase(token); |
| adjustSVGAttributes(token); |
| } |
| adjustForeignAttributes(token); |
| m_tree.insertForeignElement(WTFMove(token), currentNamespace); |
| break; |
| } |
| case HTMLToken::Type::EndTag: { |
| if (adjustedCurrentNode.namespaceURI() == SVGNames::svgNamespaceURI) |
| adjustSVGTagNameCase(token); |
| |
| if (token.name() == SVGNames::scriptTag && m_tree.currentStackItem().hasTagName(SVGNames::scriptTag)) { |
| if (scriptingContentIsAllowed(m_tree.parserContentPolicy())) |
| m_scriptToProcess = &downcast<SVGScriptElement>(m_tree.currentElement()); |
| m_tree.openElements().pop(); |
| return; |
| } |
| if (!isInHTMLNamespace(m_tree.currentStackItem())) { |
| // FIXME: This code just wants an Element* iterator, instead of an ElementRecord* |
| auto* nodeRecord = &m_tree.openElements().topRecord(); |
| if (nodeRecord->stackItem().localName() != token.name()) |
| parseError(token); |
| while (1) { |
| if (nodeRecord->stackItem().localName() == token.name()) { |
| m_tree.openElements().popUntilPopped(nodeRecord->element()); |
| return; |
| } |
| nodeRecord = nodeRecord->next(); |
| |
| if (isInHTMLNamespace(nodeRecord->stackItem())) |
| break; |
| } |
| } |
| // Otherwise, process the token according to the rules given in the section corresponding to the current insertion mode in HTML content. |
| processEndTag(WTFMove(token)); |
| break; |
| } |
| case HTMLToken::Type::Comment: |
| m_tree.insertComment(WTFMove(token)); |
| return; |
| case HTMLToken::Type::Character: { |
| String characters = token.characters(); |
| m_tree.insertTextNode(characters); |
| if (m_framesetOk && !isAllWhitespaceOrReplacementCharacters(characters)) |
| m_framesetOk = false; |
| break; |
| } |
| case HTMLToken::Type::EndOfFile: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| } |
| |
| void HTMLTreeBuilder::finished() |
| { |
| ASSERT(!m_destroyed); |
| |
| if (isParsingFragment()) |
| return; |
| |
| ASSERT(m_templateInsertionModes.isEmpty()); |
| |
| m_tree.finishedParsing(); |
| // The tree builder might have been destroyed as an indirect result of finishing the parsing. |
| } |
| |
| inline void HTMLTreeBuilder::parseError(const AtomHTMLToken&) |
| { |
| } |
| |
| } |