mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 04:47:35 +00:00
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
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
|
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
|
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-filename
|
||||||
if (type_state() == TypeAttributeState::FileUpload) {
|
case ValueAttributeMode::Filename:
|
||||||
// NOTE: This "fakepath" requirement is a sad accident of history. See the example in the File Upload state section for more information.
|
// On getting, return the string "C:\fakepath\" followed by the name of the first file in the list of selected
|
||||||
// NOTE: Since path components are not permitted in filenames in the list of selected files, the "\fakepath\" cannot be mistaken for a path component.
|
// files, if any, or the empty string if the list is empty.
|
||||||
// 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))
|
if (m_selected_files && m_selected_files->item(0))
|
||||||
return MUST(String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name()));
|
return MUST(String::formatted("C:\\fakepath\\{}", m_selected_files->item(0)->name()));
|
||||||
return String {};
|
return String {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/input.html#dom-input-value-default-on
|
VERIFY_NOT_REACHED();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
|
WebIDL::ExceptionOr<void> HTMLInputElement::set_value(String const& value)
|
||||||
{
|
{
|
||||||
auto& realm = this->realm();
|
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
|
// 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.
|
// 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())
|
if (!value.is_empty())
|
||||||
return WebIDL::InvalidStateError::create(realm, "Setting value of input type file to non-empty string"_fly_string);
|
return WebIDL::InvalidStateError::create(realm, "Setting value of input type file to non-empty string"_fly_string);
|
||||||
|
|
||||||
m_selected_files = nullptr;
|
m_selected_files = nullptr;
|
||||||
return {};
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
@ -1776,4 +1776,42 @@ bool HTMLInputElement::step_up_or_down_applies() const
|
||||||
return value_as_number_applies();
|
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
|
// https://html.spec.whatwg.org/multipage/input.html#value-sanitization-algorithm
|
||||||
String value_sanitization_algorithm(String const&) const;
|
String value_sanitization_algorithm(String const&) const;
|
||||||
|
|
||||||
|
enum class ValueAttributeMode {
|
||||||
|
Value,
|
||||||
|
Default,
|
||||||
|
DefaultOn,
|
||||||
|
Filename,
|
||||||
|
};
|
||||||
|
ValueAttributeMode value_attribute_mode() const;
|
||||||
|
|
||||||
void update_placeholder_visibility();
|
void update_placeholder_visibility();
|
||||||
JS::GCPtr<DOM::Element> m_placeholder_element;
|
JS::GCPtr<DOM::Element> m_placeholder_element;
|
||||||
JS::GCPtr<DOM::Text> m_placeholder_text_node;
|
JS::GCPtr<DOM::Text> m_placeholder_text_node;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue