diff options
author | Timothy Flynn <trflynn89@pm.me> | 2022-11-30 22:15:12 -0500 |
---|---|---|
committer | Tim Flynn <trflynn89@pm.me> | 2022-12-01 11:18:11 -0500 |
commit | 4a30446999588cf9bf0feace9ebc4eb29b3eb73a (patch) | |
tree | 44fdb8af3e3a42f66eaee44c5926dce2188ba331 | |
parent | fddbc2e3788797d62caf21f78868412d3ab4ad0b (diff) | |
download | serenity-4a30446999588cf9bf0feace9ebc4eb29b3eb73a.zip |
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.
-rw-r--r-- | Base/res/html/misc/input.html | 21 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Default.css | 4 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp | 41 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/HTML/HTMLInputElement.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp | 23 |
5 files changed, 91 insertions, 0 deletions
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 @@ +<html> +<head> + <style type="text/css"> + #placeholder1, #placeholder2 { + color: red; + width: 250px; + } + + #placeholder1::placeholder { + color: green; + } + </style> +</head> + +<body> <p> <input type="hidden" id="hidden" value="hidden" /><br /> <input type="text" id="text" value="text" /><br /> + <input type="text" id="placeholder1" placeholder="This placeholder should be green" /><br /> + <input type="text" id="placeholder2" placeholder="This placeholder should be grey" /><br /> <input type="search" id="search" value="search" /><br /> <input type="tel" id="tel" value="tel" /><br /> <input type="url" id="url" value="url" /><br /> @@ -29,6 +46,8 @@ var ids = [ "hidden", "text", + "placeholder1", + "placeholder2", "search", "tel", "url", @@ -69,3 +88,5 @@ }); </script> +</body> +</html> 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<void> 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<String> 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<void> set_value(String); + Optional<String> 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 <LibWeb/DOM/ParentNode.h> #include <LibWeb/DOM/ShadowRoot.h> #include <LibWeb/Dump.h> +#include <LibWeb/HTML/HTMLInputElement.h> #include <LibWeb/HTML/HTMLProgressElement.h> #include <LibWeb/Layout/InitialContainingBlock.h> #include <LibWeb/Layout/ListItemBox.h> @@ -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<HTML::HTMLInputElement>(dom_node)) { + auto& input_element = static_cast<HTML::HTMLInputElement&>(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<DOM::Text>(document.realm(), document, *placeholder_value); + auto* text_node = document.heap().allocate_without_realm<Layout::TextNode>(document, *text); + text_node->set_generated(true); + + push_parent(verify_cast<NodeWithStyle>(*layout_node)); + push_parent(verify_cast<NodeWithStyle>(*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<Layout::Node> TreeBuilder::build(DOM::Node& dom_node) |