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