1use std::cell::Cell;
8use std::collections::HashMap;
9
10use bitflags::bitflags;
11use embedder_traits::ViewportDetails;
12use euclid::SideOffsets2D;
13use malloc_size_of_derive::MallocSizeOf;
14use rustc_hash::FxHashMap;
15use serde::{Deserialize, Serialize};
16use servo_base::Epoch;
17use servo_base::cross_process_instant::CrossProcessInstant;
18use servo_base::id::{LCPCandidateID, ScrollTreeNodeId};
19use servo_base::print_tree::PrintTree;
20use servo_geometry::FastLayoutTransform;
21use style::values::specified::Overflow;
22use webrender_api::units::{LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutVector2D};
23use webrender_api::{
24 ColorF, ExternalScrollId, PipelineId, PropertyBindingKey, ReferenceFrameKind, ScrollLocation,
25 SpatialId, StickyOffsetBounds, TransformStyle,
26};
27
28#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
32pub struct ScrollType(u8);
33
34bitflags! {
35 impl ScrollType: u8 {
36 const InputEvents = 1 << 0;
39 const Script = 1 << 1;
41 const Touch = 1 << 2;
45 }
46}
47
48impl From<Overflow> for ScrollType {
50 fn from(overflow: Overflow) -> Self {
51 match overflow {
52 Overflow::Hidden => ScrollType::Script,
53 Overflow::Scroll | Overflow::Auto => {
54 ScrollType::Script | ScrollType::InputEvents | ScrollType::Touch
55 },
56 Overflow::Visible | Overflow::Clip => ScrollType::empty(),
57 }
58 }
59}
60
61#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
63pub struct AxesScrollSensitivity {
64 pub x: ScrollType,
65 pub y: ScrollType,
66}
67
68#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
74pub enum TouchAction {
75 Auto,
79 PanX,
82 PanY,
85 None,
88}
89
90impl From<style::values::specified::TouchAction> for TouchAction {
91 fn from(stylo: style::values::specified::TouchAction) -> Self {
92 use style::values::specified::TouchAction as T;
93 if stylo.contains(T::NONE) {
94 return TouchAction::None;
95 }
96 if stylo.contains(T::AUTO) || stylo.contains(T::MANIPULATION) {
97 return TouchAction::Auto;
98 }
99 match (stylo.contains(T::PAN_X), stylo.contains(T::PAN_Y)) {
100 (true, true) => TouchAction::Auto,
101 (true, false) => TouchAction::PanX,
102 (false, true) => TouchAction::PanY,
103 (false, false) => TouchAction::None,
104 }
105 }
106}
107
108#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
109pub enum SpatialTreeNodeInfo {
110 ReferenceFrame(ReferenceFrameNodeInfo),
111 Scroll(ScrollableNodeInfo),
112 Sticky(StickyNodeInfo),
113}
114
115#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
116pub struct StickyNodeInfo {
117 pub frame_rect: LayoutRect,
118 pub margins: SideOffsets2D<Option<f32>, LayoutPixel>,
119 pub vertical_offset_bounds: StickyOffsetBounds,
120 pub horizontal_offset_bounds: StickyOffsetBounds,
121}
122
123impl StickyNodeInfo {
124 fn calculate_sticky_offset(
129 &self,
130 viewport_scroll_offset: &LayoutVector2D,
131 viewport_rect: &LayoutRect,
132 ) -> LayoutVector2D {
133 if self.margins.top.is_none() &&
134 self.margins.bottom.is_none() &&
135 self.margins.left.is_none() &&
136 self.margins.right.is_none()
137 {
138 return LayoutVector2D::zero();
139 }
140
141 let mut sticky_rect = self.frame_rect.translate(*viewport_scroll_offset);
147
148 let mut sticky_offset = LayoutVector2D::zero();
149 if let Some(margin) = self.margins.top {
150 let top_viewport_edge = viewport_rect.min.y + margin;
151 if sticky_rect.min.y < top_viewport_edge {
152 sticky_offset.y = top_viewport_edge - sticky_rect.min.y;
155 }
156 }
157
158 if sticky_offset.y <= 0.0 &&
163 let Some(margin) = self.margins.bottom
164 {
165 sticky_rect.min.y += sticky_offset.y;
171 sticky_rect.max.y += sticky_offset.y;
172
173 let bottom_viewport_edge = viewport_rect.max.y - margin;
178 if sticky_rect.max.y > bottom_viewport_edge {
179 sticky_offset.y += bottom_viewport_edge - sticky_rect.max.y;
180 }
181 }
182
183 if let Some(margin) = self.margins.left {
185 let left_viewport_edge = viewport_rect.min.x + margin;
186 if sticky_rect.min.x < left_viewport_edge {
187 sticky_offset.x = left_viewport_edge - sticky_rect.min.x;
188 }
189 }
190
191 if sticky_offset.x <= 0.0 &&
192 let Some(margin) = self.margins.right
193 {
194 sticky_rect.min.x += sticky_offset.x;
195 sticky_rect.max.x += sticky_offset.x;
196 let right_viewport_edge = viewport_rect.max.x - margin;
197 if sticky_rect.max.x > right_viewport_edge {
198 sticky_offset.x += right_viewport_edge - sticky_rect.max.x;
199 }
200 }
201
202 let clamp =
205 |value: f32, bounds: &StickyOffsetBounds| (value).max(bounds.min).min(bounds.max);
206 sticky_offset.y = clamp(sticky_offset.y, &self.vertical_offset_bounds);
207 sticky_offset.x = clamp(sticky_offset.x, &self.horizontal_offset_bounds);
208
209 sticky_offset
210 }
211}
212
213#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
214pub struct ReferenceFrameNodeInfo {
215 pub origin: LayoutPoint,
216 pub frame_origin_for_query: LayoutPoint,
218 pub transform_style: TransformStyle,
219 pub transform: FastLayoutTransform,
220 pub kind: ReferenceFrameKind,
221}
222
223#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
226pub struct ScrollableNodeInfo {
227 pub external_id: ExternalScrollId,
230
231 pub content_rect: LayoutRect,
233
234 pub clip_rect: LayoutRect,
236
237 pub scroll_sensitivity: AxesScrollSensitivity,
239
240 pub touch_action: TouchAction,
245
246 pub offset: LayoutVector2D,
248
249 pub offset_changed: Cell<bool>,
252}
253
254impl ScrollableNodeInfo {
255 fn scroll_to_offset(
256 &mut self,
257 new_offset: LayoutVector2D,
258 context: ScrollType,
259 ) -> Option<LayoutVector2D> {
260 if !self.scroll_sensitivity.x.contains(context) &&
261 !self.scroll_sensitivity.y.contains(context)
262 {
263 return None;
264 }
265
266 let scrollable_size = self.scrollable_size();
267 let original_layer_scroll_offset = self.offset;
268
269 if scrollable_size.width > 0. && self.scroll_sensitivity.x.contains(context) {
270 self.offset.x = new_offset.x.clamp(0.0, scrollable_size.width);
271 }
272
273 if scrollable_size.height > 0. && self.scroll_sensitivity.y.contains(context) {
274 self.offset.y = new_offset.y.clamp(0.0, scrollable_size.height);
275 }
276
277 if self.offset != original_layer_scroll_offset {
278 self.offset_changed.set(true);
279 Some(self.offset)
280 } else {
281 None
282 }
283 }
284
285 fn scroll_to_webrender_location(
286 &mut self,
287 scroll_location: ScrollLocation,
288 context: ScrollType,
289 ) -> Option<LayoutVector2D> {
290 if !self.scroll_sensitivity.x.contains(context) &&
291 !self.scroll_sensitivity.y.contains(context)
292 {
293 return None;
294 }
295
296 let delta = match scroll_location {
297 ScrollLocation::Delta(delta) => delta,
298 ScrollLocation::Start => {
299 if self.offset.y.round() <= 0.0 {
300 return None;
302 }
303
304 self.offset.y = 0.0;
305 self.offset_changed.set(true);
306 return Some(self.offset);
307 },
308 ScrollLocation::End => {
309 let end_pos = self.scrollable_size().height;
310 if self.offset.y.round() >= end_pos {
311 return None;
313 }
314
315 self.offset.y = end_pos;
316 self.offset_changed.set(true);
317 return Some(self.offset);
318 },
319 };
320
321 self.scroll_to_offset(self.offset + delta, context)
322 }
323}
324
325impl ScrollableNodeInfo {
326 fn scrollable_size(&self) -> LayoutSize {
327 self.content_rect.size() - self.clip_rect.size()
328 }
329}
330
331#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
338pub struct ScrollTreeNodeTransformationCache {
339 node_to_root_transform: FastLayoutTransform,
340 root_to_node_transform: Option<FastLayoutTransform>,
341 nearest_scrolling_ancestor_offset: LayoutVector2D,
342 nearest_scrolling_ancestor_viewport: LayoutRect,
343 cumulative_sticky_offsets: LayoutVector2D,
344}
345
346#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
347pub struct ScrollTreeNode {
350 pub parent: Option<ScrollTreeNodeId>,
353
354 pub children: Vec<ScrollTreeNodeId>,
356
357 pub webrender_id: Option<SpatialId>,
360
361 pub info: SpatialTreeNodeInfo,
364
365 transformation_cache: Cell<Option<ScrollTreeNodeTransformationCache>>,
368}
369
370impl ScrollTreeNode {
371 pub fn webrender_id(&self) -> SpatialId {
374 self.webrender_id
375 .expect("Should have called ScrollTree::build_display_list before querying SpatialId")
376 }
377
378 pub fn external_id(&self) -> Option<ExternalScrollId> {
380 match self.info {
381 SpatialTreeNodeInfo::Scroll(ref info) => Some(info.external_id),
382 _ => None,
383 }
384 }
385
386 pub fn offset(&self) -> Option<LayoutVector2D> {
388 match self.info {
389 SpatialTreeNodeInfo::Scroll(ref info) => Some(info.offset),
390 _ => None,
391 }
392 }
393
394 fn scroll(
398 &mut self,
399 scroll_location: ScrollLocation,
400 context: ScrollType,
401 ) -> Option<(ExternalScrollId, LayoutVector2D)> {
402 let SpatialTreeNodeInfo::Scroll(ref mut info) = self.info else {
403 return None;
404 };
405
406 info.scroll_to_webrender_location(scroll_location, context)
407 .map(|location| (info.external_id, location))
408 }
409
410 pub fn debug_print(&self, print_tree: &mut PrintTree, node_index: usize) {
411 match &self.info {
412 SpatialTreeNodeInfo::ReferenceFrame(info) => {
413 print_tree.new_level(format!(
414 "Reference Frame({node_index}): webrender_id={:?}\
415 \norigin: {:?}\
416 \ntransform_style: {:?}\
417 \ntransform: {:?}\
418 \nkind: {:?}",
419 self.webrender_id, info.origin, info.transform_style, info.transform, info.kind,
420 ));
421 },
422 SpatialTreeNodeInfo::Scroll(info) => {
423 print_tree.new_level(format!(
424 "Scroll Frame({node_index}): webrender_id={:?}\
425 \nexternal_id: {:?}\
426 \ncontent_rect: {:?}\
427 \nclip_rect: {:?}\
428 \nscroll_sensitivity: {:?}\
429 \noffset: {:?}",
430 self.webrender_id,
431 info.external_id,
432 info.content_rect,
433 info.clip_rect,
434 info.scroll_sensitivity,
435 info.offset,
436 ));
437 },
438 SpatialTreeNodeInfo::Sticky(info) => {
439 print_tree.new_level(format!(
440 "Sticky Frame({node_index}): webrender_id={:?}\
441 \nframe_rect: {:?}\
442 \nmargins: {:?}\
443 \nhorizontal_offset_bounds: {:?}\
444 \nvertical_offset_bounds: {:?}",
445 self.webrender_id,
446 info.frame_rect,
447 info.margins,
448 info.horizontal_offset_bounds,
449 info.vertical_offset_bounds,
450 ));
451 },
452 };
453 }
454
455 fn invalidate_cached_transforms(&self, scroll_tree: &ScrollTree, ancestors_invalid: bool) {
456 let node_invalid = match &self.info {
457 SpatialTreeNodeInfo::Scroll(info) => info.offset_changed.take(),
458 _ => false,
459 };
460
461 let invalid = node_invalid || ancestors_invalid;
462 if invalid {
463 self.transformation_cache.set(None);
464 }
465
466 for child_id in &self.children {
467 scroll_tree
468 .get_node(*child_id)
469 .invalidate_cached_transforms(scroll_tree, invalid);
470 }
471 }
472}
473
474#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
478pub struct ScrollTree {
479 pub nodes: Vec<ScrollTreeNode>,
483}
484
485impl ScrollTree {
486 pub fn add_scroll_tree_node(
488 &mut self,
489 parent: Option<ScrollTreeNodeId>,
490 info: SpatialTreeNodeInfo,
491 ) -> ScrollTreeNodeId {
492 self.nodes.push(ScrollTreeNode {
493 parent,
494 children: Vec::new(),
495 webrender_id: None,
496 info,
497 transformation_cache: Cell::default(),
498 });
499
500 let new_node_id = ScrollTreeNodeId {
501 index: self.nodes.len() - 1,
502 };
503
504 if let Some(parent_id) = parent {
505 self.get_node_mut(parent_id).children.push(new_node_id);
506 }
507
508 new_node_id
509 }
510
511 pub fn update_mapping(&mut self, mapping: Vec<SpatialId>) {
514 for (spatial_id, node) in mapping.into_iter().zip(self.nodes.iter_mut()) {
515 node.webrender_id = Some(spatial_id);
516 }
517 }
518
519 pub fn get_node_mut(&mut self, id: ScrollTreeNodeId) -> &mut ScrollTreeNode {
521 &mut self.nodes[id.index]
522 }
523
524 pub fn get_node(&self, id: ScrollTreeNodeId) -> &ScrollTreeNode {
526 &self.nodes[id.index]
527 }
528
529 pub fn webrender_id(&self, id: ScrollTreeNodeId) -> SpatialId {
532 self.get_node(id).webrender_id()
533 }
534
535 pub fn scroll_node_or_ancestor_inner(
536 &mut self,
537 scroll_node_id: ScrollTreeNodeId,
538 scroll_location: ScrollLocation,
539 context: ScrollType,
540 ) -> Option<(ExternalScrollId, LayoutVector2D)> {
541 let parent = {
542 let node = &mut self.get_node_mut(scroll_node_id);
543 let result = node.scroll(scroll_location, context);
544 if result.is_some() {
545 return result;
546 }
547 node.parent
548 };
549
550 parent
551 .and_then(|parent| self.scroll_node_or_ancestor_inner(parent, scroll_location, context))
552 }
553
554 fn node_with_external_scroll_node_id(
555 &self,
556 external_id: ExternalScrollId,
557 ) -> Option<ScrollTreeNodeId> {
558 self.nodes
559 .iter()
560 .enumerate()
561 .find_map(|(index, node)| match &node.info {
562 SpatialTreeNodeInfo::Scroll(info) if info.external_id == external_id => {
563 Some(ScrollTreeNodeId { index })
564 },
565 _ => None,
566 })
567 }
568
569 pub fn touch_action_and_scrollable_axes_for(
573 &self,
574 external_id: ExternalScrollId,
575 ) -> Option<(TouchAction, bool, bool)> {
576 let node_id = self.node_with_external_scroll_node_id(external_id)?;
577 let SpatialTreeNodeInfo::Scroll(info) = &self.get_node(node_id).info else {
578 return None;
579 };
580 let scrollable_size = info.scrollable_size();
581 Some((
582 info.touch_action,
583 scrollable_size.width > 0.,
584 scrollable_size.height > 0.,
585 ))
586 }
587
588 pub fn scroll_node_or_ancestor(
593 &mut self,
594 external_id: ExternalScrollId,
595 scroll_location: ScrollLocation,
596 context: ScrollType,
597 ) -> Option<(ExternalScrollId, LayoutVector2D)> {
598 let scroll_node_id = self.node_with_external_scroll_node_id(external_id)?;
599 let result = self.scroll_node_or_ancestor_inner(scroll_node_id, scroll_location, context);
600 if result.is_some() {
601 self.invalidate_cached_transforms();
602 }
603 result
604 }
605
606 pub fn set_scroll_offset_for_node_with_external_scroll_id(
609 &mut self,
610 external_scroll_id: ExternalScrollId,
611 offset: LayoutVector2D,
612 context: ScrollType,
613 ) -> Option<LayoutVector2D> {
614 let result = self.nodes.iter_mut().find_map(|node| match node.info {
615 SpatialTreeNodeInfo::Scroll(ref mut scroll_info)
616 if scroll_info.external_id == external_scroll_id =>
617 {
618 scroll_info.scroll_to_offset(offset, context)
619 },
620 _ => None,
621 });
622
623 if result.is_some() {
624 self.invalidate_cached_transforms();
625 }
626
627 result
628 }
629
630 pub fn set_all_scroll_offsets(
635 &mut self,
636 offsets: &FxHashMap<ExternalScrollId, LayoutVector2D>,
637 ) -> FxHashMap<ExternalScrollId, LayoutVector2D> {
638 let mut result = FxHashMap::default();
639 for node in self.nodes.iter_mut() {
640 if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info &&
641 let Some(offset) = offsets.get(&scroll_info.external_id) &&
642 let Some(result_offset) =
643 scroll_info.scroll_to_offset(*offset, ScrollType::Script)
644 {
645 result.insert(scroll_info.external_id, result_offset);
646 }
647 }
648
649 if !result.is_empty() {
650 self.invalidate_cached_transforms();
651 }
652
653 result
654 }
655
656 pub fn reset_all_scroll_offsets(&mut self) {
658 for node in self.nodes.iter_mut() {
659 if let SpatialTreeNodeInfo::Scroll(ref mut scroll_info) = node.info {
660 scroll_info.scroll_to_offset(LayoutVector2D::zero(), ScrollType::Script);
661 }
662 }
663
664 self.invalidate_cached_transforms();
665 }
666
667 pub fn scroll_offsets(&self) -> FxHashMap<ExternalScrollId, LayoutVector2D> {
670 HashMap::from_iter(self.nodes.iter().filter_map(|node| match node.info {
671 SpatialTreeNodeInfo::Scroll(ref scroll_info) => {
672 Some((scroll_info.external_id, scroll_info.offset))
673 },
674 _ => None,
675 }))
676 }
677
678 pub fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D> {
681 self.nodes.iter().find_map(|node| match node.info {
682 SpatialTreeNodeInfo::Scroll(ref info) if info.external_id == id => Some(info.offset),
683 _ => None,
684 })
685 }
686
687 pub fn cumulative_node_to_root_transform(
690 &self,
691 node_id: ScrollTreeNodeId,
692 ) -> FastLayoutTransform {
693 self.cumulative_node_transform(node_id)
694 .node_to_root_transform
695 }
696
697 pub fn cumulative_root_to_node_transform(
701 &self,
702 node_id: ScrollTreeNodeId,
703 ) -> Option<FastLayoutTransform> {
704 self.cumulative_node_transform(node_id)
705 .root_to_node_transform
706 }
707
708 pub fn reference_frame_offset(&self, node_id: ScrollTreeNodeId) -> LayoutPoint {
711 let mut maybe_node_id = Some(node_id);
712 while let Some(node_id) = maybe_node_id {
713 let node = self.get_node(node_id);
714 if let SpatialTreeNodeInfo::ReferenceFrame(reference_frame) = &node.info {
715 return reference_frame.frame_origin_for_query;
716 }
717 maybe_node_id = node.parent;
718 }
719 Default::default()
720 }
721
722 pub fn cumulative_sticky_offsets(&self, node_id: ScrollTreeNodeId) -> LayoutVector2D {
725 self.cumulative_node_transform(node_id)
726 .cumulative_sticky_offsets
727 }
728
729 #[servo_tracing::instrument(name = "ScrollTree::cumulative_node_transform", skip_all)]
730 fn cumulative_node_transform(
731 &self,
732 node_id: ScrollTreeNodeId,
733 ) -> ScrollTreeNodeTransformationCache {
734 let node = self.get_node(node_id);
735 if let Some(cached_transforms) = node.transformation_cache.get() {
736 return cached_transforms;
737 }
738
739 let transforms = self.cumulative_node_transform_inner(node);
740 node.transformation_cache.set(Some(transforms));
741 transforms
742 }
743
744 #[servo_tracing::instrument(name = "ScrollTree::cumulative_node_transform_inner", skip_all)]
746 fn cumulative_node_transform_inner(
747 &self,
748 node: &ScrollTreeNode,
749 ) -> ScrollTreeNodeTransformationCache {
750 let parent_transforms = node
751 .parent
752 .map(|parent_id| self.cumulative_node_transform(parent_id))
753 .unwrap_or_default();
754
755 let node_to_root_transform = |node_to_parent_transform: FastLayoutTransform| {
756 node_to_parent_transform.then(&parent_transforms.node_to_root_transform)
757 };
758 let root_to_node_transform = |parent_to_node_transform: FastLayoutTransform| {
759 parent_transforms
760 .root_to_node_transform
761 .map_or(parent_to_node_transform, |parent_transform| {
762 parent_transform.then(&parent_to_node_transform)
763 })
764 };
765
766 match &node.info {
767 SpatialTreeNodeInfo::ReferenceFrame(info) => {
768 let offset = info.frame_origin_for_query.to_vector();
771 let node_to_parent_transform =
772 info.transform.pre_translate(-offset).then_translate(offset);
773 let parent_to_node_transform = info.transform.inverse().map(|inverse_transform| {
774 FastLayoutTransform::Offset(-info.origin.to_vector()).then(&inverse_transform)
775 });
776 ScrollTreeNodeTransformationCache {
777 node_to_root_transform: node_to_root_transform(node_to_parent_transform),
778 root_to_node_transform: parent_to_node_transform.map(root_to_node_transform),
779 nearest_scrolling_ancestor_viewport: parent_transforms
780 .nearest_scrolling_ancestor_viewport
781 .translate(-info.origin.to_vector()),
782 nearest_scrolling_ancestor_offset: parent_transforms
783 .nearest_scrolling_ancestor_offset,
784 cumulative_sticky_offsets: parent_transforms.cumulative_sticky_offsets,
785 }
786 },
787 SpatialTreeNodeInfo::Scroll(info) => {
788 let node_to_parent_transform = FastLayoutTransform::Offset(-info.offset);
789 let parent_to_node_transform = node_to_parent_transform.inverse();
790 ScrollTreeNodeTransformationCache {
791 node_to_root_transform: node_to_root_transform(node_to_parent_transform),
792 root_to_node_transform: parent_to_node_transform.map(root_to_node_transform),
793 nearest_scrolling_ancestor_viewport: info.clip_rect,
794 nearest_scrolling_ancestor_offset: -info.offset,
795 cumulative_sticky_offsets: parent_transforms.cumulative_sticky_offsets,
796 }
797 },
798
799 SpatialTreeNodeInfo::Sticky(info) => {
800 let offset = info.calculate_sticky_offset(
801 &parent_transforms.nearest_scrolling_ancestor_offset,
802 &parent_transforms.nearest_scrolling_ancestor_viewport,
803 );
804 let node_to_parent_transform = FastLayoutTransform::Offset(offset);
805 let parent_to_node_transform = node_to_parent_transform.inverse();
806 ScrollTreeNodeTransformationCache {
807 node_to_root_transform: node_to_root_transform(node_to_parent_transform),
808 root_to_node_transform: parent_to_node_transform.map(root_to_node_transform),
809 nearest_scrolling_ancestor_viewport: parent_transforms
810 .nearest_scrolling_ancestor_viewport,
811 nearest_scrolling_ancestor_offset: parent_transforms
812 .nearest_scrolling_ancestor_offset +
813 offset,
814 cumulative_sticky_offsets: parent_transforms.cumulative_sticky_offsets + offset,
815 }
816 },
817 }
818 }
819
820 #[servo_tracing::instrument(name = "ScrollTree::invalidate_cached_transforms", skip_all)]
821 fn invalidate_cached_transforms(&self) {
822 let Some(root_node) = self.nodes.first() else {
823 return;
824 };
825 root_node.invalidate_cached_transforms(self, false );
826 }
827
828 fn external_scroll_id_for_scroll_tree_node(
829 &self,
830 id: ScrollTreeNodeId,
831 ) -> Option<ExternalScrollId> {
832 let mut maybe_node = Some(self.get_node(id));
833
834 while let Some(node) = maybe_node {
835 if let Some(external_scroll_id) = node.external_id() {
836 return Some(external_scroll_id);
837 }
838 maybe_node = node.parent.map(|id| self.get_node(id));
839 }
840
841 None
842 }
843}
844
845type AdjacencyListForPrint = Vec<Vec<ScrollTreeNodeId>>;
851
852impl ScrollTree {
856 fn nodes_in_adjacency_list(&self) -> AdjacencyListForPrint {
857 let mut adjacency_list: AdjacencyListForPrint = vec![Default::default(); self.nodes.len()];
858
859 for (node_index, node) in self.nodes.iter().enumerate() {
860 let current_id = ScrollTreeNodeId { index: node_index };
861 if let Some(parent_id) = node.parent {
862 adjacency_list[parent_id.index].push(current_id);
863 }
864 }
865
866 adjacency_list
867 }
868
869 fn debug_print_traversal(
870 &self,
871 print_tree: &mut PrintTree,
872 current_id: ScrollTreeNodeId,
873 adjacency_list: &[Vec<ScrollTreeNodeId>],
874 ) {
875 for node_id in &adjacency_list[current_id.index] {
876 self.nodes[node_id.index].debug_print(print_tree, node_id.index);
877 self.debug_print_traversal(print_tree, *node_id, adjacency_list);
878 }
879 print_tree.end_level();
880 }
881
882 pub fn debug_print(&self) {
889 let mut print_tree = PrintTree::new("Scroll Tree");
890
891 let adj_list = self.nodes_in_adjacency_list();
892 let root_id = ScrollTreeNodeId { index: 0 };
893
894 self.nodes[root_id.index].debug_print(&mut print_tree, root_id.index);
895 self.debug_print_traversal(&mut print_tree, root_id, &adj_list);
896 print_tree.end_level();
897 }
898}
899
900#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
908pub struct PaintTimingReport(u8);
909
910bitflags! {
911 impl PaintTimingReport: u8 {
912 const FirstPaint = 1 << 0;
914 const FirstContentfulPaint = 1 << 1;
916 }
917}
918
919#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
921pub struct PaintTimingInfo {
922 pub rendering_update_end_time: CrossProcessInstant,
924 pub implementation_defined_presentation_time: Option<CrossProcessInstant>,
926}
927
928impl PaintTimingInfo {
929 pub fn now() -> Self {
930 Self {
931 rendering_update_end_time: CrossProcessInstant::now(),
932 implementation_defined_presentation_time: None,
933 }
934 }
935
936 pub fn with_presentation_time(self, presentation_time: CrossProcessInstant) -> Self {
939 Self {
940 implementation_defined_presentation_time: Some(presentation_time),
941 ..self
942 }
943 }
944
945 pub fn paint_time(&self) -> CrossProcessInstant {
947 self.rendering_update_end_time
950 }
951
952 pub fn presentation_time(&self) -> Option<CrossProcessInstant> {
954 self.implementation_defined_presentation_time
958 }
959
960 pub fn default_paint_timestamp(&self) -> CrossProcessInstant {
962 self.implementation_defined_presentation_time
965 .unwrap_or(self.rendering_update_end_time)
966 }
967}
968
969#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
972pub struct PaintDisplayListInfo {
973 pub pipeline_id: PipelineId,
975
976 pub viewport_details: ViewportDetails,
979
980 pub content_size: LayoutSize,
982
983 pub epoch: Epoch,
985
986 pub scroll_tree: ScrollTree,
989
990 pub root_reference_frame_id: ScrollTreeNodeId,
993
994 pub root_scroll_node_id: ScrollTreeNodeId,
997
998 pub first_reflow: bool,
1001
1002 pub paint_timing_report: PaintTimingReport,
1004
1005 pub paint_timing_info: PaintTimingInfo,
1007
1008 pub lcp_candidate: Option<(LCPCandidateID, usize)>,
1011
1012 pub caret_property_binding: Option<(PropertyBindingKey<ColorF>, ColorF)>,
1015}
1016
1017impl PaintDisplayListInfo {
1018 pub fn new(
1021 viewport_details: ViewportDetails,
1022 content_size: LayoutSize,
1023 pipeline_id: PipelineId,
1024 epoch: Epoch,
1025 viewport_scroll_sensitivity: AxesScrollSensitivity,
1026 first_reflow: bool,
1027 ) -> Self {
1028 let mut scroll_tree = ScrollTree::default();
1029 let root_reference_frame_id = scroll_tree.add_scroll_tree_node(
1030 None,
1031 SpatialTreeNodeInfo::ReferenceFrame(ReferenceFrameNodeInfo {
1032 origin: Default::default(),
1033 frame_origin_for_query: Default::default(),
1034 transform_style: TransformStyle::Flat,
1035 transform: FastLayoutTransform::identity(),
1036 kind: ReferenceFrameKind::default(),
1037 }),
1038 );
1039 let root_scroll_node_id = scroll_tree.add_scroll_tree_node(
1040 Some(root_reference_frame_id),
1041 SpatialTreeNodeInfo::Scroll(ScrollableNodeInfo {
1042 external_id: ExternalScrollId(0, pipeline_id),
1043 content_rect: LayoutRect::from_origin_and_size(LayoutPoint::zero(), content_size),
1044 clip_rect: LayoutRect::from_origin_and_size(
1045 LayoutPoint::zero(),
1046 viewport_details.layout_size(),
1047 ),
1048 scroll_sensitivity: viewport_scroll_sensitivity,
1049 touch_action: TouchAction::Auto,
1050 offset: LayoutVector2D::zero(),
1051 offset_changed: Cell::new(false),
1052 }),
1053 );
1054
1055 PaintDisplayListInfo {
1056 pipeline_id,
1057 viewport_details,
1058 content_size,
1059 epoch,
1060 scroll_tree,
1061 root_reference_frame_id,
1062 root_scroll_node_id,
1063 first_reflow,
1064 lcp_candidate: None,
1065 paint_timing_report: PaintTimingReport::default(),
1066 paint_timing_info: PaintTimingInfo::now(),
1067 caret_property_binding: Default::default(),
1068 }
1069 }
1070
1071 pub fn external_scroll_id_for_scroll_tree_node(
1072 &self,
1073 id: ScrollTreeNodeId,
1074 ) -> ExternalScrollId {
1075 self.scroll_tree
1076 .external_scroll_id_for_scroll_tree_node(id)
1077 .unwrap_or(ExternalScrollId(0, self.pipeline_id))
1078 }
1079}