From 8bb5652835ff6a9f2e22b11d28f4793c7ae9c9d9 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 31 Aug 2023 13:53:17 -0400 Subject: [PATCH] LibWeb: Implement HTMLDetailsElement's open attribute closer to the spec The spec now has a "toggle task tracker" to coalesce rapid changes to this attribute. It also now has an explicit ToggleEvent to encapsulate the old and new state of the element. This further handles the attribute being added/removed using a override of Element::attribute_changed(), rather than being the only element to instead override Element::set/remove__attribute(). --- Userland/Libraries/LibWeb/Forward.h | 1 + .../LibWeb/HTML/HTMLDetailsElement.cpp | 91 ++++++++++++------- .../LibWeb/HTML/HTMLDetailsElement.h | 15 +-- .../Libraries/LibWeb/HTML/ToggleTaskTracker.h | 25 +++++ 4 files changed, 92 insertions(+), 40 deletions(-) create mode 100644 Userland/Libraries/LibWeb/HTML/ToggleTaskTracker.h 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; +}; + +}