diff --git a/Libraries/LibWeb/DOM/EventHandler.idl b/Libraries/LibWeb/DOM/EventHandler.idl index d737a9fa3a6..c83dbda0f87 100644 --- a/Libraries/LibWeb/DOM/EventHandler.idl +++ b/Libraries/LibWeb/DOM/EventHandler.idl @@ -26,6 +26,7 @@ interface mixin GlobalEventHandlers { attribute EventHandler onchange; attribute EventHandler onclick; attribute EventHandler onclose; + attribute EventHandler oncommand; attribute EventHandler oncontextlost; attribute EventHandler oncontextmenu; attribute EventHandler oncontextrestored; diff --git a/Libraries/LibWeb/HTML/AttributeNames.h b/Libraries/LibWeb/HTML/AttributeNames.h index a868a9e2173..3e27c7d122d 100644 --- a/Libraries/LibWeb/HTML/AttributeNames.h +++ b/Libraries/LibWeb/HTML/AttributeNames.h @@ -151,6 +151,7 @@ namespace AttributeNames { __ENUMERATE_HTML_ATTRIBUTE(onchange, "onchange") \ __ENUMERATE_HTML_ATTRIBUTE(onclick, "onclick") \ __ENUMERATE_HTML_ATTRIBUTE(onclose, "onclose") \ + __ENUMERATE_HTML_ATTRIBUTE(oncommand, "oncommand") \ __ENUMERATE_HTML_ATTRIBUTE(oncontextlost, "oncontextlost") \ __ENUMERATE_HTML_ATTRIBUTE(oncontextmenu, "oncontextmenu") \ __ENUMERATE_HTML_ATTRIBUTE(oncontextrestored, "oncontextrestored") \ diff --git a/Libraries/LibWeb/HTML/EventNames.h b/Libraries/LibWeb/HTML/EventNames.h index 3420db8c3c4..3515a1fd346 100644 --- a/Libraries/LibWeb/HTML/EventNames.h +++ b/Libraries/LibWeb/HTML/EventNames.h @@ -31,6 +31,7 @@ namespace Web::HTML::EventNames { __ENUMERATE_HTML_EVENT(change) \ __ENUMERATE_HTML_EVENT(click) \ __ENUMERATE_HTML_EVENT(close) \ + __ENUMERATE_HTML_EVENT(command) \ __ENUMERATE_HTML_EVENT(complete) \ __ENUMERATE_HTML_EVENT(connect) \ __ENUMERATE_HTML_EVENT(contextlost) \ diff --git a/Libraries/LibWeb/HTML/GlobalEventHandlers.h b/Libraries/LibWeb/HTML/GlobalEventHandlers.h index f06e2d115db..4962eeb019f 100644 --- a/Libraries/LibWeb/HTML/GlobalEventHandlers.h +++ b/Libraries/LibWeb/HTML/GlobalEventHandlers.h @@ -22,6 +22,7 @@ E(onchange, HTML::EventNames::change) \ E(onclick, UIEvents::EventNames::click) \ E(onclose, HTML::EventNames::close) \ + E(oncommand, HTML::EventNames::command) \ E(oncontextlost, HTML::EventNames::contextlost) \ E(oncontextmenu, HTML::EventNames::contextmenu) \ E(oncontextrestored, HTML::EventNames::contextrestored) \ diff --git a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp index 6a89afa9d87..96b5b6809f3 100644 --- a/Libraries/LibWeb/HTML/HTMLButtonElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLButtonElement.cpp @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include namespace Web::HTML { @@ -153,12 +155,111 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event) return; } - // FIXME: 4. Let target be the result of running element's get the commandfor associated element. - // FIXME: 5. If target is not null: - // ... + // 4. Let target be the result of running element's get the commandfor associated element. + // AD-HOC: Target needs to be an HTML Element in the following steps. + GC::Ptr target = as_if(m_command_for_element.ptr()); + if (!target) { + auto target_id = attribute(AttributeNames::commandfor); + if (target_id.has_value()) { + root().for_each_in_inclusive_subtree_of_type([&](auto& candidate) { + if (candidate.attribute(HTML::AttributeNames::id) == target_id.value()) { + target = &candidate; + return TraversalDecision::Break; + } + return TraversalDecision::Continue; + }); + } + } + + // 5. If target is not null: + if (target) { + // 1. Let command be element's command attribute. + auto command = this->command(); + + // 2. If command is in the Unknown state, then return. + if (command.is_empty()) { + return; + } + + // 3. Let isPopover be true if target's popover attribute is not in the no popover state; otherwise false. + auto is_popover = target->popover().has_value(); + + // 4. If isPopover is false and command is not in the Custom state: + auto command_is_in_custom_state = command.starts_with_bytes("--"sv); + if (!is_popover && !command.starts_with_bytes("--"sv)) { + // 1. Assert: target's namespace is the HTML namespace. + VERIFY(target->namespace_uri() == Namespace::HTML); + + // 2. If this standard does not define is valid invoker command steps for target's local name, then return. + // 3. Otherwise, if the result of running target's corresponding is valid invoker command steps given command is false, then return. + if (!target->is_valid_invoker_command(command)) + return; + } + + // 5. Let continue be the result of firing an event named command at target, using CommandEvent, with its command attribute initialized to command, its source attribute initialized to element, and its cancelable and composed attributes initialized to true. + // SPEC-NOTE: DOM standard issue #1328 tracks how to better standardize associated event data in a way which makes sense on Events. Currently an event attribute initialized to a value cannot also have a getter, and so an internal slot (or map of additional fields) is required to properly specify this. + CommandEventInit event_init {}; + event_init.command = command; + event_init.source = this; + event_init.cancelable = true; + event_init.composed = true; + + auto event = CommandEvent::create(realm(), HTML::EventNames::command, move(event_init)); + event->set_is_trusted(true); + auto continue_ = target->dispatch_event(event); + + // 6. If continue is false, then return. + if (!continue_) + return; + + // 7. If target is not connected, then return. + if (!target->is_connected()) + return; + + // 8. If command is in the Custom state, then return. + if (command_is_in_custom_state) + return; + + // AD-HOC: The parameters provided in the spec do not match the function signatures in the following steps. + // The inconsistent parameters were therefore selected ad hoc. + + // 9. If command is in the Hide Popover state: + if (command == "hide-popover") { + // 1. If the result of running check popover validity given target, true, false, and null is true, then run the hide popover algorithm given target, true, true, and false. + if (MUST(target->check_popover_validity(ExpectedToBeShowing::Yes, ThrowExceptions::No, nullptr, IgnoreDomState::No))) { + MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No)); + } + } + + // 10. Otherwise, if command is in the Toggle Popover state: + else if (command == "toggle-popover") { + // 1. If the result of running check popover validity given target, false, false, and null is true, then run the show popover algorithm given target, true, true, and false. + if (MUST(target->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr, IgnoreDomState::No))) { + MUST(target->show_popover(ThrowExceptions::No, this)); + } + + // 2. Otheriwse, if the result of running check popover validity given target, true, false, and null is true, then run the hide popover algorithm given target, true, true, and false. + else if (MUST(target->check_popover_validity(ExpectedToBeShowing::Yes, ThrowExceptions::No, nullptr, IgnoreDomState::No))) { + MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No)); + } + } + + // 11. Otherwise, if command is in the Show Popover state: + else if (command == "show-popover") { + // 1. If the result of running check popover validity given target, false, false, and null is true, then run the show popover algorithm given target, true, true, and false. + if (MUST(target->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr, IgnoreDomState::No))) { + MUST(target->show_popover(ThrowExceptions::No, this)); + } + } + + // 12. Otherwise, if this standard defines invoker command steps for target's local name, then run the corresponding invoker command steps given target, element and command. + else { + target->invoker_command_steps(*this, command); + } + } // 6. Otherwise, run the popover target attribute activation behavior given element and event's target. - if (event.target() && event.target()->is_dom_node()) + else if (event.target() && event.target()->is_dom_node()) PopoverInvokerElement::popover_target_activation_behaviour(*this, as(*event.target())); } diff --git a/Libraries/LibWeb/HTML/HTMLDialogElement.cpp b/Libraries/LibWeb/HTML/HTMLDialogElement.cpp index 01ac0867cf9..af4aa372b46 100644 --- a/Libraries/LibWeb/HTML/HTMLDialogElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLDialogElement.cpp @@ -443,4 +443,39 @@ void HTMLDialogElement::set_is_modal(bool is_modal) invalidate_style(DOM::StyleInvalidationReason::NodeRemove); } +// 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") + return true; + + // 2. Return false. + return false; +} + +// https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:invoker-command-steps +void HTMLDialogElement::invoker_command_steps(DOM::Element& invoker, String& command) +{ + // 1. If element is in the popover showing state, then return. + if (popover_visibility_state() == PopoverVisibilityState::Showing) { + return; + } + + // 2. If command is in the Close state and element has an open attribute: + if (command == "close" && has_attribute(AttributeNames::open)) { + // 1. Let value be invoker's value. + // FIXME: This assumes invoker is a button. + auto value = invoker.get_attribute(AttributeNames::value); + + // 2. Close the dialog element with value. + close_the_dialog(value); + } + + // 3. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element. + if (command == "show-modal" && !has_attribute(AttributeNames::open)) { + MUST(show_a_modal_dialog(*this)); + } +} + } diff --git a/Libraries/LibWeb/HTML/HTMLDialogElement.h b/Libraries/LibWeb/HTML/HTMLDialogElement.h index 02e471e980b..5a8a1b81013 100644 --- a/Libraries/LibWeb/HTML/HTMLDialogElement.h +++ b/Libraries/LibWeb/HTML/HTMLDialogElement.h @@ -39,6 +39,9 @@ public: bool is_modal() const { return m_is_modal; } void set_is_modal(bool); + bool is_valid_invoker_command(String&) override; + void invoker_command_steps(DOM::Element&, String&) override; + private: HTMLDialogElement(DOM::Document&, DOM::QualifiedName); diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index 636f629d17b..acf935f0461 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -151,6 +151,9 @@ public: bool is_inert() const { return m_inert; } + virtual bool is_valid_invoker_command(String&) { return false; } + virtual void invoker_command_steps(DOM::Element&, String&) { } + protected: HTMLElement(DOM::Document&, DOM::QualifiedName); diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch.txt new file mode 100644 index 00000000000..01e711453e2 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch.txt @@ -0,0 +1,45 @@ +Harness status: OK + +Found 39 tests + +38 Pass +1 Fail +Pass event dispatches on click with addEventListener +Pass event dispatches on click with oncommand property +Pass setting custom command property to --foo (must include dash) sets event command +Pass setting custom command attribute to --foo (must include dash) sets event command +Pass setting custom command property to --foo- (must include dash) sets event command +Pass setting custom command attribute to --foo- (must include dash) sets event command +Pass setting custom command property to --cAsE-cArRiEs (must include dash) sets event command +Pass setting custom command attribute to --cAsE-cArRiEs (must include dash) sets event command +Pass setting custom command property to -- (must include dash) sets event command +Pass setting custom command attribute to -- (must include dash) sets event command +Pass setting custom command property to --a- (must include dash) sets event command +Pass setting custom command attribute to --a- (must include dash) sets event command +Pass setting custom command property to --a-b (must include dash) sets event command +Pass setting custom command attribute to --a-b (must include dash) sets event command +Pass setting custom command property to --- (must include dash) sets event command +Pass setting custom command attribute to --- (must include dash) sets event command +Pass setting custom command property to --show-picker (must include dash) sets event command +Pass setting custom command attribute to --show-picker (must include dash) sets event command +Pass setting custom command property to -foo (no dash) did not dispatch an event +Pass setting custom command attribute to -foo (no dash) did not dispatch an event +Pass setting custom command property to -foo- (no dash) did not dispatch an event +Pass setting custom command attribute to -foo- (no dash) did not dispatch an event +Pass setting custom command property to foo-bar (no dash) did not dispatch an event +Pass setting custom command attribute to foo-bar (no dash) did not dispatch an event +Pass setting custom command property to -foo bar (no dash) did not dispatch an event +Pass setting custom command attribute to -foo bar (no dash) did not dispatch an event +Pass setting custom command property to —-emdash (no dash) did not dispatch an event +Pass setting custom command attribute to —-emdash (no dash) did not dispatch an event +Pass setting custom command property to hidedocument (no dash) did not dispatch an event +Pass setting custom command attribute to hidedocument (no dash) did not dispatch an event +Pass event does not dispatch if click:preventDefault is called +Pass event does not dispatch on input[type=button] +Pass event does not dispatch if invoker is disabled +Pass event does NOT dispatch if button is form associated, with implicit type +Pass event does NOT dispatch if button is form associated, with explicit type=invalid +Pass event dispatches if button is form associated, with explicit type=button +Pass event does NOT dispatch if button is form associated, with explicit type=submit +Pass event does NOT dispatch if button is form associated, with explicit type=reset +Fail event dispatches if invokee is non-HTML Element \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-dialog-behavior.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-dialog-behavior.txt new file mode 100644 index 00000000000..1c9afd09d07 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-dialog-behavior.txt @@ -0,0 +1,56 @@ +Harness status: OK + +Found 50 tests + +48 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 +Pass invoking (with command property as show-modal) while changing command still opens as modal +Pass invoking (with command attribute as show-modal) closed dialog opens as modal +Pass invoking (with command attribute as show-modal) closed dialog with preventDefault is noop +Pass invoking (with command attribute as show-modal) while changing command still opens as modal +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 +Pass invoking (with command property as sHoW-mOdAl) while changing command still opens as modal +Pass invoking (with command attribute as sHoW-mOdAl) closed dialog opens as modal +Pass invoking (with command attribute as sHoW-mOdAl) closed dialog with preventDefault is noop +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 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 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 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 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 (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 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 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 \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.txt new file mode 100644 index 00000000000..a2df3fa252b --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.txt @@ -0,0 +1,32 @@ +Harness status: OK + +Found 27 tests + +27 Pass +Pass changing command attribute inside invokeevent doesn't impact the invocation +Pass invoking (as toggle-popover) closed popover opens +Pass invoking (as toggle-popover) closed popover with preventDefault does not open +Pass invoking (as show-popover) closed popover opens +Pass invoking (as show-popover) closed popover with preventDefault does not open +Pass invoking (as tOgGlE-pOpOvEr) closed popover opens +Pass invoking (as tOgGlE-pOpOvEr) closed popover with preventDefault does not open +Pass invoking (as sHoW-pOpOvEr) closed popover opens +Pass invoking (as sHoW-pOpOvEr) closed popover with preventDefault does not open +Pass invoking (as toggle-popover) open popover closes +Pass invoking (as toggle-popover) open popover with preventDefault does not close +Pass invoking (as toggle-popover) from within open popover closes +Pass invoking (as toggle-popover) from within open popover with preventDefault does not close +Pass invoking (as hide-popover) open popover closes +Pass invoking (as hide-popover) open popover with preventDefault does not close +Pass invoking (as hide-popover) from within open popover closes +Pass invoking (as hide-popover) from within open popover with preventDefault does not close +Pass invoking (as tOgGlE-pOpOvEr) open popover closes +Pass invoking (as tOgGlE-pOpOvEr) open popover with preventDefault does not close +Pass invoking (as tOgGlE-pOpOvEr) from within open popover closes +Pass invoking (as tOgGlE-pOpOvEr) from within open popover with preventDefault does not close +Pass invoking (as hIdE-pOpOvEr) open popover closes +Pass invoking (as hIdE-pOpOvEr) open popover with preventDefault does not close +Pass invoking (as hIdE-pOpOvEr) from within open popover closes +Pass invoking (as hIdE-pOpOvEr) from within open popover with preventDefault does not close +Pass invoking (as show-popover) open popover is noop +Pass invoking (as hide-popover) closed popover is noop \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch.html new file mode 100644 index 00000000000..2e64ed07af0 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/button-event-dispatch.html @@ -0,0 +1,226 @@ + + + + + + + + + +
+ + +
+ + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-dialog-behavior.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-dialog-behavior.html new file mode 100644 index 00000000000..d6cc93a9f9e --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-dialog-behavior.html @@ -0,0 +1,376 @@ + + + + + + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.html new file mode 100644 index 00000000000..7fcceea2c7f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/the-button-element/command-and-commandfor/on-popover-behavior.html @@ -0,0 +1,152 @@ + + + + + + + + + +
+ +
+ + +