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