Skip to content

Commit e670cae

Browse files
GingehADKaster
authored andcommitted
LibWeb: Ignore DOM state when hiding removed popovers
Using whatwg/html#9457 (with some changes made to catch up with the current spec) to fix a spec bug and a crash when removing a visible popover.
1 parent 108f3a9 commit e670cae

File tree

5 files changed

+51
-32
lines changed

5 files changed

+51
-32
lines changed

Libraries/LibWeb/HTML/HTMLElement.cpp

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -997,10 +997,11 @@ void HTMLElement::adjust_computed_style(CSS::ComputedProperties& style)
997997
}
998998

999999
// https://html.spec.whatwg.org/multipage/popover.html#check-popover-validity
1000-
WebIDL::ExceptionOr<bool> HTMLElement::check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document> expected_document)
1000+
// https://whatpr.org/html/9457/popover.html#check-popover-validity
1001+
WebIDL::ExceptionOr<bool> HTMLElement::check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document> expected_document, IgnoreDomState ignore_dom_state)
10011002
{
1002-
// 1. If element's popover attribute is in the no popover state, then:
1003-
if (!popover().has_value()) {
1003+
// 1. If ignoreDomState is false and element's popover attribute is in the no popover state, then:
1004+
if (ignore_dom_state == IgnoreDomState::No && !popover().has_value()) {
10041005
// 1.1. If throwExceptions is true, then throw a "NotSupportedError" DOMException.
10051006
if (throw_exceptions == ThrowExceptions::Yes)
10061007
return WebIDL::NotSupportedError::create(realm(), "Element is not a popover"_string);
@@ -1017,15 +1018,19 @@ WebIDL::ExceptionOr<bool> HTMLElement::check_popover_validity(ExpectedToBeShowin
10171018
}
10181019

10191020
// 3. If any of the following are true:
1020-
// - element is not connected;
1021+
// - ignoreDomState is false and element is not connected;
10211022
// - element's node document is not fully active;
1022-
// - expectedDocument is not null and element's node document is not expectedDocument;
1023+
// - ignoreDomState is false and expectedDocument is not null and element's node document is not expectedDocument;
10231024
// - element is a dialog element and its is modal flage is set to true; or
10241025
// - FIXME: element's fullscreen flag is set,
10251026
// then:
10261027
// 3.1 If throwExceptions is true, then throw an "InvalidStateError" DOMException.
10271028
// 3.2 Return false.
1028-
if (!is_connected() || !document().is_fully_active() || (expected_document && &document() != expected_document) || (is<HTMLDialogElement>(*this) && as<HTMLDialogElement>(*this).is_modal())) {
1029+
1030+
if ((ignore_dom_state == IgnoreDomState::No && !is_connected())
1031+
|| !document().is_fully_active()
1032+
|| (ignore_dom_state == IgnoreDomState::No && expected_document && &document() != expected_document)
1033+
|| (is<HTMLDialogElement>(*this) && as<HTMLDialogElement>(*this).is_modal())) {
10291034
if (throw_exceptions == ThrowExceptions::Yes)
10301035
return WebIDL::InvalidStateError::create(realm(), "Element is not in a valid state to show a popover"_string);
10311036
return false;
@@ -1045,10 +1050,11 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover_for_bindings(ShowPopoverOpti
10451050
}
10461051

10471052
// https://html.spec.whatwg.org/multipage/popover.html#show-popover
1053+
// https://whatpr.org/html/9457/popover.html#show-popover
10481054
WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_exceptions, GC::Ptr<HTMLElement> invoker)
10491055
{
1050-
// 1. If the result of running check popover validity given element, false, throwExceptions, and null is false, then return.
1051-
if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr)))
1056+
// 1. If the result of running check popover validity given element, false, throwExceptions, null and false is false, then return.
1057+
if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr, IgnoreDomState::No)))
10521058
return {};
10531059

10541060
// 2. Let document be element's node document.
@@ -1085,8 +1091,8 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
10851091
return {};
10861092
}
10871093

1088-
// 10. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
1089-
if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr))) {
1094+
// 10. If the result of running check popover validity given element, false, throwExceptions, document, and false is false, then run cleanupShowingFlag and return.
1095+
if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, document, IgnoreDomState::No))) {
10901096
cleanup_showing_flag();
10911097
return {};
10921098
}
@@ -1123,7 +1129,7 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
11231129
// FIXME: 18.2. If originalType is not equal to the value of element's popover attribute, then:
11241130
// FIXME: 18.2.1. If throwExceptions is true, then throw a "InvalidStateError" DOMException.
11251131
// FIXME: 18.2.2. Return.
1126-
// FIXME: 18.3. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
1132+
// FIXME: 18.3. If the result of running check popover validity given element, false, throwExceptions, document, and false is false, then run cleanupShowingFlag and return.
11271133
// FIXME: 18.4. If the result of running topmost auto or hint popover on document is null, then set shouldRestoreFocus to true.
11281134
// FIXME: 18.5. If stackToAppendTo is "auto":
11291135
// FIXME: 18.5.1. Assert: document's showing auto popover list does not contain element.
@@ -1139,7 +1145,7 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
11391145
// - closeAction being to hide a popover given element, true, true, and false.
11401146
auto close_callback_function = JS::NativeFunction::create(
11411147
realm(), [this](JS::VM&) {
1142-
MUST(hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No));
1148+
MUST(hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No));
11431149

11441150
return JS::js_undefined();
11451151
},
@@ -1168,17 +1174,19 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
11681174
}
11691175

11701176
// https://html.spec.whatwg.org/multipage/popover.html#dom-hidepopover
1177+
// https://whatpr.org/html/9457/popover.html#dom-hidepopover
11711178
WebIDL::ExceptionOr<void> HTMLElement::hide_popover_for_bindings()
11721179
{
1173-
// The hidePopover() method steps are to run the hide popover algorithm given this, true, true, and true.
1174-
return hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::Yes);
1180+
// The hidePopover() method steps are to run the hide popover algorithm given this, true, true, true, and false.
1181+
return hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::Yes, IgnoreDomState::No);
11751182
}
11761183

11771184
// https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
1178-
WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEvents fire_events, ThrowExceptions throw_exceptions)
1185+
// https://whatpr.org/html/9457/popover.html#hide-popover-algorithm
1186+
WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEvents fire_events, ThrowExceptions throw_exceptions, IgnoreDomState ignore_dom_state)
11791187
{
1180-
// 1. If the result of running check popover validity given element, true, throwExceptions, and null is false, then return.
1181-
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr)))
1188+
// 1. If the result of running check popover validity given element, true, throwExceptions, null and ignoreDomState is false, then return.
1189+
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state)))
11821190
return {};
11831191

11841192
// 2. Let document be element's node document.
@@ -1211,7 +1219,7 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
12111219
// 7. If element's popover attribute is in the auto state FIXME: or the hint state, then:
12121220
if (popover().has_value() && popover().value() == "auto"sv) {
12131221
// FIXME: 7.1. Run hide all popovers until given element, focusPreviousElement, and fireEvents.
1214-
// FIXME: 7.2. If the result of running check popover validity given element, true, and throwExceptions is false, then run cleanupSteps and return.
1222+
// FIXME: 7.2. If the result of running check popover validity given element, true, throwExceptions, and ignoreDomState is false, then run cleanupSteps and return.
12151223
}
12161224
// FIXME: 8. Let autoPopoverListContainsElement be true if document's showing auto popover list's last item is element, otherwise false.
12171225

@@ -1228,8 +1236,8 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
12281236

12291237
// 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.
12301238

1231-
// 10.3. If the result of running check popover validity given element, true, throwExceptions, and null is false, then run cleanupSteps and return.
1232-
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr))) {
1239+
// 10.3. If the result of running check popover validity given element, true, throwExceptions, null, and ignoreDomState is false, then run cleanupSteps and return.
1240+
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state))) {
12331241
cleanup_steps();
12341242
return {};
12351243
}
@@ -1262,6 +1270,7 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
12621270
}
12631271

12641272
// https://html.spec.whatwg.org/multipage/popover.html#dom-togglepopover
1273+
// https://whatpr.org/html/9457/popover.html#dom-togglepopover
12651274
WebIDL::ExceptionOr<bool> HTMLElement::toggle_popover(TogglePopoverOptionsOrForceBoolean const& options)
12661275
{
12671276
// 1. Let force be null.
@@ -1280,18 +1289,18 @@ WebIDL::ExceptionOr<bool> HTMLElement::toggle_popover(TogglePopoverOptionsOrForc
12801289
invoker = options.source;
12811290
});
12821291

1283-
// 5. If this's popover visibility state is showing, and force is null or false, then run the hide popover algorithm given this, true, true, and true.
1292+
// 5. If this's popover visibility state is showing, and force is null or false, then run the hide popover algorithm given this, true, true, true, and false.
12841293
if (popover_visibility_state() == PopoverVisibilityState::Showing && (!force.has_value() || !force.value()))
1285-
TRY(hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::Yes));
1294+
TRY(hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::Yes, IgnoreDomState::No));
12861295
// 6. Otherwise, if force is not present or true, then run show popover given this true, and invoker.
12871296
else if (!force.has_value() || force.value())
12881297
TRY(show_popover(ThrowExceptions::Yes, invoker));
12891298
// 7. Otherwise:
12901299
else {
12911300
// 7.1 Let expectedToBeShowing be true if this's popover visibility state is showing; otherwise false.
12921301
ExpectedToBeShowing expected_to_be_showing = popover_visibility_state() == PopoverVisibilityState::Showing ? ExpectedToBeShowing::Yes : ExpectedToBeShowing::No;
1293-
// 7.2 Run check popover validity given expectedToBeShowing, true, and null.
1294-
TRY(check_popover_validity(expected_to_be_showing, ThrowExceptions::Yes, nullptr));
1302+
// 7.2 Run check popover validity given expectedToBeShowing, true, null, and false.
1303+
TRY(check_popover_validity(expected_to_be_showing, ThrowExceptions::Yes, nullptr, IgnoreDomState::No));
12951304
}
12961305
// 8. Return true if this's popover visibility state is showing; otherwise false.
12971306
return popover_visibility_state() == PopoverVisibilityState::Showing;
@@ -1368,9 +1377,10 @@ void HTMLElement::removed_from(Node* old_parent, Node& old_root)
13681377
{
13691378
Element::removed_from(old_parent, old_root);
13701379

1371-
// If removedNode's popover attribute is not in the no popover state, then run the hide popover algorithm given removedNode, false, false, and false.
1380+
// https://whatpr.org/html/9457/infrastructure.html#dom-trees:concept-node-remove-ext
1381+
// If removedNode's popover attribute is not in the no popover state, then run the hide popover algorithm given removedNode, false, false, false, and true.
13721382
if (popover().has_value())
1373-
MUST(hide_popover(FocusPreviousElement::No, FireEvents::No, ThrowExceptions::No));
1383+
MUST(hide_popover(FocusPreviousElement::No, FireEvents::No, ThrowExceptions::No, IgnoreDomState::Yes));
13741384
}
13751385

13761386
// https://html.spec.whatwg.org/multipage/interaction.html#dom-accesskeylabel

Libraries/LibWeb/HTML/HTMLElement.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ enum class ExpectedToBeShowing {
6060
No,
6161
};
6262

63+
enum class IgnoreDomState {
64+
Yes,
65+
No,
66+
};
67+
6368
class HTMLElement
6469
: public DOM::Element
6570
, public HTML::GlobalEventHandlers
@@ -131,9 +136,9 @@ class HTMLElement
131136
WebIDL::ExceptionOr<void> hide_popover_for_bindings();
132137
WebIDL::ExceptionOr<bool> toggle_popover(TogglePopoverOptionsOrForceBoolean const&);
133138

134-
WebIDL::ExceptionOr<bool> check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document>);
139+
WebIDL::ExceptionOr<bool> check_popover_validity(ExpectedToBeShowing expected_to_be_showing, ThrowExceptions throw_exceptions, GC::Ptr<DOM::Document>, IgnoreDomState ignore_dom_state);
135140
WebIDL::ExceptionOr<void> show_popover(ThrowExceptions throw_exceptions, GC::Ptr<HTMLElement> invoker);
136-
WebIDL::ExceptionOr<void> hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions);
141+
WebIDL::ExceptionOr<void> hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions, IgnoreDomState ignore_dom_state);
137142

138143
protected:
139144
HTMLElement(DOM::Document&, DOM::QualifiedName);

Libraries/LibWeb/HTML/PopoverInvokerElement.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ void PopoverInvokerElement::visit_edges(JS::Cell::Visitor& visitor)
3434
}
3535

3636
// https://html.spec.whatwg.org/multipage/popover.html#popover-target-attribute-activation-behavior
37+
// https://whatpr.org/html/9457/popover.html#popover-target-attribute-activation-behavior
3738
void PopoverInvokerElement::popover_target_activation_behaviour(GC::Ref<DOM::Node> node, GC::Ref<DOM::Node> event_target)
3839
{
3940
// To run the popover target attribute activation behavior given a Node node and a Node eventTarget:
@@ -60,14 +61,14 @@ void PopoverInvokerElement::popover_target_activation_behaviour(GC::Ref<DOM::Nod
6061
&& popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Hidden)
6162
return;
6263

63-
// 6. If popover's popover visibility state is showing, then run the hide popover algorithm given popover, true, true, and false.
64+
// 6. If popover's popover visibility state is showing, then run the hide popover algorithm given popover, true, true, false, and false.
6465
if (popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Showing) {
65-
MUST(popover->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No));
66+
MUST(popover->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No));
6667
}
6768

68-
// 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.
69+
// 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.
6970
else if (popover->popover_visibility_state() == HTMLElement::PopoverVisibilityState::Hidden
70-
&& MUST(popover->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr))) {
71+
&& MUST(popover->check_popover_validity(ExpectedToBeShowing::No, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
7172
MUST(popover->show_popover(ThrowExceptions::No, as<HTMLElement>(*node)));
7273
}
7374
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
Didn't crash when showing recently hidden popover
2+
Didn't crash when removing visible popover

Tests/LibWeb/Text/input/popover-crashes.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
pop.hidePopover();
99
pop.showPopover();
1010
println("Didn't crash when showing recently hidden popover");
11+
pop.remove();
12+
println("Didn't crash when removing visible popover");
1113
});
1214
</script>

0 commit comments

Comments
 (0)