Bug 1429373 - If a perspective transform is excluded from an APZC's ancestor transform, include it in the ancestor transforms of its child APZCs. r=kats draft
authorBotond Ballo <botond@mozilla.com>
Fri, 19 Jan 2018 19:22:34 -0500
changeset 724283 3b5b3e4c0a955ee515a5b16717c25db1d0113d57
parent 724281 441d173e486b9be6557916219a849b2f9425d55b
child 724459 435e24fd33e51fcf84ac8ad64a63c59a7827907b
push id96722
push userbballo@mozilla.com
push dateWed, 24 Jan 2018 21:25:24 +0000
reviewerskats
bugs1429373
milestone59.0a1
Bug 1429373 - If a perspective transform is excluded from an APZC's ancestor transform, include it in the ancestor transforms of its child APZCs. r=kats MozReview-Commit-ID: 4EcTuD8otA7
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -74,16 +74,18 @@ struct APZCTreeManager::TreeBuildingStat
                     APZTestData* aTestData, uint32_t aPaintSequence)
     : mLayerTreeState(aLayerTreeState)
     , mIsFirstPaint(aIsFirstPaint)
     , mOriginatingLayersId(aOriginatingLayersId)
     , mPaintLogger(aTestData, aPaintSequence)
   {
   }
 
+  typedef std::unordered_map<AsyncPanZoomController*, gfx::Matrix4x4> DeferredTransformMap;
+
   // State that doesn't change as we recurse in the tree building
   const LayerTreeState* const mLayerTreeState;
   const bool mIsFirstPaint;
   const uint64_t mOriginatingLayersId;
   const APZPaintLogHelper mPaintLogger;
 
   // State that is updated as we perform the tree build
 
@@ -100,16 +102,22 @@ struct APZCTreeManager::TreeBuildingStat
   // This map is populated as we place APZCs into the new tree. Its purpose is
   // to facilitate re-using the same APZC for different layers that scroll
   // together (and thus have the same ScrollableLayerGuid).
   std::unordered_map<ScrollableLayerGuid, AsyncPanZoomController*, ScrollableLayerGuidHash> mApzcMap;
 
   // As the tree is traversed, the top element of this stack tracks whether
   // the parent scroll node has a perspective transform.
   std::stack<bool> mParentHasPerspective;
+
+  // During the tree building process, the perspective transform component
+  // of the ancestor transforms of some APZCs can be "deferred" to their
+  // children, meaning they are added to the children's ancestor transforms
+  // instead. Those deferred transforms are tracked here.
+  DeferredTransformMap mPerspectiveTransformsDeferredToChildren;
 };
 
 class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager)
@@ -389,16 +397,47 @@ APZCTreeManager::UpdateHitTestingTreeImp
           parent = parent->GetParent();
           layersId = next->GetLayersId();
           ancestorTransforms.pop();
           indents.pop();
           state.mParentHasPerspective.pop();
         });
 
     mApzcTreeLog << "[end]\n";
+
+    // If we have perspective transforms deferred to children, do another
+    // walk of the tree and actually apply them to the children.
+    // We can't do this "as we go" in the previous traversal, because by the
+    // time we realize we need to defer a perspective transform for an APZC,
+    // we may already have processed a previous layer (including children
+    // found in its subtree) that shares that APZC.
+    if (!state.mPerspectiveTransformsDeferredToChildren.empty()) {
+      ForEachNode<ReverseIterator>(mRootNode.get(),
+          [&state](HitTestingTreeNode* aNode) {
+            AsyncPanZoomController* apzc = aNode->GetApzc();
+            if (!apzc) {
+              return;
+            }
+            if (!aNode->IsPrimaryHolder()) {
+              return;
+            }
+
+            AsyncPanZoomController* parent = apzc->GetParent();
+            if (!parent) {
+              return;
+            }
+
+            auto it = state.mPerspectiveTransformsDeferredToChildren.find(parent);
+            if (it != state.mPerspectiveTransformsDeferredToChildren.end()) {
+              apzc->SetAncestorTransform(AncestorTransform{
+                it->second * apzc->GetAncestorTransform(), false
+              });
+            }
+          });
+    }
   }
 
   // We do not support tree structures where the root node has siblings.
   MOZ_ASSERT(!(mRootNode && mRootNode->GetPrevSibling()));
 
   for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
     APZCTM_LOG("Destroying node at %p with APZC %p\n",
         state.mNodesToDestroy[i].get(),
@@ -950,23 +989,30 @@ APZCTreeManager::PrepareNodeForLayer(con
     // Due to floating point inaccuracies those transforms can end up not quite
     // canceling each other. That's why we're using a fuzzy comparison here
     // instead of an exact one.
     // In addition, two ancestor transforms are allowed to differ if one of
     // them contains a perspective transform component and the other does not.
     // This represents situations where some content in a scrollable frame
     // is subject to a perspective transform and other content does not.
     // In such cases, go with the one that does not include the perspective
-    // component.
-    if (!aAncestorTransform.mTransform.FuzzyEqualsMultiplicative(apzc->GetAncestorTransform())) {
-      if (!aAncestorTransform.mContainsPerspectiveTransform &&
+    // component; the perspective transform is remembered and applied to the
+    // children instead.
+    if (!aAncestorTransform.CombinedTransform().FuzzyEqualsMultiplicative(apzc->GetAncestorTransform())) {
+      typedef TreeBuildingState::DeferredTransformMap::value_type PairType;
+      if (!aAncestorTransform.ContainsPerspectiveTransform() &&
           !apzc->AncestorTransformContainsPerspective()) {
         MOZ_ASSERT(false, "Two layers that scroll together have different ancestor transforms");
-      } else if (!aAncestorTransform.mContainsPerspectiveTransform) {
+      } else if (!aAncestorTransform.ContainsPerspectiveTransform()) {
+        aState.mPerspectiveTransformsDeferredToChildren.insert(
+            PairType{apzc, apzc->GetAncestorTransformPerspective()});
         apzc->SetAncestorTransform(aAncestorTransform);
+      } else {
+        aState.mPerspectiveTransformsDeferredToChildren.insert(
+            PairType{apzc, aAncestorTransform.GetPerspectiveTransform()});
       }
     }
 
     Maybe<ParentLayerIntRegion> clipRegion = parentHasPerspective
       ? Nothing()
       : Some(ComputeClipRegion(state->mController, aLayer));
     node->SetHitTestData(
         GetEventRegions(aLayer),
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -68,30 +68,54 @@ class PlatformSpecificStateBase {
 public:
   virtual ~PlatformSpecificStateBase() {}
   virtual AndroidSpecificState* AsAndroidSpecificState() { return nullptr; }
 };
 
 /*
  * Represents a transform from the ParentLayer coordinate space of an APZC
  * to the ParentLayer coordinate space of its parent APZC.
- * Each layer along the way contributes to the transform. We track whether
- * one of these contributions is a perspective transform, as those require
- * some special handling.
+ * Each layer along the way contributes to the transform. We track 
+ * contributions that are perspective transforms separately, as sometimes 
+ * these require special handling.
  */
 struct AncestorTransform {
   gfx::Matrix4x4 mTransform;
-  bool mContainsPerspectiveTransform;
+  gfx::Matrix4x4 mPerspectiveTransform;
+
+  AncestorTransform() = default;
+
+  AncestorTransform(const gfx::Matrix4x4& aTransform, bool aTransformIsPerspective) {
+    (aTransformIsPerspective ? mPerspectiveTransform : mTransform) = aTransform;
+  }
+
+  AncestorTransform(const gfx::Matrix4x4& aTransform,
+                    const gfx::Matrix4x4& aPerspectiveTransform)
+    : mTransform(aTransform)
+    , mPerspectiveTransform(aPerspectiveTransform)
+  {}
+
+  gfx::Matrix4x4 CombinedTransform() const {
+    return mTransform * mPerspectiveTransform;
+  }
+
+  bool ContainsPerspectiveTransform() const {
+    return !mPerspectiveTransform.IsIdentity();
+  }
+
+  gfx::Matrix4x4 GetPerspectiveTransform() const {
+    return mPerspectiveTransform;
+  }
 
   friend AncestorTransform operator*(const AncestorTransform& aA,
                                      const AncestorTransform& aB)
   {
     return AncestorTransform{
       aA.mTransform * aB.mTransform,
-      aA.mContainsPerspectiveTransform || aB.mContainsPerspectiveTransform
+      aA.mPerspectiveTransform * aB.mPerspectiveTransform
     };
   }
 };
 
 /**
  * Controller for all panning and zooming logic. Any time a user input is
  * detected and it must be processed in some way to affect what the user sees,
  * it goes through here. Listens for any input event from InputData and can
@@ -1236,21 +1260,26 @@ private:
    * hit-testing to see which APZC instance should handle touch events.
    */
 public:
   void SetAncestorTransform(const AncestorTransform& aAncestorTransform) {
     mAncestorTransform = aAncestorTransform;
   }
 
   Matrix4x4 GetAncestorTransform() const {
-    return mAncestorTransform.mTransform;
+    return mAncestorTransform.CombinedTransform();
   }
 
   bool AncestorTransformContainsPerspective() const {
-    return mAncestorTransform.mContainsPerspectiveTransform;
+    return mAncestorTransform.ContainsPerspectiveTransform();
+  }
+
+  // Return the perspective transform component of the ancestor transform.
+  Matrix4x4 GetAncestorTransformPerspective() const {
+    return mAncestorTransform.GetPerspectiveTransform();
   }
 
   // Returns whether or not this apzc contains the given screen point within
   // its composition bounds.
   bool Contains(const ScreenIntPoint& aPoint) const;
 
   bool IsOverscrolled() const {
     return mX.IsOverscrolled() || mY.IsOverscrolled();