Skip to content

Commit 5e9dabe

Browse files
committed
LibWeb: Implement popovertarget buttons
1 parent ed4481b commit 5e9dabe

14 files changed

Lines changed: 192 additions & 14 deletions

Libraries/LibWeb/DOM/Document.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5748,10 +5748,11 @@ void Document::add_an_element_to_the_top_layer(GC::Ref<Element> element)
57485748

57495749
// 3. Append el to doc’s top layer.
57505750
m_top_layer_elements.set(element);
5751-
57525751
element->set_in_top_layer(true);
57535752

57545753
// FIXME: 4. At the UA !important cascade origin, add a rule targeting el containing an overlay: auto declaration.
5754+
element->set_rendered_in_top_layer(true);
5755+
element->set_needs_style_update(true);
57555756
}
57565757

57575758
// https://drafts.csswg.org/css-position-4/#request-an-element-to-be-removed-from-the-top-layer
@@ -5764,9 +5765,12 @@ void Document::request_an_element_to_be_remove_from_the_top_layer(GC::Ref<Elemen
57645765
return;
57655766

57665767
// FIXME: 3. Remove the UA !important overlay: auto rule targeting el.
5768+
element->set_rendered_in_top_layer(false);
5769+
element->set_needs_style_update(true);
57675770

57685771
// 4. Append el to doc’s pending top layer removals.
57695772
m_top_layer_pending_removals.set(element);
5773+
element->set_in_top_layer(false);
57705774
}
57715775

57725776
// https://drafts.csswg.org/css-position-4/#remove-an-element-from-the-top-layer-immediately
@@ -5776,10 +5780,11 @@ void Document::remove_an_element_from_the_top_layer_immediately(GC::Ref<Element>
57765780

57775781
// 2. Remove el from doc’s top layer and pending top layer removals.
57785782
m_top_layer_elements.remove(element);
5779-
57805783
element->set_in_top_layer(false);
57815784

57825785
// FIXME: 3. Remove the UA !important overlay: auto rule targeting el, if it exists.
5786+
element->set_rendered_in_top_layer(false);
5787+
element->set_needs_style_update(true);
57835788
}
57845789

57855790
// https://drafts.csswg.org/css-position-4/#process-top-layer-removals
@@ -5788,11 +5793,10 @@ void Document::process_top_layer_removals()
57885793
// 1. For each element el in doc’s pending top layer removals: if el’s computed value of overlay is none, or el is
57895794
// not rendered, remove el from doc’s top layer and pending top layer removals.
57905795
for (auto& element : m_top_layer_pending_removals) {
5791-
// FIXME: Check overlay property
5792-
if (!element->paintable()) {
5796+
// FIXME: Implement overlay property
5797+
if (true || !element->paintable()) {
57935798
m_top_layer_elements.remove(element);
57945799
m_top_layer_pending_removals.remove(element);
5795-
element->set_in_top_layer(false);
57965800
}
57975801
}
57985802
}

Libraries/LibWeb/DOM/Element.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,16 @@ class Element
385385
return nullptr;
386386
}
387387

388+
// An element el is in the top layer if el is contained in its node document’s top layer
389+
// but not contained in its node document’s pending top layer removals.
388390
void set_in_top_layer(bool in_top_layer) { m_in_top_layer = in_top_layer; }
389391
bool in_top_layer() const { return m_in_top_layer; }
390392

393+
// An element el is rendered in the top layer if el is contained in its node document’s top layer,
394+
// FIXME: and el has overlay: auto.
395+
void set_rendered_in_top_layer(bool rendered_in_top_layer) { m_rendered_in_top_layer = rendered_in_top_layer; }
396+
bool rendered_in_top_layer() const { return m_rendered_in_top_layer; }
397+
391398
bool has_non_empty_counters_set() const { return m_counters_set; }
392399
Optional<CSS::CountersSet const&> counters_set();
393400
CSS::CountersSet& ensure_counters_set();
@@ -486,6 +493,7 @@ class Element
486493
Array<CSSPixelPoint, 3> m_scroll_offset;
487494

488495
bool m_in_top_layer { false };
496+
bool m_rendered_in_top_layer { false };
489497

490498
OwnPtr<CSS::CountersSet> m_counters_set;
491499

Libraries/LibWeb/HTML/HTMLButtonElement.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <LibWeb/Bindings/HTMLButtonElementPrototype.h>
88
#include <LibWeb/DOM/Document.h>
9+
#include <LibWeb/DOM/Event.h>
910
#include <LibWeb/HTML/HTMLButtonElement.h>
1011
#include <LibWeb/HTML/HTMLFormElement.h>
1112

@@ -115,7 +116,9 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event)
115116
}
116117
}
117118

118-
// 4. FIXME: Run the popover target attribute activation behavior given element.
119+
// 4. Run the popover target attribute activation behavior given element and event's target.
120+
if (event.target() && event.target()->is_dom_node())
121+
PopoverInvokerElement::popover_target_activation_behaviour(*this, verify_cast<DOM::Node>(*event.target()));
119122
}
120123

121124
bool HTMLButtonElement::is_focusable() const

Libraries/LibWeb/HTML/HTMLElement.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ class HTMLElement
133133
WebIDL::ExceptionOr<void> hide_popover_for_bindings();
134134
WebIDL::ExceptionOr<bool> toggle_popover(TogglePopoverOptionsOrForceBoolean const&);
135135

136+
WebIDL::ExceptionOr<bool> check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document>, IgnoreDomState ignore_dom_state);
136137
WebIDL::ExceptionOr<void> show_popover(ThrowExceptions throw_exceptions, GC::Ptr<HTMLElement> invoker);
137138
WebIDL::ExceptionOr<void> hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions, IgnoreDomState ignore_dom_state);
138139

@@ -162,8 +163,6 @@ class HTMLElement
162163

163164
GC::Ptr<DOM::NodeList> m_labels;
164165

165-
WebIDL::ExceptionOr<bool> check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document>, IgnoreDomState ignore_dom_state);
166-
167166
void queue_a_popover_toggle_event_task(String old_state, String new_state);
168167

169168
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals

Libraries/LibWeb/HTML/HTMLInputElement.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1261,8 +1261,10 @@ void HTMLInputElement::did_lose_focus()
12611261
commit_pending_changes();
12621262
}
12631263

1264-
void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const&)
1264+
void HTMLInputElement::form_associated_element_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const& namespace_)
12651265
{
1266+
PopoverInvokerElement::associated_attribute_changed(name, value, namespace_);
1267+
12661268
if (name == HTML::AttributeNames::checked) {
12671269
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:concept-input-checked-dirty-2
12681270
// 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;
@@ -2536,6 +2538,7 @@ bool HTMLInputElement::has_activation_behavior() const
25362538
return true;
25372539
}
25382540

2541+
// https://html.spec.whatwg.org/multipage/input.html#the-input-element:activation-behaviour
25392542
void HTMLInputElement::activation_behavior(DOM::Event const& event)
25402543
{
25412544
// The activation behavior for input elements are these steps:
@@ -2544,6 +2547,10 @@ void HTMLInputElement::activation_behavior(DOM::Event const& event)
25442547

25452548
// 2. Run this element's input activation behavior, if any, and do nothing otherwise.
25462549
run_input_activation_behavior(event).release_value_but_fixme_should_propagate_errors();
2550+
2551+
// 3. Run the popover target attribute activation behavior given element and event's target.
2552+
if (event.target() && event.target()->is_dom_node())
2553+
PopoverInvokerElement::popover_target_activation_behaviour(*this, verify_cast<DOM::Node>(*event.target()));
25472554
}
25482555

25492556
bool HTMLInputElement::has_input_activation_behavior() const

Libraries/LibWeb/HTML/HTMLInputElement.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <LibWeb/HTML/FileFilter.h>
1717
#include <LibWeb/HTML/FormAssociatedElement.h>
1818
#include <LibWeb/HTML/HTMLElement.h>
19+
#include <LibWeb/HTML/PopoverInvokerElement.h>
1920
#include <LibWeb/Layout/ImageProvider.h>
2021
#include <LibWeb/WebIDL/DOMException.h>
2122
#include <LibWeb/WebIDL/Types.h>
@@ -50,7 +51,8 @@ namespace Web::HTML {
5051
class HTMLInputElement final
5152
: public HTMLElement
5253
, public FormAssociatedTextControlElement
53-
, public Layout::ImageProvider {
54+
, public Layout::ImageProvider
55+
, public PopoverInvokerElement {
5456
WEB_PLATFORM_OBJECT(HTMLInputElement, HTMLElement);
5557
GC_DECLARE_ALLOCATOR(HTMLInputElement);
5658
FORM_ASSOCIATED_ELEMENT(HTMLElement, HTMLInputElement)

Libraries/LibWeb/HTML/HTMLInputElement.idl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import <HTML/HTMLElement.idl>
22
#import <HTML/HTMLFormElement.idl>
3+
#import <HTML/PopoverInvokerElement.idl>
34
#import <HTML/ValidityState.idl>
45
#import <FileAPI/FileList.idl>
56

@@ -73,4 +74,4 @@ interface HTMLInputElement : HTMLElement {
7374
[CEReactions, Reflect] attribute DOMString align;
7475
[CEReactions, Reflect=usemap] attribute DOMString useMap;
7576
};
76-
// FIXME: HTMLInputElement includes PopoverInvokerElement;
77+
HTMLInputElement includes PopoverInvokerElement;

Libraries/LibWeb/HTML/PopoverInvokerElement.cpp

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
66

7+
#include <LibWeb/DOM/Document.h>
78
#include <LibWeb/DOM/Element.h>
9+
#include <LibWeb/DOM/Node.h>
810
#include <LibWeb/HTML/AttributeNames.h>
11+
#include <LibWeb/HTML/FormAssociatedElement.h>
12+
#include <LibWeb/HTML/HTMLElement.h>
913
#include <LibWeb/HTML/PopoverInvokerElement.h>
1014

1115
namespace Web::HTML {
@@ -29,4 +33,93 @@ void PopoverInvokerElement::visit_edges(JS::Cell::Visitor& visitor)
2933
visitor.visit(m_popover_target_element);
3034
}
3135

36+
// https://html.spec.whatwg.org/multipage/popover.html#popover-target-attribute-activation-behavior
37+
void PopoverInvokerElement::popover_target_activation_behaviour(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> event_target)
38+
{
39+
// To run the popover target attribute activation behavior given a Node node and a Node eventTarget:
40+
41+
// 1. Let popover be node's popover target element.
42+
auto popover = PopoverInvokerElement::get_the_popover_target_element(node);
43+
44+
// 2. If popover is null, then return.
45+
if (!popover)
46+
return;
47+
48+
// 3. If eventTarget is a shadow-including inclusive descendant of popover and popover is a shadow-including descendant of node, then return.
49+
if (event_target->is_shadow_including_inclusive_descendant_of(*popover)
50+
&& popover->is_shadow_including_descendant_of(node))
51+
return;
52+
53+
// 4. If node's popovertargetaction attribute is in the show state and popover's popover visibility state is showing, then return.
54+
if (verify_cast<DOM::Element>(*node).get_attribute_value(HTML::AttributeNames::popovertargetaction).equals_ignoring_ascii_case("show"sv)
55+
&& popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Showing)
56+
return;
57+
58+
// 5. If node's popovertargetaction attribute is in the hide state and popover's popover visibility state is hidden, then return.
59+
if (verify_cast<DOM::Element>(*node).get_attribute_value(HTML::AttributeNames::popovertargetaction).equals_ignoring_ascii_case("hide"sv)
60+
&& popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Hidden)
61+
return;
62+
63+
// 6. If popover's popover visibility state is showing, then run the hide popover algorithm given popover, true, true, false, and false.
64+
if (popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Showing) {
65+
popover->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No).release_value_but_fixme_should_propagate_errors();
66+
}
67+
68+
// 7. Otherwise, if popover's popover visibility state is hidden and the result of running check popover validity given popover, false, false, null, and false is true, then run show popover given popover, false, and node.
69+
else if (popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Hidden
70+
&& popover->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr, IgnoreDomState::No).release_value_but_fixme_should_propagate_errors()) {
71+
popover->show_popover(ThrowExceptions::No, verify_cast<HTMLElement>(*node)).release_value_but_fixme_should_propagate_errors();
72+
}
73+
}
74+
75+
// https://html.spec.whatwg.org/multipage/popover.html#popover-target-element
76+
GC::Ptr<HTMLElement> PopoverInvokerElement::get_the_popover_target_element(GC::Ref<DOM::Node> node)
77+
{
78+
// To get the popover target element given a Node node, perform the following steps. They return an HTML element or null.
79+
80+
auto const* form_associated_element = dynamic_cast<FormAssociatedElement const*>(node.ptr());
81+
if (!form_associated_element)
82+
return {};
83+
84+
// 1. If node is not a button, then return null.
85+
if (!form_associated_element->is_button())
86+
return {};
87+
88+
// 2. If node is disabled, then return null.
89+
if (!form_associated_element->enabled())
90+
return {};
91+
92+
// 3. If node has a form owner and node is a submit button, then return null.
93+
if (form_associated_element->form() != nullptr && form_associated_element->is_submit_button())
94+
return {};
95+
96+
// 4. Let popoverElement be the result of running node's get the popovertarget-associated element.
97+
auto const* popover_invoker_element = dynamic_cast<PopoverInvokerElement const*>(node.ptr());
98+
GC::Ptr<HTMLElement> popover_element = verify_cast<HTMLElement>(popover_invoker_element->m_popover_target_element.ptr());
99+
100+
if (!popover_element) {
101+
auto target_id = verify_cast<HTMLElement>(*node).attribute("popovertarget"_fly_string);
102+
if (target_id.has_value()) {
103+
node->root().for_each_in_inclusive_subtree_of_type<HTMLElement>([&](auto& candidate) {
104+
if (candidate.attribute(HTML::AttributeNames::id) == target_id.value()) {
105+
popover_element = &candidate;
106+
return TraversalDecision::Break;
107+
}
108+
return TraversalDecision::Continue;
109+
});
110+
}
111+
}
112+
113+
// 5. If popoverElement is null, then return null.
114+
if (!popover_element)
115+
return {};
116+
117+
// 6. If popoverElement's popover attribute is in the no popover state, then return null.
118+
if (!popover_element->popover().has_value())
119+
return {};
120+
121+
// 7. Return popoverElement.
122+
return popover_element;
123+
}
124+
32125
}

Libraries/LibWeb/HTML/PopoverInvokerElement.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ class PopoverInvokerElement {
2121

2222
void set_popover_target_element(GC::Ptr<DOM::Element> value) { m_popover_target_element = value; }
2323

24+
static void popover_target_activation_behaviour(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> event_target);
25+
2426
protected:
2527
void visit_edges(JS::Cell::Visitor&);
2628
void associated_attribute_changed(FlyString const& name, Optional<String> const& value, Optional<FlyString> const& namespace_);
2729

2830
private:
2931
GC::Ptr<DOM::Element> m_popover_target_element;
32+
33+
static GC::Ptr<HTMLElement> get_the_popover_target_element(GC::Ref<DOM::Node> node);
3034
};
3135

3236
}

Libraries/LibWeb/Layout/TreeBuilder.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ void TreeBuilder::update_layout_tree(DOM::Node& dom_node, TreeBuilder::Context&
315315

316316
if (dom_node.is_element()) {
317317
auto& element = static_cast<DOM::Element&>(dom_node);
318-
if (element.in_top_layer() && !context.layout_top_layer)
318+
if (element.rendered_in_top_layer() && !context.layout_top_layer)
319319
return;
320320
}
321321
if (dom_node.is_element())
@@ -448,8 +448,10 @@ void TreeBuilder::update_layout_tree(DOM::Node& dom_node, TreeBuilder::Context&
448448
// Elements in the top layer do not lay out normally based on their position in the document; instead they
449449
// generate boxes as if they were siblings of the root element.
450450
TemporaryChange<bool> layout_mask(context.layout_top_layer, true);
451-
for (auto const& top_layer_element : document.top_layer_elements())
452-
update_layout_tree(top_layer_element, context, should_create_layout_node ? MustCreateSubtree::Yes : MustCreateSubtree::No);
451+
for (auto const& top_layer_element : document.top_layer_elements()) {
452+
if (top_layer_element->rendered_in_top_layer())
453+
update_layout_tree(top_layer_element, context, should_create_layout_node ? MustCreateSubtree::Yes : MustCreateSubtree::No);
454+
}
453455
}
454456
pop_parent();
455457
}

0 commit comments

Comments
 (0)