LibWeb: Fully implement the HTMLInputElement value
setter and getter
The setter was missing an implementation for the default and default/on value attribute modes. This patch adds a method to get the current value attribute mode, and implements the value setter and getter based on that mode according to the spec.
This commit is contained in:
parent
cfb9c5bb0e
commit
ce9ad3a236
Notes:
sideshowbarker
2024-07-17 01:55:29 +09:00
Author: https://github.com/trflynn89 Commit: https://github.com/SerenityOS/serenity/commit/ce9ad3a236 Pull-request: https://github.com/SerenityOS/serenity/pull/23236 Issue: https://github.com/SerenityOS/serenity/issues/23234
4 changed files with 147 additions and 70 deletions
5
Tests/LibWeb/Text/expected/input-value.txt
Normal file
5
Tests/LibWeb/Text/expected/input-value.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
pass text: "pass"
|
||||
hidden: "pass"
|
||||
button: "pass"
|
||||
checkbox: "pass"
|
||||
file: "" (threw exception)
|
26
Tests/LibWeb/Text/input/input-value.html
Normal file
26
Tests/LibWeb/Text/input/input-value.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<input id="text" type="text" />
|
||||
<input id="hidden" type="hidden" />
|
||||
<input id="button" type="button" />
|
||||
<input id="checkbox" type="checkbox" />
|
||||
<input id="file" type="file" />
|
||||
<script src="include.js"></script>
|
||||
<script>
|
||||
const testInput = (id) => {
|
||||
let input = document.getElementById(id);
|
||||
|
||||
try {
|
||||
input.value = "pass";
|
||||
println(`${id}: "${input.value}"`);
|
||||
} catch (e) {
|
||||
println(`${id}: "${input.value}" (threw exception)`);
|
||||
}
|
||||
};
|
||||
|
||||
test(() => {
|
||||
testInput("text");
|
||||
testInput("hidden");
|
||||
testInput("button");
|
||||
testInput("checkbox");
|
||||
testInput("file");
|
||||
});
|
||||
</script>
|
|
@ -346,93 +346,93 @@ void HTMLInputElement::did_pick_color(Optional<Color> picked_color)
|
|||
|
||||
String HTMLInputElement::value() const
|
||||
{
|
||||
switch (value_attribute_mode()) {
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
|
||||
case ValueAttributeMode::Value:
|
||||
// Return the current value of the element.
|
||||
return m_value;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
|
||||
case ValueAttributeMode::Default:
|
||||
// On getting, if the element has a value content attribute, return that attribute's value; otherwise, return
|
||||
// the empty string.
|
||||
return get_attribute_value(AttributeNames::value);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
|
||||
case ValueAttributeMode::DefaultOn:
|
||||
// On getting, if the element has a value content attribute, return that attribute's value; otherwise, return
|
||||
// the string "on".
|
||||
return get_attribute(AttributeNames::value).value_or("on"_string);
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
|
||||
if (type_state() == TypeAttributeState::FileUpload) {
|
||||
// NOTE: This "fakepath" requirement is a sad accident of history. See the example in the File Upload state section for more information.
|
||||
// NOTE: Since path components are not permitted in filenames in the list of selected files, the "\fakepath\" cannot be mistaken for a path component.
|
||||
// On getting, return the string "C:\fakepath\" followed by the name of the first file in the list of selected files, if any, or the empty string if the list is empty.
|
||||
case ValueAttributeMode::Filename:
|
||||
// On getting, return the string "C:\fakepath\" followed by the name of the first file in the list of selected
|
||||
// files, if any, or the empty string if the list is empty.
|
||||
if (m_selected_files && m_selected_files->item(0))
|
||||
return MUST(String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name()));
|
||||
return String {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
|
||||
if (type_state() == TypeAttributeState::Checkbox || type_state() == TypeAttributeState::RadioButton) {
|
||||
// On getting, if the element has a value content attribute, return that attribute's value; otherwise, return the string "on".
|
||||
return get_attribute(AttributeNames::value).value_or("on"_string);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
|
||||
if (type_state() == TypeAttributeState::Hidden
|
||||
|| type_state() == TypeAttributeState::SubmitButton
|
||||
|| type_state() == TypeAttributeState::ImageButton
|
||||
|| type_state() == TypeAttributeState::ResetButton
|
||||
|| type_state() == TypeAttributeState::Button) {
|
||||
// On getting, if the element has a value content attribute, return that attribute's value; otherwise, return the empty string.
|
||||
return get_attribute_value(AttributeNames::value);
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#range-state-(type=range):attr-input-value
|
||||
if (type_state() == TypeAttributeState::Range) {
|
||||
// https://html.spec.whatwg.org/multipage/input.html#concept-input-value-default-range
|
||||
double minimum = *min();
|
||||
double maximum = *max();
|
||||
double default_value = minimum + (maximum - minimum) / 2;
|
||||
if (maximum < minimum)
|
||||
default_value = minimum;
|
||||
|
||||
if (!parse_floating_point_number(m_value).has_value())
|
||||
return MUST(String::number(default_value));
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
|
||||
// Return the current value of the element.
|
||||
return m_value;
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
||||
switch (value_attribute_mode()) {
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
|
||||
case ValueAttributeMode::Value: {
|
||||
// 1. Let oldValue be the element's value.
|
||||
auto old_value = move(m_value);
|
||||
|
||||
// 2. Set the element's value to the new value.
|
||||
// NOTE: For the TextNode this is done as part of step 4 below.
|
||||
|
||||
// 3. Set the element's dirty value flag to true.
|
||||
m_dirty_value = true;
|
||||
|
||||
// 4. Invoke the value sanitization algorithm, if the element's type attribute's current state defines one.
|
||||
m_value = value_sanitization_algorithm(value);
|
||||
|
||||
// 5. If the element's value (after applying the value sanitization algorithm) is different from oldValue,
|
||||
// and the element has a text entry cursor position, move the text entry cursor position to the end of the
|
||||
// text control, unselecting any selected text and resetting the selection direction to "none".
|
||||
if (m_value != old_value) {
|
||||
if (m_text_node) {
|
||||
m_text_node->set_data(m_value);
|
||||
update_placeholder_visibility();
|
||||
|
||||
if (auto* browsing_context = document().browsing_context())
|
||||
browsing_context->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
|
||||
}
|
||||
|
||||
if (type_state() == TypeAttributeState::Color && m_color_well_element)
|
||||
update_color_well_element();
|
||||
|
||||
if (type_state() == TypeAttributeState::Range && m_slider_thumb)
|
||||
update_slider_thumb_element();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
|
||||
case ValueAttributeMode::Default:
|
||||
case ValueAttributeMode::DefaultOn:
|
||||
// On setting, set the value of the element's value content attribute to the new value.
|
||||
TRY(set_attribute(HTML::AttributeNames::value, value));
|
||||
break;
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
|
||||
if (type_state() == TypeAttributeState::FileUpload) {
|
||||
case ValueAttributeMode::Filename:
|
||||
// On setting, if the new value is the empty string, empty the list of selected files; otherwise, throw an "InvalidStateError" DOMException.
|
||||
if (!value.is_empty())
|
||||
return WebIDL::InvalidStateError::create(realm, "Setting value of input type file to non-empty string"_fly_string);
|
||||
|
||||
m_selected_files = nullptr;
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-value
|
||||
// 1. Let oldValue be the element's value.
|
||||
auto old_value = move(m_value);
|
||||
|
||||
// 2. Set the element's value to the new value.
|
||||
// NOTE: For the TextNode this is done as part of step 4 below.
|
||||
|
||||
// 3. Set the element's dirty value flag to true.
|
||||
m_dirty_value = true;
|
||||
|
||||
// 4. Invoke the value sanitization algorithm, if the element's type attribute's current state defines one.
|
||||
m_value = value_sanitization_algorithm(value);
|
||||
|
||||
// 5. If the element's value (after applying the value sanitization algorithm) is different from oldValue,
|
||||
// and the element has a text entry cursor position, move the text entry cursor position to the end of the
|
||||
// text control, unselecting any selected text and resetting the selection direction to "none".
|
||||
if (m_value != old_value) {
|
||||
if (m_text_node) {
|
||||
m_text_node->set_data(m_value);
|
||||
update_placeholder_visibility();
|
||||
|
||||
if (auto* browsing_context = document().browsing_context())
|
||||
browsing_context->set_cursor_position(DOM::Position::create(realm, *m_text_node, m_text_node->data().bytes().size()));
|
||||
}
|
||||
|
||||
if (type_state() == TypeAttributeState::Color && m_color_well_element)
|
||||
update_color_well_element();
|
||||
|
||||
if (type_state() == TypeAttributeState::Range && m_slider_thumb)
|
||||
update_slider_thumb_element();
|
||||
break;
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -1776,4 +1776,42 @@ bool HTMLInputElement::step_up_or_down_applies() const
|
|||
return value_as_number_applies();
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:dom-input-value-2
|
||||
HTMLInputElement::ValueAttributeMode HTMLInputElement::value_attribute_mode() const
|
||||
{
|
||||
switch (type_state()) {
|
||||
case TypeAttributeState::Text:
|
||||
case TypeAttributeState::Search:
|
||||
case TypeAttributeState::Telephone:
|
||||
case TypeAttributeState::URL:
|
||||
case TypeAttributeState::Email:
|
||||
case TypeAttributeState::Password:
|
||||
case TypeAttributeState::Date:
|
||||
case TypeAttributeState::Month:
|
||||
case TypeAttributeState::Week:
|
||||
case TypeAttributeState::Time:
|
||||
case TypeAttributeState::LocalDateAndTime:
|
||||
case TypeAttributeState::Number:
|
||||
case TypeAttributeState::Range:
|
||||
case TypeAttributeState::Color:
|
||||
return ValueAttributeMode::Value;
|
||||
|
||||
case TypeAttributeState::Hidden:
|
||||
case TypeAttributeState::SubmitButton:
|
||||
case TypeAttributeState::ImageButton:
|
||||
case TypeAttributeState::ResetButton:
|
||||
case TypeAttributeState::Button:
|
||||
return ValueAttributeMode::Default;
|
||||
|
||||
case TypeAttributeState::Checkbox:
|
||||
case TypeAttributeState::RadioButton:
|
||||
return ValueAttributeMode::DefaultOn;
|
||||
|
||||
case TypeAttributeState::FileUpload:
|
||||
return ValueAttributeMode::Filename;
|
||||
}
|
||||
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -216,6 +216,14 @@ private:
|
|||
// https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm
|
||||
String value_sanitization_algorithm(String const&) const;
|
||||
|
||||
enum class ValueAttributeMode {
|
||||
Value,
|
||||
Default,
|
||||
DefaultOn,
|
||||
Filename,
|
||||
};
|
||||
ValueAttributeMode value_attribute_mode() const;
|
||||
|
||||
void update_placeholder_visibility();
|
||||
JS::GCPtr<DOM::Element> m_placeholder_element;
|
||||
JS::GCPtr<DOM::Text> m_placeholder_text_node;
|
||||
|
|
Loading…
Add table
Reference in a new issue