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