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