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::GlyphStore;
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;
19use servo_config::{pref, prefs};
20use servo_geometry::MaxRect;
21use servo_url::ServoUrl;
22use style::Zero;
23use style::color::{AbsoluteColor, ColorSpace};
24use style::computed_values::background_blend_mode::SingleComputedValue as BackgroundBlendMode;
25use style::computed_values::border_image_outset::T as BorderImageOutset;
26use style::computed_values::text_decoration_style::{
27 T as ComputedTextDecorationStyle, T as TextDecorationStyle,
28};
29use style::dom::OpaqueNode;
30use style::properties::ComputedValues;
31use style::properties::longhands::visibility::computed_value::T as Visibility;
32use style::properties::style_structs::Border;
33use style::values::computed::{
34 BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
35 NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
36};
37use style::values::generics::NonNegative;
38use style::values::generics::color::ColorOrAuto;
39use style::values::generics::rect::Rect as StyleRect;
40use style::values::specified::text::TextDecorationLine;
41use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
42use webrender_api::units::{
43 DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize,
44};
45use webrender_api::{
46 self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
47 ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
48 NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags, PropertyBinding,
49 PropertyBindingKey, RasterSpace, SpatialId, SpatialTreeItemKey, StackingContextFlags, units,
50};
51use wr::units::LayoutVector2D;
52
53use crate::cell::ArcRefCell;
54use crate::context::{ImageResolver, ResolvedImage};
55pub(crate) use crate::display_list::conversions::ToWebRender;
56use crate::display_list::stacking_context::StackingContextSection;
57use crate::fragment_tree::{
58 BackgroundMode, BoxFragment, Fragment, FragmentFlags, FragmentStatus, FragmentTree,
59 SpecificLayoutInfo, Tag, TextFragment,
60};
61use crate::geom::{
62 LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
63};
64use crate::replaced::NaturalSizes;
65use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
66
67mod background;
68mod clip;
69mod conversions;
70mod gradient;
71mod hit_test;
72mod paint_timing_handler;
73mod stacking_context;
74
75use background::BackgroundPainter;
76pub(crate) use hit_test::HitTest;
77pub(crate) use paint_timing_handler::PaintTimingHandler;
78pub(crate) use stacking_context::*;
79
80const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
81
82pub(crate) struct DisplayListBuilder<'a> {
83 current_scroll_node_id: ScrollTreeNodeId,
88
89 current_reference_frame_scroll_node_id: ScrollTreeNodeId,
93
94 current_clip_id: ClipId,
99
100 pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
102
103 pub paint_info: &'a mut PaintDisplayListInfo,
105
106 inspector_highlight: Option<InspectorHighlight>,
111
112 paint_body_background: bool,
116
117 clip_map: Vec<ClipChainId>,
120
121 image_resolver: Arc<ImageResolver>,
123
124 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
126
127 paint_timing_handler: &'a mut PaintTimingHandler,
129
130 reflow_statistics: &'a mut ReflowStatistics,
132}
133
134struct InspectorHighlight {
135 tag: Tag,
137
138 state: Option<HighlightTraversalState>,
143}
144
145struct HighlightTraversalState {
146 content_box: Rect<Au, StyloCSSPixel>,
149
150 spatial_id: SpatialId,
151
152 clip_chain_id: ClipChainId,
153
154 maybe_box_fragment: Option<ArcRefCell<BoxFragment>>,
157}
158
159impl InspectorHighlight {
160 fn for_node(node: OpaqueNode) -> Self {
161 Self {
162 tag: Tag {
163 node,
164 pseudo_element_chain: Default::default(),
166 },
167 state: None,
168 }
169 }
170}
171
172impl DisplayListBuilder<'_> {
173 #[expect(clippy::too_many_arguments)]
174 pub(crate) fn build(
175 stacking_context_tree: &mut StackingContextTree,
176 fragment_tree: &FragmentTree,
177 image_resolver: Arc<ImageResolver>,
178 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
179 highlighted_dom_node: Option<OpaqueNode>,
180 debug: &DiagnosticsLogging,
181 paint_timing_handler: &mut PaintTimingHandler,
182 reflow_statistics: &mut ReflowStatistics,
183 ) -> BuiltDisplayList {
184 let paint_info = &mut stacking_context_tree.paint_info;
186 let pipeline_id = paint_info.pipeline_id;
187 let mut webrender_display_list_builder =
188 webrender_api::DisplayListBuilder::new(pipeline_id);
189 webrender_display_list_builder.begin();
190
191 if debug.display_list {
196 webrender_display_list_builder.dump_serialized_display_list();
197 }
198
199 let _span = profile_traits::trace_span!("DisplayListBuilder::build").entered();
200 let mut builder = DisplayListBuilder {
201 current_scroll_node_id: paint_info.root_reference_frame_id,
202 current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
203 current_clip_id: ClipId::INVALID,
204 webrender_display_list_builder: &mut webrender_display_list_builder,
205 paint_info,
206 inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
207 paint_body_background: true,
208 clip_map: Default::default(),
209 image_resolver,
210 device_pixel_ratio,
211 paint_timing_handler,
212 reflow_statistics,
213 };
214
215 builder.paint_info.caret_property_binding = None;
217
218 builder.add_all_spatial_nodes();
219
220 for clip in stacking_context_tree.clip_store.0.iter() {
221 builder.add_clip_to_display_list(clip);
222 }
223
224 let pipeline_id = builder.paint_info.pipeline_id;
227 let viewport_size = builder.paint_info.viewport_details.size;
228 let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
229 builder.wr().push_hit_test(
230 viewport_rect,
231 ClipChainId::INVALID,
232 SpatialId::root_reference_frame(pipeline_id),
233 PrimitiveFlags::default(),
234 (0, 0), );
236
237 stacking_context_tree
239 .root_stacking_context
240 .build_canvas_background_display_list(&mut builder, fragment_tree);
241 stacking_context_tree
242 .root_stacking_context
243 .build_display_list(&mut builder);
244 builder.paint_dom_inspector_highlight();
245
246 webrender_display_list_builder.end().1
247 }
248
249 fn wr(&mut self) -> &mut wr::DisplayListBuilder {
250 self.webrender_display_list_builder
251 }
252
253 fn pipeline_id(&mut self) -> wr::PipelineId {
254 self.paint_info.pipeline_id
255 }
256
257 fn mark_is_paintable(&mut self) {
258 self.paint_info.is_paintable = true;
259 }
260
261 fn mark_is_contentful(&mut self) {
262 self.paint_info.is_contentful = true;
263 }
264
265 fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
266 self.paint_info.scroll_tree.webrender_id(id)
267 }
268
269 fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
270 match id {
271 ClipId::INVALID => ClipChainId::INVALID,
272 _ => *self
273 .clip_map
274 .get(id.0)
275 .expect("Should never try to get clip before adding it to WebRender display list"),
276 }
277 }
278
279 pub(crate) fn add_all_spatial_nodes(&mut self) {
280 let mut spatial_tree_count = 0;
284 let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
285 let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
286
287 mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
288 mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
289
290 let pipeline_id = self.pipeline_id();
291 let pipeline_tag = ((pipeline_id.0 as u64) << 32) | pipeline_id.1 as u64;
292
293 for node in scroll_tree.nodes.iter().skip(2) {
294 let parent_scroll_node_id = node
295 .parent
296 .expect("Should have already added root reference frame");
297 let parent_spatial_node_id = mapping
298 .get(parent_scroll_node_id.index)
299 .expect("Should add spatial nodes to display list in order");
300
301 spatial_tree_count += 1;
304 let spatial_tree_item_key = SpatialTreeItemKey::new(pipeline_tag, spatial_tree_count);
305
306 mapping.push(match &node.info {
307 SpatialTreeNodeInfo::ReferenceFrame(info) => {
308 let spatial_id = self.wr().push_reference_frame(
309 info.origin,
310 *parent_spatial_node_id,
311 info.transform_style,
312 PropertyBinding::Value(*info.transform.to_transform()),
313 info.kind,
314 spatial_tree_item_key,
315 );
316 self.wr().pop_reference_frame();
317 spatial_id
318 },
319 SpatialTreeNodeInfo::Scroll(info) => {
320 self.wr().define_scroll_frame(
321 *parent_spatial_node_id,
322 info.external_id,
323 info.content_rect,
324 info.clip_rect,
325 LayoutVector2D::zero(), 0, wr::HasScrollLinkedEffect::No,
328 spatial_tree_item_key,
329 )
330 },
331 SpatialTreeNodeInfo::Sticky(info) => {
332 self.wr().define_sticky_frame(
333 *parent_spatial_node_id,
334 info.frame_rect,
335 info.margins,
336 info.vertical_offset_bounds,
337 info.horizontal_offset_bounds,
338 LayoutVector2D::zero(), spatial_tree_item_key,
340 None, )
342 },
343 });
344 }
345
346 scroll_tree.update_mapping(mapping);
347 self.paint_info.scroll_tree = scroll_tree;
348 }
349
350 pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
358 assert_eq!(
359 clip.id.0,
360 self.clip_map.len(),
361 "Clips should be added in order"
362 );
363
364 let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
365 let new_clip_id = if clip.radii.is_zero() {
366 self.wr().define_clip_rect(spatial_id, clip.rect)
367 } else {
368 self.wr().define_clip_rounded_rect(
369 spatial_id,
370 ComplexClipRegion {
371 rect: clip.rect,
372 radii: clip.radii,
373 mode: ClipMode::Clip,
374 },
375 )
376 };
377
378 let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
383 ClipChainId::INVALID => None,
384 parent => Some(parent),
385 };
386 let clip_chain_id = self
387 .wr()
388 .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
389 self.clip_map.push(clip_chain_id);
390 clip_chain_id
391 }
392
393 fn maybe_create_clip(
397 &mut self,
398 radii: wr::BorderRadius,
399 rect: units::LayoutRect,
400 force_clip_creation: bool,
401 ) -> Option<ClipChainId> {
402 if radii.is_zero() && !force_clip_creation {
403 return None;
404 }
405
406 Some(self.add_clip_to_display_list(&Clip {
407 id: ClipId(self.clip_map.len()),
408 radii,
409 rect,
410 parent_scroll_node_id: self.current_scroll_node_id,
411 parent_clip_id: self.current_clip_id,
412 }))
413 }
414
415 fn common_properties(
416 &self,
417 clip_rect: units::LayoutRect,
418 style: &ComputedValues,
419 ) -> wr::CommonItemProperties {
420 wr::CommonItemProperties {
424 clip_rect,
425 spatial_id: self.spatial_id(self.current_scroll_node_id),
426 clip_chain_id: self.clip_chain_id(self.current_clip_id),
427 flags: style.get_webrender_primitive_flags(),
428 }
429 }
430
431 fn paint_dom_inspector_highlight(&mut self) {
433 let Some(highlight) = self
434 .inspector_highlight
435 .take()
436 .and_then(|highlight| highlight.state)
437 else {
438 return;
439 };
440
441 const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
442 r: 0.23,
443 g: 0.7,
444 b: 0.87,
445 a: 0.5,
446 };
447
448 const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
449 r: 0.49,
450 g: 0.3,
451 b: 0.7,
452 a: 0.5,
453 };
454
455 const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
456 r: 0.2,
457 g: 0.2,
458 b: 0.2,
459 a: 0.5,
460 };
461
462 const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
463 r: 1.,
464 g: 0.93,
465 b: 0.,
466 a: 0.5,
467 };
468
469 let content_box = highlight.content_box.to_webrender();
471 let properties = wr::CommonItemProperties {
472 clip_rect: content_box,
473 spatial_id: highlight.spatial_id,
474 clip_chain_id: highlight.clip_chain_id,
475 flags: wr::PrimitiveFlags::default(),
476 };
477
478 self.wr()
479 .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
480
481 if let Some(box_fragment) = highlight.maybe_box_fragment {
483 let mut paint_highlight =
484 |color: webrender_api::ColorF,
485 fragment_relative_bounds: PhysicalRect<Au>,
486 widths: webrender_api::units::LayoutSideOffsets| {
487 if widths.is_zero() {
488 return;
489 }
490
491 let bounds = box_fragment
492 .borrow()
493 .offset_by_containing_block(&fragment_relative_bounds)
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 let box_fragment = box_fragment.borrow();
521 paint_highlight(
522 PADDING_BOX_HIGHLIGHT_COLOR,
523 box_fragment.padding_rect(),
524 box_fragment.padding.to_webrender(),
525 );
526 paint_highlight(
527 BORDER_BOX_HIGHLIGHT_COLOR,
528 box_fragment.border_rect(),
529 box_fragment.border.to_webrender(),
530 );
531 paint_highlight(
532 MARGIN_BOX_HIGHLIGHT_COLOR,
533 box_fragment.margin_rect(),
534 box_fragment.margin.to_webrender(),
535 );
536 }
537 }
538
539 fn check_if_paintable(&mut self, bounds: LayoutRect, clip_rect: LayoutRect, opacity: f32) {
540 if opacity <= 0.0 {
548 return;
549 }
550
551 if self
553 .paint_timing_handler
554 .check_bounding_rect(bounds, clip_rect)
555 {
556 self.mark_is_paintable();
557 }
558 }
559
560 fn check_for_lcp_candidate(
561 &mut self,
562 clip_rect: LayoutRect,
563 bounds: LayoutRect,
564 tag: Option<Tag>,
565 url: Option<ServoUrl>,
566 ) {
567 if !pref!(largest_contentful_paint_enabled) {
568 return;
569 }
570
571 let transform = self
572 .paint_info
573 .scroll_tree
574 .cumulative_node_to_root_transform(self.current_scroll_node_id);
575
576 self.paint_timing_handler
577 .update_lcp_candidate(tag, bounds, clip_rect, transform, url);
578 }
579}
580
581impl InspectorHighlight {
582 fn register_fragment_of_highlighted_dom_node(
583 &mut self,
584 fragment: &Fragment,
585 spatial_id: SpatialId,
586 clip_chain_id: ClipChainId,
587 containing_block: &PhysicalRect<Au>,
588 ) {
589 let state = self.state.get_or_insert(HighlightTraversalState {
590 content_box: Rect::zero(),
591 spatial_id,
592 clip_chain_id,
593 maybe_box_fragment: None,
594 });
595
596 if spatial_id != state.spatial_id {
599 return;
600 }
601
602 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
603 debug_assert_eq!(
604 clip_chain_id, state.clip_chain_id,
605 "Fragments of the same node must either have no clip chain or the same one"
606 );
607 }
608
609 let Some(fragment_relative_rect) = fragment.base().map(|base| base.rect) else {
610 return;
611 };
612 state.maybe_box_fragment = match fragment {
613 Fragment::Box(fragment) | Fragment::Float(fragment) => Some(fragment.clone()),
614 _ => None,
615 };
616
617 state.content_box = state
618 .content_box
619 .union(&fragment_relative_rect.translate(containing_block.origin.to_vector()));
620 }
621}
622
623impl Fragment {
624 pub(crate) fn build_display_list(
625 &self,
626 builder: &mut DisplayListBuilder,
627 containing_block: &PhysicalRect<Au>,
628 section: StackingContextSection,
629 is_hit_test_for_scrollable_overflow: bool,
630 is_collapsed_table_borders: bool,
631 text_decorations: &Arc<Vec<FragmentTextDecoration>>,
632 ) {
633 if let Some(mut base) = self.base_mut() {
634 match base.status {
635 FragmentStatus::New => {
636 builder.reflow_statistics.rebuilt_fragment_count += 1;
637 base.status = FragmentStatus::Clean;
638 },
639 FragmentStatus::StyleChanged => {
640 builder.reflow_statistics.restyle_fragment_count += 1;
641 base.status = FragmentStatus::Clean;
642 },
643 FragmentStatus::Clean => {},
644 }
645 }
646
647 let spatial_id = builder.spatial_id(builder.current_scroll_node_id);
648 let clip_chain_id = builder.clip_chain_id(builder.current_clip_id);
649 if let Some(inspector_highlight) = &mut builder.inspector_highlight {
650 if self.tag() == Some(inspector_highlight.tag) {
651 inspector_highlight.register_fragment_of_highlighted_dom_node(
652 self,
653 spatial_id,
654 clip_chain_id,
655 containing_block,
656 );
657 }
658 }
659
660 match self {
661 Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
662 let box_fragment = &*box_fragment.borrow();
663 match box_fragment.style().get_inherited_box().visibility {
664 Visibility::Visible => BuilderForBoxFragment::new(
665 box_fragment,
666 containing_block,
667 is_hit_test_for_scrollable_overflow,
668 is_collapsed_table_borders,
669 )
670 .build(builder, section),
671 Visibility::Hidden => (),
672 Visibility::Collapse => (),
673 }
674 },
675 Fragment::AbsoluteOrFixedPositioned(_) | Fragment::Positioning(_) => {},
676 Fragment::Image(image) => {
677 let image = image.borrow();
678 let style = image.base.style();
679 match style.get_inherited_box().visibility {
680 Visibility::Visible => {
681 let image_rendering =
682 style.get_inherited_box().image_rendering.to_webrender();
683 let rect = image
684 .base
685 .rect
686 .translate(containing_block.origin.to_vector())
687 .to_webrender();
688 let clip = image
689 .clip
690 .translate(containing_block.origin.to_vector())
691 .to_webrender();
692 let common = builder.common_properties(clip, &style);
693
694 if let Some(image_key) = image.image_key {
695 builder.wr().push_image(
696 &common,
697 rect,
698 image_rendering,
699 wr::AlphaType::PremultipliedAlpha,
700 image_key,
701 wr::ColorF::WHITE,
702 );
703
704 builder.check_if_paintable(
705 rect,
706 common.clip_rect,
707 style.clone_opacity(),
708 );
709
710 builder.mark_is_contentful();
714
715 if !image.showing_broken_image_icon {
717 builder.check_for_lcp_candidate(
718 common.clip_rect,
719 rect,
720 image.base.tag,
721 image.url.clone(),
722 );
723 }
724 }
725
726 if image.showing_broken_image_icon {
727 self.build_display_list_for_broken_image_border(
728 builder,
729 containing_block,
730 &common,
731 );
732 }
733 },
734 Visibility::Hidden => (),
735 Visibility::Collapse => (),
736 }
737 },
738 Fragment::IFrame(iframe) => {
739 let iframe = iframe.borrow();
740 let style = iframe.base.style();
741 match style.get_inherited_box().visibility {
742 Visibility::Visible => {
743 let rect = iframe
744 .base
745 .rect
746 .translate(containing_block.origin.to_vector());
747
748 let common = builder.common_properties(rect.to_webrender(), &style);
749 builder.wr().push_iframe(
750 rect.to_webrender(),
751 common.clip_rect,
752 &wr::SpaceAndClipInfo {
753 spatial_id: common.spatial_id,
754 clip_chain_id: common.clip_chain_id,
755 },
756 iframe.pipeline_id.into(),
757 true,
758 );
759 builder.check_if_paintable(
764 rect.to_webrender(),
765 common.clip_rect,
766 style.clone_opacity(),
767 );
768 },
769 Visibility::Hidden => (),
770 Visibility::Collapse => (),
771 }
772 },
773 Fragment::Text(text) => {
774 let text = &*text.borrow();
775 match text.base.style().get_inherited_box().visibility {
776 Visibility::Visible => self.build_display_list_for_text_fragment(
777 text,
778 builder,
779 containing_block,
780 text_decorations,
781 ),
782 Visibility::Hidden => (),
783 Visibility::Collapse => (),
784 }
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.total_characters();
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 let common = builder.common_properties(self.border_rect, &style);
1698 if self.build_border_image(builder, &common, border, border_widths) {
1699 return;
1700 }
1701
1702 let current_color = style.get_inherited_text().clone_color();
1703 let style_color = BorderStyleColor::from_border(border, ¤t_color);
1704 let details = wr::BorderDetails::Normal(wr::NormalBorder {
1705 top: self.build_border_side(style_color.top),
1706 right: self.build_border_side(style_color.right),
1707 bottom: self.build_border_side(style_color.bottom),
1708 left: self.build_border_side(style_color.left),
1709 radius: self.border_radius,
1710 do_aa: true,
1711 });
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 common: &CommonItemProperties,
1722 border: &Border,
1723 border_widths: SideOffsets2D<f32, LayoutPixel>,
1724 ) -> bool {
1725 let style = self.fragment.style();
1726 let border_style_struct = style.get_border();
1727 let border_image_outset =
1728 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
1729 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
1730 let border_image_size = border_image_area.size;
1731 let border_image_widths = resolve_border_image_width(
1732 &border_style_struct.border_image_width,
1733 border_widths,
1734 border_image_size,
1735 );
1736 let border_image_repeat = &border_style_struct.border_image_repeat;
1737 let border_image_fill = border_style_struct.border_image_slice.fill;
1738 let border_image_slice = &border_style_struct.border_image_slice.offsets;
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 let common = builder.common_properties(MaxRect::max_rect(), &style);
1887 for box_shadow in box_shadows.iter().rev() {
1888 let (rect, clip_mode) = if box_shadow.inset {
1889 (*self.padding_rect(), BoxShadowClipMode::Inset)
1890 } else {
1891 (self.border_rect, BoxShadowClipMode::Outset)
1892 };
1893
1894 builder.wr().push_box_shadow(
1895 &common,
1896 rect,
1897 LayoutVector2D::new(
1898 box_shadow.base.horizontal.px(),
1899 box_shadow.base.vertical.px(),
1900 ),
1901 rgba(style.resolve_color(&box_shadow.base.color)),
1902 box_shadow.base.blur.px(),
1903 box_shadow.spread.px(),
1904 self.border_radius,
1905 clip_mode,
1906 );
1907 }
1908 }
1909}
1910
1911fn rgba(color: AbsoluteColor) -> wr::ColorF {
1912 let rgba = color.to_color_space(ColorSpace::Srgb);
1913 wr::ColorF::new(
1914 rgba.components.0.clamp(0.0, 1.0),
1915 rgba.components.1.clamp(0.0, 1.0),
1916 rgba.components.2.clamp(0.0, 1.0),
1917 rgba.alpha,
1918 )
1919}
1920
1921fn glyphs(
1922 glyph_runs: &[Arc<GlyphStore>],
1923 mut baseline_origin: PhysicalPoint<Au>,
1924 justification_adjustment: Au,
1925 include_whitespace: bool,
1926) -> (Vec<GlyphInstance>, Au) {
1927 let mut glyphs = vec![];
1928 let mut largest_advance = Au::zero();
1929
1930 for run in glyph_runs {
1931 for glyph in run.glyphs() {
1932 if !run.is_whitespace() || include_whitespace {
1933 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
1934 let point = LayoutPoint::new(
1935 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
1936 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
1937 );
1938 let glyph_instance = GlyphInstance {
1939 index: glyph.id(),
1940 point,
1941 };
1942 glyphs.push(glyph_instance);
1943 }
1944
1945 if glyph.char_is_word_separator() {
1946 baseline_origin.x += justification_adjustment;
1947 }
1948
1949 let advance = glyph.advance();
1950 baseline_origin.x += advance;
1951 largest_advance.max_assign(advance);
1952 }
1953 }
1954 (glyphs, largest_advance)
1955}
1956
1957fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
1961 let expand = |radius: &mut f32, offset: f32| {
1962 if offset < 0.0 {
1964 *radius = (*radius + offset).max(0.0);
1965 return;
1966 }
1967
1968 if *radius > 0.0 {
1973 *radius += offset;
1974 }
1975 };
1976 if offsets.left != 0.0 {
1977 expand(&mut radii.top_left.width, offsets.left);
1978 expand(&mut radii.bottom_left.width, offsets.left);
1979 }
1980 if offsets.right != 0.0 {
1981 expand(&mut radii.top_right.width, offsets.right);
1982 expand(&mut radii.bottom_right.width, offsets.right);
1983 }
1984 if offsets.top != 0.0 {
1985 expand(&mut radii.top_left.height, offsets.top);
1986 expand(&mut radii.top_right.height, offsets.top);
1987 }
1988 if offsets.bottom != 0.0 {
1989 expand(&mut radii.bottom_right.height, offsets.bottom);
1990 expand(&mut radii.bottom_left.height, offsets.bottom);
1991 }
1992 radii
1993}
1994
1995fn resolve_border_image_outset(
1997 outset: BorderImageOutset,
1998 border: SideOffsets2D<f32, LayoutPixel>,
1999) -> SideOffsets2D<f32, LayoutPixel> {
2000 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
2001 match outset {
2002 NonNegativeLengthOrNumber::Length(length) => length.px(),
2003 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
2004 }
2005 }
2006
2007 SideOffsets2D::new(
2008 image_outset_for_side(outset.0, border.top),
2009 image_outset_for_side(outset.1, border.right),
2010 image_outset_for_side(outset.2, border.bottom),
2011 image_outset_for_side(outset.3, border.left),
2012 )
2013}
2014
2015fn resolve_border_image_width(
2017 width: &BorderImageWidth,
2018 border: SideOffsets2D<f32, LayoutPixel>,
2019 border_area: Size2D<f32, LayoutPixel>,
2020) -> SideOffsets2D<f32, LayoutPixel> {
2021 fn image_width_for_side(
2022 border_image_width: &BorderImageSideWidth,
2023 border_width: f32,
2024 total_length: f32,
2025 ) -> f32 {
2026 match border_image_width {
2027 BorderImageSideWidth::LengthPercentage(v) => {
2028 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
2029 },
2030 BorderImageSideWidth::Number(x) => border_width * x.0,
2031 BorderImageSideWidth::Auto => border_width,
2032 }
2033 }
2034
2035 SideOffsets2D::new(
2036 image_width_for_side(&width.0, border.top, border_area.height),
2037 image_width_for_side(&width.1, border.right, border_area.width),
2038 image_width_for_side(&width.2, border.bottom, border_area.height),
2039 image_width_for_side(&width.3, border.left, border_area.width),
2040 )
2041}
2042
2043fn resolve_border_image_slice(
2045 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
2046 size: Size2D<i32, UnknownUnit>,
2047) -> SideOffsets2D<i32, DevicePixel> {
2048 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
2049 match value.0 {
2050 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
2051 NumberOrPercentage::Number(n) => n.round() as i32,
2052 }
2053 }
2054
2055 SideOffsets2D::new(
2056 resolve_percentage(border_image_slice.0, size.height),
2057 resolve_percentage(border_image_slice.1, size.width),
2058 resolve_percentage(border_image_slice.2, size.height),
2059 resolve_percentage(border_image_slice.3, size.width),
2060 )
2061}
2062
2063pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
2064 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
2072 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
2073 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
2074 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
2075 if f < 1.0 {
2076 radius.top_left *= f;
2077 radius.top_right *= f;
2078 radius.bottom_right *= f;
2079 radius.bottom_left *= f;
2080 }
2081}
2082
2083pub(super) fn compute_margin_box_radius(
2091 radius: wr::BorderRadius,
2092 layout_rect: LayoutSize,
2093 fragment: &BoxFragment,
2094) -> wr::BorderRadius {
2095 let style = fragment.style();
2096 let margin = style.physical_margin();
2097 let adjust_radius = |radius: f32, margin: f32| -> f32 {
2098 if margin <= 0. || (radius / margin) >= 1. {
2099 (radius + margin).max(0.)
2100 } else {
2101 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
2102 }
2103 };
2104 let compute_margin_radius = |radius: LayoutSize,
2105 layout_rect: LayoutSize,
2106 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
2107 -> LayoutSize {
2108 let zero = LengthPercentage::zero();
2109 let width = margin
2110 .width
2111 .auto_is(|| &zero)
2112 .to_used_value(Au::from_f32_px(layout_rect.width));
2113 let height = margin
2114 .height
2115 .auto_is(|| &zero)
2116 .to_used_value(Au::from_f32_px(layout_rect.height));
2117 LayoutSize::new(
2118 adjust_radius(radius.width, width.to_f32_px()),
2119 adjust_radius(radius.height, height.to_f32_px()),
2120 )
2121 };
2122 wr::BorderRadius {
2123 top_left: compute_margin_radius(
2124 radius.top_left,
2125 layout_rect,
2126 Size2D::new(margin.left, margin.top),
2127 ),
2128 top_right: compute_margin_radius(
2129 radius.top_right,
2130 layout_rect,
2131 Size2D::new(margin.right, margin.top),
2132 ),
2133 bottom_left: compute_margin_radius(
2134 radius.bottom_left,
2135 layout_rect,
2136 Size2D::new(margin.left, margin.bottom),
2137 ),
2138 bottom_right: compute_margin_radius(
2139 radius.bottom_right,
2140 layout_rect,
2141 Size2D::new(margin.right, margin.bottom),
2142 ),
2143 }
2144}
2145
2146impl BoxFragment {
2147 fn border_radius(&self) -> BorderRadius {
2148 let style = self.style();
2149 let border = style.get_border();
2150 if border.border_top_left_radius.0.is_zero() &&
2151 border.border_top_right_radius.0.is_zero() &&
2152 border.border_bottom_right_radius.0.is_zero() &&
2153 border.border_bottom_left_radius.0.is_zero()
2154 {
2155 return BorderRadius::zero();
2156 }
2157
2158 let border_rect = self.border_rect();
2159 let resolve =
2160 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2161 let corner = |corner: &style::values::computed::BorderCornerRadius| {
2162 Size2D::new(
2163 resolve(&corner.0.width.0, border_rect.size.width),
2164 resolve(&corner.0.height.0, border_rect.size.height),
2165 )
2166 };
2167
2168 let mut radius = wr::BorderRadius {
2169 top_left: corner(&border.border_top_left_radius),
2170 top_right: corner(&border.border_top_right_radius),
2171 bottom_right: corner(&border.border_bottom_right_radius),
2172 bottom_left: corner(&border.border_bottom_left_radius),
2173 };
2174
2175 normalize_radii(&border_rect.to_webrender(), &mut radius);
2176 radius
2177 }
2178}