LibWeb: Implement popover's close watcher

Auto popovers now correctly establish a close watcher when shown.

This means popovers now correctly close with an escape key press.

Also correctly hide open popovers when removed from the document.
This commit is contained in:
Luke Warlow 2024-12-06 13:05:29 +00:00 committed by Alexander Kalenik
parent 09a55e56ee
commit 0a02eb639d
Notes: github-actions[bot] 2024-12-17 03:56:49 +00:00
2 changed files with 36 additions and 4 deletions

View file

@ -5,6 +5,7 @@
*/
#include <AK/StringBuilder.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibWeb/ARIA/Roles.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/HTMLElementPrototype.h>
@ -17,6 +18,7 @@
#include <LibWeb/DOM/Position.h>
#include <LibWeb/DOM/ShadowRoot.h>
#include <LibWeb/HTML/BrowsingContext.h>
#include <LibWeb/HTML/CloseWatcher.h>
#include <LibWeb/HTML/CustomElements/CustomElementDefinition.h>
#include <LibWeb/HTML/ElementInternals.h>
#include <LibWeb/HTML/EventHandler.h>
@ -65,6 +67,7 @@ void HTMLElement::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_labels);
visitor.visit(m_attached_internals);
visitor.visit(m_popover_invoker);
visitor.visit(m_popover_close_watcher);
}
// https://html.spec.whatwg.org/multipage/dom.html#dom-dir
@ -1051,9 +1054,20 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
// FIXME: 11.5. If originalType is not equal to the value of element's popover attribute, then throw a "InvalidStateError" DOMException.
// FIXME: 11.6. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
// FIXME: 11.7. If the result of running topmost auto popover on document is null, then set shouldRestoreFocus to true.
// FIXME: 11.8. Set element's popover close watcher to the result of establishing a close watcher given element's relevant global object, with:
// 11.8. Set element's popover close watcher to the result of establishing a close watcher given element's relevant global object, with:
m_popover_close_watcher = CloseWatcher::establish(*document.window());
// - cancelAction being to return true.
// We simply don't add an event listener for the cancel action.
// - closeAction being to hide a popover given element, true, true, and false.
auto close_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
MUST(hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No));
return JS::js_undefined();
},
0, "", &realm());
auto close_callback = realm().heap().allocate<WebIDL::CallbackType>(*close_callback_function, realm());
m_popover_close_watcher->add_event_listener_without_options(HTML::EventNames::close, DOM::IDLEventListener::create(realm(), close_callback));
}
// FIXME: 12. Set element's previously focused element to null.
@ -1116,9 +1130,13 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
// 6.1. If nestedHide is false, then set element's popover showing or hiding to false.
if (nested_hide)
m_popover_showing_or_hiding = false;
// FIXME: 6.2. If element's popover close watcher is not null, then:
// FIXME: 6.2.1. Destroy element's popover close watcher.
// FIXME: 6.2.2. Set element's popover close watcher to null.
// 6.2. If element's popover close watcher is not null, then:
if (m_popover_close_watcher) {
// 6.2.1. Destroy element's popover close watcher.
m_popover_close_watcher->destroy();
// 6.2.2. Set element's popover close watcher to null.
m_popover_close_watcher = nullptr;
}
};
// 7. If element's popover attribute is in the auto state, then:
@ -1275,6 +1293,15 @@ void HTMLElement::did_lose_focus()
document().editing_host_manager()->set_active_contenteditable_element(nullptr);
}
void HTMLElement::removed_from(Node* old_parent)
{
Element::removed_from(old_parent);
// If removedNode's popover attribute is not in the no popover state, then run the hide popover algorithm given removedNode, false, false, and false.
if (popover().has_value())
MUST(hide_popover(FocusPreviousElement::No, FireEvents::No, ThrowExceptions::No));
}
// https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel
String HTMLElement::access_key_label() const
{

View file

@ -116,6 +116,8 @@ public:
WebIDL::ExceptionOr<void> set_popover(Optional<String> value);
Optional<String> popover() const;
virtual void removed_from(Node*) override;
enum class PopoverVisibilityState {
Hidden,
Showing,
@ -181,6 +183,9 @@ private:
// https://html.spec.whatwg.org/multipage/popover.html#the-popover-attribute:toggle-task-tracker
Optional<ToggleTaskTracker> m_popover_toggle_task_tracker;
// https://html.spec.whatwg.org/multipage/popover.html#popover-close-watcher
GC::Ptr<CloseWatcher> m_popover_close_watcher;
};
}