mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-07 16:49:54 +00:00
LibWeb: Hide unrelated popovers when showing popovers
Also hides decendant popovers when hiding. Also hides unrelated popovers when showing dialogs.
This commit is contained in:
parent
bc0729f5d2
commit
91e4fb248b
Notes:
github-actions[bot]
2025-02-16 19:41:05 +00:00
Author: https://github.com/Gingeh
Commit: 91e4fb248b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3537
Reviewed-by: https://github.com/tcl3 ✅
15 changed files with 607 additions and 56 deletions
|
@ -574,6 +574,8 @@ void Document::visit_edges(Cell::Visitor& visitor)
|
||||||
|
|
||||||
visitor.visit(m_top_layer_elements);
|
visitor.visit(m_top_layer_elements);
|
||||||
visitor.visit(m_top_layer_pending_removals);
|
visitor.visit(m_top_layer_pending_removals);
|
||||||
|
visitor.visit(m_showing_auto_popover_list);
|
||||||
|
visitor.visit(m_showing_hint_popover_list);
|
||||||
visitor.visit(m_console_client);
|
visitor.visit(m_console_client);
|
||||||
visitor.visit(m_editing_host_manager);
|
visitor.visit(m_editing_host_manager);
|
||||||
visitor.visit(m_local_storage_holder);
|
visitor.visit(m_local_storage_holder);
|
||||||
|
|
|
@ -712,6 +712,13 @@ public:
|
||||||
|
|
||||||
OrderedHashTable<GC::Ref<Element>> const& top_layer_elements() const { return m_top_layer_elements; }
|
OrderedHashTable<GC::Ref<Element>> const& top_layer_elements() const { return m_top_layer_elements; }
|
||||||
|
|
||||||
|
// AD-HOC: These lists are managed dynamically instead of being generated as needed.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/11007
|
||||||
|
Vector<GC::Ref<HTML::HTMLElement>>& showing_auto_popover_list() { return m_showing_auto_popover_list; }
|
||||||
|
Vector<GC::Ref<HTML::HTMLElement>>& showing_hint_popover_list() { return m_showing_hint_popover_list; }
|
||||||
|
Vector<GC::Ref<HTML::HTMLElement>> const& showing_auto_popover_list() const { return m_showing_auto_popover_list; }
|
||||||
|
Vector<GC::Ref<HTML::HTMLElement>> const& showing_hint_popover_list() const { return m_showing_hint_popover_list; }
|
||||||
|
|
||||||
size_t transition_generation() const { return m_transition_generation; }
|
size_t transition_generation() const { return m_transition_generation; }
|
||||||
|
|
||||||
// Does document represent an embedded svg img
|
// Does document represent an embedded svg img
|
||||||
|
@ -1101,6 +1108,9 @@ private:
|
||||||
OrderedHashTable<GC::Ref<Element>> m_top_layer_elements;
|
OrderedHashTable<GC::Ref<Element>> m_top_layer_elements;
|
||||||
OrderedHashTable<GC::Ref<Element>> m_top_layer_pending_removals;
|
OrderedHashTable<GC::Ref<Element>> m_top_layer_pending_removals;
|
||||||
|
|
||||||
|
Vector<GC::Ref<HTML::HTMLElement>> m_showing_auto_popover_list;
|
||||||
|
Vector<GC::Ref<HTML::HTMLElement>> m_showing_hint_popover_list;
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots
|
// https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots
|
||||||
bool m_allow_declarative_shadow_roots { false };
|
bool m_allow_declarative_shadow_roots { false };
|
||||||
|
|
||||||
|
|
|
@ -135,11 +135,23 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
|
||||||
// 9. Set the dialog close watcher with this.
|
// 9. Set the dialog close watcher with this.
|
||||||
set_close_watcher();
|
set_close_watcher();
|
||||||
// FIXME: 10. Set this's previously focused element to the focused element.
|
// FIXME: 10. Set this's previously focused element to the focused element.
|
||||||
// FIXME: 11. Let document be this's node document.
|
|
||||||
// FIXME: 12. Let hideUntil be the result of running topmost popover ancestor given this, document's showing hint popover list, null, and false.
|
// 11. Let document be this's node document.
|
||||||
// FIXME: 13. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given this, document's showing auto popover list, null, and false.
|
auto document = m_document;
|
||||||
// FIXME: 14. If hideUntil is null, then set hideUntil to document.
|
|
||||||
// FIXME: 15. Run hide all popovers until given hideUntil, false, and true.
|
// 12. Let hideUntil be the result of running topmost popover ancestor given this, document's showing hint popover list, null, and false.
|
||||||
|
Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> hide_until = topmost_popover_ancestor(this, document->showing_hint_popover_list(), nullptr, IsPopover::No);
|
||||||
|
|
||||||
|
// 13. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given this, document's showing auto popover list, null, and false.
|
||||||
|
if (!hide_until.get<GC::Ptr<HTMLElement>>())
|
||||||
|
hide_until = topmost_popover_ancestor(this, document->showing_auto_popover_list(), nullptr, IsPopover::No);
|
||||||
|
|
||||||
|
// 14. If hideUntil is null, then set hideUntil to document.
|
||||||
|
if (!hide_until.get<GC::Ptr<HTMLElement>>())
|
||||||
|
hide_until = document;
|
||||||
|
|
||||||
|
// 15. Run hide all popovers until given hideUntil, false, and true.
|
||||||
|
hide_all_popovers_until(hide_until, FocusPreviousElement::No, FireEvents::Yes);
|
||||||
|
|
||||||
// 16. Run the dialog focusing steps given this.
|
// 16. Run the dialog focusing steps given this.
|
||||||
run_dialog_focusing_steps();
|
run_dialog_focusing_steps();
|
||||||
|
@ -224,11 +236,23 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogEleme
|
||||||
subject.set_close_watcher();
|
subject.set_close_watcher();
|
||||||
|
|
||||||
// FIXME: 18. Set subject's previously focused element to the focused element.
|
// FIXME: 18. Set subject's previously focused element to the focused element.
|
||||||
// FIXME: 19. Let document be subject's node document.
|
|
||||||
// FIXME: 20. Let hideUntil be the result of running topmost popover ancestor given subject, document's showing hint popover list, null, and false.
|
// 19. Let document be subject's node document.
|
||||||
// FIXME: 21. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given subject, document's showing auto popover list, null, and false.
|
auto& document = subject.document();
|
||||||
// FIXME: 22. If hideUntil is null, then set hideUntil to document.
|
|
||||||
// FIXME: 23. Run hide all popovers until given hideUntil, false, and true.
|
// 20. Let hideUntil be the result of running topmost popover ancestor given subject, document's showing hint popover list, null, and false.
|
||||||
|
Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> hide_until = topmost_popover_ancestor(subject, document.showing_hint_popover_list(), nullptr, IsPopover::No);
|
||||||
|
|
||||||
|
// 21. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given subject, document's showing auto popover list, null, and false.
|
||||||
|
if (!hide_until.get<GC::Ptr<HTMLElement>>())
|
||||||
|
hide_until = topmost_popover_ancestor(subject, document.showing_auto_popover_list(), nullptr, IsPopover::No);
|
||||||
|
|
||||||
|
// 22. If hideUntil is null, then set hideUntil to document.
|
||||||
|
if (!hide_until.get<GC::Ptr<HTMLElement>>())
|
||||||
|
hide_until = GC::Ptr(document);
|
||||||
|
|
||||||
|
// 23. Run hide all popovers until given hideUntil, false, and true.
|
||||||
|
hide_all_popovers_until(hide_until, FocusPreviousElement::No, FireEvents::Yes);
|
||||||
|
|
||||||
// 24. Run the dialog focusing steps given subject.
|
// 24. Run the dialog focusing steps given subject.
|
||||||
subject.run_dialog_focusing_steps();
|
subject.run_dialog_focusing_steps();
|
||||||
|
|
|
@ -1038,7 +1038,8 @@ Optional<String> HTMLElement::popover_value_to_state(Optional<String> value)
|
||||||
if (value.value().is_empty() || value.value().equals_ignoring_ascii_case("auto"sv))
|
if (value.value().is_empty() || value.value().equals_ignoring_ascii_case("auto"sv))
|
||||||
return "auto"_string;
|
return "auto"_string;
|
||||||
|
|
||||||
// FIXME: This should reflect the hint value too.
|
if (value.value().equals_ignoring_ascii_case("hint"sv))
|
||||||
|
return "hint"_string;
|
||||||
|
|
||||||
return "manual"_string;
|
return "manual"_string;
|
||||||
}
|
}
|
||||||
|
@ -1146,7 +1147,8 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
|
||||||
// 5. Let nestedShow be element's popover showing or hiding.
|
// 5. Let nestedShow be element's popover showing or hiding.
|
||||||
auto nested_show = m_popover_showing_or_hiding;
|
auto nested_show = m_popover_showing_or_hiding;
|
||||||
|
|
||||||
// FIXME: 6. Let fireEvents be the boolean negation of nestedShow.
|
// 6. Let fireEvents be the boolean negation of nestedShow.
|
||||||
|
FireEvents fire_events = nested_show ? FireEvents::No : FireEvents::Yes;
|
||||||
|
|
||||||
// 7. Set element's popover showing or hiding to true.
|
// 7. Set element's popover showing or hiding to true.
|
||||||
m_popover_showing_or_hiding = true;
|
m_popover_showing_or_hiding = true;
|
||||||
|
@ -1174,48 +1176,130 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 11. Let shouldRestoreFocus be false.
|
// 11. Let shouldRestoreFocus be false.
|
||||||
|
auto should_restore_focus = FocusPreviousElement::No;
|
||||||
|
|
||||||
// 12. Let originalType be the current state of element's popover attribute.
|
// 12. Let originalType be the current state of element's popover attribute.
|
||||||
auto original_type = popover();
|
auto original_type = popover();
|
||||||
|
|
||||||
// FIXME: 13. Let stackToAppendTo be null.
|
// 13. Let stackToAppendTo be null.
|
||||||
|
enum class StackToAppendTo : u8 {
|
||||||
|
Null,
|
||||||
|
Auto,
|
||||||
|
Hint,
|
||||||
|
};
|
||||||
|
StackToAppendTo stack_to_append_to = StackToAppendTo::Null;
|
||||||
|
|
||||||
// FIXME: 14. Let autoAncestor be the result of running the topmost popover ancestor algorithm given element, document's showing auto popover list, invoker, and true.
|
// 16. If originalType is the auto state, then:
|
||||||
|
if (original_type == "auto"sv) {
|
||||||
|
// 1. Run close entire popover list given document's showing hint popover list, shouldRestoreFocus, and fireEvents.
|
||||||
|
close_entire_popover_list(document.showing_hint_popover_list(), should_restore_focus, fire_events);
|
||||||
|
|
||||||
// FIXME: 15. Let hintAncestor be the result of running the topmost popover ancestor algorithm given element, document's showing hint popover list, invoker, and true.
|
// 2. Let ancestor be the result of running the topmost popover ancestor algorithm given element, document's showing auto popover list, invoker, and true.
|
||||||
// FIXME: 16. If originalType is the auto state, then:
|
Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> ancestor = topmost_popover_ancestor(this, document.showing_auto_popover_list(), invoker, IsPopover::Yes);
|
||||||
// FIXME: 16.1. Run close entire popover list given document's showing hint popover list, shouldRestoreFocus, and fireEvents.
|
|
||||||
// FIXME: 16.2. Let ancestor be the result of running the topmost popover ancestor algorithm given element, document's showing auto popover list, invoker, and true.
|
// 3. If ancestor is null, then set ancestor to document.
|
||||||
// FIXME: 16.3. If ancestor is null, then set ancestor to document.
|
if (!ancestor.get<GC::Ptr<HTMLElement>>())
|
||||||
// FIXME: 16.4. Run hide all popovers until given ancestor, shouldRestoreFocus, and fireEvents.
|
ancestor = GC::Ptr(document);
|
||||||
// FIXME: 16.5. Set stackToAppendTo to "auto".
|
|
||||||
// FIXME: 17. If originalType is the hint state, then:
|
// 4. Run hide all popovers until given ancestor, shouldRestoreFocus, and fireEvents.
|
||||||
// FIXME: 17.1. If hintAncestor is not null, then:
|
hide_all_popovers_until(ancestor, should_restore_focus, fire_events);
|
||||||
// FIXME: 17.1.1. Run hide all popovers until given hintAncestor, shouldRestoreFocus, and fireEvents.
|
|
||||||
// FIXME: 17.1.2. Set stackToAppendTo to "hint".
|
// 5. Set stackToAppendTo to "auto".
|
||||||
// FIXME: 17.2. Otherwise:
|
stack_to_append_to = StackToAppendTo::Auto;
|
||||||
// FIXME: 17.2.1. Run close entire popover list given document's showing hint popover list, shouldRestoreFocus, and fireEvents.
|
}
|
||||||
// FIXME: 17.2.2. If autoAncestor is not null, then:
|
|
||||||
// FIXME: 17.2.2.1. Run hide all popovers until given autoAncestor, shouldRestoreFocus, and fireEvents.
|
// 17. If originalType is the hint state, then:
|
||||||
// FIXME: 17.2.2.2. Set stackToAppendTo to "auto".
|
if (original_type == "hint"sv) {
|
||||||
// FIXME: 17.3. Otherwise, set stackToAppendTo to "hint".
|
|
||||||
// 18. If originalType is auto or FIXME: hint, then:
|
// AD-HOC: Steps 14 and 15 have been moved here to avoid hitting the `popover != manual` assertion in the topmost popover ancestor algorithm.
|
||||||
if (original_type.has_value() && (original_type.value() == "auto"sv)) {
|
// Spec issue: https://github.com/whatwg/html/issues/10988.
|
||||||
// FIXME: 18.1. Assert: stackToAppendTo is not null.
|
// 14. Let autoAncestor be the result of running the topmost popover ancestor algorithm given element, document's showing auto popover list, invoker, and true.
|
||||||
// FIXME: 18.2. If originalType is not equal to the value of element's popover attribute, then:
|
auto auto_ancestor = topmost_popover_ancestor(this, document.showing_auto_popover_list(), invoker, IsPopover::Yes);
|
||||||
// FIXME: 18.2.1. If throwExceptions is true, then throw a "InvalidStateError" DOMException.
|
|
||||||
// FIXME: 18.2.2. Return.
|
// 15. Let hintAncestor be the result of running the topmost popover ancestor algorithm given element, document's showing hint popover list, invoker, and true.
|
||||||
// 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.
|
auto hint_ancestor = topmost_popover_ancestor(this, document.showing_hint_popover_list(), invoker, IsPopover::Yes);
|
||||||
// FIXME: 18.4. If the result of running topmost auto or hint popover on document is null, then set shouldRestoreFocus to true.
|
|
||||||
// FIXME: 18.5. If stackToAppendTo is "auto":
|
// 1. If hintAncestor is not null, then:
|
||||||
// FIXME: 18.5.1. Assert: document's showing auto popover list does not contain element.
|
if (hint_ancestor) {
|
||||||
// FIXME: 18.5.2. Set element's opened in popover mode to "auto".
|
// 1. Run hide all popovers until given hintAncestor, shouldRestoreFocus, and fireEvents.
|
||||||
|
hide_all_popovers_until(hint_ancestor, should_restore_focus, fire_events);
|
||||||
|
|
||||||
|
// 2. Set stackToAppendTo to "hint".
|
||||||
|
stack_to_append_to = StackToAppendTo::Hint;
|
||||||
|
}
|
||||||
|
// 2. Otherwise:
|
||||||
|
else {
|
||||||
|
// 1. Run close entire popover list given document's showing hint popover list, shouldRestoreFocus, and fireEvents.
|
||||||
|
close_entire_popover_list(document.showing_hint_popover_list(), should_restore_focus, fire_events);
|
||||||
|
|
||||||
|
// 2. If autoAncestor is not null, then:
|
||||||
|
if (auto_ancestor) {
|
||||||
|
// 1. Run hide all popovers until given autoAncestor, shouldRestoreFocus, and fireEvents.
|
||||||
|
hide_all_popovers_until(auto_ancestor, should_restore_focus, fire_events);
|
||||||
|
|
||||||
|
// 2. Set stackToAppendTo to "auto".
|
||||||
|
stack_to_append_to = StackToAppendTo::Auto;
|
||||||
|
}
|
||||||
|
// 3. Otherwise, set stackToAppendTo to "hint".
|
||||||
|
else {
|
||||||
|
stack_to_append_to = StackToAppendTo::Hint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 18. If originalType is auto or hint, then:
|
||||||
|
if (original_type.has_value() && original_type.value().is_one_of("auto", "hint")) {
|
||||||
|
// 1. Assert: stackToAppendTo is not null.
|
||||||
|
VERIFY(stack_to_append_to != StackToAppendTo::Null);
|
||||||
|
|
||||||
|
// 2. If originalType is not equal to the value of element's popover attribute, then:
|
||||||
|
if (original_type != popover()) {
|
||||||
|
// 1. If throwExceptions is true, then throw a "InvalidStateError" DOMException.
|
||||||
|
if (throw_exceptions == ThrowExceptions::Yes)
|
||||||
|
return WebIDL::InvalidStateError::create(realm(), "Element is not in a valid state to show a popover"_string);
|
||||||
|
|
||||||
|
// 2. Return.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If the result of running check popover validity given element, false, throwExceptions, document, and false is false, then run cleanupShowingFlag and return.
|
||||||
|
if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, document, IgnoreDomState::No))) {
|
||||||
|
cleanup_showing_flag();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: 4. If the result of running topmost auto or hint popover on document is null, then set shouldRestoreFocus to true.
|
||||||
|
|
||||||
|
// 5. If stackToAppendTo is "auto":
|
||||||
|
if (stack_to_append_to == StackToAppendTo::Auto) {
|
||||||
|
// 1. Assert: document's showing auto popover list does not contain element.
|
||||||
|
VERIFY(!document.showing_auto_popover_list().contains_slow(GC::Ref(*this)));
|
||||||
|
|
||||||
|
// AD-HOC: Append element to the document's showing auto popover list.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/11007
|
||||||
|
document.showing_auto_popover_list().append(*this);
|
||||||
|
|
||||||
|
// 2. Set element's opened in popover mode to "auto".
|
||||||
|
m_opened_in_popover_mode = "auto"_string;
|
||||||
|
}
|
||||||
// Otherwise:
|
// Otherwise:
|
||||||
// FIXME: 1. Assert: stackToAppendTo is "hint".
|
else {
|
||||||
// FIXME: 2. Assert: document's showing hint popover list does not contain element.
|
// 1. Assert: stackToAppendTo is "hint".
|
||||||
// FIXME: 3. Set element's opened in popover mode to "hint".
|
VERIFY(stack_to_append_to == StackToAppendTo::Hint);
|
||||||
// 18.6. Set element's popover close watcher to the result of establishing a close watcher given element's relevant global object, with:
|
|
||||||
|
// 2. Assert: document's showing hint popover list does not contain element.
|
||||||
|
VERIFY(!document.showing_hint_popover_list().contains_slow(GC::Ref(*this)));
|
||||||
|
|
||||||
|
// AD-HOC: Append element to the document's showing hint popover list.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/11007
|
||||||
|
document.showing_hint_popover_list().append(*this);
|
||||||
|
|
||||||
|
// 3. Set element's opened in popover mode to "hint".
|
||||||
|
m_opened_in_popover_mode = "hint"_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 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());
|
m_popover_close_watcher = CloseWatcher::establish(*document.window());
|
||||||
// - cancelAction being to return true.
|
// - cancelAction being to return true.
|
||||||
// We simply don't add an event listener for the cancel action.
|
// We simply don't add an event listener for the cancel action.
|
||||||
|
@ -1261,7 +1345,7 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover_for_bindings()
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
|
// https://html.spec.whatwg.org/multipage/popover.html#hide-popover-algorithm
|
||||||
// https://whatpr.org/html/9457/popover.html#hide-popover-algorithm
|
// https://whatpr.org/html/9457/popover.html#hide-popover-algorithm
|
||||||
WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEvents fire_events, ThrowExceptions throw_exceptions, IgnoreDomState ignore_dom_state)
|
WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions, IgnoreDomState ignore_dom_state)
|
||||||
{
|
{
|
||||||
// 1. If the result of running check popover validity given element, true, throwExceptions, null and ignoreDomState is false, then return.
|
// 1. If the result of running check popover validity given element, true, throwExceptions, null and ignoreDomState is false, then return.
|
||||||
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state)))
|
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state)))
|
||||||
|
@ -1294,12 +1378,22 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 7. If element's popover attribute is in the auto state FIXME: or the hint state, then:
|
// AD-HOC: This implementation checks "opened in popover mode" instead of the current popover state.
|
||||||
if (popover().has_value() && popover().value() == "auto"sv) {
|
// Spec issue: https://github.com/whatwg/html/issues/10996.
|
||||||
// FIXME: 7.1. Run hide all popovers until given element, focusPreviousElement, and fireEvents.
|
// 7. If element's popover attribute is in the auto state or the hint state, then:
|
||||||
// FIXME: 7.2. If the result of running check popover validity given element, true, throwExceptions, and ignoreDomState is false, then run cleanupSteps and return.
|
if (m_opened_in_popover_mode.has_value() && m_opened_in_popover_mode.value().is_one_of("auto", "hint")) {
|
||||||
|
// 7.1. Run hide all popovers until given element, focusPreviousElement, and fireEvents.
|
||||||
|
hide_all_popovers_until(GC::Ptr(this), focus_previous_element, fire_events);
|
||||||
|
|
||||||
|
// 7.2. If the result of running check popover validity given element, true, throwExceptions, and ignoreDomState is false, then run cleanupSteps and return.
|
||||||
|
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state))) {
|
||||||
|
cleanup_steps();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// FIXME: 8. Let autoPopoverListContainsElement be true if document's showing auto popover list's last item is element, otherwise false.
|
// 8. Let autoPopoverListContainsElement be true if document's showing auto popover list's last item is element, otherwise false.
|
||||||
|
auto const& showing_popovers = document.showing_auto_popover_list();
|
||||||
|
bool auto_popover_list_contains_element = !showing_popovers.is_empty() && showing_popovers.last() == this;
|
||||||
|
|
||||||
// 9. Set element's popover invoker to null.
|
// 9. Set element's popover invoker to null.
|
||||||
m_popover_invoker = nullptr;
|
m_popover_invoker = nullptr;
|
||||||
|
@ -1312,7 +1406,9 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
|
||||||
event_init.new_state = "closed"_string;
|
event_init.new_state = "closed"_string;
|
||||||
dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init)));
|
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.
|
// 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.
|
||||||
|
if (auto_popover_list_contains_element && (showing_popovers.is_empty() || showing_popovers.last() != this))
|
||||||
|
hide_all_popovers_until(GC::Ptr(this), focus_previous_element, FireEvents::No);
|
||||||
|
|
||||||
// 10.3. If the result of running check popover validity given element, true, throwExceptions, null, and ignoreDomState is false, then run cleanupSteps and return.
|
// 10.3. If the result of running check popover validity given element, true, throwExceptions, null, and ignoreDomState is false, then run cleanupSteps and return.
|
||||||
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state))) {
|
if (!TRY(check_popover_validity(ExpectedToBeShowing::Yes, throw_exceptions, nullptr, ignore_dom_state))) {
|
||||||
|
@ -1326,7 +1422,33 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
|
||||||
document.remove_an_element_from_the_top_layer_immediately(*this);
|
document.remove_an_element_from_the_top_layer_immediately(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: 12. Set element's opened in popover mode to null.
|
// AD-HOC: The following block of code is all ad-hoc.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/11007
|
||||||
|
|
||||||
|
// If element's opened in popover mode is "auto" or "hint":
|
||||||
|
if (m_opened_in_popover_mode.has_value() && m_opened_in_popover_mode.value().is_one_of("auto", "hint")) {
|
||||||
|
// If document's showing hint popover list's last item is element:
|
||||||
|
auto& hint_popovers = document.showing_hint_popover_list();
|
||||||
|
if (!hint_popovers.is_empty() && hint_popovers.last() == this) {
|
||||||
|
// Assert: element's opened in popover mode is "hint".
|
||||||
|
VERIFY(m_opened_in_popover_mode == "hint"sv);
|
||||||
|
|
||||||
|
// Remove the last item from document's showing hint popover list.
|
||||||
|
hint_popovers.remove(hint_popovers.size() - 1);
|
||||||
|
}
|
||||||
|
// Otherwise:
|
||||||
|
else {
|
||||||
|
// Assert: document's showing auto popover list's last item is element.
|
||||||
|
auto& auto_popovers = document.showing_auto_popover_list();
|
||||||
|
VERIFY(!auto_popovers.is_empty() && auto_popovers.last() == this);
|
||||||
|
|
||||||
|
// Remove the last item from document's showing auto popover list.
|
||||||
|
auto_popovers.remove(auto_popovers.size() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12. Set element's opened in popover mode to null.
|
||||||
|
m_opened_in_popover_mode = {};
|
||||||
|
|
||||||
// 13. Set element's popover visibility state to hidden.
|
// 13. Set element's popover visibility state to hidden.
|
||||||
m_popover_visibility_state = PopoverVisibilityState::Hidden;
|
m_popover_visibility_state = PopoverVisibilityState::Hidden;
|
||||||
|
@ -1384,6 +1506,251 @@ WebIDL::ExceptionOr<bool> HTMLElement::toggle_popover(TogglePopoverOptionsOrForc
|
||||||
return popover_visibility_state() == PopoverVisibilityState::Showing;
|
return popover_visibility_state() == PopoverVisibilityState::Showing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AD-HOC: This implementation checks "opened in popover mode" instead of the current popover state.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/10996.
|
||||||
|
// https://html.spec.whatwg.org/multipage/popover.html#hide-all-popovers-until
|
||||||
|
void HTMLElement::hide_all_popovers_until(Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> endpoint, FocusPreviousElement focus_previous_element, FireEvents fire_events)
|
||||||
|
{
|
||||||
|
// To hide all popovers until, given an HTML element or Document endpoint, a boolean focusPreviousElement, and a boolean fireEvents:
|
||||||
|
|
||||||
|
// 1. If endpoint is an HTML element and endpoint is not in the popover showing state, then return.
|
||||||
|
if (endpoint.has<GC::Ptr<HTMLElement>>() && endpoint.get<GC::Ptr<HTMLElement>>()->popover_visibility_state() != PopoverVisibilityState::Showing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 2. Let document be endpoint's node document.
|
||||||
|
auto const* document = endpoint.visit([](auto endpoint) { return &endpoint->document(); });
|
||||||
|
|
||||||
|
// 3. Assert: endpoint is a Document or endpoint's popover visibility state is showing.
|
||||||
|
VERIFY(endpoint.has<GC::Ptr<DOM::Document>>() || endpoint.get<GC::Ptr<HTMLElement>>()->popover_visibility_state() == PopoverVisibilityState::Showing);
|
||||||
|
|
||||||
|
// 4. Assert: endpoint is a Document or endpoint's popover attribute is in the auto state or endpoint's popover attribute is in the hint state.
|
||||||
|
VERIFY(endpoint.has<GC::Ptr<DOM::Document>>() || endpoint.get<GC::Ptr<HTMLElement>>()->m_opened_in_popover_mode->is_one_of("auto", "hint"));
|
||||||
|
|
||||||
|
// 5. If endpoint is a Document:
|
||||||
|
if (endpoint.has<GC::Ptr<DOM::Document>>()) {
|
||||||
|
// 1. Run close entire popover list given document's showing hint popover list, focusPreviousElement, and fireEvents.
|
||||||
|
close_entire_popover_list(document->showing_hint_popover_list(), focus_previous_element, fire_events);
|
||||||
|
|
||||||
|
// 2. Run close entire popover list given document's showing auto popover list, focusPreviousElement, and fireEvents.
|
||||||
|
close_entire_popover_list(document->showing_auto_popover_list(), focus_previous_element, fire_events);
|
||||||
|
|
||||||
|
// 3. Return.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. If document's showing hint popover list contains endpoint:
|
||||||
|
auto endpoint_element = endpoint.get<GC::Ptr<HTMLElement>>();
|
||||||
|
if (document->showing_hint_popover_list().contains_slow(GC::Ref(*endpoint_element))) {
|
||||||
|
// 1. Assert: endpoint's popover attribute is in the hint state.
|
||||||
|
VERIFY(endpoint_element->m_opened_in_popover_mode == "hint"sv);
|
||||||
|
|
||||||
|
// 2. Run hide popover stack until given endpoint, document's showing hint popover list, focusPreviousElement, and fireEvents.
|
||||||
|
endpoint_element->hide_popover_stack_until(document->showing_hint_popover_list(), focus_previous_element, fire_events);
|
||||||
|
|
||||||
|
// 3. Return.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Run close entire popover list given document's showing hint popover list, focusPreviousElement, and fireEvents.
|
||||||
|
close_entire_popover_list(document->showing_hint_popover_list(), focus_previous_element, fire_events);
|
||||||
|
|
||||||
|
// 8. If document's showing auto popover list does not contain endpoint, then return.
|
||||||
|
if (!document->showing_auto_popover_list().contains_slow(GC::Ref(*endpoint_element)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 9. Run hide popover stack until given endpoint, document's showing auto popover list, focusPreviousElement, and fireEvents.
|
||||||
|
endpoint_element->hide_popover_stack_until(document->showing_auto_popover_list(), focus_previous_element, fire_events);
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/popover.html#hide-popover-stack-until
|
||||||
|
void HTMLElement::hide_popover_stack_until(Vector<GC::Ref<HTMLElement>> const& popover_list, FocusPreviousElement focus_previous_element, FireEvents fire_events)
|
||||||
|
{
|
||||||
|
// To hide popover stack until, given an HTML element endpoint, a list popoverList, a boolean focusPreviousElement, and a boolean fireEvents:
|
||||||
|
|
||||||
|
// 1. Let repeatingHide be false.
|
||||||
|
bool repeating_hide = false;
|
||||||
|
|
||||||
|
// 2. Perform the following steps at least once:
|
||||||
|
do {
|
||||||
|
// 1. Let lastToHide be null.
|
||||||
|
GC::Ptr<HTMLElement> last_to_hide;
|
||||||
|
|
||||||
|
// 2. For each popover in popoverList:
|
||||||
|
// AD-HOC: This needs to be iterated in reverse because step 4 hides items in reverse.
|
||||||
|
for (auto const& popover : popover_list.in_reverse()) {
|
||||||
|
// 1. If popover is endpoint, then break.
|
||||||
|
if (popover == this)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 2. Set lastToHide to popover.
|
||||||
|
last_to_hide = popover;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If lastToHide is null, then return.
|
||||||
|
if (!last_to_hide)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 4. While lastToHide's popover visibility state is showing:
|
||||||
|
while (last_to_hide->popover_visibility_state() == PopoverVisibilityState::Showing) {
|
||||||
|
// 1. Assert: popoverList is not empty.
|
||||||
|
VERIFY(!popover_list.is_empty());
|
||||||
|
|
||||||
|
// 2. Run the hide popover algorithm given the last item in popoverList, focusPreviousElement, fireEvents, and false.
|
||||||
|
MUST(popover_list.last()->hide_popover(focus_previous_element, fire_events, ThrowExceptions::No, IgnoreDomState::No));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Assert: repeatingHide is false or popoverList's last item is endpoint.
|
||||||
|
VERIFY(!repeating_hide || popover_list.last() == this);
|
||||||
|
|
||||||
|
// 6. Set repeatingHide to true if popoverList contains endpoint and popoverList's last item is not endpoint, otherwise false.
|
||||||
|
repeating_hide = popover_list.contains_slow(GC::Ref(*this)) && popover_list.last() != this;
|
||||||
|
|
||||||
|
// 7. If repeatingHide is true, then set fireEvents to false.
|
||||||
|
if (repeating_hide)
|
||||||
|
fire_events = FireEvents::No;
|
||||||
|
|
||||||
|
} while (repeating_hide);
|
||||||
|
// and keep performing them while repeatingHide is true.
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/popover.html#close-entire-popover-list
|
||||||
|
void HTMLElement::close_entire_popover_list(Vector<GC::Ref<HTMLElement>> const& popover_list, FocusPreviousElement focus_previous_element, FireEvents fire_events)
|
||||||
|
{
|
||||||
|
// To close entire popover list given a list popoverList, a boolean focusPreviousElement, and a boolean fireEvents:
|
||||||
|
|
||||||
|
// FIXME: If an event handler opens a new popover then this could be an infinite loop.
|
||||||
|
// 1. While popoverList is not empty:
|
||||||
|
while (!popover_list.is_empty()) {
|
||||||
|
// 1. Run the hide popover algorithm given popoverList's last item, focusPreviousElement, fireEvents, and false.
|
||||||
|
MUST(popover_list.last()->hide_popover(focus_previous_element, fire_events, ThrowExceptions::No, IgnoreDomState::No));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/popover.html#topmost-popover-ancestor
|
||||||
|
GC::Ptr<HTMLElement> HTMLElement::topmost_popover_ancestor(GC::Ptr<DOM::Node> new_popover_or_top_layer_element, Vector<GC::Ref<HTMLElement>> const& popover_list, GC::Ptr<HTMLElement> invoker, IsPopover is_popover)
|
||||||
|
{
|
||||||
|
// To find the topmost popover ancestor, given a Node newPopoverOrTopLayerElement, a list popoverList, an HTML element or null invoker, and a boolean isPopover, perform the following steps. They return an HTML element or null.
|
||||||
|
|
||||||
|
// 1. If isPopover is true:
|
||||||
|
auto* new_popover = as_if<HTML::HTMLElement>(*new_popover_or_top_layer_element);
|
||||||
|
if (is_popover == IsPopover::Yes) {
|
||||||
|
// 1. Assert: newPopoverOrTopLayerElement is an HTML element.
|
||||||
|
VERIFY(new_popover);
|
||||||
|
|
||||||
|
// 2. Assert: newPopoverOrTopLayerElement's popover attribute is not in the no popover state or the manual state.
|
||||||
|
VERIFY(!new_popover->popover().has_value() || new_popover->popover().value() != "manual"sv);
|
||||||
|
|
||||||
|
// 3. Assert: newPopoverOrTopLayerElement's popover visibility state is not in the popover showing state.
|
||||||
|
VERIFY(new_popover->popover_visibility_state() != PopoverVisibilityState::Showing);
|
||||||
|
}
|
||||||
|
// 2. Otherwise:
|
||||||
|
else {
|
||||||
|
// 1. Assert: invoker is null.
|
||||||
|
VERIFY(!invoker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Let popoverPositions be an empty ordered map.
|
||||||
|
OrderedHashMap<GC::Ref<HTMLElement>, int> popover_positions;
|
||||||
|
|
||||||
|
// 4. Let index be 0.
|
||||||
|
int index = 0;
|
||||||
|
|
||||||
|
// 5. For each popover of popoverList:
|
||||||
|
for (auto const& popover : popover_list) {
|
||||||
|
// 1. Set popoverPositions[popover] to index.
|
||||||
|
popover_positions.set(*popover, index);
|
||||||
|
|
||||||
|
// 2. Increment index by 1.
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. If isPopover is true, then set popoverPositions[newPopoverOrTopLayerElement] to index.
|
||||||
|
if (is_popover == IsPopover::Yes)
|
||||||
|
popover_positions.set(*new_popover, index);
|
||||||
|
|
||||||
|
// 7. Increment index by 1.
|
||||||
|
index++;
|
||||||
|
|
||||||
|
// 8. Let topmostPopoverAncestor be null.
|
||||||
|
GC::Ptr<HTMLElement> topmost_popover_ancestor;
|
||||||
|
|
||||||
|
// 9. Let checkAncestor be an algorithm which performs the following steps given candidate:
|
||||||
|
auto check_ancestor = [&](auto candidate) {
|
||||||
|
// 1. If candidate is null, then return.
|
||||||
|
if (!candidate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 2. Let okNesting be false.
|
||||||
|
bool ok_nesting = false;
|
||||||
|
|
||||||
|
// 3. Let candidateAncestor be null.
|
||||||
|
GC::Ptr<HTMLElement> candidate_ancestor;
|
||||||
|
|
||||||
|
// 4. While okNesting is false:
|
||||||
|
while (!ok_nesting) {
|
||||||
|
// 1. Set candidateAncestor to the result of running nearest inclusive open popover given candidate.
|
||||||
|
candidate_ancestor = candidate->nearest_inclusive_open_popover();
|
||||||
|
|
||||||
|
// 2. If candidateAncestor is null or popoverPositions does not contain candidateAncestor, then return.
|
||||||
|
if (!candidate_ancestor || !popover_positions.contains(*candidate_ancestor))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 3. Assert: candidateAncestor's popover attribute is not in the manual or none state.
|
||||||
|
VERIFY(!candidate_ancestor->popover().has_value() || candidate_ancestor->popover().value() != "manual"sv);
|
||||||
|
|
||||||
|
// AD-HOC: This also checks if isPopover is false.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/11008.
|
||||||
|
// 4. Set okNesting to true if newPopoverOrTopLayerElement's popover attribute is in the hint state or candidateAncestor's popover attribute is in the auto state.
|
||||||
|
if (is_popover == IsPopover::No || new_popover->popover() == "hint"sv || candidate_ancestor->popover() == "auto"sv)
|
||||||
|
ok_nesting = true;
|
||||||
|
|
||||||
|
// 5. If okNesting is false, then set candidate to candidateAncestor's parent in the flat tree.
|
||||||
|
if (!ok_nesting)
|
||||||
|
candidate = candidate_ancestor->shadow_including_first_ancestor_of_type<HTMLElement>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Let candidatePosition be popoverPositions[candidateAncestor].
|
||||||
|
auto candidate_position = popover_positions.get(*candidate_ancestor).value();
|
||||||
|
|
||||||
|
// 6. If topmostPopoverAncestor is null or popoverPositions[topmostPopoverAncestor] is less than candidatePosition, then set topmostPopoverAncestor to candidateAncestor.
|
||||||
|
if (!topmost_popover_ancestor || popover_positions.get(*topmost_popover_ancestor).value() < candidate_position)
|
||||||
|
topmost_popover_ancestor = candidate_ancestor;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 10. Run checkAncestor given newPopoverOrTopLayerElement's parent node within the flat tree.
|
||||||
|
check_ancestor(new_popover_or_top_layer_element->shadow_including_first_ancestor_of_type<HTMLElement>());
|
||||||
|
|
||||||
|
// 11. Run checkAncestor given invoker.
|
||||||
|
check_ancestor(invoker.ptr());
|
||||||
|
|
||||||
|
// 12. Return topmostPopoverAncestor.
|
||||||
|
return topmost_popover_ancestor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://html.spec.whatwg.org/multipage/popover.html#nearest-inclusive-open-popover
|
||||||
|
GC::Ptr<HTMLElement> HTMLElement::nearest_inclusive_open_popover()
|
||||||
|
{
|
||||||
|
// To find the nearest inclusive open popover given a Node node, perform the following steps. They return an HTML element or null.
|
||||||
|
|
||||||
|
// 1. Let currentNode be node.
|
||||||
|
auto* current_node = this;
|
||||||
|
|
||||||
|
// 2. While currentNode is not null:
|
||||||
|
while (current_node) {
|
||||||
|
// AD-HOC: This also allows hint popovers.
|
||||||
|
// Spec issue: https://github.com/whatwg/html/issues/11008.
|
||||||
|
// 1. If currentNode's popover attribute is in the auto state and currentNode's popover visibility state is showing, then return currentNode.
|
||||||
|
if (current_node->popover().has_value() && current_node->popover().value().is_one_of("auto", "hint") && current_node->popover_visibility_state() == PopoverVisibilityState::Showing)
|
||||||
|
return current_node;
|
||||||
|
|
||||||
|
// 2. Set currentNode to currentNode's parent in the flat tree.
|
||||||
|
current_node = current_node->shadow_including_first_ancestor_of_type<HTMLElement>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Return null.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/popover.html#queue-a-popover-toggle-event-task
|
// 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)
|
void HTMLElement::queue_a_popover_toggle_event_task(String old_state, String new_state)
|
||||||
{
|
{
|
||||||
|
|
|
@ -65,6 +65,11 @@ enum class IgnoreDomState {
|
||||||
No,
|
No,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class IsPopover {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
};
|
||||||
|
|
||||||
class HTMLElement
|
class HTMLElement
|
||||||
: public DOM::Element
|
: public DOM::Element
|
||||||
, public HTML::GlobalEventHandlers
|
, public HTML::GlobalEventHandlers
|
||||||
|
@ -123,6 +128,7 @@ public:
|
||||||
|
|
||||||
WebIDL::ExceptionOr<void> set_popover(Optional<String> value);
|
WebIDL::ExceptionOr<void> set_popover(Optional<String> value);
|
||||||
Optional<String> popover() const;
|
Optional<String> popover() const;
|
||||||
|
Optional<String> opened_in_popover_mode() const { return m_opened_in_popover_mode; }
|
||||||
|
|
||||||
virtual void removed_from(Node* old_parent, Node& old_root) override;
|
virtual void removed_from(Node* old_parent, Node& old_root) override;
|
||||||
|
|
||||||
|
@ -140,6 +146,9 @@ public:
|
||||||
WebIDL::ExceptionOr<void> show_popover(ThrowExceptions throw_exceptions, GC::Ptr<HTMLElement> invoker);
|
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, IgnoreDomState ignore_dom_state);
|
WebIDL::ExceptionOr<void> hide_popover(FocusPreviousElement focus_previous_element, FireEvents fire_events, ThrowExceptions throw_exceptions, IgnoreDomState ignore_dom_state);
|
||||||
|
|
||||||
|
static void hide_all_popovers_until(Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> endpoint, FocusPreviousElement focus_previous_element, FireEvents fire_events);
|
||||||
|
static GC::Ptr<HTMLElement> topmost_popover_ancestor(GC::Ptr<DOM::Node> new_popover_or_top_layer_element, Vector<GC::Ref<HTMLElement>> const& popover_list, GC::Ptr<HTMLElement> invoker, IsPopover is_popover);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
HTMLElement(DOM::Document&, DOM::QualifiedName);
|
HTMLElement(DOM::Document&, DOM::QualifiedName);
|
||||||
|
|
||||||
|
@ -178,6 +187,9 @@ private:
|
||||||
void queue_a_popover_toggle_event_task(String old_state, String new_state);
|
void queue_a_popover_toggle_event_task(String old_state, String new_state);
|
||||||
|
|
||||||
static Optional<String> popover_value_to_state(Optional<String> value);
|
static Optional<String> popover_value_to_state(Optional<String> value);
|
||||||
|
void hide_popover_stack_until(Vector<GC::Ref<HTMLElement>> const& popover_list, FocusPreviousElement focus_previous_element, FireEvents fire_events);
|
||||||
|
GC::Ptr<HTMLElement> nearest_inclusive_open_popover();
|
||||||
|
static void close_entire_popover_list(Vector<GC::Ref<HTMLElement>> const& popover_list, FocusPreviousElement focus_previous_element, FireEvents fire_events);
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
|
// https://html.spec.whatwg.org/multipage/custom-elements.html#attached-internals
|
||||||
GC::Ptr<ElementInternals> m_attached_internals;
|
GC::Ptr<ElementInternals> m_attached_internals;
|
||||||
|
@ -204,6 +216,8 @@ private:
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/popover.html#popover-close-watcher
|
// https://html.spec.whatwg.org/multipage/popover.html#popover-close-watcher
|
||||||
GC::Ptr<CloseWatcher> m_popover_close_watcher;
|
GC::Ptr<CloseWatcher> m_popover_close_watcher;
|
||||||
|
|
||||||
|
Optional<String> m_opened_in_popover_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
PASS
|
||||||
|
PASS
|
|
@ -0,0 +1,3 @@
|
||||||
|
PASS
|
||||||
|
PASS
|
||||||
|
PASS
|
|
@ -0,0 +1,2 @@
|
||||||
|
PASS
|
||||||
|
PASS
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
|
||||||
|
<!-- opening an auto popover hides unrelated popovers -->
|
||||||
|
<!-- hiding a popover also hides its decendants -->
|
||||||
|
|
||||||
|
<div popover id=outer>
|
||||||
|
<div popover id=middle>
|
||||||
|
<div popover id=inner>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div popover id=second>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const outer = document.getElementById("outer");
|
||||||
|
const middle = document.getElementById("middle");
|
||||||
|
const inner = document.getElementById("inner");
|
||||||
|
const second = document.getElementById("second");
|
||||||
|
|
||||||
|
outer.showPopover();
|
||||||
|
middle.showPopover();
|
||||||
|
inner.showPopover();
|
||||||
|
|
||||||
|
if (outer.matches(":popover-open")
|
||||||
|
&& middle.matches(":popover-open")
|
||||||
|
&& inner.matches(":popover-open")
|
||||||
|
&& !second.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
|
||||||
|
second.showPopover();
|
||||||
|
|
||||||
|
if (outer.matches(":popover-open")
|
||||||
|
&& !middle.matches(":popover-open")
|
||||||
|
&& !inner.matches(":popover-open")
|
||||||
|
&& second.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,39 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
|
||||||
|
<!-- hint popovers only hide other hint popovers -->
|
||||||
|
|
||||||
|
<div popover id=auto></div>
|
||||||
|
<div popover=hint id=hint1></div>
|
||||||
|
<div popover=hint id=hint2></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const auto = document.getElementById("auto");
|
||||||
|
const hint1 = document.getElementById("hint1");
|
||||||
|
const hint2 = document.getElementById("hint2");
|
||||||
|
|
||||||
|
auto.showPopover();
|
||||||
|
hint1.showPopover();
|
||||||
|
|
||||||
|
if (auto.matches(":popover-open")
|
||||||
|
&& hint1.matches(":popover-open")
|
||||||
|
&& !hint2.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
|
||||||
|
hint2.showPopover();
|
||||||
|
|
||||||
|
if (auto.matches(":popover-open")
|
||||||
|
&& !hint1.matches(":popover-open")
|
||||||
|
&& hint2.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
|
||||||
|
auto.hidePopover();
|
||||||
|
auto.showPopover();
|
||||||
|
|
||||||
|
if (auto.matches(":popover-open")
|
||||||
|
&& !hint1.matches(":popover-open")
|
||||||
|
&& !hint2.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<script src="include.js"></script>
|
<script src="../include.js"></script>
|
||||||
<div popover id="pop"></div>
|
<div popover id="pop"></div>
|
||||||
<div popover id="pop2"></div>
|
<div popover id="pop2"></div>
|
||||||
<script>
|
<script>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
|
||||||
|
<!-- popover invokers count as parents -->
|
||||||
|
|
||||||
|
<button popovertarget=outer id=outerButton></button>
|
||||||
|
<div popover id=outer>
|
||||||
|
<button popovertarget=middle id=middleButton></button>
|
||||||
|
<button popovertarget=second id=secondButton></button>
|
||||||
|
</div>
|
||||||
|
<div popover id=middle>
|
||||||
|
<button popovertarget=inner id=innerButton></button>
|
||||||
|
</div>
|
||||||
|
<div popover id=inner></div>
|
||||||
|
<div popover id=second></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
test(() => {
|
||||||
|
const outer = document.getElementById("outer");
|
||||||
|
const middle = document.getElementById("middle");
|
||||||
|
const inner = document.getElementById("inner");
|
||||||
|
const second = document.getElementById("second");
|
||||||
|
|
||||||
|
const outerButton = document.getElementById("outerButton");
|
||||||
|
const middleButton = document.getElementById("middleButton");
|
||||||
|
const innerButton = document.getElementById("innerButton");
|
||||||
|
const secondButton = document.getElementById("secondButton");
|
||||||
|
|
||||||
|
outerButton.click();
|
||||||
|
middleButton.click();
|
||||||
|
innerButton.click();
|
||||||
|
|
||||||
|
if (outer.matches(":popover-open")
|
||||||
|
&& middle.matches(":popover-open")
|
||||||
|
&& inner.matches(":popover-open")
|
||||||
|
&& !second.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
|
||||||
|
secondButton.click();
|
||||||
|
|
||||||
|
if (outer.matches(":popover-open")
|
||||||
|
&& !middle.matches(":popover-open")
|
||||||
|
&& !inner.matches(":popover-open")
|
||||||
|
&& second.matches(":popover-open"))
|
||||||
|
println("PASS");
|
||||||
|
});
|
||||||
|
</script>
|
Loading…
Add table
Add a link
Reference in a new issue