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