script/
image_animation.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// The set of [`AnimatingImages`] which is used to communicate the addition
28    /// and removal of animating images from layout.
29    #[no_trace]
30    animating_images: Arc<RwLock<AnimatingImages>>,
31
32    /// The [`TimerId`] of the currently scheduled animated image update callback.
33    #[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    /// After doing a layout, if the set of animating images was updated in some way,
100    /// schedule a new animation update.
101    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}