1use std::cell::{OnceCell, RefCell};
6use std::sync::Arc;
7
8use app_units::{AU_PER_PX, Au};
9use base::id::ScrollTreeNodeId;
10use clip::{Clip, ClipId};
11use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
12use fonts::GlyphStore;
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_config::opts::DiagnosticsLogging;
19use servo_config::pref;
20use servo_geometry::MaxRect;
21use servo_url::ServoUrl;
22use style::Zero;
23use style::color::{AbsoluteColor, ColorSpace};
24use style::computed_values::border_image_outset::T as BorderImageOutset;
25use style::computed_values::text_decoration_style::{
26 T as ComputedTextDecorationStyle, T as TextDecorationStyle,
27};
28use style::dom::OpaqueNode;
29use style::properties::ComputedValues;
30use style::properties::longhands::visibility::computed_value::T as Visibility;
31use style::properties::style_structs::Border;
32use style::values::computed::{
33 BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
34 NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
35};
36use style::values::generics::NonNegative;
37use style::values::generics::color::ColorOrAuto;
38use style::values::generics::rect::Rect as StyleRect;
39use style::values::specified::text::TextDecorationLine;
40use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
41use webrender_api::units::{
42 DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize,
43};
44use webrender_api::{
45 self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
46 ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
47 NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding,
48 SpatialId, SpatialTreeItemKey, units,
49};
50use wr::units::LayoutVector2D;
51
52use crate::cell::ArcRefCell;
53use crate::context::{ImageResolver, ResolvedImage};
54pub(crate) use crate::display_list::conversions::ToWebRender;
55use crate::display_list::stacking_context::StackingContextSection;
56use crate::fragment_tree::{
57 BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentStatus, FragmentTree,
58 SpecificLayoutInfo, Tag, TextFragment,
59};
60use crate::geom::{
61 LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
62};
63use crate::replaced::NaturalSizes;
64use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
65
66mod background;
67mod clip;
68mod conversions;
69mod gradient;
70mod hit_test;
71mod paint_timing_handler;
72mod stacking_context;
73
74use background::BackgroundPainter;
75pub(crate) use hit_test::HitTest;
76pub(crate) use paint_timing_handler::PaintTimingHandler;
77pub(crate) use stacking_context::*;
78
79const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
80
81pub(crate) struct DisplayListBuilder<'a> {
82 current_scroll_node_id: ScrollTreeNodeId,
87
88 current_reference_frame_scroll_node_id: ScrollTreeNodeId,
92
93 current_clip_id: ClipId,
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<ArcRefCell<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.display_list {
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 current_scroll_node_id: paint_info.root_reference_frame_id,
201 current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
202 current_clip_id: ClipId::INVALID,
203 webrender_display_list_builder: &mut webrender_display_list_builder,
204 paint_info,
205 inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
206 paint_body_background: true,
207 clip_map: Default::default(),
208 image_resolver,
209 device_pixel_ratio,
210 paint_timing_handler,
211 reflow_statistics,
212 };
213
214 builder.add_all_spatial_nodes();
215
216 for clip in stacking_context_tree.clip_store.0.iter() {
217 builder.add_clip_to_display_list(clip);
218 }
219
220 let pipeline_id = builder.paint_info.pipeline_id;
223 let viewport_size = builder.paint_info.viewport_details.size;
224 let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
225 builder.wr().push_hit_test(
226 viewport_rect,
227 ClipChainId::INVALID,
228 SpatialId::root_reference_frame(pipeline_id),
229 PrimitiveFlags::default(),
230 (0, 0), );
232
233 stacking_context_tree
235 .root_stacking_context
236 .build_canvas_background_display_list(&mut builder, fragment_tree);
237 stacking_context_tree
238 .root_stacking_context
239 .build_display_list(&mut builder);
240 builder.paint_dom_inspector_highlight();
241
242 webrender_display_list_builder.end().1
243 }
244
245 fn wr(&mut self) -> &mut wr::DisplayListBuilder {
246 self.webrender_display_list_builder
247 }
248
249 fn pipeline_id(&mut self) -> wr::PipelineId {
250 self.paint_info.pipeline_id
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 spatial_tree_count = 0;
276 let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
277 let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
278
279 mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
280 mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
281
282 let pipeline_id = self.pipeline_id();
283 let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
284
285 for node in scroll_tree.nodes.iter().skip(2) {
286 let parent_scroll_node_id = node
287 .parent
288 .expect("Should have already added root reference frame");
289 let parent_spatial_node_id = mapping
290 .get(parent_scroll_node_id.index)
291 .expect("Should add spatial nodes to display list in order");
292
293 spatial_tree_count += 1;
296 let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
297
298 mapping.push(match &node.info {
299 SpatialTreeNodeInfo::ReferenceFrame(info) => {
300 let spatial_id = self.wr().push_reference_frame(
301 info.origin,
302 *parent_spatial_node_id,
303 info.transform_style,
304 PropertyBinding::Value(*info.transform.to_transform()),
305 info.kind,
306 spatial_tree_item_key,
307 );
308 self.wr().pop_reference_frame();
309 spatial_id
310 },
311 SpatialTreeNodeInfo::Scroll(info) => {
312 self.wr().define_scroll_frame(
313 *parent_spatial_node_id,
314 info.external_id,
315 info.content_rect,
316 info.clip_rect,
317 LayoutVector2D::zero(), 0, wr::HasScrollLinkedEffect::No,
320 spatial_tree_item_key,
321 )
322 },
323 SpatialTreeNodeInfo::Sticky(info) => {
324 self.wr().define_sticky_frame(
325 *parent_spatial_node_id,
326 info.frame_rect,
327 info.margins,
328 info.vertical_offset_bounds,
329 info.horizontal_offset_bounds,
330 LayoutVector2D::zero(), spatial_tree_item_key,
332 None, )
334 },
335 });
336 }
337
338 scroll_tree.update_mapping(mapping);
339 self.paint_info.scroll_tree = scroll_tree;
340 }
341
342 pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
350 assert_eq!(
351 clip.id.0,
352 self.clip_map.len(),
353 "Clips should be added in order"
354 );
355
356 let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
357 let new_clip_id = if clip.radii.is_zero() {
358 self.wr().define_clip_rect(spatial_id, clip.rect)
359 } else {
360 self.wr().define_clip_rounded_rect(
361 spatial_id,
362 ComplexClipRegion {
363 rect: clip.rect,
364 radii: clip.radii,
365 mode: ClipMode::Clip,
366 },
367 )
368 };
369
370 let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
375 ClipChainId::INVALID => None,
376 parent => Some(parent),
377 };
378 let clip_chain_id = self
379 .wr()
380 .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
381 self.clip_map.push(clip_chain_id);
382 clip_chain_id
383 }
384
385 fn maybe_create_clip(
389 &mut self,
390 radii: wr::BorderRadius,
391 rect: units::LayoutRect,
392 force_clip_creation: bool,
393 ) -> Option<ClipChainId> {
394 if radii.is_zero() && !force_clip_creation {
395 return None;
396 }
397
398 Some(self.add_clip_to_display_list(&Clip {
399 id: ClipId(self.clip_map.len()),
400 radii,
401 rect,
402 parent_scroll_node_id: self.current_scroll_node_id,
403 parent_clip_id: self.current_clip_id,
404 }))
405 }
406
407 fn common_properties(
408 &self,
409 clip_rect: units::LayoutRect,
410 style: &ComputedValues,
411 ) -> wr::CommonItemProperties {
412 wr::CommonItemProperties {
416 clip_rect,
417 spatial_id: self.spatial_id(self.current_scroll_node_id),
418 clip_chain_id: self.clip_chain_id(self.current_clip_id),
419 flags: style.get_webrender_primitive_flags(),
420 }
421 }
422
423 fn paint_dom_inspector_highlight(&mut self) {
425 let Some(highlight) = self
426 .inspector_highlight
427 .take()
428 .and_then(|highlight| highlight.state)
429 else {
430 return;
431 };
432
433 const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
434 r: 0.23,
435 g: 0.7,
436 b: 0.87,
437 a: 0.5,
438 };
439
440 const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
441 r: 0.49,
442 g: 0.3,
443 b: 0.7,
444 a: 0.5,
445 };
446
447 const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
448 r: 0.2,
449 g: 0.2,
450 b: 0.2,
451 a: 0.5,
452 };
453
454 const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
455 r: 1.,
456 g: 0.93,
457 b: 0.,
458 a: 0.5,
459 };
460
461 let content_box = highlight.content_box.to_webrender();
463 let properties = wr::CommonItemProperties {
464 clip_rect: content_box,
465 spatial_id: highlight.spatial_id,
466 clip_chain_id: highlight.clip_chain_id,
467 flags: wr::PrimitiveFlags::default(),
468 };
469
470 self.wr()
471 .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
472
473 if let Some(box_fragment) = highlight.maybe_box_fragment {
475 let mut paint_highlight =
476 |color: webrender_api::ColorF,
477 fragment_relative_bounds: PhysicalRect<Au>,
478 widths: webrender_api::units::LayoutSideOffsets| {
479 if widths.is_zero() {
480 return;
481 }
482
483 let bounds = box_fragment
484 .borrow()
485 .offset_by_containing_block(&fragment_relative_bounds)
486 .to_webrender();
487
488 let border_style = wr::BorderSide {
490 color,
491 style: wr::BorderStyle::Solid,
492 };
493
494 let details = wr::BorderDetails::Normal(wr::NormalBorder {
495 top: border_style,
496 right: border_style,
497 bottom: border_style,
498 left: border_style,
499 radius: webrender_api::BorderRadius::default(),
500 do_aa: true,
501 });
502
503 let common = wr::CommonItemProperties {
504 clip_rect: bounds,
505 spatial_id: highlight.spatial_id,
506 clip_chain_id: highlight.clip_chain_id,
507 flags: wr::PrimitiveFlags::default(),
508 };
509 self.wr().push_border(&common, bounds, widths, details)
510 };
511
512 let box_fragment = box_fragment.borrow();
513 paint_highlight(
514 PADDING_BOX_HIGHLIGHT_COLOR,
515 box_fragment.padding_rect(),
516 box_fragment.padding.to_webrender(),
517 );
518 paint_highlight(
519 BORDER_BOX_HIGHLIGHT_COLOR,
520 box_fragment.border_rect(),
521 box_fragment.border.to_webrender(),
522 );
523 paint_highlight(
524 MARGIN_BOX_HIGHLIGHT_COLOR,
525 box_fragment.margin_rect(),
526 box_fragment.margin.to_webrender(),
527 );
528 }
529 }
530
531 fn check_for_contentful_paint(
532 &mut self,
533 bounds: LayoutRect,
534 clip_rect: LayoutRect,
535 opacity: f32,
536 ) {
537 if opacity <= 0.0 {
541 return;
542 }
543
544 if self
546 .paint_timing_handler
547 .check_bounding_rect(bounds, clip_rect)
548 {
549 self.mark_is_contentful();
550 }
551 }
552
553 fn check_for_lcp_candidate(
554 &mut self,
555 clip_rect: LayoutRect,
556 bounds: LayoutRect,
557 tag: Option<Tag>,
558 url: Option<ServoUrl>,
559 ) {
560 if !pref!(largest_contentful_paint_enabled) {
561 return;
562 }
563
564 let transform = self
565 .paint_info
566 .scroll_tree
567 .cumulative_node_to_root_transform(self.current_scroll_node_id);
568
569 self.paint_timing_handler
570 .update_lcp_candidate(tag, bounds, clip_rect, transform, url);
571 }
572}
573
574impl InspectorHighlight {
575 fn register_fragment_of_highlighted_dom_node(
576 &mut self,
577 fragment: &Fragment,
578 spatial_id: SpatialId,
579 clip_chain_id: ClipChainId,
580 containing_block: &PhysicalRect<Au>,
581 ) {
582 let state = self.state.get_or_insert(HighlightTraversalState {
583 content_box: Rect::zero(),
584 spatial_id,
585 clip_chain_id,
586 maybe_box_fragment: None,
587 });
588
589 if spatial_id != state.spatial_id {
592 return;
593 }
594
595 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
596 debug_assert_eq!(
597 clip_chain_id, state.clip_chain_id,
598 "Fragments of the same node must either have no clip chain or the same one"
599 );
600 }
601
602 let Some(fragment_relative_rect) = fragment.base().map(|base| base.rect) else {
603 return;
604 };
605 state.maybe_box_fragment = match fragment {
606 Fragment::Box(fragment) | Fragment::Float(fragment) => Some(fragment.clone()),
607 _ => None,
608 };
609
610 state.content_box = state
611 .content_box
612 .union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
613 }
614}
615
616impl Fragment {
617 pub(crate) fn build_display_list(
618 &self,
619 builder: &mut DisplayListBuilder,
620 containing_block: &PhysicalRect<Au>,
621 section: StackingContextSection,
622 is_hit_test_for_scrollable_overflow: bool,
623 is_collapsed_table_borders: bool,
624 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
625 ) {
626 if let Some(mut base) = self.base_mut() {
627 match base.status {
628 FragmentStatus::New => {
629 builder.reflow_statistics.rebuilt_fragment_count += 1;
630 base.status = FragmentStatus::Clean;
631 },
632 FragmentStatus::StyleChanged => {
633 builder.reflow_statistics.restyle_fragment_count += 1;
634 base.status = FragmentStatus::Clean;
635 },
636 FragmentStatus::Clean => {},
637 }
638 }
639
640 let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
641 let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
642 if let Some(inspector_highlight) = &mut builder.inspector_highlight {
643 if self.tag() == Some(inspector_highlight.tag) {
644 inspector_highlight.register_fragment_of_highlighted_dom_node(
645 self,
646 spatial_id,
647 clip_chain_id,
648 containing_block,
649 );
650 }
651 }
652
653 match self {
654 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
655 let box_fragment = &*box_fragment.borrow();
656 match box_fragment.style().get_inherited_box().visibility {
657 Visibility::Visible => BuilderForBoxFragment::new(
658 box_fragment,
659 containing_block,
660 is_hit_test_for_scrollable_overflow,
661 is_collapsed_table_borders,
662 )
663 .build(builder, section),
664 Visibility::Hidden => (),
665 Visibility::Collapse => (),
666 }
667 },
668 Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Positioning(_) => {},
669 Fragment::Image(image) => {
670 let image = image.borrow();
671 let style = image.base.style();
672 match style.get_inherited_box().visibility {
673 Visibility::Visible => {
674 let image_rendering =
675 style.get_inherited_box().image_rendering.to_webrender();
676 let rect = image
677 .base
678 .rect
679 .translate(containing_block.origin.to_vector())
680 .to_webrender();
681 let clip = image
682 .clip
683 .translate(containing_block.origin.to_vector())
684 .to_webrender();
685 let common = builder.common_properties(clip, &style);
686
687 if let Some(image_key) = image.image_key {
688 builder.wr().push_image(
689 &common,
690 rect,
691 image_rendering,
692 wr::AlphaType::PremultipliedAlpha,
693 image_key,
694 wr::ColorF::WHITE,
695 );
696
697 builder.check_for_contentful_paint(
701 rect,
702 common.clip_rect,
703 style.clone_opacity(),
704 );
705 }
706
707 if image.showing_broken_image_icon {
708 self.build_display_list_for_broken_image_border(
709 builder,
710 containing_block,
711 &common,
712 );
713 }
714
715 builder.check_for_lcp_candidate(
716 common.clip_rect,
717 rect,
718 image.base.tag,
719 image.url.clone(),
720 );
721 },
722 Visibility::Hidden => (),
723 Visibility::Collapse => (),
724 }
725 },
726 Fragment::IFrame(iframe) => {
727 let iframe = iframe.borrow();
728 let style = iframe.base.style();
729 match style.get_inherited_box().visibility {
730 Visibility::Visible => {
731 let rect = iframe
736 .base
737 .rect
738 .translate(containing_block.origin.to_vector());
739
740 let common = builder.common_properties(rect.to_webrender(), &style);
741 builder.wr().push_iframe(
742 rect.to_webrender(),
743 common.clip_rect,
744 &wr::SpaceAndClipInfo {
745 spatial_id: common.spatial_id,
746 clip_chain_id: common.clip_chain_id,
747 },
748 iframe.pipeline_id.into(),
749 true,
750 );
751 },
752 Visibility::Hidden => (),
753 Visibility::Collapse => (),
754 }
755 },
756 Fragment::Text(text) => {
757 let text = &*text.borrow();
758 match text.base.style().get_inherited_box().visibility {
759 Visibility::Visible => self.build_display_list_for_text_fragment(
760 text,
761 builder,
762 containing_block,
763 text_decorations,
764 ),
765 Visibility::Hidden => (),
766 Visibility::Collapse => (),
767 }
768 },
769 }
770 }
771
772 fn build_display_list_for_text_fragment(
773 &self,
774 fragment: &TextFragment,
775 builder: &mut DisplayListBuilder,
776 containing_block: &PhysicalRect<Au>,
777 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
778 ) {
779 let rect = fragment
782 .base
783 .rect
784 .translate(containing_block.origin.to_vector());
785 let mut baseline_origin = rect.origin;
786 baseline_origin.y += fragment.font_metrics.ascent;
787 let include_whitespace =
788 fragment.offsets.is_some() || text_decorations.iter().any(|item| !item.line.is_empty());
789
790 let (glyphs, largest_advance) = glyphs(
791 &fragment.glyphs,
792 baseline_origin,
793 fragment.justification_adjustment,
794 include_whitespace,
795 );
796
797 if glyphs.is_empty() && !fragment.is_empty_for_text_cursor {
798 return;
799 }
800
801 let parent_style = fragment.base.style();
802 let color = parent_style.clone_color();
803 let font_metrics = &fragment.font_metrics;
804 let dppx = builder.device_pixel_ratio.get();
805
806 let glyph_bounds = rect
813 .inflate(largest_advance.scale_by(2.0), Au::zero())
814 .to_webrender();
815 let common = builder.common_properties(glyph_bounds, &parent_style);
816
817 let shadows = &parent_style.get_inherited_text().text_shadow;
820 for shadow in shadows.0.iter().rev() {
821 builder.wr().push_shadow(
822 &wr::SpaceAndClipInfo {
823 spatial_id: common.spatial_id,
824 clip_chain_id: common.clip_chain_id,
825 },
826 wr::Shadow {
827 offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
828 color: rgba(shadow.color.resolve_to_absolute(&color)),
829 blur_radius: shadow.blur.px(),
830 },
831 true, );
833 }
834
835 for text_decoration in text_decorations.iter() {
836 if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
837 let mut rect = rect;
838 rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
839 rect.size.height =
840 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
841
842 self.build_display_list_for_text_decoration(
843 &parent_style,
844 builder,
845 &rect,
846 text_decoration,
847 TextDecorationLine::UNDERLINE,
848 );
849 }
850 }
851
852 for text_decoration in text_decorations.iter() {
853 if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
854 let mut rect = rect;
855 rect.size.height =
856 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
857 self.build_display_list_for_text_decoration(
858 &parent_style,
859 builder,
860 &rect,
861 text_decoration,
862 TextDecorationLine::OVERLINE,
863 );
864 }
865 }
866
867 self.build_display_list_for_text_selection(
868 fragment,
869 builder,
870 containing_block,
871 fragment.base.rect.min_x(),
872 fragment.justification_adjustment,
873 );
874
875 builder.wr().push_text(
876 &common,
877 glyph_bounds,
878 &glyphs,
879 fragment.font_key,
880 rgba(color),
881 None,
882 );
883
884 builder.check_for_contentful_paint(
888 glyph_bounds,
889 common.clip_rect,
890 parent_style.clone_opacity(),
891 );
892
893 for text_decoration in text_decorations.iter() {
894 if text_decoration
895 .line
896 .contains(TextDecorationLine::LINE_THROUGH)
897 {
898 let mut rect = rect;
899 rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
900 rect.size.height =
901 Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
902 self.build_display_list_for_text_decoration(
903 &parent_style,
904 builder,
905 &rect,
906 text_decoration,
907 TextDecorationLine::LINE_THROUGH,
908 );
909 }
910 }
911
912 if !shadows.0.is_empty() {
913 builder.wr().pop_all_shadows();
914 }
915 }
916
917 fn build_display_list_for_text_decoration(
918 &self,
919 parent_style: &ServoArc<ComputedValues>,
920 builder: &mut DisplayListBuilder,
921 rect: &PhysicalRect<Au>,
922 text_decoration: &FragmentTextDecoration,
923 line: TextDecorationLine,
924 ) {
925 if text_decoration.style == ComputedTextDecorationStyle::MozNone {
926 return;
927 }
928
929 let mut rect = rect.to_webrender();
930 let line_thickness = rect.height().ceil();
931
932 if text_decoration.style == ComputedTextDecorationStyle::Wavy {
933 rect = rect.inflate(0.0, line_thickness * 1.0);
934 }
935
936 let common_properties = builder.common_properties(rect, parent_style);
937 builder.wr().push_line(
938 &common_properties,
939 &rect,
940 line_thickness,
941 wr::LineOrientation::Horizontal,
942 &rgba(text_decoration.color),
943 text_decoration.style.to_webrender(),
944 );
945
946 if text_decoration.style == TextDecorationStyle::Double {
947 let half_height = (rect.height() / 2.0).floor().max(1.0);
948 let y_offset = match line {
949 TextDecorationLine::OVERLINE => -rect.height() - half_height,
950 _ => rect.height() + half_height,
951 };
952 let rect = rect.translate(Vector2D::new(0.0, y_offset));
953 let common_properties = builder.common_properties(rect, parent_style);
954 builder.wr().push_line(
955 &common_properties,
956 &rect,
957 line_thickness,
958 wr::LineOrientation::Horizontal,
959 &rgba(text_decoration.color),
960 text_decoration.style.to_webrender(),
961 );
962 }
963 }
964
965 fn build_display_list_for_broken_image_border(
966 &self,
967 builder: &mut DisplayListBuilder,
968 containing_block: &PhysicalRect<Au>,
969 common: &CommonItemProperties,
970 ) {
971 let border_side = BorderSide {
972 color: ColorF::BLACK,
973 style: wr::BorderStyle::Inset,
974 };
975 builder.wr().push_border(
976 common,
977 containing_block.to_webrender(),
978 LayoutSideOffsets::new_all_same(1.0),
979 BorderDetails::Normal(NormalBorder {
980 left: border_side,
981 right: border_side,
982 top: border_side,
983 bottom: border_side,
984 radius: BorderRadius::zero(),
985 do_aa: true,
986 }),
987 );
988 }
989
990 fn build_display_list_for_text_selection(
993 &self,
994 fragment: &TextFragment,
995 builder: &mut DisplayListBuilder<'_>,
996 containing_block_rect: &PhysicalRect<Au>,
997 fragment_x_offset: Au,
998 justification_adjustment: Au,
999 ) {
1000 let Some(offsets) = fragment.offsets.as_ref() else {
1001 return;
1002 };
1003
1004 let shared_selection = offsets.shared_selection.borrow();
1005 if !shared_selection.enabled {
1006 return;
1007 }
1008
1009 if offsets.character_range.start > shared_selection.character_range.end ||
1010 offsets.character_range.end < shared_selection.character_range.start
1011 {
1012 return;
1013 }
1014
1015 if fragment.is_empty_for_text_cursor &&
1020 !offsets
1021 .character_range
1022 .contains(&shared_selection.character_range.start)
1023 {
1024 return;
1025 }
1026
1027 let mut current_character_index = offsets.character_range.start;
1028 let mut current_advance = Au::zero();
1029 let mut start_advance = None;
1030 let mut end_advance = None;
1031 for glyph_store in fragment.glyphs.iter() {
1032 let glyph_store_character_count = glyph_store.total_characters();
1033 if current_character_index + glyph_store_character_count <
1034 shared_selection.character_range.start
1035 {
1036 current_advance += glyph_store.total_advance() +
1037 (justification_adjustment * glyph_store.total_word_separators() as i32);
1038 current_character_index += glyph_store_character_count;
1039 continue;
1040 }
1041
1042 if current_character_index >= shared_selection.character_range.end {
1043 break;
1044 }
1045
1046 for glyph in glyph_store.glyphs() {
1047 if current_character_index >= shared_selection.character_range.start {
1048 start_advance = start_advance.or(Some(current_advance));
1049 }
1050
1051 current_character_index += glyph.character_count();
1052 current_advance += glyph.advance();
1053 if glyph.char_is_word_separator() {
1054 current_advance += justification_adjustment;
1055 }
1056
1057 if current_character_index <= shared_selection.character_range.end {
1058 end_advance = Some(current_advance);
1059 }
1060 }
1061 }
1062
1063 let start_x = start_advance.unwrap_or(current_advance);
1064 let end_x = end_advance.unwrap_or(current_advance);
1065
1066 let parent_style = fragment.base.style();
1067 if !shared_selection.range.is_empty() {
1068 let selection_rect = Rect::new(
1069 containing_block_rect.origin +
1070 Vector2D::new(fragment_x_offset + start_x, Au::zero()),
1071 Size2D::new(end_x - start_x, containing_block_rect.height()),
1072 )
1073 .to_webrender();
1074
1075 if let Some(selection_color) = fragment
1076 .selected_style
1077 .borrow()
1078 .clone_background_color()
1079 .as_absolute()
1080 {
1081 let selection_common = builder.common_properties(selection_rect, &parent_style);
1082 builder
1083 .wr()
1084 .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1085 }
1086 return;
1087 }
1088
1089 let insertion_point_rect = Rect::new(
1090 containing_block_rect.origin + Vector2D::new(start_x + fragment_x_offset, Au::zero()),
1091 Size2D::new(
1092 INSERTION_POINT_LOGICAL_WIDTH,
1093 containing_block_rect.height(),
1094 ),
1095 )
1096 .to_webrender();
1097
1098 let color = parent_style.clone_color();
1099 let caret_color = match parent_style.clone_caret_color().0 {
1100 ColorOrAuto::Color(caret_color) => caret_color.resolve_to_absolute(&color),
1101 ColorOrAuto::Auto => color,
1102 };
1103 let insertion_point_common = builder.common_properties(insertion_point_rect, &parent_style);
1104 builder.wr().push_rect(
1105 &insertion_point_common,
1106 insertion_point_rect,
1107 rgba(caret_color),
1108 );
1109 }
1110}
1111
1112struct BuilderForBoxFragment<'a> {
1113 fragment: &'a BoxFragment,
1114 containing_block: &'a PhysicalRect<Au>,
1115 border_rect: units::LayoutRect,
1116 margin_rect: OnceCell<units::LayoutRect>,
1117 padding_rect: OnceCell<units::LayoutRect>,
1118 content_rect: OnceCell<units::LayoutRect>,
1119 border_radius: wr::BorderRadius,
1120 border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1121 padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1122 content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1123 is_hit_test_for_scrollable_overflow: bool,
1124 is_collapsed_table_borders: bool,
1125}
1126
1127impl<'a> BuilderForBoxFragment<'a> {
1128 fn new(
1129 fragment: &'a BoxFragment,
1130 containing_block: &'a PhysicalRect<Au>,
1131 is_hit_test_for_scrollable_overflow: bool,
1132 is_collapsed_table_borders: bool,
1133 ) -> Self {
1134 let border_rect = fragment
1135 .border_rect()
1136 .translate(containing_block.origin.to_vector());
1137 Self {
1138 fragment,
1139 containing_block,
1140 border_rect: border_rect.to_webrender(),
1141 border_radius: fragment.border_radius(),
1142 margin_rect: OnceCell::new(),
1143 padding_rect: OnceCell::new(),
1144 content_rect: OnceCell::new(),
1145 border_edge_clip_chain_id: RefCell::new(None),
1146 padding_edge_clip_chain_id: RefCell::new(None),
1147 content_edge_clip_chain_id: RefCell::new(None),
1148 is_hit_test_for_scrollable_overflow,
1149 is_collapsed_table_borders,
1150 }
1151 }
1152
1153 fn content_rect(&self) -> &units::LayoutRect {
1154 self.content_rect.get_or_init(|| {
1155 self.fragment
1156 .content_rect()
1157 .translate(self.containing_block.origin.to_vector())
1158 .to_webrender()
1159 })
1160 }
1161
1162 fn padding_rect(&self) -> &units::LayoutRect {
1163 self.padding_rect.get_or_init(|| {
1164 self.fragment
1165 .padding_rect()
1166 .translate(self.containing_block.origin.to_vector())
1167 .to_webrender()
1168 })
1169 }
1170
1171 fn margin_rect(&self) -> &units::LayoutRect {
1172 self.margin_rect.get_or_init(|| {
1173 self.fragment
1174 .margin_rect()
1175 .translate(self.containing_block.origin.to_vector())
1176 .to_webrender()
1177 })
1178 }
1179
1180 fn border_edge_clip(
1181 &self,
1182 builder: &mut DisplayListBuilder,
1183 force_clip_creation: bool,
1184 ) -> Option<ClipChainId> {
1185 if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1186 return Some(clip);
1187 }
1188
1189 let maybe_clip =
1190 builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation);
1191 *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1192 maybe_clip
1193 }
1194
1195 fn padding_edge_clip(
1196 &self,
1197 builder: &mut DisplayListBuilder,
1198 force_clip_creation: bool,
1199 ) -> Option<ClipChainId> {
1200 if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1201 return Some(clip);
1202 }
1203
1204 let radii = offset_radii(self.border_radius, -self.fragment.border.to_webrender());
1205 let maybe_clip =
1206 builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation);
1207 *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1208 maybe_clip
1209 }
1210
1211 fn content_edge_clip(
1212 &self,
1213 builder: &mut DisplayListBuilder,
1214 force_clip_creation: bool,
1215 ) -> Option<ClipChainId> {
1216 if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1217 return Some(clip);
1218 }
1219
1220 let radii = offset_radii(
1221 self.border_radius,
1222 -(self.fragment.border + self.fragment.padding).to_webrender(),
1223 );
1224 let maybe_clip =
1225 builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation);
1226 *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1227 maybe_clip
1228 }
1229
1230 fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
1231 if self.is_hit_test_for_scrollable_overflow &&
1232 self.fragment.style().get_inherited_ui().pointer_events !=
1233 style::computed_values::pointer_events::T::None
1234 {
1235 self.build_hit_test(
1236 builder,
1237 self.fragment
1238 .scrollable_overflow()
1239 .translate(self.containing_block.origin.to_vector())
1240 .to_webrender(),
1241 );
1242 return;
1243 }
1244 if self.is_collapsed_table_borders {
1245 self.build_collapsed_table_borders(builder);
1246 return;
1247 }
1248
1249 if section == StackingContextSection::Outline {
1250 self.build_outline(builder);
1251 return;
1252 }
1253
1254 if self
1255 .fragment
1256 .base
1257 .flags
1258 .contains(FragmentFlags::DO_NOT_PAINT)
1259 {
1260 return;
1261 }
1262
1263 self.build_background(builder);
1264 self.build_box_shadow(builder);
1265 self.build_border(builder);
1266 }
1267
1268 fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
1269 let external_scroll_node_id = builder
1270 .paint_info
1271 .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
1272
1273 let mut common = builder.common_properties(rect, &self.fragment.style());
1274 if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
1275 common.clip_chain_id = clip_chain_id;
1276 }
1277 builder.wr().push_hit_test(
1278 common.clip_rect,
1279 common.clip_chain_id,
1280 common.spatial_id,
1281 common.flags,
1282 (external_scroll_node_id.0, 0), );
1284 }
1285
1286 fn build_background_for_painter(
1287 &mut self,
1288 builder: &mut DisplayListBuilder,
1289 painter: &BackgroundPainter,
1290 ) {
1291 let b = painter.style.get_background();
1292 let background_color = painter.style.resolve_color(&b.background_color);
1293 if background_color.alpha > 0.0 {
1294 let layer_index = b.background_image.0.len() - 1;
1298 let bounds = painter.painting_area(self, builder, layer_index);
1299 let common = painter.common_properties(self, builder, layer_index, bounds);
1300 builder
1301 .wr()
1302 .push_rect(&common, bounds, rgba(background_color))
1303 }
1304
1305 self.build_background_image(builder, painter);
1306 }
1307
1308 fn build_background(&mut self, builder: &mut DisplayListBuilder) {
1309 let flags = self.fragment.base.flags;
1310
1311 if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1314 return;
1315 }
1316 if !builder.paint_body_background &&
1318 flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1319 {
1320 return;
1321 }
1322
1323 if let BackgroundMode::None = self.fragment.background_mode {
1325 return;
1326 }
1327
1328 if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1332 for extra_background in extra_backgrounds {
1333 let positioning_area = extra_background.rect;
1334 let painter = BackgroundPainter {
1335 style: &extra_background.style.borrow_mut(),
1336 painting_area_override: None,
1337 positioning_area_override: Some(
1338 positioning_area
1339 .translate(self.containing_block.origin.to_vector())
1340 .to_webrender(),
1341 ),
1342 };
1343 self.build_background_for_painter(builder, &painter);
1344 }
1345 }
1346
1347 let painter = BackgroundPainter {
1348 style: &self.fragment.style(),
1349 painting_area_override: None,
1350 positioning_area_override: None,
1351 };
1352 self.build_background_for_painter(builder, &painter);
1353 }
1354
1355 fn build_background_image(
1356 &mut self,
1357 builder: &mut DisplayListBuilder,
1358 painter: &BackgroundPainter,
1359 ) {
1360 let style = painter.style;
1361 let b = style.get_background();
1362 let node = self.fragment.base.tag.map(|tag| tag.node);
1363 for (index, image) in b.background_image.0.iter().enumerate().rev() {
1365 match builder.image_resolver.resolve_image(node, image) {
1366 Err(_) => {},
1367 Ok(ResolvedImage::Gradient(gradient)) => {
1368 let intrinsic = NaturalSizes::empty();
1369 let Some(layer) =
1370 &background::layout_layer(self, painter, builder, index, intrinsic)
1371 else {
1372 continue;
1373 };
1374
1375 match gradient::build(style, gradient, layer.tile_size, builder) {
1376 WebRenderGradient::Linear(linear_gradient) => builder.wr().push_gradient(
1377 &layer.common,
1378 layer.bounds,
1379 linear_gradient,
1380 layer.tile_size,
1381 layer.tile_spacing,
1382 ),
1383 WebRenderGradient::Radial(radial_gradient) => {
1384 builder.wr().push_radial_gradient(
1385 &layer.common,
1386 layer.bounds,
1387 radial_gradient,
1388 layer.tile_size,
1389 layer.tile_spacing,
1390 )
1391 },
1392 WebRenderGradient::Conic(conic_gradient) => {
1393 builder.wr().push_conic_gradient(
1394 &layer.common,
1395 layer.bounds,
1396 conic_gradient,
1397 layer.tile_size,
1398 layer.tile_spacing,
1399 )
1400 },
1401 }
1402 },
1403 Ok(ResolvedImage::Image { image, size }) => {
1404 let dppx = 1.0;
1406 let intrinsic =
1407 NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1408 let layer = background::layout_layer(self, painter, builder, index, intrinsic);
1409 let image_wr_key = match image {
1410 CachedImage::Raster(raster_image) => raster_image.id,
1411 CachedImage::Vector(vector_image) => {
1412 let scale = builder.device_pixel_ratio.get();
1413 let default_size: DeviceIntSize =
1414 Size2D::new(size.width * scale, size.height * scale).to_i32();
1415 let layer_size = layer.as_ref().map(|layer| {
1416 Size2D::new(
1417 layer.tile_size.width * scale,
1418 layer.tile_size.height * scale,
1419 )
1420 .to_i32()
1421 });
1422
1423 node.and_then(|node| {
1424 let size = layer_size.unwrap_or(default_size);
1425 builder.image_resolver.rasterize_vector_image(
1426 vector_image.id,
1427 size,
1428 node,
1429 vector_image.svg_id,
1430 )
1431 })
1432 .and_then(|rasterized_image| rasterized_image.id)
1433 },
1434 };
1435
1436 let Some(image_key) = image_wr_key else {
1437 continue;
1438 };
1439
1440 if let Some(layer) = layer {
1441 if layer.repeat {
1442 builder.wr().push_repeating_image(
1443 &layer.common,
1444 layer.bounds,
1445 layer.tile_size,
1446 layer.tile_spacing,
1447 style.clone_image_rendering().to_webrender(),
1448 wr::AlphaType::PremultipliedAlpha,
1449 image_key,
1450 wr::ColorF::WHITE,
1451 )
1452 } else {
1453 builder.wr().push_image(
1454 &layer.common,
1455 layer.bounds,
1456 style.clone_image_rendering().to_webrender(),
1457 wr::AlphaType::PremultipliedAlpha,
1458 image_key,
1459 wr::ColorF::WHITE,
1460 )
1461 }
1462
1463 builder.check_for_contentful_paint(
1468 layer.bounds,
1469 layer.common.clip_rect,
1470 style.clone_opacity(),
1471 );
1472
1473 builder.check_for_lcp_candidate(
1474 layer.common.clip_rect,
1475 layer.bounds,
1476 self.fragment.base.tag,
1477 None,
1478 );
1479 }
1480 },
1481 }
1482 }
1483 }
1484
1485 fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1486 wr::BorderSide {
1487 color: rgba(style_color.color),
1488 style: match style_color.style {
1489 BorderStyle::None => wr::BorderStyle::None,
1490 BorderStyle::Solid => wr::BorderStyle::Solid,
1491 BorderStyle::Double => wr::BorderStyle::Double,
1492 BorderStyle::Dotted => wr::BorderStyle::Dotted,
1493 BorderStyle::Dashed => wr::BorderStyle::Dashed,
1494 BorderStyle::Hidden => wr::BorderStyle::Hidden,
1495 BorderStyle::Groove => wr::BorderStyle::Groove,
1496 BorderStyle::Ridge => wr::BorderStyle::Ridge,
1497 BorderStyle::Inset => wr::BorderStyle::Inset,
1498 BorderStyle::Outset => wr::BorderStyle::Outset,
1499 },
1500 }
1501 }
1502
1503 fn build_collapsed_table_borders(&mut self, builder: &mut DisplayListBuilder) {
1504 let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1505 self.fragment.specific_layout_info()
1506 else {
1507 return;
1508 };
1509 let mut common =
1510 builder.common_properties(units::LayoutRect::default(), &self.fragment.style());
1511 let radius = wr::BorderRadius::default();
1512 let mut column_sum = Au::zero();
1513 for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1514 let mut row_sum = Au::zero();
1515 for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1516 let left_border = &table_info.collapsed_borders.x[x][y];
1517 let right_border = &table_info.collapsed_borders.x[x + 1][y];
1518 let top_border = &table_info.collapsed_borders.y[y][x];
1519 let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1520 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1521 left: self.build_border_side(left_border.style_color.clone()),
1522 right: self.build_border_side(right_border.style_color.clone()),
1523 top: self.build_border_side(top_border.style_color.clone()),
1524 bottom: self.build_border_side(bottom_border.style_color.clone()),
1525 radius,
1526 do_aa: true,
1527 });
1528 let mut border_widths = PhysicalSides::new(
1529 top_border.width,
1530 right_border.width,
1531 bottom_border.width,
1532 left_border.width,
1533 );
1534 let left_adjustment = if x == 0 {
1535 -border_widths.left / 2
1536 } else {
1537 std::mem::take(&mut border_widths.left) / 2
1538 };
1539 let top_adjustment = if y == 0 {
1540 -border_widths.top / 2
1541 } else {
1542 std::mem::take(&mut border_widths.top) / 2
1543 };
1544 let origin =
1545 PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1546 let size = PhysicalSize::new(
1547 *column_size - left_adjustment + border_widths.right / 2,
1548 *row_size - top_adjustment + border_widths.bottom / 2,
1549 );
1550 let border_rect = PhysicalRect::new(origin, size)
1551 .translate(self.fragment.content_rect().origin.to_vector())
1552 .translate(self.containing_block.origin.to_vector())
1553 .to_webrender();
1554 common.clip_rect = border_rect;
1555 builder.wr().push_border(
1556 &common,
1557 border_rect,
1558 border_widths.to_webrender(),
1559 details,
1560 );
1561 row_sum += *row_size;
1562 }
1563 column_sum += *column_size;
1564 }
1565 }
1566
1567 fn build_border(&mut self, builder: &mut DisplayListBuilder) {
1568 if self.fragment.has_collapsed_borders() {
1569 return;
1572 }
1573
1574 let style = self.fragment.style();
1575 let border = style.get_border();
1576 let border_widths = self.fragment.border.to_webrender();
1577
1578 if border_widths == SideOffsets2D::zero() {
1579 return;
1580 }
1581
1582 let common = builder.common_properties(self.border_rect, &style);
1584 if self.build_border_image(builder, &common, border, border_widths) {
1585 return;
1586 }
1587
1588 let current_color = style.get_inherited_text().clone_color();
1589 let style_color = BorderStyleColor::from_border(border, ¤t_color);
1590 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1591 top: self.build_border_side(style_color.top),
1592 right: self.build_border_side(style_color.right),
1593 bottom: self.build_border_side(style_color.bottom),
1594 left: self.build_border_side(style_color.left),
1595 radius: self.border_radius,
1596 do_aa: true,
1597 });
1598 builder
1599 .wr()
1600 .push_border(&common, self.border_rect, border_widths, details)
1601 }
1602
1603 fn build_border_image(
1605 &self,
1606 builder: &mut DisplayListBuilder,
1607 common: &CommonItemProperties,
1608 border: &Border,
1609 border_widths: SideOffsets2D<f32, LayoutPixel>,
1610 ) -> bool {
1611 let style = self.fragment.style();
1612 let border_style_struct = style.get_border();
1613 let border_image_outset =
1614 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1615 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1616 let border_image_size = border_image_area.size;
1617 let border_image_widths = resolve_border_image_width(
1618 &border_style_struct.border_image_width,
1619 border_widths,
1620 border_image_size,
1621 );
1622 let border_image_repeat = &border_style_struct.border_image_repeat;
1623 let border_image_fill = border_style_struct.border_image_slice.fill;
1624 let border_image_slice = &border_style_struct.border_image_slice.offsets;
1625
1626 let stops = Vec::new();
1627 let mut width = border_image_size.width;
1628 let mut height = border_image_size.height;
1629 let node = self.fragment.base.tag.map(|tag| tag.node);
1630 let source = match builder
1631 .image_resolver
1632 .resolve_image(node, &border.border_image_source)
1633 {
1634 Err(_) => return false,
1635 Ok(ResolvedImage::Image { image, size }) => {
1636 let Some(image) = image.as_raster_image() else {
1637 return false;
1638 };
1639
1640 let Some(key) = image.id else {
1641 return false;
1642 };
1643
1644 builder.check_for_contentful_paint(
1649 Box2D::from_size(size.cast_unit()),
1650 common.clip_rect,
1651 style.clone_opacity(),
1652 );
1653 width = size.width;
1654 height = size.height;
1655 let image_rendering = style.clone_image_rendering().to_webrender();
1656 NinePatchBorderSource::Image(key, image_rendering)
1657 },
1658 Ok(ResolvedImage::Gradient(gradient)) => {
1659 match gradient::build(&style, gradient, border_image_size, builder) {
1660 WebRenderGradient::Linear(gradient) => {
1661 NinePatchBorderSource::Gradient(gradient)
1662 },
1663 WebRenderGradient::Radial(gradient) => {
1664 NinePatchBorderSource::RadialGradient(gradient)
1665 },
1666 WebRenderGradient::Conic(gradient) => {
1667 NinePatchBorderSource::ConicGradient(gradient)
1668 },
1669 }
1670 },
1671 };
1672
1673 let size = Size2D::new(width as i32, height as i32);
1674
1675 if size.is_empty() || border_image_size.is_empty() {
1678 return true;
1679 }
1680
1681 let details = BorderDetails::NinePatch(NinePatchBorder {
1682 source,
1683 width: size.width,
1684 height: size.height,
1685 slice: resolve_border_image_slice(border_image_slice, size),
1686 fill: border_image_fill,
1687 repeat_horizontal: border_image_repeat.0.to_webrender(),
1688 repeat_vertical: border_image_repeat.1.to_webrender(),
1689 });
1690 builder.wr().push_border(
1691 common,
1692 border_image_area.to_box2d(),
1693 border_image_widths,
1694 details,
1695 );
1696 builder.wr().push_stops(&stops);
1697 true
1698 }
1699
1700 fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
1701 let style = self.fragment.style();
1702 let outline = style.get_outline();
1703 if outline.outline_style.none_or_hidden() {
1704 return;
1705 }
1706 let width = outline.outline_width.0.to_f32_px();
1707 if width == 0.0 {
1708 return;
1709 }
1710 let offset = outline.outline_offset.to_f32_px() + width;
1719 let outline_rect = self.border_rect.inflate(
1720 offset.max(-self.border_rect.width() / 2.0 + width),
1721 offset.max(-self.border_rect.height() / 2.0 + width),
1722 );
1723 let common = builder.common_properties(outline_rect, &style);
1724 let widths = SideOffsets2D::new_all_same(width);
1725 let border_style = match outline.outline_style {
1726 OutlineStyle::Auto => BorderStyle::Solid,
1729 OutlineStyle::BorderStyle(s) => s,
1730 };
1731 let side = self.build_border_side(BorderStyleColor {
1732 style: border_style,
1733 color: style.resolve_color(&outline.outline_color),
1734 });
1735 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1736 top: side,
1737 right: side,
1738 bottom: side,
1739 left: side,
1740 radius: offset_radii(self.border_radius, SideOffsets2D::new_all_same(offset)),
1741 do_aa: true,
1742 });
1743 builder
1744 .wr()
1745 .push_border(&common, outline_rect, widths, details)
1746 }
1747
1748 fn build_box_shadow(&self, builder: &mut DisplayListBuilder<'_>) {
1749 let style = self.fragment.style();
1750 let box_shadows = &style.get_effects().box_shadow.0;
1751 if box_shadows.is_empty() {
1752 return;
1753 }
1754
1755 let common = builder.common_properties(MaxRect::max_rect(), &style);
1757 for box_shadow in box_shadows.iter().rev() {
1758 let (rect, clip_mode) = if box_shadow.inset {
1759 (*self.padding_rect(), BoxShadowClipMode::Inset)
1760 } else {
1761 (self.border_rect, BoxShadowClipMode::Outset)
1762 };
1763
1764 builder.wr().push_box_shadow(
1765 &common,
1766 rect,
1767 LayoutVector2D::new(
1768 box_shadow.base.horizontal.px(),
1769 box_shadow.base.vertical.px(),
1770 ),
1771 rgba(style.resolve_color(&box_shadow.base.color)),
1772 box_shadow.base.blur.px(),
1773 box_shadow.spread.px(),
1774 self.border_radius,
1775 clip_mode,
1776 );
1777 }
1778 }
1779}
1780
1781fn rgba(color: AbsoluteColor) -> wr::ColorF {
1782 let rgba = color.to_color_space(ColorSpace::Srgb);
1783 wr::ColorF::new(
1784 rgba.components.0.clamp(0.0, 1.0),
1785 rgba.components.1.clamp(0.0, 1.0),
1786 rgba.components.2.clamp(0.0, 1.0),
1787 rgba.alpha,
1788 )
1789}
1790
1791fn glyphs(
1792 glyph_runs: &[Arc<GlyphStore>],
1793 mut baseline_origin: PhysicalPoint<Au>,
1794 justification_adjustment: Au,
1795 include_whitespace: bool,
1796) -> (Vec<GlyphInstance>, Au) {
1797 let mut glyphs = vec![];
1798 let mut largest_advance = Au::zero();
1799
1800 for run in glyph_runs {
1801 for glyph in run.glyphs() {
1802 if !run.is_whitespace() || include_whitespace {
1803 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
1804 let point = LayoutPoint::new(
1805 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
1806 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
1807 );
1808 let glyph_instance = GlyphInstance {
1809 index: glyph.id(),
1810 point,
1811 };
1812 glyphs.push(glyph_instance);
1813 }
1814
1815 if glyph.char_is_word_separator() {
1816 baseline_origin.x += justification_adjustment;
1817 }
1818
1819 let advance = glyph.advance();
1820 baseline_origin.x += advance;
1821 largest_advance.max_assign(advance);
1822 }
1823 }
1824 (glyphs, largest_advance)
1825}
1826
1827fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
1831 let expand = |radius: &mut f32, offset: f32| {
1832 if offset < 0.0 {
1834 *radius = (*radius + offset).max(0.0);
1835 return;
1836 }
1837
1838 if *radius > 0.0 {
1843 *radius += offset;
1844 }
1845 };
1846 if offsets.left != 0.0 {
1847 expand(&mut radii.top_left.width, offsets.left);
1848 expand(&mut radii.bottom_left.width, offsets.left);
1849 }
1850 if offsets.right != 0.0 {
1851 expand(&mut radii.top_right.width, offsets.right);
1852 expand(&mut radii.bottom_right.width, offsets.right);
1853 }
1854 if offsets.top != 0.0 {
1855 expand(&mut radii.top_left.height, offsets.top);
1856 expand(&mut radii.top_right.height, offsets.top);
1857 }
1858 if offsets.bottom != 0.0 {
1859 expand(&mut radii.bottom_right.height, offsets.bottom);
1860 expand(&mut radii.bottom_left.height, offsets.bottom);
1861 }
1862 radii
1863}
1864
1865fn resolve_border_image_outset(
1867 outset: BorderImageOutset,
1868 border: SideOffsets2D<f32, LayoutPixel>,
1869) -> SideOffsets2D<f32, LayoutPixel> {
1870 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
1871 match outset {
1872 NonNegativeLengthOrNumber::Length(length) => length.px(),
1873 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
1874 }
1875 }
1876
1877 SideOffsets2D::new(
1878 image_outset_for_side(outset.0, border.top),
1879 image_outset_for_side(outset.1, border.right),
1880 image_outset_for_side(outset.2, border.bottom),
1881 image_outset_for_side(outset.3, border.left),
1882 )
1883}
1884
1885fn resolve_border_image_width(
1887 width: &BorderImageWidth,
1888 border: SideOffsets2D<f32, LayoutPixel>,
1889 border_area: Size2D<f32, LayoutPixel>,
1890) -> SideOffsets2D<f32, LayoutPixel> {
1891 fn image_width_for_side(
1892 border_image_width: &BorderImageSideWidth,
1893 border_width: f32,
1894 total_length: f32,
1895 ) -> f32 {
1896 match border_image_width {
1897 BorderImageSideWidth::LengthPercentage(v) => {
1898 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
1899 },
1900 BorderImageSideWidth::Number(x) => border_width * x.0,
1901 BorderImageSideWidth::Auto => border_width,
1902 }
1903 }
1904
1905 SideOffsets2D::new(
1906 image_width_for_side(&width.0, border.top, border_area.height),
1907 image_width_for_side(&width.1, border.right, border_area.width),
1908 image_width_for_side(&width.2, border.bottom, border_area.height),
1909 image_width_for_side(&width.3, border.left, border_area.width),
1910 )
1911}
1912
1913fn resolve_border_image_slice(
1915 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
1916 size: Size2D<i32, UnknownUnit>,
1917) -> SideOffsets2D<i32, DevicePixel> {
1918 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
1919 match value.0 {
1920 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
1921 NumberOrPercentage::Number(n) => n.round() as i32,
1922 }
1923 }
1924
1925 SideOffsets2D::new(
1926 resolve_percentage(border_image_slice.0, size.height),
1927 resolve_percentage(border_image_slice.1, size.width),
1928 resolve_percentage(border_image_slice.2, size.height),
1929 resolve_percentage(border_image_slice.3, size.width),
1930 )
1931}
1932
1933pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
1934 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
1942 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
1943 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
1944 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
1945 if f < 1.0 {
1946 radius.top_left *= f;
1947 radius.top_right *= f;
1948 radius.bottom_right *= f;
1949 radius.bottom_left *= f;
1950 }
1951}
1952
1953pub(super) fn compute_margin_box_radius(
1961 radius: wr::BorderRadius,
1962 layout_rect: LayoutSize,
1963 fragment: &BoxFragment,
1964) -> wr::BorderRadius {
1965 let style = fragment.style();
1966 let margin = style.physical_margin();
1967 let adjust_radius = |radius: f32, margin: f32| -> f32 {
1968 if margin <= 0. || (radius / margin) >= 1. {
1969 (radius + margin).max(0.)
1970 } else {
1971 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
1972 }
1973 };
1974 let compute_margin_radius = |radius: LayoutSize,
1975 layout_rect: LayoutSize,
1976 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
1977 -> LayoutSize {
1978 let zero = LengthPercentage::zero();
1979 let width = margin
1980 .width
1981 .auto_is(|| &zero)
1982 .to_used_value(Au::from_f32_px(layout_rect.width));
1983 let height = margin
1984 .height
1985 .auto_is(|| &zero)
1986 .to_used_value(Au::from_f32_px(layout_rect.height));
1987 LayoutSize::new(
1988 adjust_radius(radius.width, width.to_f32_px()),
1989 adjust_radius(radius.height, height.to_f32_px()),
1990 )
1991 };
1992 wr::BorderRadius {
1993 top_left: compute_margin_radius(
1994 radius.top_left,
1995 layout_rect,
1996 Size2D::new(margin.left, margin.top),
1997 ),
1998 top_right: compute_margin_radius(
1999 radius.top_right,
2000 layout_rect,
2001 Size2D::new(margin.right, margin.top),
2002 ),
2003 bottom_left: compute_margin_radius(
2004 radius.bottom_left,
2005 layout_rect,
2006 Size2D::new(margin.left, margin.bottom),
2007 ),
2008 bottom_right: compute_margin_radius(
2009 radius.bottom_right,
2010 layout_rect,
2011 Size2D::new(margin.right, margin.bottom),
2012 ),
2013 }
2014}
2015
2016impl BoxFragment {
2017 fn border_radius(&self) -> BorderRadius {
2018 let style = self.style();
2019 let border = style.get_border();
2020 if border.border_top_left_radius.0.is_zero() &&
2021 border.border_top_right_radius.0.is_zero() &&
2022 border.border_bottom_right_radius.0.is_zero() &&
2023 border.border_bottom_left_radius.0.is_zero()
2024 {
2025 return BorderRadius::zero();
2026 }
2027
2028 let border_rect = self.border_rect();
2029 let resolve =
2030 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2031 let corner = |corner: &style::values::computed::BorderCornerRadius| {
2032 Size2D::new(
2033 resolve(&corner.0.width.0, border_rect.size.width),
2034 resolve(&corner.0.height.0, border_rect.size.height),
2035 )
2036 };
2037
2038 let mut radius = wr::BorderRadius {
2039 top_left: corner(&border.border_top_left_radius),
2040 top_right: corner(&border.border_top_right_radius),
2041 bottom_right: corner(&border.border_bottom_right_radius),
2042 bottom_left: corner(&border.border_bottom_left_radius),
2043 };
2044
2045 normalize_radii(&border_rect.to_webrender(), &mut radius);
2046 radius
2047 }
2048}