Skip to content

Commit 0de9bbf

Browse files
committed
LibWeb: Implement the ToggleEvent.source attribute
See: whatwg/html#11186
1 parent 3b68fd0 commit 0de9bbf

File tree

13 files changed

+295
-86
lines changed

13 files changed

+295
-86
lines changed

Libraries/LibWeb/HTML/HTMLButtonElement.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,9 +223,9 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event)
223223
// 9. If command is in the Hide Popover state:
224224
if (command == "hide-popover") {
225225
// 1. If the result of running check popover validity given target, true, false, and null is true,
226-
// then run the hide popover algorithm given target, true, true, and false.
226+
// then run the hide popover algorithm given target, true, true, false, and element.
227227
if (MUST(target->check_popover_validity(ExpectedToBeShowing::Yes, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
228-
MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No));
228+
MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No, this));
229229
}
230230
}
231231

@@ -238,9 +238,9 @@ void HTMLButtonElement::activation_behavior(DOM::Event const& event)
238238
}
239239

240240
// 2. Otheriwse, if the result of running check popover validity given target, true, false, and null is true,
241-
// then run the hide popover algorithm given target, true, true, and false.
241+
// then run the hide popover algorithm given target, true, true, false and element.
242242
else if (MUST(target->check_popover_validity(ExpectedToBeShowing::Yes, ThrowExceptions::No, nullptr, IgnoreDomState::No))) {
243-
MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No));
243+
MUST(target->hide_popover(FocusPreviousElement::Yes, FireEvents::Yes, ThrowExceptions::No, IgnoreDomState::No, this));
244244
}
245245
}
246246

Libraries/LibWeb/HTML/HTMLDialogElement.cpp

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ void HTMLDialogElement::visit_edges(JS::Cell::Visitor& visitor)
4242
Base::visit_edges(visitor);
4343

4444
visitor.visit(m_close_watcher);
45+
visitor.visit(m_request_close_source_element);
4546
}
4647

4748
void HTMLDialogElement::removed_from(Node* old_parent, Node& old_root)
@@ -63,7 +64,7 @@ void HTMLDialogElement::removed_from(Node* old_parent, Node& old_root)
6364
}
6465

6566
// https://html.spec.whatwg.org/multipage/interactive-elements.html#queue-a-dialog-toggle-event-task
66-
void HTMLDialogElement::queue_a_dialog_toggle_event_task(AK::String old_state, AK::String new_state)
67+
void HTMLDialogElement::queue_a_dialog_toggle_event_task(AK::String old_state, AK::String new_state, GC::Ptr<DOM::Element> source)
6768
{
6869
// 1. If element's dialog toggle task tracker is not null, then:
6970
if (m_dialog_toggle_task_tracker.has_value()) {
@@ -80,14 +81,14 @@ void HTMLDialogElement::queue_a_dialog_toggle_event_task(AK::String old_state, A
8081
}
8182

8283
// 2. Queue an element task given the DOM manipulation task source and element to run the following steps:
83-
auto task_id = queue_an_element_task(Task::Source::DOMManipulation, [this, old_state, new_state = move(new_state)]() {
84+
auto task_id = queue_an_element_task(Task::Source::DOMManipulation, [this, old_state, new_state = move(new_state), source]() {
8485
// 1. Fire an event named toggle at element, using ToggleEvent, with the oldState attribute initialized to
85-
// oldState and the newState attribute initialized to newState.
86+
// oldState, the newState attribute initialized to newState, and the source attribute initialized to source.
8687
ToggleEventInit event_init {};
8788
event_init.old_state = move(old_state);
8889
event_init.new_state = move(new_state);
8990

90-
dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::toggle, move(event_init)));
91+
dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::toggle, move(event_init), source));
9192

9293
// 2. Set element's dialog toggle task tracker to null.
9394
m_dialog_toggle_task_tracker = {};
@@ -127,8 +128,8 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
127128
if (has_attribute(AttributeNames::open))
128129
return {};
129130

130-
// 5. Queue a dialog toggle event task given this, "closed", and "open".
131-
queue_a_dialog_toggle_event_task("closed"_string, "open"_string);
131+
// 5. Queue a dialog toggle event task given this, "closed", "open", and null.
132+
queue_a_dialog_toggle_event_task("closed"_string, "open"_string, nullptr);
132133

133134
// 6. Add an open attribute to this, whose value is the empty string.
134135
TRY(set_attribute(AttributeNames::open, {}));
@@ -169,11 +170,11 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show()
169170
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-showmodal
170171
WebIDL::ExceptionOr<void> HTMLDialogElement::show_modal()
171172
{
172-
// The showModal() method steps are to show a modal dialog given this.
173-
return show_a_modal_dialog(*this);
173+
// The showModal() method steps are to show a modal dialog given this and null.
174+
return show_a_modal_dialog(*this, nullptr);
174175
}
175176

176-
WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogElement& subject)
177+
WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogElement& subject, GC::Ptr<DOM::Element> source)
177178
{
178179
// To show a modal dialog given a dialog element subject:
179180
auto& realm = subject.realm();
@@ -200,13 +201,13 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogEleme
200201

201202
// 6. If the result of firing an event named beforetoggle, using ToggleEvent,
202203
// with the cancelable attribute initialized to true, the oldState attribute initialized to "closed",
203-
// and the newState attribute initialized to "open" at subject is false, then return.
204+
// the newState attribute initialized to "open", and the source attribute initialized to source at subject is false, then return.
204205
ToggleEventInit event_init {};
205206
event_init.cancelable = true;
206207
event_init.old_state = "closed"_string;
207208
event_init.new_state = "open"_string;
208209

209-
auto beforetoggle_result = subject.dispatch_event(ToggleEvent::create(realm, EventNames::beforetoggle, move(event_init)));
210+
auto beforetoggle_result = subject.dispatch_event(ToggleEvent::create(realm, EventNames::beforetoggle, move(event_init), source));
210211
if (!beforetoggle_result)
211212
return {};
212213

@@ -222,8 +223,8 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogEleme
222223
if (subject.popover_visibility_state() == PopoverVisibilityState::Showing)
223224
return {};
224225

225-
// 10. Queue a dialog toggle event task given subject, "closed", and "open".
226-
subject.queue_a_dialog_toggle_event_task("closed"_string, "open"_string);
226+
// 10. Queue a dialog toggle event task given subject, "closed", "open", and source.
227+
subject.queue_a_dialog_toggle_event_task("closed"_string, "open"_string, source);
227228

228229
// 11. Add an open attribute to subject, whose value is the empty string.
229230
TRY(subject.set_attribute(AttributeNames::open, {}));
@@ -277,19 +278,24 @@ WebIDL::ExceptionOr<void> HTMLDialogElement::show_a_modal_dialog(HTMLDialogEleme
277278
void HTMLDialogElement::close(Optional<String> return_value)
278279
{
279280
// 1. If returnValue is not given, then set it to null.
280-
// 2. Close the dialog this with returnValue.
281-
close_the_dialog(move(return_value));
281+
// 2. Close the dialog this with returnValue and null.
282+
close_the_dialog(move(return_value), nullptr);
282283
}
283284

284285
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dom-dialog-requestclose
285286
void HTMLDialogElement::request_close(Optional<String> return_value)
286287
{
288+
// ADHOC: This method is an amalgamation of the requestClose() and "request to close" algorithms from the spec.
289+
// FIXME: Once the "request-close" command is implemented, this will need to be split into two methods.
290+
// For now this implementation is only used for the requestClose() method, which sets source to null.
291+
auto source = nullptr;
292+
287293
// 1. If this does not have an open attribute, then return.
288294
if (!has_attribute(AttributeNames::open))
289295
return;
290-
// ADHOC: 2. If this's close watcher is null, then close the dialog this with returnValue, and return. See https://github.com/whatwg/html/pull/10983
296+
// ADHOC: 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
291297
if (!m_close_watcher) {
292-
close_the_dialog(move(return_value));
298+
close_the_dialog(move(return_value), source);
293299
return;
294300
}
295301
// 3. Set dialog's enable close watcher for requestClose() to true.
@@ -298,6 +304,8 @@ void HTMLDialogElement::request_close(Optional<String> return_value)
298304
// 4. If returnValue is not given, then set it to null.
299305
// 5. Set this's request close return value to returnValue.
300306
m_request_close_return_value = return_value;
307+
// 6. Set subject's request close source element to source.
308+
m_request_close_source_element = source;
301309
// 6. Request to close dialog's close watcher with false.
302310
m_close_watcher->request_close(false);
303311
// 7. Set dialog's enable close watcher for requestClose() to false.
@@ -320,25 +328,25 @@ void HTMLDialogElement::set_return_value(String return_value)
320328
}
321329

322330
// https://html.spec.whatwg.org/multipage/interactive-elements.html#close-the-dialog
323-
void HTMLDialogElement::close_the_dialog(Optional<String> result)
331+
void HTMLDialogElement::close_the_dialog(Optional<String> result, GC::Ptr<DOM::Element> source)
324332
{
325333
// 1. If subject does not have an open attribute, then return.
326334
if (!has_attribute(AttributeNames::open))
327335
return;
328336

329-
// 2. Fire an event named beforetoggle, using ToggleEvent, with the oldState attribute initialized to "open" and the newState attribute initialized to "closed" at subject.
337+
// 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.
330338
ToggleEventInit event_init {};
331339
event_init.old_state = "open"_string;
332340
event_init.new_state = "closed"_string;
333341

334-
dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init)));
342+
dispatch_event(ToggleEvent::create(realm(), HTML::EventNames::beforetoggle, move(event_init), source));
335343

336344
// 3. If subject does not have an open attribute, then return.
337345
if (!has_attribute(AttributeNames::open))
338346
return;
339347

340-
// 4. Queue a dialog toggle event task given subject, "open", and "closed".
341-
queue_a_dialog_toggle_event_task("open"_string, "closed"_string);
348+
// 4. Queue a dialog toggle event task given subject, "open", "closed", and source.
349+
queue_a_dialog_toggle_event_task("open"_string, "closed"_string, source);
342350

343351
// 5. Remove subject's open attribute.
344352
remove_attribute(AttributeNames::open);
@@ -355,30 +363,33 @@ void HTMLDialogElement::close_the_dialog(Optional<String> result)
355363
// 9. Remove subject from subject's node document's open dialogs list.
356364
document().open_dialogs_list().remove_first_matching([this](auto other) { return other == this; });
357365

358-
// 10. If result is not null, then set the returnValue attribute to result.
366+
// 10. If result is not null, then set subject's return value to result.
359367
if (result.has_value())
360368
set_return_value(result.release_value());
361369

362-
// 11. Set the request close return value to null.
370+
// 11. Set subject's request close return value to null.
363371
m_request_close_return_value = {};
364372

365-
// FIXME: 12. If subject's previously focused element is not null, then:
373+
// 12. Set subject's request close source element to null.
374+
m_request_close_source_element = nullptr;
375+
376+
// FIXME: 13. If subject's previously focused element is not null, then:
366377
// 1. Let element be subject's previously focused element.
367378
// 2. Set subject's previously focused element to null.
368379
// 3. If subject's node document's focused area of the document's DOM anchor is a shadow-including inclusive descendant of subject,
369380
// or wasModal is true, then run the focusing steps for element; the viewport should not be scrolled by doing this step.
370381

371-
// 13. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
382+
// 14. Queue an element task on the user interaction task source given the subject element to fire an event named close at subject.
372383
queue_an_element_task(HTML::Task::Source::UserInteraction, [this] {
373384
auto close_event = DOM::Event::create(realm(), HTML::EventNames::close);
374385
dispatch_event(close_event);
375386
});
376387

377-
// 14. If subject's close watcher is not null, then:
388+
// 15. If subject's close watcher is not null, then:
378389
if (m_close_watcher) {
379-
// 14.1 Destroy subject's close watcher.
390+
// 15.1 Destroy subject's close watcher.
380391
m_close_watcher->destroy();
381-
// 14.2 Set subject's close watcher to null.
392+
// 15.2 Set subject's close watcher to null.
382393
m_close_watcher = nullptr;
383394
}
384395
}
@@ -401,10 +412,10 @@ void HTMLDialogElement::set_close_watcher()
401412
0, ""_fly_string, &realm());
402413
auto cancel_callback = realm().heap().allocate<WebIDL::CallbackType>(*cancel_callback_function, realm());
403414
m_close_watcher->add_event_listener_without_options(HTML::EventNames::cancel, DOM::IDLEventListener::create(realm(), cancel_callback));
404-
// - closeAction being to close the dialog given dialog and dialog's request close return value.
415+
// - closeAction being to close the dialog given dialog, dialog's request close return value, and dialog's request close source element.
405416
auto close_callback_function = JS::NativeFunction::create(
406417
realm(), [this](JS::VM&) {
407-
close_the_dialog(m_request_close_return_value);
418+
close_the_dialog(m_request_close_return_value, m_request_close_source_element);
408419

409420
return JS::js_undefined();
410421
},
@@ -475,19 +486,17 @@ void HTMLDialogElement::invoker_command_steps(DOM::Element& invoker, String& com
475486
return;
476487
}
477488

478-
// 2. If command is in the Close state and element has an open attribute:
489+
// 2. If command is in the Close state and element has an open attribute,
490+
// then close the dialog given element with invoker's optional value and invoker.
479491
if (command == "close" && has_attribute(AttributeNames::open)) {
480-
// 1. Let value be invoker's value.
481-
// FIXME: This assumes invoker is a button.
482-
auto value = invoker.get_attribute(AttributeNames::value);
483-
484-
// 2. Close the dialog element with value.
485-
close_the_dialog(value);
492+
// FIXME: This assumes invoker is a button.
493+
auto optional_value = invoker.get_attribute(AttributeNames::value);
494+
close_the_dialog(optional_value, invoker);
486495
}
487496

488-
// 3. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element.
497+
// 3. If command is the Show Modal state and element does not have an open attribute, then show a modal dialog given element and invoker.
489498
if (command == "show-modal" && !has_attribute(AttributeNames::open)) {
490-
MUST(show_a_modal_dialog(*this));
499+
MUST(show_a_modal_dialog(*this, as<HTMLElement>(invoker)));
491500
}
492501
}
493502

Libraries/LibWeb/HTML/HTMLDialogElement.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ class HTMLDialogElement final : public HTMLElement {
2626
String return_value() const;
2727
void set_return_value(String);
2828

29-
static WebIDL::ExceptionOr<void> show_a_modal_dialog(HTMLDialogElement&);
29+
static WebIDL::ExceptionOr<void> show_a_modal_dialog(HTMLDialogElement&, GC::Ptr<DOM::Element> source);
30+
31+
void close_the_dialog(Optional<String> result, GC::Ptr<DOM::Element> source);
3032

3133
WebIDL::ExceptionOr<void> show();
3234
WebIDL::ExceptionOr<void> show_modal();
@@ -50,9 +52,7 @@ class HTMLDialogElement final : public HTMLElement {
5052
virtual void initialize(JS::Realm&) override;
5153
virtual void visit_edges(Cell::Visitor&) override;
5254

53-
void queue_a_dialog_toggle_event_task(String old_state, String new_state);
54-
55-
void close_the_dialog(Optional<String> result);
55+
void queue_a_dialog_toggle_event_task(String old_state, String new_state, GC::Ptr<DOM::Element> source);
5656

5757
void run_dialog_focusing_steps();
5858

@@ -63,6 +63,7 @@ class HTMLDialogElement final : public HTMLElement {
6363
String m_return_value;
6464
bool m_is_modal { false };
6565
Optional<String> m_request_close_return_value;
66+
GC::Ptr<DOM::Element> m_request_close_source_element;
6667
GC::Ptr<CloseWatcher> m_close_watcher;
6768

6869
// https://html.spec.whatwg.org/multipage/interactive-elements.html#dialog-toggle-task-tracker

0 commit comments

Comments
 (0)