Skip to main content

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