diff --git a/Userland/Libraries/LibWeb/Animations/Animation.cpp b/Userland/Libraries/LibWeb/Animations/Animation.cpp index da37fa38fd6..2638e74da35 100644 --- a/Userland/Libraries/LibWeb/Animations/Animation.cpp +++ b/Userland/Libraries/LibWeb/Animations/Animation.cpp @@ -559,9 +559,6 @@ WebIDL::ExceptionOr Animation::play() // https://www.w3.org/TR/web-animations-1/#play-an-animation WebIDL::ExceptionOr Animation::play_an_animation(AutoRewind auto_rewind) { - if (auto document = document_for_timing()) - document->ensure_animation_timer(); - // 1. Let aborted pause be a boolean flag that is true if animation has a pending pause task, and false otherwise. auto aborted_pause = m_pending_pause_task == TaskState::Scheduled; diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 1ed130a1aeb..533a705ac75 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -4568,31 +4568,6 @@ void Document::remove_replaced_animations() } } -void Document::ensure_animation_timer() -{ - constexpr static auto timer_delay_ms = 1000 / 60; - if (!m_animation_driver_timer) { - m_animation_driver_timer = Core::Timer::create_repeating(timer_delay_ms, [this] { - bool has_animations = false; - for (auto& timeline : m_associated_animation_timelines) { - if (!timeline->associated_animations().is_empty()) { - has_animations = true; - break; - } - } - if (!has_animations) { - m_animation_driver_timer->stop(); - return; - } - auto* window_or_worker = dynamic_cast(&realm().global_object()); - VERIFY(window_or_worker); - update_animations_and_send_events(window_or_worker->performance()->now()); - }); - } - - m_animation_driver_timer->start(); -} - Vector> Document::get_animations() { Vector> relevant_animations; diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 73968ad8455..23e8591d5f4 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -624,7 +624,6 @@ public: void append_pending_animation_event(PendingAnimationEvent const&); void update_animations_and_send_events(Optional const& timestamp); void remove_replaced_animations(); - void ensure_animation_timer(); Vector> get_animations(); diff --git a/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h b/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h index b49f82ddbea..95b1feb339f 100644 --- a/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h +++ b/Userland/Libraries/LibWeb/HTML/AnimationFrameCallbackDriver.h @@ -18,19 +18,10 @@ namespace Web::HTML { struct AnimationFrameCallbackDriver { using Callback = Function; - AnimationFrameCallbackDriver() - { - m_timer = Core::Timer::create_single_shot(16, [] { - HTML::main_thread_event_loop().schedule(); - }); - } - [[nodiscard]] WebIDL::UnsignedLong add(Callback handler) { auto id = ++m_animation_frame_callback_identifier; m_callbacks.set(id, move(handler)); - if (!m_timer->is_active()) - m_timer->start(); return id; } @@ -60,7 +51,6 @@ private: WebIDL::UnsignedLong m_animation_frame_callback_identifier { 0 }; OrderedHashMap m_callbacks; - RefPtr m_timer; }; } diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp index 9858c32c031..7fd1016478f 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.cpp @@ -147,23 +147,23 @@ void EventLoop::process() if (m_skip_event_loop_processing_steps) return; - // An event loop must continually run through the following steps for as long as it exists: - - // 1. Let oldestTask be null. + // 1. Let oldestTask and taskStartTime be null. JS::GCPtr oldest_task; + [[maybe_unused]] double task_start_time = 0; - // 2. Set taskStartTime to the unsafe shared current time. - double task_start_time = HighResolutionTime::unsafe_shared_current_time(); + // 2. If the event loop has a task queue with at least one runnable task, then: + if (m_task_queue->has_runnable_tasks()) { + // 1. Let taskQueue be one such task queue, chosen in an implementation-defined manner. + auto task_queue = m_task_queue; - // 3. Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner, - // with the constraint that the chosen task queue must contain at least one runnable task. - // If there is no such task queue, then jump to the microtasks step below. - auto& task_queue = *m_task_queue; + // 2. Set taskStartTime to the unsafe shared current time. + task_start_time = HighResolutionTime::unsafe_shared_current_time(); - // 4. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue. - oldest_task = task_queue.take_first_runnable(); + // 3. Set oldestTask to the first runnable task in taskQueue, and remove it from taskQueue. + oldest_task = task_queue->take_first_runnable(); + + // FIXME: 4. If oldestTask's document is not null, then record task start time given taskStartTime and oldestTask's document. - if (oldest_task) { // 5. Set the event loop's currently running task to oldestTask. m_currently_running_task = oldest_task.ptr(); @@ -172,237 +172,221 @@ void EventLoop::process() // 7. Set the event loop's currently running task back to null. m_currently_running_task = nullptr; + + // 8. Perform a microtask checkpoint. + perform_a_microtask_checkpoint(); } - // 8. Microtasks: Perform a microtask checkpoint. - perform_a_microtask_checkpoint(); + // 3. Let taskEndTime be the unsafe shared current time. [HRT] + [[maybe_unused]] auto task_end_time = HighResolutionTime::unsafe_shared_current_time(); - if (m_is_running_reflow_steps) { - // NOTE: If we entered style-layout-repaint steps, then we need to wait for them to finish before doing next iteration. - schedule(); - return; + // 4. If oldestTask is not null, then: + if (oldest_task) { + // FIXME: 1. Let top-level browsing contexts be an empty set. + // FIXME: 2. For each environment settings object settings of oldestTask's script evaluation environment settings object set: + // FIXME: 2.1. Let global be settings's global object. + // FIXME: 2.2. If global is not a Window object, then continue. + // FIXME: 2.3. If global's browsing context is null, then continue. + // FIXME: 2.4. Let tlbc be global's browsing context's top-level browsing context. + // FIXME: 2.5. If tlbc is not null, then append it to top-level browsing contexts. + // FIXME: 3. Report long tasks, passing in taskStartTime, taskEndTime, top-level browsing contexts, and oldestTask. + // FIXME: 4. If oldestTask's document is not null, then record task end time given taskEndTime and oldestTask's document. } - m_is_running_reflow_steps = true; - ScopeGuard const guard = [this] { - m_is_running_reflow_steps = false; - }; - - // 9. Let hasARenderingOpportunity be false. - [[maybe_unused]] bool has_a_rendering_opportunity = false; - - // FIXME: 10. Let now be the current high resolution time. [HRT] - - // FIXME: 11. If oldestTask is not null, then: - - // FIXME: 1. Let top-level browsing contexts be an empty set. - - // FIXME: 2. For each environment settings object settings of oldestTask's script evaluation environment settings object set, append setting's top-level browsing context to top-level browsing contexts. - - // FIXME: 3. Report long tasks, passing in taskStartTime, now (the end time of the task), top-level browsing contexts, and oldestTask. - - // FIXME: 12. Update the rendering: if this is a window event loop, then: - - // FIXME: 1. Let docs be all Document objects whose relevant agent's event loop is this event loop, sorted arbitrarily except that the following conditions must be met: - // - Any Document B whose browsing context's container document is A must be listed after A in the list. - // - If there are two documents A and B whose browsing contexts are both child browsing contexts whose container documents are another Document C, then the order of A and B in the list must match the shadow-including tree order of their respective browsing context containers in C's node tree. - // FIXME: NOTE: The sort order specified above is missing here! - Vector> docs = documents_in_this_event_loop(); - - auto for_each_fully_active_document_in_docs = [&](auto&& callback) { - for (auto& document : docs) { - if (document->is_fully_active()) - callback(*document); - } - }; - - // AD-HOC: Since event loop processing steps do not constantly running in parallel, and - // something must trigger them, we need to manually schedule a repaint for all - // navigables that do not have a rendering opportunity at this event loop iteration. - // Otherwise their repaint will be delayed until something else will trigger event - // loop processing. - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - auto navigable = document.navigable(); - if (navigable && !navigable->has_a_rendering_opportunity() && document.needs_repaint()) - schedule(); - if (navigable && navigable->has_a_rendering_opportunity()) - return; - auto* browsing_context = document.browsing_context(); - if (!browsing_context) - return; - auto& page = browsing_context->page(); - page.client().schedule_repaint(); - }); - - // 2. Rendering opportunities: Remove from docs all Document objects whose node navigables do not have a rendering opportunity. - docs.remove_all_matching([&](auto& document) { - auto navigable = document->navigable(); - return navigable && !navigable->has_a_rendering_opportunity(); - }); - - // 3. If docs is not empty, then set hasARenderingOpportunity to true - // and set this event loop's last render opportunity time to taskStartTime. - if (!docs.is_empty()) { - has_a_rendering_opportunity = true; - m_last_render_opportunity_time = task_start_time; - } - - // FIXME: 4. Unnecessary rendering: Remove from docs all Document objects which meet both of the following conditions: - // - The user agent believes that updating the rendering of the Document's browsing context would have no visible effect, and - // - The Document's map of animation frame callbacks is empty. - // https://www.w3.org/TR/intersection-observer/#pending-initial-observation - // In the HTML Event Loops Processing Model, under the "Update the rendering" step, the "Unnecessary rendering" step should be - // modified to add an additional requirement for skipping the rendering update: - // - The document does not have pending initial IntersectionObserver targets. - - // FIXME: 5. Remove from docs all Document objects for which the user agent believes that it's preferable to skip updating the rendering for other reasons. - - // FIXME: 6. For each fully active Document in docs, flush autofocus candidates for that Document if its browsing context is a top-level browsing context. - - // 7. For each fully active Document in docs, run the resize steps for that Document, passing in now as the timestamp. [CSSOMVIEW] - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - document.run_the_resize_steps(); - }); - - // 8. For each fully active Document in docs, run the scroll steps for that Document, passing in now as the timestamp. [CSSOMVIEW] - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - document.run_the_scroll_steps(); - }); - - // 9. For each fully active Document in docs, evaluate media queries and report changes for that Document, passing in now as the timestamp. [CSSOMVIEW] - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - document.evaluate_media_queries_and_report_changes(); - }); - - // 10. For each fully active Document in docs, update animations and send events for that Document, passing in now as the timestamp. [WEBANIMATIONS] - // Note: This is handled by the document's animation timer, however, if a document has any requestAnimationFrame callbacks, we need - // to dispatch events before that happens below. Not dispatching here would be observable. - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - if (document.window()->animation_frame_callback_driver().has_callbacks()) { - document.update_animations_and_send_events(document.window()->performance()->now()); - } - }); - - // FIXME: 11. For each fully active Document in docs, run the fullscreen steps for that Document, passing in now as the timestamp. [FULLSCREEN] - - // FIXME: 12. For each fully active Document in docs, if the user agent detects that the backing storage associated with a CanvasRenderingContext2D or an OffscreenCanvasRenderingContext2D, context, has been lost, then it must run the context lost steps for each such context: - - // FIXME: 13. For each fully active Document in docs, run the animation frame callbacks for that Document, passing in now as the timestamp. - auto now = HighResolutionTime::unsafe_shared_current_time(); - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - run_animation_frame_callbacks(document, now); - }); - - // FIXME: This step is implemented following the latest specification, while the rest of this method uses an outdated spec. - // NOTE: Gathering and broadcasting of resize observations need to happen after evaluating media queries but before - // updating intersection observations steps. - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - // 1. Let resizeObserverDepth be 0. - size_t resize_observer_depth = 0; - - // 2. While true: - while (true) { - // 1. Recalculate styles and update layout for doc. - // NOTE: Recalculation of styles is handled by update_layout() - document.update_layout(); - - // FIXME: 2. Let hadInitialVisibleContentVisibilityDetermination be false. - // FIXME: 3. For each element element with 'auto' used value of 'content-visibility': - // FIXME: 4. If hadInitialVisibleContentVisibilityDetermination is true, then continue. - - // 5. Gather active resize observations at depth resizeObserverDepth for doc. - document.gather_active_observations_at_depth(resize_observer_depth); - - // 6. If doc has active resize observations: - if (document.has_active_resize_observations()) { - // 1. Set resizeObserverDepth to the result of broadcasting active resize observations given doc. - resize_observer_depth = document.broadcast_active_resize_observations(); - - // 2. Continue. - continue; - } - - // 7. Otherwise, break. - break; - } - - // 3. If doc has skipped resize observations, then deliver resize loop error given doc. - if (document.has_skipped_resize_observations()) { - // FIXME: Deliver resize loop error. - } - }); - - // 14. For each fully active Document in docs, run the update intersection observations steps for that Document, passing in now as the timestamp. [INTERSECTIONOBSERVER] - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - document.run_the_update_intersection_observations_steps(now); - }); - - // FIXME: 15. Invoke the mark paint timing algorithm for each Document object in docs. - - // 16. For each fully active Document in docs, update the rendering or user interface of that Document and its browsing context to reflect the current state. - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - auto navigable = document.navigable(); - if (navigable && document.needs_repaint()) { - auto* browsing_context = document.browsing_context(); - auto& page = browsing_context->page(); - if (navigable->is_traversable()) { - VERIFY(page.client().is_ready_to_paint()); - page.client().paint_next_frame(); - } - } - }); - - // FIXME: Not in the spec: If there is a screenshot request queued, process it now. - // This prevents tests deadlocking on screenshot requests on macOS. - for (auto& document : docs) { - if (document->needs_repaint()) - document->page().client().process_screenshot_requests(); - } - - // 13. If all of the following are true - // - this is a window event loop - // - there is no task in this event loop's task queues whose document is fully active - // - this event loop's microtask queue is empty - // - hasARenderingOpportunity is false - // FIXME: has_a_rendering_opportunity is always true - if (m_type == Type::Window && !task_queue.has_runnable_tasks() && m_microtask_queue->is_empty() /*&& !has_a_rendering_opportunity*/) { + // 5. If this is a window event loop that has no runnable task in this event loop's task queues, then: + if (m_type == Type::Window && !m_task_queue->has_runnable_tasks()) { // 1. Set this event loop's last idle period start time to the unsafe shared current time. m_last_idle_period_start_time = HighResolutionTime::unsafe_shared_current_time(); // 2. Let computeDeadline be the following steps: - // NOTE: instead of passing around a function we use this event loop, which has compute_deadline() + // Implemented in EventLoop::compute_deadline() - // 3. For each win of the same-loop windows for this event loop, - // perform the start an idle period algorithm for win with computeDeadline. [REQUESTIDLECALLBACK] - for (auto& win : same_loop_windows()) + // 3. For each win of the same-loop windows for this event loop, perform the start an idle period algorithm for win with the following step: return the result of calling computeDeadline, coarsened given win's relevant settings object's cross-origin isolated capability. [REQUESTIDLECALLBACK] + for (auto& win : same_loop_windows()) { win->start_an_idle_period(); + } } - // FIXME: 14. If this is a worker event loop, then: - - // FIXME: 1. If this event loop's agent's single realm's global object is a supported DedicatedWorkerGlobalScope and the user agent believes that it would benefit from having its rendering updated at this time, then: - // FIXME: 1. Let now be the current high resolution time. [HRT] - // FIXME: 2. Run the animation frame callbacks for that DedicatedWorkerGlobalScope, passing in now as the timestamp. - // FIXME: 3. Update the rendering of that dedicated worker to reflect the current state. - - // FIXME: 2. If there are no tasks in the event loop's task queues and the WorkerGlobalScope object's closing flag is true, then destroy the event loop, aborting these steps, resuming the run a worker steps described in the Web workers section below. - // If there are eligible tasks in the queue, schedule a new round of processing. :^) - if (m_task_queue->has_runnable_tasks() || (!m_microtask_queue->is_empty() && !m_performing_a_microtask_checkpoint)) + if (m_task_queue->has_runnable_tasks() || (!m_microtask_queue->is_empty() && !m_performing_a_microtask_checkpoint)) { schedule(); + } +} - // For each doc of docs, process top layer removals given doc. - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - document.process_top_layer_removals(); - }); +// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model +void EventLoop::queue_task_to_update_the_rendering() +{ + // FIXME: 1. Wait until at least one navigable whose active document's relevant agent's event loop is eventLoop might have a rendering opportunity. - // Not in the spec: - for_each_fully_active_document_in_docs([&](DOM::Document& document) { - if (document.readiness() == HTML::DocumentReadyState::Complete && document.style_computer().number_of_css_font_faces_with_loading_in_progress() == 0) { - HTML::TemporaryExecutionContext context(HTML::relevant_settings_object(document), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); - document.fonts()->resolve_ready_promise(); - } - }); + // 2. Set eventLoop's last render opportunity time to the unsafe shared current time. + m_last_render_opportunity_time = HighResolutionTime::unsafe_shared_current_time(); + + // 3. For each navigable that has a rendering opportunity, queue a global task on the rendering task source given navigable's active window to update the rendering: + for (auto& navigable : all_navigables()) { + if (!navigable->is_traversable()) + continue; + if (!navigable->has_a_rendering_opportunity()) + continue; + + auto document = navigable->active_document(); + if (!document) + continue; + if (document->is_decoded_svg()) + continue; + + queue_global_task(Task::Source::Rendering, *navigable->active_window(), JS::create_heap_function(navigable->heap(), [this] mutable { + VERIFY(!m_is_running_rendering_task); + m_is_running_rendering_task = true; + ScopeGuard const guard = [this] { + m_is_running_rendering_task = false; + }; + + // FIXME: 1. Let frameTimestamp be eventLoop's last render opportunity time. + + // FIXME: 2. Let docs be all fully active Document objects whose relevant agent's event loop is eventLoop, sorted arbitrarily except that the following conditions must be met: + auto docs = documents_in_this_event_loop(); + docs.remove_all_matching([&](auto& document) { + return !document->is_fully_active(); + }); + + // 3. Filter non-renderable documents: Remove from docs any Document object doc for which any of the following are true: + docs.remove_all_matching([&](auto const& document) { + auto navigable = document->navigable(); + if (!navigable) + return true; + + // FIXME: doc is render-blocked; + + // doc's visibility state is "hidden"; + if (document->visibility_state() == "hidden"sv) + return true; + + // FIXME: doc's rendering is suppressed for view transitions; or + + // doc's node navigable doesn't currently have a rendering opportunity. + if (!navigable->has_a_rendering_opportunity()) + return true; + + return false; + }); + + // FIXME: 4. Unnecessary rendering: Remove from docs any Document object doc for which all of the following are true: + + // FIXME: 5. Remove from docs all Document objects for which the user agent believes that it's preferable to skip updating the rendering for other reasons. + + // FIXME: 6. For each doc of docs, reveal doc. + + // FIXME: 7. For each doc of docs, flush autofocus candidates for doc if its node navigable is a top-level traversable. + + // 8. For each doc of docs, run the resize steps for doc. [CSSOMVIEW] + for (auto& document : docs) { + document->run_the_resize_steps(); + } + + // 9. For each doc of docs, run the scroll steps for doc. [CSSOMVIEW] + for (auto& document : docs) { + document->run_the_scroll_steps(); + } + + // 10. For each doc of docs, evaluate media queries and report changes for doc. [CSSOMVIEW] + for (auto& document : docs) { + document->evaluate_media_queries_and_report_changes(); + } + + // 11. For each doc of docs, update animations and send events for doc, passing in relative high resolution time given frameTimestamp and doc's relevant global object as the timestamp [WEBANIMATIONS] + for (auto& document : docs) { + document->update_animations_and_send_events(document->window()->performance()->now()); + }; + + // FIXME: 12. For each doc of docs, run the fullscreen steps for doc. [FULLSCREEN] + + // FIXME: 13. For each doc of docs, if the user agent detects that the backing storage associated with a CanvasRenderingContext2D or an OffscreenCanvasRenderingContext2D, context, has been lost, then it must run the context lost steps for each such context: + + // 14. For each doc of docs, run the animation frame callbacks for doc, passing in the relative high resolution time given frameTimestamp and doc's relevant global object as the timestamp. + auto now = HighResolutionTime::unsafe_shared_current_time(); + for (auto& document : docs) { + run_animation_frame_callbacks(*document, now); + } + + // FIXME: 15. Let unsafeStyleAndLayoutStartTime be the unsafe shared current time. + + // 16. For each doc of docs: + for (auto& document : docs) { + // 1. Let resizeObserverDepth be 0. + size_t resize_observer_depth = 0; + + // 2. While true: + while (true) { + // 1. Recalculate styles and update layout for doc. + // NOTE: Recalculation of styles is handled by update_layout() + document->update_layout(); + + // FIXME: 2. Let hadInitialVisibleContentVisibilityDetermination be false. + // FIXME: 3. For each element element with 'auto' used value of 'content-visibility': + // FIXME: 4. If hadInitialVisibleContentVisibilityDetermination is true, then continue. + + // 5. Gather active resize observations at depth resizeObserverDepth for doc. + document->gather_active_observations_at_depth(resize_observer_depth); + + // 6. If doc has active resize observations: + if (document->has_active_resize_observations()) { + // 1. Set resizeObserverDepth to the result of broadcasting active resize observations given doc. + resize_observer_depth = document->broadcast_active_resize_observations(); + + // 2. Continue. + continue; + } + + // 7. Otherwise, break. + break; + } + + // 3. If doc has skipped resize observations, then deliver resize loop error given doc. + if (document->has_skipped_resize_observations()) { + // FIXME: Deliver resize loop error. + } + } + + // FIXME: 17. For each doc of docs, if the focused area of doc is not a focusable area, then run the focusing steps for doc's viewport, and set doc's relevant global object's navigation API's focus changed during ongoing navigation to false. + + // FIXME: 18. For each doc of docs, perform pending transition operations for doc. [CSSVIEWTRANSITIONS] + + // 19. For each doc of docs, run the update intersection observations steps for doc, passing in the relative high resolution time given now and doc's relevant global object as the timestamp. [INTERSECTIONOBSERVER] + for (auto& document : docs) { + document->run_the_update_intersection_observations_steps(now); + } + + // FIXME: 20. For each doc of docs, record rendering time for doc given unsafeStyleAndLayoutStartTime. + + // FIXME: 21. For each doc of docs, mark paint timing for doc. + + // 22. For each doc of docs, update the rendering or user interface of doc and its node navigable to reflect the current state. + for (auto& document : docs) { + document->page().client().process_screenshot_requests(); + auto navigable = document->navigable(); + if (navigable && document->needs_repaint()) { + auto* browsing_context = document->browsing_context(); + auto& page = browsing_context->page(); + if (navigable->is_traversable()) { + VERIFY(page.client().is_ready_to_paint()); + page.client().paint_next_frame(); + } + } + } + + // 23. For each doc of docs, process top layer removals given doc. + for (auto& document : docs) { + document->process_top_layer_removals(); + } + + for (auto& document : docs) { + if (document->readiness() == HTML::DocumentReadyState::Complete && document->style_computer().number_of_css_font_faces_with_loading_in_progress() == 0) { + HTML::TemporaryExecutionContext context(HTML::relevant_settings_object(*document), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes); + document->fonts()->resolve_ready_promise(); + } + } + })); + } } // https://html.spec.whatwg.org/multipage/webappapis.html#queue-a-task diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h index 287840c893c..f125d40930a 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/EventLoop.h @@ -42,6 +42,7 @@ public: void spin_until(JS::SafeFunction goal_condition); void spin_processing_tasks_with_source_until(Task::Source, JS::SafeFunction goal_condition); void process(); + void queue_task_to_update_the_rendering(); // https://html.spec.whatwg.org/multipage/browsing-the-web.html#termination-nesting-level size_t termination_nesting_level() const { return m_termination_nesting_level; } @@ -114,7 +115,7 @@ private: bool m_skip_event_loop_processing_steps { false }; - bool m_is_running_reflow_steps { false }; + bool m_is_running_rendering_task { false }; }; EventLoop& main_thread_event_loop(); diff --git a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h index e26c57ffe3a..84933cf01e2 100644 --- a/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h +++ b/Userland/Libraries/LibWeb/HTML/EventLoop/Task.h @@ -63,6 +63,9 @@ public: // https://html.spec.whatwg.org/multipage/server-sent-events.html#remote-event-task-source RemoteEvent, + // https://html.spec.whatwg.org/multipage/webappapis.html#rendering-task-source + Rendering, + // !!! IMPORTANT: Keep this field last! // This serves as the base value of all unique task sources. // Some elements, such as the HTMLMediaElement, must have a unique task source per instance. diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h index ab854c0543a..c0eff18262e 100644 --- a/Userland/Libraries/LibWeb/Page/Page.h +++ b/Userland/Libraries/LibWeb/Page/Page.h @@ -383,7 +383,6 @@ public: virtual void inspector_did_execute_console_script([[maybe_unused]] String const& script) { } virtual void inspector_did_export_inspector_html([[maybe_unused]] String const& html) { } - virtual void schedule_repaint() = 0; virtual bool is_ready_to_paint() const = 0; virtual DisplayListPlayerType display_list_player_type() const = 0; diff --git a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h index f3bdf4edecc..32b01c8dc13 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h +++ b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.h @@ -79,7 +79,6 @@ public: virtual void paint_next_frame() override { } virtual void process_screenshot_requests() override { } virtual void paint(DevicePixelRect const&, Painting::BackingStore&, Web::PaintOptions = {}) override { } - virtual void schedule_repaint() override { } virtual bool is_ready_to_paint() const override { return true; } virtual DisplayListPlayerType display_list_player_type() const override { return m_host_page->client().display_list_player_type(); } diff --git a/Userland/Services/WebContent/PageClient.cpp b/Userland/Services/WebContent/PageClient.cpp index db50544fec2..d7d4cc2d2b1 100644 --- a/Userland/Services/WebContent/PageClient.cpp +++ b/Userland/Services/WebContent/PageClient.cpp @@ -52,18 +52,17 @@ PageClient::PageClient(PageHost& owner, u64 id) , m_backing_store_manager(*this) { setup_palette(); + + int refresh_interval = 1000 / 60; // FIXME: Account for the actual refresh rate of the display + m_paint_refresh_timer = Core::Timer::create_repeating(refresh_interval, [] { + Web::HTML::main_thread_event_loop().queue_task_to_update_the_rendering(); + }); + + m_paint_refresh_timer->start(); } PageClient::~PageClient() = default; -void PageClient::schedule_repaint() -{ - if (m_paint_state != PaintState::Ready) { - m_paint_state = PaintState::PaintWhenReady; - return; - } -} - bool PageClient::is_ready_to_paint() const { return m_paint_state == PaintState::Ready; @@ -71,13 +70,7 @@ bool PageClient::is_ready_to_paint() const void PageClient::ready_to_paint() { - auto old_paint_state = exchange(m_paint_state, PaintState::Ready); - - if (old_paint_state == PaintState::PaintWhenReady) { - // NOTE: Repainting always has to be scheduled from HTML event loop processing steps - // to make sure style and layout are up-to-date. - Web::HTML::main_thread_event_loop().schedule(); - } + m_paint_state = PaintState::Ready; } void PageClient::visit_edges(JS::Cell::Visitor& visitor) @@ -196,8 +189,6 @@ void PageClient::process_screenshot_requests() void PageClient::paint_next_frame() { - process_screenshot_requests(); - auto back_store = m_backing_store_manager.back_store(); if (!back_store) return; diff --git a/Userland/Services/WebContent/PageClient.h b/Userland/Services/WebContent/PageClient.h index 7ec1e5d914a..12146fdfedd 100644 --- a/Userland/Services/WebContent/PageClient.h +++ b/Userland/Services/WebContent/PageClient.h @@ -34,7 +34,6 @@ public: }; static void set_use_skia_painter(UseSkiaPainter); - virtual void schedule_repaint() override; virtual bool is_ready_to_paint() const override; virtual Web::Page& page() override { return *m_page; } @@ -191,7 +190,6 @@ private: enum class PaintState { Ready, WaitingForClient, - PaintWhenReady, }; PaintState m_paint_state { PaintState::Ready }; @@ -212,6 +210,8 @@ private: WeakPtr m_top_level_document_console_client; JS::Handle m_console_global_object; + + RefPtr m_paint_refresh_timer; }; } diff --git a/Userland/Services/WebWorker/PageHost.h b/Userland/Services/WebWorker/PageHost.h index f50884e59c3..0a4e0524406 100644 --- a/Userland/Services/WebWorker/PageHost.h +++ b/Userland/Services/WebWorker/PageHost.h @@ -35,7 +35,6 @@ public: virtual void process_screenshot_requests() override {}; virtual void paint(Web::DevicePixelRect const&, Web::Painting::BackingStore&, Web::PaintOptions = {}) override; virtual void request_file(Web::FileRequest) override; - virtual void schedule_repaint() override {}; virtual bool is_ready_to_paint() const override { return true; } virtual Web::DisplayListPlayerType display_list_player_type() const override { VERIFY_NOT_REACHED(); }