layout/
context.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::sync::Arc;
6
7use embedder_traits::UntrustedNodeAddress;
8use euclid::Size2D;
9use fnv::FnvHashMap;
10use fonts::FontContext;
11use layout_api::wrapper_traits::ThreadSafeLayoutNode;
12use layout_api::{
13    IFrameSizes, ImageAnimationState, PendingImage, PendingImageState, PendingRasterizationImage,
14};
15use net_traits::image_cache::{
16    Image as CachedImage, ImageCache, ImageCacheResult, ImageOrMetadataAvailable, PendingImageId,
17    UsePlaceholder,
18};
19use parking_lot::{Mutex, RwLock};
20use pixels::RasterImage;
21use rustc_hash::FxHashMap;
22use script::layout_dom::ServoThreadSafeLayoutNode;
23use servo_url::{ImmutableOrigin, ServoUrl};
24use style::context::SharedStyleContext;
25use style::dom::OpaqueNode;
26use style::values::computed::image::{Gradient, Image};
27use webrender_api::units::{DeviceIntSize, DeviceSize};
28
29pub(crate) type CachedImageOrError = Result<CachedImage, ResolveImageError>;
30
31pub(crate) struct LayoutContext<'a> {
32    pub use_rayon: bool,
33
34    /// Bits shared by the layout and style system.
35    pub style_context: SharedStyleContext<'a>,
36
37    /// A FontContext to be used during layout.
38    pub font_context: Arc<FontContext>,
39
40    /// A collection of `<iframe>` sizes to send back to script.
41    pub iframe_sizes: Mutex<IFrameSizes>,
42
43    /// An [`ImageResolver`] used for resolving images during box and fragment
44    /// tree construction. Later passed to display list construction.
45    pub image_resolver: Arc<ImageResolver>,
46}
47
48pub enum ResolvedImage<'a> {
49    Gradient(&'a Gradient),
50    // The size is tracked explicitly as image-set images can specify their
51    // natural resolution which affects the final size for raster images.
52    Image {
53        image: CachedImage,
54        size: DeviceSize,
55    },
56}
57
58#[derive(Clone, Copy, Debug)]
59pub enum ResolveImageError {
60    LoadError,
61    ImagePending,
62    OnlyMetadata,
63    InvalidUrl,
64    MissingNode,
65    ImageMissingFromImageSet,
66    NotImplementedYet,
67    None,
68}
69
70pub(crate) enum LayoutImageCacheResult {
71    Pending,
72    DataAvailable(ImageOrMetadataAvailable),
73    LoadError,
74}
75
76pub(crate) struct ImageResolver {
77    /// The origin of the `Document` that this [`ImageResolver`] resolves images for.
78    pub origin: ImmutableOrigin,
79
80    /// Reference to the script thread image cache.
81    pub image_cache: Arc<dyn ImageCache>,
82
83    /// A list of in-progress image loads to be shared with the script thread.
84    pub pending_images: Mutex<Vec<PendingImage>>,
85
86    /// A list of fully loaded vector images that need to be rasterized to a specific
87    /// size determined by layout. This will be shared with the script thread.
88    pub pending_rasterization_images: Mutex<Vec<PendingRasterizationImage>>,
89
90    /// A list of `SVGSVGElement`s encountered during layout that are not
91    /// serialized yet. This is needed to support inline SVGs as they are treated
92    /// as replaced elements and the layout is responsible for triggering the
93    /// network load for the corresponding serialzed data: urls (similar to
94    /// background images).
95    pub pending_svg_elements_for_serialization: Mutex<Vec<UntrustedNodeAddress>>,
96
97    /// A shared reference to script's map of DOM nodes with animated images. This is used
98    /// to manage image animations in script and inform the script about newly animating
99    /// nodes.
100    pub node_to_animating_image_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
101
102    // A cache that maps image resources used in CSS (e.g as the `url()` value
103    // for `background-image` or `content` property) to the final resolved image data.
104    pub resolved_images_cache:
105        Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
106
107    /// The current animation timeline value used to properly initialize animating images.
108    pub animation_timeline_value: f64,
109}
110
111impl Drop for ImageResolver {
112    fn drop(&mut self) {
113        if !std::thread::panicking() {
114            assert!(self.pending_images.lock().is_empty());
115            assert!(self.pending_rasterization_images.lock().is_empty());
116            assert!(
117                self.pending_svg_elements_for_serialization
118                    .lock()
119                    .is_empty()
120            );
121        }
122    }
123}
124
125impl ImageResolver {
126    pub(crate) fn get_or_request_image_or_meta(
127        &self,
128        node: OpaqueNode,
129        url: ServoUrl,
130        use_placeholder: UsePlaceholder,
131    ) -> LayoutImageCacheResult {
132        // Check for available image or start tracking.
133        let cache_result = self.image_cache.get_cached_image_status(
134            url.clone(),
135            self.origin.clone(),
136            None,
137            use_placeholder,
138        );
139
140        match cache_result {
141            ImageCacheResult::Available(img_or_meta) => {
142                LayoutImageCacheResult::DataAvailable(img_or_meta)
143            },
144            // Image has been requested, is still pending. Return no image for this paint loop.
145            // When the image loads it will trigger a reflow and/or repaint.
146            ImageCacheResult::Pending(id) => {
147                let image = PendingImage {
148                    state: PendingImageState::PendingResponse,
149                    node: node.into(),
150                    id,
151                    origin: self.origin.clone(),
152                };
153                self.pending_images.lock().push(image);
154                LayoutImageCacheResult::Pending
155            },
156            // Not yet requested - request image or metadata from the cache
157            ImageCacheResult::ReadyForRequest(id) => {
158                let image = PendingImage {
159                    state: PendingImageState::Unrequested(url),
160                    node: node.into(),
161                    id,
162                    origin: self.origin.clone(),
163                };
164                self.pending_images.lock().push(image);
165                LayoutImageCacheResult::Pending
166            },
167            // Image failed to load, so just return the same error.
168            ImageCacheResult::LoadError => LayoutImageCacheResult::LoadError,
169        }
170    }
171
172    pub(crate) fn handle_animated_image(&self, node: OpaqueNode, image: Arc<RasterImage>) {
173        let mut map = self.node_to_animating_image_map.write();
174        if !image.should_animate() {
175            map.remove(&node);
176            return;
177        }
178        let new_image_animation_state =
179            || ImageAnimationState::new(image.clone(), self.animation_timeline_value);
180
181        let entry = map.entry(node).or_insert_with(new_image_animation_state);
182
183        // If the entry exists, but it is for a different image id, replace it as the image
184        // has changed during this layout.
185        if entry.image.id != image.id {
186            *entry = new_image_animation_state();
187        }
188    }
189
190    pub(crate) fn get_cached_image_for_url(
191        &self,
192        node: OpaqueNode,
193        url: ServoUrl,
194        use_placeholder: UsePlaceholder,
195    ) -> Result<CachedImage, ResolveImageError> {
196        if let Some(cached_image) = self
197            .resolved_images_cache
198            .read()
199            .get(&(url.clone(), use_placeholder))
200        {
201            return cached_image.clone();
202        }
203
204        let result = self.get_or_request_image_or_meta(node, url.clone(), use_placeholder);
205        match result {
206            LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
207                ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
208                    if let Some(image) = image.as_raster_image() {
209                        self.handle_animated_image(node, image.clone());
210                    }
211
212                    let mut resolved_images_cache = self.resolved_images_cache.write();
213                    resolved_images_cache.insert((url, use_placeholder), Ok(image.clone()));
214                    Ok(image)
215                },
216                ImageOrMetadataAvailable::MetadataAvailable(..) => {
217                    Result::Err(ResolveImageError::OnlyMetadata)
218                },
219            },
220            LayoutImageCacheResult::Pending => Result::Err(ResolveImageError::ImagePending),
221            LayoutImageCacheResult::LoadError => {
222                let error = Err(ResolveImageError::LoadError);
223                self.resolved_images_cache
224                    .write()
225                    .insert((url, use_placeholder), error.clone());
226                error
227            },
228        }
229    }
230
231    pub(crate) fn rasterize_vector_image(
232        &self,
233        image_id: PendingImageId,
234        size: DeviceIntSize,
235        node: OpaqueNode,
236    ) -> Option<RasterImage> {
237        let result = self.image_cache.rasterize_vector_image(image_id, size);
238        if result.is_none() {
239            self.pending_rasterization_images
240                .lock()
241                .push(PendingRasterizationImage {
242                    id: image_id,
243                    node: node.into(),
244                    size,
245                });
246        }
247        result
248    }
249
250    pub(crate) fn queue_svg_element_for_serialization(
251        &self,
252        element: ServoThreadSafeLayoutNode<'_>,
253    ) {
254        self.pending_svg_elements_for_serialization
255            .lock()
256            .push(element.opaque().into())
257    }
258
259    pub(crate) fn resolve_image<'a>(
260        &self,
261        node: Option<OpaqueNode>,
262        image: &'a Image,
263    ) -> Result<ResolvedImage<'a>, ResolveImageError> {
264        match image {
265            // TODO: Add support for PaintWorklet and CrossFade rendering.
266            Image::None => Result::Err(ResolveImageError::None),
267            Image::CrossFade(_) => Result::Err(ResolveImageError::NotImplementedYet),
268            Image::PaintWorklet(_) => Result::Err(ResolveImageError::NotImplementedYet),
269            Image::Gradient(gradient) => Ok(ResolvedImage::Gradient(gradient)),
270            Image::Url(image_url) => {
271                // FIXME: images won’t always have in intrinsic width or
272                // height when support for SVG is added, or a WebRender
273                // `ImageKey`, for that matter.
274                //
275                // FIXME: It feels like this should take into account the pseudo
276                // element and not just the node.
277                let image_url = image_url.url().ok_or(ResolveImageError::InvalidUrl)?;
278                let node = node.ok_or(ResolveImageError::MissingNode)?;
279                let image = self.get_cached_image_for_url(
280                    node,
281                    image_url.clone().into(),
282                    UsePlaceholder::No,
283                )?;
284                let metadata = image.metadata();
285                let size = Size2D::new(metadata.width, metadata.height).to_f32();
286                Ok(ResolvedImage::Image { image, size })
287            },
288            Image::ImageSet(image_set) => {
289                image_set
290                    .items
291                    .get(image_set.selected_index)
292                    .ok_or(ResolveImageError::ImageMissingFromImageSet)
293                    .and_then(|image| {
294                        self.resolve_image(node, &image.image)
295                            .map(|info| match info {
296                                ResolvedImage::Image {
297                                    image: cached_image,
298                                    ..
299                                } => {
300                                    // From <https://drafts.csswg.org/css-images-4/#image-set-notation>:
301                                    // > A <resolution> (optional). This is used to help the UA decide
302                                    // > which <image-set-option> to choose. If the image reference is
303                                    // > for a raster image, it also specifies the image’s natural
304                                    // > resolution, overriding any other source of data that might
305                                    // > supply a natural resolution.
306                                    let image_metadata = cached_image.metadata();
307                                    let size = if cached_image.as_raster_image().is_some() {
308                                        let scale_factor = image.resolution.dppx();
309                                        Size2D::new(
310                                            image_metadata.width as f32 / scale_factor,
311                                            image_metadata.height as f32 / scale_factor,
312                                        )
313                                    } else {
314                                        Size2D::new(image_metadata.width, image_metadata.height)
315                                            .to_f32()
316                                    };
317
318                                    ResolvedImage::Image {
319                                        image: cached_image,
320                                        size,
321                                    }
322                                },
323                                _ => info,
324                            })
325                    })
326            },
327            Image::LightDark(..) => unreachable!("light-dark() should be disabled"),
328        }
329    }
330}