2020-08-01 03:04:26 +01:00
/*
2021-04-28 22:46:44 +02:00
* Copyright ( c ) 2020 , the SerenityOS developers .
2024-11-08 11:51:45 +00:00
* Copyright ( c ) 2024 , Sam Atkins < sam @ ladybird . org >
2020-08-01 03:04:26 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-08-01 03:04:26 +01:00
*/
2024-06-22 23:09:51 +01:00
# include <LibJS/Runtime/NativeFunction.h>
2024-04-27 12:09:58 +12:00
# include <LibWeb/Bindings/HTMLDialogElementPrototype.h>
2022-09-30 17:16:16 -06:00
# include <LibWeb/Bindings/Intrinsics.h>
2024-11-08 04:03:55 +13:00
# include <LibWeb/Bindings/PrincipalHostDefined.h>
2024-03-28 16:53:27 +01:00
# include <LibWeb/DOM/Document.h>
2024-02-13 22:37:16 +00:00
# include <LibWeb/DOM/Event.h>
2024-06-22 23:09:51 +01:00
# include <LibWeb/DOM/IDLEventListener.h>
# include <LibWeb/HTML/CloseWatcher.h>
2024-02-13 22:37:16 +00:00
# include <LibWeb/HTML/Focus.h>
2020-08-01 03:04:26 +01:00
# include <LibWeb/HTML/HTMLDialogElement.h>
2024-11-08 11:51:45 +00:00
# include <LibWeb/HTML/ToggleEvent.h>
2020-08-01 03:04:26 +01:00
namespace Web : : HTML {
2023-11-19 19:47:52 +01:00
JS_DEFINE_ALLOCATOR ( HTMLDialogElement ) ;
2022-02-18 21:00:52 +01:00
HTMLDialogElement : : HTMLDialogElement ( DOM : : Document & document , DOM : : QualifiedName qualified_name )
2021-02-07 11:20:15 +01:00
: HTMLElement ( document , move ( qualified_name ) )
2020-08-01 03:04:26 +01:00
{
}
2022-03-14 13:21:51 -06:00
HTMLDialogElement : : ~ HTMLDialogElement ( ) = default ;
2023-01-10 06:28:20 -05:00
2023-08-07 08:41:28 +02:00
void HTMLDialogElement : : initialize ( JS : : Realm & realm )
2023-01-10 06:28:20 -05:00
{
2023-08-07 08:41:28 +02:00
Base : : initialize ( realm ) ;
2024-03-16 13:13:08 +01:00
WEB_SET_PROTOTYPE_FOR_INTERFACE ( HTMLDialogElement ) ;
2023-01-10 06:28:20 -05:00
}
2024-06-22 23:09:51 +01:00
void HTMLDialogElement : : visit_edges ( JS : : Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
visitor . visit ( m_close_watcher ) ;
}
2024-03-28 16:53:27 +01:00
void HTMLDialogElement : : removed_from ( Node * old_parent )
{
HTMLElement : : removed_from ( old_parent ) ;
2024-06-22 23:09:51 +01:00
// 1. If removedNode's close watcher is not null, then:
if ( m_close_watcher ) {
// 1.1. Destroy removedNode's close watcher.
m_close_watcher - > destroy ( ) ;
// 1.2. Set removedNode's close watcher to null.
m_close_watcher = nullptr ;
}
2024-03-28 16:53:27 +01:00
// 2. If removedNode's node document's top layer contains removedNode, then remove an element from the top layer
// immediately given removedNode.
if ( document ( ) . top_layer_elements ( ) . contains ( * this ) )
document ( ) . remove_an_element_from_the_top_layer_immediately ( * this ) ;
}
2024-11-08 11:51:45 +00:00
// https://html.spec.whatwg.org/multipage/interactive-elements.html#queue-a-dialog-toggle-event-task
void HTMLDialogElement : : queue_a_dialog_toggle_event_task ( AK : : String old_state , AK : : String new_state )
{
// 1. If element's dialog toggle task tracker is not null, then:
if ( m_dialog_toggle_task_tracker . has_value ( ) ) {
// 1. Set oldState to element's dialog toggle task tracker's old state.
old_state = m_dialog_toggle_task_tracker - > old_state ;
// 2. Remove element's dialog toggle task tracker's task from its task queue.
HTML : : main_thread_event_loop ( ) . task_queue ( ) . remove_tasks_matching ( [ & ] ( auto const & task ) {
return task . id ( ) = = m_dialog_toggle_task_tracker - > task_id ;
} ) ;
// 3. Set element's dialog toggle task tracker to null.
m_dialog_toggle_task_tracker = { } ;
}
// 2. Queue an element task given the DOM manipulation task source and element to run the following steps:
auto task_id = queue_an_element_task ( Task : : Source : : DOMManipulation , [ this , old_state , new_state = move ( new_state ) ] ( ) {
// 1. Fire an event named toggle at element, using ToggleEvent, with the oldState attribute initialized to
// oldState and the newState attribute initialized to newState.
ToggleEventInit event_init { } ;
event_init . old_state = move ( old_state ) ;
event_init . new_state = move ( new_state ) ;
dispatch_event ( ToggleEvent : : create ( realm ( ) , HTML : : EventNames : : toggle , move ( event_init ) ) ) ;
// 2. Set element's dialog toggle task tracker to null.
m_dialog_toggle_task_tracker = { } ;
} ) ;
// 3. Set element's dialog toggle task tracker to a struct with task set to the just-queued task and old state set to oldState.
m_dialog_toggle_task_tracker = ToggleTaskTracker {
. task_id = task_id ,
. old_state = move ( old_state ) ,
} ;
}
2023-09-02 19:23:04 +02:00
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-show
2024-02-13 22:37:16 +00:00
WebIDL : : ExceptionOr < void > HTMLDialogElement : : show ( )
2023-09-02 19:23:04 +02:00
{
2024-02-13 22:37:16 +00:00
// 1. If this has an open attribute and the is modal flag of this is false, then return.
2024-11-08 11:51:45 +00:00
if ( has_attribute ( AttributeNames : : open ) & & ! m_is_modal )
return { } ;
// 2. If this has an open attribute, then throw an "InvalidStateError" DOMException.
if ( has_attribute ( AttributeNames : : open ) )
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Dialog already open " _string ) ;
// 3. 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 this is false, then return.
ToggleEventInit event_init { } ;
event_init . cancelable = true ;
event_init . old_state = " closed " _string ;
event_init . new_state = " open " _string ;
auto beforetoggle_result = dispatch_event ( ToggleEvent : : create ( realm ( ) , HTML : : EventNames : : beforetoggle , move ( event_init ) ) ) ;
if ( ! beforetoggle_result )
return { } ;
// 4. If this has an open attribute, then return.
2024-02-13 22:37:16 +00:00
if ( has_attribute ( AttributeNames : : open ) )
return { } ;
2024-11-08 11:51:45 +00:00
// 5. Queue a dialog toggle event task given subject, "closed", and "open".
queue_a_dialog_toggle_event_task ( " closed " _string , " open " _string ) ;
2024-02-13 22:37:16 +00:00
2024-11-08 11:51:45 +00:00
// 6. Add an open attribute to this, whose value is the empty string.
2024-02-13 22:37:16 +00:00
TRY ( set_attribute ( AttributeNames : : open , { } ) ) ;
2024-11-08 11:51:45 +00:00
// FIXME: 7. Set this's previously focused element to the focused element.
// 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.
2024-02-13 22:37:16 +00:00
2024-11-08 11:51:45 +00:00
// FIXME: 10. Run hide all popovers given this's node document.
// 11. Run the dialog focusing steps given this.
2024-02-13 22:37:16 +00:00
run_dialog_focusing_steps ( ) ;
return { } ;
2023-09-02 19:23:04 +02:00
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-showmodal
2024-03-28 16:53:27 +01:00
WebIDL : : ExceptionOr < void > HTMLDialogElement : : show_modal ( )
2023-09-02 19:23:04 +02:00
{
2024-03-28 16:53:27 +01:00
// 1. If this has an open attribute and the is modal flag of this is true, then return.
if ( has_attribute ( AttributeNames : : open ) & & m_is_modal )
return { } ;
// 2. If this has an open attribute, then throw an "InvalidStateError" DOMException.
if ( has_attribute ( AttributeNames : : open ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Dialog already open " _string ) ;
2024-03-28 16:53:27 +01:00
2024-10-31 11:19:14 +00:00
// 3. If this's node document is not fully active, then throw an "InvalidStateError" DOMException.
if ( ! document ( ) . is_fully_active ( ) )
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Document is not fully active " _string ) ;
// 4. If this is not connected, then throw an "InvalidStateError" DOMException.
2024-03-28 16:53:27 +01:00
if ( ! is_connected ( ) )
2024-10-12 20:56:21 +02:00
return WebIDL : : InvalidStateError : : create ( realm ( ) , " Dialog not connected " _string ) ;
2024-03-28 16:53:27 +01:00
2024-10-31 11:19:14 +00:00
// FIXME: 5. If this is in the popover showing state, then throw an "InvalidStateError" DOMException.
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// 6. 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 this is false, then return.
ToggleEventInit event_init { } ;
event_init . cancelable = true ;
event_init . old_state = " closed " _string ;
event_init . new_state = " open " _string ;
auto beforetoggle_result = dispatch_event ( ToggleEvent : : create ( realm ( ) , HTML : : EventNames : : beforetoggle , move ( event_init ) ) ) ;
if ( ! beforetoggle_result )
return { } ;
// 7. If this has an open attribute, then return.
if ( has_attribute ( AttributeNames : : open ) )
return { } ;
// 8. If this is not connected, then return.
if ( ! is_connected ( ) )
return { } ;
// FIXME: 9. If this is in the popover showing state, then return.
// 10. Queue a dialog toggle event task given subject, "closed", and "open".
queue_a_dialog_toggle_event_task ( " closed " _string , " open " _string ) ;
// 11. Add an open attribute to this, whose value is the empty string.
2024-03-28 16:53:27 +01:00
TRY ( set_attribute ( AttributeNames : : open , { } ) ) ;
2024-11-08 11:51:45 +00:00
// 12. Set the is modal flag of this to true.
2024-03-28 16:53:27 +01:00
m_is_modal = true ;
2024-11-08 11:51:45 +00:00
// FIXME: 13. Let this's node document be blocked by the modal dialog this.
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// 14. If this's node document's top layer does not already contain this, then add an element to the top layer given this.
2024-03-28 16:53:27 +01:00
if ( ! document ( ) . top_layer_elements ( ) . contains ( * this ) )
document ( ) . add_an_element_to_the_top_layer ( * this ) ;
2024-11-08 11:51:45 +00:00
// 15. Set this's close watcher to the result of establishing a close watcher given this's relevant global object, with:
2024-06-22 23:09:51 +01:00
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 = verify_cast < 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 ( ) ) ;
2024-10-26 21:02:28 +13:00
auto cancel_callback = realm ( ) . heap ( ) . allocate_without_realm < WebIDL : : CallbackType > ( * cancel_callback_function , Bindings : : principal_host_defined_environment_settings_object ( realm ( ) ) ) ;
2024-06-22 23:09:51 +01:00
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 ( { } ) ;
return JS : : js_undefined ( ) ;
} ,
0 , " " , & realm ( ) ) ;
2024-10-26 21:02:28 +13:00
auto close_callback = realm ( ) . heap ( ) . allocate_without_realm < WebIDL : : CallbackType > ( * close_callback_function , Bindings : : principal_host_defined_environment_settings_object ( realm ( ) ) ) ;
2024-06-22 23:09:51 +01:00
m_close_watcher - > add_event_listener_without_options ( HTML : : EventNames : : close , DOM : : IDLEventListener : : create ( realm ( ) , close_callback ) ) ;
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// FIXME: 16. Set this's previously focused element to the focused element.
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// FIXME: 17. Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// FIXME: 18. If hideUntil is null, then set hideUntil to this's node document.
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// FIXME: 19. Run hide all popovers until given hideUntil, false, and true.
2024-03-28 16:53:27 +01:00
2024-11-08 11:51:45 +00:00
// 20. Run the dialog focusing steps given this.
run_dialog_focusing_steps ( ) ;
2024-03-28 16:53:27 +01:00
return { } ;
2023-09-02 19:23:04 +02:00
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-close
2024-02-13 22:37:16 +00:00
void HTMLDialogElement : : close ( Optional < String > return_value )
2023-09-02 19:23:04 +02:00
{
2024-02-13 22:37:16 +00:00
// 1. If returnValue is not given, then set it to null.
// 2. Close the dialog this with returnValue.
close_the_dialog ( move ( return_value ) ) ;
2023-09-02 19:23:04 +02:00
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-returnvalue
String HTMLDialogElement : : return_value ( ) const
{
return m_return_value ;
}
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-returnvalue
void HTMLDialogElement : : set_return_value ( String return_value )
{
m_return_value = move ( return_value ) ;
}
2024-02-13 22:37:16 +00:00
// https://html.spec.whatwg.org/multipage/interactive-elements.html#close-the-dialog
void HTMLDialogElement : : close_the_dialog ( Optional < String > result )
{
// 1. If subject does not have an open attribute, then return.
if ( ! has_attribute ( AttributeNames : : open ) )
return ;
2024-11-08 11:51:45 +00:00
// 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at subject.
ToggleEventInit event_init { } ;
event_init . old_state = " open " _string ;
event_init . new_state = " closed " _string ;
dispatch_event ( ToggleEvent : : create ( realm ( ) , HTML : : EventNames : : beforetoggle , move ( event_init ) ) ) ;
// 3. If subject does not have an open attribute, then return.
if ( ! has_attribute ( AttributeNames : : open ) )
return ;
// 4. Queue a dialog toggle event task given subject, "open", and "closed".
queue_a_dialog_toggle_event_task ( " open " _string , " closed " _string ) ;
// 5. Remove subject's open attribute.
2024-02-13 22:37:16 +00:00
remove_attribute ( AttributeNames : : open ) ;
2024-11-08 11:51:45 +00:00
// 6. If the is modal flag of subject is true, then request an element to be removed from the top layer given subject.
2024-03-28 16:53:27 +01:00
if ( m_is_modal )
document ( ) . request_an_element_to_be_remove_from_the_top_layer ( * this ) ;
2024-11-08 11:51:45 +00:00
// FIXME: 7. Let wasModal be the value of subject's is modal flag.
// 8. Set the is modal flag of subject to false.
2024-03-28 16:53:27 +01:00
m_is_modal = false ;
2024-02-13 22:37:16 +00:00
2024-11-08 11:51:45 +00:00
// 9. If result is not null, then set the returnValue attribute to result.
2024-02-13 22:37:16 +00:00
if ( result . has_value ( ) )
set_return_value ( result . release_value ( ) ) ;
2024-11-08 11:51:45 +00:00
// FIXME: 10. If subject's previously focused element is not null, then:
2024-02-13 22:37:16 +00:00
// 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.
2024-11-08 11:51:45 +00:00
// 11. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
2024-02-13 22:37:16 +00:00
queue_an_element_task ( HTML : : Task : : Source : : UserInteraction , [ this ] {
auto close_event = DOM : : Event : : create ( realm ( ) , HTML : : EventNames : : close ) ;
dispatch_event ( close_event ) ;
} ) ;
2024-11-08 11:51:45 +00:00
// 12. If subject's close watcher is not null, then:
2024-06-22 23:09:51 +01:00
if ( m_close_watcher ) {
// 9.1 Destroy subject's close watcher.
m_close_watcher - > destroy ( ) ;
// 9.2 Set subject's close watcher to null.
m_close_watcher = nullptr ;
}
2024-02-13 22:37:16 +00:00
}
2024-02-13 22:37:16 +00:00
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps
void HTMLDialogElement : : run_dialog_focusing_steps ( )
{
// 1. Let control be null
JS : : GCPtr < Element > control = nullptr ;
// FIXME 2. If subject has the autofocus attribute, then set control to subject.
// FIXME 3. If control is null, then set control to the focus delegate of subject.
// 4. If control is null, then set control to subject.
if ( ! control )
control = this ;
// 5. Run the focusing steps for control.
run_focusing_steps ( control ) ;
}
2020-08-01 03:04:26 +01:00
}