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