diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 44dea0fde0..9010e1671c 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -465,6 +465,7 @@ struct PolicyContainer; struct POSTResource; struct SerializedFormData; struct SessionHistoryEntry; +struct ToggleTaskTracker; } namespace Web::HighResolutionTime { diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp index 126b49686e..66dad22e2f 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.cpp @@ -6,8 +6,10 @@ #include #include +#include #include #include +#include namespace Web::HTML { @@ -18,44 +20,65 @@ HTMLDetailsElement::HTMLDetailsElement(DOM::Document& document, DOM::QualifiedNa HTMLDetailsElement::~HTMLDetailsElement() = default; -WebIDL::ExceptionOr HTMLDetailsElement::set_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) -{ - auto result = HTMLElement::set_attribute(name, value); - if (result.is_exception()) - return result.exception(); - - if (name == HTML::AttributeNames::open) - run_details_notification_task_steps(); - - return result; -} - -void HTMLDetailsElement::remove_attribute(DeprecatedFlyString const& name) -{ - HTMLElement::remove_attribute(name); - - if (name == HTML::AttributeNames::open) - run_details_notification_task_steps(); -} - -// https://html.spec.whatwg.org/multipage/interactive-elements.html#the-details-element:details-notification-task-steps -void HTMLDetailsElement::run_details_notification_task_steps() -{ - // Whenever the open attribute is added to or removed from a details element, - // the user agent must queue an element task on the DOM manipulation task source given then details element that runs the following steps, - // which are known as the details notification task steps, for this details element: - queue_an_element_task(HTML::Task::Source::DOMManipulation, [this] { - // 1. FIXME: If another task has been queued to run the details notification task steps for this details element, then return. - - // 2. Fire an event named toggle at the details element. - dispatch_event(Web::DOM::Event::create(realm(), HTML::EventNames::toggle)); - }); -} - void HTMLDetailsElement::initialize(JS::Realm& realm) { Base::initialize(realm); set_prototype(&Bindings::ensure_web_prototype(realm, "HTMLDetailsElement")); } +void HTMLDetailsElement::attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value) +{ + Base::attribute_changed(name, value); + + // https://html.spec.whatwg.org/multipage/interactive-elements.html#details-notification-task-steps + if (name == HTML::AttributeNames::open) { + // 1. If the open attribute is added, queue a details toggle event task given the details element, "closed", and "open". + if (!value.is_null()) { + queue_a_details_toggle_event_task("closed"_string, "open"_string); + } + // 2. Otherwise, queue a details toggle event task given the details element, "open", and "closed". + else { + queue_a_details_toggle_event_task("open"_string, "closed"_string); + } + } +} + +// https://html.spec.whatwg.org/multipage/interactive-elements.html#queue-a-details-toggle-event-task +void HTMLDetailsElement::queue_a_details_toggle_event_task(String old_state, String new_state) +{ + // 1. If element's details toggle task tracker is not null, then: + if (m_details_toggle_task_tracker.has_value()) { + // 1. Set oldState to element's details toggle task tracker's old state. + old_state = move(m_details_toggle_task_tracker->old_state); + + // 2. Remove element's details 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_details_toggle_task_tracker->task_id; + }); + + // 3. Set element's details toggle task tracker to null. + m_details_toggle_task_tracker->task_id = {}; + } + + // 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(HTML::Task::Source::DOMManipulation, [this, old_state, new_state = move(new_state)]() mutable { + // 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 details toggle task tracker to null. + m_details_toggle_task_tracker = {}; + }); + + // 3. Set element's details toggle task tracker to a struct with task set to the just-queued task and old state set to oldState. + m_details_toggle_task_tracker = ToggleTaskTracker { + .task_id = task_id, + .old_state = move(old_state), + }; +} + } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h index a3fd22eb58..aa00b41b12 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h +++ b/Userland/Libraries/LibWeb/HTML/HTMLDetailsElement.h @@ -6,8 +6,10 @@ #pragma once +#include #include #include +#include namespace Web::HTML { @@ -20,16 +22,17 @@ public: // https://www.w3.org/TR/html-aria/#el-details virtual Optional default_role() const override { return ARIA::Role::group; } - // ^Element - WebIDL::ExceptionOr set_attribute(DeprecatedFlyString const& name, DeprecatedString const& value) override; - void remove_attribute(DeprecatedFlyString const& name) override; - - void run_details_notification_task_steps(); - private: HTMLDetailsElement(DOM::Document&, DOM::QualifiedName); virtual void initialize(JS::Realm&) override; + + virtual void attribute_changed(DeprecatedFlyString const& name, DeprecatedString const& value) override; + + void queue_a_details_toggle_event_task(String old_state, String new_state); + + // https://html.spec.whatwg.org/multipage/interactive-elements.html#details-toggle-task-tracker + Optional m_details_toggle_task_tracker; }; } diff --git a/Userland/Libraries/LibWeb/HTML/ToggleTaskTracker.h b/Userland/Libraries/LibWeb/HTML/ToggleTaskTracker.h new file mode 100644 index 0000000000..267da119ea --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/ToggleTaskTracker.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Web::HTML { + +// https://html.spec.whatwg.org/multipage/interaction.html#toggle-task-tracker +struct ToggleTaskTracker { + // https://html.spec.whatwg.org/multipage/interaction.html#toggle-task-task + // NOTE: We store the task's ID rather than the task itself to avoid ownership issues. + Optional task_id; + + // https://html.spec.whatwg.org/multipage/interaction.html#toggle-task-old-state + String old_state; +}; + +}