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