diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index e238752c79d..50cea491f96 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -269,6 +269,7 @@ set(SOURCES Geometry/DOMRectReadOnly.cpp HTML/AbstractWorker.cpp HTML/AnimatedBitmapDecodedImageData.cpp + HTML/AnimationFrameCallbackDriver.cpp HTML/AttributeNames.cpp HTML/AudioTrack.cpp HTML/AudioTrackList.cpp diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 359c1209a89..8d2f7c791d4 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -375,6 +375,7 @@ struct DOMPointInit; } namespace Web::HTML { +class AnimationFrameCallbackDriver; class AudioTrack; class AudioTrackList; class BroadcastChannel; diff --git a/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.cpp b/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.cpp new file mode 100644 index 00000000000..f0765c9164a --- /dev/null +++ b/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022, the SerenityOS developers. + * Copyright (c) 2024, Andreas Kling + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace Web::HTML { + +JS_DEFINE_ALLOCATOR(AnimationFrameCallbackDriver); + +void AnimationFrameCallbackDriver::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_callbacks); +} + +WebIDL::UnsignedLong AnimationFrameCallbackDriver::add(Callback handler) +{ + auto id = ++m_animation_frame_callback_identifier; + m_callbacks.set(id, handler); + return id; +} + +bool AnimationFrameCallbackDriver::remove(WebIDL::UnsignedLong id) +{ + return m_callbacks.remove(id); +} + +bool AnimationFrameCallbackDriver::has_callbacks() const +{ + return !m_callbacks.is_empty(); +} + +void AnimationFrameCallbackDriver::run(double now) +{ + auto taken_callbacks = move(m_callbacks); + for (auto& [id, callback] : taken_callbacks) + callback->function()(now); +} + +} diff --git a/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h b/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h index 95b1feb339f..24161a5d324 100644 --- a/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h +++ b/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h @@ -1,52 +1,35 @@ /* * Copyright (c) 2022, the SerenityOS developers. * Copyright (c) 2024, Andreas Kling + * Copyright (c) 2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once -#include #include -#include -#include +#include +#include #include namespace Web::HTML { -struct AnimationFrameCallbackDriver { - using Callback = Function; +class AnimationFrameCallbackDriver final : public JS::Cell { + JS_CELL(AnimationFrameCallbackDriver, JS::Cell); + JS_DECLARE_ALLOCATOR(AnimationFrameCallbackDriver); - [[nodiscard]] WebIDL::UnsignedLong add(Callback handler) - { - auto id = ++m_animation_frame_callback_identifier; - m_callbacks.set(id, move(handler)); - return id; - } + using Callback = JS::NonnullGCPtr>; - bool remove(WebIDL::UnsignedLong id) - { - auto it = m_callbacks.find(id); - if (it == m_callbacks.end()) - return false; - m_callbacks.remove(it); - return true; - } - - void run(double now) - { - auto taken_callbacks = move(m_callbacks); - for (auto& [id, callback] : taken_callbacks) - callback(now); - } - - bool has_callbacks() const - { - return !m_callbacks.is_empty(); - } +public: + [[nodiscard]] WebIDL::UnsignedLong add(Callback handler); + bool remove(WebIDL::UnsignedLong); + bool has_callbacks() const; + void run(double now); private: + virtual void visit_edges(Cell::Visitor&) override; + // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animation-frame-callback-identifier WebIDL::UnsignedLong m_animation_frame_callback_identifier { 0 }; diff --git a/Userland/Libraries/LibWeb/HTML/Window.cpp b/Userland/Libraries/LibWeb/HTML/Window.cpp index 795484b7a23..cb58fb65a93 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.cpp +++ b/Userland/Libraries/LibWeb/HTML/Window.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,7 @@ void Window::visit_edges(JS::Cell::Visitor& visitor) visitor.visit(m_navigator); visitor.visit(m_navigation); visitor.visit(m_custom_element_registry); + visitor.visit(m_animation_frame_callback_driver); visitor.visit(m_pdf_viewer_plugin_objects); visitor.visit(m_pdf_viewer_mime_type_objects); visitor.visit(m_count_queuing_strategy_size_function); @@ -1541,15 +1543,15 @@ double Window::device_pixel_ratio() const } // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#dom-animationframeprovider-requestanimationframe -WebIDL::UnsignedLong Window::request_animation_frame(WebIDL::CallbackType& callback) +WebIDL::UnsignedLong Window::request_animation_frame(JS::NonnullGCPtr callback) { // FIXME: Make this fully spec compliant. Currently implements a mix of 'requestAnimationFrame()' and 'run the animation frame callbacks'. - return m_animation_frame_callback_driver.add([this, callback = JS::make_handle(callback)](double now) { + return animation_frame_callback_driver().add(JS::create_heap_function(heap(), [this, callback](double now) { // 3. Invoke callback, passing now as the only argument, and if an exception is thrown, report the exception. auto result = WebIDL::invoke_callback(*callback, {}, JS::Value(now)); if (result.is_error()) report_exception(result, realm()); - }); + })); } // https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#animationframeprovider-cancelanimationframe @@ -1560,7 +1562,21 @@ void Window::cancel_animation_frame(WebIDL::UnsignedLong handle) // 2. Let callbacks be this's target object's map of animation frame callbacks. // 3. Remove callbacks[handle]. - (void)m_animation_frame_callback_driver.remove(handle); + (void)animation_frame_callback_driver().remove(handle); +} + +AnimationFrameCallbackDriver& Window::animation_frame_callback_driver() +{ + if (!m_animation_frame_callback_driver) + m_animation_frame_callback_driver = heap().allocate(realm()); + return *m_animation_frame_callback_driver; +} + +bool Window::has_animation_frame_callbacks() +{ + if (!m_animation_frame_callback_driver) + return false; + return m_animation_frame_callback_driver->has_callbacks(); } // https://w3c.github.io/requestidlecallback/#dom-window-requestidlecallback diff --git a/Userland/Libraries/LibWeb/HTML/Window.h b/Userland/Libraries/LibWeb/HTML/Window.h index b28db31135e..820c71aec0e 100644 --- a/Userland/Libraries/LibWeb/HTML/Window.h +++ b/Userland/Libraries/LibWeb/HTML/Window.h @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -110,8 +109,6 @@ public: }; WebIDL::ExceptionOr window_open_steps_internal(StringView url, StringView target, StringView features); - bool has_animation_frame_callbacks() const { return m_animation_frame_callback_driver.has_callbacks(); } - DOM::Event* current_event() { return m_current_event.ptr(); } DOM::Event const* current_event() const { return m_current_event.ptr(); } void set_current_event(DOM::Event* event); @@ -125,8 +122,6 @@ public: void start_an_idle_period(); - AnimationFrameCallbackDriver& animation_frame_callback_driver() { return m_animation_frame_callback_driver; } - // https://html.spec.whatwg.org/multipage/interaction.html#sticky-activation bool has_sticky_activation() const; @@ -211,7 +206,10 @@ public: i32 outer_height() const; double device_pixel_ratio() const; - WebIDL::UnsignedLong request_animation_frame(WebIDL::CallbackType&); + AnimationFrameCallbackDriver& animation_frame_callback_driver(); + bool has_animation_frame_callbacks(); + + WebIDL::UnsignedLong request_animation_frame(JS::NonnullGCPtr); void cancel_animation_frame(WebIDL::UnsignedLong handle); u32 request_idle_callback(WebIDL::CallbackType&, RequestIdleCallback::IdleRequestOptions const&); @@ -289,7 +287,7 @@ private: // Each Window object is associated with a unique instance of a CustomElementRegistry object, allocated when the Window object is created. JS::GCPtr m_custom_element_registry; - AnimationFrameCallbackDriver m_animation_frame_callback_driver; + JS::GCPtr m_animation_frame_callback_driver; // https://w3c.github.io/requestidlecallback/#dfn-list-of-idle-request-callbacks Vector> m_idle_request_callbacks; diff --git a/Userland/Libraries/LibWeb/WebDriver/Screenshot.cpp b/Userland/Libraries/LibWeb/WebDriver/Screenshot.cpp index 420b71c33fb..f03799b3ac3 100644 --- a/Userland/Libraries/LibWeb/WebDriver/Screenshot.cpp +++ b/Userland/Libraries/LibWeb/WebDriver/Screenshot.cpp @@ -93,14 +93,14 @@ Response capture_element_screenshot(Painter const& painter, Page& page, DOM::Ele return canvas; }; - (void)element.document().window()->animation_frame_callback_driver().add([&](auto) { + (void)element.document().window()->animation_frame_callback_driver().add(JS::create_heap_function(element.heap(), [&](double) { auto canvas_or_error = draw_bounding_box_from_the_framebuffer(); if (canvas_or_error.is_error()) { encoded_string_or_error = canvas_or_error.release_error(); return; } encoded_string_or_error = encode_canvas_element(canvas_or_error.release_value()); - }); + })); Platform::EventLoopPlugin::the().spin_until(JS::create_heap_function(element.document().heap(), [&]() { return encoded_string_or_error.has_value(); })); return encoded_string_or_error.release_value();