script/
image_animation.rs1use std::cell::Cell;
6use std::sync::Arc;
7use std::time::Duration;
8
9use compositing_traits::{ImageUpdate, SerializableImageData};
10use ipc_channel::ipc::IpcSharedMemory;
11use layout_api::AnimatingImages;
12use malloc_size_of::MallocSizeOf;
13use parking_lot::RwLock;
14use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
15use timers::{TimerEventRequest, TimerId};
16use webrender_api::units::DeviceIntSize;
17use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
18
19use crate::dom::bindings::refcounted::Trusted;
20use crate::dom::node::Node;
21use crate::dom::window::Window;
22use crate::script_thread::with_script_thread;
23
24#[derive(Clone, Default, JSTraceable)]
25#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
26pub struct ImageAnimationManager {
27 #[no_trace]
30 animating_images: Arc<RwLock<AnimatingImages>>,
31
32 #[no_trace]
34 callback_timer_id: Cell<Option<TimerId>>,
35}
36
37impl MallocSizeOf for ImageAnimationManager {
38 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
39 (*self.animating_images.read()).size_of(ops)
40 }
41}
42
43impl ImageAnimationManager {
44 pub(crate) fn animating_images(&self) -> Arc<RwLock<AnimatingImages>> {
45 self.animating_images.clone()
46 }
47
48 fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
49 self.animating_images
50 .read()
51 .node_to_state_map
52 .values()
53 .map(|state| state.duration_to_next_frame(now))
54 .min()
55 }
56
57 pub(crate) fn update_active_frames(&self, window: &Window, now: f64) {
58 if self.animating_images.read().is_empty() {
59 return;
60 }
61
62 let updates = self
63 .animating_images
64 .write()
65 .node_to_state_map
66 .values_mut()
67 .filter_map(|state| {
68 if !state.update_frame_for_animation_timeline_value(now) {
69 return None;
70 }
71
72 let image = &state.image;
73 let frame = image
74 .frame(state.active_frame)
75 .expect("active_frame should within range of frames");
76
77 Some(ImageUpdate::UpdateImage(
78 image.id.unwrap(),
79 ImageDescriptor {
80 format: ImageFormat::BGRA8,
81 size: DeviceIntSize::new(
82 image.metadata.width as i32,
83 image.metadata.height as i32,
84 ),
85 stride: None,
86 offset: 0,
87 flags: ImageDescriptorFlags::ALLOW_MIPMAPS,
88 },
89 SerializableImageData::Raw(IpcSharedMemory::from_bytes(frame.bytes)),
90 None,
91 ))
92 })
93 .collect();
94 window.compositor_api().update_images(updates);
95
96 self.maybe_schedule_update(window, now);
97 }
98
99 pub(crate) fn maybe_schedule_update_after_layout(&self, window: &Window, now: f64) {
102 if self.animating_images().write().clear_dirty() {
103 self.maybe_schedule_update(window, now);
104 }
105 }
106
107 fn maybe_schedule_update(&self, window: &Window, now: f64) {
108 with_script_thread(|script_thread| {
109 if let Some(current_timer_id) = self.callback_timer_id.take() {
110 self.callback_timer_id.set(None);
111 script_thread.cancel_timer(current_timer_id);
112 }
113
114 if let Some(duration) = self.duration_to_next_frame(now) {
115 let trusted_window = Trusted::new(window);
116 let timer_id = script_thread.schedule_timer(TimerEventRequest {
117 callback: Box::new(move || {
118 let window = trusted_window.root();
119 window.Document().set_has_pending_animated_image_update();
120 }),
121 duration,
122 });
123 self.callback_timer_id.set(Some(timer_id));
124 }
125 })
126 }
127
128 pub(crate) fn cancel_animations_for_node(&self, node: &Node) {
129 self.animating_images().write().remove(node.to_opaque());
130 }
131}