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