mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
LibWeb: Add input and textarea minlength and maxlength support
This commit is contained in:
parent
9b645d20b9
commit
a2f101c10b
Notes:
sideshowbarker
2024-07-17 21:11:12 +09:00
Author: https://github.com/bplaat Commit: https://github.com/SerenityOS/serenity/commit/a2f101c10b Pull-request: https://github.com/SerenityOS/serenity/pull/22944 Reviewed-by: https://github.com/AtkinsSJ Reviewed-by: https://github.com/shannonbooth Reviewed-by: https://github.com/trflynn89
15 changed files with 162 additions and 5 deletions
1
Tests/LibWeb/Text/expected/input-maxlength.txt
Normal file
1
Tests/LibWeb/Text/expected/input-maxlength.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Hello
|
1
Tests/LibWeb/Text/expected/textarea-maxlength.txt
Normal file
1
Tests/LibWeb/Text/expected/textarea-maxlength.txt
Normal file
|
@ -0,0 +1 @@
|
|||
Hello
|
7
Tests/LibWeb/Text/input/input-maxlength.html
Normal file
7
Tests/LibWeb/Text/input/input-maxlength.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<input id="input" type="text" maxlength="5"><script src="include.js"></script><script>
|
||||
test(() => {
|
||||
const input = document.getElementById('input');
|
||||
internals.sendText(input, 'Hello World!');
|
||||
internals.commitText();
|
||||
});
|
||||
</script>
|
7
Tests/LibWeb/Text/input/textarea-maxlength.html
Normal file
7
Tests/LibWeb/Text/input/textarea-maxlength.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<textarea id="textarea" maxlength="5"></textarea><script src="include.js"></script><script>
|
||||
test(() => {
|
||||
const textarea = document.getElementById('textarea');
|
||||
internals.sendText(textarea, 'Hello World!');
|
||||
internals.commitText();
|
||||
});
|
||||
</script>
|
|
@ -35,6 +35,9 @@ public:
|
|||
|
||||
void set_always_editable(bool b) { m_always_editable = b; }
|
||||
|
||||
Optional<size_t> max_length() const { return m_max_length; }
|
||||
void set_max_length(Optional<size_t> max_length) { m_max_length = move(max_length); }
|
||||
|
||||
template<DerivedFrom<EditableTextNodeOwner> T>
|
||||
void set_editable_text_node_owner(Badge<T>, EditableTextNodeOwner& owner_element) { m_owner = &owner_element; }
|
||||
EditableTextNodeOwner* editable_text_node_owner() { return m_owner.ptr(); }
|
||||
|
@ -55,6 +58,7 @@ private:
|
|||
JS::GCPtr<EditableTextNodeOwner> m_owner;
|
||||
|
||||
bool m_always_editable { false };
|
||||
Optional<size_t> m_max_length {};
|
||||
bool m_is_password_input { false };
|
||||
};
|
||||
|
||||
|
|
|
@ -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) \
|
||||
|
|
|
@ -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<String> const& maybe_value)
|
||||
{
|
||||
|
@ -728,6 +741,7 @@ void HTMLInputElement::create_text_input_shadow_tree()
|
|||
m_text_node->set_editable_text_node_owner(Badge<HTMLInputElement> {}, *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::ImageBox>(*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<void> 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<void> 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
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
#include <LibWeb/Layout/ImageProvider.h>
|
||||
#include <LibWeb/WebIDL/DOMException.h>
|
||||
#include <LibWeb/WebIDL/Types.h>
|
||||
|
||||
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<FileAPI::FileList>);
|
||||
|
||||
WebIDL::Long max_length() const;
|
||||
WebIDL::ExceptionOr<void> set_max_length(WebIDL::Long);
|
||||
|
||||
WebIDL::Long min_length() const;
|
||||
WebIDL::ExceptionOr<void> set_min_length(WebIDL::Long);
|
||||
|
||||
unsigned size() const;
|
||||
WebIDL::ExceptionOr<void> set_size(unsigned value);
|
||||
|
||||
|
@ -235,6 +242,7 @@ private:
|
|||
WebIDL::ExceptionOr<void> run_input_activation_behavior(DOM::Event const&);
|
||||
void set_checked_within_group();
|
||||
|
||||
void handle_maxlength_attribute();
|
||||
void handle_readonly_attribute(Optional<String> const& value);
|
||||
WebIDL::ExceptionOr<void> handle_src_attribute(StringView value);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2024, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* 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<void> 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<void> 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<String> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
* Copyright (c) 2022, Luke Wilde <lukew@serenityos.org>
|
||||
* Copyright (c) 2024, Bastiaan van der Plaat <bastiaan.v.d.plaat@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -11,6 +12,7 @@
|
|||
#include <LibWeb/DOM/Text.h>
|
||||
#include <LibWeb/HTML/FormAssociatedElement.h>
|
||||
#include <LibWeb/HTML/HTMLElement.h>
|
||||
#include <LibWeb/WebIDL/Types.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -77,6 +79,12 @@ public:
|
|||
|
||||
u32 text_length() const;
|
||||
|
||||
WebIDL::Long max_length() const;
|
||||
WebIDL::ExceptionOr<void> set_max_length(WebIDL::Long);
|
||||
|
||||
WebIDL::Long min_length() const;
|
||||
WebIDL::ExceptionOr<void> set_min_length(WebIDL::Long);
|
||||
|
||||
unsigned cols() const;
|
||||
WebIDL::ExceptionOr<void> set_cols(unsigned);
|
||||
|
||||
|
@ -95,6 +103,7 @@ private:
|
|||
void create_shadow_tree_if_needed();
|
||||
|
||||
void handle_readonly_attribute(Optional<String> const& value);
|
||||
void handle_maxlength_attribute();
|
||||
|
||||
void update_placeholder_visibility();
|
||||
JS::GCPtr<DOM::Element> m_placeholder_element;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -92,4 +92,11 @@ Optional<double> parse_floating_point_number(StringView string)
|
|||
return maybe_double.value();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<String> 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibWeb/WebIDL/ExceptionOr.h>
|
||||
#include <LibWeb/WebIDL/Types.h>
|
||||
|
||||
namespace Web::HTML {
|
||||
|
||||
|
@ -17,4 +19,6 @@ Optional<u32> parse_non_negative_integer(StringView string);
|
|||
|
||||
Optional<double> parse_floating_point_number(StringView string);
|
||||
|
||||
WebIDL::ExceptionOr<String> convert_non_negative_integer_to_string(JS::Realm&, WebIDL::Long);
|
||||
|
||||
}
|
||||
|
|
|
@ -111,8 +111,14 @@ void EditEventHandler::handle_insert(JS::NonnullGCPtr<DOM::Position> 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();
|
||||
|
|
Loading…
Reference in a new issue