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