From 4a30446999588cf9bf0feace9ebc4eb29b3eb73a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 30 Nov 2022 22:15:12 -0500 Subject: [PATCH] LibWeb: Support displaying HTMLInputElement placeholder values This adds support for parsing the ::placeholder pseudo-element and injecting an anonymous layout node with that element when the input element's data is empty. --- Base/res/html/misc/input.html | 21 ++++++++++ Userland/Libraries/LibWeb/CSS/Default.css | 4 ++ .../LibWeb/HTML/HTMLInputElement.cpp | 41 +++++++++++++++++++ .../Libraries/LibWeb/HTML/HTMLInputElement.h | 2 + .../Libraries/LibWeb/Layout/TreeBuilder.cpp | 23 +++++++++++ 5 files changed, 91 insertions(+) diff --git a/Base/res/html/misc/input.html b/Base/res/html/misc/input.html index b679713398..16ed67fe98 100644 --- a/Base/res/html/misc/input.html +++ b/Base/res/html/misc/input.html @@ -1,6 +1,23 @@ + + + + + +



+
+



@@ -29,6 +46,8 @@ var ids = [ "hidden", "text", + "placeholder1", + "placeholder2", "search", "tel", "url", @@ -69,3 +88,5 @@ }); + + diff --git a/Userland/Libraries/LibWeb/CSS/Default.css b/Userland/Libraries/LibWeb/CSS/Default.css index 60b8ec8c6a..928dfff8f5 100644 --- a/Userland/Libraries/LibWeb/CSS/Default.css +++ b/Userland/Libraries/LibWeb/CSS/Default.css @@ -49,6 +49,10 @@ input[type=submit], input[type=button], input[type=reset], input[type=checkbox], cursor: unset; } +input::placeholder { + color: rgb(117, 117, 117); +} + button, input[type=submit], input[type=button], input[type=reset] { padding: 1px 4px; background-color: -libweb-palette-button; diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp index a719379e30..e453066a32 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp @@ -317,6 +317,47 @@ WebIDL::ExceptionOr HTMLInputElement::set_value(String value) return {}; } +// https://html.spec.whatwg.org/multipage/input.html#the-input-element:attr-input-placeholder-3 +static bool is_allowed_to_have_placeholder(HTML::HTMLInputElement::TypeAttributeState state) +{ + switch (state) { + case HTML::HTMLInputElement::TypeAttributeState::Text: + case HTML::HTMLInputElement::TypeAttributeState::Search: + case HTML::HTMLInputElement::TypeAttributeState::URL: + case HTML::HTMLInputElement::TypeAttributeState::Telephone: + case HTML::HTMLInputElement::TypeAttributeState::Email: + case HTML::HTMLInputElement::TypeAttributeState::Password: + case HTML::HTMLInputElement::TypeAttributeState::Number: + return true; + default: + return false; + } +} + +// https://html.spec.whatwg.org/multipage/input.html#attr-input-placeholder +Optional HTMLInputElement::placeholder_value() const +{ + if (!m_text_node || !m_text_node->data().is_empty()) + return {}; + if (!is_allowed_to_have_placeholder(type_state())) + return {}; + if (!has_attribute(HTML::AttributeNames::placeholder)) + return {}; + + auto placeholder = attribute(HTML::AttributeNames::placeholder); + + if (placeholder.contains('\r') || placeholder.contains('\n')) { + StringBuilder builder; + for (auto ch : placeholder) { + if (ch != '\r' && ch != '\n') + builder.append(ch); + } + placeholder = builder.to_string(); + } + + return placeholder; +} + void HTMLInputElement::create_shadow_tree_if_needed() { if (shadow_root()) diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h index 3b942e0d10..25aea40154 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h @@ -66,6 +66,8 @@ public: String value() const; WebIDL::ExceptionOr set_value(String); + Optional placeholder_value() const; + bool checked() const { return m_checked; } enum class ChangeSource { Programmatic, diff --git a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp index a95f5a0bf1..18f6ef194b 100644 --- a/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp +++ b/Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -292,6 +293,28 @@ void TreeBuilder::create_layout_tree(DOM::Node& dom_node, TreeBuilder::Context& progress.set_pseudo_element_node({}, CSS::Selector::PseudoElement::ProgressValue, progress_value); } } + + if (is(dom_node)) { + auto& input_element = static_cast(dom_node); + + if (auto placeholder_value = input_element.placeholder_value(); placeholder_value.has_value()) { + auto placeholder_style = style_computer.compute_style(input_element, CSS::Selector::PseudoElement::Placeholder); + auto placeholder = DOM::Element::create_layout_node_for_display_type(document, placeholder_style->display(), placeholder_style, nullptr); + + auto* text = document.heap().allocate(document.realm(), document, *placeholder_value); + auto* text_node = document.heap().allocate_without_realm(document, *text); + text_node->set_generated(true); + + push_parent(verify_cast(*layout_node)); + push_parent(verify_cast(*placeholder)); + insert_node_into_inline_or_block_ancestor(*text_node, text_node->display(), AppendOrPrepend::Append); + pop_parent(); + insert_node_into_inline_or_block_ancestor(*placeholder, placeholder->display(), AppendOrPrepend::Append); + pop_parent(); + + input_element.set_pseudo_element_node({}, CSS::Selector::PseudoElement::Placeholder, placeholder); + } + } } JS::GCPtr TreeBuilder::build(DOM::Node& dom_node)