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