1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-07 21:17:07 +09:00

LibWeb: Implement the request-close command

See: https://github.com/whatwg/html/pull/11045
This commit is contained in:
Gingeh 2025-06-04 15:35:43 +10:00 committed by Tim Ledbetter
parent fc35229dab
commit 9ef9f8d38f
Notes: github-actions[bot] 2025-06-07 03:07:08 +00:00
5 changed files with 358 additions and 13 deletions

View file

@ -289,9 +289,10 @@ String HTMLButtonElement::command() const
// show-popover Show Popover Shows the targeted popover element.
// hide-popover Hide Popover Hides the targeted popover element.
// close Close Closes the targeted dialog element.
// request-close Request Close Requests to close the targeted dialog element.
// show-modal Show Modal Opens the targeted dialog element as modal.
// A custom command keyword Custom Only dispatches the command event on the targeted element.
Array valid_values { "toggle-popover"_string, "show-popover"_string, "hide-popover"_string, "close"_string, "show-modal"_string };
Array valid_values { "toggle-popover"_string, "show-popover"_string, "hide-popover"_string, "close"_string, "request-close"_string, "show-modal"_string };
// 2. If command is in the Custom state, then return command's value.
// A custom command keyword is a string that starts with "--".

View file

@ -285,11 +285,14 @@ void HTMLDialogElement::close(Optional<String> return_value)
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-requestclose
void HTMLDialogElement::request_close(Optional<String> return_value)
{
// AD-HOC: This method is an amalgamation of the requestClose() and "request to close" algorithms from the spec.
// FIXME: Once the "request-close" command is implemented, this will need to be split into two methods.
// For now this implementation is only used for the requestClose() method, which sets source to null.
auto source = nullptr;
// 1. If returnValue is not given, then set it to null.
// 2. Request to close the dialog this with returnValue and null.
request_close_the_dialog(move(return_value), nullptr);
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-request-close
void HTMLDialogElement::request_close_the_dialog(Optional<String> return_value, GC::Ptr<DOM::Element> source)
{
// 1. If this does not have an open attribute, then return.
if (!has_attribute(AttributeNames::open))
return;
@ -470,8 +473,8 @@ void HTMLDialogElement::set_is_modal(bool is_modal)
// https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:is-valid-invoker-command-steps
bool HTMLDialogElement::is_valid_invoker_command(String& command)
{
// 1. If command is in the Close state or in the Show Modal state, then return true.
if (command == "close" || command == "show-modal")
// 1. If command is in the Close state, the Request Close state, or the Show Modal state, then return true.
if (command == "close" || command == "request-close" || command == "show-modal")
return true;
// 2. Return false.
@ -494,7 +497,15 @@ void HTMLDialogElement::invoker_command_steps(DOM::Element& invoker, String& com
close_the_dialog(optional_value, invoker);
}
// 3. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element and 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);
request_close_the_dialog(optional_value, invoker);
}
// 4. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element and invoker.
if (command == "show-modal" && !has_attribute(AttributeNames::open)) {
MUST(show_a_modal_dialog(*this, invoker));
}

View file

@ -29,6 +29,7 @@ public:
static WebIDL::ExceptionOr<void> show_a_modal_dialog(HTMLDialogElement&, GC::Ptr<DOM::Element> source);
void close_the_dialog(Optional<String> result, GC::Ptr<DOM::Element> source);
void request_close_the_dialog(Optional<String> return_value, GC::Ptr<DOM::Element> source);
WebIDL::ExceptionOr<void> show();
WebIDL::ExceptionOr<void> show_modal();

View file

@ -1,8 +1,8 @@
Harness status: OK
Found 50 tests
Found 104 tests
48 Pass
102 Pass
2 Fail
Pass invoking (with command property as show-modal) closed dialog opens as modal
Pass invoking (with command property as show-modal) closed dialog with preventDefault is noop
@ -18,39 +18,93 @@ Pass invoking (with command attribute as sHoW-mOdAl) closed dialog with preventD
Pass invoking (with command attribute as sHoW-mOdAl) while changing command still opens as modal
Pass invoking to close (with command property as close) open dialog closes
Pass invoking to close (with command property as close) open dialog closes and sets returnValue
Pass invoking to close (with command property as close) open dialog closes and overrides returnValue
Pass invoking to close (with command property as close) open dialog closes and overrides returnValue when empty string
Pass invoking to close (with command property as close) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to close (with command property as close) open dialog with preventDefault is no-op
Pass invoking to close (with command property as close) open modal dialog with preventDefault is no-op
Pass invoking to close (with command property as close) open dialog while changing command still closes
Pass invoking to close (with command property as close) open modal dialog while changing command still closes
Pass invoking to close (with command attribute as close) open dialog closes
Pass invoking to close (with command attribute as close) open dialog closes and sets returnValue
Pass invoking to close (with command attribute as close) open dialog closes and overrides returnValue
Pass invoking to close (with command attribute as close) open dialog closes and overrides returnValue when empty string
Pass invoking to close (with command attribute as close) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to close (with command attribute as close) open dialog with preventDefault is no-op
Pass invoking to close (with command attribute as close) open modal dialog with preventDefault is no-op
Pass invoking to close (with command attribute as close) open dialog while changing command still closes
Pass invoking to close (with command attribute as close) open modal dialog while changing command still closes
Pass invoking to close (with command property as cLoSe) open dialog closes
Pass invoking to close (with command property as cLoSe) open dialog closes and sets returnValue
Pass invoking to close (with command property as cLoSe) open dialog closes and overrides returnValue
Pass invoking to close (with command property as cLoSe) open dialog closes and overrides returnValue when empty string
Pass invoking to close (with command property as cLoSe) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to close (with command property as cLoSe) open dialog with preventDefault is no-op
Pass invoking to close (with command property as cLoSe) open modal dialog with preventDefault is no-op
Pass invoking to close (with command property as cLoSe) open dialog while changing command still closes
Pass invoking to close (with command property as cLoSe) open modal dialog while changing command still closes
Pass invoking to close (with command attribute as cLoSe) open dialog closes
Pass invoking to close (with command attribute as cLoSe) open dialog closes and sets returnValue
Pass invoking to close (with command attribute as cLoSe) open dialog closes and overrides returnValue
Pass invoking to close (with command attribute as cLoSe) open dialog closes and overrides returnValue when empty string
Pass invoking to close (with command attribute as cLoSe) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to close (with command attribute as cLoSe) open dialog with preventDefault is no-op
Pass invoking to close (with command attribute as cLoSe) open modal dialog with preventDefault is no-op
Pass invoking to close (with command attribute as cLoSe) open dialog while changing command still closes
Pass invoking to close (with command attribute as cLoSe) open modal dialog while changing command still closes
Pass invoking to request-close (with command property as request-close) open dialog closes
Pass invoking to request-close with value (with command property as request-close) open dialog closes and sets returnValue
Pass invoking to request-close with value (with command property as request-close) open dialog closes and overrides returnValue
Pass invoking to request-close with value (with command property as request-close) open dialog closes and overrides returnValue when empty string
Pass invoking to request-close with value (with command property as request-close) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to request-close (with command property as request-close) open dialog with preventDefault is no-op
Pass invoking to request-close (with command property as request-close) open modal dialog with preventDefault is no-op
Pass invoking to request-close (with command property as request-close) open dialog while changing command still closes
Pass invoking to request-close (with command property as request-close) open modal dialog while changing command still closes
Pass invoking to request-close (with command attribute as request-close) open dialog closes
Pass invoking to request-close with value (with command attribute as request-close) open dialog closes and sets returnValue
Pass invoking to request-close with value (with command attribute as request-close) open dialog closes and overrides returnValue
Pass invoking to request-close with value (with command attribute as request-close) open dialog closes and overrides returnValue when empty string
Pass invoking to request-close with value (with command attribute as request-close) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to request-close (with command attribute as request-close) open dialog with preventDefault is no-op
Pass invoking to request-close (with command attribute as request-close) open modal dialog with preventDefault is no-op
Pass invoking to request-close (with command attribute as request-close) open dialog while changing command still closes
Pass invoking to request-close (with command attribute as request-close) open modal dialog while changing command still closes
Pass invoking to request-close (with command property as reQuEst-Close) open dialog closes
Pass invoking to request-close with value (with command property as reQuEst-Close) open dialog closes and sets returnValue
Pass invoking to request-close with value (with command property as reQuEst-Close) open dialog closes and overrides returnValue
Pass invoking to request-close with value (with command property as reQuEst-Close) open dialog closes and overrides returnValue when empty string
Pass invoking to request-close with value (with command property as reQuEst-Close) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to request-close (with command property as reQuEst-Close) open dialog with preventDefault is no-op
Pass invoking to request-close (with command property as reQuEst-Close) open modal dialog with preventDefault is no-op
Pass invoking to request-close (with command property as reQuEst-Close) open dialog while changing command still closes
Pass invoking to request-close (with command property as reQuEst-Close) open modal dialog while changing command still closes
Pass invoking to request-close (with command attribute as reQuEst-Close) open dialog closes
Pass invoking to request-close with value (with command attribute as reQuEst-Close) open dialog closes and sets returnValue
Pass invoking to request-close with value (with command attribute as reQuEst-Close) open dialog closes and overrides returnValue
Pass invoking to request-close with value (with command attribute as reQuEst-Close) open dialog closes and overrides returnValue when empty string
Pass invoking to request-close with value (with command attribute as reQuEst-Close) open dialog closes and doesn't override returnValue when missing value attribute
Pass invoking to request-close (with command attribute as reQuEst-Close) open dialog with preventDefault is no-op
Pass invoking to request-close (with command attribute as reQuEst-Close) open modal dialog with preventDefault is no-op
Pass invoking to request-close (with command attribute as reQuEst-Close) open dialog while changing command still closes
Pass invoking to request-close (with command attribute as reQuEst-Close) open modal dialog while changing command still closes
Pass invoking (as show-modal) open dialog is noop
Pass invoking (as show-modal) open modal, while changing command still a no-op
Pass invoking (as show-modal) closed popover dialog opens as modal
Pass invoking (as close) already closed dialog is noop
Pass invoking (as request-close) already closed dialog is noop
Pass invoking (as show-modal) dialog as open popover=manual is noop
Pass invoking (as show-modal) dialog as open popover=auto is noop
Pass invoking (as close) dialog as open popover=manual is noop
Pass invoking (as close) dialog as open popover=auto is noop
Pass invoking (as request-close) dialog as open popover=manual is noop
Pass invoking (as request-close) dialog as open popover=auto is noop
Pass invoking (as show-modal) dialog that is removed is noop
Fail invoking (as show-modal) dialog from a detached invoker
Pass invoking (as show-modal) detached dialog from a detached invoker
Fail invoking (as close) dialog that is removed is noop
Pass invoking (as close) dialog from a detached invoker
Pass invoking (as close) detached dialog from a detached invoker
Pass invoking (as close) detached dialog from a detached invoker
Pass invoking (as request-close) dialog that is removed is noop
Pass invoking (as request-close) dialog from a detached invoker
Pass invoking (as request-close) detached dialog from a detached invoker

View file

@ -135,6 +135,69 @@
`invoking to close (with command ${setType} as ${command}) open dialog closes and sets returnValue`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.returnValue = "test";
containedinvoker.setAttribute("value", "foo");
containedinvoker.click();
assert_equals(invokee.returnValue, "foo");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to close (with command ${setType} as ${command}) open dialog closes and overrides returnValue`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.returnValue = "test";
containedinvoker.setAttribute("value", "");
containedinvoker.click();
assert_equals(invokee.returnValue, "");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to close (with command ${setType} as ${command}) open dialog closes and overrides returnValue when empty string`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.returnValue = "test";
containedinvoker.removeAttribute("value");
containedinvoker.click();
assert_equals(invokee.returnValue, "test");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to close (with command ${setType} as ${command}) open dialog closes and doesn't override returnValue when missing value attribute`,
);
test(
function (t) {
t.add_cleanup(resetState);
@ -231,6 +294,208 @@
});
});
// request to close an already open dialog
["request-close", /* test case sensitivity */ "reQuEst-Close"].forEach((command) => {
["property", "attribute"].forEach((setType) => {
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
containedinvoker.click();
assert_equals(invokee.returnValue, "");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close (with command ${setType} as ${command}) open dialog closes`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
containedinvoker.setAttribute("value", "foo");
containedinvoker.click();
assert_equals(invokee.returnValue, "foo");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and sets returnValue`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.returnValue = "test";
containedinvoker.setAttribute("value", "foo");
containedinvoker.click();
assert_equals(invokee.returnValue, "foo");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and overrides returnValue`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.returnValue = "test";
containedinvoker.setAttribute("value", "");
containedinvoker.click();
assert_equals(invokee.returnValue, "");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and overrides returnValue when empty string`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.returnValue = "test";
containedinvoker.removeAttribute("value");
containedinvoker.click();
assert_equals(invokee.returnValue, "test");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close with value (with command ${setType} as ${command}) open dialog closes and doesn't override returnValue when missing value attribute`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (typeof command === "string") {
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
}
invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
containedinvoker.click();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close (with command ${setType} as ${command}) open dialog with preventDefault is no-op`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.addEventListener("command", (e) => e.preventDefault(), {
once: true,
});
containedinvoker.click();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close (with command ${setType} as ${command}) open modal dialog with preventDefault is no-op`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.show();
assert_true(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.addEventListener(
"command",
(e) => {
containedinvoker.setAttribute("command", "show");
},
{ once: true },
);
containedinvoker.click();
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close (with command ${setType} as ${command}) open dialog while changing command still closes`,
);
test(
function (t) {
t.add_cleanup(resetState);
invokee.showModal();
assert_true(invokee.open, "invokee.open");
assert_true(invokee.matches(":modal"), "invokee :modal");
if (setType === "property") {
containedinvoker.command = command;
} else {
containedinvoker.setAttribute("command", command);
}
invokee.addEventListener(
"command",
(e) => {
containedinvoker.setAttribute("command", "show");
},
{ once: true },
);
containedinvoker.click();
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
},
`invoking to request-close (with command ${setType} as ${command}) open modal dialog while changing command still closes`,
);
});
});
// show-modal explicit behaviours
promise_test(async function (t) {
@ -285,8 +550,21 @@
assert_false(invokee.matches(":modal"), "invokee :modal");
}, "invoking (as close) already closed dialog is noop");
// request-close explicit behaviours
promise_test(async function (t) {
t.add_cleanup(resetState);
invokerbutton.setAttribute("command", "request-close");
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
containedinvoker.click();
assert_false(invokee.open, "invokee.open");
assert_false(invokee.matches(":modal"), "invokee :modal");
}, "invoking (as request-close) already closed dialog is noop");
// Open Popovers using Dialog actions
["show-modal", "close"].forEach((command) => {
["show-modal", "close", "request-close"].forEach((command) => {
["manual", "auto"].forEach((popoverState) => {
test(
function (t) {
@ -317,7 +595,7 @@
});
// Elements being disconnected during invoke steps
["show-modal", "close"].forEach((command) => {
["show-modal", "close", "request-close"].forEach((command) => {
promise_test(
async function (t) {
t.add_cleanup(() => {