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