1use std::cell::{OnceCell, RefCell};
6use std::sync::Arc;
7
8use app_units::{AU_PER_PX, Au};
9use clip::Clip;
10pub(crate) use clip::ClipId;
11use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
12use fonts::ShapedTextSlice;
13use gradient::WebRenderGradient;
14use layout_api::ReflowStatistics;
15use net_traits::image_cache::Image as CachedImage;
16use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
17use servo_arc::Arc as ServoArc;
18use servo_base::id::{PipelineId, ScrollTreeNodeId};
19use servo_config::opts::{DiagnosticsLogging, DiagnosticsLoggingOption};
20use servo_config::{pref, prefs};
21use servo_url::ServoUrl;
22use style::Zero;
23use style::color::{AbsoluteColor, ColorSpace};
24use style::computed_values::background_blend_mode::SingleComputedValue as BackgroundBlendMode;
25use style::computed_values::border_image_outset::T as BorderImageOutset;
26use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
27use style::computed_values::overflow_x::T as ComputedOverflow;
28use style::computed_values::text_decoration_style::{
29 T as ComputedTextDecorationStyle, T as TextDecorationStyle,
30};
31use style::dom::OpaqueNode;
32use style::properties::ComputedValues;
33use style::properties::longhands::visibility::computed_value::T as Visibility;
34use style::properties::style_structs::Border;
35use style::values::computed::basic_shape::ClipPath;
36use style::values::computed::{
37 BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
38 NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
39};
40use style::values::generics::NonNegative;
41use style::values::generics::color::ColorOrAuto;
42use style::values::generics::rect::Rect as StyleRect;
43use style::values::specified::TransformStyle;
44use style::values::specified::text::TextDecorationLine;
45use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
46use webrender_api::units::{
47 DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize,
48};
49use webrender_api::{
50 self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
51 ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
52 NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding,
53 PropertyBindingKey, RasterSpace, SpatialId, StackingContextFlags, units,
54};
55use wr::units::LayoutVector2D;
56
57use crate::context::{ImageResolver, ResolvedImage};
58use crate::display_list::background::BackgroundPainter;
59use crate::display_list::conversions::FilterToWebRender;
60pub(crate) use crate::display_list::conversions::ToWebRender;
61use crate::display_list::paint_traversal::{PaintTraversal, PaintTraversalHandler, TraversalState};
62use crate::fragment_tree::{
63 BackgroundMode, BaseFragment, BoxFragment, BoxFragmentWithStyle, ContainingBlockCalculation,
64 Fragment, FragmentFlags, FragmentStatus, FragmentTree, IFrameFragment, ImageFragment,
65 PositioningFragment, SpecificLayoutInfo, Tag, TextFragment,
66};
67use crate::geom::{
68 LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
69};
70use crate::replaced::NaturalSizes;
71use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
72
73mod background;
74mod clip;
75mod conversions;
76mod gradient;
77mod hit_test;
78mod paint_timing_handler;
79mod paint_traversal;
80mod stacking_context;
81
82pub(crate) use hit_test::HitTest;
83pub(crate) use paint_timing_handler::PaintTimingHandler;
84pub(crate) use stacking_context::*;
85
86const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
87
88pub(crate) struct DisplayListBuilder<'a> {
89 fragment_tree: &'a FragmentTree,
91
92 current_reference_frame_scroll_node_id: ScrollTreeNodeId,
97
98 pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
100
101 pub paint_info: &'a mut PaintDisplayListInfo,
103
104 inspector_highlight: Option<InspectorHighlight>,
109
110 paint_body_background: bool,
114
115 clip_map: Vec<ClipChainId>,
118
119 image_resolver: Arc<ImageResolver>,
121
122 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
124
125 paint_timing_handler: &'a mut PaintTimingHandler,
127
128 reflow_statistics: &'a mut ReflowStatistics,
130}
131
132struct InspectorHighlight {
133 tag: Tag,
135
136 state: Option<HighlightTraversalState>,
141}
142
143struct HighlightTraversalState {
144 content_box: Rect<Au, StyloCSSPixel>,
147
148 spatial_id: SpatialId,
149
150 clip_chain_id: ClipChainId,
151
152 maybe_box_fragment: Option<Arc<BoxFragment>>,
155}
156
157impl InspectorHighlight {
158 fn for_node(node: OpaqueNode) -> Self {
159 Self {
160 tag: Tag {
161 node,
162 pseudo_element_chain: Default::default(),
164 },
165 state: None,
166 }
167 }
168}
169
170impl DisplayListBuilder<'_> {
171 #[expect(clippy::too_many_arguments)]
172 pub(crate) fn build(
173 stacking_context_tree: &mut StackingContextTree,
174 fragment_tree: &FragmentTree,
175 image_resolver: Arc<ImageResolver>,
176 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
177 highlighted_dom_node: Option<OpaqueNode>,
178 debug: &DiagnosticsLogging,
179 paint_timing_handler: &mut PaintTimingHandler,
180 reflow_statistics: &mut ReflowStatistics,
181 ) -> BuiltDisplayList {
182 let paint_info = &mut stacking_context_tree.paint_info;
184 let pipeline_id = paint_info.pipeline_id;
185 let mut webrender_display_list_builder =
186 webrender_api::DisplayListBuilder::new(pipeline_id);
187 webrender_display_list_builder.begin();
188
189 if debug.is_enabled(DiagnosticsLoggingOption::DisplayList) {
194 webrender_display_list_builder.dump_serialized_display_list();
195 }
196
197 let _span = profile_traits::trace_span!("DisplayListBuilder::build").entered();
198 let mut builder = DisplayListBuilder {
199 fragment_tree,
200 current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
201 webrender_display_list_builder: &mut webrender_display_list_builder,
202 paint_info,
203 inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
204 paint_body_background: true,
205 clip_map: Default::default(),
206 image_resolver,
207 device_pixel_ratio,
208 paint_timing_handler,
209 reflow_statistics,
210 };
211
212 builder.paint_info.caret_property_binding = None;
214
215 builder.add_all_spatial_nodes();
216
217 for clip in stacking_context_tree.clip_store.0.iter() {
218 builder.add_clip_to_display_list(clip);
219 }
220
221 let pipeline_id = builder.paint_info.pipeline_id;
224 let viewport_size = builder.paint_info.viewport_details.size;
225 let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
226 builder.wr().push_hit_test(
227 viewport_rect,
228 ClipChainId::INVALID,
229 SpatialId::root_reference_frame(pipeline_id),
230 PrimitiveFlags::default(),
231 (0, 0), );
233
234 PaintTraversal::traverse(&stacking_context_tree.root_stacking_context, &mut builder);
235 builder.paint_dom_inspector_highlight();
236
237 webrender_display_list_builder.end().1
238 }
239
240 fn wr(&mut self) -> &mut wr::DisplayListBuilder {
241 self.webrender_display_list_builder
242 }
243
244 fn pipeline_id(&mut self) -> wr::PipelineId {
245 self.paint_info.pipeline_id
246 }
247
248 fn mark_is_paintable(&mut self) {
249 self.paint_info.is_paintable = true;
250 }
251
252 fn mark_is_contentful(&mut self) {
253 self.paint_info.is_contentful = true;
254 }
255
256 fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
257 self.paint_info.scroll_tree.webrender_id(id)
258 }
259
260 fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
261 match id {
262 ClipId::INVALID => ClipChainId::INVALID,
263 _ => *self
264 .clip_map
265 .get(id.0)
266 .expect("Should never try to get clip before adding it to WebRender display list"),
267 }
268 }
269
270 pub(crate) fn add_all_spatial_nodes(&mut self) {
271 let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
275 let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
276
277 mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
278 mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
279
280 for node in scroll_tree.nodes.iter().skip(2) {
281 let parent_scroll_node_id = node
282 .parent
283 .expect("Should have already added root reference frame");
284 let parent_spatial_node_id = mapping
285 .get(parent_scroll_node_id.index)
286 .expect("Should add spatial nodes to display list in order");
287
288 mapping.push(match &node.info {
289 SpatialTreeNodeInfo::ReferenceFrame(info) => {
290 let spatial_id = self.wr().push_reference_frame(
291 info.origin,
292 *parent_spatial_node_id,
293 info.transform_style,
294 PropertyBinding::Value(*info.transform.to_transform()),
295 info.kind,
296 );
297 self.wr().pop_reference_frame();
298 spatial_id
299 },
300 SpatialTreeNodeInfo::Scroll(info) => {
301 self.wr().define_scroll_frame(
302 *parent_spatial_node_id,
303 info.external_id,
304 info.content_rect,
305 info.clip_rect,
306 LayoutVector2D::zero(), 0, wr::HasScrollLinkedEffect::No,
309 )
310 },
311 SpatialTreeNodeInfo::Sticky(info) => {
312 self.wr().define_sticky_frame(
313 *parent_spatial_node_id,
314 info.frame_rect,
315 info.margins,
316 info.vertical_offset_bounds,
317 info.horizontal_offset_bounds,
318 LayoutVector2D::zero(), None, )
321 },
322 });
323 }
324
325 scroll_tree.update_mapping(mapping);
326 self.paint_info.scroll_tree = scroll_tree;
327 }
328
329 pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
337 assert_eq!(
338 clip.id.0,
339 self.clip_map.len(),
340 "Clips should be added in order"
341 );
342
343 let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
344 let new_clip_id = if clip.radii.is_zero() {
345 self.wr().define_clip_rect(spatial_id, clip.rect)
346 } else {
347 self.wr().define_clip_rounded_rect(
348 spatial_id,
349 ComplexClipRegion {
350 rect: clip.rect,
351 radii: clip.radii,
352 mode: ClipMode::Clip,
353 },
354 )
355 };
356
357 let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
362 ClipChainId::INVALID => None,
363 parent => Some(parent),
364 };
365 let clip_chain_id = self
366 .wr()
367 .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
368 self.clip_map.push(clip_chain_id);
369 clip_chain_id
370 }
371
372 fn maybe_create_clip(
376 &mut self,
377 state: &TraversalState,
378 radii: wr::BorderRadius,
379 rect: units::LayoutRect,
380 force_clip_creation: bool,
381 ) -> Option<ClipChainId> {
382 if radii.is_zero() && !force_clip_creation {
383 return None;
384 }
385
386 Some(self.add_clip_to_display_list(&Clip {
387 id: ClipId(self.clip_map.len()),
388 radii,
389 rect,
390 parent_scroll_node_id: state.spatial_id,
391 parent_clip_id: state.clip_id,
392 }))
393 }
394
395 fn push_webrender_stacking_context_if_necessary(
396 &mut self,
397 stacking_context: &StackingContext,
398 ) -> bool {
399 if stacking_context.context_type == StackingContextType::StackingContainer {
400 return false;
401 }
402
403 let StackingContextFragments::Fragment(fragment) = &stacking_context.fragment else {
404 return false;
405 };
406
407 let style = fragment.style();
410 let effects = style.get_effects();
411 let transform_style = style.used_transform_style(fragment.base.flags);
412 if effects.filter.0.is_empty() &&
413 effects.opacity == 1.0 &&
414 effects.mix_blend_mode == ComputedMixBlendMode::Normal &&
415 !style.has_effective_transform_or_perspective(FragmentFlags::empty()) &&
416 style.get_svg().clip_path == ClipPath::None &&
417 transform_style == TransformStyle::Flat
418 {
419 return false;
420 }
421
422 let current_color = style.clone_color();
424 let mut filters: Vec<wr::FilterOp> = effects
425 .filter
426 .0
427 .iter()
428 .map(|filter| FilterToWebRender::to_webrender(filter, ¤t_color))
429 .collect();
430 if effects.opacity != 1.0 {
431 filters.push(wr::FilterOp::Opacity(
432 effects.opacity.into(),
433 effects.opacity,
434 ));
435 }
436
437 let spatial_id = self.spatial_id(stacking_context.scroll_tree_node_id);
444
445 let clip_chain_id = match stacking_context.clip_id {
450 ClipId::INVALID => None,
451 clip_id => Some(self.clip_chain_id(clip_id)),
452 };
453
454 self.wr().push_stacking_context(
455 spatial_id,
456 style.get_webrender_primitive_flags(),
457 clip_chain_id,
458 transform_style.to_webrender(),
459 effects.mix_blend_mode.to_webrender(),
460 &filters,
461 &[], wr::RasterSpace::Screen,
463 wr::StackingContextFlags::empty(),
464 None, );
466
467 true
468 }
469
470 fn common_properties(
471 &self,
472 state: &TraversalState,
473 clip_rect: units::LayoutRect,
474 style: &ComputedValues,
475 ) -> wr::CommonItemProperties {
476 wr::CommonItemProperties {
480 clip_rect,
481 spatial_id: self.spatial_id(state.spatial_id),
482 clip_chain_id: self.clip_chain_id(state.clip_id),
483 flags: style.get_webrender_primitive_flags(),
484 }
485 }
486
487 fn paint_dom_inspector_highlight(&mut self) {
489 let Some(highlight) = self
490 .inspector_highlight
491 .take()
492 .and_then(|highlight| highlight.state)
493 else {
494 return;
495 };
496
497 const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
498 r: 0.23,
499 g: 0.7,
500 b: 0.87,
501 a: 0.5,
502 };
503
504 const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
505 r: 0.49,
506 g: 0.3,
507 b: 0.7,
508 a: 0.5,
509 };
510
511 const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
512 r: 0.2,
513 g: 0.2,
514 b: 0.2,
515 a: 0.5,
516 };
517
518 const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
519 r: 1.,
520 g: 0.93,
521 b: 0.,
522 a: 0.5,
523 };
524
525 let content_box = highlight.content_box.to_webrender();
527 let properties = wr::CommonItemProperties {
528 clip_rect: content_box,
529 spatial_id: highlight.spatial_id,
530 clip_chain_id: highlight.clip_chain_id,
531 flags: wr::PrimitiveFlags::default(),
532 };
533
534 self.wr()
535 .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
536
537 if let Some(box_fragment) = highlight.maybe_box_fragment {
539 let mut paint_highlight =
540 |color: webrender_api::ColorF,
541 fragment_relative_bounds: PhysicalRect<Au>,
542 widths: webrender_api::units::LayoutSideOffsets| {
543 if widths.is_zero() {
544 return;
545 }
546
547 let bounds = box_fragment
548 .offset_by_containing_block(
549 &fragment_relative_bounds,
550 ContainingBlockCalculation::AlreadyDoneWithStackingContextTree,
551 )
552 .to_webrender();
553
554 let border_style = wr::BorderSide {
556 color,
557 style: wr::BorderStyle::Solid,
558 };
559
560 let details = wr::BorderDetails::Normal(wr::NormalBorder {
561 top: border_style,
562 right: border_style,
563 bottom: border_style,
564 left: border_style,
565 radius: webrender_api::BorderRadius::default(),
566 do_aa: true,
567 });
568
569 let common = wr::CommonItemProperties {
570 clip_rect: bounds,
571 spatial_id: highlight.spatial_id,
572 clip_chain_id: highlight.clip_chain_id,
573 flags: wr::PrimitiveFlags::default(),
574 };
575 self.wr().push_border(&common, bounds, widths, details)
576 };
577
578 paint_highlight(
579 PADDING_BOX_HIGHLIGHT_COLOR,
580 box_fragment.padding_rect(),
581 box_fragment.padding.to_webrender(),
582 );
583 paint_highlight(
584 BORDER_BOX_HIGHLIGHT_COLOR,
585 box_fragment.border_rect(),
586 box_fragment.border.to_webrender(),
587 );
588 paint_highlight(
589 MARGIN_BOX_HIGHLIGHT_COLOR,
590 box_fragment.margin_rect(),
591 box_fragment.margin.to_webrender(),
592 );
593 }
594 }
595
596 fn check_if_paintable(&mut self, bounds: LayoutRect, clip_rect: LayoutRect, opacity: f32) {
597 if opacity <= 0.0 {
605 return;
606 }
607
608 if self
610 .paint_timing_handler
611 .check_bounding_rect(bounds, clip_rect)
612 {
613 self.mark_is_paintable();
614 }
615 }
616
617 fn check_for_lcp_candidate(
618 &mut self,
619 state: &TraversalState,
620 clip_rect: LayoutRect,
621 bounds: LayoutRect,
622 tag: Option<Tag>,
623 url: Option<ServoUrl>,
624 ) {
625 if !pref!(largest_contentful_paint_enabled) {
626 return;
627 }
628
629 let transform = self
630 .paint_info
631 .scroll_tree
632 .cumulative_node_to_root_transform(state.spatial_id);
633
634 self.paint_timing_handler
635 .update_lcp_candidate(tag, bounds, clip_rect, transform, url);
636 }
637}
638
639impl PaintTraversalHandler for DisplayListBuilder<'_> {
640 type StackingContextState = (bool, Option<ScrollTreeNodeId>);
641
642 fn visit_stacking_context(
643 &mut self,
644 stacking_context: &StackingContext,
645 ) -> Self::StackingContextState {
646 let pushed_stacking_context =
647 self.push_webrender_stacking_context_if_necessary(stacking_context);
648
649 let root_fragment = stacking_context.fragment();
653 let old_reference_frame = root_fragment.and_then(|root_fragment| {
654 root_fragment
655 .style()
656 .has_effective_transform_or_perspective(root_fragment.base.flags)
657 .then(|| {
658 std::mem::replace(
659 &mut self.current_reference_frame_scroll_node_id,
660 stacking_context.scroll_tree_node_id,
661 )
662 })
663 });
664 (pushed_stacking_context, old_reference_frame)
665 }
666
667 fn leave_stacking_context(
668 &mut self,
669 _: &TraversalState,
670 stacking_context_state: Self::StackingContextState,
671 ) {
672 let (pushed_stacking_context, old_reference_frame) = stacking_context_state;
673 if pushed_stacking_context {
674 self.wr().pop_stacking_context();
675 }
676
677 if let Some(old_reference_frame) = old_reference_frame {
678 self.current_reference_frame_scroll_node_id = old_reference_frame;
679 }
680 }
681
682 fn visit_box(&mut self, state: &TraversalState, fragment: &BoxFragmentWithStyle<'_>) {
683 fragment.base.visit_fragment(self);
684
685 if let Some(mut inspector_highlight) = self.inspector_highlight.take() &&
686 fragment.base.tag == Some(inspector_highlight.tag)
687 {
688 inspector_highlight.register_fragment_of_highlighted_dom_node(self, state, fragment);
689 self.inspector_highlight = Some(inspector_highlight);
690 }
691
692 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
693 return;
694 };
695
696 BuilderForBoxFragment::new(fragment, state.origin).build(self, state)
697 }
698
699 fn visit_iframe(&mut self, state: &TraversalState, fragment: &Arc<IFrameFragment>) {
700 fragment.base.visit_fragment(self);
701
702 let style = fragment.base.style();
703 if style.get_inherited_box().visibility != Visibility::Visible {
704 return;
705 }
706
707 let rect = fragment.base.rect().translate(state.origin.to_vector());
708 let common = self.common_properties(state, rect.to_webrender(), &style);
709 self.wr().push_iframe(
710 rect.to_webrender(),
711 common.clip_rect,
712 &wr::SpaceAndClipInfo {
713 spatial_id: common.spatial_id,
714 clip_chain_id: common.clip_chain_id,
715 },
716 fragment.pipeline_id.into(),
717 true,
718 );
719 self.check_if_paintable(rect.to_webrender(), common.clip_rect, style.clone_opacity());
724 }
725
726 fn visit_image(
727 &mut self,
728 state: &TraversalState,
729 containing_block: PhysicalRect<Au>,
730 fragment: &Arc<ImageFragment>,
731 ) {
732 fragment.base.visit_fragment(self);
733
734 let style = fragment.base.style();
735 if style.get_inherited_box().visibility != Visibility::Visible {
736 return;
737 }
738
739 let image_rendering = style.get_inherited_box().image_rendering.to_webrender();
740 let rect = fragment
741 .base
742 .rect()
743 .translate(containing_block.origin.to_vector())
744 .to_webrender();
745 let clip = fragment
746 .clip
747 .translate(containing_block.origin.to_vector())
748 .to_webrender();
749 let common = self.common_properties(state, clip, &style);
750
751 if let Some(image_key) = fragment.image_key {
752 self.wr().push_image(
753 &common,
754 rect,
755 image_rendering,
756 wr::AlphaType::PremultipliedAlpha,
757 image_key,
758 wr::ColorF::WHITE,
759 );
760
761 self.check_if_paintable(rect, common.clip_rect, style.clone_opacity());
762
763 if !fragment.showing_broken_image_icon {
771 self.mark_is_contentful();
772
773 self.check_for_lcp_candidate(
774 state,
775 common.clip_rect,
776 rect,
777 fragment.base.tag,
778 fragment.url.clone(),
779 );
780 }
781 }
782
783 if fragment.showing_broken_image_icon {
784 Fragment::build_display_list_for_broken_image_border(self, &containing_block, &common);
785 }
786 }
787
788 fn visit_text(
789 &mut self,
790 state: &TraversalState,
791 containing_block: PhysicalRect<Au>,
792 fragment: &Arc<TextFragment>,
793 ) {
794 fragment.base.visit_fragment(self);
795
796 let style = fragment.base.style();
797 if style.get_inherited_box().visibility != Visibility::Visible {
798 return;
799 }
800 Fragment::build_display_list_for_text_fragment(fragment, self, state, &containing_block);
801 }
802
803 fn visit_positioning(&mut self, _state: &TraversalState, fragment: &Arc<PositioningFragment>) {
804 fragment.base.visit_fragment(self);
805 }
806
807 fn visit_box_for_root_background(&mut self, state: &TraversalState) {
813 let Some(fragment) = self.fragment_tree.root_box_fragment() else {
814 return;
815 };
816 let fragment = fragment.with_style();
817
818 let source_style = {
819 let root_fragment_style = fragment.style();
825 if root_fragment_style.background_is_transparent() {
826 let body_fragment = self.fragment_tree.body_fragment();
827 self.paint_body_background = body_fragment.is_none();
828 body_fragment
829 .map(|body_fragment| body_fragment.style().clone())
830 .unwrap_or(fragment.style().clone())
831 } else {
832 root_fragment_style.clone()
833 }
834 };
835
836 if source_style.background_is_transparent() {
839 return;
840 }
841
842 let painting_area = self
849 .fragment_tree
850 .initial_containing_block
851 .union(&self.fragment_tree.scrollable_overflow())
852 .to_webrender();
853
854 let background_color =
855 source_style.resolve_color(&source_style.get_background().background_color);
856 if background_color.alpha > 0.0 {
857 let common = self.common_properties(state, painting_area, &source_style);
858 let color = rgba(background_color);
859 self.wr().push_rect(&common, painting_area, color);
860
861 let default_background_color = servo_config::pref!(shell_background_color_rgba);
865 let default_background_color = AbsoluteColor::new(
866 ColorSpace::Srgb,
867 default_background_color[0] as f32,
868 default_background_color[1] as f32,
869 default_background_color[2] as f32,
870 default_background_color[3] as f32,
871 )
872 .into_srgb_legacy();
873 if background_color != default_background_color {
874 self.mark_is_paintable();
875 }
876 }
877
878 let mut fragment_builder = BuilderForBoxFragment::new(
879 &fragment,
880 self.fragment_tree.initial_containing_block.origin,
881 );
882 let painter = BackgroundPainter {
883 style: &source_style,
884 painting_area_override: Some(painting_area),
885 positioning_area_override: None,
886 };
887 fragment_builder.build_background_image(self, state, &painter);
888 }
889
890 fn visit_box_for_outline(&mut self, state: &TraversalState, fragment: &Arc<BoxFragment>) {
891 let fragment = fragment.with_style();
892 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
893 return;
894 };
895 BuilderForBoxFragment::new(&fragment, state.origin).build_outline(self, state)
896 }
897
898 fn visit_box_for_collapsed_table_borders(
899 &mut self,
900 state: &TraversalState,
901 fragment: &BoxFragmentWithStyle<'_>,
902 ) {
903 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
904 return;
905 };
906 BuilderForBoxFragment::new(fragment, state.origin)
907 .build_collapsed_table_borders(self, state)
908 }
909}
910
911impl InspectorHighlight {
912 fn register_fragment_of_highlighted_dom_node(
913 &mut self,
914 builder: &mut DisplayListBuilder,
915 traversal_state: &TraversalState,
916 fragment: &Arc<BoxFragment>,
917 ) {
918 let spatial_id = builder.spatial_id(traversal_state.spatial_id);
919 let clip_chain_id = builder.clip_chain_id(traversal_state.clip_id);
920 let state = self.state.get_or_insert_with(|| HighlightTraversalState {
921 content_box: Rect::zero(),
922 spatial_id,
923 clip_chain_id,
924 maybe_box_fragment: Some(fragment.clone()),
925 });
926
927 if spatial_id != state.spatial_id {
930 return;
931 }
932
933 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
934 debug_assert_eq!(
935 clip_chain_id, state.clip_chain_id,
936 "Fragments of the same node must either have no clip chain or the same one"
937 );
938 }
939
940 state.maybe_box_fragment = Some(fragment.clone());
941 state.content_box = state.content_box.union(
942 &fragment
943 .base
944 .rect()
945 .translate(traversal_state.origin.to_vector()),
946 );
947 }
948}
949
950impl Fragment {
951 fn build_display_list_for_text_fragment(
952 fragment: &TextFragment,
953 builder: &mut DisplayListBuilder,
954 state: &TraversalState,
955 containing_block: &PhysicalRect<Au>,
956 ) {
957 let rect = fragment
960 .base
961 .rect()
962 .translate(containing_block.origin.to_vector());
963 let mut baseline_origin = rect.origin;
964 baseline_origin.y += fragment.font_metrics.ascent;
965
966 let include_whitespace = fragment.offsets.is_some() ||
967 state
968 .text_decorations
969 .iter()
970 .any(|item| !item.line.is_empty());
971
972 let (glyphs, largest_advance) = glyphs(
973 &fragment.glyphs,
974 baseline_origin,
975 fragment.justification_adjustment,
976 include_whitespace,
977 );
978
979 if glyphs.is_empty() && !fragment.is_empty_for_text_cursor {
980 return;
981 }
982
983 let parent_style = fragment.base.style();
984 let color = parent_style.clone_color();
985 let font_metrics = &fragment.font_metrics;
986 let dppx = builder.device_pixel_ratio.get();
987
988 let glyph_bounds = rect
995 .inflate(largest_advance.scale_by(2.0), Au::zero())
996 .to_webrender();
997 let common = builder.common_properties(state, glyph_bounds, &parent_style);
998
999 let shadows = &parent_style.get_inherited_text().text_shadow;
1002 for shadow in shadows.0.iter().rev() {
1003 builder.wr().push_shadow(
1004 &wr::SpaceAndClipInfo {
1005 spatial_id: common.spatial_id,
1006 clip_chain_id: common.clip_chain_id,
1007 },
1008 wr::Shadow {
1009 offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
1010 color: rgba(shadow.color.resolve_to_absolute(&color)),
1011 blur_radius: shadow.blur.px(),
1012 },
1013 true, );
1015 }
1016
1017 for text_decoration in state.text_decorations.iter() {
1018 if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
1019 let mut rect = rect;
1020 rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
1021 rect.size.height =
1022 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
1023
1024 Self::build_display_list_for_text_decoration(
1025 state,
1026 &parent_style,
1027 builder,
1028 &rect,
1029 text_decoration,
1030 TextDecorationLine::UNDERLINE,
1031 );
1032 }
1033 }
1034
1035 for text_decoration in state.text_decorations.iter() {
1036 if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
1037 let mut rect = rect;
1038 rect.size.height =
1039 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
1040 Self::build_display_list_for_text_decoration(
1041 state,
1042 &parent_style,
1043 builder,
1044 &rect,
1045 text_decoration,
1046 TextDecorationLine::OVERLINE,
1047 );
1048 }
1049 }
1050
1051 Self::build_display_list_for_text_selection(
1052 fragment,
1053 builder,
1054 state,
1055 containing_block,
1056 fragment.base.rect().min_x(),
1057 fragment.justification_adjustment,
1058 );
1059
1060 builder.wr().push_text(
1061 &common,
1062 glyph_bounds,
1063 &glyphs,
1064 fragment.font_key,
1065 rgba(color),
1066 None,
1067 );
1068
1069 builder.check_if_paintable(glyph_bounds, common.clip_rect, parent_style.clone_opacity());
1070
1071 builder.mark_is_contentful();
1075
1076 for text_decoration in state.text_decorations.iter() {
1077 if text_decoration
1078 .line
1079 .contains(TextDecorationLine::LINE_THROUGH)
1080 {
1081 let mut rect = rect;
1082 rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
1083 rect.size.height =
1084 Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
1085 Self::build_display_list_for_text_decoration(
1086 state,
1087 &parent_style,
1088 builder,
1089 &rect,
1090 text_decoration,
1091 TextDecorationLine::LINE_THROUGH,
1092 );
1093 }
1094 }
1095
1096 if !shadows.0.is_empty() {
1097 builder.wr().pop_all_shadows();
1098 }
1099 }
1100
1101 fn build_display_list_for_text_decoration(
1102 state: &TraversalState,
1103 parent_style: &ServoArc<ComputedValues>,
1104 builder: &mut DisplayListBuilder,
1105 rect: &PhysicalRect<Au>,
1106 text_decoration: &FragmentTextDecoration,
1107 line: TextDecorationLine,
1108 ) {
1109 if text_decoration.style == ComputedTextDecorationStyle::MozNone {
1110 return;
1111 }
1112
1113 let mut rect = rect.to_webrender();
1114 let wavy_line_thickness = rect.height().ceil();
1115 if text_decoration.style == ComputedTextDecorationStyle::Wavy {
1116 rect = rect.inflate(0.0, wavy_line_thickness);
1117 }
1118
1119 let expand_rect_for_text_decoration = |mut rect: Box2D<f32, LayoutPixel>| {
1126 if matches!(
1127 text_decoration.style,
1128 ComputedTextDecorationStyle::Dotted |
1129 ComputedTextDecorationStyle::Dashed |
1130 ComputedTextDecorationStyle::Wavy,
1131 ) {
1132 rect.min.x = rect.min.x.min(0.0);
1133 }
1134 rect
1135 };
1136
1137 let common_properties = builder.common_properties(state, rect, parent_style);
1138 builder.wr().push_line(
1139 &common_properties,
1140 &expand_rect_for_text_decoration(rect),
1141 wavy_line_thickness,
1142 wr::LineOrientation::Horizontal,
1143 &rgba(text_decoration.color),
1144 text_decoration.style.to_webrender(),
1145 );
1146
1147 if text_decoration.style == TextDecorationStyle::Double {
1148 let half_height = (rect.height() / 2.0).floor().max(1.0);
1149 let y_offset = match line {
1150 TextDecorationLine::OVERLINE => -rect.height() - half_height,
1151 _ => rect.height() + half_height,
1152 };
1153 let rect = rect.translate(Vector2D::new(0.0, y_offset));
1154 let common_properties = builder.common_properties(state, rect, parent_style);
1155 builder.wr().push_line(
1156 &common_properties,
1157 &rect,
1158 wavy_line_thickness,
1159 wr::LineOrientation::Horizontal,
1160 &rgba(text_decoration.color),
1161 text_decoration.style.to_webrender(),
1162 );
1163 }
1164 }
1165
1166 fn build_display_list_for_broken_image_border(
1167 builder: &mut DisplayListBuilder,
1168 containing_block: &PhysicalRect<Au>,
1169 common: &CommonItemProperties,
1170 ) {
1171 let border_side = BorderSide {
1172 color: ColorF::BLACK,
1173 style: wr::BorderStyle::Inset,
1174 };
1175 builder.wr().push_border(
1176 common,
1177 containing_block.to_webrender(),
1178 LayoutSideOffsets::new_all_same(1.0),
1179 BorderDetails::Normal(NormalBorder {
1180 left: border_side,
1181 right: border_side,
1182 top: border_side,
1183 bottom: border_side,
1184 radius: BorderRadius::zero(),
1185 do_aa: true,
1186 }),
1187 );
1188 }
1189
1190 fn build_display_list_for_text_selection(
1193 fragment: &TextFragment,
1194 builder: &mut DisplayListBuilder<'_>,
1195 state: &TraversalState,
1196 containing_block_rect: &PhysicalRect<Au>,
1197 fragment_x_offset: Au,
1198 justification_adjustment: Au,
1199 ) {
1200 let Some(offsets) = fragment.offsets.as_ref() else {
1201 return;
1202 };
1203
1204 let shared_selection = offsets.shared_selection.borrow();
1205 if !shared_selection.enabled {
1206 return;
1207 }
1208
1209 if offsets.character_range.start > shared_selection.character_range.end ||
1210 offsets.character_range.end < shared_selection.character_range.start
1211 {
1212 return;
1213 }
1214
1215 if fragment.is_empty_for_text_cursor &&
1220 !offsets
1221 .character_range
1222 .contains(&shared_selection.character_range.start)
1223 {
1224 return;
1225 }
1226
1227 let mut current_character_index = offsets.character_range.start;
1228 let mut current_advance = Au::zero();
1229 let mut start_advance = None;
1230 let mut end_advance = None;
1231 for glyph_store in fragment.glyphs.iter() {
1232 let glyph_store_character_count = glyph_store.character_count();
1233 if current_character_index + glyph_store_character_count <
1234 shared_selection.character_range.start
1235 {
1236 current_advance += glyph_store.total_advance() +
1237 (justification_adjustment * glyph_store.total_word_separators() as i32);
1238 current_character_index += glyph_store_character_count;
1239 continue;
1240 }
1241
1242 if current_character_index >= shared_selection.character_range.end {
1243 break;
1244 }
1245
1246 for glyph in glyph_store.glyphs() {
1247 if current_character_index >= shared_selection.character_range.start {
1248 start_advance = start_advance.or(Some(current_advance));
1249 }
1250
1251 current_character_index += glyph.character_count();
1252 current_advance += glyph.advance();
1253 if glyph.char_is_word_separator() {
1254 current_advance += justification_adjustment;
1255 }
1256
1257 if current_character_index <= shared_selection.character_range.end {
1258 end_advance = Some(current_advance);
1259 }
1260 }
1261 }
1262
1263 let start_x = start_advance.unwrap_or(current_advance);
1264 let end_x = end_advance.unwrap_or(current_advance);
1265
1266 let parent_style = fragment.base.style();
1267 if !shared_selection.range.is_empty() {
1268 let selection_rect = Rect::new(
1269 containing_block_rect.origin +
1270 Vector2D::new(fragment_x_offset + start_x, Au::zero()),
1271 Size2D::new(end_x - start_x, containing_block_rect.height()),
1272 )
1273 .to_webrender();
1274
1275 if let Some(selection_color) = fragment
1276 .selected_style
1277 .borrow()
1278 .clone_background_color()
1279 .as_absolute()
1280 {
1281 let selection_common =
1282 builder.common_properties(state, selection_rect, &parent_style);
1283 builder
1284 .wr()
1285 .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1286 }
1287 return;
1288 }
1289
1290 let insertion_point_rect = Rect::new(
1291 containing_block_rect.origin + Vector2D::new(start_x + fragment_x_offset, Au::zero()),
1292 Size2D::new(
1293 INSERTION_POINT_LOGICAL_WIDTH,
1294 containing_block_rect.height(),
1295 ),
1296 )
1297 .to_webrender();
1298
1299 let color = parent_style.clone_color();
1300 let caret_color = match parent_style.clone_caret_color().0 {
1301 ColorOrAuto::Color(caret_color) => caret_color.resolve_to_absolute(&color),
1302 ColorOrAuto::Auto => color,
1303 };
1304 let insertion_point_common =
1305 builder.common_properties(state, insertion_point_rect, &parent_style);
1306
1307 let caret_color = rgba(caret_color);
1308 let property_binding = if prefs::get().editing_caret_blink_time().is_some() {
1309 let pipeline_id: PipelineId = builder.paint_info.pipeline_id.into();
1313 let property_binding_key = PropertyBindingKey::new(pipeline_id.into());
1314 builder.paint_info.caret_property_binding = Some((property_binding_key, caret_color));
1315 PropertyBinding::Binding(property_binding_key, caret_color)
1316 } else {
1317 PropertyBinding::Value(caret_color)
1318 };
1319
1320 builder.wr().push_rect_with_animation(
1321 &insertion_point_common,
1322 insertion_point_rect,
1323 property_binding,
1324 );
1325 }
1326}
1327
1328struct BuilderForBoxFragment<'a> {
1329 fragment: &'a BoxFragmentWithStyle<'a>,
1330 containing_block_origin: PhysicalPoint<Au>,
1331 border_rect: units::LayoutRect,
1332 margin_rect: OnceCell<units::LayoutRect>,
1333 padding_rect: OnceCell<units::LayoutRect>,
1334 content_rect: OnceCell<units::LayoutRect>,
1335 border_radius: OnceCell<wr::BorderRadius>,
1336 border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1337 padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1338 content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1339}
1340
1341impl<'a> BuilderForBoxFragment<'a> {
1342 fn new(
1343 fragment: &'a BoxFragmentWithStyle<'a>,
1344 containing_block_origin: PhysicalPoint<Au>,
1345 ) -> Self {
1346 let border_rect = fragment
1347 .border_rect()
1348 .translate(containing_block_origin.to_vector());
1349 Self {
1350 fragment,
1351 containing_block_origin,
1352 border_rect: border_rect.to_webrender(),
1353 border_radius: OnceCell::new(),
1354 margin_rect: OnceCell::new(),
1355 padding_rect: OnceCell::new(),
1356 content_rect: OnceCell::new(),
1357 border_edge_clip_chain_id: RefCell::new(None),
1358 padding_edge_clip_chain_id: RefCell::new(None),
1359 content_edge_clip_chain_id: RefCell::new(None),
1360 }
1361 }
1362
1363 fn border_radius(&self) -> BorderRadius {
1364 *self
1365 .border_radius
1366 .get_or_init(|| self.fragment.border_radius())
1367 }
1368
1369 fn content_rect(&self) -> &units::LayoutRect {
1370 self.content_rect.get_or_init(|| {
1371 self.fragment
1372 .content_rect()
1373 .translate(self.containing_block_origin.to_vector())
1374 .to_webrender()
1375 })
1376 }
1377
1378 fn padding_rect(&self) -> &units::LayoutRect {
1379 self.padding_rect.get_or_init(|| {
1380 self.fragment
1381 .padding_rect()
1382 .translate(self.containing_block_origin.to_vector())
1383 .to_webrender()
1384 })
1385 }
1386
1387 fn margin_rect(&self) -> &units::LayoutRect {
1388 self.margin_rect.get_or_init(|| {
1389 self.fragment
1390 .margin_rect()
1391 .translate(self.containing_block_origin.to_vector())
1392 .to_webrender()
1393 })
1394 }
1395
1396 fn border_edge_clip(
1397 &self,
1398 builder: &mut DisplayListBuilder,
1399 state: &TraversalState,
1400 force_clip_creation: bool,
1401 ) -> Option<ClipChainId> {
1402 if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1403 return Some(clip);
1404 }
1405
1406 let maybe_clip = builder.maybe_create_clip(
1407 state,
1408 self.border_radius(),
1409 self.border_rect,
1410 force_clip_creation,
1411 );
1412 *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1413 maybe_clip
1414 }
1415
1416 fn padding_edge_clip(
1417 &self,
1418 builder: &mut DisplayListBuilder,
1419 state: &TraversalState,
1420 force_clip_creation: bool,
1421 ) -> Option<ClipChainId> {
1422 if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1423 return Some(clip);
1424 }
1425
1426 let radii = offset_radii(self.border_radius(), -self.fragment.border.to_webrender());
1427 let maybe_clip =
1428 builder.maybe_create_clip(state, radii, *self.padding_rect(), force_clip_creation);
1429 *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1430 maybe_clip
1431 }
1432
1433 fn content_edge_clip(
1434 &self,
1435 builder: &mut DisplayListBuilder,
1436 state: &TraversalState,
1437 force_clip_creation: bool,
1438 ) -> Option<ClipChainId> {
1439 if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1440 return Some(clip);
1441 }
1442
1443 let radii = offset_radii(
1444 self.border_radius(),
1445 -(self.fragment.border + self.fragment.padding).to_webrender(),
1446 );
1447 let maybe_clip =
1448 builder.maybe_create_clip(state, radii, *self.content_rect(), force_clip_creation);
1449 *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1450 maybe_clip
1451 }
1452
1453 fn build(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1454 if self
1455 .fragment
1456 .base
1457 .flags
1458 .contains(FragmentFlags::DO_NOT_PAINT)
1459 {
1460 return;
1461 }
1462
1463 self.build_background(builder, state);
1464 self.build_box_shadow(builder, state);
1465 if !self.fragment.is_table_grid_with_collapsed_borders() {
1466 self.build_border(builder, state);
1467 }
1468
1469 let overflow = self
1470 .fragment
1471 .style()
1472 .effective_overflow(self.fragment.base.flags);
1473 let scrolls_via_user_input =
1474 |overflow| matches!(overflow, ComputedOverflow::Scroll | ComputedOverflow::Auto);
1475 if (scrolls_via_user_input(overflow.x) || scrolls_via_user_input(overflow.y)) &&
1476 self.fragment.style().get_inherited_ui().pointer_events !=
1477 style::computed_values::pointer_events::T::None
1478 {
1479 let mut inner_state = state.clone();
1480 inner_state.spatial_id = self
1481 .fragment
1482 .generated_scroll_tree_node_id()
1483 .unwrap_or(state.spatial_id);
1484 inner_state.clip_id = self.fragment.generated_clip_id().unwrap_or(state.clip_id);
1485
1486 self.build_hit_test(
1487 builder,
1488 &inner_state,
1489 self.fragment
1490 .scrollable_overflow()
1491 .translate(self.containing_block_origin.to_vector())
1492 .to_webrender(),
1493 );
1494 }
1495 }
1496
1497 fn build_hit_test(
1498 &self,
1499 builder: &mut DisplayListBuilder,
1500 state: &TraversalState,
1501 rect: LayoutRect,
1502 ) {
1503 let external_scroll_node_id = builder
1504 .paint_info
1505 .external_scroll_id_for_scroll_tree_node(state.spatial_id);
1506
1507 let mut common = builder.common_properties(state, rect, self.fragment.style());
1508 if let Some(clip_chain_id) = self.border_edge_clip(builder, state, false) {
1509 common.clip_chain_id = clip_chain_id;
1510 }
1511 builder.wr().push_hit_test(
1512 common.clip_rect,
1513 common.clip_chain_id,
1514 common.spatial_id,
1515 common.flags,
1516 (external_scroll_node_id.0, 0), );
1518 }
1519
1520 fn build_background_for_painter(
1521 &mut self,
1522 builder: &mut DisplayListBuilder,
1523 state: &TraversalState,
1524 painter: &BackgroundPainter,
1525 ) {
1526 let b = painter.style.get_background();
1527 let background_color = painter.style.resolve_color(&b.background_color);
1528 if background_color.alpha > 0.0 {
1529 let layer_index = b.background_image.0.len() - 1;
1533 let bounds = painter.painting_area(self, builder, layer_index);
1534 let common = painter.common_properties(self, builder, state, layer_index, bounds);
1535 builder
1536 .wr()
1537 .push_rect(&common, bounds, rgba(background_color));
1538
1539 let default_background_color = servo_config::pref!(shell_background_color_rgba);
1543 let default_background_color = AbsoluteColor::new(
1544 ColorSpace::Srgb,
1545 default_background_color[0] as f32,
1546 default_background_color[1] as f32,
1547 default_background_color[2] as f32,
1548 default_background_color[3] as f32,
1549 )
1550 .into_srgb_legacy();
1551 if background_color != default_background_color {
1552 builder.mark_is_paintable();
1553 }
1554 }
1555
1556 self.build_background_image(builder, state, painter);
1557 }
1558
1559 fn build_background(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1560 let flags = self.fragment.base.flags;
1561
1562 if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1565 return;
1566 }
1567 if !builder.paint_body_background &&
1569 flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1570 {
1571 return;
1572 }
1573
1574 if let BackgroundMode::None = self.fragment.background_mode {
1576 return;
1577 }
1578
1579 if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1583 for extra_background in extra_backgrounds {
1584 let positioning_area = extra_background.rect;
1585 let painter = BackgroundPainter {
1586 style: &extra_background.style.borrow_mut(),
1587 painting_area_override: None,
1588 positioning_area_override: Some(
1589 positioning_area
1590 .translate(self.containing_block_origin.to_vector())
1591 .to_webrender(),
1592 ),
1593 };
1594 self.build_background_for_painter(builder, state, &painter);
1595 }
1596 }
1597
1598 let painter = BackgroundPainter {
1599 style: self.fragment.style(),
1600 painting_area_override: None,
1601 positioning_area_override: None,
1602 };
1603 self.build_background_for_painter(builder, state, &painter);
1604 }
1605
1606 fn build_background_image(
1607 &mut self,
1608 builder: &mut DisplayListBuilder,
1609 state: &TraversalState,
1610 painter: &BackgroundPainter,
1611 ) {
1612 let style = painter.style;
1613 let b = style.get_background();
1614 let need_blend_container = b
1615 .background_blend_mode
1616 .0
1617 .iter()
1618 .take(b.background_image.0.len())
1619 .any(|background_blend_mode| background_blend_mode != &BackgroundBlendMode::Normal);
1620
1621 let push_stacking_context = |builder: &mut DisplayListBuilder,
1622 blend_mode: BackgroundBlendMode,
1623 flags: StackingContextFlags|
1624 -> bool {
1625 let spatial_id = builder.spatial_id(state.spatial_id);
1626 builder.wr().push_stacking_context(
1627 spatial_id,
1628 PrimitiveFlags::empty(),
1629 None,
1630 webrender_api::TransformStyle::Flat,
1631 blend_mode.to_webrender(),
1632 &[],
1633 &[],
1634 RasterSpace::Screen,
1635 flags,
1636 None,
1637 );
1638 true
1639 };
1640
1641 if need_blend_container {
1642 push_stacking_context(
1643 builder,
1644 BackgroundBlendMode::Normal,
1645 StackingContextFlags::IS_BLEND_CONTAINER,
1646 );
1647 }
1648
1649 let node = self.fragment.base.tag.map(|tag| tag.node);
1650 for (index, image) in b.background_image.0.iter().enumerate().rev() {
1652 let Ok(resolved_image) = builder.image_resolver.resolve_image(node, image) else {
1653 continue;
1654 };
1655 match resolved_image {
1656 ResolvedImage::Gradient(_) | ResolvedImage::Color(_) => {
1657 let intrinsic = NaturalSizes::empty();
1658 let Some(layer) =
1659 &background::layout_layer(self, painter, builder, state, index, intrinsic)
1660 else {
1661 continue;
1662 };
1663
1664 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1665 if needs_blending {
1666 push_stacking_context(builder, layer.blend_mode, Default::default());
1667 }
1668
1669 match resolved_image {
1670 ResolvedImage::Gradient(gradient) => {
1671 match gradient::build(style, gradient, layer.tile_size, builder) {
1672 WebRenderGradient::Linear(linear_gradient) => {
1673 builder.wr().push_gradient(
1674 &layer.common,
1675 layer.bounds,
1676 linear_gradient,
1677 layer.tile_size,
1678 layer.tile_spacing,
1679 )
1680 },
1681 WebRenderGradient::Radial(radial_gradient) => {
1682 builder.wr().push_radial_gradient(
1683 &layer.common,
1684 layer.bounds,
1685 radial_gradient,
1686 layer.tile_size,
1687 layer.tile_spacing,
1688 )
1689 },
1690 WebRenderGradient::Conic(conic_gradient) => {
1691 builder.wr().push_conic_gradient(
1692 &layer.common,
1693 layer.bounds,
1694 conic_gradient,
1695 layer.tile_size,
1696 layer.tile_spacing,
1697 )
1698 },
1699 }
1700 },
1701 ResolvedImage::Color(color) => {
1702 let color = rgba(style.resolve_color(color));
1703 builder.wr().push_rect(&layer.common, layer.bounds, color);
1704 },
1705 _ => {},
1706 }
1707
1708 if needs_blending {
1709 builder.wr().pop_stacking_context();
1710 }
1711
1712 builder.check_if_paintable(
1713 layer.bounds,
1714 layer.common.clip_rect,
1715 style.clone_opacity(),
1716 );
1717 },
1718 ResolvedImage::Image { image, size } => {
1719 let dppx = 1.0;
1721 let intrinsic =
1722 NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1723 let layer =
1724 background::layout_layer(self, painter, builder, state, index, intrinsic);
1725
1726 let image_wr_key = match image {
1727 CachedImage::Raster(raster_image) => raster_image.id,
1728 CachedImage::Vector(vector_image) => {
1729 let scale = builder.device_pixel_ratio.get();
1730 let default_size: DeviceIntSize =
1731 Size2D::new(size.width * scale, size.height * scale).to_i32();
1732 let layer_size = layer.as_ref().map(|layer| {
1733 Size2D::new(
1734 layer.tile_size.width * scale,
1735 layer.tile_size.height * scale,
1736 )
1737 .to_i32()
1738 });
1739
1740 node.and_then(|node| {
1741 let size = layer_size.unwrap_or(default_size);
1742 builder.image_resolver.rasterize_vector_image(
1743 vector_image.id,
1744 size,
1745 node,
1746 vector_image.svg_id,
1747 )
1748 })
1749 .and_then(|rasterized_image| rasterized_image.id)
1750 },
1751 };
1752
1753 let Some(image_key) = image_wr_key else {
1754 continue;
1755 };
1756
1757 if let Some(layer) = layer {
1758 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1759 if needs_blending {
1760 push_stacking_context(builder, layer.blend_mode, Default::default());
1761 }
1762
1763 if layer.repeat {
1764 builder.wr().push_repeating_image(
1765 &layer.common,
1766 layer.bounds,
1767 layer.tile_size,
1768 layer.tile_spacing,
1769 style.clone_image_rendering().to_webrender(),
1770 wr::AlphaType::PremultipliedAlpha,
1771 image_key,
1772 wr::ColorF::WHITE,
1773 )
1774 } else {
1775 builder.wr().push_image(
1776 &layer.common,
1777 layer.bounds,
1778 style.clone_image_rendering().to_webrender(),
1779 wr::AlphaType::PremultipliedAlpha,
1780 image_key,
1781 wr::ColorF::WHITE,
1782 )
1783 }
1784
1785 if needs_blending {
1786 builder.wr().pop_stacking_context();
1787 }
1788
1789 builder.check_if_paintable(
1790 layer.bounds,
1791 layer.common.clip_rect,
1792 style.clone_opacity(),
1793 );
1794
1795 builder.mark_is_contentful();
1800
1801 builder.check_for_lcp_candidate(
1802 state,
1803 layer.common.clip_rect,
1804 layer.bounds,
1805 self.fragment.base.tag,
1806 None,
1807 );
1808 }
1809 },
1810 }
1811 }
1812
1813 if need_blend_container {
1814 builder.wr().pop_stacking_context();
1815 }
1816 }
1817
1818 fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1819 wr::BorderSide {
1820 color: rgba(style_color.color),
1821 style: match style_color.style {
1822 BorderStyle::None => wr::BorderStyle::None,
1823 BorderStyle::Solid => wr::BorderStyle::Solid,
1824 BorderStyle::Double => wr::BorderStyle::Double,
1825 BorderStyle::Dotted => wr::BorderStyle::Dotted,
1826 BorderStyle::Dashed => wr::BorderStyle::Dashed,
1827 BorderStyle::Hidden => wr::BorderStyle::Hidden,
1828 BorderStyle::Groove => wr::BorderStyle::Groove,
1829 BorderStyle::Ridge => wr::BorderStyle::Ridge,
1830 BorderStyle::Inset => wr::BorderStyle::Inset,
1831 BorderStyle::Outset => wr::BorderStyle::Outset,
1832 },
1833 }
1834 }
1835
1836 fn build_collapsed_table_borders(
1837 &mut self,
1838 builder: &mut DisplayListBuilder,
1839 state: &TraversalState,
1840 ) {
1841 if self
1842 .fragment
1843 .base
1844 .flags
1845 .contains(FragmentFlags::DO_NOT_PAINT)
1846 {
1847 return;
1848 }
1849
1850 let layout_info = self.fragment.specific_layout_info();
1851 let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1852 layout_info.as_deref()
1853 else {
1854 return;
1855 };
1856 let mut common =
1857 builder.common_properties(state, units::LayoutRect::default(), self.fragment.style());
1858 let radius = wr::BorderRadius::default();
1859 let mut column_sum = Au::zero();
1860 for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1861 let mut row_sum = Au::zero();
1862 for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1863 let left_border = &table_info.collapsed_borders.x[x][y];
1864 let right_border = &table_info.collapsed_borders.x[x + 1][y];
1865 let top_border = &table_info.collapsed_borders.y[y][x];
1866 let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1867 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1868 left: self.build_border_side(left_border.style_color.clone()),
1869 right: self.build_border_side(right_border.style_color.clone()),
1870 top: self.build_border_side(top_border.style_color.clone()),
1871 bottom: self.build_border_side(bottom_border.style_color.clone()),
1872 radius,
1873 do_aa: true,
1874 });
1875 let mut border_widths = PhysicalSides::new(
1876 top_border.width,
1877 right_border.width,
1878 bottom_border.width,
1879 left_border.width,
1880 );
1881 let left_adjustment = if x == 0 {
1882 -border_widths.left / 2
1883 } else {
1884 std::mem::take(&mut border_widths.left) / 2
1885 };
1886 let top_adjustment = if y == 0 {
1887 -border_widths.top / 2
1888 } else {
1889 std::mem::take(&mut border_widths.top) / 2
1890 };
1891 let origin =
1892 PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1893 let size = PhysicalSize::new(
1894 *column_size - left_adjustment + border_widths.right / 2,
1895 *row_size - top_adjustment + border_widths.bottom / 2,
1896 );
1897 let border_rect = PhysicalRect::new(origin, size)
1898 .translate(self.fragment.content_rect().origin.to_vector())
1899 .translate(self.containing_block_origin.to_vector())
1900 .to_webrender();
1901 common.clip_rect = border_rect;
1902 builder.wr().push_border(
1903 &common,
1904 border_rect,
1905 border_widths.to_webrender(),
1906 details,
1907 );
1908 row_sum += *row_size;
1909 }
1910 column_sum += *column_size;
1911 }
1912 }
1913
1914 fn build_border(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1915 if self.fragment.has_collapsed_borders() {
1916 return;
1919 }
1920
1921 let style = self.fragment.style();
1922 let border = style.get_border();
1923 let border_widths = self.fragment.border.to_webrender();
1924
1925 if border_widths == SideOffsets2D::zero() {
1926 return;
1927 }
1928
1929 if self.build_border_image(builder, state, border, border_widths) {
1931 return;
1932 }
1933
1934 let current_color = style.get_inherited_text().clone_color();
1935 let style_color = BorderStyleColor::from_border(border, ¤t_color);
1936 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1937 top: self.build_border_side(style_color.top),
1938 right: self.build_border_side(style_color.right),
1939 bottom: self.build_border_side(style_color.bottom),
1940 left: self.build_border_side(style_color.left),
1941 radius: self.border_radius(),
1942 do_aa: true,
1943 });
1944 let common = builder.common_properties(state, self.border_rect, style);
1945 builder
1946 .wr()
1947 .push_border(&common, self.border_rect, border_widths, details)
1948 }
1949
1950 fn build_border_image(
1952 &self,
1953 builder: &mut DisplayListBuilder,
1954 state: &TraversalState,
1955 border: &Border,
1956 border_widths: SideOffsets2D<f32, LayoutPixel>,
1957 ) -> bool {
1958 let style = self.fragment.style();
1959 let border_style_struct = style.get_border();
1960 let border_image_outset =
1961 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1962 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1963 let border_image_size = border_image_area.size;
1964 let border_image_widths = resolve_border_image_width(
1965 &border_style_struct.border_image_width,
1966 border_widths,
1967 border_image_size,
1968 );
1969 let border_image_repeat = &border_style_struct.border_image_repeat;
1970 let border_image_fill = border_style_struct.border_image_slice.fill;
1971 let border_image_slice = &border_style_struct.border_image_slice.offsets;
1972 let common = builder.common_properties(state, border_image_area.to_box2d(), style);
1973
1974 let stops = Vec::new();
1975 let mut width = border_image_size.width;
1976 let mut height = border_image_size.height;
1977 let node = self.fragment.base.tag.map(|tag| tag.node);
1978 let source = match builder
1979 .image_resolver
1980 .resolve_image(node, &border.border_image_source)
1981 {
1982 Err(_) => return false,
1983 Ok(ResolvedImage::Image { image, size }) => {
1984 let image_key = match image {
1985 CachedImage::Raster(raster_image) => raster_image.id,
1986 CachedImage::Vector(vector_image) => {
1987 let scale = builder.device_pixel_ratio.get();
1988 let size = Size2D::new(size.width * scale, size.height * scale).to_i32();
1989 node.and_then(|node| {
1990 builder.image_resolver.rasterize_vector_image(
1991 vector_image.id,
1992 size,
1993 node,
1994 vector_image.svg_id,
1995 )
1996 })
1997 .and_then(|rasterized_image| rasterized_image.id)
1998 },
1999 };
2000
2001 let Some(key) = image_key else {
2002 return false;
2003 };
2004
2005 builder.check_if_paintable(
2006 Box2D::from_size(size.cast_unit()),
2007 common.clip_rect,
2008 style.clone_opacity(),
2009 );
2010
2011 builder.mark_is_contentful();
2016
2017 width = size.width;
2018 height = size.height;
2019 let image_rendering = style.clone_image_rendering().to_webrender();
2020 NinePatchBorderSource::Image(key, image_rendering)
2021 },
2022 Ok(ResolvedImage::Gradient(gradient)) => {
2023 match gradient::build(style, gradient, border_image_size, builder) {
2024 WebRenderGradient::Linear(gradient) => {
2025 NinePatchBorderSource::Gradient(gradient)
2026 },
2027 WebRenderGradient::Radial(gradient) => {
2028 NinePatchBorderSource::RadialGradient(gradient)
2029 },
2030 WebRenderGradient::Conic(gradient) => {
2031 NinePatchBorderSource::ConicGradient(gradient)
2032 },
2033 }
2034 },
2035 Ok(ResolvedImage::Color(color)) => {
2036 let color = rgba(style.resolve_color(color));
2039 let gradient = builder.wr().create_gradient(
2040 Point2D::zero(),
2041 Point2D::zero(),
2042 vec![
2043 wr::GradientStop { offset: 0.0, color },
2044 wr::GradientStop { offset: 1.0, color },
2045 ],
2046 wr::ExtendMode::Clamp,
2047 );
2048 NinePatchBorderSource::Gradient(gradient)
2049 },
2050 };
2051
2052 let size = Size2D::new(width as i32, height as i32);
2053
2054 if size.is_empty() || border_image_size.is_empty() {
2057 return true;
2058 }
2059
2060 let details = BorderDetails::NinePatch(NinePatchBorder {
2061 source,
2062 width: size.width,
2063 height: size.height,
2064 slice: resolve_border_image_slice(border_image_slice, size),
2065 fill: border_image_fill,
2066 repeat_horizontal: border_image_repeat.0.to_webrender(),
2067 repeat_vertical: border_image_repeat.1.to_webrender(),
2068 });
2069 builder.wr().push_border(
2070 &common,
2071 border_image_area.to_box2d(),
2072 border_image_widths,
2073 details,
2074 );
2075 builder.wr().push_stops(&stops);
2076 true
2077 }
2078
2079 fn build_outline(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2080 let style = self.fragment.style();
2081 let outline = style.get_outline();
2082 if outline.outline_style.none_or_hidden() {
2083 return;
2084 }
2085 let width = outline.outline_width.0.to_f32_px();
2086 if width == 0.0 {
2087 return;
2088 }
2089 let offset = outline.outline_offset.to_f32_px() + width;
2098 let outline_rect = self.border_rect.inflate(
2099 offset.max(-self.border_rect.width() / 2.0 + width),
2100 offset.max(-self.border_rect.height() / 2.0 + width),
2101 );
2102 let common = builder.common_properties(state, outline_rect, style);
2103 let widths = SideOffsets2D::new_all_same(width);
2104 let border_style = match outline.outline_style {
2105 OutlineStyle::Auto => BorderStyle::Solid,
2108 OutlineStyle::BorderStyle(s) => s,
2109 };
2110 let side = self.build_border_side(BorderStyleColor {
2111 style: border_style,
2112 color: style.resolve_color(&outline.outline_color),
2113 });
2114 let details = wr::BorderDetails::Normal(wr::NormalBorder {
2115 top: side,
2116 right: side,
2117 bottom: side,
2118 left: side,
2119 radius: offset_radii(self.border_radius(), SideOffsets2D::new_all_same(offset)),
2120 do_aa: true,
2121 });
2122 builder
2123 .wr()
2124 .push_border(&common, outline_rect, widths, details)
2125 }
2126
2127 fn build_box_shadow(&self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2128 let style = self.fragment.style();
2129 let box_shadows = &style.get_effects().box_shadow.0;
2130 if box_shadows.is_empty() {
2131 return;
2132 }
2133
2134 for box_shadow in box_shadows.iter().rev() {
2136 let (rect, clip_mode) = if box_shadow.inset {
2137 (*self.padding_rect(), BoxShadowClipMode::Inset)
2138 } else {
2139 (self.border_rect, BoxShadowClipMode::Outset)
2140 };
2141
2142 let offset = LayoutVector2D::new(
2143 box_shadow.base.horizontal.px(),
2144 box_shadow.base.vertical.px(),
2145 );
2146 let spread = box_shadow.spread.px();
2147 let blur = box_shadow.base.blur.px();
2148 let clip_rect = match clip_mode {
2149 BoxShadowClipMode::Inset => rect,
2151 BoxShadowClipMode::Outset => {
2154 let extra_size_from_blur = (blur * 3.0).ceil();
2155 rect.translate(offset)
2156 .inflate(spread, spread)
2157 .inflate(extra_size_from_blur, extra_size_from_blur)
2158 },
2159 };
2160 let border_radius = match clip_mode {
2161 BoxShadowClipMode::Inset => {
2162 offset_radii(self.border_radius(), -self.fragment.border.to_webrender())
2166 },
2167 BoxShadowClipMode::Outset => self.border_radius(),
2168 };
2169 let shadow_radius = offset_radii(
2170 border_radius,
2171 SideOffsets2D::new_all_same(match clip_mode {
2172 BoxShadowClipMode::Inset => -spread,
2173 BoxShadowClipMode::Outset => spread,
2174 }),
2175 );
2176 let common = builder.common_properties(state, clip_rect, style);
2177 builder.wr().push_box_shadow(
2178 &common,
2179 rect,
2180 offset,
2181 rgba(style.resolve_color(&box_shadow.base.color)),
2182 blur,
2183 spread,
2184 border_radius,
2185 shadow_radius,
2186 clip_mode,
2187 );
2188 }
2189 }
2190}
2191
2192fn rgba(color: AbsoluteColor) -> wr::ColorF {
2193 let rgba = color.to_color_space(ColorSpace::Srgb);
2194 wr::ColorF::new(
2195 rgba.components.0.clamp(0.0, 1.0),
2196 rgba.components.1.clamp(0.0, 1.0),
2197 rgba.components.2.clamp(0.0, 1.0),
2198 rgba.alpha,
2199 )
2200}
2201
2202fn glyphs(
2203 shaped_text_slices: &[Arc<ShapedTextSlice>],
2204 mut baseline_origin: PhysicalPoint<Au>,
2205 justification_adjustment: Au,
2206 include_whitespace: bool,
2207) -> (Vec<GlyphInstance>, Au) {
2208 let mut glyphs = vec![];
2209 let mut largest_advance = Au::zero();
2210
2211 for shaped_text_slice in shaped_text_slices {
2212 for glyph in shaped_text_slice.glyphs() {
2213 if !shaped_text_slice.is_whitespace() || include_whitespace {
2214 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2215 let point = LayoutPoint::new(
2216 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
2217 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
2218 );
2219 let glyph_instance = GlyphInstance {
2220 index: glyph.id(),
2221 point,
2222 };
2223 glyphs.push(glyph_instance);
2224 }
2225
2226 if glyph.char_is_word_separator() {
2227 baseline_origin.x += justification_adjustment;
2228 }
2229
2230 let advance = glyph.advance();
2231 baseline_origin.x += advance;
2232 largest_advance.max_assign(advance);
2233 }
2234 }
2235 (glyphs, largest_advance)
2236}
2237
2238fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
2242 let expand = |radius: &mut f32, offset: f32| {
2243 if offset < 0.0 {
2245 *radius = (*radius + offset).max(0.0);
2246 return;
2247 }
2248
2249 if *radius > 0.0 {
2254 *radius += offset;
2255 }
2256 };
2257 if offsets.left != 0.0 {
2258 expand(&mut radii.top_left.width, offsets.left);
2259 expand(&mut radii.bottom_left.width, offsets.left);
2260 }
2261 if offsets.right != 0.0 {
2262 expand(&mut radii.top_right.width, offsets.right);
2263 expand(&mut radii.bottom_right.width, offsets.right);
2264 }
2265 if offsets.top != 0.0 {
2266 expand(&mut radii.top_left.height, offsets.top);
2267 expand(&mut radii.top_right.height, offsets.top);
2268 }
2269 if offsets.bottom != 0.0 {
2270 expand(&mut radii.bottom_right.height, offsets.bottom);
2271 expand(&mut radii.bottom_left.height, offsets.bottom);
2272 }
2273 radii
2274}
2275
2276fn resolve_border_image_outset(
2278 outset: BorderImageOutset,
2279 border: SideOffsets2D<f32, LayoutPixel>,
2280) -> SideOffsets2D<f32, LayoutPixel> {
2281 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
2282 match outset {
2283 NonNegativeLengthOrNumber::Length(length) => length.px(),
2284 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
2285 }
2286 }
2287
2288 SideOffsets2D::new(
2289 image_outset_for_side(outset.0, border.top),
2290 image_outset_for_side(outset.1, border.right),
2291 image_outset_for_side(outset.2, border.bottom),
2292 image_outset_for_side(outset.3, border.left),
2293 )
2294}
2295
2296fn resolve_border_image_width(
2298 width: &BorderImageWidth,
2299 border: SideOffsets2D<f32, LayoutPixel>,
2300 border_area: Size2D<f32, LayoutPixel>,
2301) -> SideOffsets2D<f32, LayoutPixel> {
2302 fn image_width_for_side(
2303 border_image_width: &BorderImageSideWidth,
2304 border_width: f32,
2305 total_length: f32,
2306 ) -> f32 {
2307 match border_image_width {
2308 BorderImageSideWidth::LengthPercentage(v) => {
2309 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
2310 },
2311 BorderImageSideWidth::Number(x) => border_width * x.0,
2312 BorderImageSideWidth::Auto => border_width,
2313 }
2314 }
2315
2316 SideOffsets2D::new(
2317 image_width_for_side(&width.0, border.top, border_area.height),
2318 image_width_for_side(&width.1, border.right, border_area.width),
2319 image_width_for_side(&width.2, border.bottom, border_area.height),
2320 image_width_for_side(&width.3, border.left, border_area.width),
2321 )
2322}
2323
2324fn resolve_border_image_slice(
2326 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
2327 size: Size2D<i32, UnknownUnit>,
2328) -> SideOffsets2D<i32, DevicePixel> {
2329 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
2330 match value.0 {
2331 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
2332 NumberOrPercentage::Number(n) => n.round() as i32,
2333 }
2334 }
2335
2336 SideOffsets2D::new(
2337 resolve_percentage(border_image_slice.0, size.height),
2338 resolve_percentage(border_image_slice.1, size.width),
2339 resolve_percentage(border_image_slice.2, size.height),
2340 resolve_percentage(border_image_slice.3, size.width),
2341 )
2342}
2343
2344pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
2345 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
2353 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
2354 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
2355 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
2356 if f < 1.0 {
2357 radius.top_left *= f;
2358 radius.top_right *= f;
2359 radius.bottom_right *= f;
2360 radius.bottom_left *= f;
2361 }
2362}
2363
2364pub(super) fn compute_margin_box_radius(
2372 radius: wr::BorderRadius,
2373 layout_rect: LayoutSize,
2374 fragment: &BoxFragment,
2375) -> wr::BorderRadius {
2376 let style = fragment.style();
2377 let margin = style.physical_margin();
2378 let adjust_radius = |radius: f32, margin: f32| -> f32 {
2379 if margin <= 0. || (radius / margin) >= 1. {
2380 (radius + margin).max(0.)
2381 } else {
2382 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
2383 }
2384 };
2385 let compute_margin_radius = |radius: LayoutSize,
2386 layout_rect: LayoutSize,
2387 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
2388 -> LayoutSize {
2389 let zero = LengthPercentage::zero();
2390 let width = margin
2391 .width
2392 .auto_is(|| &zero)
2393 .to_used_value(Au::from_f32_px(layout_rect.width));
2394 let height = margin
2395 .height
2396 .auto_is(|| &zero)
2397 .to_used_value(Au::from_f32_px(layout_rect.height));
2398 LayoutSize::new(
2399 adjust_radius(radius.width, width.to_f32_px()),
2400 adjust_radius(radius.height, height.to_f32_px()),
2401 )
2402 };
2403 wr::BorderRadius {
2404 top_left: compute_margin_radius(
2405 radius.top_left,
2406 layout_rect,
2407 Size2D::new(margin.left, margin.top),
2408 ),
2409 top_right: compute_margin_radius(
2410 radius.top_right,
2411 layout_rect,
2412 Size2D::new(margin.right, margin.top),
2413 ),
2414 bottom_left: compute_margin_radius(
2415 radius.bottom_left,
2416 layout_rect,
2417 Size2D::new(margin.left, margin.bottom),
2418 ),
2419 bottom_right: compute_margin_radius(
2420 radius.bottom_right,
2421 layout_rect,
2422 Size2D::new(margin.right, margin.bottom),
2423 ),
2424 }
2425}
2426
2427impl BoxFragment {
2428 fn border_radius(&self) -> BorderRadius {
2429 let style = self.style();
2430 let border = style.get_border();
2431 if border.border_top_left_radius.0.is_zero() &&
2432 border.border_top_right_radius.0.is_zero() &&
2433 border.border_bottom_right_radius.0.is_zero() &&
2434 border.border_bottom_left_radius.0.is_zero()
2435 {
2436 return BorderRadius::zero();
2437 }
2438
2439 let border_rect = self.border_rect();
2440 let resolve =
2441 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2442 let corner = |corner: &style::values::computed::BorderCornerRadius| {
2443 Size2D::new(
2444 resolve(&corner.0.width.0, border_rect.size.width),
2445 resolve(&corner.0.height.0, border_rect.size.height),
2446 )
2447 };
2448
2449 let mut radius = wr::BorderRadius {
2450 top_left: corner(&border.border_top_left_radius),
2451 top_right: corner(&border.border_top_right_radius),
2452 bottom_right: corner(&border.border_bottom_right_radius),
2453 bottom_left: corner(&border.border_bottom_left_radius),
2454 };
2455
2456 normalize_radii(&border_rect.to_webrender(), &mut radius);
2457 radius
2458 }
2459}
2460
2461impl BaseFragment {
2462 fn visit_fragment(&self, builder: &mut DisplayListBuilder) {
2463 match self.status() {
2464 FragmentStatus::New => {
2465 builder.reflow_statistics.rebuilt_fragment_count += 1;
2466 self.set_status(FragmentStatus::Clean)
2467 },
2468 FragmentStatus::StyleChanged => {
2469 builder.reflow_statistics.restyle_fragment_count += 1;
2470 self.set_status(FragmentStatus::Clean)
2471 },
2472 FragmentStatus::OnlyDescendantsChanged => {
2473 builder.reflow_statistics.only_descendants_changed_count += 1;
2474 self.set_status(FragmentStatus::Clean)
2475 },
2476 FragmentStatus::Clean => {},
2477 }
2478 }
2479}