From 88d9dc148cff12b70c5aec090ce44845fff7cf00 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 25 Jun 2024 10:49:54 +0200 Subject: [PATCH] LibWeb: Update HTML fragment serialization for declarative shadow DOM (cherry picked from commit e62db9c1181f5af49242656d3e065ceb0db800b9) --- Userland/Libraries/LibWeb/DOM/Node.cpp | 8 +- .../LibWeb/HTML/Parser/HTMLParser.cpp | 107 +++++++++++++----- .../Libraries/LibWeb/HTML/Parser/HTMLParser.h | 6 +- 3 files changed, 89 insertions(+), 32 deletions(-) diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index 94e05acb36..a44f7d9a39 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -1352,17 +1352,17 @@ void Node::string_replace_all(String const& string) replace_all(node); } -// https://w3c.github.io/DOM-Parsing/#dfn-fragment-serializing-algorithm +// https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#fragment-serializing-algorithm-steps WebIDL::ExceptionOr Node::serialize_fragment(DOMParsing::RequireWellFormed require_well_formed, FragmentSerializationMode fragment_serialization_mode) const { // 1. Let context document be the value of node's node document. auto const& context_document = document(); - // 2. If context document is an HTML document, return an HTML serialization of node. + // 2. If context document is an HTML document, return the result of HTML fragment serialization algorithm with node, false, and « ». if (context_document.is_html_document()) - return HTML::HTMLParser::serialize_html_fragment(*this, fragment_serialization_mode); + return HTML::HTMLParser::serialize_html_fragment(*this, HTML::HTMLParser::SerializableShadowRoots::No, {}, fragment_serialization_mode); - // 3. Otherwise, context document is an XML document; return an XML serialization of node passing the flag require well-formed. + // 3. Return the XML serialization of node given require well-formed. return DOMParsing::serialize_node_to_xml_string(*this, require_well_formed); } diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp index 9d31c5a98d..9b40d5d290 100644 --- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp +++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.cpp @@ -4411,7 +4411,7 @@ static String escape_string(StringView string, AttributeMode attribute_mode) } // https://html.spec.whatwg.org/multipage/parsing.html#html-fragment-serialisation-algorithm -String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentSerializationMode fragment_serialization_mode) +String HTMLParser::serialize_html_fragment(DOM::Node const& node, SerializableShadowRoots serializable_shadow_roots, Vector> const& shadow_roots, DOM::FragmentSerializationMode fragment_serialization_mode) { // NOTE: Steps in this function are jumbled a bit to accommodate the Element.outerHTML API. // When called with FragmentSerializationMode::Outer, we will serialize the element itself, @@ -4421,8 +4421,8 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS StringBuilder builder; auto serialize_element = [&](DOM::Element const& element) { - // 1. If current node is an element in the HTML namespace, the MathML namespace, or the SVG namespace, then let tagname be current node's local name. - // Otherwise, let tagname be current node's qualified name. + // If current node is an element in the HTML namespace, the MathML namespace, or the SVG namespace, then let tagname be current node's local name. + // Otherwise, let tagname be current node's qualified name. FlyString tag_name; if (element.namespace_uri().has_value() && element.namespace_uri()->is_one_of(Namespace::HTML, Namespace::MathML, Namespace::SVG)) @@ -4430,22 +4430,27 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS else tag_name = element.qualified_name(); - // 2. Append a U+003C LESS-THAN SIGN character (<), followed by tagname. + // Append a U+003C LESS-THAN SIGN character (<), followed by tagname. builder.append('<'); builder.append(tag_name); - // 3. If current node's is value is not null, and the element does not have an is attribute in its attribute list, - // then append the string " is="", followed by current node's is value escaped as described below in attribute mode, - // followed by a U+0022 QUOTATION MARK character ("). + // If current node's is value is not null, and the element does not have an is attribute in its attribute list, + // then append the string " is="", + // followed by current node's is value escaped as described below in attribute mode, + // followed by a U+0022 QUOTATION MARK character ("). if (element.is_value().has_value() && !element.has_attribute(AttributeNames::is)) { builder.append(" is=\""sv); builder.append(escape_string(element.is_value().value(), AttributeMode::Yes)); builder.append('"'); } - // 4. For each attribute that the element has, append a U+0020 SPACE character, the attribute's serialized name as described below, a U+003D EQUALS SIGN character (=), - // a U+0022 QUOTATION MARK character ("), the attribute's value, escaped as described below in attribute mode, and a second U+0022 QUOTATION MARK character ("). - // NOTE: The order of attributes is implementation-defined. The only constraint is that the order must be stable. + // For each attribute that the element has, + // append a U+0020 SPACE character, + // the attribute's serialized name as described below, + // a U+003D EQUALS SIGN character (=), + // a U+0022 QUOTATION MARK character ("), + // the attribute's value, escaped as described below in attribute mode, + // and a second U+0022 QUOTATION MARK character ("). element.for_each_attribute([&](auto const& attribute) { builder.append(' '); @@ -4472,16 +4477,20 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS builder.append('"'); }); - // 5. Append a U+003E GREATER-THAN SIGN character (>). + // Append a U+003E GREATER-THAN SIGN character (>). builder.append('>'); - // 6. If current node serializes as void, then continue on to the next child node at this point. + // If current node serializes as void, then continue on to the next child node at this point. if (element.serializes_as_void()) return IterationDecision::Continue; - // 7. Append the value of running the HTML fragment serialization algorithm on the current node element (thus recursing into this algorithm for that element), - // followed by a U+003C LESS-THAN SIGN character (<), a U+002F SOLIDUS character (/), tagname again, and finally a U+003E GREATER-THAN SIGN character (>). - builder.append(serialize_html_fragment(element)); + // Append the value of running the HTML fragment serialization algorithm with current node, + // serializableShadowRoots, and shadowRoots (thus recursing into this algorithm for that node), + // followed by a U+003C LESS-THAN SIGN character (<), + // a U+002F SOLIDUS character (/), + // tagname again, + // and finally a U+003E GREATER-THAN SIGN character (>). + builder.append(serialize_html_fragment(element, serializable_shadow_roots, shadow_roots)); builder.append("'); @@ -4510,9 +4519,53 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS // (NOTE: This is out of order of the spec to avoid another dynamic cast. The second step just creates a string builder, so it shouldn't matter) if (is(element)) actual_node = verify_cast(element).content(); + + // 4. If current node is a shadow host, then: + if (element.is_shadow_host()) { + // 1. Let shadow be current node's shadow root. + auto shadow = element.shadow_root(); + + // 2. If one of the following is true: + // - serializableShadowRoots is true and shadow's serializable is true; or + // - shadowRoots contains shadow, + if ((serializable_shadow_roots == SerializableShadowRoots::Yes && shadow->serializable()) + || shadow_roots.find_first_index_if([&](auto& entry) { return entry == shadow; }).has_value()) { + // then: + // 1. Append "". + builder.append(""sv); + } + } } - // 4. For each child node of the node, in tree order, run the following steps: + // 5. For each child node of the node, in tree order, run the following steps: actual_node->for_each_child([&](DOM::Node& current_node) { // 1. Let current node be the child node being processed. @@ -4533,8 +4586,8 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS if (is(parent)) { auto& parent_element = verify_cast(*parent); - // 1. If the parent of current node is a style, script, xmp, iframe, noembed, noframes, or plaintext element, - // or if the parent of current node is a noscript element and scripting is enabled for the node, then append the value of current node's data IDL attribute literally. + // If the parent of current node is a style, script, xmp, iframe, noembed, noframes, or plaintext element, + // or if the parent of current node is a noscript element and scripting is enabled for the node, then append the value of current node's data IDL attribute literally. if (parent_element.local_name().is_one_of(HTML::TagNames::style, HTML::TagNames::script, HTML::TagNames::xmp, HTML::TagNames::iframe, HTML::TagNames::noembed, HTML::TagNames::noframes, HTML::TagNames::plaintext) || (parent_element.local_name() == HTML::TagNames::noscript && !parent_element.is_scripting_disabled())) { builder.append(text_node.data()); @@ -4542,7 +4595,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS } } - // 2. Otherwise, append the value of current node's data IDL attribute, escaped as described below. + // Otherwise, append the value of current node's data IDL attribute, escaped as described below. builder.append(escape_string(text_node.data(), AttributeMode::No)); } @@ -4550,8 +4603,8 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS // -> If current node is a Comment auto& comment_node = verify_cast(current_node); - // 1. Append the literal string "" (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN). + // Append the literal string "" (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN). builder.append(""sv); @@ -4562,8 +4615,8 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS // -> If current node is a ProcessingInstruction auto& processing_instruction_node = verify_cast(current_node); - // 1. Append the literal string "). + // Append the literal string "). builder.append(" If current node is a DocumentType auto& document_type_node = verify_cast(current_node); - // 1. Append the literal string "" (U+003E GREATER-THAN SIGN). + // Append the literal string "" (U+003E GREATER-THAN SIGN). builder.append("'); @@ -4588,7 +4641,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS return IterationDecision::Continue; }); - // 5. Return s. + // 6. Return s. return MUST(builder.to_string()); } diff --git a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h index 54febce881..9a0b40b48f 100644 --- a/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h +++ b/Userland/Libraries/LibWeb/HTML/Parser/HTMLParser.h @@ -61,7 +61,11 @@ public: DOM::Document& document(); static Vector> parse_html_fragment(DOM::Element& context_element, StringView); - static String serialize_html_fragment(DOM::Node const& node, DOM::FragmentSerializationMode = DOM::FragmentSerializationMode::Inner); + enum class SerializableShadowRoots { + No, + Yes, + }; + static String serialize_html_fragment(DOM::Node const&, SerializableShadowRoots, Vector> const&, DOM::FragmentSerializationMode = DOM::FragmentSerializationMode::Inner); enum class InsertionMode { #define __ENUMERATE_INSERTION_MODE(mode) mode,