| /* |
| * Copyright (C) 2010 Google, Inc. All Rights Reserved. |
| * Copyright (C) 2011 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "core/html/parser/HTMLConstructionSite.h" |
| |
| #include <limits> |
| #include "bindings/core/v8/Microtask.h" |
| #include "bindings/core/v8/V8PerIsolateData.h" |
| #include "core/HTMLElementFactory.h" |
| #include "core/HTMLNames.h" |
| #include "core/dom/Comment.h" |
| #include "core/dom/DocumentFragment.h" |
| #include "core/dom/DocumentType.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h" |
| #include "core/dom/TemplateContentDocumentFragment.h" |
| #include "core/dom/Text.h" |
| #include "core/dom/ThrowOnDynamicMarkupInsertionCountIncrementer.h" |
| #include "core/dom/custom/CEReactionsScope.h" |
| #include "core/dom/custom/CustomElementDefinition.h" |
| #include "core/dom/custom/CustomElementDescriptor.h" |
| #include "core/dom/custom/CustomElementRegistry.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameClient.h" |
| #include "core/html/FormAssociated.h" |
| #include "core/html/HTMLFormElement.h" |
| #include "core/html/HTMLHtmlElement.h" |
| #include "core/html/HTMLPlugInElement.h" |
| #include "core/html/HTMLScriptElement.h" |
| #include "core/html/HTMLTemplateElement.h" |
| #include "core/html/parser/AtomicHTMLToken.h" |
| #include "core/html/parser/HTMLParserIdioms.h" |
| #include "core/html/parser/HTMLParserReentryPermit.h" |
| #include "core/html/parser/HTMLStackItem.h" |
| #include "core/html/parser/HTMLToken.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/svg/SVGScriptElement.h" |
| #include "platform/text/TextBreakIterator.h" |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| static const unsigned kMaximumHTMLParserDOMTreeDepth = 512; |
| |
| static inline void SetAttributes(Element* element, |
| AtomicHTMLToken* token, |
| ParserContentPolicy parser_content_policy) { |
| if (!ScriptingContentIsAllowed(parser_content_policy)) |
| element->StripScriptingAttributes(token->Attributes()); |
| element->ParserSetAttributes(token->Attributes()); |
| } |
| |
| static bool HasImpliedEndTag(const HTMLStackItem* item) { |
| return item->HasTagName(ddTag) || item->HasTagName(dtTag) || |
| item->HasTagName(liTag) || item->HasTagName(optionTag) || |
| item->HasTagName(optgroupTag) || item->HasTagName(pTag) || |
| item->HasTagName(rbTag) || item->HasTagName(rpTag) || |
| item->HasTagName(rtTag) || item->HasTagName(rtcTag); |
| } |
| |
| static bool ShouldUseLengthLimit(const ContainerNode& node) { |
| return !isHTMLScriptElement(node) && !isHTMLStyleElement(node) && |
| !isSVGScriptElement(node); |
| } |
| |
| static unsigned TextLengthLimitForContainer(const ContainerNode& node) { |
| return ShouldUseLengthLimit(node) ? Text::kDefaultLengthLimit |
| : std::numeric_limits<unsigned>::max(); |
| } |
| |
| static inline bool IsAllWhitespace(const String& string) { |
| return string.IsAllSpecialCharacters<IsHTMLSpace<UChar>>(); |
| } |
| |
| static inline void Insert(HTMLConstructionSiteTask& task) { |
| if (isHTMLTemplateElement(*task.parent)) |
| task.parent = toHTMLTemplateElement(task.parent.Get())->content(); |
| |
| // https://html.spec.whatwg.org/#insert-a-foreign-element |
| // 3.1, (3) Push (pop) an element queue |
| CEReactionsScope reactions; |
| if (task.next_child) |
| task.parent->ParserInsertBefore(task.child.Get(), *task.next_child); |
| else |
| task.parent->ParserAppendChild(task.child.Get()); |
| } |
| |
| static inline void ExecuteInsertTask(HTMLConstructionSiteTask& task) { |
| DCHECK_EQ(task.operation, HTMLConstructionSiteTask::kInsert); |
| |
| Insert(task); |
| |
| if (task.child->IsElementNode()) { |
| Element& child = ToElement(*task.child); |
| child.BeginParsingChildren(); |
| if (task.self_closing) |
| child.FinishParsingChildren(); |
| } |
| } |
| |
| static inline void ExecuteInsertTextTask(HTMLConstructionSiteTask& task) { |
| DCHECK_EQ(task.operation, HTMLConstructionSiteTask::kInsertText); |
| DCHECK(task.child->IsTextNode()); |
| |
| // Merge text nodes into previous ones if possible: |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html#insert-a-character |
| Text* new_text = ToText(task.child.Get()); |
| Node* previous_child = task.next_child ? task.next_child->previousSibling() |
| : task.parent->lastChild(); |
| if (previous_child && previous_child->IsTextNode()) { |
| Text* previous_text = ToText(previous_child); |
| unsigned length_limit = TextLengthLimitForContainer(*task.parent); |
| if (previous_text->length() + new_text->length() < length_limit) { |
| previous_text->ParserAppendData(new_text->data()); |
| return; |
| } |
| } |
| |
| Insert(task); |
| } |
| |
| static inline void ExecuteReparentTask(HTMLConstructionSiteTask& task) { |
| DCHECK_EQ(task.operation, HTMLConstructionSiteTask::kReparent); |
| |
| task.parent->ParserAppendChild(task.child); |
| } |
| |
| static inline void ExecuteInsertAlreadyParsedChildTask( |
| HTMLConstructionSiteTask& task) { |
| DCHECK_EQ(task.operation, |
| HTMLConstructionSiteTask::kInsertAlreadyParsedChild); |
| |
| Insert(task); |
| } |
| |
| static inline void ExecuteTakeAllChildrenTask(HTMLConstructionSiteTask& task) { |
| DCHECK_EQ(task.operation, HTMLConstructionSiteTask::kTakeAllChildren); |
| |
| task.parent->ParserTakeAllChildrenFrom(*task.OldParent()); |
| } |
| |
| void HTMLConstructionSite::ExecuteTask(HTMLConstructionSiteTask& task) { |
| DCHECK(task_queue_.IsEmpty()); |
| if (task.operation == HTMLConstructionSiteTask::kInsert) |
| return ExecuteInsertTask(task); |
| |
| if (task.operation == HTMLConstructionSiteTask::kInsertText) |
| return ExecuteInsertTextTask(task); |
| |
| // All the cases below this point are only used by the adoption agency. |
| |
| if (task.operation == HTMLConstructionSiteTask::kInsertAlreadyParsedChild) |
| return ExecuteInsertAlreadyParsedChildTask(task); |
| |
| if (task.operation == HTMLConstructionSiteTask::kReparent) |
| return ExecuteReparentTask(task); |
| |
| if (task.operation == HTMLConstructionSiteTask::kTakeAllChildren) |
| return ExecuteTakeAllChildrenTask(task); |
| |
| NOTREACHED(); |
| } |
| |
| // This is only needed for TextDocuments where we might have text nodes |
| // approaching the default length limit (~64k) and we don't want to break a text |
| // node in the middle of a combining character. |
| static unsigned FindBreakIndexBetween(const StringBuilder& string, |
| unsigned current_position, |
| unsigned proposed_break_index) { |
| DCHECK_LT(current_position, proposed_break_index); |
| DCHECK_LE(proposed_break_index, string.length()); |
| // The end of the string is always a valid break. |
| if (proposed_break_index == string.length()) |
| return proposed_break_index; |
| |
| // Latin-1 does not have breakable boundaries. If we ever moved to a different |
| // 8-bit encoding this could be wrong. |
| if (string.Is8Bit()) |
| return proposed_break_index; |
| |
| const UChar* break_search_characters = |
| string.Characters16() + current_position; |
| // We need at least two characters look-ahead to account for UTF-16 |
| // surrogates, but can't search off the end of the buffer! |
| unsigned break_search_length = |
| std::min(proposed_break_index - current_position + 2, |
| string.length() - current_position); |
| NonSharedCharacterBreakIterator it(break_search_characters, |
| break_search_length); |
| |
| if (it.IsBreak(proposed_break_index - current_position)) |
| return proposed_break_index; |
| |
| int adjusted_break_index_in_substring = |
| it.Preceding(proposed_break_index - current_position); |
| if (adjusted_break_index_in_substring > 0) |
| return current_position + adjusted_break_index_in_substring; |
| // We failed to find a breakable point, let the caller figure out what to do. |
| return 0; |
| } |
| |
| static String AtomizeIfAllWhitespace(const String& string, |
| WhitespaceMode whitespace_mode) { |
| // Strings composed entirely of whitespace are likely to be repeated. Turn |
| // them into AtomicString so we share a single string for each. |
| if (whitespace_mode == kAllWhitespace || |
| (whitespace_mode == kWhitespaceUnknown && IsAllWhitespace(string))) |
| return AtomicString(string).GetString(); |
| return string; |
| } |
| |
| void HTMLConstructionSite::FlushPendingText(FlushMode mode) { |
| if (pending_text_.IsEmpty()) |
| return; |
| |
| if (mode == kFlushIfAtTextLimit && |
| !ShouldUseLengthLimit(*pending_text_.parent)) |
| return; |
| |
| PendingText pending_text; |
| // Hold onto the current pending text on the stack so that queueTask doesn't |
| // recurse infinitely. |
| pending_text_.Swap(pending_text); |
| DCHECK(pending_text_.IsEmpty()); |
| |
| // Splitting text nodes into smaller chunks contradicts HTML5 spec, but is |
| // necessary for performance, see: |
| // https://bugs.webkit.org/show_bug.cgi?id=55898 |
| unsigned length_limit = TextLengthLimitForContainer(*pending_text.parent); |
| |
| unsigned current_position = 0; |
| const StringBuilder& string = pending_text.string_builder; |
| while (current_position < string.length()) { |
| unsigned proposed_break_index = |
| std::min(current_position + length_limit, string.length()); |
| unsigned break_index = |
| FindBreakIndexBetween(string, current_position, proposed_break_index); |
| DCHECK_LE(break_index, string.length()); |
| String substring = |
| string.Substring(current_position, break_index - current_position); |
| substring = AtomizeIfAllWhitespace(substring, pending_text.whitespace_mode); |
| |
| HTMLConstructionSiteTask task(HTMLConstructionSiteTask::kInsertText); |
| task.parent = pending_text.parent; |
| task.next_child = pending_text.next_child; |
| task.child = Text::Create(task.parent->GetDocument(), substring); |
| QueueTask(task); |
| |
| DCHECK_GT(break_index, current_position); |
| DCHECK_EQ(break_index - current_position, substring.length()); |
| DCHECK_EQ(ToText(task.child.Get())->length(), substring.length()); |
| current_position = break_index; |
| } |
| } |
| |
| void HTMLConstructionSite::QueueTask(const HTMLConstructionSiteTask& task) { |
| FlushPendingText(kFlushAlways); |
| DCHECK(pending_text_.IsEmpty()); |
| task_queue_.push_back(task); |
| } |
| |
| void HTMLConstructionSite::AttachLater(ContainerNode* parent, |
| Node* child, |
| bool self_closing) { |
| DCHECK(ScriptingContentIsAllowed(parser_content_policy_) || |
| !child->IsElementNode() || !ToElement(child)->IsScriptElement()); |
| DCHECK(PluginContentIsAllowed(parser_content_policy_) || |
| !IsHTMLPlugInElement(child)); |
| |
| HTMLConstructionSiteTask task(HTMLConstructionSiteTask::kInsert); |
| task.parent = parent; |
| task.child = child; |
| task.self_closing = self_closing; |
| |
| if (ShouldFosterParent()) { |
| FosterParent(task.child); |
| return; |
| } |
| |
| // Add as a sibling of the parent if we have reached the maximum depth |
| // allowed. |
| if (open_elements_.StackDepth() > kMaximumHTMLParserDOMTreeDepth && |
| task.parent->parentNode()) |
| task.parent = task.parent->parentNode(); |
| |
| DCHECK(task.parent); |
| QueueTask(task); |
| } |
| |
| void HTMLConstructionSite::ExecuteQueuedTasks() { |
| // This has no affect on pendingText, and we may have pendingText remaining |
| // after executing all other queued tasks. |
| const size_t size = task_queue_.size(); |
| if (!size) |
| return; |
| |
| // Copy the task queue into a local variable in case executeTask re-enters the |
| // parser. |
| TaskQueue queue; |
| queue.Swap(task_queue_); |
| |
| for (auto& task : queue) |
| ExecuteTask(task); |
| |
| // We might be detached now. |
| } |
| |
| HTMLConstructionSite::HTMLConstructionSite( |
| HTMLParserReentryPermit* reentry_permit, |
| Document& document, |
| ParserContentPolicy parser_content_policy) |
| : reentry_permit_(reentry_permit), |
| document_(&document), |
| attachment_root_(document), |
| parser_content_policy_(parser_content_policy), |
| is_parsing_fragment_(false), |
| redirect_attach_to_foster_parent_(false), |
| in_quirks_mode_(document.InQuirksMode()) { |
| DCHECK(document_->IsHTMLDocument() || document_->IsXHTMLDocument()); |
| } |
| |
| void HTMLConstructionSite::InitFragmentParsing(DocumentFragment* fragment, |
| Element* context_element) { |
| DCHECK(context_element); |
| DCHECK_EQ(document_, &fragment->GetDocument()); |
| DCHECK_EQ(in_quirks_mode_, fragment->GetDocument().InQuirksMode()); |
| DCHECK(!is_parsing_fragment_); |
| DCHECK(!form_); |
| |
| attachment_root_ = fragment; |
| is_parsing_fragment_ = true; |
| |
| if (!context_element->GetDocument().IsTemplateDocument()) |
| form_ = Traversal<HTMLFormElement>::FirstAncestorOrSelf(*context_element); |
| } |
| |
| HTMLConstructionSite::~HTMLConstructionSite() { |
| // Depending on why we're being destroyed it might be OK to forget queued |
| // tasks, but currently we don't expect to. |
| DCHECK(task_queue_.IsEmpty()); |
| // Currently we assume that text will never be the last token in the document |
| // and that we'll always queue some additional task to cause it to flush. |
| DCHECK(pending_text_.IsEmpty()); |
| } |
| |
| DEFINE_TRACE(HTMLConstructionSite) { |
| visitor->Trace(document_); |
| visitor->Trace(attachment_root_); |
| visitor->Trace(head_); |
| visitor->Trace(form_); |
| visitor->Trace(open_elements_); |
| visitor->Trace(active_formatting_elements_); |
| visitor->Trace(task_queue_); |
| visitor->Trace(pending_text_); |
| } |
| |
| void HTMLConstructionSite::Detach() { |
| // FIXME: We'd like to ASSERT here that we're canceling and not just |
| // discarding text that really should have made it into the DOM earlier, but |
| // there doesn't seem to be a nice way to do that. |
| pending_text_.Discard(); |
| document_ = nullptr; |
| attachment_root_ = nullptr; |
| } |
| |
| HTMLFormElement* HTMLConstructionSite::TakeForm() { |
| return form_.Release(); |
| } |
| |
| void HTMLConstructionSite::InsertHTMLHtmlStartTagBeforeHTML( |
| AtomicHTMLToken* token) { |
| DCHECK(document_); |
| HTMLHtmlElement* element = HTMLHtmlElement::Create(*document_); |
| SetAttributes(element, token, parser_content_policy_); |
| AttachLater(attachment_root_, element); |
| open_elements_.PushHTMLHtmlElement(HTMLStackItem::Create(element, token)); |
| |
| ExecuteQueuedTasks(); |
| element->InsertedByParser(); |
| } |
| |
| void HTMLConstructionSite::MergeAttributesFromTokenIntoElement( |
| AtomicHTMLToken* token, |
| Element* element) { |
| if (token->Attributes().IsEmpty()) |
| return; |
| |
| for (const auto& token_attribute : token->Attributes()) { |
| if (element->AttributesWithoutUpdate().FindIndex( |
| token_attribute.GetName()) == kNotFound) |
| element->setAttribute(token_attribute.GetName(), token_attribute.Value()); |
| } |
| } |
| |
| void HTMLConstructionSite::InsertHTMLHtmlStartTagInBody( |
| AtomicHTMLToken* token) { |
| // Fragments do not have a root HTML element, so any additional HTML elements |
| // encountered during fragment parsing should be ignored. |
| if (is_parsing_fragment_) |
| return; |
| |
| MergeAttributesFromTokenIntoElement(token, open_elements_.HtmlElement()); |
| } |
| |
| void HTMLConstructionSite::InsertHTMLBodyStartTagInBody( |
| AtomicHTMLToken* token) { |
| MergeAttributesFromTokenIntoElement(token, open_elements_.BodyElement()); |
| } |
| |
| void HTMLConstructionSite::SetDefaultCompatibilityMode() { |
| if (is_parsing_fragment_) |
| return; |
| SetCompatibilityMode(Document::kQuirksMode); |
| } |
| |
| void HTMLConstructionSite::SetCompatibilityMode( |
| Document::CompatibilityMode mode) { |
| in_quirks_mode_ = (mode == Document::kQuirksMode); |
| document_->SetCompatibilityMode(mode); |
| } |
| |
| void HTMLConstructionSite::SetCompatibilityModeFromDoctype( |
| const String& name, |
| const String& public_id, |
| const String& system_id) { |
| // There are three possible compatibility modes: |
| // Quirks - quirks mode emulates WinIE and NS4. CSS parsing is also relaxed in |
| // this mode, e.g., unit types can be omitted from numbers. |
| // Limited Quirks - This mode is identical to no-quirks mode except for its |
| // treatment of line-height in the inline box model. |
| // No Quirks - no quirks apply. Web pages will obey the specifications to the |
| // letter. |
| |
| // Check for Quirks Mode. |
| if (name != "html" || |
| public_id.StartsWith("+//Silmaril//dtd html Pro v0r11 19970101//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith( |
| "-//AdvaSoft Ltd//DTD HTML 3.0 asWedit + extensions//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//AS//DTD HTML 3.0 asWedit + extensions//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.0 Level 1//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.0 Level 2//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.0 Strict Level 1//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.0 Strict Level 2//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.0 Strict//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 2.1E//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 3.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 3.2 Final//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 3.2//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML 3//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Level 0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Level 1//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Level 2//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Level 3//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Strict Level 0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Strict Level 1//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Strict Level 2//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Strict Level 3//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML Strict//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//IETF//DTD HTML//", kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Metrius//DTD Metrius Presentational//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith( |
| "-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Microsoft//DTD Internet Explorer 2.0 HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Microsoft//DTD Internet Explorer 2.0 Tables//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith( |
| "-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Microsoft//DTD Internet Explorer 3.0 HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Microsoft//DTD Internet Explorer 3.0 Tables//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Netscape Comm. Corp.//DTD HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Netscape Comm. Corp.//DTD Strict HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//O'Reilly and Associates//DTD HTML 2.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith( |
| "-//O'Reilly and Associates//DTD HTML Extended 1.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith( |
| "-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//SoftQuad Software//DTD HoTMetaL PRO " |
| "6.0::19990601::extensions to HTML 4.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//SoftQuad//DTD HoTMetaL PRO " |
| "4.0::19971010::extensions to HTML 4.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Spyglass//DTD HTML 2.0 Extended//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//SQ//DTD HTML 2.0 HoTMetaL + extensions//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//Sun Microsystems Corp.//DTD HotJava HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith( |
| "-//Sun Microsystems Corp.//DTD HotJava Strict HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 3 1995-03-24//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 3.2 Draft//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 3.2 Final//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 3.2//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 3.2S Draft//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 4.0 Frameset//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML 4.0 Transitional//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML Experimental 19960712//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD HTML Experimental 970421//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD W3 HTML//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3O//DTD W3 HTML 3.0//", |
| kTextCaseASCIIInsensitive) || |
| DeprecatedEqualIgnoringCase(public_id, |
| "-//W3O//DTD W3 HTML Strict 3.0//EN//") || |
| public_id.StartsWith("-//WebTechs//DTD Mozilla HTML 2.0//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//WebTechs//DTD Mozilla HTML//", |
| kTextCaseASCIIInsensitive) || |
| DeprecatedEqualIgnoringCase(public_id, |
| "-/W3C/DTD HTML 4.0 Transitional/EN") || |
| DeprecatedEqualIgnoringCase(public_id, "HTML") || |
| DeprecatedEqualIgnoringCase( |
| system_id, |
| "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") || |
| (system_id.IsEmpty() && |
| public_id.StartsWith("-//W3C//DTD HTML 4.01 Frameset//", |
| kTextCaseASCIIInsensitive)) || |
| (system_id.IsEmpty() && |
| public_id.StartsWith("-//W3C//DTD HTML 4.01 Transitional//", |
| kTextCaseASCIIInsensitive))) { |
| SetCompatibilityMode(Document::kQuirksMode); |
| return; |
| } |
| |
| // Check for Limited Quirks Mode. |
| if (public_id.StartsWith("-//W3C//DTD XHTML 1.0 Frameset//", |
| kTextCaseASCIIInsensitive) || |
| public_id.StartsWith("-//W3C//DTD XHTML 1.0 Transitional//", |
| kTextCaseASCIIInsensitive) || |
| (!system_id.IsEmpty() && |
| public_id.StartsWith("-//W3C//DTD HTML 4.01 Frameset//", |
| kTextCaseASCIIInsensitive)) || |
| (!system_id.IsEmpty() && |
| public_id.StartsWith("-//W3C//DTD HTML 4.01 Transitional//", |
| kTextCaseASCIIInsensitive))) { |
| SetCompatibilityMode(Document::kLimitedQuirksMode); |
| return; |
| } |
| |
| // Otherwise we are No Quirks Mode. |
| SetCompatibilityMode(Document::kNoQuirksMode); |
| } |
| |
| void HTMLConstructionSite::ProcessEndOfFile() { |
| DCHECK(CurrentNode()); |
| Flush(kFlushAlways); |
| OpenElements()->PopAll(); |
| } |
| |
| void HTMLConstructionSite::FinishedParsing() { |
| // We shouldn't have any queued tasks but we might have pending text which we |
| // need to promote to tasks and execute. |
| DCHECK(task_queue_.IsEmpty()); |
| Flush(kFlushAlways); |
| document_->FinishedParsing(); |
| } |
| |
| void HTMLConstructionSite::InsertDoctype(AtomicHTMLToken* token) { |
| DCHECK_EQ(token->GetType(), HTMLToken::DOCTYPE); |
| |
| const String& public_id = |
| StringImpl::Create8BitIfPossible(token->PublicIdentifier()); |
| const String& system_id = |
| StringImpl::Create8BitIfPossible(token->SystemIdentifier()); |
| DocumentType* doctype = |
| DocumentType::Create(document_, token->GetName(), public_id, system_id); |
| AttachLater(attachment_root_, doctype); |
| |
| // DOCTYPE nodes are only processed when parsing fragments w/o |
| // contextElements, which never occurs. However, if we ever chose to support |
| // such, this code is subtly wrong, because context-less fragments can |
| // determine their own quirks mode, and thus change parsing rules (like <p> |
| // inside <table>). For now we ASSERT that we never hit this code in a |
| // fragment, as changing the owning document's compatibility mode would be |
| // wrong. |
| DCHECK(!is_parsing_fragment_); |
| if (is_parsing_fragment_) |
| return; |
| |
| if (token->ForceQuirks()) |
| SetCompatibilityMode(Document::kQuirksMode); |
| else { |
| SetCompatibilityModeFromDoctype(token->GetName(), public_id, system_id); |
| } |
| } |
| |
| void HTMLConstructionSite::InsertComment(AtomicHTMLToken* token) { |
| DCHECK_EQ(token->GetType(), HTMLToken::kComment); |
| AttachLater(CurrentNode(), |
| Comment::Create(OwnerDocumentForCurrentNode(), token->Comment())); |
| } |
| |
| void HTMLConstructionSite::InsertCommentOnDocument(AtomicHTMLToken* token) { |
| DCHECK_EQ(token->GetType(), HTMLToken::kComment); |
| DCHECK(document_); |
| AttachLater(attachment_root_, Comment::Create(*document_, token->Comment())); |
| } |
| |
| void HTMLConstructionSite::InsertCommentOnHTMLHtmlElement( |
| AtomicHTMLToken* token) { |
| DCHECK_EQ(token->GetType(), HTMLToken::kComment); |
| ContainerNode* parent = open_elements_.RootNode(); |
| AttachLater(parent, Comment::Create(parent->GetDocument(), token->Comment())); |
| } |
| |
| void HTMLConstructionSite::InsertHTMLHeadElement(AtomicHTMLToken* token) { |
| DCHECK(!ShouldFosterParent()); |
| head_ = HTMLStackItem::Create(CreateElement(token, xhtmlNamespaceURI), token); |
| AttachLater(CurrentNode(), head_->GetElement()); |
| open_elements_.PushHTMLHeadElement(head_); |
| } |
| |
| void HTMLConstructionSite::InsertHTMLBodyElement(AtomicHTMLToken* token) { |
| DCHECK(!ShouldFosterParent()); |
| Element* body = CreateElement(token, xhtmlNamespaceURI); |
| AttachLater(CurrentNode(), body); |
| open_elements_.PushHTMLBodyElement(HTMLStackItem::Create(body, token)); |
| if (document_) |
| document_->WillInsertBody(); |
| } |
| |
| void HTMLConstructionSite::InsertHTMLFormElement(AtomicHTMLToken* token, |
| bool is_demoted) { |
| Element* element = CreateElement(token, xhtmlNamespaceURI); |
| DCHECK(isHTMLFormElement(element)); |
| HTMLFormElement* form_element = toHTMLFormElement(element); |
| if (!OpenElements()->HasTemplateInHTMLScope()) |
| form_ = form_element; |
| form_element->SetDemoted(is_demoted); |
| AttachLater(CurrentNode(), form_element); |
| open_elements_.Push(HTMLStackItem::Create(form_element, token)); |
| } |
| |
| void HTMLConstructionSite::InsertHTMLElement(AtomicHTMLToken* token) { |
| Element* element = CreateElement(token, xhtmlNamespaceURI); |
| AttachLater(CurrentNode(), element); |
| open_elements_.Push(HTMLStackItem::Create(element, token)); |
| } |
| |
| void HTMLConstructionSite::InsertSelfClosingHTMLElementDestroyingToken( |
| AtomicHTMLToken* token) { |
| DCHECK_EQ(token->GetType(), HTMLToken::kStartTag); |
| // Normally HTMLElementStack is responsible for calling finishParsingChildren, |
| // but self-closing elements are never in the element stack so the stack |
| // doesn't get a chance to tell them that we're done parsing their children. |
| AttachLater(CurrentNode(), CreateElement(token, xhtmlNamespaceURI), true); |
| // FIXME: Do we want to acknowledge the token's self-closing flag? |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#acknowledge-self-closing-flag |
| } |
| |
| void HTMLConstructionSite::InsertFormattingElement(AtomicHTMLToken* token) { |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html#the-stack-of-open-elements |
| // Possible active formatting elements include: |
| // a, b, big, code, em, font, i, nobr, s, small, strike, strong, tt, and u. |
| InsertHTMLElement(token); |
| active_formatting_elements_.Append(CurrentElementRecord()->StackItem()); |
| } |
| |
| void HTMLConstructionSite::InsertScriptElement(AtomicHTMLToken* token) { |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#already-started |
| // http://html5.org/specs/dom-parsing.html#dom-range-createcontextualfragment |
| // For createContextualFragment, the specifications say to mark it |
| // parser-inserted and already-started and later unmark them. However, we |
| // short circuit that logic to avoid the subtree traversal to find script |
| // elements since scripts can never see those flags or effects thereof. |
| const bool parser_inserted = parser_content_policy_ != |
| kAllowScriptingContentAndDoNotMarkAlreadyStarted; |
| const bool already_started = is_parsing_fragment_ && parser_inserted; |
| // TODO(csharrison): This logic only works if the tokenizer/parser was not |
| // blocked waiting for scripts when the element was inserted. This usually |
| // fails for instance, on second document.write if a script writes twice in a |
| // row. To fix this, the parser might have to keep track of raw string |
| // position. |
| // TODO(csharrison): Refactor this so that the bools that are passed |
| // in are packed in a bitfield from an enum class. |
| const bool created_during_document_write = |
| OwnerDocumentForCurrentNode().IsInDocumentWrite(); |
| HTMLScriptElement* element = |
| HTMLScriptElement::Create(OwnerDocumentForCurrentNode(), parser_inserted, |
| already_started, created_during_document_write); |
| SetAttributes(element, token, parser_content_policy_); |
| if (ScriptingContentIsAllowed(parser_content_policy_)) |
| AttachLater(CurrentNode(), element); |
| open_elements_.Push(HTMLStackItem::Create(element, token)); |
| } |
| |
| void HTMLConstructionSite::InsertForeignElement( |
| AtomicHTMLToken* token, |
| const AtomicString& namespace_uri) { |
| DCHECK_EQ(token->GetType(), HTMLToken::kStartTag); |
| // parseError when xmlns or xmlns:xlink are wrong. |
| DVLOG(1) << "Not implemented."; |
| |
| Element* element = CreateElement(token, namespace_uri); |
| if (ScriptingContentIsAllowed(parser_content_policy_) || |
| !element->IsScriptElement()) { |
| AttachLater(CurrentNode(), element, token->SelfClosing()); |
| } |
| if (!token->SelfClosing()) |
| open_elements_.Push(HTMLStackItem::Create(element, token, namespace_uri)); |
| } |
| |
| void HTMLConstructionSite::InsertTextNode(const StringView& string, |
| WhitespaceMode whitespace_mode) { |
| HTMLConstructionSiteTask dummy_task(HTMLConstructionSiteTask::kInsert); |
| dummy_task.parent = CurrentNode(); |
| |
| if (ShouldFosterParent()) |
| FindFosterSite(dummy_task); |
| |
| // FIXME: This probably doesn't need to be done both here and in insert(Task). |
| if (isHTMLTemplateElement(*dummy_task.parent)) |
| dummy_task.parent = |
| toHTMLTemplateElement(dummy_task.parent.Get())->content(); |
| |
| // Unclear when parent != case occurs. Somehow we insert text into two |
| // separate nodes while processing the same Token. The nextChild != |
| // dummy.nextChild case occurs whenever foster parenting happened and we hit a |
| // new text node "<table>a</table>b" In either case we have to flush the |
| // pending text into the task queue before making more. |
| if (!pending_text_.IsEmpty() && |
| (pending_text_.parent != dummy_task.parent || |
| pending_text_.next_child != dummy_task.next_child)) |
| FlushPendingText(kFlushAlways); |
| pending_text_.Append(dummy_task.parent, dummy_task.next_child, string, |
| whitespace_mode); |
| } |
| |
| void HTMLConstructionSite::Reparent(HTMLElementStack::ElementRecord* new_parent, |
| HTMLElementStack::ElementRecord* child) { |
| HTMLConstructionSiteTask task(HTMLConstructionSiteTask::kReparent); |
| task.parent = new_parent->GetNode(); |
| task.child = child->GetNode(); |
| QueueTask(task); |
| } |
| |
| void HTMLConstructionSite::Reparent(HTMLElementStack::ElementRecord* new_parent, |
| HTMLStackItem* child) { |
| HTMLConstructionSiteTask task(HTMLConstructionSiteTask::kReparent); |
| task.parent = new_parent->GetNode(); |
| task.child = child->GetNode(); |
| QueueTask(task); |
| } |
| |
| void HTMLConstructionSite::InsertAlreadyParsedChild( |
| HTMLStackItem* new_parent, |
| HTMLElementStack::ElementRecord* child) { |
| if (new_parent->CausesFosterParenting()) { |
| FosterParent(child->GetNode()); |
| return; |
| } |
| |
| HTMLConstructionSiteTask task( |
| HTMLConstructionSiteTask::kInsertAlreadyParsedChild); |
| task.parent = new_parent->GetNode(); |
| task.child = child->GetNode(); |
| QueueTask(task); |
| } |
| |
| void HTMLConstructionSite::TakeAllChildren( |
| HTMLStackItem* new_parent, |
| HTMLElementStack::ElementRecord* old_parent) { |
| HTMLConstructionSiteTask task(HTMLConstructionSiteTask::kTakeAllChildren); |
| task.parent = new_parent->GetNode(); |
| task.child = old_parent->GetNode(); |
| QueueTask(task); |
| } |
| |
| CreateElementFlags HTMLConstructionSite::GetCreateElementFlags() const { |
| return is_parsing_fragment_ ? kCreatedByFragmentParser : kCreatedByParser; |
| } |
| |
| inline Document& HTMLConstructionSite::OwnerDocumentForCurrentNode() { |
| if (isHTMLTemplateElement(*CurrentNode())) |
| return toHTMLTemplateElement(CurrentElement())->content()->GetDocument(); |
| return CurrentNode()->GetDocument(); |
| } |
| |
| // "look up a custom element definition" for a token |
| // https://html.spec.whatwg.org/#look-up-a-custom-element-definition |
| CustomElementDefinition* HTMLConstructionSite::LookUpCustomElementDefinition( |
| Document& document, |
| AtomicHTMLToken* token) { |
| // "2. If document does not have a browsing context, return null." |
| LocalDOMWindow* window = document.ExecutingWindow(); |
| if (!window) |
| return nullptr; |
| |
| // "3. Let registry be document's browsing context's Window's |
| // CustomElementRegistry object." |
| CustomElementRegistry* registry = window->MaybeCustomElements(); |
| if (!registry) |
| return nullptr; |
| |
| const AtomicString& local_name = token->GetName(); |
| const Attribute* is_attribute = token->GetAttributeItem(HTMLNames::isAttr); |
| const AtomicString& name = is_attribute ? is_attribute->Value() : local_name; |
| CustomElementDescriptor descriptor(name, local_name); |
| |
| // 4.-6. |
| return registry->DefinitionFor(descriptor); |
| } |
| |
| // "create an element for a token" |
| // https://html.spec.whatwg.org/multipage/syntax.html#create-an-element-for-the-token |
| Element* HTMLConstructionSite::CreateElement( |
| AtomicHTMLToken* token, |
| const AtomicString& namespace_uri) { |
| // "1. Let document be intended parent's node document." |
| Document& document = OwnerDocumentForCurrentNode(); |
| |
| // "2. Let local name be the tag name of the token." |
| QualifiedName tag_name(g_null_atom, token->GetName(), namespace_uri); |
| // "3. Let is be the value of the "is" attribute in the given token ..." etc. |
| // "4. Let definition be the result of looking up a custom element ..." etc. |
| CustomElementDefinition* definition = |
| is_parsing_fragment_ ? nullptr |
| : LookUpCustomElementDefinition(document, token); |
| // "5. If definition is non-null and the parser was not originally created |
| // for the HTML fragment parsing algorithm, then let will execute script |
| // be true." |
| bool will_execute_script = definition && !is_parsing_fragment_; |
| |
| Element* element; |
| |
| if (will_execute_script) { |
| // "6.1 Increment the document's throw-on-dynamic-insertion counter." |
| ThrowOnDynamicMarkupInsertionCountIncrementer |
| throw_on_dynamic_markup_insertions(&document); |
| |
| // "6.2 If the JavaScript execution context stack is empty, |
| // then perform a microtask checkpoint." |
| |
| // TODO(dominicc): This is the way the Blink HTML parser performs |
| // checkpoints, but note the spec is different--it talks about the |
| // JavaScript stack, not the script nesting level. |
| if (0u == reentry_permit_->ScriptNestingLevel()) |
| Microtask::PerformCheckpoint(V8PerIsolateData::MainThreadIsolate()); |
| |
| // "6.3 Push a new element queue onto the custom element |
| // reactions stack." |
| CEReactionsScope reactions; |
| |
| // 7. |
| element = definition->CreateElementSync(document, tag_name); |
| |
| // "8. Append each attribute in the given token to element." We don't use |
| // setAttributes here because the custom element constructor may have |
| // manipulated attributes. |
| for (const auto& attribute : token->Attributes()) |
| element->setAttribute(attribute.GetName(), attribute.Value()); |
| |
| // "9. If will execute script is true, then ..." etc. The CEReactionsScope |
| // and ThrowOnDynamicMarkupInsertionCountIncrementer destructors implement |
| // steps 9.1-3. |
| } else { |
| element = document.createElement(tag_name, GetCreateElementFlags()); |
| // Definition for the created element does not exist here and it cannot be |
| // custom or failed. |
| DCHECK_NE(element->GetCustomElementState(), CustomElementState::kCustom); |
| DCHECK_NE(element->GetCustomElementState(), CustomElementState::kFailed); |
| |
| // TODO(dominicc): Move these steps so they happen for custom |
| // elements as well as built-in elements when customized built in |
| // elements are implemented for resettable, listed elements. |
| |
| // 10. If element has an xmlns attribute in the XMLNS namespace |
| // whose value is not exactly the same as the element's namespace, |
| // that is a parse error. Similarly, if element has an xmlns:xlink |
| // attribute in the XMLNS namespace whose value is not the XLink |
| // Namespace, that is a parse error. |
| |
| // TODO(dominicc): Implement step 10 when the HTML parser does |
| // something useful with parse errors. |
| |
| // 11. If element is a resettable element, invoke its reset |
| // algorithm. (This initializes the element's value and |
| // checkedness based on the element's attributes.) |
| // TODO(dominicc): Implement step 11, resettable elements. |
| |
| // 12. If element is a form-associated element, and the form |
| // element pointer is not null, and there is no template element |
| // on the stack of open elements, ... |
| FormAssociated* form_associated_element = |
| element->IsHTMLElement() |
| ? ToHTMLElement(element)->ToFormAssociatedOrNull() |
| : nullptr; |
| if (form_associated_element && document.GetFrame() && form_.Get()) { |
| // ... and element is either not listed or doesn't have a form |
| // attribute, and the intended parent is in the same tree as the |
| // element pointed to by the form element pointer, associate |
| // element with the form element pointed to by the form element |
| // pointer, and suppress the running of the reset the form owner |
| // algorithm when the parser subsequently attempts to insert the |
| // element. |
| |
| // TODO(dominicc): There are many differences to the spec here; |
| // some of them are observable: |
| // |
| // - The HTML spec tracks whether there is a template element on |
| // the stack both for manipulating the form element pointer |
| // and using it here. |
| // - FormAssociated::AssociateWith implementations don't do the |
| // "same tree" check; for example |
| // HTMLImageElement::AssociateWith just checks whether the form |
| // is in *a* tree. This check should be done here consistently. |
| // - ListedElement is a mixin; add IsListedElement and skip |
| // setting the form for listed attributes with form=. Instead |
| // we set attributes (step 8) out of order, after this step, |
| // to reset the form association. |
| form_associated_element->AssociateWith(form_.Get()); |
| } |
| // "8. Append each attribute in the given token to element." |
| SetAttributes(element, token, parser_content_policy_); |
| } |
| |
| return element; |
| } |
| |
| HTMLStackItem* HTMLConstructionSite::CreateElementFromSavedToken( |
| HTMLStackItem* item) { |
| Element* element; |
| // NOTE: Moving from item -> token -> item copies the Attribute vector twice! |
| AtomicHTMLToken fake_token(HTMLToken::kStartTag, item->LocalName(), |
| item->Attributes()); |
| element = CreateElement(&fake_token, item->NamespaceURI()); |
| return HTMLStackItem::Create(element, &fake_token, item->NamespaceURI()); |
| } |
| |
| bool HTMLConstructionSite::IndexOfFirstUnopenFormattingElement( |
| unsigned& first_unopen_element_index) const { |
| if (active_formatting_elements_.IsEmpty()) |
| return false; |
| unsigned index = active_formatting_elements_.size(); |
| do { |
| --index; |
| const HTMLFormattingElementList::Entry& entry = |
| active_formatting_elements_.at(index); |
| if (entry.IsMarker() || open_elements_.Contains(entry.GetElement())) { |
| first_unopen_element_index = index + 1; |
| return first_unopen_element_index < active_formatting_elements_.size(); |
| } |
| } while (index); |
| first_unopen_element_index = index; |
| return true; |
| } |
| |
| void HTMLConstructionSite::ReconstructTheActiveFormattingElements() { |
| unsigned first_unopen_element_index; |
| if (!IndexOfFirstUnopenFormattingElement(first_unopen_element_index)) |
| return; |
| |
| unsigned unopen_entry_index = first_unopen_element_index; |
| DCHECK_LT(unopen_entry_index, active_formatting_elements_.size()); |
| for (; unopen_entry_index < active_formatting_elements_.size(); |
| ++unopen_entry_index) { |
| HTMLFormattingElementList::Entry& unopened_entry = |
| active_formatting_elements_.at(unopen_entry_index); |
| HTMLStackItem* reconstructed = |
| CreateElementFromSavedToken(unopened_entry.StackItem()); |
| AttachLater(CurrentNode(), reconstructed->GetNode()); |
| open_elements_.Push(reconstructed); |
| unopened_entry.ReplaceElement(reconstructed); |
| } |
| } |
| |
| void HTMLConstructionSite::GenerateImpliedEndTagsWithExclusion( |
| const AtomicString& tag_name) { |
| while (HasImpliedEndTag(CurrentStackItem()) && |
| !CurrentStackItem()->MatchesHTMLTag(tag_name)) |
| open_elements_.Pop(); |
| } |
| |
| void HTMLConstructionSite::GenerateImpliedEndTags() { |
| while (HasImpliedEndTag(CurrentStackItem())) |
| open_elements_.Pop(); |
| } |
| |
| bool HTMLConstructionSite::InQuirksMode() { |
| return in_quirks_mode_; |
| } |
| |
| // Adjusts |task| to match the "adjusted insertion location" determined by the |
| // foster parenting algorithm, laid out as the substeps of step 2 of |
| // https://html.spec.whatwg.org/#appropriate-place-for-inserting-a-node |
| void HTMLConstructionSite::FindFosterSite(HTMLConstructionSiteTask& task) { |
| // 2.1 |
| HTMLElementStack::ElementRecord* last_template = |
| open_elements_.Topmost(templateTag.LocalName()); |
| |
| // 2.2 |
| HTMLElementStack::ElementRecord* last_table = |
| open_elements_.Topmost(tableTag.LocalName()); |
| |
| // 2.3 |
| if (last_template && (!last_table || last_template->IsAbove(last_table))) { |
| task.parent = last_template->GetElement(); |
| return; |
| } |
| |
| // 2.4 |
| if (!last_table) { |
| // Fragment case |
| task.parent = open_elements_.RootNode(); // DocumentFragment |
| return; |
| } |
| |
| // 2.5 |
| if (ContainerNode* parent = last_table->GetElement()->parentNode()) { |
| task.parent = parent; |
| task.next_child = last_table->GetElement(); |
| return; |
| } |
| |
| // 2.6, 2.7 |
| task.parent = last_table->Next()->GetElement(); |
| } |
| |
| bool HTMLConstructionSite::ShouldFosterParent() const { |
| return redirect_attach_to_foster_parent_ && |
| CurrentStackItem()->IsElementNode() && |
| CurrentStackItem()->CausesFosterParenting(); |
| } |
| |
| void HTMLConstructionSite::FosterParent(Node* node) { |
| HTMLConstructionSiteTask task(HTMLConstructionSiteTask::kInsert); |
| FindFosterSite(task); |
| task.child = node; |
| DCHECK(task.parent); |
| QueueTask(task); |
| } |
| |
| DEFINE_TRACE(HTMLConstructionSite::PendingText) { |
| visitor->Trace(parent); |
| visitor->Trace(next_child); |
| } |
| |
| } // namespace blink |