aboutsummaryrefslogtreecommitdiffstats
path: root/components/shared/compositing/display_list.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/shared/compositing/display_list.rs')
-rw-r--r--components/shared/compositing/display_list.rs373
1 files changed, 373 insertions, 0 deletions
diff --git a/components/shared/compositing/display_list.rs b/components/shared/compositing/display_list.rs
new file mode 100644
index 00000000000..2b7883c3ce1
--- /dev/null
+++ b/components/shared/compositing/display_list.rs
@@ -0,0 +1,373 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+//! Defines data structures which are consumed by the Compositor.
+
+use base::id::ScrollTreeNodeId;
+use embedder_traits::Cursor;
+use serde::{Deserialize, Serialize};
+use style::values::specified::Overflow;
+use webrender_api::units::{LayoutSize, LayoutVector2D};
+use webrender_api::{Epoch, ExternalScrollId, PipelineId, ScrollLocation, SpatialId};
+
+/// The scroll sensitivity of a scroll node in a particular axis ie whether it can be scrolled due to
+/// input events and script events or only script events.
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub enum ScrollSensitivity {
+ /// This node can be scrolled by input and script events.
+ ScriptAndInputEvents,
+ /// This node can only be scrolled by script events.
+ Script,
+ /// This node cannot be scrolled.
+ None,
+}
+
+/// Convert [Overflow] to [ScrollSensitivity].
+impl From<Overflow> for ScrollSensitivity {
+ fn from(overflow: Overflow) -> Self {
+ match overflow {
+ Overflow::Hidden => ScrollSensitivity::Script,
+ Overflow::Scroll | Overflow::Auto => ScrollSensitivity::ScriptAndInputEvents,
+ Overflow::Visible | Overflow::Clip => ScrollSensitivity::None,
+ }
+ }
+}
+
+/// The [ScrollSensitivity] of particular node in the vertical and horizontal axes.
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
+pub struct AxesScrollSensitivity {
+ pub x: ScrollSensitivity,
+ pub y: ScrollSensitivity,
+}
+
+/// Information that Servo keeps alongside WebRender display items
+/// in order to add more context to hit test results.
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub struct HitTestInfo {
+ /// The id of the node of this hit test item.
+ pub node: u64,
+
+ /// The cursor of this node's hit test item.
+ pub cursor: Option<Cursor>,
+
+ /// The id of the [ScrollTree] associated with this hit test item.
+ pub scroll_tree_node: ScrollTreeNodeId,
+}
+
+/// Data stored for nodes in the [ScrollTree] that actually scroll,
+/// as opposed to reference frames and sticky nodes which do not.
+#[derive(Debug, Deserialize, Serialize)]
+pub struct ScrollableNodeInfo {
+ /// The external scroll id of this node, used to track
+ /// it between successive re-layouts.
+ pub external_id: ExternalScrollId,
+
+ /// Amount that this `ScrollableNode` can scroll in both directions.
+ pub scrollable_size: LayoutSize,
+
+ /// Whether this `ScrollableNode` is sensitive to input events.
+ pub scroll_sensitivity: AxesScrollSensitivity,
+
+ /// The current offset of this scroll node.
+ pub offset: LayoutVector2D,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+/// A node in a tree of scroll nodes. This may either be a scrollable
+/// node which responds to scroll events or a non-scrollable one.
+pub struct ScrollTreeNode {
+ /// The index of the parent of this node in the tree. If this is
+ /// None then this is the root node.
+ pub parent: Option<ScrollTreeNodeId>,
+
+ /// Scrolling data which will not be None if this is a scrolling node.
+ pub scroll_info: Option<ScrollableNodeInfo>,
+}
+
+impl ScrollTreeNode {
+ /// Get the external id of this node.
+ pub fn external_id(&self) -> Option<ExternalScrollId> {
+ self.scroll_info.as_ref().map(|info| info.external_id)
+ }
+
+ /// Get the offset id of this node if it applies.
+ pub fn offset(&self) -> Option<LayoutVector2D> {
+ self.scroll_info.as_ref().map(|info| info.offset)
+ }
+
+ /// Set the offset for this node, returns false if this was a
+ /// non-scrolling node for which you cannot set the offset.
+ pub fn set_offset(&mut self, new_offset: LayoutVector2D) -> bool {
+ match self.scroll_info {
+ Some(ref mut info) => {
+ let scrollable_width = info.scrollable_size.width;
+ let scrollable_height = info.scrollable_size.height;
+
+ if scrollable_width > 0. {
+ info.offset.x = (new_offset.x).min(0.0).max(-scrollable_width);
+ }
+
+ if scrollable_height > 0. {
+ info.offset.y = (new_offset.y).min(0.0).max(-scrollable_height);
+ }
+ true
+ },
+ _ => false,
+ }
+ }
+
+ /// Scroll this node given a WebRender ScrollLocation. Returns a tuple that can
+ /// be used to scroll an individual WebRender scroll frame if the operation
+ /// actually changed an offset.
+ pub fn scroll(
+ &mut self,
+ scroll_location: ScrollLocation,
+ ) -> Option<(ExternalScrollId, LayoutVector2D)> {
+ let info = match self.scroll_info {
+ Some(ref mut data) => data,
+ None => return None,
+ };
+
+ if info.scroll_sensitivity.x != ScrollSensitivity::ScriptAndInputEvents &&
+ info.scroll_sensitivity.y != ScrollSensitivity::ScriptAndInputEvents
+ {
+ return None;
+ }
+
+ let delta = match scroll_location {
+ ScrollLocation::Delta(delta) => delta,
+ ScrollLocation::Start => {
+ if info.offset.y.round() >= 0.0 {
+ // Nothing to do on this layer.
+ return None;
+ }
+
+ info.offset.y = 0.0;
+ return Some((info.external_id, info.offset));
+ },
+ ScrollLocation::End => {
+ let end_pos = -info.scrollable_size.height;
+ if info.offset.y.round() <= end_pos {
+ // Nothing to do on this layer.
+ return None;
+ }
+
+ info.offset.y = end_pos;
+ return Some((info.external_id, info.offset));
+ },
+ };
+
+ let scrollable_width = info.scrollable_size.width;
+ let scrollable_height = info.scrollable_size.height;
+ let original_layer_scroll_offset = info.offset;
+
+ if scrollable_width > 0. &&
+ info.scroll_sensitivity.x == ScrollSensitivity::ScriptAndInputEvents
+ {
+ info.offset.x = (info.offset.x + delta.x).min(0.0).max(-scrollable_width);
+ }
+
+ if scrollable_height > 0. &&
+ info.scroll_sensitivity.y == ScrollSensitivity::ScriptAndInputEvents
+ {
+ info.offset.y = (info.offset.y + delta.y).min(0.0).max(-scrollable_height);
+ }
+
+ if info.offset != original_layer_scroll_offset {
+ Some((info.external_id, info.offset))
+ } else {
+ None
+ }
+ }
+}
+
+/// A tree of spatial nodes, which mirrors the spatial nodes in the WebRender
+/// display list, except these are used to scrolling in the compositor so that
+/// new offsets can be sent to WebRender.
+#[derive(Debug, Default, Deserialize, Serialize)]
+pub struct ScrollTree {
+ /// A list of compositor-side scroll nodes that describe the tree
+ /// of WebRender spatial nodes, used by the compositor to scroll the
+ /// contents of the display list.
+ pub nodes: Vec<ScrollTreeNode>,
+}
+
+impl ScrollTree {
+ /// Add a scroll node to this ScrollTree returning the id of the new node.
+ pub fn add_scroll_tree_node(
+ &mut self,
+ parent: Option<&ScrollTreeNodeId>,
+ spatial_id: SpatialId,
+ scroll_info: Option<ScrollableNodeInfo>,
+ ) -> ScrollTreeNodeId {
+ self.nodes.push(ScrollTreeNode {
+ parent: parent.cloned(),
+ scroll_info,
+ });
+ ScrollTreeNodeId {
+ index: self.nodes.len() - 1,
+ spatial_id,
+ }
+ }
+
+ /// Get a mutable reference to the node with the given index.
+ pub fn get_node_mut(&mut self, id: &ScrollTreeNodeId) -> &mut ScrollTreeNode {
+ &mut self.nodes[id.index]
+ }
+
+ /// Get an immutable reference to the node with the given index.
+ pub fn get_node(&mut self, id: &ScrollTreeNodeId) -> &ScrollTreeNode {
+ &self.nodes[id.index]
+ }
+
+ /// Scroll the given scroll node on this scroll tree. If the node cannot be scrolled,
+ /// because it isn't a scrollable node or it's already scrolled to the maximum scroll
+ /// extent, try to scroll an ancestor of this node. Returns the node scrolled and the
+ /// new offset if a scroll was performed, otherwise returns None.
+ pub fn scroll_node_or_ancestor(
+ &mut self,
+ scroll_node_id: &ScrollTreeNodeId,
+ scroll_location: ScrollLocation,
+ ) -> Option<(ExternalScrollId, LayoutVector2D)> {
+ let parent = {
+ let node = &mut self.get_node_mut(scroll_node_id);
+ let result = node.scroll(scroll_location);
+ if result.is_some() {
+ return result;
+ }
+ node.parent
+ };
+
+ parent.and_then(|parent| self.scroll_node_or_ancestor(&parent, scroll_location))
+ }
+
+ /// Given an [`ExternalScrollId`] and an offset, update the scroll offset of the scroll node
+ /// with the given id.
+ pub fn set_scroll_offsets_for_node_with_external_scroll_id(
+ &mut self,
+ external_scroll_id: ExternalScrollId,
+ offset: LayoutVector2D,
+ ) -> bool {
+ for node in self.nodes.iter_mut() {
+ match node.scroll_info {
+ Some(ref mut scroll_info) if scroll_info.external_id == external_scroll_id => {
+ scroll_info.offset = offset;
+ return true;
+ },
+ _ => {},
+ }
+ }
+ false
+ }
+}
+
+/// A data structure which stores compositor-side information about
+/// display lists sent to the compositor.
+#[derive(Debug, Deserialize, Serialize)]
+pub struct CompositorDisplayListInfo {
+ /// The WebRender [PipelineId] of this display list.
+ pub pipeline_id: PipelineId,
+
+ /// The size of the viewport that this display list renders into.
+ pub viewport_size: LayoutSize,
+
+ /// The size of this display list's content.
+ pub content_size: LayoutSize,
+
+ /// The epoch of the display list.
+ pub epoch: Epoch,
+
+ /// An array of `HitTestInfo` which is used to store information
+ /// to assist the compositor to take various actions (set the cursor,
+ /// scroll without layout) using a WebRender hit test result.
+ pub hit_test_info: Vec<HitTestInfo>,
+
+ /// A ScrollTree used by the compositor to scroll the contents of the
+ /// display list.
+ pub scroll_tree: ScrollTree,
+
+ /// The `ScrollTreeNodeId` of the root reference frame of this info's scroll
+ /// tree.
+ pub root_reference_frame_id: ScrollTreeNodeId,
+
+ /// The `ScrollTreeNodeId` of the topmost scrolling frame of this info's scroll
+ /// tree.
+ pub root_scroll_node_id: ScrollTreeNodeId,
+
+ /// Contentful paint i.e. whether the display list contains items of type
+ /// text, image, non-white canvas or SVG). Used by metrics.
+ /// See <https://w3c.github.io/paint-timing/#first-contentful-paint>.
+ pub is_contentful: bool,
+
+ /// Whether the first layout or a subsequent (incremental) layout triggered this
+ /// display list creation.
+ pub first_reflow: bool,
+}
+
+impl CompositorDisplayListInfo {
+ /// Create a new CompositorDisplayListInfo with the root reference frame
+ /// and scroll frame already added to the scroll tree.
+ pub fn new(
+ viewport_size: LayoutSize,
+ content_size: LayoutSize,
+ pipeline_id: PipelineId,
+ epoch: Epoch,
+ viewport_scroll_sensitivity: AxesScrollSensitivity,
+ first_reflow: bool,
+ ) -> Self {
+ let mut scroll_tree = ScrollTree::default();
+ let root_reference_frame_id = scroll_tree.add_scroll_tree_node(
+ None,
+ SpatialId::root_reference_frame(pipeline_id),
+ None,
+ );
+ let root_scroll_node_id = scroll_tree.add_scroll_tree_node(
+ Some(&root_reference_frame_id),
+ SpatialId::root_scroll_node(pipeline_id),
+ Some(ScrollableNodeInfo {
+ external_id: ExternalScrollId(0, pipeline_id),
+ scrollable_size: content_size - viewport_size,
+ scroll_sensitivity: viewport_scroll_sensitivity,
+ offset: LayoutVector2D::zero(),
+ }),
+ );
+
+ CompositorDisplayListInfo {
+ pipeline_id,
+ viewport_size,
+ content_size,
+ epoch,
+ hit_test_info: Default::default(),
+ scroll_tree,
+ root_reference_frame_id,
+ root_scroll_node_id,
+ is_contentful: false,
+ first_reflow,
+ }
+ }
+
+ /// Add or re-use a duplicate HitTestInfo entry in this `CompositorHitTestInfo`
+ /// and return the index.
+ pub fn add_hit_test_info(
+ &mut self,
+ node: u64,
+ cursor: Option<Cursor>,
+ scroll_tree_node: ScrollTreeNodeId,
+ ) -> usize {
+ let hit_test_info = HitTestInfo {
+ node,
+ cursor,
+ scroll_tree_node,
+ };
+
+ if let Some(last) = self.hit_test_info.last() {
+ if hit_test_info == *last {
+ return self.hit_test_info.len() - 1;
+ }
+ }
+
+ self.hit_test_info.push(hit_test_info);
+ self.hit_test_info.len() - 1
+ }
+}