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