1use std::cell::{OnceCell, RefCell};
6use std::sync::atomic::Ordering;
7use std::sync::{Arc, LazyLock};
8
9use app_units::{AU_PER_PX, Au};
10use clip::Clip;
11pub(crate) use clip::ClipId;
12use euclid::{Box2D, Point2D, Rect, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
13use fonts::{FontMetrics, ShapedTextSlice};
14use gradient::WebRenderGradient;
15use layout_api::ReflowStatistics;
16use paint_api::display_list::{PaintDisplayListInfo, SpatialTreeNodeInfo};
17use servo_arc::Arc as ServoArc;
18use servo_base::cross_process_instant::CrossProcessInstant;
19use servo_base::id::{PipelineId, ScrollTreeNodeId};
20use servo_config::opts::{DiagnosticsLogging, DiagnosticsLoggingOption};
21use servo_config::{pref, prefs};
22use servo_url::ServoUrl;
23use style::Zero;
24use style::color::{AbsoluteColor, ColorSpace};
25use style::computed_values::background_blend_mode::SingleComputedValue as BackgroundBlendMode;
26use style::computed_values::border_image_outset::T as BorderImageOutset;
27use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
28use style::computed_values::overflow_x::T as ComputedOverflow;
29use style::computed_values::text_decoration_style::T as ComputedTextDecorationStyle;
30use style::computed_values::text_decoration_thickness::T as TextDecorationThickness;
31use style::dom::OpaqueNode;
32use style::properties::ComputedValues;
33use style::properties::longhands::visibility::computed_value::T as Visibility;
34use style::properties::style_structs::Border;
35use style::values::computed::basic_shape::ClipPath as ComputedClipPath;
36use style::values::computed::{
37 BorderImageSideWidth, BorderImageWidth, BorderStyle, LengthPercentage,
38 NonNegativeLengthOrNumber, NumberOrPercentage, OutlineStyle,
39};
40use style::values::generics::NonNegative;
41use style::values::generics::color::ColorOrAuto;
42use style::values::generics::rect::Rect as StyleRect;
43use style::values::specified::text::TextDecorationLine;
44use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
45use webrender_api::units::{
46 DeviceIntSize, DevicePixel, LayoutPixel, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize,
47};
48use webrender_api::{
49 self as wr, BorderDetails, BorderRadius, BorderSide, BoxShadowClipMode, BuiltDisplayList,
50 ClipChainId, ClipMode, ColorF, CommonItemProperties, ComplexClipRegion, GlyphInstance,
51 MixBlendMode, NinePatchBorder, NinePatchBorderSource, NormalBorder, PrimitiveFlags,
52 PropertyBinding, PropertyBindingKey, RasterSpace, SpatialId, StackingContextFlags,
53 TransformStyle, units,
54};
55use wr::units::LayoutVector2D;
56
57use crate::context::{ImageResolver, ResolvedImage};
58use crate::display_list::background::BackgroundPainter;
59use crate::display_list::conversions::FilterToWebRender;
60pub(crate) use crate::display_list::conversions::ToWebRender;
61use crate::display_list::paint_traversal::{PaintTraversal, PaintTraversalHandler, TraversalState};
62use crate::fragment_tree::{
63 BackgroundMode, BaseFragment, BoxFragment, BoxFragmentWithStyle,
64 CumulativeContentRectCalculation, Fragment, FragmentFlags, FragmentStatus, FragmentTree,
65 IFrameFragment, ImageFragment, PositioningFragment, SpecificLayoutInfo, Tag, TextFragment,
66};
67use crate::geom::{
68 LengthPercentageOrAuto, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize,
69};
70use crate::replaced::NaturalSizes;
71use crate::style_ext::{BorderStyleColor, ComputedValuesExt};
72
73mod background;
74mod clip;
75mod conversions;
76mod gradient;
77mod hit_test;
78mod paint_timing_handler;
79mod paint_traversal;
80mod painted_region;
81mod stacking_context;
82
83pub(crate) use hit_test::{ClosestFragmentSearch, HitTest};
84pub(crate) use paint_timing_handler::{
85 ContainerTimingRootChain, ContainerTimingRoots, PaintTimingHandler,
86};
87pub(crate) use stacking_context::*;
88
89const INSERTION_POINT_LOGICAL_WIDTH: Au = Au(AU_PER_PX);
90
91static UNFOCUSED_SELECTION_COLOR: LazyLock<AbsoluteColor> =
96 LazyLock::new(|| AbsoluteColor::srgb_legacy(200, 200, 200, 1.0));
97
98pub(crate) struct DisplayListBuilder<'a> {
99 fragment_tree: &'a FragmentTree,
101
102 current_reference_frame_scroll_node_id: ScrollTreeNodeId,
107
108 pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
110
111 pub paint_info: &'a mut PaintDisplayListInfo,
113
114 inspector_highlight: Option<InspectorHighlight>,
119
120 paint_body_background: bool,
124
125 clip_map: Vec<ClipChainId>,
128
129 image_resolver: Arc<ImageResolver>,
131
132 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
134
135 paint_timing_handler: &'a mut PaintTimingHandler,
137
138 reflow_statistics: &'a mut ReflowStatistics,
140
141 largest_contentful_paint_enabled: bool,
143
144 container_timing_enabled: bool,
146
147 shell_background_color: AbsoluteColor,
149
150 frame_focused: bool,
153
154 glyph_buffer: Vec<GlyphInstance>,
157}
158
159struct InspectorHighlight {
160 tag: Tag,
162
163 state: Option<HighlightTraversalState>,
168}
169
170struct HighlightTraversalState {
171 content_box: Rect<Au, StyloCSSPixel>,
174
175 spatial_id: SpatialId,
176
177 clip_chain_id: ClipChainId,
178
179 maybe_box_fragment: Option<Arc<BoxFragment>>,
182}
183
184impl InspectorHighlight {
185 fn for_node(node: OpaqueNode) -> Self {
186 Self {
187 tag: Tag {
188 node,
189 pseudo_element_chain: Default::default(),
191 },
192 state: None,
193 }
194 }
195}
196
197impl DisplayListBuilder<'_> {
198 #[expect(clippy::too_many_arguments)]
199 pub(crate) fn build(
200 stacking_context_tree: &mut StackingContextTree,
201 fragment_tree: &FragmentTree,
202 image_resolver: Arc<ImageResolver>,
203 device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
204 highlighted_dom_node: Option<OpaqueNode>,
205 debug: &DiagnosticsLogging,
206 paint_timing_handler: &mut PaintTimingHandler,
207 reflow_statistics: &mut ReflowStatistics,
208 frame_focused: bool,
209 ) -> BuiltDisplayList {
210 let paint_info = &mut stacking_context_tree.paint_info;
212 let pipeline_id = paint_info.pipeline_id;
213 let mut webrender_display_list_builder =
214 webrender_api::DisplayListBuilder::new(pipeline_id);
215 webrender_display_list_builder.begin();
216
217 if debug.is_enabled(DiagnosticsLoggingOption::DisplayList) {
222 webrender_display_list_builder.dump_serialized_display_list();
223 }
224
225 let shell_background_color = {
226 let default_background_color = pref!(shell_background_color_rgba);
227 AbsoluteColor::new(
228 ColorSpace::Srgb,
229 default_background_color[0] as f32,
230 default_background_color[1] as f32,
231 default_background_color[2] as f32,
232 default_background_color[3] as f32,
233 )
234 .into_srgb_legacy()
235 };
236
237 let _span = profile_traits::trace_span!("DisplayListBuilder::build").entered();
238 let mut builder = DisplayListBuilder {
239 fragment_tree,
240 current_reference_frame_scroll_node_id: paint_info.root_reference_frame_id,
241 webrender_display_list_builder: &mut webrender_display_list_builder,
242 paint_info,
243 inspector_highlight: highlighted_dom_node.map(InspectorHighlight::for_node),
244 paint_body_background: true,
245 clip_map: Default::default(),
246 image_resolver,
247 device_pixel_ratio,
248 paint_timing_handler,
249 reflow_statistics,
250 largest_contentful_paint_enabled: pref!(largest_contentful_paint_enabled),
251 container_timing_enabled: pref!(container_timing_enabled),
252 shell_background_color,
253 frame_focused,
254 glyph_buffer: Vec::new(),
255 };
256
257 builder.paint_info.caret_property_binding = None;
259
260 builder.add_all_spatial_nodes();
261
262 for clip in stacking_context_tree.clip_store.0.iter() {
263 builder.add_clip_to_display_list(clip);
264 }
265
266 let pipeline_id = builder.paint_info.pipeline_id;
269 let viewport_size = builder.paint_info.viewport_details.size;
270 let viewport_rect = LayoutRect::from_size(viewport_size.cast_unit());
271 builder.wr().push_hit_test(
272 viewport_rect,
273 ClipChainId::INVALID,
274 SpatialId::root_reference_frame(pipeline_id),
275 PrimitiveFlags::default(),
276 (0, 0), );
278
279 PaintTraversal::traverse(&stacking_context_tree.root_stacking_context, &mut builder);
280 builder.paint_dom_inspector_highlight();
281
282 webrender_display_list_builder.end().1
283 }
284
285 fn wr(&mut self) -> &mut wr::DisplayListBuilder {
286 self.webrender_display_list_builder
287 }
288
289 fn pipeline_id(&self) -> wr::PipelineId {
290 self.paint_info.pipeline_id
291 }
292
293 fn mark_is_paintable(&mut self) {
294 self.paint_timing_handler.mark_document_is_paintable();
295 }
296
297 fn mark_is_contentful(&mut self) {
298 self.paint_timing_handler.mark_document_is_contentful();
299 }
300
301 fn spatial_id(&self, id: ScrollTreeNodeId) -> SpatialId {
302 self.paint_info.scroll_tree.webrender_id(id)
303 }
304
305 fn clip_chain_id(&self, id: ClipId) -> ClipChainId {
306 match id {
307 ClipId::INVALID => ClipChainId::INVALID,
308 _ => *self
309 .clip_map
310 .get(id.0)
311 .expect("Should never try to get clip before adding it to WebRender display list"),
312 }
313 }
314
315 pub(crate) fn add_all_spatial_nodes(&mut self) {
316 let mut scroll_tree = std::mem::take(&mut self.paint_info.scroll_tree);
320 let mut mapping = Vec::with_capacity(scroll_tree.nodes.len());
321
322 mapping.push(SpatialId::root_reference_frame(self.pipeline_id()));
323 mapping.push(SpatialId::root_scroll_node(self.pipeline_id()));
324
325 for node in scroll_tree.nodes.iter().skip(2) {
326 let parent_scroll_node_id = node
327 .parent
328 .expect("Should have already added root reference frame");
329 let parent_spatial_node_id = mapping
330 .get(parent_scroll_node_id.index)
331 .expect("Should add spatial nodes to display list in order");
332
333 mapping.push(match &node.info {
334 SpatialTreeNodeInfo::ReferenceFrame(info) => {
335 let spatial_id = self.wr().push_reference_frame(
336 info.origin,
337 *parent_spatial_node_id,
338 info.transform_style,
339 PropertyBinding::Value(*info.transform.to_transform()),
340 info.kind,
341 );
342 self.wr().pop_reference_frame();
343 spatial_id
344 },
345 SpatialTreeNodeInfo::Scroll(info) => {
346 self.wr().define_scroll_frame(
347 *parent_spatial_node_id,
348 info.external_id,
349 info.content_rect,
350 info.clip_rect,
351 LayoutVector2D::zero(), 0, wr::HasScrollLinkedEffect::No,
354 )
355 },
356 SpatialTreeNodeInfo::Sticky(info) => {
357 self.wr().define_sticky_frame(
358 *parent_spatial_node_id,
359 info.frame_rect,
360 info.margins,
361 info.vertical_offset_bounds,
362 info.horizontal_offset_bounds,
363 LayoutVector2D::zero(), None, )
366 },
367 });
368 }
369
370 scroll_tree.update_mapping(mapping);
371 self.paint_info.scroll_tree = scroll_tree;
372 }
373
374 pub(crate) fn add_clip_to_display_list(&mut self, clip: &Clip) -> ClipChainId {
382 assert_eq!(
383 clip.id.0,
384 self.clip_map.len(),
385 "Clips should be added in order"
386 );
387
388 let spatial_id = self.spatial_id(clip.parent_scroll_node_id);
389 let new_clip_id = if clip.radii.is_zero() {
390 self.wr().define_clip_rect(spatial_id, clip.rect)
391 } else {
392 self.wr().define_clip_rounded_rect(
393 spatial_id,
394 ComplexClipRegion {
395 rect: clip.rect,
396 radii: clip.radii,
397 mode: ClipMode::Clip,
398 },
399 )
400 };
401
402 let parent_clip_chain_id = match self.clip_chain_id(clip.parent_clip_id) {
407 ClipChainId::INVALID => None,
408 parent => Some(parent),
409 };
410 let clip_chain_id = self
411 .wr()
412 .define_clip_chain(parent_clip_chain_id, [new_clip_id]);
413 self.clip_map.push(clip_chain_id);
414 clip_chain_id
415 }
416
417 fn maybe_create_clip(
421 &mut self,
422 state: &TraversalState,
423 radii: wr::BorderRadius,
424 rect: units::LayoutRect,
425 force_clip_creation: bool,
426 ) -> Option<ClipChainId> {
427 if radii.is_zero() && !force_clip_creation {
428 return None;
429 }
430
431 Some(self.add_clip_to_display_list(&Clip {
432 id: ClipId(self.clip_map.len()),
433 radii,
434 rect,
435 parent_scroll_node_id: state.spatial_id,
436 parent_clip_id: state.clip_id,
437 }))
438 }
439
440 fn push_webrender_stacking_context_for_transform_if_necessary(
441 &mut self,
442 stacking_context: &StackingContext,
443 ) -> bool {
444 let StackingContextFragments::Fragment(fragment) = &stacking_context.fragment else {
445 return false;
446 };
447
448 let style = fragment.style();
449 let transform_style = style
450 .used_transform_style(fragment.base.flags)
451 .to_webrender();
452
453 if !style.has_effective_transform_or_perspective(FragmentFlags::empty()) &&
454 (transform_style == TransformStyle::Flat &&
455 !stacking_context.participates_in_a_3d_rendering_context)
456 {
457 return false;
458 }
459
460 let clip_chain_id = match stacking_context.clip_id {
465 ClipId::INVALID => None,
466 clip_id => Some(self.clip_chain_id(clip_id)),
467 };
468 let spatial_id = self.spatial_id(stacking_context.scroll_tree_node_id);
469
470 self.wr().push_stacking_context(
471 spatial_id,
472 style.webrender_primitive_flags(),
473 clip_chain_id,
474 transform_style,
475 MixBlendMode::Normal,
476 &[], &[], wr::RasterSpace::Screen,
479 StackingContextFlags::empty(),
480 None, );
482 true
483 }
484
485 fn push_webrender_stacking_context_if_necessary(
486 &mut self,
487 stacking_context: &StackingContext,
488 ) -> bool {
489 if stacking_context.context_type == StackingContextType::StackingContainer {
490 return false;
491 }
492
493 let mut is_blend_container = stacking_context.children.iter().any(|child| {
494 child.fragment().is_some_and(|fragment| {
495 fragment.style().get_mix_blend_mode() != &ComputedMixBlendMode::Normal
496 })
497 });
498
499 let mix_blend_mode;
500 let mut filters: Vec<_>;
501 let mut stacking_context_flags = StackingContextFlags::empty();
502 match &stacking_context.fragment {
503 StackingContextFragments::Fragment(fragment) => {
504 let style = fragment.style();
505 let effects = style.get_effects();
506
507 mix_blend_mode = effects.mix_blend_mode.to_webrender();
508
509 is_blend_container &= !fragment.base.flags.contains(FragmentFlags::IS_ROOT_ELEMENT);
516
517 if !is_blend_container &&
520 effects.filter.0.is_empty() &&
521 effects.backdrop_filter.0.is_empty() &&
522 effects.opacity == 1.0 &&
523 effects.mix_blend_mode == ComputedMixBlendMode::Normal &&
524 style.get_svg().clip_path == ComputedClipPath::None
525 {
526 return false;
527 }
528
529 let current_color = &style.get_inherited_text().color;
531 filters = effects
532 .filter
533 .0
534 .iter()
535 .map(|filter| FilterToWebRender::to_webrender(filter, current_color))
536 .collect();
537 if effects.opacity != 1.0 {
538 filters.push(wr::FilterOp::Opacity(
539 effects.opacity.into(),
540 effects.opacity,
541 ));
542 }
543 },
544 StackingContextFragments::Root if is_blend_container => {
547 mix_blend_mode = MixBlendMode::Normal;
548 filters = Vec::new();
549 },
550 _ => return false,
551 };
552
553 if is_blend_container {
554 stacking_context_flags.insert(StackingContextFlags::IS_BLEND_CONTAINER);
555 }
556
557 let clip_chain_id = match stacking_context.clip_id {
562 ClipId::INVALID => None,
563 clip_id => Some(self.clip_chain_id(clip_id)),
564 };
565 let spatial_id = self.spatial_id(stacking_context.scroll_tree_node_id);
566
567 self.wr().push_stacking_context(
568 spatial_id,
569 PrimitiveFlags::default(),
570 clip_chain_id,
571 TransformStyle::Flat,
572 mix_blend_mode,
573 &filters,
574 &[], wr::RasterSpace::Screen,
576 stacking_context_flags,
577 None, );
579
580 true
581 }
582
583 fn common_properties(
584 &self,
585 state: &TraversalState,
586 clip_rect: units::LayoutRect,
587 style: &ComputedValues,
588 ) -> wr::CommonItemProperties {
589 wr::CommonItemProperties {
593 clip_rect,
594 spatial_id: self.spatial_id(state.spatial_id),
595 clip_chain_id: self.clip_chain_id(state.clip_id),
596 flags: style.webrender_primitive_flags(),
597 }
598 }
599
600 fn paint_dom_inspector_highlight(&mut self) {
602 let Some(highlight) = self
603 .inspector_highlight
604 .take()
605 .and_then(|highlight| highlight.state)
606 else {
607 return;
608 };
609
610 const CONTENT_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
611 r: 0.23,
612 g: 0.7,
613 b: 0.87,
614 a: 0.5,
615 };
616
617 const PADDING_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
618 r: 0.49,
619 g: 0.3,
620 b: 0.7,
621 a: 0.5,
622 };
623
624 const BORDER_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
625 r: 0.2,
626 g: 0.2,
627 b: 0.2,
628 a: 0.5,
629 };
630
631 const MARGIN_BOX_HIGHLIGHT_COLOR: webrender_api::ColorF = webrender_api::ColorF {
632 r: 1.,
633 g: 0.93,
634 b: 0.,
635 a: 0.5,
636 };
637
638 let content_box = highlight.content_box.to_webrender();
640 let properties = wr::CommonItemProperties {
641 clip_rect: content_box,
642 spatial_id: highlight.spatial_id,
643 clip_chain_id: highlight.clip_chain_id,
644 flags: wr::PrimitiveFlags::default(),
645 };
646
647 self.wr()
648 .push_rect(&properties, content_box, CONTENT_BOX_HIGHLIGHT_COLOR);
649
650 if let Some(box_fragment) = highlight.maybe_box_fragment {
652 let mut paint_highlight =
653 |color: webrender_api::ColorF,
654 fragment_relative_bounds: PhysicalRect<Au>,
655 widths: webrender_api::units::LayoutSideOffsets| {
656 if widths.is_zero() {
657 return;
658 }
659
660 let bounds = box_fragment
661 .offset_by_cumulative_content_offset(
662 &fragment_relative_bounds,
663 CumulativeContentRectCalculation::AlreadyDoneWithStackingContextTree,
664 )
665 .to_webrender();
666
667 let border_style = wr::BorderSide {
669 color,
670 style: wr::BorderStyle::Solid,
671 };
672
673 let details = wr::BorderDetails::Normal(wr::NormalBorder {
674 top: border_style,
675 right: border_style,
676 bottom: border_style,
677 left: border_style,
678 radius: webrender_api::BorderRadius::default(),
679 do_aa: true,
680 });
681
682 let common = wr::CommonItemProperties {
683 clip_rect: bounds,
684 spatial_id: highlight.spatial_id,
685 clip_chain_id: highlight.clip_chain_id,
686 flags: wr::PrimitiveFlags::default(),
687 };
688 self.wr().push_border(&common, bounds, widths, details)
689 };
690
691 paint_highlight(
692 PADDING_BOX_HIGHLIGHT_COLOR,
693 box_fragment.padding_rect(),
694 box_fragment.padding.to_webrender(),
695 );
696 paint_highlight(
697 BORDER_BOX_HIGHLIGHT_COLOR,
698 box_fragment.border_rect(),
699 box_fragment.border.to_webrender(),
700 );
701 paint_highlight(
702 MARGIN_BOX_HIGHLIGHT_COLOR,
703 box_fragment.margin_rect(),
704 box_fragment.margin.to_webrender(),
705 );
706 }
707 }
708
709 #[expect(clippy::too_many_arguments)]
710 fn collect_image_record(
711 &mut self,
712 state: &TraversalState,
713 bounds: LayoutRect,
714 clip_rect: LayoutRect,
715 tag: Option<Tag>,
716 url: Option<ServoUrl>,
717 natural_width: Option<Au>,
718 natural_height: Option<Au>,
719 load_time: Option<CrossProcessInstant>,
720 ) {
721 if !self.largest_contentful_paint_enabled {
722 return;
723 }
724
725 let transform = self
726 .paint_info
727 .scroll_tree
728 .cumulative_node_to_root_transform(state.spatial_id);
729
730 self.paint_timing_handler.append_image_record(
731 tag,
732 bounds,
733 clip_rect,
734 transform,
735 url,
736 natural_width,
737 natural_height,
738 load_time,
739 );
740 }
741
742 #[expect(clippy::too_many_arguments)]
745 fn collect_container_timing_record(
746 &mut self,
747 state: &TraversalState,
748 container_timing_roots: &ContainerTimingRoots,
749 tag: Option<Tag>,
750 bounds: LayoutRect,
751 clip_rect: LayoutRect,
752 natural_width: Option<Au>,
753 natural_height: Option<Au>,
754 ) {
755 if !self.container_timing_enabled {
756 return;
757 }
758 let Some(container_timing_roots) = container_timing_roots else {
759 return;
760 };
761
762 let Some(tag) = tag else {
764 return;
765 };
766
767 let transform = self
768 .paint_info
769 .scroll_tree
770 .cumulative_node_to_root_transform(state.spatial_id);
771
772 self.paint_timing_handler.update_container_timing(
773 container_timing_roots,
774 tag.node,
775 bounds,
776 clip_rect,
777 transform,
778 natural_width,
779 natural_height,
780 );
781 }
782
783 fn visit_stacking_context_reference_frame_info(
784 &mut self,
785 stacking_context: &StackingContext,
786 ) -> (usize, Option<ScrollTreeNodeId>) {
787 let Some(reference_frame_info) = &stacking_context.reference_frame_info else {
788 return (0, None);
789 };
790
791 let old_reference_frame_spatial_id = std::mem::replace(
795 &mut self.current_reference_frame_scroll_node_id,
796 stacking_context.scroll_tree_node_id,
797 );
798
799 if reference_frame_info.captured_clip_id == ClipId::INVALID {
800 return (0, Some(old_reference_frame_spatial_id));
801 }
802
803 let clip_chain_id = Some(self.clip_chain_id(reference_frame_info.captured_clip_id));
812 let spatial_id = self.spatial_id(reference_frame_info.parent_spatial_node_id);
813 self.wr().push_stacking_context(
814 spatial_id,
815 PrimitiveFlags::default(),
816 clip_chain_id,
817 webrender_api::TransformStyle::Flat,
818 webrender_api::MixBlendMode::Normal,
819 &[], &[], wr::RasterSpace::Screen,
822 wr::StackingContextFlags::empty(),
823 None, );
825
826 (1, Some(old_reference_frame_spatial_id))
827 }
828}
829
830impl PaintTraversalHandler for DisplayListBuilder<'_> {
831 type StackingContextState = (usize, Option<ScrollTreeNodeId>);
836
837 fn visit_stacking_context(
838 &mut self,
839 stacking_context: &StackingContext,
840 ) -> Self::StackingContextState {
841 let (mut stacking_contexts_pushed, old_reference_frame) =
842 self.visit_stacking_context_reference_frame_info(stacking_context);
843 if self.push_webrender_stacking_context_for_transform_if_necessary(stacking_context) {
844 stacking_contexts_pushed += 1;
845 }
846 if self.push_webrender_stacking_context_if_necessary(stacking_context) {
847 stacking_contexts_pushed += 1;
848 }
849 (stacking_contexts_pushed, old_reference_frame)
850 }
851
852 fn leave_stacking_context(
853 &mut self,
854 _: &TraversalState,
855 stacking_context_state: Self::StackingContextState,
856 ) {
857 let (stacking_contexts_pushed, old_reference_frame) = stacking_context_state;
858 for _ in 0..stacking_contexts_pushed {
859 self.wr().pop_stacking_context();
860 }
861
862 if let Some(old_reference_frame) = old_reference_frame {
863 self.current_reference_frame_scroll_node_id = old_reference_frame;
864 }
865 }
866
867 fn visit_box(&mut self, state: &TraversalState, fragment: &BoxFragmentWithStyle<'_>) {
868 fragment.base.visit_fragment(self);
869
870 if let Some(mut inspector_highlight) = self.inspector_highlight.take() &&
871 fragment.base.tag == Some(inspector_highlight.tag)
872 {
873 inspector_highlight.register_fragment_of_highlighted_dom_node(self, state, fragment);
874 self.inspector_highlight = Some(inspector_highlight);
875 }
876
877 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
878 return;
879 };
880
881 BuilderForBoxFragment::new(fragment, state.origin).build(self, state)
882 }
883
884 fn visit_iframe(&mut self, state: &TraversalState, fragment: &Arc<IFrameFragment>) {
885 fragment.base.visit_fragment(self);
886
887 let style = fragment.style.borrow();
888 if style.get_inherited_box().visibility != Visibility::Visible {
889 return;
890 }
891
892 let rect = fragment.base.rect().translate(state.origin.to_vector());
893 let common = self.common_properties(state, rect.to_webrender(), &style);
894 self.wr().push_iframe(
895 rect.to_webrender(),
896 common.clip_rect,
897 &wr::SpaceAndClipInfo {
898 spatial_id: common.spatial_id,
899 clip_chain_id: common.clip_chain_id,
900 },
901 fragment.pipeline_id.into(),
902 true,
903 );
904 self.paint_timing_handler
910 .check_if_paintable(rect.to_webrender(), state.accumulated_opacity);
911 }
912
913 fn visit_image(
914 &mut self,
915 state: &TraversalState,
916 containing_block: PhysicalRect<Au>,
917 fragment: &Arc<ImageFragment>,
918 ) {
919 fragment.base.visit_fragment(self);
920
921 let style = fragment.style.borrow();
922 if style.get_inherited_box().visibility != Visibility::Visible {
923 return;
924 }
925
926 let image_rendering = style.get_inherited_box().image_rendering.to_webrender();
927 let rect = fragment
928 .base
929 .rect()
930 .translate(containing_block.origin.to_vector())
931 .to_webrender();
932 let clip = fragment
933 .clip
934 .translate(containing_block.origin.to_vector())
935 .to_webrender();
936 let common = self.common_properties(state, clip, &style);
937
938 if let Some(image_key) = fragment.image_key {
939 self.wr().push_image(
940 &common,
941 rect,
942 image_rendering,
943 wr::AlphaType::PremultipliedAlpha,
944 image_key,
945 wr::ColorF::WHITE,
946 );
947
948 if self
949 .paint_timing_handler
950 .check_if_paintable(rect, state.accumulated_opacity)
951 {
952 if !fragment.showing_broken_image_icon {
960 self.mark_is_contentful();
961
962 self.collect_image_record(
963 state,
964 rect,
965 common.clip_rect,
966 fragment.base.tag,
967 fragment.url.clone(),
968 fragment.natural_width,
969 fragment.natural_height,
970 fragment.load_time,
971 );
972
973 self.collect_container_timing_record(
974 state,
975 &state.container_timing_roots,
976 state.containing_element_tag,
977 rect,
978 common.clip_rect,
979 fragment.natural_width,
980 fragment.natural_height,
981 );
982 }
983 }
984 }
985
986 if fragment.showing_broken_image_icon {
987 Fragment::build_display_list_for_broken_image_border(self, &containing_block, &common);
988 }
989
990 if fragment.selected.load(Ordering::Relaxed) {
991 let selected_style = fragment.selected_style.borrow();
994 let mut background_color =
995 if self.frame_focused && !ServoArc::ptr_eq(&*selected_style, &*style) {
996 selected_style.resolve_color(&selected_style.get_background().background_color)
997 } else {
998 *UNFOCUSED_SELECTION_COLOR
999 };
1000
1001 if !background_color.is_transparent() {
1002 background_color.alpha *= 0.5;
1003 self.wr().push_rect(
1004 &common,
1005 containing_block.to_webrender(),
1006 rgba(background_color),
1007 );
1008 }
1009 }
1010 }
1011
1012 fn visit_text(
1013 &mut self,
1014 state: &TraversalState,
1015 line_box_rect: PhysicalRect<Au>,
1016 fragment: &Arc<TextFragment>,
1017 ) {
1018 fragment.base.visit_fragment(self);
1019
1020 let style = fragment.style();
1021 if style.get_inherited_box().visibility != Visibility::Visible {
1022 return;
1023 }
1024 Fragment::build_display_list_for_text_fragment(fragment, self, state, &line_box_rect);
1025 }
1026
1027 fn visit_positioning(&mut self, _state: &TraversalState, fragment: &Arc<PositioningFragment>) {
1028 fragment.base.visit_fragment(self);
1029 }
1030
1031 fn visit_box_for_root_background(&mut self, state: &TraversalState) {
1037 let Some(fragment) = self.fragment_tree.root_box_fragment() else {
1038 return;
1039 };
1040 let fragment = fragment.with_style();
1041
1042 let source_style = {
1043 let root_fragment_style = fragment.style();
1049 if root_fragment_style.background_is_transparent() {
1050 let body_fragment = self.fragment_tree.body_fragment();
1051 self.paint_body_background = body_fragment.is_none();
1052 body_fragment
1053 .map(|body_fragment| body_fragment.style().clone())
1054 .unwrap_or(fragment.style().clone())
1055 } else {
1056 root_fragment_style.clone()
1057 }
1058 };
1059
1060 if source_style.background_is_transparent() {
1063 return;
1064 }
1065
1066 let painting_area = self
1073 .fragment_tree
1074 .initial_containing_block
1075 .union(&self.fragment_tree.scrollable_overflow())
1076 .to_webrender();
1077
1078 let background_color =
1079 source_style.resolve_color(&source_style.get_background().background_color);
1080 if background_color.alpha > 0.0 {
1081 let common = self.common_properties(state, painting_area, &source_style);
1082 let color = rgba(background_color);
1083 self.wr().push_rect(&common, painting_area, color);
1084
1085 if background_color != self.shell_background_color {
1089 self.mark_is_paintable();
1090 }
1091 }
1092
1093 let fragment_builder = BuilderForBoxFragment::new(
1094 &fragment,
1095 self.fragment_tree.initial_containing_block.origin,
1096 );
1097 let painter = BackgroundPainter {
1098 style: &source_style,
1099 painting_area_override: Some(painting_area),
1100 positioning_area_override: None,
1101 };
1102 fragment_builder.build_background_image(self, state, &painter);
1103 }
1104
1105 fn visit_box_for_outline(&mut self, state: &TraversalState, fragment: &Arc<BoxFragment>) {
1106 let fragment = fragment.with_style();
1107 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
1108 return;
1109 };
1110 BuilderForBoxFragment::new(&fragment, state.origin).build_outline(self, state)
1111 }
1112
1113 fn visit_box_for_collapsed_table_borders(
1114 &mut self,
1115 state: &TraversalState,
1116 fragment: &BoxFragmentWithStyle<'_>,
1117 ) {
1118 if fragment.style().get_inherited_box().visibility != Visibility::Visible {
1119 return;
1120 };
1121 BuilderForBoxFragment::new(fragment, state.origin)
1122 .build_collapsed_table_borders(self, state)
1123 }
1124}
1125
1126impl InspectorHighlight {
1127 fn register_fragment_of_highlighted_dom_node(
1128 &mut self,
1129 builder: &DisplayListBuilder,
1130 traversal_state: &TraversalState,
1131 fragment: &Arc<BoxFragment>,
1132 ) {
1133 let spatial_id = builder.spatial_id(traversal_state.spatial_id);
1134 let clip_chain_id = builder.clip_chain_id(traversal_state.clip_id);
1135 let state = self.state.get_or_insert_with(|| HighlightTraversalState {
1136 content_box: Rect::zero(),
1137 spatial_id,
1138 clip_chain_id,
1139 maybe_box_fragment: Some(fragment.clone()),
1140 });
1141
1142 if spatial_id != state.spatial_id {
1145 return;
1146 }
1147
1148 if clip_chain_id != ClipChainId::INVALID && state.clip_chain_id != ClipChainId::INVALID {
1149 debug_assert_eq!(
1150 clip_chain_id, state.clip_chain_id,
1151 "Fragments of the same node must either have no clip chain or the same one"
1152 );
1153 }
1154
1155 state.maybe_box_fragment = Some(fragment.clone());
1156 state.content_box = state.content_box.union(
1157 &fragment
1158 .base
1159 .rect()
1160 .translate(traversal_state.origin.to_vector()),
1161 );
1162 }
1163}
1164
1165impl Fragment {
1166 fn build_display_list_for_text_fragment(
1167 fragment: &TextFragment,
1168 builder: &mut DisplayListBuilder,
1169 state: &TraversalState,
1170 line_box_rect: &PhysicalRect<Au>,
1171 ) {
1172 let rect = fragment.base.rect().translate(state.origin.to_vector());
1175 let mut baseline_origin = rect.origin;
1176 baseline_origin.y += fragment.font_metrics.ascent;
1177
1178 let (largest_advance, entirely_white_space) = collect_glyphs(
1179 &mut builder.glyph_buffer,
1180 &fragment.glyphs,
1181 baseline_origin,
1182 fragment.justification_adjustment,
1183 );
1184
1185 if builder.glyph_buffer.is_empty() && !fragment.is_empty_for_text_cursor {
1186 return;
1187 }
1188
1189 let parent_style = fragment.style();
1190 let color = parent_style.slow_clone_color();
1191 let glyph_bounds = rect
1198 .inflate(largest_advance.scale_by(2.0), Au::zero())
1199 .to_webrender();
1200 let common = builder.common_properties(state, glyph_bounds, &parent_style);
1201
1202 let shadows = &parent_style.get_inherited_text().text_shadow;
1205 for shadow in shadows.0.iter().rev() {
1206 builder.wr().push_shadow(
1207 &wr::SpaceAndClipInfo {
1208 spatial_id: common.spatial_id,
1209 clip_chain_id: common.clip_chain_id,
1210 },
1211 wr::Shadow {
1212 offset: LayoutVector2D::new(shadow.horizontal.px(), shadow.vertical.px()),
1213 color: rgba(shadow.color.resolve_to_absolute(&color)),
1214 blur_radius: shadow.blur.px(),
1215 },
1216 true, );
1218 }
1219
1220 Self::build_display_list_for_text_selection(fragment, builder, state, line_box_rect);
1221
1222 for decoration in [
1223 TextDecorationLine::UNDERLINE,
1224 TextDecorationLine::SPELLING_ERROR,
1225 TextDecorationLine::GRAMMAR_ERROR,
1226 TextDecorationLine::OVERLINE,
1227 ] {
1228 Self::build_display_list_for_text_decoration_line(
1229 state,
1230 &parent_style,
1231 builder,
1232 rect,
1233 &fragment.font_metrics,
1234 decoration,
1235 );
1236 }
1237
1238 builder.webrender_display_list_builder.push_text(
1239 &common,
1240 glyph_bounds,
1241 &builder.glyph_buffer,
1242 fragment.font_key,
1243 rgba(color),
1244 None,
1245 );
1246
1247 if builder
1248 .paint_timing_handler
1249 .check_if_paintable(glyph_bounds, state.accumulated_opacity)
1250 {
1251 if !entirely_white_space && *parent_style.get_opacity() > 0. {
1256 builder.mark_is_contentful();
1257
1258 builder.collect_container_timing_record(
1259 state,
1260 &state.container_timing_roots,
1261 state.containing_element_tag,
1262 glyph_bounds,
1263 common.clip_rect,
1264 None,
1265 None,
1266 );
1267
1268 if let Some(tag) = state.containing_element_tag &&
1270 builder.largest_contentful_paint_enabled
1271 {
1272 let transform = builder
1273 .paint_info
1274 .scroll_tree
1275 .cumulative_node_to_root_transform(state.spatial_id);
1276 builder.paint_timing_handler.accumulate_text_rect(
1277 tag,
1278 rect.to_webrender(),
1279 transform,
1280 &parent_style,
1281 );
1282 }
1283 }
1284 }
1285
1286 Self::build_display_list_for_text_decoration_line(
1287 state,
1288 &parent_style,
1289 builder,
1290 rect,
1291 &fragment.font_metrics,
1292 TextDecorationLine::LINE_THROUGH,
1293 );
1294
1295 if !shadows.0.is_empty() {
1296 builder.wr().pop_all_shadows();
1297 }
1298 }
1299
1300 fn build_display_list_for_text_decoration_line(
1301 state: &TraversalState,
1302 parent_style: &ServoArc<ComputedValues>,
1303 builder: &mut DisplayListBuilder,
1304 rect: PhysicalRect<Au>,
1305 font_metrics: &FontMetrics,
1306 text_decoration_line: TextDecorationLine,
1307 ) {
1308 if state.text_decorations.is_empty() {
1309 return;
1310 }
1311
1312 let font_size = parent_style.get_font().font_size;
1313 let dppx = builder.device_pixel_ratio.get();
1314 let resolve_thickness = |thickness: &TextDecorationThickness| -> Au {
1315 let resolved = match thickness {
1316 TextDecorationThickness::LengthPercentage(length_percentage) => {
1317 length_percentage.resolve(font_size.computed_size.0).px()
1318 },
1319 TextDecorationThickness::Auto | TextDecorationThickness::FromFont => {
1320 font_metrics.underline_size.to_f32_px()
1321 },
1322 };
1323
1324 if resolved == 0.0 {
1328 Au::zero()
1329 } else {
1330 Au::from_f32_px((resolved * dppx).floor().max(1.0) / dppx)
1331 }
1332 };
1333
1334 for text_decoration in state.text_decorations.iter() {
1335 if !text_decoration.line.contains(text_decoration_line) {
1336 continue;
1337 }
1338
1339 let (color, style, y_offset, height) = match text_decoration_line {
1340 TextDecorationLine::UNDERLINE => (
1342 text_decoration.color,
1343 text_decoration.style,
1344 font_metrics.ascent - font_metrics.underline_offset,
1345 resolve_thickness(&text_decoration.thickness),
1346 ),
1347 TextDecorationLine::SPELLING_ERROR => (
1358 AbsoluteColor::srgb_legacy(255, 0, 0, 1.0),
1359 ComputedTextDecorationStyle::Wavy,
1360 font_metrics.ascent - font_metrics.underline_offset,
1361 resolve_thickness(&TextDecorationThickness::Auto),
1362 ),
1363 TextDecorationLine::GRAMMAR_ERROR => (
1365 AbsoluteColor::srgb_legacy(0, 128, 0, 1.0),
1366 ComputedTextDecorationStyle::Wavy,
1367 font_metrics.ascent - font_metrics.underline_offset,
1368 resolve_thickness(&TextDecorationThickness::Auto),
1369 ),
1370 TextDecorationLine::LINE_THROUGH => (
1372 text_decoration.color,
1373 text_decoration.style,
1374 font_metrics.ascent - font_metrics.strikeout_offset,
1375 resolve_thickness(&text_decoration.thickness),
1376 ),
1377 TextDecorationLine::OVERLINE => (
1379 text_decoration.color,
1380 text_decoration.style,
1381 Au::zero(),
1382 resolve_thickness(&text_decoration.thickness),
1383 ),
1384 _ => unreachable!("Got unexpected text decoration line"),
1385 };
1386
1387 if style == ComputedTextDecorationStyle::None {
1388 continue;
1389 }
1390
1391 let mut rect = rect;
1392 rect.origin.y += y_offset;
1393 rect.size.height = height;
1394
1395 let mut rect = rect.to_webrender();
1396 let wavy_line_thickness = rect.height().ceil();
1397 if style == ComputedTextDecorationStyle::Wavy {
1398 rect = rect.inflate(0.0, wavy_line_thickness);
1399 }
1400
1401 let common_properties = builder.common_properties(state, rect, parent_style);
1402
1403 if matches!(
1410 style,
1411 ComputedTextDecorationStyle::Dotted |
1412 ComputedTextDecorationStyle::Dashed |
1413 ComputedTextDecorationStyle::Wavy,
1414 ) {
1415 rect.min.x = rect.min.x.min(0.0);
1416 }
1417
1418 builder.wr().push_line(
1419 &common_properties,
1420 &rect,
1421 wavy_line_thickness,
1422 wr::LineOrientation::Horizontal,
1423 &rgba(color),
1424 style.to_webrender(),
1425 );
1426
1427 if style == ComputedTextDecorationStyle::Double {
1428 let half_height = (rect.height() / 2.0).floor().max(1.0);
1429 let y_offset = match text_decoration_line {
1430 TextDecorationLine::OVERLINE => -rect.height() - half_height,
1431 _ => rect.height() + half_height,
1432 };
1433 let rect = rect.translate(Vector2D::new(0.0, y_offset));
1434 let common_properties = builder.common_properties(state, rect, parent_style);
1435 builder.wr().push_line(
1436 &common_properties,
1437 &rect,
1438 wavy_line_thickness,
1439 wr::LineOrientation::Horizontal,
1440 &rgba(color),
1441 style.to_webrender(),
1442 );
1443 }
1444 }
1445 }
1446
1447 fn build_display_list_for_broken_image_border(
1448 builder: &mut DisplayListBuilder,
1449 containing_block: &PhysicalRect<Au>,
1450 common: &CommonItemProperties,
1451 ) {
1452 let border_side = BorderSide {
1453 color: ColorF::BLACK,
1454 style: wr::BorderStyle::Inset,
1455 };
1456 builder.wr().push_border(
1457 common,
1458 containing_block.to_webrender(),
1459 LayoutSideOffsets::new_all_same(1.0),
1460 BorderDetails::Normal(NormalBorder {
1461 left: border_side,
1462 right: border_side,
1463 top: border_side,
1464 bottom: border_side,
1465 radius: BorderRadius::zero(),
1466 do_aa: true,
1467 }),
1468 );
1469 }
1470
1471 fn build_display_list_for_text_selection(
1474 fragment: &TextFragment,
1475 builder: &mut DisplayListBuilder<'_>,
1476 state: &TraversalState,
1477 line_box_rect: &PhysicalRect<Au>,
1478 ) {
1479 let run_data = &fragment.run_data;
1480 let Some(selection) = *run_data.selection.borrow() else {
1481 return;
1482 };
1483
1484 let selection_character_range = run_data.map_dom_range_to_transformed_range(selection);
1488
1489 if fragment.character_range_in_dom_node.start > selection_character_range.end ||
1490 fragment.character_range_in_dom_node.end < selection_character_range.start
1491 {
1492 return;
1493 }
1494
1495 if fragment.is_empty_for_text_cursor &&
1500 !fragment
1501 .character_range_in_dom_node
1502 .contains(&selection_character_range.start)
1503 {
1504 return;
1505 }
1506
1507 let mut current_character_index = fragment.character_range_in_dom_node.start;
1508 let mut current_advance = Au::zero();
1509 let mut start_advance = None;
1510 let mut end_advance = None;
1511 for glyph_store in fragment.glyphs.iter() {
1512 let glyph_store_character_count = glyph_store.character_count();
1513 if current_character_index + glyph_store_character_count <
1514 selection_character_range.start
1515 {
1516 current_advance += glyph_store.total_advance() +
1517 (fragment.justification_adjustment *
1518 glyph_store.total_word_separators() as i32);
1519 current_character_index += glyph_store_character_count;
1520 continue;
1521 }
1522
1523 if current_character_index >= selection_character_range.end {
1524 break;
1525 }
1526
1527 for glyph in glyph_store.glyphs() {
1528 if current_character_index >= selection_character_range.start {
1529 start_advance = start_advance.or(Some(current_advance));
1530 }
1531
1532 current_character_index += glyph.character_count();
1533 current_advance += glyph.advance();
1534 if glyph.char_is_word_separator() {
1535 current_advance += fragment.justification_adjustment;
1536 }
1537
1538 if current_character_index <= selection_character_range.end {
1539 end_advance = Some(current_advance);
1540 }
1541 }
1542 }
1543
1544 let start_x = start_advance.unwrap_or(current_advance);
1545 let end_x = end_advance.unwrap_or(current_advance);
1546
1547 let fragment_rect = fragment.base.rect();
1548 let fragment_origin =
1549 (state.origin + fragment_rect.origin.to_vector()) + Vector2D::new(start_x, Au::zero());
1550
1551 let parent_style = fragment.style();
1552 if !selection_character_range.is_empty() {
1553 let selection_rect = Rect::new(
1554 Point2D::new(fragment_origin.x, line_box_rect.min_y()),
1555 Size2D::new(end_x - start_x, line_box_rect.height()),
1556 )
1557 .to_webrender();
1558
1559 let unfocused_color = *UNFOCUSED_SELECTION_COLOR;
1560 let selected_style = fragment.selected_style();
1561 let selection_color = if builder.frame_focused {
1562 selected_style
1563 .get_background_color()
1564 .as_absolute()
1565 .unwrap_or(&unfocused_color)
1566 } else {
1567 &unfocused_color
1568 };
1569
1570 let selection_common = builder.common_properties(state, selection_rect, &parent_style);
1571 builder
1572 .wr()
1573 .push_rect(&selection_common, selection_rect, rgba(*selection_color));
1574
1575 return;
1576 }
1577
1578 if !fragment.run_data.paints_caret {
1579 return;
1580 }
1581
1582 if !builder.frame_focused {
1585 return;
1586 }
1587
1588 let insertion_point_rect = Rect::new(
1589 fragment_origin,
1590 Size2D::new(
1591 INSERTION_POINT_LOGICAL_WIDTH,
1592 fragment.font_metrics.line_gap,
1593 ),
1594 )
1595 .to_webrender();
1596
1597 let color = parent_style.get_color();
1598 let caret_color = match parent_style.get_caret_color().0 {
1599 ColorOrAuto::Color(ref caret_color) => caret_color.resolve_to_absolute(color),
1600 ColorOrAuto::Auto => *color,
1601 };
1602 let insertion_point_common =
1603 builder.common_properties(state, insertion_point_rect, &parent_style);
1604
1605 let caret_color = rgba(caret_color);
1606 let property_binding = if prefs::get().editing_caret_blink_time().is_some() {
1607 let pipeline_id: PipelineId = builder.paint_info.pipeline_id.into();
1611 let property_binding_key = PropertyBindingKey::new(pipeline_id.into());
1612 builder.paint_info.caret_property_binding = Some((property_binding_key, caret_color));
1613 PropertyBinding::Binding(property_binding_key, caret_color)
1614 } else {
1615 PropertyBinding::Value(caret_color)
1616 };
1617
1618 builder.wr().push_rect_with_animation(
1619 &insertion_point_common,
1620 insertion_point_rect,
1621 property_binding,
1622 );
1623 }
1624}
1625
1626struct BuilderForBoxFragment<'a> {
1627 fragment: &'a BoxFragmentWithStyle<'a>,
1628 containing_block_origin: PhysicalPoint<Au>,
1629 border_rect: units::LayoutRect,
1630 margin_rect: OnceCell<units::LayoutRect>,
1631 padding_rect: OnceCell<units::LayoutRect>,
1632 content_rect: OnceCell<units::LayoutRect>,
1633 border_radius: OnceCell<wr::BorderRadius>,
1634 border_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1635 padding_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1636 content_edge_clip_chain_id: RefCell<Option<ClipChainId>>,
1637}
1638
1639impl<'a> BuilderForBoxFragment<'a> {
1640 fn new(
1641 fragment: &'a BoxFragmentWithStyle<'a>,
1642 containing_block_origin: PhysicalPoint<Au>,
1643 ) -> Self {
1644 let border_rect = fragment
1645 .border_rect()
1646 .translate(containing_block_origin.to_vector());
1647 Self {
1648 fragment,
1649 containing_block_origin,
1650 border_rect: border_rect.to_webrender(),
1651 border_radius: OnceCell::new(),
1652 margin_rect: OnceCell::new(),
1653 padding_rect: OnceCell::new(),
1654 content_rect: OnceCell::new(),
1655 border_edge_clip_chain_id: RefCell::new(None),
1656 padding_edge_clip_chain_id: RefCell::new(None),
1657 content_edge_clip_chain_id: RefCell::new(None),
1658 }
1659 }
1660
1661 fn border_radius(&self) -> BorderRadius {
1662 *self
1663 .border_radius
1664 .get_or_init(|| self.fragment.border_radius())
1665 }
1666
1667 fn content_rect(&self) -> &units::LayoutRect {
1668 self.content_rect.get_or_init(|| {
1669 self.fragment
1670 .content_rect()
1671 .translate(self.containing_block_origin.to_vector())
1672 .to_webrender()
1673 })
1674 }
1675
1676 fn padding_rect(&self) -> &units::LayoutRect {
1677 self.padding_rect.get_or_init(|| {
1678 self.fragment
1679 .padding_rect()
1680 .translate(self.containing_block_origin.to_vector())
1681 .to_webrender()
1682 })
1683 }
1684
1685 fn margin_rect(&self) -> &units::LayoutRect {
1686 self.margin_rect.get_or_init(|| {
1687 self.fragment
1688 .margin_rect()
1689 .translate(self.containing_block_origin.to_vector())
1690 .to_webrender()
1691 })
1692 }
1693
1694 fn border_edge_clip(
1695 &self,
1696 builder: &mut DisplayListBuilder,
1697 state: &TraversalState,
1698 force_clip_creation: bool,
1699 ) -> Option<ClipChainId> {
1700 if let Some(clip) = *self.border_edge_clip_chain_id.borrow() {
1701 return Some(clip);
1702 }
1703
1704 let maybe_clip = builder.maybe_create_clip(
1705 state,
1706 self.border_radius(),
1707 self.border_rect,
1708 force_clip_creation,
1709 );
1710 *self.border_edge_clip_chain_id.borrow_mut() = maybe_clip;
1711 maybe_clip
1712 }
1713
1714 fn padding_edge_clip(
1715 &self,
1716 builder: &mut DisplayListBuilder,
1717 state: &TraversalState,
1718 force_clip_creation: bool,
1719 ) -> Option<ClipChainId> {
1720 if let Some(clip) = *self.padding_edge_clip_chain_id.borrow() {
1721 return Some(clip);
1722 }
1723
1724 let radii = offset_radii(self.border_radius(), -self.fragment.border.to_webrender());
1725 let maybe_clip =
1726 builder.maybe_create_clip(state, radii, *self.padding_rect(), force_clip_creation);
1727 *self.padding_edge_clip_chain_id.borrow_mut() = maybe_clip;
1728 maybe_clip
1729 }
1730
1731 fn content_edge_clip(
1732 &self,
1733 builder: &mut DisplayListBuilder,
1734 state: &TraversalState,
1735 force_clip_creation: bool,
1736 ) -> Option<ClipChainId> {
1737 if let Some(clip) = *self.content_edge_clip_chain_id.borrow() {
1738 return Some(clip);
1739 }
1740
1741 let radii = offset_radii(
1742 self.border_radius(),
1743 -(self.fragment.border + self.fragment.padding).to_webrender(),
1744 );
1745 let maybe_clip =
1746 builder.maybe_create_clip(state, radii, *self.content_rect(), force_clip_creation);
1747 *self.content_edge_clip_chain_id.borrow_mut() = maybe_clip;
1748 maybe_clip
1749 }
1750
1751 fn build(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1752 if self
1753 .fragment
1754 .base
1755 .flags
1756 .contains(FragmentFlags::DO_NOT_PAINT)
1757 {
1758 return;
1759 }
1760
1761 self.build_backdrop_filter(builder, state);
1762 self.build_background(builder, state);
1763 self.build_box_shadow(builder, state);
1764 if !self.fragment.is_table_grid_with_collapsed_borders() {
1765 self.build_border(builder, state);
1766 }
1767
1768 let overflow = self
1769 .fragment
1770 .style()
1771 .effective_overflow(self.fragment.base.flags);
1772 let scrolls_via_user_input =
1773 |overflow| matches!(overflow, ComputedOverflow::Scroll | ComputedOverflow::Auto);
1774 if (scrolls_via_user_input(overflow.x) || scrolls_via_user_input(overflow.y)) &&
1775 self.fragment.style().get_inherited_ui().pointer_events !=
1776 style::computed_values::pointer_events::T::None
1777 {
1778 let mut inner_state = state.clone();
1779 inner_state.spatial_id = self
1780 .fragment
1781 .generated_scroll_tree_node_id()
1782 .unwrap_or(state.spatial_id);
1783 inner_state.clip_id = self.fragment.generated_clip_id().unwrap_or(state.clip_id);
1784
1785 self.build_hit_test(
1786 builder,
1787 &inner_state,
1788 self.fragment
1789 .scrollable_overflow()
1790 .translate(self.containing_block_origin.to_vector())
1791 .to_webrender(),
1792 );
1793 }
1794 }
1795
1796 fn build_hit_test(
1797 &self,
1798 builder: &mut DisplayListBuilder,
1799 state: &TraversalState,
1800 rect: LayoutRect,
1801 ) {
1802 let external_scroll_node_id = builder
1803 .paint_info
1804 .external_scroll_id_for_scroll_tree_node(state.spatial_id);
1805
1806 let mut common = builder.common_properties(state, rect, self.fragment.style());
1807 if let Some(clip_chain_id) = self.border_edge_clip(builder, state, false) {
1808 common.clip_chain_id = clip_chain_id;
1809 }
1810 builder.wr().push_hit_test(
1811 common.clip_rect,
1812 common.clip_chain_id,
1813 common.spatial_id,
1814 common.flags,
1815 (external_scroll_node_id.0, 0), );
1817 }
1818
1819 fn build_background_for_painter(
1820 &mut self,
1821 builder: &mut DisplayListBuilder,
1822 state: &TraversalState,
1823 painter: &BackgroundPainter,
1824 ) {
1825 let b = painter.style.get_background();
1826 let background_color = painter.style.resolve_color(&b.background_color);
1827 if background_color.alpha > 0.0 {
1828 let layer_index = b.background_image.0.len() - 1;
1832 let bounds = painter.painting_area(self, builder, layer_index);
1833 let common = painter.common_properties(self, builder, state, layer_index, bounds);
1834 builder
1835 .wr()
1836 .push_rect(&common, bounds, rgba(background_color));
1837
1838 if background_color != builder.shell_background_color {
1842 builder.mark_is_paintable();
1843 }
1844 }
1845
1846 self.build_background_image(builder, state, painter);
1847 }
1848
1849 fn build_backdrop_filter(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1850 if self
1854 .fragment
1855 .base
1856 .flags
1857 .contains(FragmentFlags::IS_ROOT_ELEMENT)
1858 {
1859 return;
1860 }
1861
1862 let style = self.fragment.style();
1863 let effects = style.get_effects();
1864 if effects.backdrop_filter.0.is_empty() {
1865 return;
1866 }
1867
1868 let current_color = style.get_color();
1869 let filters: Vec<_> = effects
1870 .backdrop_filter
1871 .0
1872 .iter()
1873 .map(|filter| FilterToWebRender::to_webrender(filter, current_color))
1874 .collect();
1875
1876 let mut common = builder.common_properties(state, self.border_rect, style);
1877 if let Some(clip_chain_id) = self.border_edge_clip(builder, state, false) {
1878 common.clip_chain_id = clip_chain_id;
1879 }
1880 builder.wr().push_backdrop_filter(&common, &filters, &[]);
1881 }
1882
1883 fn build_background(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
1884 let flags = self.fragment.base.flags;
1885
1886 if flags.intersects(FragmentFlags::IS_ROOT_ELEMENT) {
1889 return;
1890 }
1891 if !builder.paint_body_background &&
1893 flags.intersects(FragmentFlags::IS_BODY_ELEMENT_OF_HTML_ELEMENT_ROOT)
1894 {
1895 return;
1896 }
1897
1898 if let BackgroundMode::None = self.fragment.background_mode {
1900 return;
1901 }
1902
1903 if let BackgroundMode::Extra(ref extra_backgrounds) = self.fragment.background_mode {
1907 for extra_background in extra_backgrounds {
1908 let positioning_area = extra_background.rect;
1909 let painter = BackgroundPainter {
1910 style: &extra_background.style.borrow_mut(),
1911 painting_area_override: None,
1912 positioning_area_override: Some(
1913 positioning_area
1914 .translate(self.containing_block_origin.to_vector())
1915 .to_webrender(),
1916 ),
1917 };
1918 self.build_background_for_painter(builder, state, &painter);
1919 }
1920 }
1921
1922 let painter = BackgroundPainter {
1923 style: self.fragment.style(),
1924 painting_area_override: None,
1925 positioning_area_override: None,
1926 };
1927 self.build_background_for_painter(builder, state, &painter);
1928 }
1929
1930 fn build_background_image(
1931 &self,
1932 builder: &mut DisplayListBuilder,
1933 state: &TraversalState,
1934 painter: &BackgroundPainter,
1935 ) {
1936 let style = painter.style;
1937 let b = style.get_background();
1938 let need_blend_container = b
1939 .background_blend_mode
1940 .0
1941 .iter()
1942 .take(b.background_image.0.len())
1943 .any(|background_blend_mode| background_blend_mode != &BackgroundBlendMode::Normal);
1944
1945 let push_stacking_context = |builder: &mut DisplayListBuilder,
1946 blend_mode: BackgroundBlendMode,
1947 flags: StackingContextFlags|
1948 -> bool {
1949 let spatial_id = builder.spatial_id(state.spatial_id);
1950 builder.wr().push_stacking_context(
1951 spatial_id,
1952 PrimitiveFlags::default(),
1953 None,
1954 TransformStyle::Flat,
1955 blend_mode.to_webrender(),
1956 &[],
1957 &[],
1958 RasterSpace::Screen,
1959 flags,
1960 None,
1961 );
1962 true
1963 };
1964
1965 if need_blend_container {
1966 push_stacking_context(
1967 builder,
1968 BackgroundBlendMode::Normal,
1969 StackingContextFlags::IS_BLEND_CONTAINER,
1970 );
1971 }
1972
1973 let node = self.fragment.base.tag.map(|tag| tag.node);
1974 for (index, image) in b.background_image.0.iter().enumerate().rev() {
1976 let Ok(resolved_image) = builder.image_resolver.resolve_image(node, image) else {
1977 continue;
1978 };
1979 match resolved_image {
1980 ResolvedImage::Gradient(_) | ResolvedImage::Color(_) => {
1981 let intrinsic = NaturalSizes::empty();
1982 let Some(layer) =
1983 &background::layout_layer(self, painter, builder, state, index, intrinsic)
1984 else {
1985 continue;
1986 };
1987
1988 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
1989 if needs_blending {
1990 push_stacking_context(builder, layer.blend_mode, Default::default());
1991 }
1992
1993 match resolved_image {
1994 ResolvedImage::Gradient(gradient) => {
1995 match gradient::build(style, gradient, layer.tile_size, builder) {
1996 WebRenderGradient::Linear(linear_gradient) => {
1997 builder.wr().push_gradient(
1998 &layer.common,
1999 layer.bounds,
2000 linear_gradient,
2001 layer.tile_size,
2002 layer.tile_spacing,
2003 )
2004 },
2005 WebRenderGradient::Radial(radial_gradient) => {
2006 builder.wr().push_radial_gradient(
2007 &layer.common,
2008 layer.bounds,
2009 radial_gradient,
2010 layer.tile_size,
2011 layer.tile_spacing,
2012 )
2013 },
2014 WebRenderGradient::Conic(conic_gradient) => {
2015 builder.wr().push_conic_gradient(
2016 &layer.common,
2017 layer.bounds,
2018 conic_gradient,
2019 layer.tile_size,
2020 layer.tile_spacing,
2021 )
2022 },
2023 }
2024 },
2025 ResolvedImage::Color(color) => {
2026 let color = rgba(style.resolve_color(color));
2027 builder.wr().push_rect(&layer.common, layer.bounds, color);
2028 },
2029 _ => {},
2030 }
2031
2032 if needs_blending {
2033 builder.wr().pop_stacking_context();
2034 }
2035
2036 builder
2037 .paint_timing_handler
2038 .check_if_paintable(layer.bounds, state.accumulated_opacity);
2039 },
2040 ResolvedImage::Image { image, size, url } => {
2041 let dppx = 1.0;
2043 let intrinsic =
2044 NaturalSizes::from_width_and_height(size.width / dppx, size.height / dppx);
2045 let layer =
2046 background::layout_layer(self, painter, builder, state, index, intrinsic);
2047
2048 let scale = builder.device_pixel_ratio.get();
2049 let default_size: DeviceIntSize =
2050 Size2D::new(size.width * scale, size.height * scale).to_i32();
2051 let preferred_size = layer.as_ref().map(|layer| {
2052 Size2D::new(
2053 layer.tile_size.width * scale,
2054 layer.tile_size.height * scale,
2055 )
2056 .to_i32()
2057 });
2058
2059 let Some(image_key) = builder.image_resolver.image_key_from_cached_image(
2060 &image,
2061 preferred_size.unwrap_or(default_size),
2062 node,
2063 ) else {
2064 continue;
2065 };
2066
2067 if let Some(layer) = layer {
2068 let needs_blending = layer.blend_mode != BackgroundBlendMode::Normal;
2069 if needs_blending {
2070 push_stacking_context(builder, layer.blend_mode, Default::default());
2071 }
2072
2073 if layer.repeat {
2074 builder.wr().push_repeating_image(
2075 &layer.common,
2076 layer.bounds,
2077 layer.tile_size,
2078 layer.tile_spacing,
2079 style.get_image_rendering().to_webrender(),
2080 wr::AlphaType::PremultipliedAlpha,
2081 image_key,
2082 wr::ColorF::WHITE,
2083 )
2084 } else {
2085 builder.wr().push_image(
2086 &layer.common,
2087 layer.bounds,
2088 style.get_image_rendering().to_webrender(),
2089 wr::AlphaType::PremultipliedAlpha,
2090 image_key,
2091 wr::ColorF::WHITE,
2092 )
2093 }
2094
2095 if needs_blending {
2096 builder.wr().pop_stacking_context();
2097 }
2098
2099 if builder
2100 .paint_timing_handler
2101 .check_if_paintable(layer.bounds, state.accumulated_opacity)
2102 {
2103 builder.mark_is_contentful();
2108
2109 let natural_width = Some(Au::from_f32_px(size.width / dppx));
2110 let natural_height = Some(Au::from_f32_px(size.height / dppx));
2111 builder.collect_image_record(
2112 state,
2113 layer.bounds,
2114 layer.common.clip_rect,
2115 self.fragment.base.tag,
2116 Some(url),
2117 natural_width,
2118 natural_height,
2119 image.load_time(),
2120 );
2121
2122 builder.collect_container_timing_record(
2123 state,
2124 &state.container_timing_roots_including(self.fragment),
2125 self.fragment.base.tag.or(state.containing_element_tag),
2126 layer.bounds,
2127 layer.common.clip_rect,
2128 natural_width,
2129 natural_height,
2130 );
2131 }
2132 }
2133 },
2134 }
2135 }
2136
2137 if need_blend_container {
2138 builder.wr().pop_stacking_context();
2139 }
2140 }
2141
2142 fn build_border_side(&self, style_color: BorderStyleColor) -> wr::BorderSide {
2143 wr::BorderSide {
2144 color: rgba(style_color.color),
2145 style: match style_color.style {
2146 BorderStyle::None => wr::BorderStyle::None,
2147 BorderStyle::Solid => wr::BorderStyle::Solid,
2148 BorderStyle::Double => wr::BorderStyle::Double,
2149 BorderStyle::Dotted => wr::BorderStyle::Dotted,
2150 BorderStyle::Dashed => wr::BorderStyle::Dashed,
2151 BorderStyle::Hidden => wr::BorderStyle::Hidden,
2152 BorderStyle::Groove => wr::BorderStyle::Groove,
2153 BorderStyle::Ridge => wr::BorderStyle::Ridge,
2154 BorderStyle::Inset => wr::BorderStyle::Inset,
2155 BorderStyle::Outset => wr::BorderStyle::Outset,
2156 },
2157 }
2158 }
2159
2160 fn build_collapsed_table_borders(
2161 &self,
2162 builder: &mut DisplayListBuilder,
2163 state: &TraversalState,
2164 ) {
2165 if self
2166 .fragment
2167 .base
2168 .flags
2169 .contains(FragmentFlags::DO_NOT_PAINT)
2170 {
2171 return;
2172 }
2173
2174 let layout_info = self.fragment.specific_layout_info();
2175 let Some(SpecificLayoutInfo::TableGridWithCollapsedBorders(table_info)) =
2176 layout_info.as_deref()
2177 else {
2178 return;
2179 };
2180 let mut common =
2181 builder.common_properties(state, units::LayoutRect::default(), self.fragment.style());
2182 let radius = wr::BorderRadius::default();
2183 let mut column_sum = Au::zero();
2184 for (x, column_size) in table_info.track_sizes.x.iter().enumerate() {
2185 let mut row_sum = Au::zero();
2186 for (y, row_size) in table_info.track_sizes.y.iter().enumerate() {
2187 let left_border = &table_info.collapsed_borders.x[x][y];
2188 let right_border = &table_info.collapsed_borders.x[x + 1][y];
2189 let top_border = &table_info.collapsed_borders.y[y][x];
2190 let bottom_border = &table_info.collapsed_borders.y[y + 1][x];
2191 let details = wr::BorderDetails::Normal(wr::NormalBorder {
2192 left: self.build_border_side(left_border.style_color.clone()),
2193 right: self.build_border_side(right_border.style_color.clone()),
2194 top: self.build_border_side(top_border.style_color.clone()),
2195 bottom: self.build_border_side(bottom_border.style_color.clone()),
2196 radius,
2197 do_aa: true,
2198 });
2199 let mut border_widths = PhysicalSides::new(
2200 top_border.width,
2201 right_border.width,
2202 bottom_border.width,
2203 left_border.width,
2204 );
2205 let left_adjustment = if x == 0 {
2206 -border_widths.left / 2
2207 } else {
2208 std::mem::take(&mut border_widths.left) / 2
2209 };
2210 let top_adjustment = if y == 0 {
2211 -border_widths.top / 2
2212 } else {
2213 std::mem::take(&mut border_widths.top) / 2
2214 };
2215 let origin =
2216 PhysicalPoint::new(column_sum + left_adjustment, row_sum + top_adjustment);
2217 let size = PhysicalSize::new(
2218 *column_size - left_adjustment + border_widths.right / 2,
2219 *row_size - top_adjustment + border_widths.bottom / 2,
2220 );
2221 let border_rect = PhysicalRect::new(origin, size)
2222 .translate(self.fragment.content_rect().origin.to_vector())
2223 .translate(self.containing_block_origin.to_vector())
2224 .to_webrender();
2225 common.clip_rect = border_rect;
2226 builder.wr().push_border(
2227 &common,
2228 border_rect,
2229 border_widths.to_webrender(),
2230 details,
2231 );
2232 row_sum += *row_size;
2233 }
2234 column_sum += *column_size;
2235 }
2236 }
2237
2238 fn build_border(&mut self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2239 if self.fragment.has_collapsed_borders() {
2240 return;
2243 }
2244
2245 let style = self.fragment.style();
2246 let border = style.get_border();
2247 let border_widths = self.fragment.border.to_webrender();
2248
2249 if border_widths == SideOffsets2D::zero() {
2250 return;
2251 }
2252
2253 if self.build_border_image(builder, state, border, border_widths) {
2255 return;
2256 }
2257
2258 let current_color = style.get_inherited_text().get_color();
2259 let style_color = BorderStyleColor::from_border(border, current_color);
2260 let details = wr::BorderDetails::Normal(wr::NormalBorder {
2261 top: self.build_border_side(style_color.top),
2262 right: self.build_border_side(style_color.right),
2263 bottom: self.build_border_side(style_color.bottom),
2264 left: self.build_border_side(style_color.left),
2265 radius: self.border_radius(),
2266 do_aa: true,
2267 });
2268 let common = builder.common_properties(state, self.border_rect, style);
2269 builder
2270 .wr()
2271 .push_border(&common, self.border_rect, border_widths, details)
2272 }
2273
2274 fn build_border_image(
2276 &self,
2277 builder: &mut DisplayListBuilder,
2278 state: &TraversalState,
2279 border: &Border,
2280 border_widths: SideOffsets2D<f32, LayoutPixel>,
2281 ) -> bool {
2282 let style = self.fragment.style();
2283 let border_style_struct = style.get_border();
2284 let border_image_outset =
2285 resolve_border_image_outset(border_style_struct.border_image_outset, border_widths);
2286 let border_image_area = self.border_rect.to_rect().outer_rect(border_image_outset);
2287 let border_image_size = border_image_area.size;
2288 let border_image_widths = resolve_border_image_width(
2289 &border_style_struct.border_image_width,
2290 border_widths,
2291 border_image_size,
2292 );
2293 let border_image_repeat = &border_style_struct.border_image_repeat;
2294 let border_image_fill = border_style_struct.border_image_slice.fill;
2295 let border_image_slice = &border_style_struct.border_image_slice.offsets;
2296 let common = builder.common_properties(state, border_image_area.to_box2d(), style);
2297
2298 let stops = Vec::new();
2299 let mut width = border_image_size.width;
2300 let mut height = border_image_size.height;
2301 let node = self.fragment.base.tag.map(|tag| tag.node);
2302 let source = match builder
2303 .image_resolver
2304 .resolve_image(node, &border.border_image_source)
2305 {
2306 Err(_) => return false,
2307 Ok(ResolvedImage::Image { image, size, .. }) => {
2308 let scale = builder.device_pixel_ratio.get();
2309 let raster_size = Size2D::new(size.width * scale, size.height * scale).to_i32();
2310 let Some(key) =
2311 builder
2312 .image_resolver
2313 .image_key_from_cached_image(&image, raster_size, node)
2314 else {
2315 return false;
2316 };
2317
2318 if builder.paint_timing_handler.check_if_paintable(
2319 Box2D::from_size(size.cast_unit()),
2320 state.accumulated_opacity,
2321 ) {
2322 builder.mark_is_contentful();
2327 }
2328
2329 width = size.width;
2330 height = size.height;
2331 let image_rendering = style.get_image_rendering().to_webrender();
2332 NinePatchBorderSource::Image(key, image_rendering)
2333 },
2334 Ok(ResolvedImage::Gradient(gradient)) => {
2335 match gradient::build(style, gradient, border_image_size, builder) {
2336 WebRenderGradient::Linear(gradient) => {
2337 NinePatchBorderSource::Gradient(gradient)
2338 },
2339 WebRenderGradient::Radial(gradient) => {
2340 NinePatchBorderSource::RadialGradient(gradient)
2341 },
2342 WebRenderGradient::Conic(gradient) => {
2343 NinePatchBorderSource::ConicGradient(gradient)
2344 },
2345 }
2346 },
2347 Ok(ResolvedImage::Color(color)) => {
2348 let color = rgba(style.resolve_color(color));
2351 let gradient = builder.wr().create_gradient(
2352 Point2D::zero(),
2353 Point2D::zero(),
2354 vec![
2355 wr::GradientStop { offset: 0.0, color },
2356 wr::GradientStop { offset: 1.0, color },
2357 ],
2358 wr::ExtendMode::Clamp,
2359 );
2360 NinePatchBorderSource::Gradient(gradient)
2361 },
2362 };
2363
2364 let size = Size2D::new(width as i32, height as i32);
2365
2366 if size.is_empty() || border_image_size.is_empty() {
2369 return true;
2370 }
2371
2372 let details = BorderDetails::NinePatch(NinePatchBorder {
2373 source,
2374 width: size.width,
2375 height: size.height,
2376 slice: resolve_border_image_slice(border_image_slice, size),
2377 fill: border_image_fill,
2378 repeat_horizontal: border_image_repeat.0.to_webrender(),
2379 repeat_vertical: border_image_repeat.1.to_webrender(),
2380 });
2381 builder.wr().push_border(
2382 &common,
2383 border_image_area.to_box2d(),
2384 border_image_widths,
2385 details,
2386 );
2387 builder.wr().push_stops(&stops);
2388 true
2389 }
2390
2391 fn build_outline(&self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2392 let style = self.fragment.style();
2393 let outline = style.get_outline();
2394 if outline.outline_style.none_or_hidden() {
2395 return;
2396 }
2397 let width = outline.outline_width.0.to_f32_px();
2398 if width == 0.0 {
2399 return;
2400 }
2401 let offset = outline.outline_offset.to_f32_px() + width;
2410 let outline_rect = self.border_rect.inflate(
2411 offset.max(-self.border_rect.width() / 2.0 + width),
2412 offset.max(-self.border_rect.height() / 2.0 + width),
2413 );
2414 let common = builder.common_properties(state, outline_rect, style);
2415 let widths = SideOffsets2D::new_all_same(width);
2416 let border_style = match outline.outline_style {
2417 OutlineStyle::Auto => BorderStyle::Solid,
2420 OutlineStyle::BorderStyle(s) => s,
2421 };
2422 let side = self.build_border_side(BorderStyleColor {
2423 style: border_style,
2424 color: style.resolve_color(&outline.outline_color),
2425 });
2426 let details = wr::BorderDetails::Normal(wr::NormalBorder {
2427 top: side,
2428 right: side,
2429 bottom: side,
2430 left: side,
2431 radius: offset_radii(self.border_radius(), SideOffsets2D::new_all_same(offset)),
2432 do_aa: true,
2433 });
2434 builder
2435 .wr()
2436 .push_border(&common, outline_rect, widths, details)
2437 }
2438
2439 fn build_box_shadow(&self, builder: &mut DisplayListBuilder, state: &TraversalState) {
2440 let style = self.fragment.style();
2441 let box_shadows = &style.get_effects().box_shadow.0;
2442 if box_shadows.is_empty() {
2443 return;
2444 }
2445
2446 for box_shadow in box_shadows.iter().rev() {
2448 let (rect, clip_mode) = if box_shadow.inset {
2449 (*self.padding_rect(), BoxShadowClipMode::Inset)
2450 } else {
2451 (self.border_rect, BoxShadowClipMode::Outset)
2452 };
2453
2454 let offset = LayoutVector2D::new(
2455 box_shadow.base.horizontal.px(),
2456 box_shadow.base.vertical.px(),
2457 );
2458 let spread = box_shadow.spread.px();
2459 let blur = box_shadow.base.blur.px();
2460 let clip_rect = match clip_mode {
2461 BoxShadowClipMode::Inset => rect,
2463 BoxShadowClipMode::Outset => {
2466 let extra_size_from_blur = (blur * 3.0).ceil();
2467 rect.translate(offset)
2468 .inflate(spread, spread)
2469 .inflate(extra_size_from_blur, extra_size_from_blur)
2470 },
2471 };
2472 let border_radius = match clip_mode {
2473 BoxShadowClipMode::Inset => {
2474 offset_radii(self.border_radius(), -self.fragment.border.to_webrender())
2478 },
2479 BoxShadowClipMode::Outset => self.border_radius(),
2480 };
2481 let shadow_radius = offset_radii(
2482 border_radius,
2483 SideOffsets2D::new_all_same(match clip_mode {
2484 BoxShadowClipMode::Inset => -spread,
2485 BoxShadowClipMode::Outset => spread,
2486 }),
2487 );
2488 let common = builder.common_properties(state, clip_rect, style);
2489 builder.wr().push_box_shadow(
2490 &common,
2491 rect,
2492 offset,
2493 rgba(style.resolve_color(&box_shadow.base.color)),
2494 blur,
2495 spread,
2496 border_radius,
2497 shadow_radius,
2498 clip_mode,
2499 );
2500 }
2501 }
2502}
2503
2504fn rgba(color: AbsoluteColor) -> wr::ColorF {
2505 let rgba = color.to_color_space(ColorSpace::Srgb);
2506 wr::ColorF::new(
2507 rgba.components.0.clamp(0.0, 1.0),
2508 rgba.components.1.clamp(0.0, 1.0),
2509 rgba.components.2.clamp(0.0, 1.0),
2510 rgba.alpha.clamp(0.0, 1.0),
2511 )
2512}
2513
2514fn collect_glyphs(
2520 glyphs: &mut Vec<GlyphInstance>,
2521 shaped_text_slices: &[Arc<ShapedTextSlice>],
2522 mut baseline_origin: PhysicalPoint<Au>,
2523 justification_adjustment: Au,
2524) -> (Au, bool) {
2525 glyphs.clear();
2526 let mut largest_advance = Au::zero();
2527 let mut entirely_white_space = true;
2528
2529 for shaped_text_slice in shaped_text_slices {
2530 entirely_white_space &= shaped_text_slice.all_white_space();
2531 for glyph in shaped_text_slice.glyphs() {
2532 let glyph_offset = glyph.offset().unwrap_or(Point2D::zero());
2533 let point = LayoutPoint::new(
2534 baseline_origin.x.to_f32_px() + glyph_offset.x.to_f32_px(),
2535 baseline_origin.y.to_f32_px() + glyph_offset.y.to_f32_px(),
2536 );
2537 let glyph_instance = GlyphInstance {
2538 index: glyph.id(),
2539 point,
2540 };
2541 glyphs.push(glyph_instance);
2542
2543 if glyph.char_is_word_separator() {
2544 baseline_origin.x += justification_adjustment;
2545 }
2546
2547 let advance = glyph.advance();
2548 baseline_origin.x += advance;
2549 largest_advance.max_assign(advance);
2550 }
2551 }
2552 (largest_advance, entirely_white_space)
2553}
2554
2555fn offset_radii(mut radii: BorderRadius, offsets: LayoutSideOffsets) -> BorderRadius {
2559 let expand = |radius: &mut f32, offset: f32| {
2560 if offset < 0.0 {
2562 *radius = (*radius + offset).max(0.0);
2563 return;
2564 }
2565
2566 if *radius > 0.0 {
2571 *radius += offset;
2572 }
2573 };
2574 if offsets.left != 0.0 {
2575 expand(&mut radii.top_left.width, offsets.left);
2576 expand(&mut radii.bottom_left.width, offsets.left);
2577 }
2578 if offsets.right != 0.0 {
2579 expand(&mut radii.top_right.width, offsets.right);
2580 expand(&mut radii.bottom_right.width, offsets.right);
2581 }
2582 if offsets.top != 0.0 {
2583 expand(&mut radii.top_left.height, offsets.top);
2584 expand(&mut radii.top_right.height, offsets.top);
2585 }
2586 if offsets.bottom != 0.0 {
2587 expand(&mut radii.bottom_right.height, offsets.bottom);
2588 expand(&mut radii.bottom_left.height, offsets.bottom);
2589 }
2590 radii
2591}
2592
2593fn resolve_border_image_outset(
2595 outset: BorderImageOutset,
2596 border: SideOffsets2D<f32, LayoutPixel>,
2597) -> SideOffsets2D<f32, LayoutPixel> {
2598 fn image_outset_for_side(outset: NonNegativeLengthOrNumber, border_width: f32) -> f32 {
2599 match outset {
2600 NonNegativeLengthOrNumber::Length(length) => length.px(),
2601 NonNegativeLengthOrNumber::Number(factor) => border_width * factor.0,
2602 }
2603 }
2604
2605 SideOffsets2D::new(
2606 image_outset_for_side(outset.0, border.top),
2607 image_outset_for_side(outset.1, border.right),
2608 image_outset_for_side(outset.2, border.bottom),
2609 image_outset_for_side(outset.3, border.left),
2610 )
2611}
2612
2613fn resolve_border_image_width(
2615 width: &BorderImageWidth,
2616 border: SideOffsets2D<f32, LayoutPixel>,
2617 border_area: Size2D<f32, LayoutPixel>,
2618) -> SideOffsets2D<f32, LayoutPixel> {
2619 fn image_width_for_side(
2620 border_image_width: &BorderImageSideWidth,
2621 border_width: f32,
2622 total_length: f32,
2623 ) -> f32 {
2624 match border_image_width {
2625 BorderImageSideWidth::LengthPercentage(v) => {
2626 v.to_used_value(Au::from_f32_px(total_length)).to_f32_px()
2627 },
2628 BorderImageSideWidth::Number(x) => border_width * x.0,
2629 BorderImageSideWidth::Auto => border_width,
2630 }
2631 }
2632
2633 SideOffsets2D::new(
2634 image_width_for_side(&width.0, border.top, border_area.height),
2635 image_width_for_side(&width.1, border.right, border_area.width),
2636 image_width_for_side(&width.2, border.bottom, border_area.height),
2637 image_width_for_side(&width.3, border.left, border_area.width),
2638 )
2639}
2640
2641fn resolve_border_image_slice(
2643 border_image_slice: &StyleRect<NonNegative<NumberOrPercentage>>,
2644 size: Size2D<i32, UnknownUnit>,
2645) -> SideOffsets2D<i32, DevicePixel> {
2646 fn resolve_percentage(value: NonNegative<NumberOrPercentage>, length: i32) -> i32 {
2647 match value.0 {
2648 NumberOrPercentage::Percentage(p) => (p.0 * length as f32).round() as i32,
2649 NumberOrPercentage::Number(n) => n.round() as i32,
2650 }
2651 }
2652
2653 SideOffsets2D::new(
2654 resolve_percentage(border_image_slice.0, size.height),
2655 resolve_percentage(border_image_slice.1, size.width),
2656 resolve_percentage(border_image_slice.2, size.height),
2657 resolve_percentage(border_image_slice.3, size.width),
2658 )
2659}
2660
2661pub(super) fn normalize_radii(rect: &units::LayoutRect, radius: &mut wr::BorderRadius) {
2662 let f = (rect.width() / (radius.top_left.width + radius.top_right.width))
2670 .min(rect.width() / (radius.bottom_left.width + radius.bottom_right.width))
2671 .min(rect.height() / (radius.top_left.height + radius.bottom_left.height))
2672 .min(rect.height() / (radius.top_right.height + radius.bottom_right.height));
2673 if f < 1.0 {
2674 radius.top_left *= f;
2675 radius.top_right *= f;
2676 radius.bottom_right *= f;
2677 radius.bottom_left *= f;
2678 }
2679}
2680
2681pub(super) fn compute_margin_box_radius(
2689 radius: wr::BorderRadius,
2690 layout_rect: LayoutSize,
2691 fragment: &BoxFragment,
2692) -> wr::BorderRadius {
2693 let style = fragment.style();
2694 let margin = style.physical_margin();
2695 let adjust_radius = |radius: f32, margin: f32| -> f32 {
2696 if margin <= 0. || (radius / margin) >= 1. {
2697 (radius + margin).max(0.)
2698 } else {
2699 radius + (margin * (1. + (radius / margin - 1.).powf(3.)))
2700 }
2701 };
2702 let compute_margin_radius = |radius: LayoutSize,
2703 layout_rect: LayoutSize,
2704 margin: Size2D<LengthPercentageOrAuto, UnknownUnit>|
2705 -> LayoutSize {
2706 let zero = LengthPercentage::zero();
2707 let width = margin
2708 .width
2709 .auto_is(|| &zero)
2710 .to_used_value(Au::from_f32_px(layout_rect.width));
2711 let height = margin
2712 .height
2713 .auto_is(|| &zero)
2714 .to_used_value(Au::from_f32_px(layout_rect.height));
2715 LayoutSize::new(
2716 adjust_radius(radius.width, width.to_f32_px()),
2717 adjust_radius(radius.height, height.to_f32_px()),
2718 )
2719 };
2720 wr::BorderRadius {
2721 top_left: compute_margin_radius(
2722 radius.top_left,
2723 layout_rect,
2724 Size2D::new(margin.left, margin.top),
2725 ),
2726 top_right: compute_margin_radius(
2727 radius.top_right,
2728 layout_rect,
2729 Size2D::new(margin.right, margin.top),
2730 ),
2731 bottom_left: compute_margin_radius(
2732 radius.bottom_left,
2733 layout_rect,
2734 Size2D::new(margin.left, margin.bottom),
2735 ),
2736 bottom_right: compute_margin_radius(
2737 radius.bottom_right,
2738 layout_rect,
2739 Size2D::new(margin.right, margin.bottom),
2740 ),
2741 }
2742}
2743
2744impl BoxFragment {
2745 fn border_radius(&self) -> BorderRadius {
2746 let style = self.style();
2747 let border = style.get_border();
2748 if border.border_top_left_radius.0.is_zero() &&
2749 border.border_top_right_radius.0.is_zero() &&
2750 border.border_bottom_right_radius.0.is_zero() &&
2751 border.border_bottom_left_radius.0.is_zero()
2752 {
2753 return BorderRadius::zero();
2754 }
2755
2756 let border_rect = self.border_rect();
2757 let resolve =
2758 |radius: &LengthPercentage, box_size: Au| radius.to_used_value(box_size).to_f32_px();
2759 let corner = |corner: &style::values::computed::BorderCornerRadius| {
2760 Size2D::new(
2761 resolve(&corner.0.width.0, border_rect.size.width),
2762 resolve(&corner.0.height.0, border_rect.size.height),
2763 )
2764 };
2765
2766 let mut radius = wr::BorderRadius {
2767 top_left: corner(&border.border_top_left_radius),
2768 top_right: corner(&border.border_top_right_radius),
2769 bottom_right: corner(&border.border_bottom_right_radius),
2770 bottom_left: corner(&border.border_bottom_left_radius),
2771 };
2772
2773 normalize_radii(&border_rect.to_webrender(), &mut radius);
2774 radius
2775 }
2776}
2777
2778impl BaseFragment {
2779 fn visit_fragment(&self, builder: &mut DisplayListBuilder) {
2780 match self.status() {
2781 FragmentStatus::New => {
2782 builder.reflow_statistics.rebuilt_fragment_count += 1;
2783 self.set_status(FragmentStatus::Clean)
2784 },
2785 FragmentStatus::StyleChanged => {
2786 builder.reflow_statistics.restyle_fragment_count += 1;
2787 self.set_status(FragmentStatus::Clean)
2788 },
2789 FragmentStatus::OnlyDescendantsChanged => {
2790 builder.reflow_statistics.only_descendants_changed_count += 1;
2791 self.set_status(FragmentStatus::Clean)
2792 },
2793 FragmentStatus::Clean => {},
2794 }
2795 }
2796}