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