) [267,11 240x21.84375]
+ PaintableWithLines (BlockContainer
) [267,11 240x21.84375]
TextPaintable (TextNode<#text>)
diff --git a/Tests/LibWeb/Layout/input/textarea-content.html b/Tests/LibWeb/Layout/input/textarea-content.html
index daaf12f8c83..137ec9262a4 100644
--- a/Tests/LibWeb/Layout/input/textarea-content.html
+++ b/Tests/LibWeb/Layout/input/textarea-content.html
@@ -1,9 +1,11 @@
-
-
+
-
diff --git a/Userland/Libraries/LibWeb/CSS/Default.css b/Userland/Libraries/LibWeb/CSS/Default.css
index 55db64d477e..f61d198adb1 100644
--- a/Userland/Libraries/LibWeb/CSS/Default.css
+++ b/Userland/Libraries/LibWeb/CSS/Default.css
@@ -43,7 +43,7 @@ textarea {
height: attr(rows lh, 2lh);
}
-input::placeholder {
+input::placeholder, textarea::placeholder {
color: GrayText;
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
index 2107379bf79..98780faaf53 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
@@ -47,6 +47,8 @@ void HTMLTextAreaElement::initialize(JS::Realm& realm)
void HTMLTextAreaElement::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
+ visitor.visit(m_placeholder_element);
+ visitor.visit(m_placeholder_text_node);
visitor.visit(m_inner_text_element);
visitor.visit(m_text_node);
}
@@ -86,6 +88,8 @@ void HTMLTextAreaElement::reset_algorithm()
m_dirty = false;
// and set the raw value of element to its child text content.
m_raw_value = child_text_content();
+
+ update_placeholder_visibility();
}
void HTMLTextAreaElement::form_associated_element_was_inserted()
@@ -181,9 +185,22 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
return;
auto shadow_root = heap().allocate(realm(), document(), *this, Bindings::ShadowRootMode::Closed);
+ set_shadow_root(shadow_root);
+
auto element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
+ MUST(shadow_root->append_child(element));
+
+ m_placeholder_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
+ m_placeholder_element->set_use_pseudo_element(CSS::Selector::PseudoElement::Type::Placeholder);
+ MUST(element->append_child(*m_placeholder_element));
+
+ m_placeholder_text_node = heap().allocate(realm(), document(), String {});
+ m_placeholder_text_node->set_data(get_attribute(HTML::AttributeNames::placeholder).value_or(String {}));
+ m_placeholder_text_node->set_editable_text_node_owner(Badge {}, *this);
+ MUST(m_placeholder_element->append_child(*m_placeholder_text_node));
m_inner_text_element = MUST(DOM::create_element(document(), HTML::TagNames::div, Namespace::HTML));
+ MUST(element->append_child(*m_inner_text_element));
m_text_node = heap().allocate(realm(), document(), String {});
m_text_node->set_always_editable(true);
@@ -191,11 +208,23 @@ void HTMLTextAreaElement::create_shadow_tree_if_needed()
// NOTE: If `children_changed()` was called before now, `m_raw_value` will hold the text content.
// Otherwise, it will get filled in whenever that does get called.
m_text_node->set_text_content(m_raw_value);
-
MUST(m_inner_text_element->append_child(*m_text_node));
- MUST(element->append_child(*m_inner_text_element));
- MUST(shadow_root->append_child(element));
- set_shadow_root(shadow_root);
+
+ update_placeholder_visibility();
+}
+
+void HTMLTextAreaElement::update_placeholder_visibility()
+{
+ if (!m_placeholder_element)
+ return;
+ if (!m_text_node)
+ return;
+ auto placeholder_text = get_attribute(AttributeNames::placeholder);
+ if (placeholder_text.has_value() && m_text_node->data().is_empty()) {
+ MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "block"sv));
+ } else {
+ MUST(m_placeholder_element->style_for_bindings()->set_property(CSS::PropertyID::Display, "none"sv));
+ }
}
// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element:children-changed-steps
@@ -207,6 +236,16 @@ void HTMLTextAreaElement::children_changed()
m_raw_value = child_text_content();
if (m_text_node)
m_text_node->set_text_content(m_raw_value);
+ update_placeholder_visibility();
+ }
+}
+
+void HTMLTextAreaElement::attribute_changed(FlyString const& name, Optional const& value)
+{
+ HTMLElement::attribute_changed(name, value);
+ if (name == HTML::AttributeNames::placeholder) {
+ if (m_placeholder_text_node)
+ m_placeholder_text_node->set_data(value.value_or(String {}));
}
}
@@ -214,6 +253,8 @@ void HTMLTextAreaElement::did_edit_text_node(Badge)
{
// A textarea element's dirty value flag must be set to true whenever the user interacts with the control in a way that changes the raw value.
m_dirty = true;
+
+ update_placeholder_visibility();
}
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
index 359aaacbc8a..6d38ff9ef76 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
@@ -56,6 +56,8 @@ public:
virtual bool is_auto_capitalize_inheriting() const override { return true; }
// ^HTMLElement
+ virtual void attribute_changed(FlyString const&, Optional const&) override;
+
// https://html.spec.whatwg.org/multipage/forms.html#category-label
virtual bool is_labelable() const override { return true; }
@@ -94,6 +96,10 @@ private:
void create_shadow_tree_if_needed();
+ void update_placeholder_visibility();
+ JS::GCPtr m_placeholder_element;
+ JS::GCPtr m_placeholder_text_node;
+
JS::GCPtr m_inner_text_element;
JS::GCPtr m_text_node;