From ed3dd8fbe03f41ee816cbe87c898a0cdc71d1115 Mon Sep 17 00:00:00 2001 From: TIN TUN AUNG <62133983+rayguo17@users.noreply.github.com> Date: Sat, 29 Mar 2025 07:19:49 +0800 Subject: [PATCH] Animation: Aggregate Animated Image Info to Document (#36141) Signed-off-by: rayguo17 --- Cargo.lock | 1 + components/layout_2020/context.rs | 27 ++++++++++++- components/layout_2020/replaced.rs | 4 ++ components/layout_thread_2020/lib.rs | 45 ++++++++++++++++++---- components/script/dom/document.rs | 11 ++++++ components/script/dom/window.rs | 7 +++- components/script/image_animation.rs | 29 ++++++++++++++ components/script/lib.rs | 1 + components/shared/script_layout/Cargo.toml | 1 + components/shared/script_layout/lib.rs | 28 ++++++++++++++ 10 files changed, 145 insertions(+), 9 deletions(-) create mode 100644 components/script/image_animation.rs diff --git a/Cargo.lock b/Cargo.lock index 980dac6cbff..d86efd3ff01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6426,6 +6426,7 @@ dependencies = [ "fnv", "fonts", "fonts_traits", + "fxhash", "html5ever", "ipc-channel", "libc", diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs index 2dbfb7eabb8..c825852dae5 100644 --- a/components/layout_2020/context.rs +++ b/components/layout_2020/context.rs @@ -7,11 +7,13 @@ use std::sync::Arc; use base::id::PipelineId; use fnv::FnvHashMap; use fonts::FontContext; +use fxhash::FxHashMap; use net_traits::image_cache::{ ImageCache, ImageCacheResult, ImageOrMetadataAvailable, UsePlaceholder, }; use parking_lot::{Mutex, RwLock}; -use script_layout_interface::{IFrameSizes, PendingImage, PendingImageState}; +use pixels::Image; +use script_layout_interface::{IFrameSizes, ImageAnimationState, PendingImage, PendingImageState}; use servo_url::{ImmutableOrigin, ServoUrl}; use style::context::SharedStyleContext; use style::dom::OpaqueNode; @@ -40,6 +42,8 @@ pub struct LayoutContext<'a> { pub webrender_image_cache: Arc>>, + + pub node_image_animation_map: Arc>>, } impl Drop for LayoutContext<'_> { @@ -100,6 +104,26 @@ impl LayoutContext<'_> { } } + pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc) { + let mut store = self.node_image_animation_map.write(); + + // 1. first check whether node previously being track for animated image. + if let Some(image_state) = store.get(&node) { + // a. if the node is not containing the same image as before. + if image_state.image_key() != image.id { + if image.should_animate() { + // i. Register/Replace tracking item in image_animation_manager. + store.insert(node, ImageAnimationState::new(image)); + } else { + // ii. Cancel Action if the node's image is no longer animated. + store.remove(&node); + } + } + } else if image.should_animate() { + store.insert(node, ImageAnimationState::new(image)); + } + } + pub fn get_webrender_image_for_url( &self, node: OpaqueNode, @@ -116,6 +140,7 @@ impl LayoutContext<'_> { match self.get_or_request_image_or_meta(node, url.clone(), use_placeholder) { Some(ImageOrMetadataAvailable::ImageAvailable { image, .. }) => { + self.handle_animated_image(node, image.clone()); let image_info = WebRenderImageInfo { width: image.width, height: image.height, diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs index f387b47731e..47bb932fa9e 100644 --- a/components/layout_2020/replaced.rs +++ b/components/layout_2020/replaced.rs @@ -182,6 +182,10 @@ impl ReplacedContents { } }; + if let ReplacedContentKind::Image(Some(ref image)) = kind { + context.handle_animated_image(element.opaque(), image.clone()); + } + let natural_size = if let Some(naturalc_size_in_dots) = natural_size_in_dots { // FIXME: should 'image-resolution' (when implemented) be used *instead* of // `script::dom::htmlimageelement::ImageRequest::current_pixel_density`? diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 35c9de28fad..470ce1d7600 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -24,7 +24,7 @@ use euclid::{Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashMap; use fonts::{FontContext, FontContextWebFontMethods}; use fonts_traits::StylesheetWebFontLoadFinishedCallback; -use fxhash::FxHashMap; +use fxhash::{FxHashMap, FxHashSet}; use ipc_channel::ipc::IpcSender; use layout::context::LayoutContext; use layout::display_list::{DisplayList, WebRenderImageInfo}; @@ -46,15 +46,15 @@ use profile_traits::time::{ use profile_traits::{path, time_profile}; use script::layout_dom::{ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::{ - Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal, - ReflowRequest, ReflowResult, TrustedNodeAddress, + ImageAnimationState, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, + OffsetParentResponse, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress, }; use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage}; use servo_arc::Arc as ServoArc; use servo_config::opts::{self, DebugOptions}; use servo_config::pref; use servo_url::ServoUrl; -use style::animation::DocumentAnimationSet; +use style::animation::{AnimationSetKey, DocumentAnimationSet}; use style::context::{ QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext, }; @@ -536,7 +536,7 @@ impl LayoutThread { &'a self, guards: StylesheetGuards<'a>, snapshot_map: &'a SnapshotMap, - reflow_request: &ReflowRequest, + reflow_request: &mut ReflowRequest, use_rayon: bool, ) -> LayoutContext<'a> { let traversal_flags = match reflow_request.stylesheets_changed { @@ -558,6 +558,9 @@ impl LayoutThread { font_context: self.font_context.clone(), webrender_image_cache: self.webrender_image_cache.clone(), pending_images: Mutex::default(), + node_image_animation_map: Arc::new(RwLock::new(std::mem::take( + &mut reflow_request.node_to_image_animation_map, + ))), iframe_sizes: Mutex::default(), use_rayon, } @@ -696,8 +699,12 @@ impl LayoutThread { let rayon_pool = rayon_pool.as_ref(); // Create a layout context for use throughout the following passes. - let mut layout_context = - self.build_layout_context(guards.clone(), &map, &reflow_request, rayon_pool.is_some()); + let mut layout_context = self.build_layout_context( + guards.clone(), + &map, + &mut reflow_request, + rayon_pool.is_some(), + ); let dirty_root = unsafe { ServoLayoutNode::new(&reflow_request.dirty_root.unwrap()) @@ -791,9 +798,12 @@ impl LayoutThread { let pending_images = std::mem::take(&mut *layout_context.pending_images.lock()); let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock()); + let node_to_image_animation_map = + std::mem::take(&mut *layout_context.node_image_animation_map.write()); Some(ReflowResult { pending_images, iframe_sizes, + node_to_image_animation_map, }) } @@ -821,6 +831,11 @@ impl LayoutThread { &fragment_tree, ); + Self::cancel_image_animation_for_nodes_not_in_fragment_tree( + context.node_image_animation_map.clone(), + &fragment_tree, + ); + if !reflow_goal.needs_display_list() { return; } @@ -920,6 +935,22 @@ impl LayoutThread { } } + fn cancel_image_animation_for_nodes_not_in_fragment_tree( + image_animation_set: Arc>>, + root: &FragmentTree, + ) { + let mut image_animations = image_animation_set.write().to_owned(); + let mut invalid_nodes: FxHashSet = image_animations + .keys() + .cloned() + .map(|node| AnimationSetKey::new(node, None)) + .collect(); + root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); + for node in &invalid_nodes { + image_animations.remove(&node.node); + } + } + fn viewport_did_change(&mut self, window_size_data: WindowSizeData) -> bool { let new_pixel_ratio = window_size_data.device_pixel_ratio.get(); let new_viewport_size = Size2D::new( diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 339220d3d64..f27bd303648 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -199,6 +199,7 @@ use crate::dom::xpathevaluator::XPathEvaluator; use crate::drag_data_store::{DragDataStore, Kind, Mode}; use crate::fetch::FetchCanceller; use crate::iframe_collection::IFrameCollection; +use crate::image_animation::ImageAnimationManager; use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg}; use crate::network_listener::{NetworkListener, PreInvoke}; use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; @@ -484,6 +485,8 @@ pub(crate) struct Document { animation_timeline: DomRefCell, /// Animations for this Document animations: DomRefCell, + /// Image Animation Manager for this Document + image_animation_manager: DomRefCell, /// The nearest inclusive ancestors to all the nodes that require a restyle. dirty_root: MutNullableDom, /// @@ -3877,6 +3880,7 @@ impl Document { DomRefCell::new(AnimationTimeline::new()) }, animations: DomRefCell::new(Animations::new()), + image_animation_manager: DomRefCell::new(ImageAnimationManager::new()), dirty_root: Default::default(), declarative_refresh: Default::default(), pending_animation_ticks: Default::default(), @@ -4715,6 +4719,13 @@ impl Document { self.animations().send_pending_events(self.window(), can_gc); } + pub(crate) fn image_animation_manager(&self) -> Ref { + self.image_animation_manager.borrow() + } + pub(crate) fn image_animation_manager_mut(&self) -> RefMut { + self.image_animation_manager.borrow_mut() + } + pub(crate) fn will_declaratively_refresh(&self) -> bool { self.declarative_refresh.borrow().is_some() } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index e8ba3e6b307..0479f24fac8 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -1967,6 +1967,9 @@ impl Window { pending_restyles, animation_timeline_value: document.current_animation_timeline_value(), animations: document.animations().sets.clone(), + node_to_image_animation_map: document + .image_animation_manager_mut() + .take_image_animate_set(), theme: self.theme.get(), }; @@ -2017,7 +2020,9 @@ impl Window { if !size_messages.is_empty() { self.send_to_constellation(ScriptMsg::IFrameSizes(size_messages)); } - + document + .image_animation_manager_mut() + .restore_image_animate_set(results.node_to_image_animation_map); document.update_animations_post_reflow(); self.update_constellation_epoch(); diff --git a/components/script/image_animation.rs b/components/script/image_animation.rs new file mode 100644 index 00000000000..89e4c93b828 --- /dev/null +++ b/components/script/image_animation.rs @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use fxhash::FxHashMap; +use script_layout_interface::ImageAnimationState; +use style::dom::OpaqueNode; + +#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)] +pub struct ImageAnimationManager { + #[no_trace] + pub node_to_image_map: FxHashMap, +} + +impl ImageAnimationManager { + pub fn new() -> Self { + ImageAnimationManager { + node_to_image_map: Default::default(), + } + } + + pub fn take_image_animate_set(&mut self) -> FxHashMap { + std::mem::take(&mut self.node_to_image_map) + } + + pub fn restore_image_animate_set(&mut self, map: FxHashMap) { + let _ = std::mem::replace(&mut self.node_to_image_map, map); + } +} diff --git a/components/script/lib.rs b/components/script/lib.rs index d0afb2551b0..b2ade0330c9 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -43,6 +43,7 @@ mod layout_image; pub(crate) mod document_collection; pub(crate) mod iframe_collection; +pub(crate) mod image_animation; pub mod layout_dom; mod mem; #[allow(unsafe_code)] diff --git a/components/shared/script_layout/Cargo.toml b/components/shared/script_layout/Cargo.toml index ddb4017c21c..13d50e4fafe 100644 --- a/components/shared/script_layout/Cargo.toml +++ b/components/shared/script_layout/Cargo.toml @@ -21,6 +21,7 @@ euclid = { workspace = true } fnv = { workspace = true } fonts = { path = "../../fonts" } fonts_traits = { workspace = true } +fxhash = { workspace = true } html5ever = { workspace = true } ipc-channel = { workspace = true } libc = { workspace = true } diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 9c70394b0fd..3001f9ce77d 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -24,10 +24,12 @@ use euclid::Size2D; use euclid::default::{Point2D, Rect}; use fnv::FnvHashMap; use fonts::{FontContext, SystemFontServiceProxy}; +use fxhash::FxHashMap; use ipc_channel::ipc::IpcSender; use libc::c_void; use malloc_size_of_derive::MallocSizeOf; use net_traits::image_cache::{ImageCache, PendingImageId}; +use pixels::Image; use profile_traits::mem::Report; use profile_traits::time; use script_traits::{InitialScriptState, LoadData, Painter, ScriptThreadMessage}; @@ -400,6 +402,8 @@ pub struct ReflowResult { /// to communicate them with the Constellation and also the `Window` /// element of their content pages. pub iframe_sizes: IFrameSizes, + /// The mapping of node to animated image, need to be returned to ImageAnimationManager + pub node_to_image_animation_map: FxHashMap, } /// Information needed for a script-initiated reflow. @@ -427,6 +431,8 @@ pub struct ReflowRequest { pub animation_timeline_value: f64, /// The set of animations for this document. pub animations: DocumentAnimationSet, + /// The set of image animations. + pub node_to_image_animation_map: FxHashMap, /// The theme for the window pub theme: PrefersColorScheme, } @@ -501,3 +507,25 @@ pub fn node_id_from_scroll_id(id: usize) -> Option { } None } + +#[derive(Clone, Debug, MallocSizeOf)] +pub struct ImageAnimationState { + #[ignore_malloc_size_of = "Arc is hard"] + image: Arc, + active_frame: usize, + last_update_time: f64, +} + +impl ImageAnimationState { + pub fn new(image: Arc) -> Self { + Self { + image, + active_frame: 0, + last_update_time: 0., + } + } + + pub fn image_key(&self) -> Option { + self.image.id + } +}