LibWeb: Implement popovertarget buttons

This commit is contained in:
Gingeh 2025-01-14 15:15:38 +11:00 committed by Andrew Kaster
commit 108f3a9aac
Notes: github-actions[bot] 2025-01-30 22:49:42 +00:00
14 changed files with 188 additions and 14 deletions

View file

@ -6,6 +6,7 @@
#include <LibWeb/Bindings/HTMLButtonElementPrototype.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/HTMLButtonElement.h>
#include <LibWeb/HTML/HTMLFormElement.h>
@ -115,7 +116,9 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event)
}
}
// 4. FIXME: Run the popover target attribute activation behavior given element.
// 4. Run the popover target attribute activation behavior given element and event's target.
if (event.target() && event.target()->is_dom_node())
PopoverInvokerElement::popover_target_activation_behaviour(*this, as<DOM::Node>(*event.target()));
}
bool HTMLButtonElement::is_focusable() const

View file

@ -131,6 +131,7 @@ public:
WebIDL::ExceptionOr<void> hide_popover_for_bindings();
WebIDL::ExceptionOr<bool> toggle_popover(TogglePopoverOptionsOrForceBoolean const&);
WebIDL::ExceptionOr<bool> check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document>);
WebIDL::ExceptionOr<void> show_popover(ThrowExceptions throw_exceptions, GC::Ptr<HTMLElement> invoker);
WebIDL::ExceptionOr<void> hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions);
@ -160,8 +161,6 @@ private:
GC::Ptr<DOM::NodeList> m_labels;
WebIDL::ExceptionOr<bool> check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document>);
void queue_a_popover_toggle_event_task(String old_state, String new_state);
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals

View file

@ -1261,8 +1261,10 @@ void HTMLInputElement::did_lose_focus()
commit_pending_changes();
}
void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const&)
void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const& namespace_)
{
PopoverInvokerElement::associated_attribute_changed(name, value, namespace_);
if (name == HTML::AttributeNames::checked) {
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-input-checked-dirty-2
// When the checked content attribute is added, if the control does not have dirty checkedness, the user agent must set the checkedness of the element to true;
@ -2538,6 +2540,7 @@ bool HTMLInputElement::has_activation_behavior() const
return true;
}
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
void HTMLInputElement::activation_behavior(DOM::Event const& event)
{
// The activation behavior for input elements are these steps:
@ -2546,6 +2549,10 @@ void HTMLInputElement::activation_behavior(DOM::Event const& event)
// 2. Run this element's input activation behavior, if any, and do nothing otherwise.
run_input_activation_behavior(event).release_value_but_fixme_should_propagate_errors();
// 3. Run the popover target attribute activation behavior given element and event's target.
if (event.target() && event.target()->is_dom_node())
PopoverInvokerElement::popover_target_activation_behaviour(*this, as<DOM::Node>(*event.target()));
}
bool HTMLInputElement::has_input_activation_behavior() const

View file

@ -16,6 +16,7 @@
#include <LibWeb/HTML/FileFilter.h>
#include <LibWeb/HTML/FormAssociatedElement.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/HTML/PopoverInvokerElement.h>
#include <LibWeb/Layout/ImageProvider.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/Types.h>
@ -50,7 +51,8 @@ namespace Web::HTML {
class HTMLInputElement final
: public HTMLElement
, public FormAssociatedTextControlElement
, public Layout::ImageProvider {
, public Layout::ImageProvider
, public PopoverInvokerElement {
WEB_PLATFORM_OBJECT(HTMLInputElement, HTMLElement);
GC_DECLARE_ALLOCATOR(HTMLInputElement);
FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLInputElement)

View file

@ -1,5 +1,6 @@
#import <HTML/HTMLElement.idl>
#import <HTML/HTMLFormElement.idl>
#import <HTML/PopoverInvokerElement.idl>
#import <HTML/ValidityState.idl>
#import <FileAPI/FileList.idl>
@ -73,4 +74,4 @@ interface HTMLInputElement : HTMLElement {
[CEReactions, Reflect] attribute DOMString align;
[CEReactions, Reflect=usemap] attribute DOMString useMap;
};
// FIXME: HTMLInputElement includes PopoverInvokerElement;
HTMLInputElement includes PopoverInvokerElement;

View file

@ -4,8 +4,12 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOM/Node.h>
#include <LibWeb/HTML/AttributeNames.h>
#include <LibWeb/HTML/FormAssociatedElement.h>
#include <LibWeb/HTML/HTMLElement.h>
#include <LibWeb/HTML/PopoverInvokerElement.h>
namespace Web::HTML {
@ -29,4 +33,92 @@ void PopoverInvokerElement::visit_edges(JS::Cell::Visitor& visitor)
visitor.visit(m_popover_target_element);
}
// https://html.spec.whatwg.org/multipage/popover.html#popover-target-attribute-activation-behavior
void PopoverInvokerElement::popover_target_activation_behaviour(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> event_target)
{
// To run the popover target attribute activation behavior given a Node node and a Node eventTarget:
// 1. Let popover be node's popover target element.
auto popover = PopoverInvokerElement::get_the_popover_target_element(node);
// 2. If popover is null, then return.
if (!popover)
return;
// 3. If eventTarget is a shadow-including inclusive descendant of popover and popover is a shadow-including descendant of node, then return.
if (event_target->is_shadow_including_inclusive_descendant_of(*popover)
&& popover->is_shadow_including_descendant_of(node))
return;
// 4. If node's popovertargetaction attribute is in the show state and popover's popover visibility state is showing, then return.
if (as<DOM::Element>(*node).get_attribute_value(HTML::AttributeNames::popovertargetaction).equals_ignoring_ascii_case("show"sv)
&& popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Showing)
return;
// 5. If node's popovertargetaction attribute is in the hide state and popover's popover visibility state is hidden, then return.
if (as<DOM::Element>(*node).get_attribute_value(HTML::AttributeNames::popovertargetaction).equals_ignoring_ascii_case("hide"sv)
&& popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Hidden)
return;
// 6. If popover's popover visibility state is showing, then run the hide popover algorithm given popover, true, true, and false.
if (popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Showing) {
MUST(popover->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No));
}
// 7. Otherwise, if popover's popover visibility state is hidden and the result of running check popover validity given popover, false, false, and null is true, then run show popover given popover, false, and node.
else if (popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Hidden
&& MUST(popover->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr))) {
MUST(popover->show_popover(ThrowExceptions::No, as<HTMLElement>(*node)));
}
}
// https://html.spec.whatwg.org/multipage/popover.html#popover-target-element
GC::Ptr<HTMLElement> PopoverInvokerElement::get_the_popover_target_element(GC::Ref<DOM::Node> node)
{
// To get the popover target element given a Node node, perform the following steps. They return an HTML element or null.
auto const* form_associated_element = dynamic_cast<FormAssociatedElement const*>(node.ptr());
VERIFY(form_associated_element);
// 1. If node is not a button, then return null.
if (!form_associated_element->is_button())
return {};
// 2. If node is disabled, then return null.
if (!form_associated_element->enabled())
return {};
// 3. If node has a form owner and node is a submit button, then return null.
if (form_associated_element->form() != nullptr && form_associated_element->is_submit_button())
return {};
// 4. Let popoverElement be the result of running node's get the popovertarget-associated element.
auto const* popover_invoker_element = dynamic_cast<PopoverInvokerElement const*>(node.ptr());
VERIFY(popover_invoker_element);
GC::Ptr<HTMLElement> popover_element = as<HTMLElement>(popover_invoker_element->m_popover_target_element.ptr());
if (!popover_element) {
auto target_id = as<HTMLElement>(*node).attribute("popovertarget"_fly_string);
if (target_id.has_value()) {
node->root().for_each_in_inclusive_subtree_of_type<HTMLElement>([&](auto& candidate) {
if (candidate.attribute(HTML::AttributeNames::id) == target_id.value()) {
popover_element = &candidate;
return TraversalDecision::Break;
}
return TraversalDecision::Continue;
});
}
}
// 5. If popoverElement is null, then return null.
if (!popover_element)
return {};
// 6. If popoverElement's popover attribute is in the no popover state, then return null.
if (!popover_element->popover().has_value())
return {};
// 7. Return popoverElement.
return popover_element;
}
}

View file

@ -21,12 +21,16 @@ public:
void set_popover_target_element(GC::Ptr<DOM::Element> value) { m_popover_target_element = value; }
static void popover_target_activation_behaviour(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> event_target);
protected:
void visit_edges(JS::Cell::Visitor&);
void associated_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const& namespace_);
private:
GC::Ptr<DOM::Element> m_popover_target_element;
static GC::Ptr<HTMLElement> get_the_popover_target_element(GC::Ref<DOM::Node> node);
};
}