diff --git a/Libraries/LibWeb/HTML/HTMLElement.cpp b/Libraries/LibWeb/HTML/HTMLElement.cpp index 9428792102f..727cf225720 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLElement.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1016,9 +1017,21 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except m_popover_showing_or_hiding = false; }; - // FIXME: 8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return. + // 8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return. + ToggleEventInit event_init {}; + event_init.old_state = "closed"_string; + event_init.new_state = "open"_string; + event_init.cancelable = true; + if (!dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init)))) { + cleanup_showing_flag->function()(); + return {}; + } - // FIXME: 9. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return. + // 9. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return. + if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr))) { + cleanup_showing_flag->function()(); + return {}; + } // 10. Let shouldRestoreFocus be false. bool should_restore_focus = false; @@ -1056,7 +1069,9 @@ WebIDL::ExceptionOr HTMLElement::show_popover(ThrowExceptions throw_except // FIXME: then set element's previously focused element to originallyFocusedElement. } - // FIXME: 20. Queue a popover toggle event task given element, "closed", and "open". + // 20. Queue a popover toggle event task given element, "closed", and "open". + queue_a_popover_toggle_event_task("closed"_string, "open"_string); + // 21. Run cleanupShowingFlag. cleanup_showing_flag(); @@ -1112,9 +1127,19 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // 10. If fireEvents is true: if (fire_events == FireEvents::Yes) { - // FIXME: 10.1. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at element. + // 10.1. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at element. + ToggleEventInit event_init {}; + event_init.old_state = "open"_string; + event_init.new_state = "closed"_string; + dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init))); + // FIXME: 10.2. If autoPopoverListContainsElement is true and document's showing auto popover list's last item is not element, then run hide all popovers until given element, focusPreviousElement, and false. - // FIXME: 10.3. If the result of running check popover validity given element, true, throwExceptions, and null is false, then run cleanupSteps and return. + + // 10.3. If the result of running check popover validity given element, true, throwExceptions, and null is false, then run cleanupSteps and return. + if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr))) { + cleanup_steps->function()(); + return {}; + } // 10.4. Request an element to be removed from the top layer given element. document.request_an_element_to_be_remove_from_the_top_layer(*this); } else { @@ -1125,7 +1150,9 @@ WebIDL::ExceptionOr HTMLElement::hide_popover(FocusPreviousElement, FireEv // 12. Set element's popover visibility state to hidden. m_popover_visibility_state = PopoverVisibilityState::Hidden; - // FIXME: 13. If fireEvents is true, then queue a popover toggle event task given element, "open", and "closed". + // 13. If fireEvents is true, then queue a popover toggle event task given element, "open", and "closed". + if (fire_events == FireEvents::Yes) + queue_a_popover_toggle_event_task("open"_string, "closed"_string); // FIXME: 14. Let previouslyFocusedElement be element's previously focused element. @@ -1175,6 +1202,44 @@ WebIDL::ExceptionOr HTMLElement::toggle_popover(TogglePopoverOptionsOrForc return popover_visibility_state() == PopoverVisibilityState::Showing; } +// https://html.spec.whatwg.org/multipage/popover.html#queue-a-popover-toggle-event-task +void HTMLElement::queue_a_popover_toggle_event_task(String old_state, String new_state) +{ + // 1. If element's popover toggle task tracker is not null, then: + if (m_popover_toggle_task_tracker.has_value()) { + // 1. Set oldState to element's popover toggle task tracker's old state. + old_state = move(m_popover_toggle_task_tracker->old_state); + + // 2. Remove element's popover toggle task tracker's task from its task queue. + HTML::main_thread_event_loop().task_queue().remove_tasks_matching([&](auto const& task) { + return task.id() == m_popover_toggle_task_tracker->task_id; + }); + + // 3. Set element's popover toggle task tracker to null. + m_popover_toggle_task_tracker->task_id = {}; + } + + // 2. Queue an element task given the DOM manipulation task source and element to run the following steps: + auto task_id = queue_an_element_task(HTML::Task::Source::DOMManipulation, [this, old_state, new_state = move(new_state)]() mutable { + // 1. Fire an event named toggle at element, using ToggleEvent, with the oldState attribute initialized to + // oldState and the newState attribute initialized to newState. + ToggleEventInit event_init {}; + event_init.old_state = move(old_state); + event_init.new_state = move(new_state); + + dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::toggle, move(event_init))); + + // 2. Set element's popover toggle task tracker to null. + m_popover_toggle_task_tracker = {}; + }); + + // 3. Set element's popover toggle task tracker to a struct with task set to the just-queued task and old state set to oldState. + m_popover_toggle_task_tracker = ToggleTaskTracker { + .task_id = task_id, + .old_state = move(old_state), + }; +} + void HTMLElement::did_receive_focus() { if (!first_is_one_of(m_content_editable_state, ContentEditableState::True, ContentEditableState::PlaintextOnly)) diff --git a/Libraries/LibWeb/HTML/HTMLElement.h b/Libraries/LibWeb/HTML/HTMLElement.h index ac52ac3e53c..c44bef4327e 100644 --- a/Libraries/LibWeb/HTML/HTMLElement.h +++ b/Libraries/LibWeb/HTML/HTMLElement.h @@ -6,10 +6,12 @@ #pragma once +#include #include #include #include #include +#include #include namespace Web::HTML { @@ -127,8 +129,6 @@ public: WebIDL::ExceptionOr show_popover(ThrowExceptions throw_exceptions, GC::Ptr invoker); WebIDL::ExceptionOr hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions); - WebIDL::ExceptionOr check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr); - protected: HTMLElement(DOM::Document&, DOM::QualifiedName); @@ -155,6 +155,10 @@ private: GC::Ptr m_labels; + WebIDL::ExceptionOr check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr); + + void queue_a_popover_toggle_event_task(String old_state, String new_state); + // https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals GC::Ptr m_attached_internals; @@ -174,6 +178,9 @@ private: // https://html.spec.whatwg.org/multipage/popover.html#popover-showing-or-hiding bool m_popover_showing_or_hiding { false }; + + // https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute:toggle-task-tracker + Optional m_popover_toggle_task_tracker; }; } diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt new file mode 100644 index 00000000000..95da3bfe380 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.txt @@ -0,0 +1,6 @@ +Harness status: OK + +Found 1 tests + +1 Pass +Pass Ensure the `beforetoggle` event can be used to populate content before the popover renders \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt index e5c8b8d1c57..163e59bbeb2 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/togglePopover.txt @@ -2,8 +2,7 @@ Harness status: OK Found 3 tests -2 Pass -1 Fail +3 Pass Pass togglePopover should toggle the popover and return true or false as specified. -Fail togglePopover's return value should reflect what the end state is, not just the force parameter. +Pass togglePopover's return value should reflect what the end state is, not just the force parameter. Pass togglePopover should throw an exception when there is no popover attribute. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt new file mode 100644 index 00000000000..c9bae1f73fc --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/html/semantics/popovers/toggleevent-interface.txt @@ -0,0 +1,42 @@ +Harness status: OK + +Found 37 tests + +37 Pass +Pass the event is an instance of ToggleEvent +Pass the event inherts from Event +Pass Missing type argument +Pass type argument is string +Pass type argument is null +Pass event type set to undefined +Pass oldState has default value of empty string +Pass oldState is readonly +Pass newState has default value of empty string +Pass newState is readonly +Pass ToggleEventInit argument is null +Pass ToggleEventInit argument is undefined +Pass ToggleEventInit argument is empty dictionary +Pass oldState set to 'sample' +Pass oldState set to undefined +Pass oldState set to null +Pass oldState set to false +Pass oldState set to true +Pass oldState set to a number +Pass oldState set to [] +Pass oldState set to [1, 2, 3] +Pass oldState set to an object +Pass oldState set to an object with a valueOf function +Pass ToggleEventInit properties set value +Pass ToggleEventInit properties set value 2 +Pass ToggleEventInit properties set value 3 +Pass ToggleEventInit properties set value 4 +Pass newState set to 'sample' +Pass newState set to undefined +Pass newState set to null +Pass newState set to false +Pass newState set to true +Pass newState set to a number +Pass newState set to [] +Pass newState set to [1, 2, 3] +Pass newState set to an object +Pass newState set to an object with a valueOf function \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html new file mode 100644 index 00000000000..cd60b6c03cc --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/popover-beforetoggle-opening-event.html @@ -0,0 +1,33 @@ + + +Popover beforetoggle event + + + + + + +
+ + diff --git a/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html new file mode 100644 index 00000000000..0b81a79193d --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/html/semantics/popovers/toggleevent-interface.html @@ -0,0 +1,208 @@ + + + + + + + + +