summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2022-11-30 22:15:12 -0500
committerTim Flynn <trflynn89@pm.me>2022-12-01 11:18:11 -0500
commit4a30446999588cf9bf0feace9ebc4eb29b3eb73a (patch)
tree44fdb8af3e3a42f66eaee44c5926dce2188ba331
parentfddbc2e3788797d62caf21f78868412d3ab4ad0b (diff)
downloadserenity-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.html21
-rw-r--r--Userland/Libraries/LibWeb/CSS/Default.css4
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp41
-rw-r--r--Userland/Libraries/LibWeb/HTML/HTMLInputElement.h2
-rw-r--r--Userland/Libraries/LibWeb/Layout/TreeBuilder.cpp23
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)