1use std::cell::{OnceCell, RefCell};
6use std::sync::Arc;
7
8use app_units::{AU_PER_PX, Au};
9use clip::{Clip, ClipId};
10use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
11use fonts::ShapedTextSlice;
12use gradient::WebRenderGradient;
13use layout_api::ReflowStatistics;
14use net_traits::image_cache::Image as CachedImage;
15use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
16use servo_arc::Arc as ServoArc;
17use servo_base::id::{PipelineId, ScrollTreeNodeId};
18use servo_config::opts::{DiagnosticsLogging, DiagnosticsLoggingOption};
19use servo_config::{pref, prefs};
20use servo_url::ServoUrl;
21use style::Zero;
22use style::color::{AbsoluteColor, ColorSpace};
23use style::computed_values::background_blend_mode::SingleComputedValue as BackgroundBlendMode;
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 PropertyBindingKey, RasterSpace, SpatialId, SpatialTreeItemKey, StackingContextFlags, 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.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 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.paint_info.caret_property_binding = None;
216
217 builder.add_all_spatial_nodes();
218
219 for clip in stacking_context_tree.clip_store.0.iter() {
220 builder.add_clip_to_display_list(clip);
221 }
222
223 let pipeline_id = builder.paint_info.pipeline_id;
226 let viewport_size = builder.paint_info.viewport_details.size;
227 let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
228 builder.wr().push_hit_test(
229 viewport_rect,
230 ClipChainId::INVALID,
231 SpatialId::root_reference_frame(pipeline_id),
232 PrimitiveFlags::default(),
233 (0, 0), );
235
236 stacking_context_tree
238 .root_stacking_context
239 .build_canvas_background_display_list(&mut builder, fragment_tree);
240 stacking_context_tree
241 .root_stacking_context
242 .build_display_list(&mut builder);
243 builder.paint_dom_inspector_highlight();
244
245 webrender_display_list_builder.end().1
246 }
247
248 fn wr(&mut self) -> &mut wr::DisplayListBuilder {
249 self.webrender_display_list_builder
250 }
251
252 fn pipeline_id(&mut self) -> wr::PipelineId {
253 self.paint_info.pipeline_id
254 }
255
256 fn mark_is_paintable(&mut self) {
257 self.paint_info.is_paintable = true;
258 }
259
260 fn mark_is_contentful(&mut self) {
261 self.paint_info.is_contentful = true;
262 }
263
264 fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
265 self.paint_info.scroll_tree.webrender_id(id)
266 }
267
268 fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
269 match id {
270 ClipId::INVALID => ClipChainId::INVALID,
271 _ => *self
272 .clip_map
273 .get(id.0)
274 .expect("Should never try to get clip before adding it to WebRender display list"),
275 }
276 }
277
278 pub(crate) fn add_all_spatial_nodes(&mut self) {
279 let mut spatial_tree_count = 0;
283 let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
284 let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
285
286 mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
287 mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
288
289 let pipeline_id = self.pipeline_id();
290 let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
291
292 for node in scroll_tree.nodes.iter().skip(2) {
293 let parent_scroll_node_id = node
294 .parent
295 .expect("Should have already added root reference frame");
296 let parent_spatial_node_id = mapping
297 .get(parent_scroll_node_id.index)
298 .expect("Should add spatial nodes to display list in order");
299
300 spatial_tree_count += 1;
303 let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
304
305 mapping.push(match &node.info {
306 SpatialTreeNodeInfo::ReferenceFrame(info) => {
307 let spatial_id = self.wr().push_reference_frame(
308 info.origin,
309 *parent_spatial_node_id,
310 info.transform_style,
311 PropertyBinding::Value(*info.transform.to_transform()),
312 info.kind,
313 spatial_tree_item_key,
314 );
315 self.wr().pop_reference_frame();
316 spatial_id
317 },
318 SpatialTreeNodeInfo::Scroll(info) => {
319 self.wr().define_scroll_frame(
320 *parent_spatial_node_id,
321 info.external_id,
322 info.content_rect,
323 info.clip_rect,
324 LayoutVector2D::zero(), 0, wr::HasScrollLinkedEffect::No,
327 spatial_tree_item_key,
328 )
329 },
330 SpatialTreeNodeInfo::Sticky(info) => {
331 self.wr().define_sticky_frame(
332 *parent_spatial_node_id,
333 info.frame_rect,
334 info.margins,
335 info.vertical_offset_bounds,
336 info.horizontal_offset_bounds,
337 LayoutVector2D::zero(), spatial_tree_item_key,
339 None, )
341 },
342 });
343 }
344
345 scroll_tree.update_mapping(mapping);
346 self.paint_info.scroll_tree = scroll_tree;
347 }
348
349 pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
357 assert_eq!(
358 clip.id.0,
359 self.clip_map.len(),
360 "Clips should be added in order"
361 );
362
363 let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
364 let new_clip_id = if clip.radii.is_zero() {
365 self.wr().define_clip_rect(spatial_id, clip.rect)
366 } else {
367 self.wr().define_clip_rounded_rect(
368 spatial_id,
369 ComplexClipRegion {
370 rect: clip.rect,
371 radii: clip.radii,
372 mode: ClipMode::Clip,
373 },
374 )
375 };
376
377 let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
382 ClipChainId::INVALID => None,
383 parent => Some(parent),
384 };
385 let clip_chain_id = self
386 .wr()
387 .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
388 self.clip_map.push(clip_chain_id);
389 clip_chain_id
390 }
391
392 fn maybe_create_clip(
396 &mut self,
397 radii: wr::BorderRadius,
398 rect: units::LayoutRect,
399 force_clip_creation: bool,
400 ) -> Option<ClipChainId> {
401 if radii.is_zero() && !force_clip_creation {
402 return None;
403 }
404
405 Some(self.add_clip_to_display_list(&Clip {
406 id: ClipId(self.clip_map.len()),
407 radii,
408 rect,
409 parent_scroll_node_id: self.current_scroll_node_id,
410 parent_clip_id: self.current_clip_id,
411 }))
412 }
413
414 fn common_properties(
415 &self,
416 clip_rect: units::LayoutRect,
417 style: &ComputedValues,
418 ) -> wr::CommonItemProperties {
419 wr::CommonItemProperties {
423 clip_rect,
424 spatial_id: self.spatial_id(self.current_scroll_node_id),
425 clip_chain_id: self.clip_chain_id(self.current_clip_id),
426 flags: style.get_webrender_primitive_flags(),
427 }
428 }
429
430 fn paint_dom_inspector_highlight(&mut self) {
432 let Some(highlight) = self
433 .inspector_highlight
434 .take()
435 .and_then(|highlight| highlight.state)
436 else {
437 return;
438 };
439
440 const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
441 r: 0.23,
442 g: 0.7,
443 b: 0.87,
444 a: 0.5,
445 };
446
447 const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
448 r: 0.49,
449 g: 0.3,
450 b: 0.7,
451 a: 0.5,
452 };
453
454 const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
455 r: 0.2,
456 g: 0.2,
457 b: 0.2,
458 a: 0.5,
459 };
460
461 const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
462 r: 1.,
463 g: 0.93,
464 b: 0.,
465 a: 0.5,
466 };
467
468 let content_box = highlight.content_box.to_webrender();
470 let properties = wr::CommonItemProperties {
471 clip_rect: content_box,
472 spatial_id: highlight.spatial_id,
473 clip_chain_id: highlight.clip_chain_id,
474 flags: wr::PrimitiveFlags::default(),
475 };
476
477 self.wr()
478 .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
479
480 if let Some(box_fragment) = highlight.maybe_box_fragment {
482 let mut paint_highlight =
483 |color: webrender_api::ColorF,
484 fragment_relative_bounds: PhysicalRect<Au>,
485 widths: webrender_api::units::LayoutSideOffsets| {
486 if widths.is_zero() {
487 return;
488 }
489
490 let bounds = box_fragment
491 .borrow()
492 .offset_by_containing_block(&fragment_relative_bounds)
493 .to_webrender();
494
495 let border_style = wr::BorderSide {
497 color,
498 style: wr::BorderStyle::Solid,
499 };
500
501 let details = wr::BorderDetails::Normal(wr::NormalBorder {
502 top: border_style,
503 right: border_style,
504 bottom: border_style,
505 left: border_style,
506 radius: webrender_api::BorderRadius::default(),
507 do_aa: true,
508 });
509
510 let common = wr::CommonItemProperties {
511 clip_rect: bounds,
512 spatial_id: highlight.spatial_id,
513 clip_chain_id: highlight.clip_chain_id,
514 flags: wr::PrimitiveFlags::default(),
515 };
516 self.wr().push_border(&common, bounds, widths, details)
517 };
518
519 let box_fragment = box_fragment.borrow();
520 paint_highlight(
521 PADDING_BOX_HIGHLIGHT_COLOR,
522 box_fragment.padding_rect(),
523 box_fragment.padding.to_webrender(),
524 );
525 paint_highlight(
526 BORDER_BOX_HIGHLIGHT_COLOR,
527 box_fragment.border_rect(),
528 box_fragment.border.to_webrender(),
529 );
530 paint_highlight(
531 MARGIN_BOX_HIGHLIGHT_COLOR,
532 box_fragment.margin_rect(),
533 box_fragment.margin.to_webrender(),
534 );
535 }
536 }
537
538 fn check_if_paintable(&mut self, bounds: LayoutRect, clip_rect: LayoutRect, opacity: f32) {
539 if opacity <= 0.0 {
547 return;
548 }
549
550 if self
552 .paint_timing_handler
553 .check_bounding_rect(bounds, clip_rect)
554 {
555 self.mark_is_paintable();
556 }
557 }
558
559 fn check_for_lcp_candidate(
560 &mut self,
561 clip_rect: LayoutRect,
562 bounds: LayoutRect,
563 tag: Option<Tag>,
564 url: Option<ServoUrl>,
565 ) {
566 if !pref!(largest_contentful_paint_enabled) {
567 return;
568 }
569
570 let transform = self
571 .paint_info
572 .scroll_tree
573 .cumulative_node_to_root_transform(self.current_scroll_node_id);
574
575 self.paint_timing_handler
576 .update_lcp_candidate(tag, bounds, clip_rect, transform, url);
577 }
578}
579
580impl InspectorHighlight {
581 fn register_fragment_of_highlighted_dom_node(
582 &mut self,
583 fragment: &Fragment,
584 spatial_id: SpatialId,
585 clip_chain_id: ClipChainId,
586 containing_block: &PhysicalRect<Au>,
587 ) {
588 let state = self.state.get_or_insert(HighlightTraversalState {
589 content_box: Rect::zero(),
590 spatial_id,
591 clip_chain_id,
592 maybe_box_fragment: None,
593 });
594
595 if spatial_id != state.spatial_id {
598 return;
599 }
600
601 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
602 debug_assert_eq!(
603 clip_chain_id, state.clip_chain_id,
604 "Fragments of the same node must either have no clip chain or the same one"
605 );
606 }
607
608 let Some(fragment_relative_rect) = fragment.base().map(|base| base.rect) else {
609 return;
610 };
611 state.maybe_box_fragment = match fragment {
612 Fragment::Box(fragment) | Fragment::Float(fragment) => Some(fragment.clone()),
613 _ => None,
614 };
615
616 state.content_box = state
617 .content_box
618 .union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
619 }
620}
621
622impl Fragment {
623 pub(crate) fn build_display_list(
624 &self,
625 builder: &mut DisplayListBuilder,
626 containing_block: &PhysicalRect<Au>,
627 section: StackingContextSection,
628 is_hit_test_for_scrollable_overflow: bool,
629 is_collapsed_table_borders: bool,
630 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
631 ) {
632 if let Some(mut base) = self.base_mut() {
633 match base.status {
634 FragmentStatus::New => {
635 builder.reflow_statistics.rebuilt_fragment_count += 1;
636 base.status = FragmentStatus::Clean;
637 },
638 FragmentStatus::StyleChanged => {
639 builder.reflow_statistics.restyle_fragment_count += 1;
640 base.status = FragmentStatus::Clean;
641 },
642 FragmentStatus::PositionMaybeChanged => {
643 builder.reflow_statistics.possibly_moved_fragment_count += 1;
644 base.status = FragmentStatus::Clean;
645 },
646 FragmentStatus::Clean => {},
647 }
648 }
649
650 let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
651 let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
652 if let Some(inspector_highlight) = &mut builder.inspector_highlight {
653 if self.tag() == Some(inspector_highlight.tag) {
654 inspector_highlight.register_fragment_of_highlighted_dom_node(
655 self,
656 spatial_id,
657 clip_chain_id,
658 containing_block,
659 );
660 }
661 }
662
663 match self {
664 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
665 let box_fragment = &*box_fragment.borrow();
666 match box_fragment.style().get_inherited_box().visibility {
667 Visibility::Visible => BuilderForBoxFragment::new(
668 box_fragment,
669 containing_block,
670 is_hit_test_for_scrollable_overflow,
671 is_collapsed_table_borders,
672 )
673 .build(builder, section),
674 Visibility::Hidden => (),
675 Visibility::Collapse => (),
676 }
677 },
678 Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Positioning(_) => {},
679 Fragment::Image(image) => {
680 let image = image.borrow();
681 let style = image.base.style();
682 match style.get_inherited_box().visibility {
683 Visibility::Visible => {
684 let image_rendering =
685 style.get_inherited_box().image_rendering.to_webrender();
686 let rect = image
687 .base
688 .rect
689 .translate(containing_block.origin.to_vector())
690 .to_webrender();
691 let clip = image
692 .clip
693 .translate(containing_block.origin.to_vector())
694 .to_webrender();
695 let common = builder.common_properties(clip, &style);
696
697 if let Some(image_key) = image.image_key {
698 builder.wr().push_image(
699 &common,
700 rect,
701 image_rendering,
702 wr::AlphaType::PremultipliedAlpha,
703 image_key,
704 wr::ColorF::WHITE,
705 );
706
707 builder.check_if_paintable(
708 rect,
709 common.clip_rect,
710 style.clone_opacity(),
711 );
712
713 if !image.showing_broken_image_icon {
721 builder.mark_is_contentful();
722
723 builder.check_for_lcp_candidate(
724 common.clip_rect,
725 rect,
726 image.base.tag,
727 image.url.clone(),
728 );
729 }
730 }
731
732 if image.showing_broken_image_icon {
733 self.build_display_list_for_broken_image_border(
734 builder,
735 containing_block,
736 &common,
737 );
738 }
739 },
740 Visibility::Hidden => (),
741 Visibility::Collapse => (),
742 }
743 },
744 Fragment::IFrame(iframe) => {
745 let iframe = iframe.borrow();
746 let style = iframe.base.style();
747 match style.get_inherited_box().visibility {
748 Visibility::Visible => {
749 let rect = iframe
750 .base
751 .rect
752 .translate(containing_block.origin.to_vector());
753
754 let common = builder.common_properties(rect.to_webrender(), &style);
755 builder.wr().push_iframe(
756 rect.to_webrender(),
757 common.clip_rect,
758 &wr::SpaceAndClipInfo {
759 spatial_id: common.spatial_id,
760 clip_chain_id: common.clip_chain_id,
761 },
762 iframe.pipeline_id.into(),
763 true,
764 );
765 builder.check_if_paintable(
770 rect.to_webrender(),
771 common.clip_rect,
772 style.clone_opacity(),
773 );
774 },
775 Visibility::Hidden => (),
776 Visibility::Collapse => (),
777 }
778 },
779 Fragment::Text(text) => {
780 let text = &*text.borrow();
781 match text.base.style().get_inherited_box().visibility {
782 Visibility::Visible => self.build_display_list_for_text_fragment(
783 text,
784 builder,
785 containing_block,
786 text_decorations,
787 ),
788 Visibility::Hidden => (),
789 Visibility::Collapse => (),
790 }
791 },
792 }
793 }
794
795 fn build_display_list_for_text_fragment(
796 &self,
797 fragment: &TextFragment,
798 builder: &mut DisplayListBuilder,
799 containing_block: &PhysicalRect<Au>,
800 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
801 ) {
802 let rect = fragment
805 .base
806 .rect
807 .translate(containing_block.origin.to_vector());
808 let mut baseline_origin = rect.origin;
809 baseline_origin.y += fragment.font_metrics.ascent;
810 let include_whitespace =
811 fragment.offsets.is_some() || text_decorations.iter().any(|item| !item.line.is_empty());
812
813 let (glyphs, largest_advance) = glyphs(
814 &fragment.glyphs,
815 baseline_origin,
816 fragment.justification_adjustment,
817 include_whitespace,
818 );
819
820 if glyphs.is_empty() && !fragment.is_empty_for_text_cursor {
821 return;
822 }
823
824 let parent_style = fragment.base.style();
825 let color = parent_style.clone_color();
826 let font_metrics = &fragment.font_metrics;
827 let dppx = builder.device_pixel_ratio.get();
828
829 let glyph_bounds = rect
836 .inflate(largest_advance.scale_by(2.0), Au::zero())
837 .to_webrender();
838 let common = builder.common_properties(glyph_bounds, &parent_style);
839
840 let shadows = &parent_style.get_inherited_text().text_shadow;
843 for shadow in shadows.0.iter().rev() {
844 builder.wr().push_shadow(
845 &wr::SpaceAndClipInfo {
846 spatial_id: common.spatial_id,
847 clip_chain_id: common.clip_chain_id,
848 },
849 wr::Shadow {
850 offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
851 color: rgba(shadow.color.resolve_to_absolute(&color)),
852 blur_radius: shadow.blur.px(),
853 },
854 true, );
856 }
857
858 for text_decoration in text_decorations.iter() {
859 if text_decoration.line.contains(TextDecorationLine::UNDERLINE) {
860 let mut rect = rect;
861 rect.origin.y += font_metrics.ascent - font_metrics.underline_offset;
862 rect.size.height =
863 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
864
865 self.build_display_list_for_text_decoration(
866 &parent_style,
867 builder,
868 &rect,
869 text_decoration,
870 TextDecorationLine::UNDERLINE,
871 );
872 }
873 }
874
875 for text_decoration in text_decorations.iter() {
876 if text_decoration.line.contains(TextDecorationLine::OVERLINE) {
877 let mut rect = rect;
878 rect.size.height =
879 Au::from_f32_px(font_metrics.underline_size.to_nearest_pixel(dppx));
880 self.build_display_list_for_text_decoration(
881 &parent_style,
882 builder,
883 &rect,
884 text_decoration,
885 TextDecorationLine::OVERLINE,
886 );
887 }
888 }
889
890 self.build_display_list_for_text_selection(
891 fragment,
892 builder,
893 containing_block,
894 fragment.base.rect.min_x(),
895 fragment.justification_adjustment,
896 );
897
898 builder.wr().push_text(
899 &common,
900 glyph_bounds,
901 &glyphs,
902 fragment.font_key,
903 rgba(color),
904 None,
905 );
906
907 builder.check_if_paintable(glyph_bounds, common.clip_rect, parent_style.clone_opacity());
908
909 builder.mark_is_contentful();
913
914 for text_decoration in text_decorations.iter() {
915 if text_decoration
916 .line
917 .contains(TextDecorationLine::LINE_THROUGH)
918 {
919 let mut rect = rect;
920 rect.origin.y += font_metrics.ascent - font_metrics.strikeout_offset;
921 rect.size.height =
922 Au::from_f32_px(font_metrics.strikeout_size.to_nearest_pixel(dppx));
923 self.build_display_list_for_text_decoration(
924 &parent_style,
925 builder,
926 &rect,
927 text_decoration,
928 TextDecorationLine::LINE_THROUGH,
929 );
930 }
931 }
932
933 if !shadows.0.is_empty() {
934 builder.wr().pop_all_shadows();
935 }
936 }
937
938 fn build_display_list_for_text_decoration(
939 &self,
940 parent_style: &ServoArc<ComputedValues>,
941 builder: &mut DisplayListBuilder,
942 rect: &PhysicalRect<Au>,
943 text_decoration: &FragmentTextDecoration,
944 line: TextDecorationLine,
945 ) {
946 if text_decoration.style == ComputedTextDecorationStyle::MozNone {
947 return;
948 }
949
950 let mut rect = rect.to_webrender();
951 let line_thickness = rect.height().ceil();
952
953 if text_decoration.style == ComputedTextDecorationStyle::Wavy {
954 rect = rect.inflate(0.0, line_thickness * 1.0);
955 }
956
957 let common_properties = builder.common_properties(rect, parent_style);
958 builder.wr().push_line(
959 &common_properties,
960 &rect,
961 line_thickness,
962 wr::LineOrientation::Horizontal,
963 &rgba(text_decoration.color),
964 text_decoration.style.to_webrender(),
965 );
966
967 if text_decoration.style == TextDecorationStyle::Double {
968 let half_height = (rect.height() / 2.0).floor().max(1.0);
969 let y_offset = match line {
970 TextDecorationLine::OVERLINE => -rect.height() - half_height,
971 _ => rect.height() + half_height,
972 };
973 let rect = rect.translate(Vector2D::new(0.0, y_offset));
974 let common_properties = builder.common_properties(rect, parent_style);
975 builder.wr().push_line(
976 &common_properties,
977 &rect,
978 line_thickness,
979 wr::LineOrientation::Horizontal,
980 &rgba(text_decoration.color),
981 text_decoration.style.to_webrender(),
982 );
983 }
984 }
985
986 fn build_display_list_for_broken_image_border(
987 &self,
988 builder: &mut DisplayListBuilder,
989 containing_block: &PhysicalRect<Au>,
990 common: &CommonItemProperties,
991 ) {
992 let border_side = BorderSide {
993 color: ColorF::BLACK,
994 style: wr::BorderStyle::Inset,
995 };
996 builder.wr().push_border(
997 common,
998 containing_block.to_webrender(),
999 LayoutSideOffsets::new_all_same(1.0),
1000 BorderDetails::Normal(NormalBorder {
1001 left: border_side,
1002 right: border_side,
1003 top: border_side,
1004 bottom: border_side,
1005 radius: BorderRadius::zero(),
1006 do_aa: true,
1007 }),
1008 );
1009 }
1010
1011 fn build_display_list_for_text_selection(
1014 &self,
1015 fragment: &TextFragment,
1016 builder: &mut DisplayListBuilder<'_>,
1017 containing_block_rect: &PhysicalRect<Au>,
1018 fragment_x_offset: Au,
1019 justification_adjustment: Au,
1020 ) {
1021 let Some(offsets) = fragment.offsets.as_ref() else {
1022 return;
1023 };
1024
1025 let shared_selection = offsets.shared_selection.borrow();
1026 if !shared_selection.enabled {
1027 return;
1028 }
1029
1030 if offsets.character_range.start > shared_selection.character_range.end ||
1031 offsets.character_range.end < shared_selection.character_range.start
1032 {
1033 return;
1034 }
1035
1036 if fragment.is_empty_for_text_cursor &&
1041 !offsets
1042 .character_range
1043 .contains(&shared_selection.character_range.start)
1044 {
1045 return;
1046 }
1047
1048 let mut current_character_index = offsets.character_range.start;
1049 let mut current_advance = Au::zero();
1050 let mut start_advance = None;
1051 let mut end_advance = None;
1052 for glyph_store in fragment.glyphs.iter() {
1053 let glyph_store_character_count = glyph_store.character_count();
1054 if current_character_index + glyph_store_character_count <
1055 shared_selection.character_range.start
1056 {
1057 current_advance += glyph_store.total_advance() +
1058 (justification_adjustment * glyph_store.total_word_separators() as i32);
1059 current_character_index += glyph_store_character_count;
1060 continue;
1061 }
1062
1063 if current_character_index >= shared_selection.character_range.end {
1064 break;
1065 }
1066
1067 for glyph in glyph_store.glyphs() {
1068 if current_character_index >= shared_selection.character_range.start {
1069 start_advance = start_advance.or(Some(current_advance));
1070 }
1071
1072 current_character_index += glyph.character_count();
1073 current_advance += glyph.advance();
1074 if glyph.char_is_word_separator() {
1075 current_advance += justification_adjustment;
1076 }
1077
1078 if current_character_index <= shared_selection.character_range.end {
1079 end_advance = Some(current_advance);
1080 }
1081 }
1082 }
1083
1084 let start_x = start_advance.unwrap_or(current_advance);
1085 let end_x = end_advance.unwrap_or(current_advance);
1086
1087 let parent_style = fragment.base.style();
1088 if !shared_selection.range.is_empty() {
1089 let selection_rect = Rect::new(
1090 containing_block_rect.origin +
1091 Vector2D::new(fragment_x_offset + start_x, Au::zero()),
1092 Size2D::new(end_x - start_x, containing_block_rect.height()),
1093 )
1094 .to_webrender();
1095
1096 if let Some(selection_color) = fragment
1097 .selected_style
1098 .borrow()
1099 .clone_background_color()
1100 .as_absolute()
1101 {
1102 let selection_common = builder.common_properties(selection_rect, &parent_style);
1103 builder
1104 .wr()
1105 .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1106 }
1107 return;
1108 }
1109
1110 let insertion_point_rect = Rect::new(
1111 containing_block_rect.origin + Vector2D::new(start_x + fragment_x_offset, Au::zero()),
1112 Size2D::new(
1113 INSERTION_POINT_LOGICAL_WIDTH,
1114 containing_block_rect.height(),
1115 ),
1116 )
1117 .to_webrender();
1118
1119 let color = parent_style.clone_color();
1120 let caret_color = match parent_style.clone_caret_color().0 {
1121 ColorOrAuto::Color(caret_color) => caret_color.resolve_to_absolute(&color),
1122 ColorOrAuto::Auto => color,
1123 };
1124 let insertion_point_common = builder.common_properties(insertion_point_rect, &parent_style);
1125
1126 let caret_color = rgba(caret_color);
1127 let property_binding = if prefs::get().editing_caret_blink_time().is_some() {
1128 let pipeline_id: PipelineId = builder.paint_info.pipeline_id.into();
1132 let property_binding_key = PropertyBindingKey::new(pipeline_id.into());
1133 builder.paint_info.caret_property_binding = Some((property_binding_key, caret_color));
1134 PropertyBinding::Binding(property_binding_key, caret_color)
1135 } else {
1136 PropertyBinding::Value(caret_color)
1137 };
1138
1139 builder.wr().push_rect_with_animation(
1140 &insertion_point_common,
1141 insertion_point_rect,
1142 property_binding,
1143 );
1144 }
1145}
1146
1147struct BuilderForBoxFragment<'a> {
1148 fragment: &'a BoxFragment,
1149 containing_block: &'a PhysicalRect<Au>,
1150 border_rect: units::LayoutRect,
1151 margin_rect: OnceCell<units::LayoutRect>,
1152 padding_rect: OnceCell<units::LayoutRect>,
1153 content_rect: OnceCell<units::LayoutRect>,
1154 border_radius: wr::BorderRadius,
1155 border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1156 padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1157 content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1158 is_hit_test_for_scrollable_overflow: bool,
1159 is_collapsed_table_borders: bool,
1160}
1161
1162impl<'a> BuilderForBoxFragment<'a> {
1163 fn new(
1164 fragment: &'a BoxFragment,
1165 containing_block: &'a PhysicalRect<Au>,
1166 is_hit_test_for_scrollable_overflow: bool,
1167 is_collapsed_table_borders: bool,
1168 ) -> Self {
1169 let border_rect = fragment
1170 .border_rect()
1171 .translate(containing_block.origin.to_vector());
1172 Self {
1173 fragment,
1174 containing_block,
1175 border_rect: border_rect.to_webrender(),
1176 border_radius: fragment.border_radius(),
1177 margin_rect: OnceCell::new(),
1178 padding_rect: OnceCell::new(),
1179 content_rect: OnceCell::new(),
1180 border_edge_clip_chain_id: RefCell::new(None),
1181 padding_edge_clip_chain_id: RefCell::new(None),
1182 content_edge_clip_chain_id: RefCell::new(None),
1183 is_hit_test_for_scrollable_overflow,
1184 is_collapsed_table_borders,
1185 }
1186 }
1187
1188 fn content_rect(&self) -> &units::LayoutRect {
1189 self.content_rect.get_or_init(|| {
1190 self.fragment
1191 .content_rect()
1192 .translate(self.containing_block.origin.to_vector())
1193 .to_webrender()
1194 })
1195 }
1196
1197 fn padding_rect(&self) -> &units::LayoutRect {
1198 self.padding_rect.get_or_init(|| {
1199 self.fragment
1200 .padding_rect()
1201 .translate(self.containing_block.origin.to_vector())
1202 .to_webrender()
1203 })
1204 }
1205
1206 fn margin_rect(&self) -> &units::LayoutRect {
1207 self.margin_rect.get_or_init(|| {
1208 self.fragment
1209 .margin_rect()
1210 .translate(self.containing_block.origin.to_vector())
1211 .to_webrender()
1212 })
1213 }
1214
1215 fn border_edge_clip(
1216 &self,
1217 builder: &mut DisplayListBuilder,
1218 force_clip_creation: bool,
1219 ) -> Option<ClipChainId> {
1220 if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1221 return Some(clip);
1222 }
1223
1224 let maybe_clip =
1225 builder.maybe_create_clip(self.border_radius, self.border_rect, force_clip_creation);
1226 *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1227 maybe_clip
1228 }
1229
1230 fn padding_edge_clip(
1231 &self,
1232 builder: &mut DisplayListBuilder,
1233 force_clip_creation: bool,
1234 ) -> Option<ClipChainId> {
1235 if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1236 return Some(clip);
1237 }
1238
1239 let radii = offset_radii(self.border_radius, -self.fragment.border.to_webrender());
1240 let maybe_clip =
1241 builder.maybe_create_clip(radii, *self.padding_rect(), force_clip_creation);
1242 *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1243 maybe_clip
1244 }
1245
1246 fn content_edge_clip(
1247 &self,
1248 builder: &mut DisplayListBuilder,
1249 force_clip_creation: bool,
1250 ) -> Option<ClipChainId> {
1251 if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1252 return Some(clip);
1253 }
1254
1255 let radii = offset_radii(
1256 self.border_radius,
1257 -(self.fragment.border + self.fragment.padding).to_webrender(),
1258 );
1259 let maybe_clip =
1260 builder.maybe_create_clip(radii, *self.content_rect(), force_clip_creation);
1261 *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1262 maybe_clip
1263 }
1264
1265 fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) {
1266 if self.is_hit_test_for_scrollable_overflow &&
1267 self.fragment.style().get_inherited_ui().pointer_events !=
1268 style::computed_values::pointer_events::T::None
1269 {
1270 self.build_hit_test(
1271 builder,
1272 self.fragment
1273 .scrollable_overflow()
1274 .translate(self.containing_block.origin.to_vector())
1275 .to_webrender(),
1276 );
1277 return;
1278 }
1279 if self.is_collapsed_table_borders {
1280 self.build_collapsed_table_borders(builder);
1281 return;
1282 }
1283
1284 if section == StackingContextSection::Outline {
1285 self.build_outline(builder);
1286 return;
1287 }
1288
1289 if self
1290 .fragment
1291 .base
1292 .flags
1293 .contains(FragmentFlags::DO_NOT_PAINT)
1294 {
1295 return;
1296 }
1297
1298 self.build_background(builder);
1299 self.build_box_shadow(builder);
1300 self.build_border(builder);
1301 }
1302
1303 fn build_hit_test(&self, builder: &mut DisplayListBuilder, rect: LayoutRect) {
1304 let external_scroll_node_id = builder
1305 .paint_info
1306 .external_scroll_id_for_scroll_tree_node(builder.current_scroll_node_id);
1307
1308 let mut common = builder.common_properties(rect, &self.fragment.style());
1309 if let Some(clip_chain_id) = self.border_edge_clip(builder, false) {
1310 common.clip_chain_id = clip_chain_id;
1311 }
1312 builder.wr().push_hit_test(
1313 common.clip_rect,
1314 common.clip_chain_id,
1315 common.spatial_id,
1316 common.flags,
1317 (external_scroll_node_id.0, 0), );
1319 }
1320
1321 fn build_background_for_painter(
1322 &mut self,
1323 builder: &mut DisplayListBuilder,
1324 painter: &BackgroundPainter,
1325 ) {
1326 let b = painter.style.get_background();
1327 let background_color = painter.style.resolve_color(&b.background_color);
1328 if background_color.alpha > 0.0 {
1329 let layer_index = b.background_image.0.len() - 1;
1333 let bounds = painter.painting_area(self, builder, layer_index);
1334 let common = painter.common_properties(self, builder, layer_index, bounds);
1335 builder
1336 .wr()
1337 .push_rect(&common, bounds, rgba(background_color));
1338
1339 let default_background_color = servo_config::pref!(shell_background_color_rgba);
1343 let default_background_color = AbsoluteColor::new(
1344 ColorSpace::Srgb,
1345 default_background_color[0] as f32,
1346 default_background_color[1] as f32,
1347 default_background_color[2] as f32,
1348 default_background_color[3] as f32,
1349 )
1350 .into_srgb_legacy();
1351 if background_color != default_background_color {
1352 builder.mark_is_paintable();
1353 }
1354 }
1355
1356 self.build_background_image(builder, painter);
1357 }
1358
1359 fn build_background(&mut self, builder: &mut DisplayListBuilder) {
1360 let flags = self.fragment.base.flags;
1361
1362 if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1365 return;
1366 }
1367 if !builder.paint_body_background &&
1369 flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1370 {
1371 return;
1372 }
1373
1374 if let BackgroundMode::None = self.fragment.background_mode {
1376 return;
1377 }
1378
1379 if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1383 for extra_background in extra_backgrounds {
1384 let positioning_area = extra_background.rect;
1385 let painter = BackgroundPainter {
1386 style: &extra_background.style.borrow_mut(),
1387 painting_area_override: None,
1388 positioning_area_override: Some(
1389 positioning_area
1390 .translate(self.containing_block.origin.to_vector())
1391 .to_webrender(),
1392 ),
1393 };
1394 self.build_background_for_painter(builder, &painter);
1395 }
1396 }
1397
1398 let painter = BackgroundPainter {
1399 style: &self.fragment.style(),
1400 painting_area_override: None,
1401 positioning_area_override: None,
1402 };
1403 self.build_background_for_painter(builder, &painter);
1404 }
1405
1406 fn build_background_image(
1407 &mut self,
1408 builder: &mut DisplayListBuilder,
1409 painter: &BackgroundPainter,
1410 ) {
1411 let style = painter.style;
1412 let b = style.get_background();
1413 let need_blend_container = b
1414 .background_blend_mode
1415 .0
1416 .iter()
1417 .take(b.background_image.0.len())
1418 .any(|background_blend_mode| background_blend_mode != &BackgroundBlendMode::Normal);
1419
1420 let push_stacking_context = |builder: &mut DisplayListBuilder,
1421 blend_mode: BackgroundBlendMode,
1422 flags: StackingContextFlags|
1423 -> bool {
1424 let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
1425 builder.wr().push_stacking_context(
1426 LayoutPoint::zero(), spatial_id,
1428 PrimitiveFlags::empty(),
1429 None,
1430 webrender_api::TransformStyle::Flat,
1431 blend_mode.to_webrender(),
1432 &[],
1433 &[],
1434 &[],
1435 RasterSpace::Screen,
1436 flags,
1437 None,
1438 );
1439 true
1440 };
1441
1442 if need_blend_container {
1443 push_stacking_context(
1444 builder,
1445 BackgroundBlendMode::Normal,
1446 StackingContextFlags::IS_BLEND_CONTAINER,
1447 );
1448 }
1449
1450 let node = self.fragment.base.tag.map(|tag| tag.node);
1451 for (index, image) in b.background_image.0.iter().enumerate().rev() {
1453 match builder.image_resolver.resolve_image(node, image) {
1454 Err(_) => {},
1455 Ok(ResolvedImage::Gradient(gradient)) => {
1456 let intrinsic = NaturalSizes::empty();
1457 let Some(layer) =
1458 &background::layout_layer(self, painter, builder, index, intrinsic)
1459 else {
1460 continue;
1461 };
1462
1463 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1464 if needs_blending {
1465 push_stacking_context(builder, layer.blend_mode, Default::default());
1466 }
1467
1468 match gradient::build(style, gradient, layer.tile_size, builder) {
1469 WebRenderGradient::Linear(linear_gradient) => builder.wr().push_gradient(
1470 &layer.common,
1471 layer.bounds,
1472 linear_gradient,
1473 layer.tile_size,
1474 layer.tile_spacing,
1475 ),
1476 WebRenderGradient::Radial(radial_gradient) => {
1477 builder.wr().push_radial_gradient(
1478 &layer.common,
1479 layer.bounds,
1480 radial_gradient,
1481 layer.tile_size,
1482 layer.tile_spacing,
1483 )
1484 },
1485 WebRenderGradient::Conic(conic_gradient) => {
1486 builder.wr().push_conic_gradient(
1487 &layer.common,
1488 layer.bounds,
1489 conic_gradient,
1490 layer.tile_size,
1491 layer.tile_spacing,
1492 )
1493 },
1494 }
1495
1496 if needs_blending {
1497 builder.wr().pop_stacking_context();
1498 }
1499
1500 builder.check_if_paintable(
1501 layer.bounds,
1502 layer.common.clip_rect,
1503 style.clone_opacity(),
1504 );
1505 },
1506 Ok(ResolvedImage::Image { image, size }) => {
1507 let dppx = 1.0;
1509 let intrinsic =
1510 NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
1511 let layer = background::layout_layer(self, painter, builder, index, intrinsic);
1512
1513 let image_wr_key = match image {
1514 CachedImage::Raster(raster_image) => raster_image.id,
1515 CachedImage::Vector(vector_image) => {
1516 let scale = builder.device_pixel_ratio.get();
1517 let default_size: DeviceIntSize =
1518 Size2D::new(size.width * scale, size.height * scale).to_i32();
1519 let layer_size = layer.as_ref().map(|layer| {
1520 Size2D::new(
1521 layer.tile_size.width * scale,
1522 layer.tile_size.height * scale,
1523 )
1524 .to_i32()
1525 });
1526
1527 node.and_then(|node| {
1528 let size = layer_size.unwrap_or(default_size);
1529 builder.image_resolver.rasterize_vector_image(
1530 vector_image.id,
1531 size,
1532 node,
1533 vector_image.svg_id,
1534 )
1535 })
1536 .and_then(|rasterized_image| rasterized_image.id)
1537 },
1538 };
1539
1540 let Some(image_key) = image_wr_key else {
1541 continue;
1542 };
1543
1544 if let Some(layer) = layer {
1545 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1546 if needs_blending {
1547 push_stacking_context(builder, layer.blend_mode, Default::default());
1548 }
1549
1550 if layer.repeat {
1551 builder.wr().push_repeating_image(
1552 &layer.common,
1553 layer.bounds,
1554 layer.tile_size,
1555 layer.tile_spacing,
1556 style.clone_image_rendering().to_webrender(),
1557 wr::AlphaType::PremultipliedAlpha,
1558 image_key,
1559 wr::ColorF::WHITE,
1560 )
1561 } else {
1562 builder.wr().push_image(
1563 &layer.common,
1564 layer.bounds,
1565 style.clone_image_rendering().to_webrender(),
1566 wr::AlphaType::PremultipliedAlpha,
1567 image_key,
1568 wr::ColorF::WHITE,
1569 )
1570 }
1571
1572 if needs_blending {
1573 builder.wr().pop_stacking_context();
1574 }
1575
1576 builder.check_if_paintable(
1577 layer.bounds,
1578 layer.common.clip_rect,
1579 style.clone_opacity(),
1580 );
1581
1582 builder.mark_is_contentful();
1587
1588 builder.check_for_lcp_candidate(
1589 layer.common.clip_rect,
1590 layer.bounds,
1591 self.fragment.base.tag,
1592 None,
1593 );
1594 }
1595 },
1596 }
1597 }
1598
1599 if need_blend_container {
1600 builder.wr().pop_stacking_context();
1601 }
1602 }
1603
1604 fn build_border_side(&mut self, style_color: BorderStyleColor) -> wr::BorderSide {
1605 wr::BorderSide {
1606 color: rgba(style_color.color),
1607 style: match style_color.style {
1608 BorderStyle::None => wr::BorderStyle::None,
1609 BorderStyle::Solid => wr::BorderStyle::Solid,
1610 BorderStyle::Double => wr::BorderStyle::Double,
1611 BorderStyle::Dotted => wr::BorderStyle::Dotted,
1612 BorderStyle::Dashed => wr::BorderStyle::Dashed,
1613 BorderStyle::Hidden => wr::BorderStyle::Hidden,
1614 BorderStyle::Groove => wr::BorderStyle::Groove,
1615 BorderStyle::Ridge => wr::BorderStyle::Ridge,
1616 BorderStyle::Inset => wr::BorderStyle::Inset,
1617 BorderStyle::Outset => wr::BorderStyle::Outset,
1618 },
1619 }
1620 }
1621
1622 fn build_collapsed_table_borders(&mut self, builder: &mut DisplayListBuilder) {
1623 let layout_info = self.fragment.specific_layout_info();
1624 let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
1625 layout_info.as_deref()
1626 else {
1627 return;
1628 };
1629 let mut common =
1630 builder.common_properties(units::LayoutRect::default(), &self.fragment.style());
1631 let radius = wr::BorderRadius::default();
1632 let mut column_sum = Au::zero();
1633 for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
1634 let mut row_sum = Au::zero();
1635 for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
1636 let left_border = &table_info.collapsed_borders.x[x][y];
1637 let right_border = &table_info.collapsed_borders.x[x + 1][y];
1638 let top_border = &table_info.collapsed_borders.y[y][x];
1639 let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
1640 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1641 left: self.build_border_side(left_border.style_color.clone()),
1642 right: self.build_border_side(right_border.style_color.clone()),
1643 top: self.build_border_side(top_border.style_color.clone()),
1644 bottom: self.build_border_side(bottom_border.style_color.clone()),
1645 radius,
1646 do_aa: true,
1647 });
1648 let mut border_widths = PhysicalSides::new(
1649 top_border.width,
1650 right_border.width,
1651 bottom_border.width,
1652 left_border.width,
1653 );
1654 let left_adjustment = if x == 0 {
1655 -border_widths.left / 2
1656 } else {
1657 std::mem::take(&mut border_widths.left) / 2
1658 };
1659 let top_adjustment = if y == 0 {
1660 -border_widths.top / 2
1661 } else {
1662 std::mem::take(&mut border_widths.top) / 2
1663 };
1664 let origin =
1665 PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
1666 let size = PhysicalSize::new(
1667 *column_size - left_adjustment + border_widths.right / 2,
1668 *row_size - top_adjustment + border_widths.bottom / 2,
1669 );
1670 let border_rect = PhysicalRect::new(origin, size)
1671 .translate(self.fragment.content_rect().origin.to_vector())
1672 .translate(self.containing_block.origin.to_vector())
1673 .to_webrender();
1674 common.clip_rect = border_rect;
1675 builder.wr().push_border(
1676 &common,
1677 border_rect,
1678 border_widths.to_webrender(),
1679 details,
1680 );
1681 row_sum += *row_size;
1682 }
1683 column_sum += *column_size;
1684 }
1685 }
1686
1687 fn build_border(&mut self, builder: &mut DisplayListBuilder) {
1688 if self.fragment.has_collapsed_borders() {
1689 return;
1692 }
1693
1694 let style = self.fragment.style();
1695 let border = style.get_border();
1696 let border_widths = self.fragment.border.to_webrender();
1697
1698 if border_widths == SideOffsets2D::zero() {
1699 return;
1700 }
1701
1702 let common = builder.common_properties(self.border_rect, &style);
1704 if self.build_border_image(builder, &common, border, border_widths) {
1705 return;
1706 }
1707
1708 let current_color = style.get_inherited_text().clone_color();
1709 let style_color = BorderStyleColor::from_border(border, ¤t_color);
1710 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1711 top: self.build_border_side(style_color.top),
1712 right: self.build_border_side(style_color.right),
1713 bottom: self.build_border_side(style_color.bottom),
1714 left: self.build_border_side(style_color.left),
1715 radius: self.border_radius,
1716 do_aa: true,
1717 });
1718 builder
1719 .wr()
1720 .push_border(&common, self.border_rect, border_widths, details)
1721 }
1722
1723 fn build_border_image(
1725 &self,
1726 builder: &mut DisplayListBuilder,
1727 common: &CommonItemProperties,
1728 border: &Border,
1729 border_widths: SideOffsets2D<f32, LayoutPixel>,
1730 ) -> bool {
1731 let style = self.fragment.style();
1732 let border_style_struct = style.get_border();
1733 let border_image_outset =
1734 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1735 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1736 let border_image_size = border_image_area.size;
1737 let border_image_widths = resolve_border_image_width(
1738 &border_style_struct.border_image_width,
1739 border_widths,
1740 border_image_size,
1741 );
1742 let border_image_repeat = &border_style_struct.border_image_repeat;
1743 let border_image_fill = border_style_struct.border_image_slice.fill;
1744 let border_image_slice = &border_style_struct.border_image_slice.offsets;
1745
1746 let stops = Vec::new();
1747 let mut width = border_image_size.width;
1748 let mut height = border_image_size.height;
1749 let node = self.fragment.base.tag.map(|tag| tag.node);
1750 let source = match builder
1751 .image_resolver
1752 .resolve_image(node, &border.border_image_source)
1753 {
1754 Err(_) => return false,
1755 Ok(ResolvedImage::Image { image, size }) => {
1756 let image_key = match image {
1757 CachedImage::Raster(raster_image) => raster_image.id,
1758 CachedImage::Vector(vector_image) => {
1759 let scale = builder.device_pixel_ratio.get();
1760 let size = Size2D::new(size.width * scale, size.height * scale).to_i32();
1761 node.and_then(|node| {
1762 builder.image_resolver.rasterize_vector_image(
1763 vector_image.id,
1764 size,
1765 node,
1766 vector_image.svg_id,
1767 )
1768 })
1769 .and_then(|rasterized_image| rasterized_image.id)
1770 },
1771 };
1772
1773 let Some(key) = image_key else {
1774 return false;
1775 };
1776
1777 builder.check_if_paintable(
1778 Box2D::from_size(size.cast_unit()),
1779 common.clip_rect,
1780 style.clone_opacity(),
1781 );
1782
1783 builder.mark_is_contentful();
1788
1789 width = size.width;
1790 height = size.height;
1791 let image_rendering = style.clone_image_rendering().to_webrender();
1792 NinePatchBorderSource::Image(key, image_rendering)
1793 },
1794 Ok(ResolvedImage::Gradient(gradient)) => {
1795 match gradient::build(&style, gradient, border_image_size, builder) {
1796 WebRenderGradient::Linear(gradient) => {
1797 NinePatchBorderSource::Gradient(gradient)
1798 },
1799 WebRenderGradient::Radial(gradient) => {
1800 NinePatchBorderSource::RadialGradient(gradient)
1801 },
1802 WebRenderGradient::Conic(gradient) => {
1803 NinePatchBorderSource::ConicGradient(gradient)
1804 },
1805 }
1806 },
1807 };
1808
1809 let size = Size2D::new(width as i32, height as i32);
1810
1811 if size.is_empty() || border_image_size.is_empty() {
1814 return true;
1815 }
1816
1817 let details = BorderDetails::NinePatch(NinePatchBorder {
1818 source,
1819 width: size.width,
1820 height: size.height,
1821 slice: resolve_border_image_slice(border_image_slice, size),
1822 fill: border_image_fill,
1823 repeat_horizontal: border_image_repeat.0.to_webrender(),
1824 repeat_vertical: border_image_repeat.1.to_webrender(),
1825 });
1826 builder.wr().push_border(
1827 common,
1828 border_image_area.to_box2d(),
1829 border_image_widths,
1830 details,
1831 );
1832 builder.wr().push_stops(&stops);
1833 true
1834 }
1835
1836 fn build_outline(&mut self, builder: &mut DisplayListBuilder) {
1837 let style = self.fragment.style();
1838 let outline = style.get_outline();
1839 if outline.outline_style.none_or_hidden() {
1840 return;
1841 }
1842 let width = outline.outline_width.0.to_f32_px();
1843 if width == 0.0 {
1844 return;
1845 }
1846 let offset = outline.outline_offset.to_f32_px() + width;
1855 let outline_rect = self.border_rect.inflate(
1856 offset.max(-self.border_rect.width() / 2.0 + width),
1857 offset.max(-self.border_rect.height() / 2.0 + width),
1858 );
1859 let common = builder.common_properties(outline_rect, &style);
1860 let widths = SideOffsets2D::new_all_same(width);
1861 let border_style = match outline.outline_style {
1862 OutlineStyle::Auto => BorderStyle::Solid,
1865 OutlineStyle::BorderStyle(s) => s,
1866 };
1867 let side = self.build_border_side(BorderStyleColor {
1868 style: border_style,
1869 color: style.resolve_color(&outline.outline_color),
1870 });
1871 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1872 top: side,
1873 right: side,
1874 bottom: side,
1875 left: side,
1876 radius: offset_radii(self.border_radius, SideOffsets2D::new_all_same(offset)),
1877 do_aa: true,
1878 });
1879 builder
1880 .wr()
1881 .push_border(&common, outline_rect, widths, details)
1882 }
1883
1884 fn build_box_shadow(&self, builder: &mut DisplayListBuilder<'_>) {
1885 let style = self.fragment.style();
1886 let box_shadows = &style.get_effects().box_shadow.0;
1887 if box_shadows.is_empty() {
1888 return;
1889 }
1890
1891 for box_shadow in box_shadows.iter().rev() {
1893 let (rect, clip_mode) = if box_shadow.inset {
1894 (*self.padding_rect(), BoxShadowClipMode::Inset)
1895 } else {
1896 (self.border_rect, BoxShadowClipMode::Outset)
1897 };
1898
1899 let offset = LayoutVector2D::new(
1900 box_shadow.base.horizontal.px(),
1901 box_shadow.base.vertical.px(),
1902 );
1903 let spread = box_shadow.spread.px();
1904 let blur = box_shadow.base.blur.px();
1905 let clip_rect = match clip_mode {
1906 BoxShadowClipMode::Inset => rect,
1908 BoxShadowClipMode::Outset => {
1911 let extra_size_from_blur = (blur * 3.0).ceil();
1912 rect.translate(offset)
1913 .inflate(spread, spread)
1914 .inflate(extra_size_from_blur, extra_size_from_blur)
1915 },
1916 };
1917 let common = builder.common_properties(clip_rect, &style);
1918
1919 builder.wr().push_box_shadow(
1920 &common,
1921 rect,
1922 offset,
1923 rgba(style.resolve_color(&box_shadow.base.color)),
1924 blur,
1925 spread,
1926 self.border_radius,
1927 clip_mode,
1928 );
1929 }
1930 }
1931}
1932
1933fn rgba(color: AbsoluteColor) -> wr::ColorF {
1934 let rgba = color.to_color_space(ColorSpace::Srgb);
1935 wr::ColorF::new(
1936 rgba.components.0.clamp(0.0, 1.0),
1937 rgba.components.1.clamp(0.0, 1.0),
1938 rgba.components.2.clamp(0.0, 1.0),
1939 rgba.alpha,
1940 )
1941}
1942
1943fn glyphs(
1944 shaped_text_slices: &[Arc<ShapedTextSlice>],
1945 mut baseline_origin: PhysicalPoint<Au>,
1946 justification_adjustment: Au,
1947 include_whitespace: bool,
1948) -> (Vec<GlyphInstance>, Au) {
1949 let mut glyphs = vec![];
1950 let mut largest_advance = Au::zero();
1951
1952 for shaped_text_slice in shaped_text_slices {
1953 for glyph in shaped_text_slice.glyphs() {
1954 if !shaped_text_slice.is_whitespace() || include_whitespace {
1955 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
1956 let point = LayoutPoint::new(
1957 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
1958 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
1959 );
1960 let glyph_instance = GlyphInstance {
1961 index: glyph.id(),
1962 point,
1963 };
1964 glyphs.push(glyph_instance);
1965 }
1966
1967 if glyph.char_is_word_separator() {
1968 baseline_origin.x += justification_adjustment;
1969 }
1970
1971 let advance = glyph.advance();
1972 baseline_origin.x += advance;
1973 largest_advance.max_assign(advance);
1974 }
1975 }
1976 (glyphs, largest_advance)
1977}
1978
1979fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
1983 let expand = |radius: &mut f32, offset: f32| {
1984 if offset < 0.0 {
1986 *radius = (*radius + offset).max(0.0);
1987 return;
1988 }
1989
1990 if *radius > 0.0 {
1995 *radius += offset;
1996 }
1997 };
1998 if offsets.left != 0.0 {
1999 expand(&mut radii.top_left.width, offsets.left);
2000 expand(&mut radii.bottom_left.width, offsets.left);
2001 }
2002 if offsets.right != 0.0 {
2003 expand(&mut radii.top_right.width, offsets.right);
2004 expand(&mut radii.bottom_right.width, offsets.right);
2005 }
2006 if offsets.top != 0.0 {
2007 expand(&mut radii.top_left.height, offsets.top);
2008 expand(&mut radii.top_right.height, offsets.top);
2009 }
2010 if offsets.bottom != 0.0 {
2011 expand(&mut radii.bottom_right.height, offsets.bottom);
2012 expand(&mut radii.bottom_left.height, offsets.bottom);
2013 }
2014 radii
2015}
2016
2017fn resolve_border_image_outset(
2019 outset: BorderImageOutset,
2020 border: SideOffsets2D<f32, LayoutPixel>,
2021) -> SideOffsets2D<f32, LayoutPixel> {
2022 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
2023 match outset {
2024 NonNegativeLengthOrNumber::Length(length) => length.px(),
2025 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
2026 }
2027 }
2028
2029 SideOffsets2D::new(
2030 image_outset_for_side(outset.0, border.top),
2031 image_outset_for_side(outset.1, border.right),
2032 image_outset_for_side(outset.2, border.bottom),
2033 image_outset_for_side(outset.3, border.left),
2034 )
2035}
2036
2037fn resolve_border_image_width(
2039 width: &BorderImageWidth,
2040 border: SideOffsets2D<f32, LayoutPixel>,
2041 border_area: Size2D<f32, LayoutPixel>,
2042) -> SideOffsets2D<f32, LayoutPixel> {
2043 fn image_width_for_side(
2044 border_image_width: &BorderImageSideWidth,
2045 border_width: f32,
2046 total_length: f32,
2047 ) -> f32 {
2048 match border_image_width {
2049 BorderImageSideWidth::LengthPercentage(v) => {
2050 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
2051 },
2052 BorderImageSideWidth::Number(x) => border_width * x.0,
2053 BorderImageSideWidth::Auto => border_width,
2054 }
2055 }
2056
2057 SideOffsets2D::new(
2058 image_width_for_side(&width.0, border.top, border_area.height),
2059 image_width_for_side(&width.1, border.right, border_area.width),
2060 image_width_for_side(&width.2, border.bottom, border_area.height),
2061 image_width_for_side(&width.3, border.left, border_area.width),
2062 )
2063}
2064
2065fn resolve_border_image_slice(
2067 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
2068 size: Size2D<i32, UnknownUnit>,
2069) -> SideOffsets2D<i32, DevicePixel> {
2070 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
2071 match value.0 {
2072 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
2073 NumberOrPercentage::Number(n) => n.round() as i32,
2074 }
2075 }
2076
2077 SideOffsets2D::new(
2078 resolve_percentage(border_image_slice.0, size.height),
2079 resolve_percentage(border_image_slice.1, size.width),
2080 resolve_percentage(border_image_slice.2, size.height),
2081 resolve_percentage(border_image_slice.3, size.width),
2082 )
2083}
2084
2085pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
2086 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
2094 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
2095 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
2096 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
2097 if f < 1.0 {
2098 radius.top_left *= f;
2099 radius.top_right *= f;
2100 radius.bottom_right *= f;
2101 radius.bottom_left *= f;
2102 }
2103}
2104
2105pub(super) fn compute_margin_box_radius(
2113 radius: wr::BorderRadius,
2114 layout_rect: LayoutSize,
2115 fragment: &BoxFragment,
2116) -> wr::BorderRadius {
2117 let style = fragment.style();
2118 let margin = style.physical_margin();
2119 let adjust_radius = |radius: f32, margin: f32| -> f32 {
2120 if margin <= 0. || (radius / margin) >= 1. {
2121 (radius + margin).max(0.)
2122 } else {
2123 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
2124 }
2125 };
2126 let compute_margin_radius = |radius: LayoutSize,
2127 layout_rect: LayoutSize,
2128 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
2129 -> LayoutSize {
2130 let zero = LengthPercentage::zero();
2131 let width = margin
2132 .width
2133 .auto_is(|| &zero)
2134 .to_used_value(Au::from_f32_px(layout_rect.width));
2135 let height = margin
2136 .height
2137 .auto_is(|| &zero)
2138 .to_used_value(Au::from_f32_px(layout_rect.height));
2139 LayoutSize::new(
2140 adjust_radius(radius.width, width.to_f32_px()),
2141 adjust_radius(radius.height, height.to_f32_px()),
2142 )
2143 };
2144 wr::BorderRadius {
2145 top_left: compute_margin_radius(
2146 radius.top_left,
2147 layout_rect,
2148 Size2D::new(margin.left, margin.top),
2149 ),
2150 top_right: compute_margin_radius(
2151 radius.top_right,
2152 layout_rect,
2153 Size2D::new(margin.right, margin.top),
2154 ),
2155 bottom_left: compute_margin_radius(
2156 radius.bottom_left,
2157 layout_rect,
2158 Size2D::new(margin.left, margin.bottom),
2159 ),
2160 bottom_right: compute_margin_radius(
2161 radius.bottom_right,
2162 layout_rect,
2163 Size2D::new(margin.right, margin.bottom),
2164 ),
2165 }
2166}
2167
2168impl BoxFragment {
2169 fn border_radius(&self) -> BorderRadius {
2170 let style = self.style();
2171 let border = style.get_border();
2172 if border.border_top_left_radius.0.is_zero() &&
2173 border.border_top_right_radius.0.is_zero() &&
2174 border.border_bottom_right_radius.0.is_zero() &&
2175 border.border_bottom_left_radius.0.is_zero()
2176 {
2177 return BorderRadius::zero();
2178 }
2179
2180 let border_rect = self.border_rect();
2181 let resolve =
2182 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2183 let corner = |corner: &style::values::computed::BorderCornerRadius| {
2184 Size2D::new(
2185 resolve(&corner.0.width.0, border_rect.size.width),
2186 resolve(&corner.0.height.0, border_rect.size.height),
2187 )
2188 };
2189
2190 let mut radius = wr::BorderRadius {
2191 top_left: corner(&border.border_top_left_radius),
2192 top_right: corner(&border.border_top_right_radius),
2193 bottom_right: corner(&border.border_bottom_right_radius),
2194 bottom_left: corner(&border.border_bottom_left_radius),
2195 };
2196
2197 normalize_radii(&border_rect.to_webrender(), &mut radius);
2198 radius
2199 }
2200}