mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-24 18:02:20 +00:00
LibWeb/HTML: Implement and use "optional value"
Corresponds to f3444c23ff
Also import a test.
This commit is contained in:
parent
22cc36eeaa
commit
af17f38bbf
Notes:
github-actions[bot]
2025-07-08 16:10:58 +00:00
Author: https://github.com/AtkinsSJ
Commit: af17f38bbf
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5353
Reviewed-by: https://github.com/tcl3 ✅
9 changed files with 228 additions and 7 deletions
|
@ -126,6 +126,7 @@ public:
|
||||||
bool suffering_from_a_custom_error() const;
|
bool suffering_from_a_custom_error() const;
|
||||||
|
|
||||||
virtual String value() const { return String {}; }
|
virtual String value() const { return String {}; }
|
||||||
|
virtual Optional<String> optional_value() const { VERIFY_NOT_REACHED(); }
|
||||||
|
|
||||||
virtual HTMLElement& form_associated_element_to_html_element() = 0;
|
virtual HTMLElement& form_associated_element_to_html_element() = 0;
|
||||||
HTMLElement const& form_associated_element_to_html_element() const { return const_cast<FormAssociatedElement&>(*this).form_associated_element_to_html_element(); }
|
HTMLElement const& form_associated_element_to_html_element() const { return const_cast<FormAssociatedElement&>(*this).form_associated_element_to_html_element(); }
|
||||||
|
|
|
@ -119,9 +119,17 @@ bool HTMLButtonElement::is_submit_button() const
|
||||||
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-value
|
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-value
|
||||||
String HTMLButtonElement::value() const
|
String HTMLButtonElement::value() const
|
||||||
{
|
{
|
||||||
|
// The element's value is the value of the element's value attribute, if there is one; otherwise the empty string.
|
||||||
return attribute(AttributeNames::value).value_or(String {});
|
return attribute(AttributeNames::value).value_or(String {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element:concept-fe-optional-value
|
||||||
|
Optional<String> HTMLButtonElement::optional_value() const
|
||||||
|
{
|
||||||
|
// The element's optional value is the value of the element's value attribute, if there is one; otherwise null.
|
||||||
|
return attribute(AttributeNames::value);
|
||||||
|
}
|
||||||
|
|
||||||
bool HTMLButtonElement::has_activation_behavior() const
|
bool HTMLButtonElement::has_activation_behavior() const
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -76,6 +76,7 @@ public:
|
||||||
virtual Optional<ARIA::Role> default_role() const override { return ARIA::Role::button; }
|
virtual Optional<ARIA::Role> default_role() const override { return ARIA::Role::button; }
|
||||||
|
|
||||||
virtual String value() const override;
|
virtual String value() const override;
|
||||||
|
virtual Optional<String> optional_value() const override;
|
||||||
|
|
||||||
virtual bool has_activation_behavior() const override;
|
virtual bool has_activation_behavior() const override;
|
||||||
virtual void activation_behavior(DOM::Event const&) override;
|
virtual void activation_behavior(DOM::Event const&) override;
|
||||||
|
|
|
@ -492,16 +492,14 @@ void HTMLDialogElement::invoker_command_steps(DOM::Element& invoker, String& com
|
||||||
// 2. If command is in the Close state and element has an open attribute,
|
// 2. If command is in the Close state and element has an open attribute,
|
||||||
// then close the dialog given element with invoker's optional value and invoker.
|
// then close the dialog given element with invoker's optional value and invoker.
|
||||||
if (command == "close" && has_attribute(AttributeNames::open)) {
|
if (command == "close" && has_attribute(AttributeNames::open)) {
|
||||||
// FIXME: This assumes invoker is a button.
|
auto const optional_value = as<FormAssociatedElement>(invoker).optional_value();
|
||||||
auto optional_value = invoker.get_attribute(AttributeNames::value);
|
|
||||||
close_the_dialog(optional_value, invoker);
|
close_the_dialog(optional_value, invoker);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. If command is in the Request Close state and element has an open attribute,
|
// 3. If command is in the Request Close state and element has an open attribute,
|
||||||
// then request to close the dialog element with invoker's optional value and invoker.
|
// then request to close the dialog element with invoker's optional value and invoker.
|
||||||
if (command == "request-close" && has_attribute(AttributeNames::open)) {
|
if (command == "request-close" && has_attribute(AttributeNames::open)) {
|
||||||
// FIXME: This assumes invoker is a button.
|
auto const optional_value = as<FormAssociatedElement>(invoker).optional_value();
|
||||||
auto optional_value = invoker.get_attribute(AttributeNames::value);
|
|
||||||
request_close_the_dialog(optional_value, invoker);
|
request_close_the_dialog(optional_value, invoker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -225,9 +225,10 @@ WebIDL::ExceptionOr<void> HTMLFormElement::submit_form(GC::Ref<HTMLElement> subm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Otherwise, if submitter has a value, then set result to that value.
|
// 5. Otherwise, if submitter is a submit button, then set result to submitter's optional value.
|
||||||
if (!result.has_value())
|
else if (auto* form_associated_element = as_if<FormAssociatedElement>(*submitter); form_associated_element && form_associated_element->is_submit_button()) {
|
||||||
result = submitter->get_attribute_value(AttributeNames::value);
|
result = form_associated_element->optional_value();
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Close the dialog subject with result and null.
|
// 6. Close the dialog subject with result and null.
|
||||||
subject->close_the_dialog(move(result), nullptr);
|
subject->close_the_dialog(move(result), nullptr);
|
||||||
|
|
|
@ -659,6 +659,18 @@ String HTMLInputElement::value() const
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<String> HTMLInputElement::optional_value() const
|
||||||
|
{
|
||||||
|
switch (m_type) {
|
||||||
|
// https://html.spec.whatwg.org/multipage/input.html#submit-button-state-(type=submit):concept-fe-optional-value
|
||||||
|
case TypeAttributeState::SubmitButton:
|
||||||
|
// The element's optional value is the value of the element's value attribute, if there is one; otherwise null.
|
||||||
|
return get_attribute(AttributeNames::value);
|
||||||
|
default:
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
|
|
|
@ -80,6 +80,7 @@ public:
|
||||||
String default_value() const { return get_attribute_value(HTML::AttributeNames::value); }
|
String default_value() const { return get_attribute_value(HTML::AttributeNames::value); }
|
||||||
|
|
||||||
virtual String value() const override;
|
virtual String value() const override;
|
||||||
|
virtual Optional<String> optional_value() const override;
|
||||||
WebIDL::ExceptionOr<void> set_value(String const&);
|
WebIDL::ExceptionOr<void> set_value(String const&);
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-textarea/input-relevant-value
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
Harness status: Error
|
||||||
|
|
||||||
|
Found 8 tests
|
||||||
|
|
||||||
|
6 Pass
|
||||||
|
2 Fail
|
||||||
|
Pass click the form submission button should close the dialog
|
||||||
|
Pass form submission should return correct value
|
||||||
|
Pass returnValue doesn't update when there's no value attribute.
|
||||||
|
Pass returnValue does update when there's an empty value attribute.
|
||||||
|
Fail input image button should return the coordinates
|
||||||
|
Pass formmethod attribute should use dialog form submission
|
||||||
|
Pass closing the dialog while submitting should stop the submission
|
||||||
|
Fail calling form.submit() in click handler of submit button should start the submission synchronously
|
|
@ -0,0 +1,185 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<meta charset=urf-8>
|
||||||
|
<meta name=viewport content="width=device-width,initial-scale=1">
|
||||||
|
<title>Test dialog form submission</title>
|
||||||
|
<script src="../../../../resources/testharness.js"></script>
|
||||||
|
<script src="../../../../resources/testharnessreport.js"></script>
|
||||||
|
<script src="../../../../resources/testdriver.js"></script>
|
||||||
|
<script src="../../../../resources/testdriver-actions.js"></script>
|
||||||
|
<script src="../../../../resources/testdriver-vendor.js"></script>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<dialog id="favDialog">
|
||||||
|
<form id="dialogForm" method="dialog">
|
||||||
|
<button id="confirmBtn" value="default">Confirm</button>
|
||||||
|
<input id="confirmImgBtn" src="./resources/submit.jpg" width="41"
|
||||||
|
height="41" type="image" alt="Hello">
|
||||||
|
</form>
|
||||||
|
<form method="post">
|
||||||
|
<input id="confirmImgBtn2" src="./resources/submit.jpg" width="41"
|
||||||
|
formmethod="dialog" height="41" type="image" alt="Hello">
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
<script>
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
assert_equals(dialog.returnValue, "default", "Return the default value");
|
||||||
|
}, 'click the form submission button should close the dialog');
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.returnValue = "initial";
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
button.value = "sushi";
|
||||||
|
button.click();
|
||||||
|
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
assert_equals(dialog.returnValue, "sushi", "Return the updated value");
|
||||||
|
}, 'form submission should return correct value');
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.returnValue = "initial";
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
button.removeAttribute("value");
|
||||||
|
button.click();
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
assert_equals(dialog.returnValue, "initial", "returnValue should not be updated");
|
||||||
|
}, "returnValue doesn't update when there's no value attribute.");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.returnValue = "initial";
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
button.setAttribute("value", "");
|
||||||
|
button.click();
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
assert_equals(dialog.returnValue, "", "returnValue should be updated");
|
||||||
|
}, "returnValue does update when there's an empty value attribute.");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
const button = document.querySelector('input');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
let expectedReturnValue = "";
|
||||||
|
button.addEventListener('click', function(event) {
|
||||||
|
expectedReturnValue = event.offsetX + "," + event.offsetY;
|
||||||
|
});
|
||||||
|
await test_driver.click(button);
|
||||||
|
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
assert_not_equals(dialog.returnValue, "", "returnValue shouldn't be empty string");
|
||||||
|
assert_equals(dialog.returnValue, expectedReturnValue, "returnValue should be the offsets of the click");
|
||||||
|
}, "input image button should return the coordinates");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.showModal();
|
||||||
|
const button = document.getElementById('confirmImgBtn2');
|
||||||
|
await test_driver.click(button);
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
}, "formmethod attribute should use dialog form submission");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.returnValue = "";
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
button.value = "sushi";
|
||||||
|
|
||||||
|
const dialogForm = document.getElementById('dialogForm');
|
||||||
|
dialogForm.onsubmit = function() {
|
||||||
|
dialog.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
// If the submission request got processed, the returnValue should change
|
||||||
|
// to "sushi" because that's the value of the submitter
|
||||||
|
assert_equals(dialog.returnValue, "", "dialog's returnValue remains the same");
|
||||||
|
}, "closing the dialog while submitting should stop the submission");
|
||||||
|
|
||||||
|
promise_test(async (t) => {
|
||||||
|
const dialog = document.querySelector('dialog');
|
||||||
|
t.add_cleanup(() => {
|
||||||
|
dialog.close();
|
||||||
|
dialog.returnValue = "";
|
||||||
|
button.removeAttribute("value");
|
||||||
|
});
|
||||||
|
dialog.returnValue = undefined;
|
||||||
|
dialog.showModal();
|
||||||
|
|
||||||
|
let submitEvent = false;
|
||||||
|
const dialogForm = document.getElementById('dialogForm');
|
||||||
|
dialogForm.onsubmit = function() {
|
||||||
|
submitEvent = true;
|
||||||
|
assert_false(dialog.open, "dialog should be closed");
|
||||||
|
assert_equals(dialog.returnValue, "", "dialog's returnValue remains the same");
|
||||||
|
};
|
||||||
|
|
||||||
|
const button = document.querySelector('button');
|
||||||
|
button.value = "sushi";
|
||||||
|
button.onclick = function() {
|
||||||
|
dialogForm.submit();
|
||||||
|
assert_false(dialog.open, "dialog should be closed now");
|
||||||
|
// The returnValue should be "" because there is no submitter
|
||||||
|
assert_equals(dialog.returnValue, "", "returnValue shouldn be empty string");
|
||||||
|
};
|
||||||
|
|
||||||
|
button.click();
|
||||||
|
assert_true(submitEvent, "Should have submit event");
|
||||||
|
assert_false(dialog.open, "dialog should be closed");
|
||||||
|
assert_equals(dialog.returnValue, "", "dialog's returnValue remains the same");
|
||||||
|
}, "calling form.submit() in click handler of submit button should start the submission synchronously");
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
Loading…
Add table
Add a link
Reference in a new issue