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