1use app_units::{Au, MAX_AU};
6use base::id::{BrowsingContextId, PipelineId};
7use data_url::DataUrl;
8use embedder_traits::ViewportDetails;
9use euclid::{Scale, Size2D};
10use html5ever::local_name;
11use layout_api::wrapper_traits::ThreadSafeLayoutNode;
12use layout_api::{IFrameSize, LayoutImageDestination, SVGElementData};
13use malloc_size_of_derive::MallocSizeOf;
14use net_traits::image_cache::{Image, ImageOrMetadataAvailable, VectorImage};
15use script::layout_dom::ServoThreadSafeLayoutNode;
16use selectors::Element;
17use servo_arc::Arc as ServoArc;
18use style::Zero;
19use style::attr::AttrValue;
20use style::computed_values::object_fit::T as ObjectFit;
21use style::logical_geometry::{Direction, WritingMode};
22use style::properties::{ComputedValues, StyleBuilder};
23use style::rule_cache::RuleCacheConditions;
24use style::servo::url::ComputedUrl;
25use style::stylesheets::container_rule::ContainerSizeQuery;
26use style::values::CSSFloat;
27use style::values::computed::image::Image as ComputedImage;
28use style::values::computed::{Content, Context, ToComputedValue};
29use style::values::generics::counters::{GenericContentItem, GenericContentItems};
30use url::Url;
31use webrender_api::ImageKey;
32
33use crate::cell::ArcRefCell;
34use crate::context::{LayoutContext, LayoutImageCacheResult};
35use crate::dom::NodeExt;
36use crate::fragment_tree::{
37 BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment, IFrameFragment, ImageFragment,
38};
39use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize};
40use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
41use crate::sizing::{
42 ComputeInlineContentSizes, InlineContentSizesResult, LazySize, SizeConstraint,
43};
44use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, LayoutStyle};
45use crate::{ConstraintSpace, ContainingBlock};
46
47#[derive(Debug, MallocSizeOf)]
48pub(crate) struct ReplacedContents {
49 pub kind: ReplacedContentKind,
50 natural_size: NaturalSizes,
51 base_fragment_info: BaseFragmentInfo,
52}
53
54#[derive(Debug, MallocSizeOf)]
72pub(crate) struct NaturalSizes {
73 pub width: Option<Au>,
74 pub height: Option<Au>,
75 pub ratio: Option<CSSFloat>,
76}
77
78impl NaturalSizes {
79 pub(crate) fn from_width_and_height(width: f32, height: f32) -> Self {
80 let ratio = if width.is_normal() && height.is_normal() {
84 Some(width / height)
85 } else {
86 None
87 };
88
89 Self {
90 width: Some(Au::from_f32_px(width)),
91 height: Some(Au::from_f32_px(height)),
92 ratio,
93 }
94 }
95
96 pub(crate) fn from_natural_size_in_dots(natural_size_in_dots: PhysicalSize<f64>) -> Self {
97 let dppx = 1.0;
101 let width = natural_size_in_dots.width as f32 / dppx;
102 let height = natural_size_in_dots.height as f32 / dppx;
103 Self::from_width_and_height(width, height)
104 }
105
106 pub(crate) fn empty() -> Self {
107 Self {
108 width: None,
109 height: None,
110 ratio: None,
111 }
112 }
113}
114
115#[derive(Debug, MallocSizeOf)]
116pub(crate) struct CanvasInfo {
117 pub source: Option<ImageKey>,
118}
119
120#[derive(Debug, MallocSizeOf)]
121pub(crate) struct IFrameInfo {
122 pub pipeline_id: PipelineId,
123 pub browsing_context_id: BrowsingContextId,
124}
125
126#[derive(Debug, MallocSizeOf)]
127pub(crate) struct VideoInfo {
128 pub image_key: webrender_api::ImageKey,
129}
130
131#[derive(Debug, MallocSizeOf)]
132pub(crate) enum ReplacedContentKind {
133 Image(Option<Image>, bool ),
134 IFrame(IFrameInfo),
135 Canvas(CanvasInfo),
136 Video(Option<VideoInfo>),
137 SVGElement(Option<VectorImage>),
138 Audio,
139}
140
141impl ReplacedContents {
142 pub fn for_element(
143 node: ServoThreadSafeLayoutNode<'_>,
144 context: &LayoutContext,
145 ) -> Option<Self> {
146 if let Some(ref data_attribute_string) = node.as_typeless_object_with_data_attribute() {
147 if let Some(url) = try_to_parse_image_data_url(data_attribute_string) {
148 return Self::from_image_url(
149 node,
150 context,
151 &ComputedUrl::Valid(ServoArc::new(url)),
152 );
153 }
154 }
155
156 let (kind, natural_size) = {
157 if let Some((image, natural_size_in_dots)) = node.as_image() {
158 if let Some(content_image) = Self::from_content_property(node, context) {
159 return Some(content_image);
160 }
161 (
162 ReplacedContentKind::Image(image, node.showing_broken_image_icon()),
163 NaturalSizes::from_natural_size_in_dots(natural_size_in_dots),
164 )
165 } else if let Some((canvas_info, natural_size_in_dots)) = node.as_canvas() {
166 (
167 ReplacedContentKind::Canvas(canvas_info),
168 NaturalSizes::from_natural_size_in_dots(natural_size_in_dots),
169 )
170 } else if let Some((pipeline_id, browsing_context_id)) = node.as_iframe() {
171 (
172 ReplacedContentKind::IFrame(IFrameInfo {
173 pipeline_id,
174 browsing_context_id,
175 }),
176 NaturalSizes::empty(),
177 )
178 } else if let Some((image_key, natural_size_in_dots)) = node.as_video() {
179 (
180 ReplacedContentKind::Video(image_key.map(|key| VideoInfo { image_key: key })),
181 natural_size_in_dots
182 .map_or_else(NaturalSizes::empty, NaturalSizes::from_natural_size_in_dots),
183 )
184 } else if let Some(svg_data) = node.as_svg() {
185 Self::svg_kind_size(svg_data, context, node)?
186 } else if node
187 .as_html_element()
188 .is_some_and(|element| element.has_local_name(&local_name!("audio")))
189 {
190 let natural_size = NaturalSizes {
191 width: None,
192 height: Some(Au::from_px(40)),
195 ratio: None,
196 };
197 (ReplacedContentKind::Audio, natural_size)
198 } else {
199 return Self::from_content_property(node, context);
200 }
201 };
202
203 if let ReplacedContentKind::Image(Some(Image::Raster(ref image)), _) = kind {
204 context
205 .image_resolver
206 .handle_animated_image(node.opaque(), image.clone());
207 }
208
209 Some(Self {
210 kind,
211 natural_size,
212 base_fragment_info: node.into(),
213 })
214 }
215
216 fn svg_kind_size(
217 svg_data: SVGElementData,
218 context: &LayoutContext,
219 node: ServoThreadSafeLayoutNode<'_>,
220 ) -> Option<(ReplacedContentKind, NaturalSizes)> {
221 let rule_cache_conditions = &mut RuleCacheConditions::default();
222
223 let parent_style = node.style(&context.style_context);
224 let style_builder = StyleBuilder::new(
225 context.style_context.stylist.device(),
226 Some(context.style_context.stylist),
227 Some(&parent_style),
228 None,
229 None,
230 false,
231 );
232
233 let to_computed_context = Context::new(
234 style_builder,
235 context.style_context.quirks_mode(),
236 rule_cache_conditions,
237 ContainerSizeQuery::none(),
238 );
239
240 let attr_to_computed = |attr_val: &AttrValue| {
241 if let AttrValue::Length(_, length) = attr_val {
242 length.to_computed_value(&to_computed_context)
243 } else {
244 None
245 }
246 };
247 let width = svg_data.width.and_then(attr_to_computed);
248 let height = svg_data.height.and_then(attr_to_computed);
249
250 let ratio = match (width, height) {
251 (Some(width), Some(height)) if !width.is_zero() && !height.is_zero() => {
252 Some(width.px() / height.px())
253 },
254 _ => svg_data.ratio_from_view_box(),
255 };
256
257 let natural_size = NaturalSizes {
258 width: width.map(|w| Au::from_f32_px(w.px())),
259 height: height.map(|h| Au::from_f32_px(h.px())),
260 ratio,
261 };
262
263 let svg_source = match svg_data.source {
264 None => {
265 context
268 .image_resolver
269 .queue_svg_element_for_serialization(node);
270 return None;
271 },
272 Some(Err(_)) => {
273 return None;
275 },
276 Some(Ok(svg_source)) => svg_source,
277 };
278
279 let result = context
280 .image_resolver
281 .get_cached_image_for_url(
282 node.opaque(),
283 svg_source,
284 LayoutImageDestination::BoxTreeConstruction,
285 )
286 .ok();
287
288 let vector_image = result.map(|result| match result {
289 Image::Vector(mut vector_image) => {
290 vector_image.svg_id = Some(svg_data.svg_id);
291 vector_image
292 },
293 _ => unreachable!("SVG element can't contain a raster image."),
294 });
295
296 Some((ReplacedContentKind::SVGElement(vector_image), natural_size))
297 }
298
299 fn from_content_property(
300 node: ServoThreadSafeLayoutNode<'_>,
301 context: &LayoutContext,
302 ) -> Option<Self> {
303 if let Content::Items(GenericContentItems { items, .. }) =
306 node.style(&context.style_context).clone_content()
307 {
308 if let [GenericContentItem::Image(image)] = items.as_slice() {
309 return Some(
311 Self::from_image(node, context, image)
312 .unwrap_or_else(|| Self::zero_sized_invalid_image(node)),
313 );
314 }
315 }
316 None
317 }
318
319 pub fn from_image_url(
320 node: ServoThreadSafeLayoutNode<'_>,
321 context: &LayoutContext,
322 image_url: &ComputedUrl,
323 ) -> Option<Self> {
324 if let ComputedUrl::Valid(image_url) = image_url {
325 let (image, width, height) = match context.image_resolver.get_or_request_image_or_meta(
326 node.opaque(),
327 image_url.clone().into(),
328 LayoutImageDestination::BoxTreeConstruction,
329 ) {
330 LayoutImageCacheResult::DataAvailable(img_or_meta) => match img_or_meta {
331 ImageOrMetadataAvailable::ImageAvailable { image, .. } => {
332 if let Image::Raster(image) = &image {
333 context
334 .image_resolver
335 .handle_animated_image(node.opaque(), image.clone());
336 }
337 let metadata = image.metadata();
338 (
339 Some(image.clone()),
340 metadata.width as f32,
341 metadata.height as f32,
342 )
343 },
344 ImageOrMetadataAvailable::MetadataAvailable(metadata, _id) => {
345 (None, metadata.width as f32, metadata.height as f32)
346 },
347 },
348 LayoutImageCacheResult::Pending | LayoutImageCacheResult::LoadError => return None,
349 };
350
351 return Some(Self {
352 kind: ReplacedContentKind::Image(image, false ),
353 natural_size: NaturalSizes::from_width_and_height(width, height),
354 base_fragment_info: node.into(),
355 });
356 }
357 None
358 }
359
360 pub fn from_image(
361 element: ServoThreadSafeLayoutNode<'_>,
362 context: &LayoutContext,
363 image: &ComputedImage,
364 ) -> Option<Self> {
365 match image {
366 ComputedImage::Url(image_url) => Self::from_image_url(element, context, image_url),
367 _ => None, }
369 }
370
371 pub(crate) fn zero_sized_invalid_image(node: ServoThreadSafeLayoutNode<'_>) -> Self {
372 Self {
373 kind: ReplacedContentKind::Image(None, false ),
374 natural_size: NaturalSizes::from_width_and_height(0., 0.),
375 base_fragment_info: node.into(),
376 }
377 }
378
379 #[inline]
380 fn is_broken_image(&self) -> bool {
381 matches!(self.kind, ReplacedContentKind::Image(_, true))
382 }
383
384 #[inline]
385 fn content_size(
386 &self,
387 axis: Direction,
388 preferred_aspect_ratio: Option<AspectRatio>,
389 get_size_in_opposite_axis: &dyn Fn() -> SizeConstraint,
390 get_fallback_size: &dyn Fn() -> Au,
391 ) -> Au {
392 let Some(ratio) = preferred_aspect_ratio else {
393 return get_fallback_size();
394 };
395 let transfer = |size| ratio.compute_dependent_size(axis, size);
396 match get_size_in_opposite_axis() {
397 SizeConstraint::Definite(size) => transfer(size),
398 SizeConstraint::MinMax(min_size, max_size) => get_fallback_size()
399 .clamp_between_extremums(transfer(min_size), max_size.map(transfer)),
400 }
401 }
402
403 fn calculate_fragment_rect(
404 &self,
405 style: &ServoArc<ComputedValues>,
406 size: PhysicalSize<Au>,
407 ) -> (PhysicalSize<Au>, PhysicalRect<Au>) {
408 if let ReplacedContentKind::Image(Some(Image::Raster(image)), true) = &self.kind {
409 let size = Size2D::new(
410 Au::from_f32_px(image.metadata.width as f32),
411 Au::from_f32_px(image.metadata.height as f32),
412 )
413 .min(size);
414 return (PhysicalSize::zero(), size.into());
415 }
416
417 let natural_size = PhysicalSize::new(
418 self.natural_size.width.unwrap_or(size.width),
419 self.natural_size.height.unwrap_or(size.height),
420 );
421
422 let object_fit_size = self.natural_size.ratio.map_or(size, |width_over_height| {
423 let preserve_aspect_ratio_with_comparison =
424 |size: PhysicalSize<Au>, comparison: fn(&Au, &Au) -> bool| {
425 let candidate_width = size.height.scale_by(width_over_height);
426 if comparison(&candidate_width, &size.width) {
427 return PhysicalSize::new(candidate_width, size.height);
428 }
429
430 let candidate_height = size.width.scale_by(1. / width_over_height);
431 debug_assert!(comparison(&candidate_height, &size.height));
432 PhysicalSize::new(size.width, candidate_height)
433 };
434
435 match style.clone_object_fit() {
436 ObjectFit::Fill => size,
437 ObjectFit::Contain => preserve_aspect_ratio_with_comparison(size, PartialOrd::le),
438 ObjectFit::Cover => preserve_aspect_ratio_with_comparison(size, PartialOrd::ge),
439 ObjectFit::None => natural_size,
440 ObjectFit::ScaleDown => {
441 preserve_aspect_ratio_with_comparison(size.min(natural_size), PartialOrd::le)
442 },
443 }
444 });
445
446 let object_position = style.clone_object_position();
447 let horizontal_position = object_position
448 .horizontal
449 .to_used_value(size.width - object_fit_size.width);
450 let vertical_position = object_position
451 .vertical
452 .to_used_value(size.height - object_fit_size.height);
453
454 let object_position = PhysicalPoint::new(horizontal_position, vertical_position);
455 (
456 object_fit_size,
457 PhysicalRect::new(object_position, object_fit_size),
458 )
459 }
460
461 pub fn make_fragments(
462 &self,
463 layout_context: &LayoutContext,
464 style: &ServoArc<ComputedValues>,
465 size: PhysicalSize<Au>,
466 ) -> Vec<Fragment> {
467 let (object_fit_size, rect) = self.calculate_fragment_rect(style, size);
468 let clip = PhysicalRect::new(PhysicalPoint::origin(), size);
469
470 let mut base = BaseFragment::new(self.base_fragment_info, style.clone().into(), rect);
471 match &self.kind {
472 ReplacedContentKind::Image(image, showing_broken_image_icon) => image
473 .as_ref()
474 .and_then(|image| match image {
475 Image::Raster(raster_image) => raster_image.id,
476 Image::Vector(vector_image) => {
477 let scale = layout_context.style_context.device_pixel_ratio();
478 let width = object_fit_size.width.scale_by(scale.0).to_px();
479 let height = object_fit_size.height.scale_by(scale.0).to_px();
480 let size = Size2D::new(width, height);
481 let tag = self.base_fragment_info.tag?;
482 layout_context
483 .image_resolver
484 .rasterize_vector_image(
485 vector_image.id,
486 size,
487 tag.node,
488 vector_image.svg_id.clone(),
489 )
490 .and_then(|i| i.id)
491 },
492 })
493 .map(|image_key| {
494 Fragment::Image(ArcRefCell::new(ImageFragment {
495 base,
496 clip,
497 image_key: Some(image_key),
498 showing_broken_image_icon: *showing_broken_image_icon,
499 }))
500 })
501 .into_iter()
502 .collect(),
503 ReplacedContentKind::Video(video) => {
504 vec![Fragment::Image(ArcRefCell::new(ImageFragment {
505 base,
506 clip,
507 image_key: video.as_ref().map(|video| video.image_key),
508 showing_broken_image_icon: false,
509 }))]
510 },
511 ReplacedContentKind::IFrame(iframe) => {
512 let size = Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px());
513 let hidpi_scale_factor = layout_context.style_context.device_pixel_ratio();
514
515 layout_context.iframe_sizes.lock().insert(
516 iframe.browsing_context_id,
517 IFrameSize {
518 browsing_context_id: iframe.browsing_context_id,
519 pipeline_id: iframe.pipeline_id,
520 viewport_details: ViewportDetails {
521 size,
522 hidpi_scale_factor: Scale::new(hidpi_scale_factor.0),
523 },
524 },
525 );
526 vec![Fragment::IFrame(ArcRefCell::new(IFrameFragment {
527 base,
528 pipeline_id: iframe.pipeline_id,
529 }))]
530 },
531 ReplacedContentKind::Canvas(canvas_info) => {
532 if self.natural_size.width == Some(Au::zero()) ||
533 self.natural_size.height == Some(Au::zero())
534 {
535 return vec![];
536 }
537
538 let Some(image_key) = canvas_info.source else {
539 return vec![];
540 };
541
542 vec![Fragment::Image(ArcRefCell::new(ImageFragment {
543 base,
544 clip,
545 image_key: Some(image_key),
546 showing_broken_image_icon: false,
547 }))]
548 },
549 ReplacedContentKind::SVGElement(vector_image) => {
550 let Some(vector_image) = vector_image else {
551 return vec![];
552 };
553
554 base.rect = PhysicalSize::new(
556 vector_image
557 .metadata
558 .width
559 .try_into()
560 .map_or(MAX_AU, Au::from_px),
561 vector_image
562 .metadata
563 .height
564 .try_into()
565 .map_or(MAX_AU, Au::from_px),
566 )
567 .into();
568
569 let scale = layout_context.style_context.device_pixel_ratio();
570 let raster_size = Size2D::new(
571 base.rect.size.width.scale_by(scale.0).to_px(),
572 base.rect.size.height.scale_by(scale.0).to_px(),
573 );
574
575 let tag = self.base_fragment_info.tag.unwrap();
576 layout_context
577 .image_resolver
578 .rasterize_vector_image(
579 vector_image.id,
580 raster_size,
581 tag.node,
582 vector_image.svg_id.clone(),
583 )
584 .and_then(|image| image.id)
585 .map(|image_key| {
586 Fragment::Image(ArcRefCell::new(ImageFragment {
587 base,
588 clip,
589 image_key: Some(image_key),
590 showing_broken_image_icon: false,
591 }))
592 })
593 .into_iter()
594 .collect()
595 },
596 ReplacedContentKind::Audio => vec![],
597 }
598 }
599
600 pub(crate) fn preferred_aspect_ratio(
601 &self,
602 style: &ComputedValues,
603 padding_border_sums: &LogicalVec2<Au>,
604 ) -> Option<AspectRatio> {
605 if matches!(self.kind, ReplacedContentKind::Audio) {
606 return None;
609 }
610 if self.is_broken_image() {
611 style.preferred_aspect_ratio(None, padding_border_sums)
617 } else {
618 style.preferred_aspect_ratio(self.natural_size.ratio, padding_border_sums)
619 }
620 }
621
622 pub(crate) fn fallback_inline_size(&self, writing_mode: WritingMode) -> Au {
628 if writing_mode.is_horizontal() {
629 self.natural_size.width.unwrap_or_else(|| Au::from_px(300))
630 } else {
631 self.natural_size.height.unwrap_or_else(|| Au::from_px(150))
632 }
633 }
634
635 pub(crate) fn fallback_block_size(&self, writing_mode: WritingMode) -> Au {
641 if writing_mode.is_horizontal() {
642 self.natural_size.height.unwrap_or_else(|| Au::from_px(150))
643 } else {
644 self.natural_size.width.unwrap_or_else(|| Au::from_px(300))
645 }
646 }
647
648 pub(crate) fn logical_natural_sizes(
649 &self,
650 writing_mode: WritingMode,
651 ) -> LogicalVec2<Option<Au>> {
652 if writing_mode.is_horizontal() {
653 LogicalVec2 {
654 inline: self.natural_size.width,
655 block: self.natural_size.height,
656 }
657 } else {
658 LogicalVec2 {
659 inline: self.natural_size.height,
660 block: self.natural_size.width,
661 }
662 }
663 }
664
665 #[inline]
666 pub(crate) fn layout_style<'a>(&self, base: &'a LayoutBoxBase) -> LayoutStyle<'a> {
667 LayoutStyle::Default(&base.style)
668 }
669
670 pub(crate) fn layout(
671 &self,
672 layout_context: &LayoutContext,
673 containing_block_for_children: &ContainingBlock,
674 preferred_aspect_ratio: Option<AspectRatio>,
675 base: &LayoutBoxBase,
676 lazy_block_size: &LazySize,
677 ) -> CacheableLayoutResult {
678 let writing_mode = base.style.writing_mode;
679 let inline_size = containing_block_for_children.size.inline;
680 let content_block_size = self.content_size(
681 Direction::Block,
682 preferred_aspect_ratio,
683 &|| SizeConstraint::Definite(inline_size),
684 &|| self.fallback_block_size(writing_mode),
685 );
686 let size = LogicalVec2 {
687 inline: inline_size,
688 block: lazy_block_size.resolve(|| content_block_size),
689 }
690 .to_physical_size(writing_mode);
691 CacheableLayoutResult {
692 baselines: Default::default(),
693 collapsible_margins_in_children: CollapsedBlockMargins::zero(),
694 content_block_size,
695 content_inline_size_for_table: None,
696 depends_on_block_constraints: true,
699 fragments: self.make_fragments(layout_context, &base.style, size),
700 specific_layout_info: None,
701 }
702 }
703}
704
705impl ComputeInlineContentSizes for ReplacedContents {
706 fn compute_inline_content_sizes(
707 &self,
708 _: &LayoutContext,
709 constraint_space: &ConstraintSpace,
710 ) -> InlineContentSizesResult {
711 let inline_content_size = self.content_size(
712 Direction::Inline,
713 constraint_space.preferred_aspect_ratio,
714 &|| constraint_space.block_size,
715 &|| self.fallback_inline_size(constraint_space.style.writing_mode),
716 );
717 InlineContentSizesResult {
718 sizes: inline_content_size.into(),
719 depends_on_block_constraints: constraint_space.preferred_aspect_ratio.is_some(),
720 }
721 }
722}
723
724fn try_to_parse_image_data_url(string: &str) -> Option<Url> {
725 if !string.starts_with("data:") {
726 return None;
727 }
728 let data_url = DataUrl::process(string).ok()?;
729 let mime_type = data_url.mime_type();
730 if mime_type.type_ != "image" {
731 return None;
732 }
733
734 if !matches!(
737 mime_type.subtype.as_str(),
738 "png" | "jpeg" | "gif" | "webp" | "bmp" | "ico"
739 ) {
740 return None;
741 }
742
743 Url::parse(string).ok()
744}