1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-09 09:34:57 +09:00

LibWeb: Update spec comments for dialog, popover and close watcher

This commit is contained in:
Luke Warlow 2025-01-24 12:44:17 +00:00 committed by Tim Ledbetter
parent 0bb0061915
commit 391a08bf17
Notes: github-actions[bot] 2025-01-25 03:41:07 +00:00
6 changed files with 165 additions and 114 deletions

View file

@ -82,44 +82,47 @@ CloseWatcher::CloseWatcher(JS::Realm& realm)
// https://html.spec.whatwg.org/multipage/interaction.html#close-watcher-request-close
bool CloseWatcher::request_close()
{
// 1. If closeWatcher is not active, then return.
// 1. If closeWatcher is not active, then return true.
if (!m_is_active)
return true;
// 2. If closeWatcher's is running cancel action is true, then return true.
// FIXME: 2. If the result of running closeWatcher's get enabled state is false, then return true.
// 3. If closeWatcher's is running cancel action is true, then return true.
if (m_is_running_cancel_action)
return true;
// 3. Let window be closeWatcher's window.
// 4. Let window be closeWatcher's window.
auto& window = as<HTML::Window>(realm().global_object());
// 4. If window's associated Document is not fully active, then return true.
// 5. If window's associated Document is not fully active, then return true.
if (!window.associated_document().is_fully_active())
return true;
// 5. Let canPreventClose be true if window's close watcher manager's groups's size is less than window's close watcher manager's allowed number of groups,
// 6. Let canPreventClose be true if window's close watcher manager's groups's size is less than window's close watcher manager's allowed number of groups,
// and window has history-action activation; otherwise false.
auto manager = window.close_watcher_manager();
bool can_prevent_close = manager->can_prevent_close() && window.has_history_action_activation();
// 6. Set closeWatcher's is running cancel action to true.
// 7. Set closeWatcher's is running cancel action to true.
m_is_running_cancel_action = true;
// 7. Let shouldContinue be the result of running closeWatcher's cancel action given canPreventClose.
// 8. Let shouldContinue be the result of running closeWatcher's cancel action given canPreventClose.
bool should_continue = dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .cancelable = can_prevent_close }));
// 8. Set closeWatcher's is running cancel action to false.
// 9. Set closeWatcher's is running cancel action to false.
m_is_running_cancel_action = false;
// 9. If shouldContinue is false, then:
// 10. If shouldContinue is false, then:
if (!should_continue) {
// 9.1 Assert: canPreventClose is true.
// 10.1 Assert: canPreventClose is true.
VERIFY(can_prevent_close);
// 9.2 Consume history-action user activation given window.
// 10.2 Consume history-action user activation given window.
window.consume_history_action_user_activation();
// 10.3 Return false.
return false;
}
// 10. Close closeWatcher.
// 11. Close closeWatcher.
close();
// 11. Return true.
// 12. Return true.
return true;
}
@ -130,14 +133,16 @@ void CloseWatcher::close()
if (!m_is_active)
return;
// 2. If closeWatcher's window's associated Document is not fully active, then return.
// FIXME: 2. If the result of running closeWatcher's get enabled state is false, then return.
// 3. If closeWatcher's window's associated Document is not fully active, then return.
if (!as<HTML::Window>(realm().global_object()).associated_document().is_fully_active())
return;
// 3. Destroy closeWatcher.
// 4. Destroy closeWatcher.
destroy();
// 4. Run closeWatcher's close action.
// 5. Run closeWatcher's close action.
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::close));
}

View file

@ -75,11 +75,11 @@ bool CloseWatcherManager::process_close_watchers()
}
// 2.2 For each closeWatcher of group, in reverse order:
for (auto it = group_copy.rbegin(); it != group_copy.rend(); ++it) {
// 2.1.1 Set processedACloseWatcher to true.
// FIXME: 2.2.1 If the result of running closeWatcher's get enabled state is true, set processedACloseWatcher to true.
processed_a_close_watcher = true;
// 2.1.2 Let shouldProceed be the result of requesting to close closeWatcher.
// 2.2.2 Let shouldProceed be the result of requesting to close closeWatcher with true.
bool should_proceed = (*it)->request_close();
// 2.1.3 If shouldProceed is false, then break;
// 2.2.3 If shouldProceed is false, then break;
if (!should_proceed)
break;
}

View file

@ -130,15 +130,17 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
// 6. Add an open attribute to this, whose value is the empty string.
TRY(set_attribute(AttributeNames::open, {}));
// FIXME: 7. Set this's previously focused element to the focused element.
// FIXME: 7. Assert: this's node document's open dialogs list does not contain this.
// FIXME: 8. Add this to this's node document's open dialogs list.
// FIXME: 9. Set the dialog close watcher with this.
// 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.
// 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.
// FIXME: 14. If hideUntil is null, then set hideUntil to document.
// FIXME: 15. Run hide all popovers until given hideUntil, false, and true.
// FIXME: 8. Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
// FIXME: 9. If hideUntil is null, then set hideUntil to this's node document.
// FIXME: 10. Run hide all popovers given this's node document.
// 11. Run the dialog focusing steps given this.
// 16. Run the dialog focusing steps given this.
run_dialog_focusing_steps();
return {};
@ -147,7 +149,7 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-showmodal
WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
{
// 1. If this has an open attribute and the is modal flag of this is true, then return.
// 1. If this has an open attribute and is modal of this is true, then return.
if (has_attribute(AttributeNames::open) && m_is_modal)
return {};
@ -197,50 +199,29 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
// 11. Add an open attribute to this, whose value is the empty string.
TRY(set_attribute(AttributeNames::open, {}));
// 12. Set the is modal flag of this to true.
// 12. Set is modal of this to true.
m_is_modal = true;
// FIXME: 13. Let this's node document be blocked by the modal dialog this.
// FIXME: 13. Assert: this's node document's open dialogs list does not contain this.
// FIXME: 14. Add this to this's node document's open dialogs list.
// FIXME: 15. Let this's node document be blocked by the modal dialog this.
// 14. If this's node document's top layer does not already contain this, then add an element to the top layer given this.
// 16. If this's node document's top layer does not already contain this, then add an element to the top layer given this.
if (!document().top_layer_elements().contains(*this))
document().add_an_element_to_the_top_layer(*this);
// 15. Set this's close watcher to the result of establishing a close watcher given this's relevant global object, with:
m_close_watcher = CloseWatcher::establish(*document().window());
// - cancelAction given canPreventClose being to return the result of firing an event named cancel at this, with the cancelable attribute initialized to canPreventClose.
auto cancel_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM& vm) {
auto& event = as<DOM::Event>(vm.argument(0).as_object());
bool can_prevent_close = event.cancelable();
auto should_continue = dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .cancelable = can_prevent_close }));
if (!should_continue)
event.prevent_default();
return JS::js_undefined();
},
0, "", &realm());
auto cancel_callback = realm().heap().allocate<WebIDL::CallbackType>(*cancel_callback_function, realm());
m_close_watcher->add_event_listener_without_options(HTML::EventNames::cancel, DOM::IDLEventListener::create(realm(), cancel_callback));
// - closeAction being to close the dialog given this and null.
auto close_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
close_the_dialog({});
// 17. Set the dialog close watcher with this.
set_close_watcher();
return JS::js_undefined();
},
0, "", &realm());
auto close_callback = realm().heap().allocate<WebIDL::CallbackType>(*close_callback_function, realm());
m_close_watcher->add_event_listener_without_options(HTML::EventNames::close, DOM::IDLEventListener::create(realm(), close_callback));
// FIXME: 18. Set this's previously focused element to the focused element.
// FIXME: 16. Set this's previously focused element to the focused element.
// FIXME: 19. Let document be this's node document.
// FIXME: 20. Let hideUntil be the result of running topmost popover ancestor given this, document's showing hint popover list, null, and false.
// FIXME: 21. 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.
// FIXME: 22. If hideUntil is null, then set hideUntil to document.
// FIXME: 23. Run hide all popovers until given hideUntil, false, and true.
// FIXME: 17. Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
// FIXME: 18. If hideUntil is null, then set hideUntil to this's node document.
// FIXME: 19. Run hide all popovers until given hideUntil, false, and true.
// 20. Run the dialog focusing steps given this.
// 24. Run the dialog focusing steps given this.
run_dialog_focusing_steps();
return {};
@ -299,31 +280,66 @@ void HTMLDialogElement::close_the_dialog(Optional<String> result)
// 8. Set the is modal flag of subject to false.
m_is_modal = false;
// 9. If result is not null, then set the returnValue attribute to result.
// FIXME: 9. Remove subject from subject's node document's open dialogs list.
// 10. If result is not null, then set the returnValue attribute to result.
if (result.has_value())
set_return_value(result.release_value());
// FIXME: 10. If subject's previously focused element is not null, then:
// FIXME: 11. Set the request close return value to null.
// FIXME: 12. If subject's previously focused element is not null, then:
// 1. Let element be subject's previously focused element.
// 2. Set subject's previously focused element to null.
// 3. If subject's node document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of element,
// or wasModal is true, then run the focusing steps for element; the viewport should not be scrolled by doing this step.
// 11. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
// 13. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
auto close_event = DOM::Event::create(realm(), HTML::EventNames::close);
dispatch_event(close_event);
});
// 12. If subject's close watcher is not null, then:
// 14. If subject's close watcher is not null, then:
if (m_close_watcher) {
// 9.1 Destroy subject's close watcher.
// 14.1 Destroy subject's close watcher.
m_close_watcher->destroy();
// 9.2 Set subject's close watcher to null.
// 14.2 Set subject's close watcher to null.
m_close_watcher = nullptr;
}
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#set-the-dialog-close-watcher
void HTMLDialogElement::set_close_watcher()
{
// 1. Set dialog's close watcher to the result of establishing a close watcher given dialog's relevant global object, with:
m_close_watcher = CloseWatcher::establish(*document().window());
// - cancelAction given canPreventClose being to return the result of firing an event named cancel at dialog, with the cancelable attribute initialized to canPreventClose.
auto cancel_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM& vm) {
auto& event = as<DOM::Event>(vm.argument(0).as_object());
bool can_prevent_close = event.cancelable();
auto should_continue = dispatch_event(DOM::Event::create(realm(), HTML::EventNames::cancel, { .cancelable = can_prevent_close }));
if (!should_continue)
event.prevent_default();
return JS::js_undefined();
},
0, "", &realm());
auto cancel_callback = realm().heap().allocate<WebIDL::CallbackType>(*cancel_callback_function, realm());
m_close_watcher->add_event_listener_without_options(HTML::EventNames::cancel, DOM::IDLEventListener::create(realm(), cancel_callback));
// - closeAction being to close the dialog given dialog and FIXME: dialog's request close return value.
auto close_callback_function = JS::NativeFunction::create(
realm(), [this](JS::VM&) {
close_the_dialog({});
return JS::js_undefined();
},
0, "", &realm());
auto close_callback = realm().heap().allocate<WebIDL::CallbackType>(*close_callback_function, realm());
m_close_watcher->add_event_listener_without_options(HTML::EventNames::close, DOM::IDLEventListener::create(realm(), close_callback));
// FIXME: - getEnabledState being to return true if dialog's enable close watcher for requestClose() is true or dialog's computed closed-by state is not None; otherwise false.
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps
void HTMLDialogElement::run_dialog_focusing_steps()
{

View file

@ -46,6 +46,8 @@ private:
void run_dialog_focusing_steps();
void set_close_watcher();
String m_return_value;
bool m_is_modal { false };
GC::Ptr<CloseWatcher> m_close_watcher;

View file

@ -8,8 +8,10 @@ interface HTMLDialogElement : HTMLElement {
[CEReactions, Reflect] attribute boolean open;
attribute DOMString returnValue;
[FIXME, CEReactions] attribute DOMString closedBy;
[CEReactions] undefined show();
[CEReactions] undefined showModal();
[CEReactions] undefined close(optional DOMString returnValue);
[FIXME, CEReactions] undefined requestClose(optional DOMString returnValue);
};

View file

@ -970,6 +970,8 @@ Optional<String> HTMLElement::popover() const
if (value.value().is_empty() || value.value().equals_ignoring_ascii_case("auto"sv))
return "auto"_string;
// FIXME: This should reflect the hint value too.
return "manual"_string;
}
@ -1061,17 +1063,19 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
// 5. Let nestedShow be element's popover showing or hiding.
auto nested_show = m_popover_showing_or_hiding;
// 6. Set element's popover showing or hiding to true.
// FIXME: 6. Let fireEvents be the boolean negation of nestedShow.
// 7. Set element's popover showing or hiding to true.
m_popover_showing_or_hiding = true;
// 7. Let cleanupShowingFlag be the following steps:
// 8. Let cleanupShowingFlag be the following steps:
auto cleanup_showing_flag = [&nested_show, this] {
// 7.1. If nestedShow is false, then set element's popover showing or hiding to false.
// 8.1. If nestedShow is false, then set element's popover showing or hiding to false.
if (!nested_show)
m_popover_showing_or_hiding = false;
};
// 8. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return.
// 9. If the result of firing an event named beforetoggle, using ToggleEvent, with the cancelable attribute initialized to true, the oldState attribute initialized to "closed", and the newState attribute initialized to "open" at element is false, then run cleanupShowingFlag and return.
ToggleEventInit event_init {};
event_init.old_state = "closed"_string;
event_init.new_state = "open"_string;
@ -1081,25 +1085,54 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
return {};
}
// 9. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
// 10. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
if (!TRY(check_popover_validity(ExpectedToBeShowing::No, throw_exceptions, nullptr))) {
cleanup_showing_flag();
return {};
}
// 10. Let shouldRestoreFocus be false.
bool should_restore_focus = false;
// FIXME: 11. Let shouldRestoreFocus be false.
// 11. If element's popover attribute is in the auto state, then:
if (popover().has_value() && popover().value() == "auto"sv) {
// FIXME: 11.1. Let originalType be the value of element's popover attribute.
// FIXME: 11.2. Let ancestor be the result of running the topmost popover ancestor algorithm given element, invoker, and true.
// FIXME: 11.3. If ancestor is null, then set ancestor to document.
// FIXME: 11.4. Run hide all popovers until given ancestor, false, and not nestedShow.
// FIXME: 11.5. If originalType is not equal to the value of element's popover attribute, then throw a "InvalidStateError" DOMException.
// FIXME: 11.6. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
// FIXME: 11.7. If the result of running topmost auto popover on document is null, then set shouldRestoreFocus to true.
// 11.8. Set element's popover close watcher to the result of establishing a close watcher given element's relevant global object, with:
// 12. Let originalType be the current state of element's popover attribute.
auto original_type = popover();
// FIXME: 13. Let stackToAppendTo be 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.
// 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.
// FIXME: 16. If originalType is the auto state, then:
// 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.
// FIXME: 16.3. If ancestor is null, then set ancestor to document.
// FIXME: 16.4. Run hide all popovers until given ancestor, shouldRestoreFocus, and fireEvents.
// FIXME: 16.5. Set stackToAppendTo to "auto".
// FIXME: 17. If originalType is the hint state, then:
// FIXME: 17.1. If hintAncestor is not null, then:
// FIXME: 17.1.1. Run hide all popovers until given hintAncestor, shouldRestoreFocus, and fireEvents.
// FIXME: 17.1.2. Set stackToAppendTo to "hint".
// FIXME: 17.2. Otherwise:
// 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.
// FIXME: 17.2.2.2. Set stackToAppendTo to "auto".
// FIXME: 17.3. Otherwise, set stackToAppendTo to "hint".
// 18. If originalType is auto or FIXME: hint, then:
if (original_type.has_value() && (original_type.value() == "auto"sv)) {
// FIXME: 18.1. Assert: stackToAppendTo is not null.
// FIXME: 18.2. If originalType is not equal to the value of element's popover attribute, then:
// FIXME: 18.2.1. If throwExceptions is true, then throw a "InvalidStateError" DOMException.
// FIXME: 18.2.2. Return.
// FIXME: 18.3. If the result of running check popover validity given element, false, throwExceptions, and document is false, then run cleanupShowingFlag and return.
// 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":
// FIXME: 18.5.1. Assert: document's showing auto popover list does not contain element.
// FIXME: 18.5.2. Set element's opened in popover mode to "auto".
// Otherwise:
// FIXME: 1. Assert: stackToAppendTo is "hint".
// FIXME: 2. Assert: document's showing hint popover list does not contain element.
// FIXME: 3. Set element's opened in popover mode to "hint".
// 18.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());
// - cancelAction being to return true.
// We simply don't add an event listener for the cancel action.
@ -1113,31 +1146,22 @@ WebIDL::ExceptionOr<void> HTMLElement::show_popover(ThrowExceptions throw_except
0, "", &realm());
auto close_callback = realm().heap().allocate<WebIDL::CallbackType>(*close_callback_function, realm());
m_popover_close_watcher->add_event_listener_without_options(HTML::EventNames::close, DOM::IDLEventListener::create(realm(), close_callback));
// FIXME: - getEnabledState being to return true.
}
// FIXME: 12. Set element's previously focused element to null.
// FIXME: 13. Let originallyFocusedElement be document's focused area of the document's DOM anchor.
// 14. Add an element to the top layer given element.
// FIXME: 19. Set element's previously focused element to null.
// FIXME: 20. Let originallyFocusedElement be document's focused area of the document's DOM anchor.
// 21. Add an element to the top layer given element.
document.add_an_element_to_the_top_layer(*this);
// 15. Set element's popover visibility state to showing.
// 22. Set element's popover visibility state to showing.
m_popover_visibility_state = PopoverVisibilityState::Showing;
// 16. Set element's popover invoker to invoker.
// 23. Set element's popover invoker to invoker.
m_popover_invoker = invoker;
// FIXME: 17. Set element's implicit anchor element to invoker.
// FIXME: 18. Run the popover focusing steps given element.
// 19. If shouldRestoreFocus is true and element's popover attribute is not in the no popover state
if (should_restore_focus && popover().has_value()) {
// FIXME: then set element's previously focused element to originallyFocusedElement.
}
// 20. Queue a popover toggle event task given element, "closed", and "open".
// FIXME: 24. Set element's implicit anchor element to invoker.
// FIXME: 25. Run the popover focusing steps given element.
// FIXME: 26. If shouldRestoreFocus is true and element's popover attribute is not in the no popover state, then set element's previously focused element to originallyFocusedElement.
// 27. Queue a popover toggle event task given element, "closed", and "open".
queue_a_popover_toggle_event_task("closed"_string, "open"_string);
// 21. Run cleanupShowingFlag.
// 28. Run cleanupShowingFlag.
cleanup_showing_flag();
return {};
@ -1184,7 +1208,7 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
}
};
// 7. If element's popover attribute is in the auto state, then:
// 7. If element's popover attribute is in the auto state FIXME: or the hint state, then:
if (popover().has_value() && popover().value() == "auto"sv) {
// FIXME: 7.1. Run hide all popovers until given element, focusPreviousElement, and fireEvents.
// FIXME: 7.2. If the result of running check popover validity given element, true, and throwExceptions is false, then run cleanupSteps and return.
@ -1216,20 +1240,22 @@ WebIDL::ExceptionOr<void> HTMLElement::hide_popover(FocusPreviousElement, FireEv
document.remove_an_element_from_the_top_layer_immediately(*this);
}
// 12. Set element's popover visibility state to hidden.
// FIXME: 12. Set element's opened in popover mode to null.
// 13. Set element's popover visibility state to hidden.
m_popover_visibility_state = PopoverVisibilityState::Hidden;
// 13. If fireEvents is true, then queue a popover toggle event task given element, "open", and "closed".
// 14. If fireEvents is true, then queue a popover toggle event task given element, "open", and "closed".
if (fire_events == FireEvents::Yes)
queue_a_popover_toggle_event_task("open"_string, "closed"_string);
// FIXME: 14. Let previouslyFocusedElement be element's previously focused element.
// FIXME: 15. Let previouslyFocusedElement be element's previously focused element.
// FIXME: 15. If previouslyFocusedElement is not null, then:
// FIXME: 15.1. Set element's previously focused element to null.
// FIXME: 15.2. If focusPreviousElement is true and document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of element, then run the focusing steps for previouslyFocusedElement; the viewport should not be scrolled by doing this step.
// FIXME: 16. If previouslyFocusedElement is not null, then:
// FIXME: 16.1. Set element's previously focused element to null.
// FIXME: 16.2. If focusPreviousElement is true and document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of element, then run the focusing steps for previouslyFocusedElement; the viewport should not be scrolled by doing this step.
// 16. Run cleanupSteps.
// 17. Run cleanupSteps.
cleanup_steps();
return {};