Commit 8c13b1fe authored by Botond Ballo's avatar Botond Ballo
Browse files

Bug 1737725 - Ensure hit-testing never observes scroll offsets that don't have...

Bug 1737725 - Ensure hit-testing never observes scroll offsets that don't have async deltas applies. r=hiro,gw

When a new scene is swapped in on the render backend thread, its
scroll frames have scroll offsets that come from the main thread
and do not reflect async scroll deltas until such deltas are
sampled from APZ.

It's possible for hit-testing to observe the scene in this
temporary state, potentially leading to incorrect hit-test results.

To avoid this, save the async offsets from the previous scene
and apply them to the new scene until we can sample proper offsets
from APZ.

Differential Revision: https://phabricator.services.mozilla.com/D173100
parent dce6901d
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -854,6 +854,11 @@ impl RenderBackend {
                    Some(txn.frame_stats)
                };

                // Before updating the spatial tree, save the most recently sampled
                // scroll offsets (which include async deltas).
                let last_sampled_scroll_offsets =
                    doc.spatial_tree.get_last_sampled_scroll_offsets();

                if let Some(updates) = txn.spatial_tree_updates.take() {
                    doc.spatial_tree.apply_updates(updates);
                }
@@ -874,6 +879,15 @@ impl RenderBackend {
                    doc.data_stores.apply_updates(updates, &mut doc.profile);
                }

                // Apply the last sampled scroll offsets from the previous scene,
                // to the current scene. The offsets are identified by scroll ids
                // which are stable across scenes. This ensures that a hit test,
                // which could occur in between post-swap hook and the call to
                // update_document() below, does not observe raw main-thread offsets
                // from the new scene that don't have async deltas applied to them.
                doc.spatial_tree
                    .apply_last_sampled_scroll_offsets(last_sampled_scroll_offsets);

                // Build the hit tester while the APZ lock is held so that its content
                // is in sync with the gecko APZ tree.
                if !doc.hit_tester_is_valid {
+25 −0
Original line number Diff line number Diff line
@@ -911,6 +911,31 @@ impl SpatialTree {
        });
    }

    pub fn get_last_sampled_scroll_offsets(
        &self,
    ) -> FastHashMap<ExternalScrollId, Vec<SampledScrollOffset>> {
        let mut result = FastHashMap::default();
        self.visit_nodes(|_, node| {
            if let SpatialNodeType::ScrollFrame(ref scrolling) = node.node_type {
                result.insert(scrolling.external_id, scrolling.offsets.clone());
            }
        });
        result
    }

    pub fn apply_last_sampled_scroll_offsets(
        &mut self,
        last_sampled_offsets: FastHashMap<ExternalScrollId, Vec<SampledScrollOffset>>,
    ) {
        self.visit_nodes_mut(|_, node| {
            if let SpatialNodeType::ScrollFrame(ref mut scrolling) = node.node_type {
                if let Some(offsets) = last_sampled_offsets.get(&scrolling.external_id) {
                    scrolling.offsets = offsets.clone();
                }
            }
        });
    }

    pub fn get_spatial_node(&self, index: SpatialNodeIndex) -> &SpatialNode {
        &self.spatial_nodes[index.0 as usize]
    }