mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 21:47:45 +00:00
LibWeb: Make Node.textContent more spec compliant
The current implementation felt a bit ad-hoc and notably allowed textContent to operate on all node types. It also only returned the child text content of the Node instead of the descendant text content.
This commit is contained in:
parent
d36838d050
commit
5e61382849
4 changed files with 40 additions and 14 deletions
|
@ -25,8 +25,6 @@ public:
|
|||
|
||||
unsigned length() const { return m_data.length(); }
|
||||
|
||||
virtual String text_content() const override { return m_data; }
|
||||
|
||||
protected:
|
||||
explicit CharacterData(Document&, NodeType, const String&);
|
||||
|
||||
|
|
|
@ -98,22 +98,45 @@ const HTML::HTMLElement* Node::enclosing_html_element_with_attribute(const FlySt
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
String Node::text_content() const
|
||||
// https://dom.spec.whatwg.org/#concept-descendant-text-content
|
||||
String Node::descendant_text_content() const
|
||||
{
|
||||
StringBuilder builder;
|
||||
for (auto* child = first_child(); child; child = child->next_sibling()) {
|
||||
builder.append(child->text_content());
|
||||
}
|
||||
for_each_in_subtree_of_type<Text>([&](auto& text_node) {
|
||||
builder.append(text_node.data());
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return builder.to_string();
|
||||
}
|
||||
|
||||
void Node::set_text_content(const String& content)
|
||||
// https://dom.spec.whatwg.org/#dom-node-textcontent
|
||||
String Node::text_content() const
|
||||
{
|
||||
if (is_text()) {
|
||||
verify_cast<Text>(this)->set_data(content);
|
||||
if (is<DocumentFragment>(this) || is<Element>(this))
|
||||
return descendant_text_content();
|
||||
else if (is<CharacterData>(this))
|
||||
return verify_cast<CharacterData>(this)->data();
|
||||
|
||||
// FIXME: Else if this is an Attr node, return this's value.
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://dom.spec.whatwg.org/#ref-for-dom-node-textcontent%E2%91%A0
|
||||
void Node::set_text_content(String const& content)
|
||||
{
|
||||
if (is<DocumentFragment>(this) || is<Element>(this)) {
|
||||
string_replace_all(content);
|
||||
} else if (is<CharacterData>(this)) {
|
||||
// FIXME: CharacterData::set_data is not spec compliant. Make this match the spec when set_data becomes spec compliant.
|
||||
// Do note that this will make this function able to throw an exception.
|
||||
|
||||
auto* character_data_node = verify_cast<CharacterData>(this);
|
||||
character_data_node->set_data(content);
|
||||
} else {
|
||||
remove_all_children();
|
||||
append_child(document().create_text_node(content));
|
||||
// FIXME: Else if this is an Attr node, set an existing attribute value with this and the given value.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
set_needs_style_update(true);
|
||||
|
|
|
@ -99,8 +99,9 @@ public:
|
|||
|
||||
virtual FlyString node_name() const = 0;
|
||||
|
||||
virtual String text_content() const;
|
||||
void set_text_content(const String&);
|
||||
String descendant_text_content() const;
|
||||
String text_content() const;
|
||||
void set_text_content(String const&);
|
||||
|
||||
Document& document() { return *m_document; }
|
||||
const Document& document() const { return *m_document; }
|
||||
|
|
|
@ -14,7 +14,11 @@ interface Node : EventTarget {
|
|||
readonly attribute Element? parentElement;
|
||||
readonly attribute boolean isConnected;
|
||||
readonly attribute Document? ownerDocument;
|
||||
attribute DOMString textContent;
|
||||
|
||||
// FIXME: [LegacyNullToEmptyString] is not allowed on nullable types as per the Web IDL spec.
|
||||
// However, we only apply it to setters, so this works as a stop gap.
|
||||
// Replace this with something like a special cased [LegacyNullToEmptyString].
|
||||
[LegacyNullToEmptyString] attribute DOMString? textContent;
|
||||
|
||||
Node appendChild(Node node);
|
||||
[ImplementedAs=pre_insert] Node insertBefore(Node node, Node? child);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue