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
--- 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();