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