@@ -46,22 +46,22 @@ void HTMLDialogElement::visit_edges(JS::Cell::Visitor& visitor)
4646 visitor.visit (m_previously_focused_element);
4747}
4848
49+ // https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:html-element-removing-steps
4950void HTMLDialogElement::removed_from (Node* old_parent, Node& old_root)
5051{
5152 HTMLElement::removed_from (old_parent, old_root);
5253
53- // 1. If removedNode's close watcher is not null, then:
54- if (m_close_watcher) {
55- // 1.1. Destroy removedNode's close watcher.
56- m_close_watcher->destroy ();
57- // 1.2. Set removedNode's close watcher to null.
58- m_close_watcher = nullptr ;
59- }
54+ // 1. If removedNode has an open attribute, then run the dialog cleanup steps given removedNode.
55+ if (has_attribute (AttributeNames::open))
56+ run_dialog_cleanup_steps ();
6057
6158 // 2. If removedNode's node document's top layer contains removedNode, then remove an element from the top layer
6259 // immediately given removedNode.
6360 if (document ().top_layer_elements ().contains (*this ))
6461 document ().remove_an_element_from_the_top_layer_immediately (*this );
62+
63+ // 3. Set is modal of removedNode to false.
64+ set_is_modal (false );
6565}
6666
6767// https://html.spec.whatwg.org/multipage/interactive-elements.html#queue-a-dialog-toggle-event-task
@@ -115,8 +115,8 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
115115 return WebIDL::InvalidStateError::create (realm (), " Dialog already open" _utf16);
116116
117117 // 3. If the result of firing an event named beforetoggle, using ToggleEvent,
118- // with the cancelable attribute initialized to true, the oldState attribute initialized to "closed",
119- // and the newState attribute initialized to "open" at this is false, then return.
118+ // with the cancelable attribute initialized to true, the oldState attribute initialized to "closed",
119+ // and the newState attribute initialized to "open" at this is false, then return.
120120 ToggleEventInit event_init {};
121121 event_init.cancelable = true ;
122122 event_init.old_state = " closed" _string;
@@ -136,36 +136,29 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
136136 // 6. Add an open attribute to this, whose value is the empty string.
137137 set_attribute_value (AttributeNames::open, String {});
138138
139- // 7. Assert: this's node document's open dialogs list does not contain this.
140- VERIFY (!m_document->open_dialogs_list ().contains_slow (GC::Ref (*this )));
141-
142- // 8. Add this to this's node document's open dialogs list.
143- m_document->open_dialogs_list ().append (*this );
144-
145- // 9. Set the dialog close watcher with this.
146- set_close_watcher ();
147-
148- // 10. Set this's previously focused element to the focused element.
139+ // 7. Set this's previously focused element to the focused element.
149140 m_previously_focused_element = document ().focused_area ();
150141
151- // 11 . Let document be this's node document.
142+ // 8 . Let document be this's node document.
152143 auto document = m_document;
153144
154- // 12. Let hideUntil be the result of running topmost popover ancestor given this, document's showing hint popover list, null, and false.
145+ // 9. Let hideUntil be the result of running topmost popover ancestor given this, document's showing hint popover
146+ // list, null, and false.
155147 Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> hide_until = topmost_popover_ancestor (this , document->showing_hint_popover_list (), nullptr , IsPopover::No);
156148
157- // 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.
149+ // 10. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given this,
150+ // document's showing auto popover list, null, and false.
158151 if (!hide_until.get <GC::Ptr<HTMLElement>>())
159152 hide_until = topmost_popover_ancestor (this , document->showing_auto_popover_list (), nullptr , IsPopover::No);
160153
161- // 14 . If hideUntil is null, then set hideUntil to document.
154+ // 11 . If hideUntil is null, then set hideUntil to document.
162155 if (!hide_until.get <GC::Ptr<HTMLElement>>())
163156 hide_until = document;
164157
165- // 15 . Run hide all popovers until given hideUntil, false, and true.
158+ // 12 . Run hide all popovers until given hideUntil, false, and true.
166159 hide_all_popovers_until (hide_until, FocusPreviousElement::No, FireEvents::Yes);
167160
168- // 16 . Run the dialog focusing steps given this.
161+ // 13 . Run the dialog focusing steps given this.
169162 run_dialog_focusing_steps ();
170163
171164 return {};
@@ -181,7 +174,6 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
181174// https://html.spec.whatwg.org/multipage/interactive-elements.html#show-a-modal-dialog
182175WebIDL::ExceptionOr<void > HTMLDialogElement::show_a_modal_dialog (HTMLDialogElement& subject, GC::Ptr<DOM::Element> source)
183176{
184- // To show a modal dialog given a dialog element subject:
185177 auto & realm = subject.realm ();
186178
187179 // 1. If subject has an open attribute and is modal of subject is true, then return.
@@ -236,46 +228,41 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogEleme
236228 // 11. Add an open attribute to subject, whose value is the empty string.
237229 subject.set_attribute_value (AttributeNames::open, String {});
238230
239- // 12. Set is modal of subject to true .
240- subject.set_is_modal ( true );
231+ // 12. Assert: subject's close watcher is not null .
232+ VERIFY ( subject.m_close_watcher );
241233
242- // 13. Assert: subject's node document's open dialogs list does not contain subject.
243- // AD-HOC: This assertion is skipped because it fails if the open attribute was removed before calling showModal()
244- // See https://github.com/whatwg/html/issues/10953 and https://github.com/whatwg/html/pull/10954
245- // VERIFY(!subject.document().open_dialogs_list().contains_slow(GC::Ref(subject)));
246-
247- // 14. Add subject to subject's node document's open dialogs list.
248- subject.document ().open_dialogs_list ().append (subject);
234+ // 13. Set is modal of subject to true.
235+ subject.set_is_modal (true );
249236
250- // FIXME: 15 . Set subject's node document to be blocked by the modal dialog subject.
237+ // FIXME: 14 . Set subject's node document to be blocked by the modal dialog subject.
251238
252- // 16. If subject's node document's top layer does not already contain subject, then add an element to the top layer given subject.
239+ // 15. If subject's node document's top layer does not already contain subject, then add an element to the top
240+ // layer given subject.
253241 if (!subject.document ().top_layer_elements ().contains (subject))
254242 subject.document ().add_an_element_to_the_top_layer (subject);
255243
256- // 17. Set the dialog close watcher with subject.
257- subject.set_close_watcher ();
244+ // FIXME: 16. Set subject's previously focused element to the focused element.
258245
259- // FIXME: 18. Set subject's previously focused element to the focused element.
260-
261- // 19. Let document be subject's node document.
246+ // 17. Let document be subject's node document.
262247 auto & document = subject.document ();
263248
264- // 20. Let hideUntil be the result of running topmost popover ancestor given subject, document's showing hint popover list, null, and false.
249+ // 18. Let hideUntil be the result of running topmost popover ancestor given subject, document's showing hint
250+ // popover list, null, and false.
265251 Variant<GC::Ptr<HTMLElement>, GC::Ptr<DOM::Document>> hide_until = topmost_popover_ancestor (subject, document.showing_hint_popover_list (), nullptr , IsPopover::No);
266252
267- // 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.
253+ // 19. If hideUntil is null, then set hideUntil to the result of running topmost popover ancestor given subject,
254+ // document's showing auto popover list, null, and false.
268255 if (!hide_until.get <GC::Ptr<HTMLElement>>())
269256 hide_until = topmost_popover_ancestor (subject, document.showing_auto_popover_list (), nullptr , IsPopover::No);
270257
271- // 22 . If hideUntil is null, then set hideUntil to document.
258+ // 20 . If hideUntil is null, then set hideUntil to document.
272259 if (!hide_until.get <GC::Ptr<HTMLElement>>())
273260 hide_until = GC::Ptr (document);
274261
275- // 23 . Run hide all popovers until given hideUntil, false, and true.
262+ // 21 . Run hide all popovers until given hideUntil, false, and true.
276263 hide_all_popovers_until (hide_until, FocusPreviousElement::No, FireEvents::Yes);
277264
278- // 24 . Run the dialog focusing steps given subject.
265+ // 22 . Run the dialog focusing steps given subject.
279266 subject.run_dialog_focusing_steps ();
280267
281268 return {};
@@ -303,21 +290,27 @@ void HTMLDialogElement::request_close_the_dialog(Optional<String> return_value,
303290 // 1. If this does not have an open attribute, then return.
304291 if (!has_attribute (AttributeNames::open))
305292 return ;
306- // AD-HOC: 2. If this's close watcher is null, then close the dialog this with returnValue and source, and return. See https://github.com/whatwg/html/pull/10983
307- if (!m_close_watcher) {
308- close_the_dialog ( move (return_value), source);
293+
294+ // 2. If subject is not connected or subject's node document is not fully active, then return.
295+ if (! is_connected () || ! document (). is_fully_active ())
309296 return ;
310- }
311- // 3. Set dialog's enable close watcher for requestClose() to true.
297+
298+ // 3. Assert: subject's close watcher is not null.
299+ VERIFY (m_close_watcher);
300+
301+ // 4. Set subject's enable close watcher for request close to true.
312302 m_enable_close_watcher_for_request_close = true ;
313- // 4. If returnValue is not given, then set it to null.
314- // 5. Set this 's request close return value to returnValue.
303+
304+ // 5. Set subject 's request close return value to returnValue.
315305 m_request_close_return_value = return_value;
306+
316307 // 6. Set subject's request close source element to source.
317308 m_request_close_source_element = source;
318- // 6. Request to close dialog's close watcher with false.
309+
310+ // 7. Request to close dialog's close watcher with false.
319311 m_close_watcher->request_close (false );
320- // 7. Set dialog's enable close watcher for requestClose() to false.
312+
313+ // 8. Set subject's enable close watcher for request close to false.
321314 m_enable_close_watcher_for_request_close = false ;
322315}
323316
@@ -340,7 +333,8 @@ void HTMLDialogElement::close_the_dialog(Optional<String> result, GC::Ptr<DOM::E
340333 if (!has_attribute (AttributeNames::open))
341334 return ;
342335
343- // 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open", the newState attribute initialized to "closed", and the source attribute initialized to source at subject.
336+ // 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open", the
337+ // newState attribute initialized to "closed", and the source attribute initialized to source at subject.
344338 ToggleEventInit event_init {};
345339 event_init.old_state = " open" _string;
346340 event_init.new_state = " closed" _string;
@@ -358,58 +352,49 @@ void HTMLDialogElement::close_the_dialog(Optional<String> result, GC::Ptr<DOM::E
358352 // 5. Remove subject's open attribute.
359353 remove_attribute (AttributeNames::open);
360354
361- // 6. If the is modal flag of subject is true, then request an element to be removed from the top layer given subject.
355+ // 6. If is modal of subject is true, then request an element to be removed from the top layer given subject.
362356 if (m_is_modal)
363357 document ().request_an_element_to_be_remove_from_the_top_layer (*this );
364358
365359 // 7. Let wasModal be the value of subject's is modal flag.
366360 auto was_modal = m_is_modal;
367361
368- // 8. Set the is modal flag of subject to false.
362+ // 8. Set is modal of subject to false.
369363 set_is_modal (false );
370364
371- // 9. Remove subject from subject's node document's open dialogs list.
372- document ().open_dialogs_list ().remove_first_matching ([this ](auto other) { return other == this ; });
373-
374- // 10. If result is not null, then set subject's return value to result.
365+ // 9. If result is not null, then set subject's returnValue attribute to result.
375366 if (result.has_value ())
376367 set_return_value (result.release_value ());
377368
378- // 11 . Set subject's request close return value to null.
369+ // 10 . Set subject's request close return value to null.
379370 m_request_close_return_value = {};
380371
381- // 12 . Set subject's request close source element to null.
372+ // 11 . Set subject's request close source element to null.
382373 m_request_close_source_element = nullptr ;
383374
384- // 13 . If subject's previously focused element is not null, then:
375+ // 12 . If subject's previously focused element is not null, then:
385376 if (m_previously_focused_element) {
386377 // 1. Let element be subject's previously focused element.
387378 auto element = m_previously_focused_element;
388379
389380 // 2. Set subject's previously focused element to null.
390381 m_previously_focused_element = nullptr ;
391382
392- // 3. If subject's node document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of subject,
393- // or wasModal is true, then run the focusing steps for element; the viewport should not be scrolled by doing this step.
383+ // 3. If subject's node document's focused area of the document's DOM anchor is a shadow-including inclusive
384+ // descendant of subject, or wasModal is true, then run the focusing steps for element; the viewport should
385+ // not be scrolled by doing this step.
394386 auto focused_element = document ().focused_area ();
395387 auto is_focus_within_dialog = focused_element && focused_element->is_shadow_including_inclusive_descendant_of (*this );
396388 if (is_focus_within_dialog || was_modal)
397389 run_focusing_steps (element);
398390 }
399391
400- // 14. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
392+ // 13. Queue an element task on the user interaction task source given the subject element to fire an event named
393+ // close at subject.
401394 queue_an_element_task (HTML::Task::Source::UserInteraction, [this ] {
402395 auto close_event = DOM::Event::create (realm (), HTML::EventNames::close);
403396 dispatch_event (close_event);
404397 });
405-
406- // 15. If subject's close watcher is not null, then:
407- if (m_close_watcher) {
408- // 1. Destroy subject's close watcher.
409- m_close_watcher->destroy ();
410- // 2. Set subject's close watcher to null.
411- m_close_watcher = nullptr ;
412- }
413398}
414399
415400// https://html.spec.whatwg.org/multipage/interactive-elements.html#set-the-dialog-close-watcher
@@ -460,6 +445,41 @@ void HTMLDialogElement::set_close_watcher()
460445 m_close_watcher->add_event_listener_without_options (HTML::EventNames::close, DOM::IDLEventListener::create (realm (), close_callback));
461446}
462447
448+ // https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-setup-steps
449+ void HTMLDialogElement::run_dialog_setup_steps ()
450+ {
451+ // 1. Assert: subject has an open attribute.
452+ VERIFY (has_attribute (AttributeNames::open));
453+
454+ // 2. Assert: subject is connected.
455+ VERIFY (is_connected ());
456+
457+ // 3. Assert: subject's node document's open dialogs list does not contain subject.
458+ VERIFY (!m_document->open_dialogs_list ().contains_slow (GC::Ref (*this )));
459+
460+ // 4. Add subject to subject's node document's open dialogs list.
461+ m_document->open_dialogs_list ().append (*this );
462+
463+ // 5. Set the dialog close watcher with subject.
464+ set_close_watcher ();
465+ }
466+
467+ // https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-cleanup-steps
468+ void HTMLDialogElement::run_dialog_cleanup_steps ()
469+ {
470+ // 1. Remove subject from subject's node document's open dialogs list.
471+ document ().open_dialogs_list ().remove_first_matching ([this ](auto other) { return other == this ; });
472+
473+ // 2. If subject's close watcher is not null, then:
474+ if (m_close_watcher) {
475+ // 1. Destroy subject's close watcher.
476+ m_close_watcher->destroy ();
477+
478+ // 2. Set subject's close watcher to null.
479+ m_close_watcher = nullptr ;
480+ }
481+ }
482+
463483// https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-focusing-steps
464484void HTMLDialogElement::run_dialog_focusing_steps ()
465485{
@@ -532,7 +552,8 @@ void HTMLDialogElement::command_steps(DOM::Element& source, String& command)
532552 request_close_the_dialog (optional_value, source);
533553 }
534554
535- // 4. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element and source.
555+ // 4. If command is the Show Modal state and element does not have an open attribute,
556+ // then show a modal dialog given element and source.
536557 if (command == " show-modal" && !has_attribute (AttributeNames::open)) {
537558 MUST (show_a_modal_dialog (*this , source));
538559 }
@@ -545,7 +566,8 @@ GC::Ptr<HTMLDialogElement> HTMLDialogElement::nearest_clicked_dialog(UIEvents::P
545566
546567 // 1. Let target be event's target.
547568
548- // 2. If target is a dialog element, target has an open attribute, target's is modal is true, and event's clientX and clientY are outside the bounds of target, then return null.
569+ // 2. If target is a dialog element, target has an open attribute, target's is modal is true, and event's clientX
570+ // and clientY are outside the bounds of target, then return null.
549571 if (auto const * target_dialog = as_if<HTMLDialogElement>(*target); target_dialog
550572 && target_dialog->has_attribute (AttributeNames::open)
551573 && target_dialog->is_modal ()
@@ -626,4 +648,48 @@ void HTMLDialogElement::light_dismiss_open_dialogs(UIEvents::PointerEvent const&
626648 }
627649}
628650
651+ // https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:html-element-insertion-steps
652+ void HTMLDialogElement::inserted ()
653+ {
654+ Base::inserted ();
655+
656+ // 1. If insertedNode's node document is not fully active, then return.
657+ if (!document ().is_fully_active ())
658+ return ;
659+
660+ // 2. If insertedNode has an open attribute and is connected, then run the dialog setup steps given insertedNode.
661+ if (has_attribute (AttributeNames::open) && is_connected ())
662+ run_dialog_setup_steps ();
663+ }
664+
665+ // https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element:concept-element-attributes-change-ext
666+ void HTMLDialogElement::attribute_changed (FlyString const & local_name, Optional<String> const & old_value, Optional<String> const & value, Optional<FlyString> const & namespace_)
667+ {
668+ Base::attribute_changed (local_name, old_value, value, namespace_);
669+
670+ // 1. If namespace is not null, then return.
671+ if (namespace_.has_value ())
672+ return ;
673+
674+ // 2. If localName is not open, then return.
675+ if (local_name != " open" _fly_string)
676+ return ;
677+
678+ // 3. If value is null and oldValue is not null, then run the dialog cleanup steps given element.
679+ if (!value.has_value () && old_value.has_value ())
680+ run_dialog_cleanup_steps ();
681+
682+ // 4. If element's node document is not fully active, then return.
683+ if (!document ().is_fully_active ())
684+ return ;
685+
686+ // 5. If element is not connected, then return.
687+ if (!is_connected ())
688+ return ;
689+
690+ // 6. If value is not null and oldValue is null, then run the dialog setup steps given element.
691+ if (value.has_value () && !old_value.has_value ())
692+ run_dialog_setup_steps ();
693+ }
694+
629695}
0 commit comments