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