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;
|
||||
|
||||
virtual String value() const { return String {}; }
|
||||
virtual Optional<String> optional_value() const { VERIFY_NOT_REACHED(); }
|
||||
|
||||
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(); }
|
||||
|
|
|
@ -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
|
||||
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 {});
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
return true;
|
||||
|
|
|
@ -76,6 +76,7 @@ public:
|
|||
virtual Optional<ARIA::Role> default_role() const override { return ARIA::Role::button; }
|
||||
|
||||
virtual String value() const override;
|
||||
virtual Optional<String> optional_value() const override;
|
||||
|
||||
virtual bool has_activation_behavior() 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,
|
||||
// then close the dialog given element with invoker's optional value and invoker.
|
||||
if (command == "close" && has_attribute(AttributeNames::open)) {
|
||||
// FIXME: This assumes invoker is a button.
|
||||
auto optional_value = invoker.get_attribute(AttributeNames::value);
|
||||
auto const optional_value = as<FormAssociatedElement>(invoker).optional_value();
|
||||
close_the_dialog(optional_value, invoker);
|
||||
}
|
||||
|
||||
// 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.
|
||||
if (command == "request-close" && has_attribute(AttributeNames::open)) {
|
||||
// FIXME: This assumes invoker is a button.
|
||||
auto optional_value = invoker.get_attribute(AttributeNames::value);
|
||||
auto const optional_value = as<FormAssociatedElement>(invoker).optional_value();
|
||||
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.
|
||||
if (!result.has_value())
|
||||
result = submitter->get_attribute_value(AttributeNames::value);
|
||||
// 5. Otherwise, if submitter is a submit button, then set result to submitter's optional value.
|
||||
else if (auto* form_associated_element = as_if<FormAssociatedElement>(*submitter); form_associated_element && form_associated_element->is_submit_button()) {
|
||||
result = form_associated_element->optional_value();
|
||||
}
|
||||
|
||||
// 6. Close the dialog subject with result and null.
|
||||
subject->close_the_dialog(move(result), nullptr);
|
||||
|
|
|
@ -659,6 +659,18 @@ String HTMLInputElement::value() const
|
|||
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)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
|
|
|
@ -80,6 +80,7 @@ public:
|
|||
String default_value() const { return get_attribute_value(HTML::AttributeNames::value); }
|
||||
|
||||
virtual String value() const override;
|
||||
virtual Optional<String> optional_value() const override;
|
||||
WebIDL::ExceptionOr<void> set_value(String const&);
|
||||
|
||||
// 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