mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-21 12:05:15 +00:00
LibWeb: Implement Element.outerHTML
This piggybacks on the same fragment serialization code that innerHTML uses, but instead of constructing an imaginary parent element like the spec asks us to, we just add a separate serialization mode that includes the context element in the serialized markup. This makes the image carousel on https://utah.edu/ show up :^)
This commit is contained in:
parent
0412e17bac
commit
870a954e11
Notes:
sideshowbarker
2024-07-18 02:13:10 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/870a954e11 Pull-request: https://github.com/SerenityOS/serenity/pull/23905 Reviewed-by: https://github.com/trflynn89
9 changed files with 120 additions and 75 deletions
2
Tests/LibWeb/Text/expected/HTML/outerHTML.txt
Normal file
2
Tests/LibWeb/Text/expected/HTML/outerHTML.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
hello students outerHTML: <div id="foo"><b>hello students</b></div>
|
||||
innerHTML: <b>hello students</b>
|
8
Tests/LibWeb/Text/input/HTML/outerHTML.html
Normal file
8
Tests/LibWeb/Text/input/HTML/outerHTML.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<script src="../include.js"></script>
|
||||
<div id="foo"><b>hello students</b></div>
|
||||
<script>
|
||||
test(() => {
|
||||
println("outerHTML: " + foo.outerHTML)
|
||||
println("innerHTML: " + foo.innerHTML)
|
||||
});
|
||||
</script>
|
|
@ -1402,6 +1402,19 @@ bool Element::is_actually_disabled() const
|
|||
return false;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/DOM-Parsing/#dom-element-outerhtml
|
||||
WebIDL::ExceptionOr<String> Element::outer_html() const
|
||||
{
|
||||
return serialize_fragment(DOMParsing::RequireWellFormed::Yes, FragmentSerializationMode::Outer);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/DOM-Parsing/#dom-element-outerhtml
|
||||
WebIDL::ExceptionOr<void> Element::set_outer_html(String const&)
|
||||
{
|
||||
dbgln("FIXME: Implement Element::set_outer_html()");
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml
|
||||
WebIDL::ExceptionOr<void> Element::insert_adjacent_html(String const& position, String const& text)
|
||||
{
|
||||
|
|
|
@ -175,6 +175,9 @@ public:
|
|||
|
||||
WebIDL::ExceptionOr<void> insert_adjacent_html(String const& position, String const& text);
|
||||
|
||||
WebIDL::ExceptionOr<String> outer_html() const;
|
||||
WebIDL::ExceptionOr<void> set_outer_html(String const&);
|
||||
|
||||
bool is_focused() const;
|
||||
bool is_active() const;
|
||||
bool is_target() const;
|
||||
|
|
|
@ -85,6 +85,8 @@ interface Element : Node {
|
|||
undefined insertAdjacentText(DOMString where, DOMString data);
|
||||
[CEReactions] undefined insertAdjacentHTML(DOMString position, DOMString text);
|
||||
|
||||
[CEReactions, LegacyNullToEmptyString] attribute DOMString outerHTML;
|
||||
|
||||
undefined scrollIntoView(optional (boolean or ScrollIntoViewOptions) arg = {});
|
||||
|
||||
undefined scroll(optional ScrollToOptions options = {});
|
||||
|
|
|
@ -1336,14 +1336,14 @@ void Node::string_replace_all(String const& string)
|
|||
}
|
||||
|
||||
// https://w3c.github.io/DOM-Parsing/#dfn-fragment-serializing-algorithm
|
||||
WebIDL::ExceptionOr<String> Node::serialize_fragment(DOMParsing::RequireWellFormed require_well_formed) 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.
|
||||
auto const& context_document = document();
|
||||
|
||||
// 2. If context document is an HTML document, return an HTML serialization of node.
|
||||
if (context_document.is_html_document())
|
||||
return HTML::HTMLParser::serialize_html_fragment(*this);
|
||||
return HTML::HTMLParser::serialize_html_fragment(*this, fragment_serialization_mode);
|
||||
|
||||
// 3. Otherwise, context document is an XML document; return an XML serialization of node passing the flag require well-formed.
|
||||
return DOMParsing::serialize_node_to_xml_string(*this, require_well_formed);
|
||||
|
|
|
@ -45,6 +45,11 @@ struct GetRootNodeOptions {
|
|||
bool composed { false };
|
||||
};
|
||||
|
||||
enum class FragmentSerializationMode {
|
||||
Inner,
|
||||
Outer,
|
||||
};
|
||||
|
||||
class Node : public EventTarget {
|
||||
WEB_PLATFORM_OBJECT(Node, EventTarget);
|
||||
|
||||
|
@ -242,7 +247,7 @@ public:
|
|||
i32 unique_id() const { return m_unique_id; }
|
||||
static Node* from_unique_id(i32);
|
||||
|
||||
WebIDL::ExceptionOr<String> serialize_fragment(DOMParsing::RequireWellFormed) const;
|
||||
WebIDL::ExceptionOr<String> serialize_fragment(DOMParsing::RequireWellFormed, FragmentSerializationMode = FragmentSerializationMode::Inner) const;
|
||||
|
||||
void replace_all(JS::GCPtr<Node>);
|
||||
void string_replace_all(String const&);
|
||||
|
|
|
@ -4275,8 +4275,89 @@ 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)
|
||||
String HTMLParser::serialize_html_fragment(DOM::Node const& node, 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,
|
||||
// not just its children.
|
||||
|
||||
// 2. Let s be a string, and initialize it to the empty string.
|
||||
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.
|
||||
FlyString tag_name;
|
||||
|
||||
if (element.namespace_uri().has_value() && element.namespace_uri()->is_one_of(Namespace::HTML, Namespace::MathML, Namespace::SVG))
|
||||
tag_name = element.local_name();
|
||||
else
|
||||
tag_name = element.qualified_name();
|
||||
|
||||
// 2. 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 (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.
|
||||
element.for_each_attribute([&](auto const& attribute) {
|
||||
builder.append(' ');
|
||||
|
||||
// An attribute's serialized name for the purposes of the previous paragraph must be determined as follows:
|
||||
|
||||
// NOTE: As far as I can tell, these steps are equivalent to just using the qualified name.
|
||||
//
|
||||
// -> If the attribute has no namespace:
|
||||
// The attribute's serialized name is the attribute's local name.
|
||||
// -> If the attribute is in the XML namespace:
|
||||
// The attribute's serialized name is the string "xml:" followed by the attribute's local name.
|
||||
// -> If the attribute is in the XMLNS namespace and the attribute's local name is xmlns:
|
||||
// The attribute's serialized name is the string "xmlns".
|
||||
// -> If the attribute is in the XMLNS namespace and the attribute's local name is not xmlns:
|
||||
// The attribute's serialized name is the string "xmlns:" followed by the attribute's local name.
|
||||
// -> If the attribute is in the XLink namespace:
|
||||
// The attribute's serialized name is the string "xlink:" followed by the attribute's local name.
|
||||
// -> If the attribute is in some other namespace:
|
||||
// The attribute's serialized name is the attribute's qualified name.
|
||||
builder.append(attribute.name());
|
||||
|
||||
builder.append("=\""sv);
|
||||
builder.append(escape_string(attribute.value(), AttributeMode::Yes));
|
||||
builder.append('"');
|
||||
});
|
||||
|
||||
// 5. 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 (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));
|
||||
builder.append("</"sv);
|
||||
builder.append(tag_name);
|
||||
builder.append('>');
|
||||
|
||||
return IterationDecision::Continue;
|
||||
};
|
||||
|
||||
if (fragment_serialization_mode == DOM::FragmentSerializationMode::Outer) {
|
||||
serialize_element(verify_cast<DOM::Element>(node));
|
||||
return builder.to_string_without_validation();
|
||||
}
|
||||
|
||||
// The algorithm takes as input a DOM Element, Document, or DocumentFragment referred to as the node.
|
||||
VERIFY(node.is_element() || node.is_document() || node.is_document_fragment());
|
||||
JS::NonnullGCPtr<DOM::Node const> actual_node = node;
|
||||
|
@ -4295,9 +4376,6 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node)
|
|||
actual_node = verify_cast<HTML::HTMLTemplateElement>(element).content();
|
||||
}
|
||||
|
||||
// 2. Let s be a string, and initialize it to the empty string.
|
||||
StringBuilder builder;
|
||||
|
||||
// 4. 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.
|
||||
|
@ -4307,73 +4385,7 @@ String HTMLParser::serialize_html_fragment(DOM::Node const& node)
|
|||
if (is<DOM::Element>(current_node)) {
|
||||
// -> If current node is an Element
|
||||
auto& element = verify_cast<DOM::Element>(current_node);
|
||||
|
||||
// 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.
|
||||
FlyString tag_name;
|
||||
|
||||
if (element.namespace_uri().has_value() && element.namespace_uri()->is_one_of(Namespace::HTML, Namespace::MathML, Namespace::SVG))
|
||||
tag_name = element.local_name();
|
||||
else
|
||||
tag_name = element.qualified_name();
|
||||
|
||||
// 2. 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 (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.
|
||||
element.for_each_attribute([&](auto const& attribute) {
|
||||
builder.append(' ');
|
||||
|
||||
// An attribute's serialized name for the purposes of the previous paragraph must be determined as follows:
|
||||
|
||||
// NOTE: As far as I can tell, these steps are equivalent to just using the qualified name.
|
||||
//
|
||||
// -> If the attribute has no namespace:
|
||||
// The attribute's serialized name is the attribute's local name.
|
||||
// -> If the attribute is in the XML namespace:
|
||||
// The attribute's serialized name is the string "xml:" followed by the attribute's local name.
|
||||
// -> If the attribute is in the XMLNS namespace and the attribute's local name is xmlns:
|
||||
// The attribute's serialized name is the string "xmlns".
|
||||
// -> If the attribute is in the XMLNS namespace and the attribute's local name is not xmlns:
|
||||
// The attribute's serialized name is the string "xmlns:" followed by the attribute's local name.
|
||||
// -> If the attribute is in the XLink namespace:
|
||||
// The attribute's serialized name is the string "xlink:" followed by the attribute's local name.
|
||||
// -> If the attribute is in some other namespace:
|
||||
// The attribute's serialized name is the attribute's qualified name.
|
||||
builder.append(attribute.name());
|
||||
|
||||
builder.append("=\""sv);
|
||||
builder.append(escape_string(attribute.value(), AttributeMode::Yes));
|
||||
builder.append('"');
|
||||
});
|
||||
|
||||
// 5. 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 (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));
|
||||
builder.append("</"sv);
|
||||
builder.append(tag_name);
|
||||
builder.append('>');
|
||||
|
||||
return IterationDecision::Continue;
|
||||
return serialize_element(element);
|
||||
}
|
||||
|
||||
if (is<DOM::Text>(current_node)) {
|
||||
|
|
|
@ -61,7 +61,7 @@ public:
|
|||
DOM::Document& document();
|
||||
|
||||
static Vector<JS::Handle<DOM::Node>> parse_html_fragment(DOM::Element& context_element, StringView);
|
||||
static String serialize_html_fragment(DOM::Node const& node);
|
||||
static String serialize_html_fragment(DOM::Node const& node, DOM::FragmentSerializationMode = DOM::FragmentSerializationMode::Inner);
|
||||
|
||||
enum class InsertionMode {
|
||||
#define __ENUMERATE_INSERTION_MODE(mode) mode,
|
||||
|
|
Loading…
Add table
Reference in a new issue