mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-15 20:33:10 +00:00
LibWeb: Update HTML fragment serialization for declarative shadow DOM
(cherry picked from commit e62db9c1181f5af49242656d3e065ceb0db800b9)
This commit is contained in:
parent
a5189537e9
commit
88d9dc148c
|
@ -1352,17 +1352,17 @@ void Node::string_replace_all(String const& string)
|
||||||
replace_all(node);
|
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<String> Node::serialize_fragment(DOMParsing::RequireWellFormed require_well_formed, FragmentSerializationMode fragment_serialization_mode) const
|
WebIDL::ExceptionOr<String> 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.
|
// 1. Let context document be the value of node's node document.
|
||||||
auto const& context_document = 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())
|
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);
|
return DOMParsing::serialize_node_to_xml_string(*this, require_well_formed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
// 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<JS::Handle<DOM::ShadowRoot>> const& shadow_roots, DOM::FragmentSerializationMode fragment_serialization_mode)
|
||||||
{
|
{
|
||||||
// NOTE: Steps in this function are jumbled a bit to accommodate the Element.outerHTML API.
|
// 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,
|
// 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;
|
StringBuilder builder;
|
||||||
|
|
||||||
auto serialize_element = [&](DOM::Element const& element) {
|
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.
|
// 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.
|
// Otherwise, let tagname be current node's qualified name.
|
||||||
FlyString tag_name;
|
FlyString tag_name;
|
||||||
|
|
||||||
if (element.namespace_uri().has_value() && element.namespace_uri()->is_one_of(Namespace::HTML, Namespace::MathML, Namespace::SVG))
|
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
|
else
|
||||||
tag_name = element.qualified_name();
|
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('<');
|
||||||
builder.append(tag_name);
|
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,
|
// 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,
|
// then append the string " is="",
|
||||||
// followed by a U+0022 QUOTATION MARK character (").
|
// 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)) {
|
if (element.is_value().has_value() && !element.has_attribute(AttributeNames::is)) {
|
||||||
builder.append(" is=\""sv);
|
builder.append(" is=\""sv);
|
||||||
builder.append(escape_string(element.is_value().value(), AttributeMode::Yes));
|
builder.append(escape_string(element.is_value().value(), AttributeMode::Yes));
|
||||||
builder.append('"');
|
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 (=),
|
// For each attribute that the element has,
|
||||||
// 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 (").
|
// append a U+0020 SPACE character,
|
||||||
// NOTE: The order of attributes is implementation-defined. The only constraint is that the order must be stable.
|
// 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) {
|
element.for_each_attribute([&](auto const& attribute) {
|
||||||
builder.append(' ');
|
builder.append(' ');
|
||||||
|
|
||||||
|
@ -4472,16 +4477,20 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS
|
||||||
builder.append('"');
|
builder.append('"');
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. Append a U+003E GREATER-THAN SIGN character (>).
|
// Append a U+003E GREATER-THAN SIGN character (>).
|
||||||
builder.append('>');
|
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())
|
if (element.serializes_as_void())
|
||||||
return IterationDecision::Continue;
|
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),
|
// Append the value of running the HTML fragment serialization algorithm with current 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 (>).
|
// serializableShadowRoots, and shadowRoots (thus recursing into this algorithm for that node),
|
||||||
builder.append(serialize_html_fragment(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, serializable_shadow_roots, shadow_roots));
|
||||||
builder.append("</"sv);
|
builder.append("</"sv);
|
||||||
builder.append(tag_name);
|
builder.append(tag_name);
|
||||||
builder.append('>');
|
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)
|
// (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<HTML::HTMLTemplateElement>(element))
|
if (is<HTML::HTMLTemplateElement>(element))
|
||||||
actual_node = verify_cast<HTML::HTMLTemplateElement>(element).content();
|
actual_node = verify_cast<HTML::HTMLTemplateElement>(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 "<template shadowrootmode="".
|
||||||
|
builder.append("<template shadowrootmode=\""sv);
|
||||||
|
|
||||||
|
// 2. If shadow's mode is "open", then append "open". Otherwise, append "closed".
|
||||||
|
builder.append(shadow->mode() == Bindings::ShadowRootMode::Open ? "open"sv : "closed"sv);
|
||||||
|
|
||||||
|
// 3. Append """.
|
||||||
|
builder.append('"');
|
||||||
|
|
||||||
|
// 4. If shadow's delegates focus is set, then append " shadowrootdelegatesfocus=""".
|
||||||
|
if (shadow->delegates_focus())
|
||||||
|
builder.append(" shadowrootdelegatesfocus=\"\""sv);
|
||||||
|
|
||||||
|
// 5. If shadow's serializable is set, then append " shadowrootserializable=""".
|
||||||
|
if (shadow->serializable())
|
||||||
|
builder.append(" shadowrootserializable=\"\""sv);
|
||||||
|
|
||||||
|
// 6. If shadow's clonable is set, then append " shadowrootclonable=""".
|
||||||
|
if (shadow->clonable())
|
||||||
|
builder.append(" shadowrootclonable=\"\""sv);
|
||||||
|
|
||||||
|
// 7. Append ">".
|
||||||
|
builder.append('>');
|
||||||
|
|
||||||
|
// 8. Append the value of running the HTML fragment serialization algorithm with shadow,
|
||||||
|
// serializableShadowRoots, and shadowRoots (thus recursing into this algorithm for that element).
|
||||||
|
builder.append(serialize_html_fragment(*shadow, serializable_shadow_roots, shadow_roots));
|
||||||
|
|
||||||
|
// 9. Append "</template>".
|
||||||
|
builder.append("</template>"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) {
|
actual_node->for_each_child([&](DOM::Node& current_node) {
|
||||||
// 1. Let current node be the child node being processed.
|
// 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<DOM::Element>(parent)) {
|
if (is<DOM::Element>(parent)) {
|
||||||
auto& parent_element = verify_cast<DOM::Element>(*parent);
|
auto& parent_element = verify_cast<DOM::Element>(*parent);
|
||||||
|
|
||||||
// 1. If the parent of current node is a style, script, xmp, iframe, noembed, noframes, or plaintext element,
|
// 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.
|
// 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)
|
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())) {
|
|| (parent_element.local_name() == HTML::TagNames::noscript && !parent_element.is_scripting_disabled())) {
|
||||||
builder.append(text_node.data());
|
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));
|
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
|
// -> If current node is a Comment
|
||||||
auto& comment_node = verify_cast<DOM::Comment>(current_node);
|
auto& comment_node = verify_cast<DOM::Comment>(current_node);
|
||||||
|
|
||||||
// 1. Append the literal string "<!--" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS),
|
// Append the literal string "<!--" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS),
|
||||||
// followed by the value of current node's data IDL attribute, followed by the literal string "-->" (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN).
|
// followed by the value of current node's data IDL attribute, followed by the literal string "-->" (U+002D HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN).
|
||||||
builder.append("<!--"sv);
|
builder.append("<!--"sv);
|
||||||
builder.append(comment_node.data());
|
builder.append(comment_node.data());
|
||||||
builder.append("-->"sv);
|
builder.append("-->"sv);
|
||||||
|
@ -4562,8 +4615,8 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS
|
||||||
// -> If current node is a ProcessingInstruction
|
// -> If current node is a ProcessingInstruction
|
||||||
auto& processing_instruction_node = verify_cast<DOM::ProcessingInstruction>(current_node);
|
auto& processing_instruction_node = verify_cast<DOM::ProcessingInstruction>(current_node);
|
||||||
|
|
||||||
// 1. Append the literal string "<?" (U+003C LESS-THAN SIGN, U+003F QUESTION MARK), followed by the value of current node's target IDL attribute,
|
// Append the literal string "<?" (U+003C LESS-THAN SIGN, U+003F QUESTION MARK), followed by the value of current node's target IDL attribute,
|
||||||
// followed by a single U+0020 SPACE character, followed by the value of current node's data IDL attribute, followed by a single U+003E GREATER-THAN SIGN character (>).
|
// followed by a single U+0020 SPACE character, followed by the value of current node's data IDL attribute, followed by a single U+003E GREATER-THAN SIGN character (>).
|
||||||
builder.append("<?"sv);
|
builder.append("<?"sv);
|
||||||
builder.append(processing_instruction_node.target());
|
builder.append(processing_instruction_node.target());
|
||||||
builder.append(' ');
|
builder.append(' ');
|
||||||
|
@ -4576,9 +4629,9 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS
|
||||||
// -> If current node is a DocumentType
|
// -> If current node is a DocumentType
|
||||||
auto& document_type_node = verify_cast<DOM::DocumentType>(current_node);
|
auto& document_type_node = verify_cast<DOM::DocumentType>(current_node);
|
||||||
|
|
||||||
// 1. Append the literal string "<!DOCTYPE" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+0044 LATIN CAPITAL LETTER D, U+004F LATIN CAPITAL LETTER O,
|
// Append the literal string "<!DOCTYPE" (U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+0044 LATIN CAPITAL LETTER D, U+004F LATIN CAPITAL LETTER O,
|
||||||
// U+0043 LATIN CAPITAL LETTER C, U+0054 LATIN CAPITAL LETTER T, U+0059 LATIN CAPITAL LETTER Y, U+0050 LATIN CAPITAL LETTER P, U+0045 LATIN CAPITAL LETTER E),
|
// U+0043 LATIN CAPITAL LETTER C, U+0054 LATIN CAPITAL LETTER T, U+0059 LATIN CAPITAL LETTER Y, U+0050 LATIN CAPITAL LETTER P, U+0045 LATIN CAPITAL LETTER E),
|
||||||
// followed by a space (U+0020 SPACE), followed by the value of current node's name IDL attribute, followed by the literal string ">" (U+003E GREATER-THAN SIGN).
|
// followed by a space (U+0020 SPACE), followed by the value of current node's name IDL attribute, followed by the literal string ">" (U+003E GREATER-THAN SIGN).
|
||||||
builder.append("<!DOCTYPE "sv);
|
builder.append("<!DOCTYPE "sv);
|
||||||
builder.append(document_type_node.name());
|
builder.append(document_type_node.name());
|
||||||
builder.append('>');
|
builder.append('>');
|
||||||
|
@ -4588,7 +4641,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node, DOM::FragmentS
|
||||||
return IterationDecision::Continue;
|
return IterationDecision::Continue;
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. Return s.
|
// 6. Return s.
|
||||||
return MUST(builder.to_string());
|
return MUST(builder.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,11 @@ public:
|
||||||
DOM::Document& document();
|
DOM::Document& document();
|
||||||
|
|
||||||
static Vector<JS::Handle<DOM::Node>> parse_html_fragment(DOM::Element& context_element, StringView);
|
static Vector<JS::Handle<DOM::Node>> 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<JS::Handle<DOM::ShadowRoot>> const&, DOM::FragmentSerializationMode = DOM::FragmentSerializationMode::Inner);
|
||||||
|
|
||||||
enum class InsertionMode {
|
enum class InsertionMode {
|
||||||
#define __ENUMERATE_INSERTION_MODE(mode) mode,
|
#define __ENUMERATE_INSERTION_MODE(mode) mode,
|
||||||
|
|
Loading…
Reference in a new issue