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 fn visit_stacking_context_reference_frame_info(
639 &mut self,
640 stacking_context: &StackingContext,
641 ) -> (usize, Option<ScrollTreeNodeId>) {
642 let Some(reference_frame_info) = &stacking_context.reference_frame_info else {
643 return (0, None);
644 };
645
646 let old_reference_frame_spatial_id = std::mem::replace(
650 &mut self.current_reference_frame_scroll_node_id,
651 stacking_context.scroll_tree_node_id,
652 );
653
654 if reference_frame_info.captured_clip_id == ClipId::INVALID {
655 return (0, Some(old_reference_frame_spatial_id));
656 }
657
658 let clip_chain_id = Some(self.clip_chain_id(reference_frame_info.captured_clip_id));
667 let spatial_id = self.spatial_id(reference_frame_info.parent_spatial_node_id);
668 self.wr().push_stacking_context(
669 spatial_id,
670 PrimitiveFlags::default(),
671 clip_chain_id,
672 webrender_api::TransformStyle::Flat,
673 webrender_api::MixBlendMode::Normal,
674 &[], &[], wr::RasterSpace::Screen,
677 wr::StackingContextFlags::empty(),
678 None, );
680
681 (1, Some(old_reference_frame_spatial_id))
682 }
683}
684
685impl PaintTraversalHandler for DisplayListBuilder<'_> {
686 type StackingContextState = (usize, Option<ScrollTreeNodeId>);
691
692 fn visit_stacking_context(
693 &mut self,
694 stacking_context: &StackingContext,
695 ) -> Self::StackingContextState {
696 let (mut stacking_contexts_pushed, old_reference_frame) =
697 self.visit_stacking_context_reference_frame_info(stacking_context);
698 if self.push_webrender_stacking_context_if_necessary(stacking_context) {
699 stacking_contexts_pushed += 1;
700 }
701 (stacking_contexts_pushed, old_reference_frame)
702 }
703
704 fn leave_stacking_context(
705 &mut self,
706 _: &TraversalState,
707 stacking_context_state: Self::StackingContextState,
708 ) {
709 let (stacking_contexts_pushed, old_reference_frame) = stacking_context_state;
710 for _ in 0..stacking_contexts_pushed {
711 self.wr().pop_stacking_context();
712 }
713
714 if let Some(old_reference_frame) = old_reference_frame {
715 self.current_reference_frame_scroll_node_id = old_reference_frame;
716 }
717 }
718
719 fn visit_box(&mut self, state: &TraversalState, fragment: &BoxFragmentWithStyle<'_>) {
720 fragment.base.visit_fragment(self);
721
722 if let Some(mut inspector_highlight) = self.inspector_highlight.take() &&
723 fragment.base.tag == Some(inspector_highlight.tag)
724 {
725 inspector_highlight.register_fragment_of_highlighted_dom_node(self, state, fragment);
726 self.inspector_highlight = Some(inspector_highlight);
727 }
728
729 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
730 return;
731 };
732
733 BuilderForBoxFragment::new(fragment, state.origin).build(self, state)
734 }
735
736 fn visit_iframe(&mut self, state: &TraversalState, fragment: &Arc<IFrameFragment>) {
737 fragment.base.visit_fragment(self);
738
739 let style = fragment.base.style();
740 if style.get_inherited_box().visibility != Visibility::Visible {
741 return;
742 }
743
744 let rect = fragment.base.rect().translate(state.origin.to_vector());
745 let common = self.common_properties(state, rect.to_webrender(), &style);
746 self.wr().push_iframe(
747 rect.to_webrender(),
748 common.clip_rect,
749 &wr::SpaceAndClipInfo {
750 spatial_id: common.spatial_id,
751 clip_chain_id: common.clip_chain_id,
752 },
753 fragment.pipeline_id.into(),
754 true,
755 );
756 self.check_if_paintable(rect.to_webrender(), common.clip_rect, style.clone_opacity());
761 }
762
763 fn visit_image(
764 &mut self,
765 state: &TraversalState,
766 containing_block: PhysicalRect<Au>,
767 fragment: &Arc<ImageFragment>,
768 ) {
769 fragment.base.visit_fragment(self);
770
771 let style = fragment.base.style();
772 if style.get_inherited_box().visibility != Visibility::Visible {
773 return;
774 }
775
776 let image_rendering = style.get_inherited_box().image_rendering.to_webrender();
777 let rect = fragment
778 .base
779 .rect()
780 .translate(containing_block.origin.to_vector())
781 .to_webrender();
782 let clip = fragment
783 .clip
784 .translate(containing_block.origin.to_vector())
785 .to_webrender();
786 let common = self.common_properties(state, clip, &style);
787
788 if let Some(image_key) = fragment.image_key {
789 self.wr().push_image(
790 &common,
791 rect,
792 image_rendering,
793 wr::AlphaType::PremultipliedAlpha,
794 image_key,
795 wr::ColorF::WHITE,
796 );
797
798 self.check_if_paintable(rect, common.clip_rect, style.clone_opacity());
799
800 if !fragment.showing_broken_image_icon {
808 self.mark_is_contentful();
809
810 self.check_for_lcp_candidate(
811 state,
812 common.clip_rect,
813 rect,
814 fragment.base.tag,
815 fragment.url.clone(),
816 );
817 }
818 }
819
820 if fragment.showing_broken_image_icon {
821 Fragment::build_display_list_for_broken_image_border(self, &containing_block, &common);
822 }
823 }
824
825 fn visit_text(
826 &mut self,
827 state: &TraversalState,
828 containing_block: PhysicalRect<Au>,
829 fragment: &Arc<TextFragment>,
830 ) {
831 fragment.base.visit_fragment(self);
832
833 let style = fragment.base.style();
834 if style.get_inherited_box().visibility != Visibility::Visible {
835 return;
836 }
837 Fragment::build_display_list_for_text_fragment(fragment, self, state, &containing_block);
838 }
839
840 fn visit_positioning(&mut self, _state: &TraversalState, fragment: &Arc<PositioningFragment>) {
841 fragment.base.visit_fragment(self);
842 }
843
844 fn visit_box_for_root_background(&mut self, state: &TraversalState) {
850 let Some(fragment) = self.fragment_tree.root_box_fragment() else {
851 return;
852 };
853 let fragment = fragment.with_style();
854
855 let source_style = {
856 let root_fragment_style = fragment.style();
862 if root_fragment_style.background_is_transparent() {
863 let body_fragment = self.fragment_tree.body_fragment();
864 self.paint_body_background = body_fragment.is_none();
865 body_fragment
866 .map(|body_fragment| body_fragment.style().clone())
867 .unwrap_or(fragment.style().clone())
868 } else {
869 root_fragment_style.clone()
870 }
871 };
872
873 if source_style.background_is_transparent() {
876 return;
877 }
878
879 let painting_area = self
886 .fragment_tree
887 .initial_containing_block
888 .union(&self.fragment_tree.scrollable_overflow())
889 .to_webrender();
890
891 let background_color =
892 source_style.resolve_color(&source_style.get_background().background_color);
893 if background_color.alpha > 0.0 {
894 let common = self.common_properties(state, painting_area, &source_style);
895 let color = rgba(background_color);
896 self.wr().push_rect(&common, painting_area, color);
897
898 let default_background_color = servo_config::pref!(shell_background_color_rgba);
902 let default_background_color = AbsoluteColor::new(
903 ColorSpace::Srgb,
904 default_background_color[0] as f32,
905 default_background_color[1] as f32,
906 default_background_color[2] as f32,
907 default_background_color[3] as f32,
908 )
909 .into_srgb_legacy();
910 if background_color != default_background_color {
911 self.mark_is_paintable();
912 }
913 }
914
915 let mut fragment_builder = BuilderForBoxFragment::new(
916 &fragment,
917 self.fragment_tree.initial_containing_block.origin,
918 );
919 let painter = BackgroundPainter {
920 style: &source_style,
921 painting_area_override: Some(painting_area),
922 positioning_area_override: None,
923 };
924 fragment_builder.build_background_image(self, state, &painter);
925 }
926
927 fn visit_box_for_outline(&mut self, state: &TraversalState, fragment: &Arc<BoxFragment>) {
928 let fragment = fragment.with_style();
929 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
930 return;
931 };
932 BuilderForBoxFragment::new(&fragment, state.origin).build_outline(self, state)
933 }
934
935 fn visit_box_for_collapsed_table_borders(
936 &mut self,
937 state: &TraversalState,
938 fragment: &BoxFragmentWithStyle<'_>,
939 ) {
940 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
941 return;
942 };
943 BuilderForBoxFragment::new(fragment, state.origin)
944 .build_collapsed_table_borders(self, state)
945 }
946}
947
948impl InspectorHighlight {
949 fn register_fragment_of_highlighted_dom_node(
950 &mut self,
951 builder: &mut DisplayListBuilder,
952 traversal_state: &TraversalState,
953 fragment: &Arc<BoxFragment>,
954 ) {
955 let spatial_id = builder.spatial_id(traversal_state.spatial_id);
956 let clip_chain_id = builder.clip_chain_id(traversal_state.clip_id);
957 let state = self.state.get_or_insert_with(|| HighlightTraversalState {
958 content_box: Rect::zero(),
959 spatial_id,
960 clip_chain_id,
961 maybe_box_fragment: Some(fragment.clone()),
962 });
963
964 if spatial_id != state.spatial_id {
967 return;
968 }
969
970 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
971 debug_assert_eq!(
972 clip_chain_id, state.clip_chain_id,
973 "Fragments of the same node must either have no clip chain or the same one"
974 );
975 }
976
977 state.maybe_box_fragment = Some(fragment.clone());
978 state.content_box = state.content_box.union(
979 &fragment
980 .base
981 .rect()
982 .translate(traversal_state.origin.to_vector()),
983 );
984 }
985}
986
987impl Fragment {
988 fn build_display_list_for_text_fragment(
989 fragment: &TextFragment,
990 builder: &mut DisplayListBuilder,
991 state: &TraversalState,
992 containing_block: &PhysicalRect<Au>,
993 ) {
994 let rect = fragment
997 .base
998 .rect()
999 .translate(containing_block.origin.to_vector());
1000 let mut baseline_origin = rect.origin;
1001 baseline_origin.y += fragment.font_metrics.ascent;
1002
1003 let include_whitespace = fragment.offsets.is_some() ||
1004 state
1005 .text_decorations
1006 .iter()
1007 .any(|item| !item.line.is_empty());
1008
1009 let (glyphs, largest_advance) = glyphs(
1010 &fragment.glyphs,
1011 baseline_origin,
1012 fragment.justification_adjustment,
1013 include_whitespace,
1014 );
1015
1016 if glyphs.is_empty() && !fragment.is_empty_for_text_cursor {
1017 return;
1018 }
1019
1020 let parent_style = fragment.base.style();
1021 let color = parent_style.clone_color();
1022 let font_metrics = &fragment.font_metrics;
1023 let dppx = builder.device_pixel_ratio.get();
1024
1025 let glyph_bounds = rect
1032 .inflate(largest_advance.scale_by(2.0), Au::zero())
1033 .to_webrender();
1034 let common = builder.common_properties(state, glyph_bounds, &parent_style);
1035
1036 let shadows = &parent_style.get_inherited_text().text_shadow;
1039 for shadow in shadows.0.iter().rev() {
1040 builder.wr().push_shadow(
1041 &wr::SpaceAndClipInfo {
1042 spatial_id: common.spatial_id,
1043 clip_chain_id: common.clip_chain_id,
1044 },
1045 wr::Shadow {
1046 offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
1047 color: rgba(shadow.color.resolve_to_absolute(&color)),
1048 blur_radius: shadow.blur.px(),
1049 },
1050 true, );
1052 }
1053
1054 for text_decoration in state.text_decorations.iter() {
1055 if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
1056 let mut rect = rect;
1057 rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
1058 rect.size.height =
1059 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
1060
1061 Self::build_display_list_for_text_decoration(
1062 state,
1063 &parent_style,
1064 builder,
1065 &rect,
1066 text_decoration,
1067 TextDecorationLine::UNDERLINE,
1068 );
1069 }
1070 }
1071
1072 for text_decoration in state.text_decorations.iter() {
1073 if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
1074 let mut rect = rect;
1075 rect.size.height =
1076 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
1077 Self::build_display_list_for_text_decoration(
1078 state,
1079 &parent_style,
1080 builder,
1081 &rect,
1082 text_decoration,
1083 TextDecorationLine::OVERLINE,
1084 );
1085 }
1086 }
1087
1088 Self::build_display_list_for_text_selection(
1089 fragment,
1090 builder,
1091 state,
1092 containing_block,
1093 fragment.base.rect().min_x(),
1094 fragment.justification_adjustment,
1095 );
1096
1097 builder.wr().push_text(
1098 &common,
1099 glyph_bounds,
1100 &glyphs,
1101 fragment.font_key,
1102 rgba(color),
1103 None,
1104 );
1105
1106 builder.check_if_paintable(glyph_bounds, common.clip_rect, parent_style.clone_opacity());
1107
1108 builder.mark_is_contentful();
1112
1113 for text_decoration in state.text_decorations.iter() {
1114 if text_decoration
1115 .line
1116 .contains(TextDecorationLine::LINE_THROUGH)
1117 {
1118 let mut rect = rect;
1119 rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
1120 rect.size.height =
1121 Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
1122 Self::build_display_list_for_text_decoration(
1123 state,
1124 &parent_style,
1125 builder,
1126 &rect,
1127 text_decoration,
1128 TextDecorationLine::LINE_THROUGH,
1129 );
1130 }
1131 }
1132
1133 if !shadows.0.is_empty() {
1134 builder.wr().pop_all_shadows();
1135 }
1136 }
1137
1138 fn build_display_list_for_text_decoration(
1139 state: &TraversalState,
1140 parent_style: &ServoArc<ComputedValues>,
1141 builder: &mut DisplayListBuilder,
1142 rect: &PhysicalRect<Au>,
1143 text_decoration: &FragmentTextDecoration,
1144 line: TextDecorationLine,
1145 ) {
1146 if text_decoration.style == ComputedTextDecorationStyle::MozNone {
1147 return;
1148 }
1149
1150 let mut rect = rect.to_webrender();
1151 let wavy_line_thickness = rect.height().ceil();
1152 if text_decoration.style == ComputedTextDecorationStyle::Wavy {
1153 rect = rect.inflate(0.0, wavy_line_thickness);
1154 }
1155
1156 let expand_rect_for_text_decoration = |mut rect: Box2D<f32, LayoutPixel>| {
1163 if matches!(
1164 text_decoration.style,
1165 ComputedTextDecorationStyle::Dotted |
1166 ComputedTextDecorationStyle::Dashed |
1167 ComputedTextDecorationStyle::Wavy,
1168 ) {
1169 rect.min.x = rect.min.x.min(0.0);
1170 }
1171 rect
1172 };
1173
1174 let common_properties = builder.common_properties(state, rect, parent_style);
1175 builder.wr().push_line(
1176 &common_properties,
1177 &expand_rect_for_text_decoration(rect),
1178 wavy_line_thickness,
1179 wr::LineOrientation::Horizontal,
1180 &rgba(text_decoration.color),
1181 text_decoration.style.to_webrender(),
1182 );
1183
1184 if text_decoration.style == TextDecorationStyle::Double {
1185 let half_height = (rect.height() / 2.0).floor().max(1.0);
1186 let y_offset = match line {
1187 TextDecorationLine::OVERLINE => -rect.height() - half_height,
1188 _ => rect.height() + half_height,
1189 };
1190 let rect = rect.translate(Vector2D::new(0.0, y_offset));
1191 let common_properties = builder.common_properties(state, rect, parent_style);
1192 builder.wr().push_line(
1193 &common_properties,
1194 &rect,
1195 wavy_line_thickness,
1196 wr::LineOrientation::Horizontal,
1197 &rgba(text_decoration.color),
1198 text_decoration.style.to_webrender(),
1199 );
1200 }
1201 }
1202
1203 fn build_display_list_for_broken_image_border(
1204 builder: &mut DisplayListBuilder,
1205 containing_block: &PhysicalRect<Au>,
1206 common: &CommonItemProperties,
1207 ) {
1208 let border_side = BorderSide {
1209 color: ColorF::BLACK,
1210 style: wr::BorderStyle::Inset,
1211 };
1212 builder.wr().push_border(
1213 common,
1214 containing_block.to_webrender(),
1215 LayoutSideOffsets::new_all_same(1.0),
1216 BorderDetails::Normal(NormalBorder {
1217 left: border_side,
1218 right: border_side,
1219 top: border_side,
1220 bottom: border_side,
1221 radius: BorderRadius::zero(),
1222 do_aa: true,
1223 }),
1224 );
1225 }
1226
1227 fn build_display_list_for_text_selection(
1230 fragment: &TextFragment,
1231 builder: &mut DisplayListBuilder<'_>,
1232 state: &TraversalState,
1233 containing_block_rect: &PhysicalRect<Au>,
1234 fragment_x_offset: Au,
1235 justification_adjustment: Au,
1236 ) {
1237 let Some(offsets) = fragment.offsets.as_ref() else {
1238 return;
1239 };
1240
1241 let shared_selection = offsets.shared_selection.borrow();
1242 if !shared_selection.enabled {
1243 return;
1244 }
1245
1246 if offsets.character_range.start > shared_selection.character_range.end ||
1247 offsets.character_range.end < shared_selection.character_range.start
1248 {
1249 return;
1250 }
1251
1252 if fragment.is_empty_for_text_cursor &&
1257 !offsets
1258 .character_range
1259 .contains(&shared_selection.character_range.start)
1260 {
1261 return;
1262 }
1263
1264 let mut current_character_index = offsets.character_range.start;
1265 let mut current_advance = Au::zero();
1266 let mut start_advance = None;
1267 let mut end_advance = None;
1268 for glyph_store in fragment.glyphs.iter() {
1269 let glyph_store_character_count = glyph_store.character_count();
1270 if current_character_index + glyph_store_character_count <
1271 shared_selection.character_range.start
1272 {
1273 current_advance += glyph_store.total_advance() +
1274 (justification_adjustment * glyph_store.total_word_separators() as i32);
1275 current_character_index += glyph_store_character_count;
1276 continue;
1277 }
1278
1279 if current_character_index >= shared_selection.character_range.end {
1280 break;
1281 }
1282
1283 for glyph in glyph_store.glyphs() {
1284 if current_character_index >= shared_selection.character_range.start {
1285 start_advance = start_advance.or(Some(current_advance));
1286 }
1287
1288 current_character_index += glyph.character_count();
1289 current_advance += glyph.advance();
1290 if glyph.char_is_word_separator() {
1291 current_advance += justification_adjustment;
1292 }
1293
1294 if current_character_index <= shared_selection.character_range.end {
1295 end_advance = Some(current_advance);
1296 }
1297 }
1298 }
1299
1300 let start_x = start_advance.unwrap_or(current_advance);
1301 let end_x = end_advance.unwrap_or(current_advance);
1302
1303 let parent_style = fragment.base.style();
1304 if !shared_selection.range.is_empty() {
1305 let selection_rect = Rect::new(
1306 containing_block_rect.origin +
1307 Vector2D::new(fragment_x_offset + start_x, Au::zero()),
1308 Size2D::new(end_x - start_x, containing_block_rect.height()),
1309 )
1310 .to_webrender();
1311
1312 if let Some(selection_color) = fragment
1313 .selected_style
1314 .borrow()
1315 .clone_background_color()
1316 .as_absolute()
1317 {
1318 let selection_common =
1319 builder.common_properties(state, selection_rect, &parent_style);
1320 builder
1321 .wr()
1322 .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1323 }
1324 return;
1325 }
1326
1327 let insertion_point_rect = Rect::new(
1328 containing_block_rect.origin + Vector2D::new(start_x + fragment_x_offset, Au::zero()),
1329 Size2D::new(
1330 INSERTION_POINT_LOGICAL_WIDTH,
1331 containing_block_rect.height(),
1332 ),
1333 )
1334 .to_webrender();
1335
1336 let color = parent_style.clone_color();
1337 let caret_color = match parent_style.clone_caret_color().0 {
1338 ColorOrAuto::Color(caret_color) => caret_color.resolve_to_absolute(&color),
1339 ColorOrAuto::Auto => color,
1340 };
1341 let insertion_point_common =
1342 builder.common_properties(state, insertion_point_rect, &parent_style);
1343
1344 let caret_color = rgba(caret_color);
1345 let property_binding = if prefs::get().editing_caret_blink_time().is_some() {
1346 let pipeline_id: PipelineId = builder.paint_info.pipeline_id.into();
1350 let property_binding_key = PropertyBindingKey::new(pipeline_id.into());
1351 builder.paint_info.caret_property_binding = Some((property_binding_key, caret_color));
1352 PropertyBinding::Binding(property_binding_key, caret_color)
1353 } else {
1354 PropertyBinding::Value(caret_color)
1355 };
1356
1357 builder.wr().push_rect_with_animation(
1358 &insertion_point_common,
1359 insertion_point_rect,
1360 property_binding,
1361 );
1362 }
1363}
1364
1365struct BuilderForBoxFragment<'a> {
1366 fragment: &'a BoxFragmentWithStyle<'a>,
1367 containing_block_origin: PhysicalPoint<Au>,
1368 border_rect: units::LayoutRect,
1369 margin_rect: OnceCell<units::LayoutRect>,
1370 padding_rect: OnceCell<units::LayoutRect>,
1371 content_rect: OnceCell<units::LayoutRect>,
1372 border_radius: OnceCell<wr::BorderRadius>,
1373 border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1374 padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1375 content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1376}
1377
1378impl<'a> BuilderForBoxFragment<'a> {
1379 fn new(
1380 fragment: &'a BoxFragmentWithStyle<'a>,
1381 containing_block_origin: PhysicalPoint<Au>,
1382 ) -> Self {
1383 let border_rect = fragment
1384 .border_rect()
1385 .translate(containing_block_origin.to_vector());
1386 Self {
1387 fragment,
1388 containing_block_origin,
1389 border_rect: border_rect.to_webrender(),
1390 border_radius: OnceCell::new(),
1391 margin_rect: OnceCell::new(),
1392 padding_rect: OnceCell::new(),
1393 content_rect: OnceCell::new(),
1394 border_edge_clip_chain_id: RefCell::new(None),
1395 padding_edge_clip_chain_id: RefCell::new(None),
1396 content_edge_clip_chain_id: RefCell::new(None),
1397 }
1398 }
1399
1400 fn border_radius(&self) -> BorderRadius {
1401 *self
1402 .border_radius
1403 .get_or_init(|| self.fragment.border_radius())
1404 }
1405
1406 fn content_rect(&self) -> &units::LayoutRect {
1407 self.content_rect.get_or_init(|| {
1408 self.fragment
1409 .content_rect()
1410 .translate(self.containing_block_origin.to_vector())
1411 .to_webrender()
1412 })
1413 }
1414
1415 fn padding_rect(&self) -> &units::LayoutRect {
1416 self.padding_rect.get_or_init(|| {
1417 self.fragment
1418 .padding_rect()
1419 .translate(self.containing_block_origin.to_vector())
1420 .to_webrender()
1421 })
1422 }
1423
1424 fn margin_rect(&self) -> &units::LayoutRect {
1425 self.margin_rect.get_or_init(|| {
1426 self.fragment
1427 .margin_rect()
1428 .translate(self.containing_block_origin.to_vector())
1429 .to_webrender()
1430 })
1431 }
1432
1433 fn border_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.border_edge_clip_chain_id.borrow() {
1440 return Some(clip);
1441 }
1442
1443 let maybe_clip = builder.maybe_create_clip(
1444 state,
1445 self.border_radius(),
1446 self.border_rect,
1447 force_clip_creation,
1448 );
1449 *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1450 maybe_clip
1451 }
1452
1453 fn padding_edge_clip(
1454 &self,
1455 builder: &mut DisplayListBuilder,
1456 state: &TraversalState,
1457 force_clip_creation: bool,
1458 ) -> Option<ClipChainId> {
1459 if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1460 return Some(clip);
1461 }
1462
1463 let radii = offset_radii(self.border_radius(), -self.fragment.border.to_webrender());
1464 let maybe_clip =
1465 builder.maybe_create_clip(state, radii, *self.padding_rect(), force_clip_creation);
1466 *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1467 maybe_clip
1468 }
1469
1470 fn content_edge_clip(
1471 &self,
1472 builder: &mut DisplayListBuilder,
1473 state: &TraversalState,
1474 force_clip_creation: bool,
1475 ) -> Option<ClipChainId> {
1476 if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1477 return Some(clip);
1478 }
1479
1480 let radii = offset_radii(
1481 self.border_radius(),
1482 -(self.fragment.border + self.fragment.padding).to_webrender(),
1483 );
1484 let maybe_clip =
1485 builder.maybe_create_clip(state, radii, *self.content_rect(), force_clip_creation);
1486 *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1487 maybe_clip
1488 }
1489
1490 fn build(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1491 if self
1492 .fragment
1493 .base
1494 .flags
1495 .contains(FragmentFlags::DO_NOT_PAINT)
1496 {
1497 return;
1498 }
1499
1500 self.build_background(builder, state);
1501 self.build_box_shadow(builder, state);
1502 if !self.fragment.is_table_grid_with_collapsed_borders() {
1503 self.build_border(builder, state);
1504 }
1505
1506 let overflow = self
1507 .fragment
1508 .style()
1509 .effective_overflow(self.fragment.base.flags);
1510 let scrolls_via_user_input =
1511 |overflow| matches!(overflow, ComputedOverflow::Scroll | ComputedOverflow::Auto);
1512 if (scrolls_via_user_input(overflow.x) || scrolls_via_user_input(overflow.y)) &&
1513 self.fragment.style().get_inherited_ui().pointer_events !=
1514 style::computed_values::pointer_events::T::None
1515 {
1516 let mut inner_state = state.clone();
1517 inner_state.spatial_id = self
1518 .fragment
1519 .generated_scroll_tree_node_id()
1520 .unwrap_or(state.spatial_id);
1521 inner_state.clip_id = self.fragment.generated_clip_id().unwrap_or(state.clip_id);
1522
1523 self.build_hit_test(
1524 builder,
1525 &inner_state,
1526 self.fragment
1527 .scrollable_overflow()
1528 .translate(self.containing_block_origin.to_vector())
1529 .to_webrender(),
1530 );
1531 }
1532 }
1533
1534 fn build_hit_test(
1535 &self,
1536 builder: &mut DisplayListBuilder,
1537 state: &TraversalState,
1538 rect: LayoutRect,
1539 ) {
1540 let external_scroll_node_id = builder
1541 .paint_info
1542 .external_scroll_id_for_scroll_tree_node(state.spatial_id);
1543
1544 let mut common = builder.common_properties(state, rect, self.fragment.style());
1545 if let Some(clip_chain_id) = self.border_edge_clip(builder, state, false) {
1546 common.clip_chain_id = clip_chain_id;
1547 }
1548 builder.wr().push_hit_test(
1549 common.clip_rect,
1550 common.clip_chain_id,
1551 common.spatial_id,
1552 common.flags,
1553 (external_scroll_node_id.0, 0), );
1555 }
1556
1557 fn build_background_for_painter(
1558 &mut self,
1559 builder: &mut DisplayListBuilder,
1560 state: &TraversalState,
1561 painter: &BackgroundPainter,
1562 ) {
1563 let b = painter.style.get_background();
1564 let background_color = painter.style.resolve_color(&b.background_color);
1565 if background_color.alpha > 0.0 {
1566 let layer_index = b.background_image.0.len() - 1;
1570 let bounds = painter.painting_area(self, builder, layer_index);
1571 let common = painter.common_properties(self, builder, state, layer_index, bounds);
1572 builder
1573 .wr()
1574 .push_rect(&common, bounds, rgba(background_color));
1575
1576 let default_background_color = servo_config::pref!(shell_background_color_rgba);
1580 let default_background_color = AbsoluteColor::new(
1581 ColorSpace::Srgb,
1582 default_background_color[0] as f32,
1583 default_background_color[1] as f32,
1584 default_background_color[2] as f32,
1585 default_background_color[3] as f32,
1586 )
1587 .into_srgb_legacy();
1588 if background_color != default_background_color {
1589 builder.mark_is_paintable();
1590 }
1591 }
1592
1593 self.build_background_image(builder, state, painter);
1594 }
1595
1596 fn build_background(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1597 let flags = self.fragment.base.flags;
1598
1599 if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1602 return;
1603 }
1604 if !builder.paint_body_background &&
1606 flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1607 {
1608 return;
1609 }
1610
1611 if let BackgroundMode::None = self.fragment.background_mode {
1613 return;
1614 }
1615
1616 if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1620 for extra_background in extra_backgrounds {
1621 let positioning_area = extra_background.rect;
1622 let painter = BackgroundPainter {
1623 style: &extra_background.style.borrow_mut(),
1624 painting_area_override: None,
1625 positioning_area_override: Some(
1626 positioning_area
1627 .translate(self.containing_block_origin.to_vector())
1628 .to_webrender(),
1629 ),
1630 };
1631 self.build_background_for_painter(builder, state, &painter);
1632 }
1633 }
1634
1635 let painter = BackgroundPainter {
1636 style: self.fragment.style(),
1637 painting_area_override: None,
1638 positioning_area_override: None,
1639 };
1640 self.build_background_for_painter(builder, state, &painter);
1641 }
1642
1643 fn build_background_image(
1644 &mut self,
1645 builder: &mut DisplayListBuilder,
1646 state: &TraversalState,
1647 painter: &BackgroundPainter,
1648 ) {
1649 let style = painter.style;
1650 let b = style.get_background();
1651 let need_blend_container = b
1652 .background_blend_mode
1653 .0
1654 .iter()
1655 .take(b.background_image.0.len())
1656 .any(|background_blend_mode| background_blend_mode != &BackgroundBlendMode::Normal);
1657
1658 let push_stacking_context = |builder: &mut DisplayListBuilder,
1659 blend_mode: BackgroundBlendMode,
1660 flags: StackingContextFlags|
1661 -> bool {
1662 let spatial_id = builder.spatial_id(state.spatial_id);
1663 builder.wr().push_stacking_context(
1664 spatial_id,
1665 PrimitiveFlags::empty(),
1666 None,
1667 webrender_api::TransformStyle::Flat,
1668 blend_mode.to_webrender(),
1669 &[],
1670 &[],
1671 RasterSpace::Screen,
1672 flags,
1673 None,
1674 );
1675 true
1676 };
1677
1678 if need_blend_container {
1679 push_stacking_context(
1680 builder,
1681 BackgroundBlendMode::Normal,
1682 StackingContextFlags::IS_BLEND_CONTAINER,
1683 );
1684 }
1685
1686 let node = self.fragment.base.tag.map(|tag| tag.node);
1687 for (index, image) in b.background_image.0.iter().enumerate().rev() {
1689 let Ok(resolved_image) = builder.image_resolver.resolve_image(node, image) else {
1690 continue;
1691 };
1692 match resolved_image {
1693 ResolvedImage::Gradient(_) | ResolvedImage::Color(_) => {
1694 let intrinsic = NaturalSizes::empty();
1695 let Some(layer) =
1696 &background::layout_layer(self, painter, builder, state, index, intrinsic)
1697 else {
1698 continue;
1699 };
1700
1701 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1702 if needs_blending {
1703 push_stacking_context(builder, layer.blend_mode, Default::default());
1704 }
1705
1706 match resolved_image {
1707 ResolvedImage::Gradient(gradient) => {
1708 match gradient::build(style, gradient, layer.tile_size, builder) {
1709 WebRenderGradient::Linear(linear_gradient) => {
1710 builder.wr().push_gradient(
1711 &layer.common,
1712 layer.bounds,
1713 linear_gradient,
1714 layer.tile_size,
1715 layer.tile_spacing,
1716 )
1717 },
1718 WebRenderGradient::Radial(radial_gradient) => {
1719 builder.wr().push_radial_gradient(
1720 &layer.common,
1721 layer.bounds,
1722 radial_gradient,
1723 layer.tile_size,
1724 layer.tile_spacing,
1725 )
1726 },
1727 WebRenderGradient::Conic(conic_gradient) => {
1728 builder.wr().push_conic_gradient(
1729 &layer.common,
1730 layer.bounds,
1731 conic_gradient,
1732 layer.tile_size,
1733 layer.tile_spacing,
1734 )
1735 },
1736 }
1737 },
1738 ResolvedImage::Color(color) => {
1739 let color = rgba(style.resolve_color(color));
1740 builder.wr().push_rect(&layer.common, layer.bounds, color);
1741 },
1742 _ => {},
1743 }
1744
1745 if needs_blending {
1746 builder.wr().pop_stacking_context();
1747 }
1748
1749 builder.check_if_paintable(
1750 layer.bounds,
1751 layer.common.clip_rect,
1752 style.clone_opacity(),
1753 );
1754 },
1755 ResolvedImage::Image { image, size } => {
1756 let dppx = 1.0;
1758 let intrinsic =
1759 NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1760 let layer =
1761 background::layout_layer(self, painter, builder, state, index, intrinsic);
1762
1763 let image_wr_key = match image {
1764 CachedImage::Raster(raster_image) => raster_image.id,
1765 CachedImage::Vector(vector_image) => {
1766 let scale = builder.device_pixel_ratio.get();
1767 let default_size: DeviceIntSize =
1768 Size2D::new(size.width * scale, size.height * scale).to_i32();
1769 let layer_size = layer.as_ref().map(|layer| {
1770 Size2D::new(
1771 layer.tile_size.width * scale,
1772 layer.tile_size.height * scale,
1773 )
1774 .to_i32()
1775 });
1776
1777 node.and_then(|node| {
1778 let size = layer_size.unwrap_or(default_size);
1779 builder.image_resolver.rasterize_vector_image(
1780 vector_image.id,
1781 size,
1782 node,
1783 vector_image.svg_id,
1784 )
1785 })
1786 .and_then(|rasterized_image| rasterized_image.id)
1787 },
1788 };
1789
1790 let Some(image_key) = image_wr_key else {
1791 continue;
1792 };
1793
1794 if let Some(layer) = layer {
1795 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1796 if needs_blending {
1797 push_stacking_context(builder, layer.blend_mode, Default::default());
1798 }
1799
1800 if layer.repeat {
1801 builder.wr().push_repeating_image(
1802 &layer.common,
1803 layer.bounds,
1804 layer.tile_size,
1805 layer.tile_spacing,
1806 style.clone_image_rendering().to_webrender(),
1807 wr::AlphaType::PremultipliedAlpha,
1808 image_key,
1809 wr::ColorF::WHITE,
1810 )
1811 } else {
1812 builder.wr().push_image(
1813 &layer.common,
1814 layer.bounds,
1815 style.clone_image_rendering().to_webrender(),
1816 wr::AlphaType::PremultipliedAlpha,
1817 image_key,
1818 wr::ColorF::WHITE,
1819 )
1820 }
1821
1822 if needs_blending {
1823 builder.wr().pop_stacking_context();
1824 }
1825
1826 builder.check_if_paintable(
1827 layer.bounds,
1828 layer.common.clip_rect,
1829 style.clone_opacity(),
1830 );
1831
1832 builder.mark_is_contentful();
1837
1838 builder.check_for_lcp_candidate(
1839 state,
1840 layer.common.clip_rect,
1841 layer.bounds,
1842 self.fragment.base.tag,
1843 None,
1844 );
1845 }
1846 },
1847 }
1848 }
1849
1850 if need_blend_container {
1851 builder.wr().pop_stacking_context();
1852 }
1853 }
1854
1855 fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1856 wr::BorderSide {
1857 color: rgba(style_color.color),
1858 style: match style_color.style {
1859 BorderStyle::None => wr::BorderStyle::None,
1860 BorderStyle::Solid => wr::BorderStyle::Solid,
1861 BorderStyle::Double => wr::BorderStyle::Double,
1862 BorderStyle::Dotted => wr::BorderStyle::Dotted,
1863 BorderStyle::Dashed => wr::BorderStyle::Dashed,
1864 BorderStyle::Hidden => wr::BorderStyle::Hidden,
1865 BorderStyle::Groove => wr::BorderStyle::Groove,
1866 BorderStyle::Ridge => wr::BorderStyle::Ridge,
1867 BorderStyle::Inset => wr::BorderStyle::Inset,
1868 BorderStyle::Outset => wr::BorderStyle::Outset,
1869 },
1870 }
1871 }
1872
1873 fn build_collapsed_table_borders(
1874 &mut self,
1875 builder: &mut DisplayListBuilder,
1876 state: &TraversalState,
1877 ) {
1878 if self
1879 .fragment
1880 .base
1881 .flags
1882 .contains(FragmentFlags::DO_NOT_PAINT)
1883 {
1884 return;
1885 }
1886
1887 let layout_info = self.fragment.specific_layout_info();
1888 let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1889 layout_info.as_deref()
1890 else {
1891 return;
1892 };
1893 let mut common =
1894 builder.common_properties(state, units::LayoutRect::default(), self.fragment.style());
1895 let radius = wr::BorderRadius::default();
1896 let mut column_sum = Au::zero();
1897 for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1898 let mut row_sum = Au::zero();
1899 for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1900 let left_border = &table_info.collapsed_borders.x[x][y];
1901 let right_border = &table_info.collapsed_borders.x[x + 1][y];
1902 let top_border = &table_info.collapsed_borders.y[y][x];
1903 let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1904 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1905 left: self.build_border_side(left_border.style_color.clone()),
1906 right: self.build_border_side(right_border.style_color.clone()),
1907 top: self.build_border_side(top_border.style_color.clone()),
1908 bottom: self.build_border_side(bottom_border.style_color.clone()),
1909 radius,
1910 do_aa: true,
1911 });
1912 let mut border_widths = PhysicalSides::new(
1913 top_border.width,
1914 right_border.width,
1915 bottom_border.width,
1916 left_border.width,
1917 );
1918 let left_adjustment = if x == 0 {
1919 -border_widths.left / 2
1920 } else {
1921 std::mem::take(&mut border_widths.left) / 2
1922 };
1923 let top_adjustment = if y == 0 {
1924 -border_widths.top / 2
1925 } else {
1926 std::mem::take(&mut border_widths.top) / 2
1927 };
1928 let origin =
1929 PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1930 let size = PhysicalSize::new(
1931 *column_size - left_adjustment + border_widths.right / 2,
1932 *row_size - top_adjustment + border_widths.bottom / 2,
1933 );
1934 let border_rect = PhysicalRect::new(origin, size)
1935 .translate(self.fragment.content_rect().origin.to_vector())
1936 .translate(self.containing_block_origin.to_vector())
1937 .to_webrender();
1938 common.clip_rect = border_rect;
1939 builder.wr().push_border(
1940 &common,
1941 border_rect,
1942 border_widths.to_webrender(),
1943 details,
1944 );
1945 row_sum += *row_size;
1946 }
1947 column_sum += *column_size;
1948 }
1949 }
1950
1951 fn build_border(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1952 if self.fragment.has_collapsed_borders() {
1953 return;
1956 }
1957
1958 let style = self.fragment.style();
1959 let border = style.get_border();
1960 let border_widths = self.fragment.border.to_webrender();
1961
1962 if border_widths == SideOffsets2D::zero() {
1963 return;
1964 }
1965
1966 if self.build_border_image(builder, state, border, border_widths) {
1968 return;
1969 }
1970
1971 let current_color = style.get_inherited_text().clone_color();
1972 let style_color = BorderStyleColor::from_border(border, ¤t_color);
1973 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1974 top: self.build_border_side(style_color.top),
1975 right: self.build_border_side(style_color.right),
1976 bottom: self.build_border_side(style_color.bottom),
1977 left: self.build_border_side(style_color.left),
1978 radius: self.border_radius(),
1979 do_aa: true,
1980 });
1981 let common = builder.common_properties(state, self.border_rect, style);
1982 builder
1983 .wr()
1984 .push_border(&common, self.border_rect, border_widths, details)
1985 }
1986
1987 fn build_border_image(
1989 &self,
1990 builder: &mut DisplayListBuilder,
1991 state: &TraversalState,
1992 border: &Border,
1993 border_widths: SideOffsets2D<f32, LayoutPixel>,
1994 ) -> bool {
1995 let style = self.fragment.style();
1996 let border_style_struct = style.get_border();
1997 let border_image_outset =
1998 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1999 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
2000 let border_image_size = border_image_area.size;
2001 let border_image_widths = resolve_border_image_width(
2002 &border_style_struct.border_image_width,
2003 border_widths,
2004 border_image_size,
2005 );
2006 let border_image_repeat = &border_style_struct.border_image_repeat;
2007 let border_image_fill = border_style_struct.border_image_slice.fill;
2008 let border_image_slice = &border_style_struct.border_image_slice.offsets;
2009 let common = builder.common_properties(state, border_image_area.to_box2d(), style);
2010
2011 let stops = Vec::new();
2012 let mut width = border_image_size.width;
2013 let mut height = border_image_size.height;
2014 let node = self.fragment.base.tag.map(|tag| tag.node);
2015 let source = match builder
2016 .image_resolver
2017 .resolve_image(node, &border.border_image_source)
2018 {
2019 Err(_) => return false,
2020 Ok(ResolvedImage::Image { image, size }) => {
2021 let image_key = match image {
2022 CachedImage::Raster(raster_image) => raster_image.id,
2023 CachedImage::Vector(vector_image) => {
2024 let scale = builder.device_pixel_ratio.get();
2025 let size = Size2D::new(size.width * scale, size.height * scale).to_i32();
2026 node.and_then(|node| {
2027 builder.image_resolver.rasterize_vector_image(
2028 vector_image.id,
2029 size,
2030 node,
2031 vector_image.svg_id,
2032 )
2033 })
2034 .and_then(|rasterized_image| rasterized_image.id)
2035 },
2036 };
2037
2038 let Some(key) = image_key else {
2039 return false;
2040 };
2041
2042 builder.check_if_paintable(
2043 Box2D::from_size(size.cast_unit()),
2044 common.clip_rect,
2045 style.clone_opacity(),
2046 );
2047
2048 builder.mark_is_contentful();
2053
2054 width = size.width;
2055 height = size.height;
2056 let image_rendering = style.clone_image_rendering().to_webrender();
2057 NinePatchBorderSource::Image(key, image_rendering)
2058 },
2059 Ok(ResolvedImage::Gradient(gradient)) => {
2060 match gradient::build(style, gradient, border_image_size, builder) {
2061 WebRenderGradient::Linear(gradient) => {
2062 NinePatchBorderSource::Gradient(gradient)
2063 },
2064 WebRenderGradient::Radial(gradient) => {
2065 NinePatchBorderSource::RadialGradient(gradient)
2066 },
2067 WebRenderGradient::Conic(gradient) => {
2068 NinePatchBorderSource::ConicGradient(gradient)
2069 },
2070 }
2071 },
2072 Ok(ResolvedImage::Color(color)) => {
2073 let color = rgba(style.resolve_color(color));
2076 let gradient = builder.wr().create_gradient(
2077 Point2D::zero(),
2078 Point2D::zero(),
2079 vec![
2080 wr::GradientStop { offset: 0.0, color },
2081 wr::GradientStop { offset: 1.0, color },
2082 ],
2083 wr::ExtendMode::Clamp,
2084 );
2085 NinePatchBorderSource::Gradient(gradient)
2086 },
2087 };
2088
2089 let size = Size2D::new(width as i32, height as i32);
2090
2091 if size.is_empty() || border_image_size.is_empty() {
2094 return true;
2095 }
2096
2097 let details = BorderDetails::NinePatch(NinePatchBorder {
2098 source,
2099 width: size.width,
2100 height: size.height,
2101 slice: resolve_border_image_slice(border_image_slice, size),
2102 fill: border_image_fill,
2103 repeat_horizontal: border_image_repeat.0.to_webrender(),
2104 repeat_vertical: border_image_repeat.1.to_webrender(),
2105 });
2106 builder.wr().push_border(
2107 &common,
2108 border_image_area.to_box2d(),
2109 border_image_widths,
2110 details,
2111 );
2112 builder.wr().push_stops(&stops);
2113 true
2114 }
2115
2116 fn build_outline(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2117 let style = self.fragment.style();
2118 let outline = style.get_outline();
2119 if outline.outline_style.none_or_hidden() {
2120 return;
2121 }
2122 let width = outline.outline_width.0.to_f32_px();
2123 if width == 0.0 {
2124 return;
2125 }
2126 let offset = outline.outline_offset.to_f32_px() + width;
2135 let outline_rect = self.border_rect.inflate(
2136 offset.max(-self.border_rect.width() / 2.0 + width),
2137 offset.max(-self.border_rect.height() / 2.0 + width),
2138 );
2139 let common = builder.common_properties(state, outline_rect, style);
2140 let widths = SideOffsets2D::new_all_same(width);
2141 let border_style = match outline.outline_style {
2142 OutlineStyle::Auto => BorderStyle::Solid,
2145 OutlineStyle::BorderStyle(s) => s,
2146 };
2147 let side = self.build_border_side(BorderStyleColor {
2148 style: border_style,
2149 color: style.resolve_color(&outline.outline_color),
2150 });
2151 let details = wr::BorderDetails::Normal(wr::NormalBorder {
2152 top: side,
2153 right: side,
2154 bottom: side,
2155 left: side,
2156 radius: offset_radii(self.border_radius(), SideOffsets2D::new_all_same(offset)),
2157 do_aa: true,
2158 });
2159 builder
2160 .wr()
2161 .push_border(&common, outline_rect, widths, details)
2162 }
2163
2164 fn build_box_shadow(&self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2165 let style = self.fragment.style();
2166 let box_shadows = &style.get_effects().box_shadow.0;
2167 if box_shadows.is_empty() {
2168 return;
2169 }
2170
2171 for box_shadow in box_shadows.iter().rev() {
2173 let (rect, clip_mode) = if box_shadow.inset {
2174 (*self.padding_rect(), BoxShadowClipMode::Inset)
2175 } else {
2176 (self.border_rect, BoxShadowClipMode::Outset)
2177 };
2178
2179 let offset = LayoutVector2D::new(
2180 box_shadow.base.horizontal.px(),
2181 box_shadow.base.vertical.px(),
2182 );
2183 let spread = box_shadow.spread.px();
2184 let blur = box_shadow.base.blur.px();
2185 let clip_rect = match clip_mode {
2186 BoxShadowClipMode::Inset => rect,
2188 BoxShadowClipMode::Outset => {
2191 let extra_size_from_blur = (blur * 3.0).ceil();
2192 rect.translate(offset)
2193 .inflate(spread, spread)
2194 .inflate(extra_size_from_blur, extra_size_from_blur)
2195 },
2196 };
2197 let border_radius = match clip_mode {
2198 BoxShadowClipMode::Inset => {
2199 offset_radii(self.border_radius(), -self.fragment.border.to_webrender())
2203 },
2204 BoxShadowClipMode::Outset => self.border_radius(),
2205 };
2206 let shadow_radius = offset_radii(
2207 border_radius,
2208 SideOffsets2D::new_all_same(match clip_mode {
2209 BoxShadowClipMode::Inset => -spread,
2210 BoxShadowClipMode::Outset => spread,
2211 }),
2212 );
2213 let common = builder.common_properties(state, clip_rect, style);
2214 builder.wr().push_box_shadow(
2215 &common,
2216 rect,
2217 offset,
2218 rgba(style.resolve_color(&box_shadow.base.color)),
2219 blur,
2220 spread,
2221 border_radius,
2222 shadow_radius,
2223 clip_mode,
2224 );
2225 }
2226 }
2227}
2228
2229fn rgba(color: AbsoluteColor) -> wr::ColorF {
2230 let rgba = color.to_color_space(ColorSpace::Srgb);
2231 wr::ColorF::new(
2232 rgba.components.0.clamp(0.0, 1.0),
2233 rgba.components.1.clamp(0.0, 1.0),
2234 rgba.components.2.clamp(0.0, 1.0),
2235 rgba.alpha.clamp(0.0, 1.0),
2236 )
2237}
2238
2239fn glyphs(
2240 shaped_text_slices: &[Arc<ShapedTextSlice>],
2241 mut baseline_origin: PhysicalPoint<Au>,
2242 justification_adjustment: Au,
2243 include_whitespace: bool,
2244) -> (Vec<GlyphInstance>, Au) {
2245 let mut glyphs = vec![];
2246 let mut largest_advance = Au::zero();
2247
2248 for shaped_text_slice in shaped_text_slices {
2249 for glyph in shaped_text_slice.glyphs() {
2250 if !shaped_text_slice.is_whitespace() || include_whitespace {
2251 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2252 let point = LayoutPoint::new(
2253 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
2254 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
2255 );
2256 let glyph_instance = GlyphInstance {
2257 index: glyph.id(),
2258 point,
2259 };
2260 glyphs.push(glyph_instance);
2261 }
2262
2263 if glyph.char_is_word_separator() {
2264 baseline_origin.x += justification_adjustment;
2265 }
2266
2267 let advance = glyph.advance();
2268 baseline_origin.x += advance;
2269 largest_advance.max_assign(advance);
2270 }
2271 }
2272 (glyphs, largest_advance)
2273}
2274
2275fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
2279 let expand = |radius: &mut f32, offset: f32| {
2280 if offset < 0.0 {
2282 *radius = (*radius + offset).max(0.0);
2283 return;
2284 }
2285
2286 if *radius > 0.0 {
2291 *radius += offset;
2292 }
2293 };
2294 if offsets.left != 0.0 {
2295 expand(&mut radii.top_left.width, offsets.left);
2296 expand(&mut radii.bottom_left.width, offsets.left);
2297 }
2298 if offsets.right != 0.0 {
2299 expand(&mut radii.top_right.width, offsets.right);
2300 expand(&mut radii.bottom_right.width, offsets.right);
2301 }
2302 if offsets.top != 0.0 {
2303 expand(&mut radii.top_left.height, offsets.top);
2304 expand(&mut radii.top_right.height, offsets.top);
2305 }
2306 if offsets.bottom != 0.0 {
2307 expand(&mut radii.bottom_right.height, offsets.bottom);
2308 expand(&mut radii.bottom_left.height, offsets.bottom);
2309 }
2310 radii
2311}
2312
2313fn resolve_border_image_outset(
2315 outset: BorderImageOutset,
2316 border: SideOffsets2D<f32, LayoutPixel>,
2317) -> SideOffsets2D<f32, LayoutPixel> {
2318 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
2319 match outset {
2320 NonNegativeLengthOrNumber::Length(length) => length.px(),
2321 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
2322 }
2323 }
2324
2325 SideOffsets2D::new(
2326 image_outset_for_side(outset.0, border.top),
2327 image_outset_for_side(outset.1, border.right),
2328 image_outset_for_side(outset.2, border.bottom),
2329 image_outset_for_side(outset.3, border.left),
2330 )
2331}
2332
2333fn resolve_border_image_width(
2335 width: &BorderImageWidth,
2336 border: SideOffsets2D<f32, LayoutPixel>,
2337 border_area: Size2D<f32, LayoutPixel>,
2338) -> SideOffsets2D<f32, LayoutPixel> {
2339 fn image_width_for_side(
2340 border_image_width: &BorderImageSideWidth,
2341 border_width: f32,
2342 total_length: f32,
2343 ) -> f32 {
2344 match border_image_width {
2345 BorderImageSideWidth::LengthPercentage(v) => {
2346 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
2347 },
2348 BorderImageSideWidth::Number(x) => border_width * x.0,
2349 BorderImageSideWidth::Auto => border_width,
2350 }
2351 }
2352
2353 SideOffsets2D::new(
2354 image_width_for_side(&width.0, border.top, border_area.height),
2355 image_width_for_side(&width.1, border.right, border_area.width),
2356 image_width_for_side(&width.2, border.bottom, border_area.height),
2357 image_width_for_side(&width.3, border.left, border_area.width),
2358 )
2359}
2360
2361fn resolve_border_image_slice(
2363 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
2364 size: Size2D<i32, UnknownUnit>,
2365) -> SideOffsets2D<i32, DevicePixel> {
2366 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
2367 match value.0 {
2368 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
2369 NumberOrPercentage::Number(n) => n.round() as i32,
2370 }
2371 }
2372
2373 SideOffsets2D::new(
2374 resolve_percentage(border_image_slice.0, size.height),
2375 resolve_percentage(border_image_slice.1, size.width),
2376 resolve_percentage(border_image_slice.2, size.height),
2377 resolve_percentage(border_image_slice.3, size.width),
2378 )
2379}
2380
2381pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
2382 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
2390 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
2391 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
2392 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
2393 if f < 1.0 {
2394 radius.top_left *= f;
2395 radius.top_right *= f;
2396 radius.bottom_right *= f;
2397 radius.bottom_left *= f;
2398 }
2399}
2400
2401pub(super) fn compute_margin_box_radius(
2409 radius: wr::BorderRadius,
2410 layout_rect: LayoutSize,
2411 fragment: &BoxFragment,
2412) -> wr::BorderRadius {
2413 let style = fragment.style();
2414 let margin = style.physical_margin();
2415 let adjust_radius = |radius: f32, margin: f32| -> f32 {
2416 if margin <= 0. || (radius / margin) >= 1. {
2417 (radius + margin).max(0.)
2418 } else {
2419 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
2420 }
2421 };
2422 let compute_margin_radius = |radius: LayoutSize,
2423 layout_rect: LayoutSize,
2424 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
2425 -> LayoutSize {
2426 let zero = LengthPercentage::zero();
2427 let width = margin
2428 .width
2429 .auto_is(|| &zero)
2430 .to_used_value(Au::from_f32_px(layout_rect.width));
2431 let height = margin
2432 .height
2433 .auto_is(|| &zero)
2434 .to_used_value(Au::from_f32_px(layout_rect.height));
2435 LayoutSize::new(
2436 adjust_radius(radius.width, width.to_f32_px()),
2437 adjust_radius(radius.height, height.to_f32_px()),
2438 )
2439 };
2440 wr::BorderRadius {
2441 top_left: compute_margin_radius(
2442 radius.top_left,
2443 layout_rect,
2444 Size2D::new(margin.left, margin.top),
2445 ),
2446 top_right: compute_margin_radius(
2447 radius.top_right,
2448 layout_rect,
2449 Size2D::new(margin.right, margin.top),
2450 ),
2451 bottom_left: compute_margin_radius(
2452 radius.bottom_left,
2453 layout_rect,
2454 Size2D::new(margin.left, margin.bottom),
2455 ),
2456 bottom_right: compute_margin_radius(
2457 radius.bottom_right,
2458 layout_rect,
2459 Size2D::new(margin.right, margin.bottom),
2460 ),
2461 }
2462}
2463
2464impl BoxFragment {
2465 fn border_radius(&self) -> BorderRadius {
2466 let style = self.style();
2467 let border = style.get_border();
2468 if border.border_top_left_radius.0.is_zero() &&
2469 border.border_top_right_radius.0.is_zero() &&
2470 border.border_bottom_right_radius.0.is_zero() &&
2471 border.border_bottom_left_radius.0.is_zero()
2472 {
2473 return BorderRadius::zero();
2474 }
2475
2476 let border_rect = self.border_rect();
2477 let resolve =
2478 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2479 let corner = |corner: &style::values::computed::BorderCornerRadius| {
2480 Size2D::new(
2481 resolve(&corner.0.width.0, border_rect.size.width),
2482 resolve(&corner.0.height.0, border_rect.size.height),
2483 )
2484 };
2485
2486 let mut radius = wr::BorderRadius {
2487 top_left: corner(&border.border_top_left_radius),
2488 top_right: corner(&border.border_top_right_radius),
2489 bottom_right: corner(&border.border_bottom_right_radius),
2490 bottom_left: corner(&border.border_bottom_left_radius),
2491 };
2492
2493 normalize_radii(&border_rect.to_webrender(), &mut radius);
2494 radius
2495 }
2496}
2497
2498impl BaseFragment {
2499 fn visit_fragment(&self, builder: &mut DisplayListBuilder) {
2500 match self.status() {
2501 FragmentStatus::New => {
2502 builder.reflow_statistics.rebuilt_fragment_count += 1;
2503 self.set_status(FragmentStatus::Clean)
2504 },
2505 FragmentStatus::StyleChanged => {
2506 builder.reflow_statistics.restyle_fragment_count += 1;
2507 self.set_status(FragmentStatus::Clean)
2508 },
2509 FragmentStatus::OnlyDescendantsChanged => {
2510 builder.reflow_statistics.only_descendants_changed_count += 1;
2511 self.set_status(FragmentStatus::Clean)
2512 },
2513 FragmentStatus::Clean => {},
2514 }
2515 }
2516}