11/*
22 * Copyright (c) 2020, the SerenityOS developers.
3+ * Copyright (c) 2024, Sam Atkins <sam@ladybird.org>
34 *
45 * SPDX-License-Identifier: BSD-2-Clause
56 */
1314#include < LibWeb/HTML/CloseWatcher.h>
1415#include < LibWeb/HTML/Focus.h>
1516#include < LibWeb/HTML/HTMLDialogElement.h>
17+ #include < LibWeb/HTML/ToggleEvent.h>
1618
1719namespace Web ::HTML {
1820
@@ -56,23 +58,86 @@ void HTMLDialogElement::removed_from(Node* old_parent)
5658 document ().remove_an_element_from_the_top_layer_immediately (*this );
5759}
5860
61+ // https://html.spec.whatwg.org/multipage/interactive-elements.html#queue-a-dialog-toggle-event-task
62+ void HTMLDialogElement::queue_a_dialog_toggle_event_task (AK::String old_state, AK::String new_state)
63+ {
64+ // 1. If element's dialog toggle task tracker is not null, then:
65+ if (m_dialog_toggle_task_tracker.has_value ()) {
66+ // 1. Set oldState to element's dialog toggle task tracker's old state.
67+ old_state = m_dialog_toggle_task_tracker->old_state ;
68+
69+ // 2. Remove element's dialog toggle task tracker's task from its task queue.
70+ HTML::main_thread_event_loop ().task_queue ().remove_tasks_matching ([&](auto const & task) {
71+ return task.id () == m_dialog_toggle_task_tracker->task_id ;
72+ });
73+
74+ // 3. Set element's dialog toggle task tracker to null.
75+ m_dialog_toggle_task_tracker = {};
76+ }
77+
78+ // 2. Queue an element task given the DOM manipulation task source and element to run the following steps:
79+ auto task_id = queue_an_element_task (Task::Source::DOMManipulation, [this , old_state, new_state = move (new_state)]() {
80+ // 1. Fire an event named toggle at element, using ToggleEvent, with the oldState attribute initialized to
81+ // oldState and the newState attribute initialized to newState.
82+ ToggleEventInit event_init {};
83+ event_init.old_state = move (old_state);
84+ event_init.new_state = move (new_state);
85+
86+ dispatch_event (ToggleEvent::create (realm (), HTML::EventNames::toggle, move (event_init)));
87+
88+ // 2. Set element's dialog toggle task tracker to null.
89+ m_dialog_toggle_task_tracker = {};
90+ });
91+
92+ // 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.
93+ m_dialog_toggle_task_tracker = ToggleTaskTracker {
94+ .task_id = task_id,
95+ .old_state = move (old_state),
96+ };
97+ }
98+
5999// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-show
60100WebIDL::ExceptionOr<void > HTMLDialogElement::show ()
61101{
62102 // 1. If this has an open attribute and the is modal flag of this is false, then return.
63- // FIXME: Add modal flag check here when modal dialog support is added
103+ if (has_attribute (AttributeNames::open) && !m_is_modal)
104+ return {};
105+
106+ // 2. If this has an open attribute, then throw an "InvalidStateError" DOMException.
107+ if (has_attribute (AttributeNames::open))
108+ return WebIDL::InvalidStateError::create (realm (), " Dialog already open" _string);
109+
110+ // 3. If the result of firing an event named beforetoggle, using ToggleEvent,
111+ // with the cancelable attribute initialized to true, the oldState attribute initialized to "closed",
112+ // and the newState attribute initialized to "open" at this is false, then return.
113+ ToggleEventInit event_init {};
114+ event_init.cancelable = true ;
115+ event_init.old_state = " closed" _string;
116+ event_init.new_state = " open" _string;
117+
118+ auto beforetoggle_result = dispatch_event (ToggleEvent::create (realm (), HTML::EventNames::beforetoggle, move (event_init)));
119+ if (!beforetoggle_result)
120+ return {};
121+
122+ // 4. If this has an open attribute, then return.
64123 if (has_attribute (AttributeNames::open))
65124 return {};
66125
67- // FIXME: 2. If this has an open attribute, then throw an "InvalidStateError" DOMException.
126+ // 5. Queue a dialog toggle event task given subject, "closed", and "open".
127+ queue_a_dialog_toggle_event_task (" closed" _string, " open" _string);
68128
69- // 3 . Add an open attribute to this, whose value is the empty string.
129+ // 6 . Add an open attribute to this, whose value is the empty string.
70130 TRY (set_attribute (AttributeNames::open, {}));
71131
72- // FIXME 4. Set this's previously focused element to the focused element.
73- // FIXME 5. Run hide all popovers given this's node document.
132+ // FIXME: 7. Set this's previously focused element to the focused element.
133+
134+ // FIXME: 8. Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
135+
136+ // FIXME: 9. If hideUntil is null, then set hideUntil to this's node document.
74137
75- // 6. Run the dialog focusing steps given this.
138+ // FIXME: 10. Run hide all popovers given this's node document.
139+
140+ // 11. Run the dialog focusing steps given this.
76141 run_dialog_focusing_steps ();
77142
78143 return {};
@@ -99,19 +164,44 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
99164
100165 // FIXME: 5. If this is in the popover showing state, then throw an "InvalidStateError" DOMException.
101166
102- // 6. Add an open attribute to this, whose value is the empty string.
167+ // 6. If the result of firing an event named beforetoggle, using ToggleEvent,
168+ // with the cancelable attribute initialized to true, the oldState attribute initialized to "closed",
169+ // and the newState attribute initialized to "open" at this is false, then return.
170+ ToggleEventInit event_init {};
171+ event_init.cancelable = true ;
172+ event_init.old_state = " closed" _string;
173+ event_init.new_state = " open" _string;
174+
175+ auto beforetoggle_result = dispatch_event (ToggleEvent::create (realm (), HTML::EventNames::beforetoggle, move (event_init)));
176+ if (!beforetoggle_result)
177+ return {};
178+
179+ // 7. If this has an open attribute, then return.
180+ if (has_attribute (AttributeNames::open))
181+ return {};
182+
183+ // 8. If this is not connected, then return.
184+ if (!is_connected ())
185+ return {};
186+
187+ // FIXME: 9. If this is in the popover showing state, then return.
188+
189+ // 10. Queue a dialog toggle event task given subject, "closed", and "open".
190+ queue_a_dialog_toggle_event_task (" closed" _string, " open" _string);
191+
192+ // 11. Add an open attribute to this, whose value is the empty string.
103193 TRY (set_attribute (AttributeNames::open, {}));
104194
105- // 7 . Set the is modal flag of this to true.
195+ // 12 . Set the is modal flag of this to true.
106196 m_is_modal = true ;
107197
108- // FIXME: 8 . Let this's node document be blocked by the modal dialog this.
198+ // FIXME: 13 . Let this's node document be blocked by the modal dialog this.
109199
110- // 9 . If this's node document's top layer does not already contain this, then add an element to the top layer given this.
200+ // 14 . If this's node document's top layer does not already contain this, then add an element to the top layer given this.
111201 if (!document ().top_layer_elements ().contains (*this ))
112202 document ().add_an_element_to_the_top_layer (*this );
113203
114- // 10 . Set this's close watcher to the result of establishing a close watcher given this's relevant global object
204+ // 15 . Set this's close watcher to the result of establishing a close watcher given this's relevant global object, with:
115205 m_close_watcher = CloseWatcher::establish (*document ().window ());
116206 // - cancelAction given canPreventClose being to return the result of firing an event named cancel at this, with the cancelable attribute initialized to canPreventClose.
117207 auto cancel_callback_function = JS::NativeFunction::create (
@@ -137,15 +227,16 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
137227 auto close_callback = realm ().heap ().allocate_without_realm <WebIDL::CallbackType>(*close_callback_function, Bindings::principal_host_defined_environment_settings_object (realm ()));
138228 m_close_watcher->add_event_listener_without_options (HTML::EventNames::close, DOM::IDLEventListener::create (realm (), close_callback));
139229
140- // FIXME: 11 . Set this's previously focused element to the focused element.
230+ // FIXME: 16 . Set this's previously focused element to the focused element.
141231
142- // FIXME: 12 . Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
232+ // FIXME: 17 . Let hideUntil be the result of running topmost popover ancestor given this, null, and false.
143233
144- // FIXME: 13 . If hideUntil is null, then set hideUntil to this's node document.
234+ // FIXME: 18 . If hideUntil is null, then set hideUntil to this's node document.
145235
146- // FIXME: 14 . Run hide all popovers until given hideUntil, false, and true.
236+ // FIXME: 19 . Run hide all popovers until given hideUntil, false, and true.
147237
148- // FIXME: 15. Run the dialog focusing steps given this.
238+ // 20. Run the dialog focusing steps given this.
239+ run_dialog_focusing_steps ();
149240
150241 return {};
151242}
@@ -177,34 +268,49 @@ void HTMLDialogElement::close_the_dialog(Optional<String> result)
177268 if (!has_attribute (AttributeNames::open))
178269 return ;
179270
180- // 2. Remove subject's open attribute.
271+ // 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at subject.
272+ ToggleEventInit event_init {};
273+ event_init.old_state = " open" _string;
274+ event_init.new_state = " closed" _string;
275+
276+ dispatch_event (ToggleEvent::create (realm (), HTML::EventNames::beforetoggle, move (event_init)));
277+
278+ // 3. If subject does not have an open attribute, then return.
279+ if (!has_attribute (AttributeNames::open))
280+ return ;
281+
282+ // 4. Queue a dialog toggle event task given subject, "open", and "closed".
283+ queue_a_dialog_toggle_event_task (" open" _string, " closed" _string);
284+
285+ // 5. Remove subject's open attribute.
181286 remove_attribute (AttributeNames::open);
182287
183- // 3 . If the is modal flag of subject is true, then request an element to be removed from the top layer given subject.
288+ // 6 . If the is modal flag of subject is true, then request an element to be removed from the top layer given subject.
184289 if (m_is_modal)
185290 document ().request_an_element_to_be_remove_from_the_top_layer (*this );
186- // FIXME: 4. Let wasModal be the value of subject's is modal flag.
187291
188- // 5. Set the is modal flag of subject to false.
292+ // FIXME: 7. Let wasModal be the value of subject's is modal flag.
293+
294+ // 8. Set the is modal flag of subject to false.
189295 m_is_modal = false ;
190296
191- // 6 . If result is not null, then set the returnValue attribute to result.
297+ // 9 . If result is not null, then set the returnValue attribute to result.
192298 if (result.has_value ())
193299 set_return_value (result.release_value ());
194300
195- // FIXME: 7 . If subject's previously focused element is not null, then:
301+ // FIXME: 10 . If subject's previously focused element is not null, then:
196302 // 1. Let element be subject's previously focused element.
197303 // 2. Set subject's previously focused element to null.
198304 // 3. If subject's node document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of element,
199305 // or wasModal is true, then run the focusing steps for element; the viewport should not be scrolled by doing this step.
200306
201- // 8 . Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
307+ // 11 . Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
202308 queue_an_element_task (HTML::Task::Source::UserInteraction, [this ] {
203309 auto close_event = DOM::Event::create (realm (), HTML::EventNames::close);
204310 dispatch_event (close_event);
205311 });
206312
207- // 9 . If subject's close watcher is not null, then:
313+ // 12 . If subject's close watcher is not null, then:
208314 if (m_close_watcher) {
209315 // 9.1 Destroy subject's close watcher.
210316 m_close_watcher->destroy ();
0 commit comments