diff --git a/Tests/LibWeb/Text/expected/input-maxlength.txt b/Tests/LibWeb/Text/expected/input-maxlength.txt
new file mode 100644
index 00000000000..a8f9fd84ee0
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/input-maxlength.txt
@@ -0,0 +1 @@
+Hello
diff --git a/Tests/LibWeb/Text/expected/textarea-maxlength.txt b/Tests/LibWeb/Text/expected/textarea-maxlength.txt
new file mode 100644
index 00000000000..a8f9fd84ee0
--- /dev/null
+++ b/Tests/LibWeb/Text/expected/textarea-maxlength.txt
@@ -0,0 +1 @@
+Hello
diff --git a/Tests/LibWeb/Text/input/input-maxlength.html b/Tests/LibWeb/Text/input/input-maxlength.html
new file mode 100644
index 00000000000..f6d57bc5298
--- /dev/null
+++ b/Tests/LibWeb/Text/input/input-maxlength.html
@@ -0,0 +1,7 @@
+
diff --git a/Tests/LibWeb/Text/input/textarea-maxlength.html b/Tests/LibWeb/Text/input/textarea-maxlength.html
new file mode 100644
index 00000000000..3f97d5729fa
--- /dev/null
+++ b/Tests/LibWeb/Text/input/textarea-maxlength.html
@@ -0,0 +1,7 @@
+
diff --git a/Userland/Libraries/LibWeb/DOM/Text.h b/Userland/Libraries/LibWeb/DOM/Text.h
index a7dae767f84..621273880f9 100644
--- a/Userland/Libraries/LibWeb/DOM/Text.h
+++ b/Userland/Libraries/LibWeb/DOM/Text.h
@@ -35,6 +35,9 @@ public:
void set_always_editable(bool b) { m_always_editable = b; }
+ Optional max_length() const { return m_max_length; }
+ void set_max_length(Optional max_length) { m_max_length = move(max_length); }
+
template T>
void set_editable_text_node_owner(Badge, EditableTextNodeOwner& owner_element) { m_owner = &owner_element; }
EditableTextNodeOwner* editable_text_node_owner() { return m_owner.ptr(); }
@@ -55,6 +58,7 @@ private:
JS::GCPtr m_owner;
bool m_always_editable { false };
+ Optional m_max_length {};
bool m_is_password_input { false };
};
diff --git a/Userland/Libraries/LibWeb/HTML/AttributeNames.h b/Userland/Libraries/LibWeb/HTML/AttributeNames.h
index 6bee2a22482..9af36d13741 100644
--- a/Userland/Libraries/LibWeb/HTML/AttributeNames.h
+++ b/Userland/Libraries/LibWeb/HTML/AttributeNames.h
@@ -103,9 +103,11 @@ namespace AttributeNames {
__ENUMERATE_HTML_ATTRIBUTE(marginheight) \
__ENUMERATE_HTML_ATTRIBUTE(marginwidth) \
__ENUMERATE_HTML_ATTRIBUTE(max) \
+ __ENUMERATE_HTML_ATTRIBUTE(maxlength) \
__ENUMERATE_HTML_ATTRIBUTE(media) \
__ENUMERATE_HTML_ATTRIBUTE(method) \
__ENUMERATE_HTML_ATTRIBUTE(min) \
+ __ENUMERATE_HTML_ATTRIBUTE(minlength) \
__ENUMERATE_HTML_ATTRIBUTE(multiple) \
__ENUMERATE_HTML_ATTRIBUTE(muted) \
__ENUMERATE_HTML_ATTRIBUTE(name) \
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
index 09cb4db062f..7051630c59f 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.cpp
@@ -578,6 +578,19 @@ static bool is_allowed_to_be_readonly(HTML::HTMLInputElement::TypeAttributeState
}
}
+// https://html.spec.whatwg.org/multipage/input.html#attr-input-maxlength
+void HTMLInputElement::handle_maxlength_attribute()
+{
+ if (m_text_node) {
+ auto max_length = this->max_length();
+ if (max_length >= 0) {
+ m_text_node->set_max_length(max_length);
+ } else {
+ m_text_node->set_max_length({});
+ }
+ }
+}
+
// https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly
void HTMLInputElement::handle_readonly_attribute(Optional const& maybe_value)
{
@@ -728,6 +741,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
m_text_node->set_editable_text_node_owner(Badge {}, *this);
if (type_state() == TypeAttributeState::Password)
m_text_node->set_is_password_input({}, true);
+ handle_maxlength_attribute();
MUST(m_inner_text_element->append_child(*m_text_node));
update_placeholder_visibility();
@@ -1024,6 +1038,8 @@ void HTMLInputElement::form_associated_element_attribute_changed(FlyString const
} else if (name == HTML::AttributeNames::alt) {
if (layout_node() && type_state() == TypeAttributeState::ImageButton)
did_update_alt_text(verify_cast(*layout_node()));
+ } else if (name == HTML::AttributeNames::maxlength) {
+ handle_maxlength_attribute();
}
}
@@ -1445,6 +1461,40 @@ void HTMLInputElement::apply_presentational_hints(CSS::StyleProperties& style) c
});
}
+// https://html.spec.whatwg.org/multipage/input.html#dom-input-maxlength
+WebIDL::Long HTMLInputElement::max_length() const
+{
+ // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
+ if (auto maxlength_string = get_attribute(HTML::AttributeNames::maxlength); maxlength_string.has_value()) {
+ if (auto maxlength = parse_non_negative_integer(*maxlength_string); maxlength.has_value())
+ return *maxlength;
+ }
+ return -1;
+}
+
+WebIDL::ExceptionOr HTMLInputElement::set_max_length(WebIDL::Long value)
+{
+ // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
+ return set_attribute(HTML::AttributeNames::maxlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
+}
+
+// https://html.spec.whatwg.org/multipage/input.html#dom-input-minlength
+WebIDL::Long HTMLInputElement::min_length() const
+{
+ // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
+ if (auto minlength_string = get_attribute(HTML::AttributeNames::minlength); minlength_string.has_value()) {
+ if (auto minlength = parse_non_negative_integer(*minlength_string); minlength.has_value())
+ return *minlength;
+ }
+ return -1;
+}
+
+WebIDL::ExceptionOr HTMLInputElement::set_min_length(WebIDL::Long value)
+{
+ // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
+ return set_attribute(HTML::AttributeNames::minlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
+}
+
// https://html.spec.whatwg.org/multipage/input.html#the-size-attribute
unsigned HTMLInputElement::size() const
{
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
index 045c6589881..0f87ecbfc94 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.h
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
namespace Web::HTML {
@@ -104,6 +105,12 @@ public:
// https://html.spec.whatwg.org/multipage/input.html#update-the-file-selection
void update_the_file_selection(JS::NonnullGCPtr);
+ WebIDL::Long max_length() const;
+ WebIDL::ExceptionOr set_max_length(WebIDL::Long);
+
+ WebIDL::Long min_length() const;
+ WebIDL::ExceptionOr set_min_length(WebIDL::Long);
+
unsigned size() const;
WebIDL::ExceptionOr set_size(unsigned value);
@@ -235,6 +242,7 @@ private:
WebIDL::ExceptionOr run_input_activation_behavior(DOM::Event const&);
void set_checked_within_group();
+ void handle_maxlength_attribute();
void handle_readonly_attribute(Optional const& value);
WebIDL::ExceptionOr handle_src_attribute(StringView value);
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
index 27f19847332..a41d19732f7 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLInputElement.idl
@@ -25,9 +25,9 @@ interface HTMLInputElement : HTMLElement {
attribute boolean indeterminate;
// FIXME: readonly attribute HTMLDataListElement? list;
[CEReactions, Reflect] attribute DOMString max;
- // FIXME: [CEReactions] attribute long maxLength;
+ [CEReactions] attribute long maxLength;
[CEReactions, Reflect] attribute DOMString min;
- // FIXME: [CEReactions] attribute long minLength;
+ [CEReactions] attribute long minLength;
[CEReactions, Reflect] attribute boolean multiple;
[CEReactions, Reflect] attribute DOMString name;
// FIXME: [CEReactions] attribute DOMString pattern;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
index a3ec6d558bb..0b2e294966d 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.cpp
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2023, Sam Atkins
+ * Copyright (c) 2024, Bastiaan van der Plaat
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -147,6 +148,40 @@ u32 HTMLTextAreaElement::text_length() const
return Utf16View { utf16_data }.length_in_code_units();
}
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength
+WebIDL::Long HTMLTextAreaElement::max_length() const
+{
+ // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
+ if (auto maxlength_string = get_attribute(HTML::AttributeNames::maxlength); maxlength_string.has_value()) {
+ if (auto maxlength = parse_non_negative_integer(*maxlength_string); maxlength.has_value())
+ return *maxlength;
+ }
+ return -1;
+}
+
+WebIDL::ExceptionOr HTMLTextAreaElement::set_max_length(WebIDL::Long value)
+{
+ // The maxLength IDL attribute must reflect the maxlength content attribute, limited to only non-negative numbers.
+ return set_attribute(HTML::AttributeNames::maxlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
+}
+
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-minlength
+WebIDL::Long HTMLTextAreaElement::min_length() const
+{
+ // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
+ if (auto minlength_string = get_attribute(HTML::AttributeNames::minlength); minlength_string.has_value()) {
+ if (auto minlength = parse_non_negative_integer(*minlength_string); minlength.has_value())
+ return *minlength;
+ }
+ return -1;
+}
+
+WebIDL::ExceptionOr HTMLTextAreaElement::set_min_length(WebIDL::Long value)
+{
+ // The minLength IDL attribute must reflect the minlength content attribute, limited to only non-negative numbers.
+ return set_attribute(HTML::AttributeNames::minlength, TRY(convert_non_negative_integer_to_string(realm(), value)));
+}
+
// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-cols
unsigned HTMLTextAreaElement::cols() const
{
@@ -208,6 +243,7 @@ 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);
+ handle_maxlength_attribute();
MUST(m_inner_text_element->append_child(*m_text_node));
update_placeholder_visibility();
@@ -223,6 +259,19 @@ void HTMLTextAreaElement::handle_readonly_attribute(Optional const& mayb
m_text_node->set_always_editable(m_is_mutable);
}
+// https://html.spec.whatwg.org/multipage/form-elements.html#dom-textarea-maxlength
+void HTMLTextAreaElement::handle_maxlength_attribute()
+{
+ if (m_text_node) {
+ auto max_length = this->max_length();
+ if (max_length >= 0) {
+ m_text_node->set_max_length(max_length);
+ } else {
+ m_text_node->set_max_length({});
+ }
+ }
+}
+
void HTMLTextAreaElement::update_placeholder_visibility()
{
if (!m_placeholder_element)
@@ -257,6 +306,8 @@ void HTMLTextAreaElement::form_associated_element_attribute_changed(FlyString co
m_placeholder_text_node->set_data(value.value_or(String {}));
} else if (name == HTML::AttributeNames::readonly) {
handle_readonly_attribute(value);
+ } else if (name == HTML::AttributeNames::maxlength) {
+ handle_maxlength_attribute();
}
}
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
index e2d4837d798..76cee0b6a68 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.h
@@ -1,6 +1,7 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
* Copyright (c) 2022, Luke Wilde
+ * Copyright (c) 2024, Bastiaan van der Plaat
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@@ -11,6 +12,7 @@
#include
#include
#include
+#include
namespace Web::HTML {
@@ -77,6 +79,12 @@ public:
u32 text_length() const;
+ WebIDL::Long max_length() const;
+ WebIDL::ExceptionOr set_max_length(WebIDL::Long);
+
+ WebIDL::Long min_length() const;
+ WebIDL::ExceptionOr set_min_length(WebIDL::Long);
+
unsigned cols() const;
WebIDL::ExceptionOr set_cols(unsigned);
@@ -95,6 +103,7 @@ private:
void create_shadow_tree_if_needed();
void handle_readonly_attribute(Optional const& value);
+ void handle_maxlength_attribute();
void update_placeholder_visibility();
JS::GCPtr m_placeholder_element;
diff --git a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
index 6fe82e9eba9..0929acc7d55 100644
--- a/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
+++ b/Userland/Libraries/LibWeb/HTML/HTMLTextAreaElement.idl
@@ -11,8 +11,8 @@ interface HTMLTextAreaElement : HTMLElement {
[CEReactions, Reflect=dirname] attribute DOMString dirName;
[CEReactions, Reflect] attribute boolean disabled;
readonly attribute HTMLFormElement? form;
- // FIXME: [CEReactions] attribute long maxLength;
- // FIXME: [CEReactions] attribute long minLength;
+ [CEReactions] attribute long maxLength;
+ [CEReactions] attribute long minLength;
[CEReactions, Reflect] attribute DOMString name;
[CEReactions, Reflect] attribute DOMString placeholder;
[CEReactions, Reflect=readonly] attribute boolean readOnly;
diff --git a/Userland/Libraries/LibWeb/HTML/Numbers.cpp b/Userland/Libraries/LibWeb/HTML/Numbers.cpp
index 4c930e109c7..58d18ccefea 100644
--- a/Userland/Libraries/LibWeb/HTML/Numbers.cpp
+++ b/Userland/Libraries/LibWeb/HTML/Numbers.cpp
@@ -92,4 +92,11 @@ Optional parse_floating_point_number(StringView string)
return maybe_double.value();
}
+WebIDL::ExceptionOr convert_non_negative_integer_to_string(JS::Realm& realm, WebIDL::Long value)
+{
+ if (value < 0)
+ return WebIDL::IndexSizeError::create(realm, "The attribute is limited to only non-negative numbers"_fly_string);
+ return MUST(String::number(value));
+}
+
}
diff --git a/Userland/Libraries/LibWeb/HTML/Numbers.h b/Userland/Libraries/LibWeb/HTML/Numbers.h
index 83f2aeeca4f..28d9be20e57 100644
--- a/Userland/Libraries/LibWeb/HTML/Numbers.h
+++ b/Userland/Libraries/LibWeb/HTML/Numbers.h
@@ -8,6 +8,8 @@
#include
#include
+#include
+#include
namespace Web::HTML {
@@ -17,4 +19,6 @@ Optional parse_non_negative_integer(StringView string);
Optional parse_floating_point_number(StringView string);
+WebIDL::ExceptionOr convert_non_negative_integer_to_string(JS::Realm&, WebIDL::Long);
+
}
diff --git a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
index 9dfe950c00f..5b3dccdcfb2 100644
--- a/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
+++ b/Userland/Libraries/LibWeb/Page/EditEventHandler.cpp
@@ -111,8 +111,14 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr position, u
builder.append(node.data().bytes_as_string_view().substring_view(0, position->offset()));
builder.append_code_point(code_point);
builder.append(node.data().bytes_as_string_view().substring_view(position->offset()));
- node.set_data(MUST(builder.to_string()));
+ // Cut string by max length
+ // FIXME: Cut by UTF-16 code units instead of raw bytes
+ if (auto max_length = node.max_length(); max_length.has_value() && builder.string_view().length() > *max_length) {
+ node.set_data(MUST(String::from_utf8(builder.string_view().substring_view(0, *max_length))));
+ } else {
+ node.set_data(MUST(builder.to_string()));
+ }
node.invalidate_style();
} else {
auto& node = *position->node();