Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 http://mozilla.org/MPL/2.0/. */
#include <stack>
#include <unordered_set>
#include "APZCTreeManager.h"
#include "AsyncPanZoomController.h"
#include "Compositor.h" // for Compositor
#include "DragTracker.h" // for DragTracker
#include "GenericFlingAnimation.h" // for FLING_LOG
#include "HitTestingTreeNode.h" // for HitTestingTreeNode
#include "InputBlockState.h" // for InputBlockState
#include "InputData.h" // for InputData, etc
#include "Layers.h" // for Layer, etc
#include "WRHitTester.h" // for WRHitTester
#include "mozilla/RecursiveMutex.h"
#include "mozilla/dom/MouseEventBinding.h" // for MouseEvent constants
#include "mozilla/dom/BrowserParent.h" // for AreRecordReplayTabsActive
#include "mozilla/dom/Touch.h" // for Touch
#include "mozilla/gfx/CompositorHitTestInfo.h"
#include "mozilla/gfx/LoggingConstants.h"
#include "mozilla/gfx/gfxVars.h" // for gfxVars
#include "mozilla/gfx/GPUParent.h" // for GPUParent
#include "mozilla/gfx/Logging.h" // for gfx::TreeLog
#include "mozilla/gfx/Point.h" // for Point
#include "mozilla/layers/APZSampler.h" // for APZSampler
#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread, etc
#include "mozilla/layers/APZUpdater.h" // for APZUpdater
#include "mozilla/layers/APZUtils.h" // for AsyncTransform
#include "mozilla/layers/AsyncDragMetrics.h" // for AsyncDragMetrics
#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent, etc
#include "mozilla/layers/DoubleTapToZoom.h" // for ZoomTarget
#include "mozilla/layers/MatrixMessage.h"
#include "mozilla/layers/UiCompositorControllerParent.h"
#include "mozilla/layers/WebRenderScrollDataWrapper.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/mozalloc.h" // for operator new
#include "mozilla/Preferences.h" // for Preferences
#include "mozilla/StaticPrefs_accessibility.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/ToString.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/EventStateManager.h" // for WheelPrefs
#include "mozilla/webrender/WebRenderAPI.h"
#include "nsDebug.h" // for NS_WARNING
#include "nsPoint.h" // for nsIntPoint
#include "nsThreadUtils.h" // for NS_IsMainThread
#include "ScrollThumbUtils.h" // for ComputeTransformForScrollThumb
#include "OverscrollHandoffState.h" // for OverscrollHandoffState
#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch, etc
#include "Units.h" // for ParentlayerPixel
#include "GestureEventListener.h" // for GestureEventListener::setLongTapEnabled
#include "UnitTransforms.h" // for ViewAs
mozilla::LazyLogModule mozilla::layers::APZCTreeManager::sLog("apz.manager");
#define APZCTM_LOG(...) \
MOZ_LOG(APZCTreeManager::sLog, LogLevel::Debug, (__VA_ARGS__))
static mozilla::LazyLogModule sApzKeyLog("apz.key");
#define APZ_KEY_LOG(...) MOZ_LOG(sApzKeyLog, LogLevel::Debug, (__VA_ARGS__))
namespace mozilla {
namespace layers {
using mozilla::gfx::CompositorHitTestFlags;
using mozilla::gfx::CompositorHitTestInfo;
using mozilla::gfx::CompositorHitTestInvisibleToHit;
using mozilla::gfx::LOG_DEFAULT;
typedef mozilla::gfx::Point Point;
typedef mozilla::gfx::Point4D Point4D;
typedef mozilla::gfx::Matrix4x4 Matrix4x4;
typedef CompositorBridgeParent::LayerTreeState LayerTreeState;
struct APZCTreeManager::TreeBuildingState {
TreeBuildingState(LayersId aRootLayersId, bool aIsFirstPaint,
LayersId aOriginatingLayersId, APZTestData* aTestData,
uint32_t aPaintSequence)
: mIsFirstPaint(aIsFirstPaint),
mOriginatingLayersId(aOriginatingLayersId),
mPaintLogger(aTestData, aPaintSequence) {
CompositorBridgeParent::CallWithIndirectShadowTree(
aRootLayersId, [this](LayerTreeState& aState) -> void {
mCompositorController = aState.GetCompositorController();
});
}
typedef std::unordered_map<AsyncPanZoomController*, gfx::Matrix4x4>
DeferredTransformMap;
// State that doesn't change as we recurse in the tree building
RefPtr<CompositorController> mCompositorController;
const bool mIsFirstPaint;
const LayersId mOriginatingLayersId;
const APZPaintLogHelper mPaintLogger;
// State that is updated as we perform the tree build
// A list of nodes that need to be destroyed at the end of the tree building.
// This is initialized with all nodes in the old tree, and nodes are removed
// from it as we reuse them in the new tree.
nsTArray<RefPtr<HitTestingTreeNode>> mNodesToDestroy;
// 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). The presShellId
// doesn't matter for this purpose, and we move the map to the APZCTreeManager
// after we're done building, so it's useful to have the presshell-ignoring
// map for that.
std::unordered_map<ScrollableLayerGuid, ApzcMapData,
ScrollableLayerGuid::HashIgnoringPresShellFn,
ScrollableLayerGuid::EqualIgnoringPresShellFn>
mApzcMap;
// This is populated with all the HitTestingTreeNodes that are scroll thumbs
// and have a scrollthumb animation id (which indicates that they need to be
// sampled for WebRender on the sampler thread).
std::vector<HitTestingTreeNode*> mScrollThumbs;
// This is populated with all the scroll target nodes. We use in conjunction
// with mScrollThumbs to build APZCTreeManager::mScrollThumbInfo.
std::unordered_map<ScrollableLayerGuid, HitTestingTreeNode*,
ScrollableLayerGuid::HashIgnoringPresShellFn,
ScrollableLayerGuid::EqualIgnoringPresShellFn>
mScrollTargets;
// 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;
// As we recurse down through the tree, this picks up the zoom animation id
// from a node in the layer tree, and propagates it downwards to the nearest
// APZC instance that is for an RCD node. Generally it will be set on the
// root node of the layers (sub-)tree, which may not be same as the RCD node
// for the subtree, and so we need this mechanism to ensure it gets propagated
// to the RCD's APZC instance. Once it is set on the APZC instance, the value
// is cleared back to Nothing(). Note that this is only used in the WebRender
// codepath.
Maybe<uint64_t> mZoomAnimationId;
// See corresponding members of APZCTreeManager. These are the same thing, but
// on the tree-walking state. They are populated while walking the tree in
// a layers update, and then moved into APZCTreeManager.
std::vector<FixedPositionInfo> mFixedPositionInfo;
std::vector<RootScrollbarInfo> mRootScrollbarInfo;
std::vector<StickyPositionInfo> mStickyPositionInfo;
// As we recurse down through reflayers in the tree, this picks up the
// cumulative EventRegionsOverride flags from the reflayers, and is used to
// apply them to descendant layers.
std::stack<EventRegionsOverride> mOverrideFlags;
};
class APZCTreeManager::CheckerboardFlushObserver : public nsIObserver {
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
explicit CheckerboardFlushObserver(APZCTreeManager* aTreeManager)
: mTreeManager(aTreeManager) {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
MOZ_ASSERT(obsSvc);
if (obsSvc) {
obsSvc->AddObserver(this, "APZ:FlushActiveCheckerboard", false);
}
}
void Unregister() {
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
if (obsSvc) {
obsSvc->RemoveObserver(this, "APZ:FlushActiveCheckerboard");
}
mTreeManager = nullptr;
}
protected:
virtual ~CheckerboardFlushObserver() = default;
private:
RefPtr<APZCTreeManager> mTreeManager;
};
NS_IMPL_ISUPPORTS(APZCTreeManager::CheckerboardFlushObserver, nsIObserver)
NS_IMETHODIMP
APZCTreeManager::CheckerboardFlushObserver::Observe(nsISupports* aSubject,
const char* aTopic,
const char16_t*) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mTreeManager.get());
RecursiveMutexAutoLock lock(mTreeManager->mTreeLock);
if (mTreeManager->mRootNode) {
ForEachNode<ReverseIterator>(
mTreeManager->mRootNode.get(), [](HitTestingTreeNode* aNode) {
if (aNode->IsPrimaryHolder()) {
MOZ_ASSERT(aNode->GetApzc());
aNode->GetApzc()->FlushActiveCheckerboardReport();
}
});
}
if (XRE_IsGPUProcess()) {
if (gfx::GPUParent* gpu = gfx::GPUParent::GetSingleton()) {
nsCString topic("APZ:FlushActiveCheckerboard:Done");
Unused << gpu->SendNotifyUiObservers(topic);
}
} else {
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIObserverService> obsSvc =
mozilla::services::GetObserverService();
if (obsSvc) {
obsSvc->NotifyObservers(nullptr, "APZ:FlushActiveCheckerboard:Done",
nullptr);
}
}
return NS_OK;
}
/**
* A RAII class used for setting the focus sequence number on input events
* as they are being processed. Any input event is assumed to be potentially
* focus changing unless explicitly marked otherwise.
*/
class MOZ_RAII AutoFocusSequenceNumberSetter {
public:
AutoFocusSequenceNumberSetter(FocusState& aFocusState, InputData& aEvent)
: mFocusState(aFocusState), mEvent(aEvent), mMayChangeFocus(true) {}
void MarkAsNonFocusChanging() { mMayChangeFocus = false; }
~AutoFocusSequenceNumberSetter() {
if (mMayChangeFocus) {
mFocusState.ReceiveFocusChangingEvent();
APZ_KEY_LOG(
"Marking input with type=%d as focus changing with seq=%" PRIu64 "\n",
static_cast<int>(mEvent.mInputType),
mFocusState.LastAPZProcessedEvent());
} else {
APZ_KEY_LOG(
"Marking input with type=%d as non focus changing with seq=%" PRIu64
"\n",
static_cast<int>(mEvent.mInputType),
mFocusState.LastAPZProcessedEvent());
}
mEvent.mFocusSequenceNumber = mFocusState.LastAPZProcessedEvent();
}
private:
FocusState& mFocusState;
InputData& mEvent;
bool mMayChangeFocus;
};
APZCTreeManager::APZCTreeManager(LayersId aRootLayersId,
UniquePtr<IAPZHitTester> aHitTester)
: mTestSampleTime(Nothing(), "APZCTreeManager::mTestSampleTime"),
mInputQueue(new InputQueue()),
mRootLayersId(aRootLayersId),
mSampler(nullptr),
mUpdater(nullptr),
mTreeLock("APZCTreeLock"),
mMapLock("APZCMapLock"),
mRetainedTouchIdentifier(-1),
mInScrollbarTouchDrag(false),
mCurrentMousePosition(ScreenPoint(),
"APZCTreeManager::mCurrentMousePosition"),
mApzcTreeLog("apzctree"),
mTestDataLock("APZTestDataLock"),
mDPI(160.0),
mHitTester(std::move(aHitTester)) {
RefPtr<APZCTreeManager> self(this);
NS_DispatchToMainThread(NS_NewRunnableFunction(
"layers::APZCTreeManager::APZCTreeManager",
[self] { self->mFlushObserver = new CheckerboardFlushObserver(self); }));
AsyncPanZoomController::InitializeGlobalState();
mApzcTreeLog.ConditionOnPrefFunction(StaticPrefs::apz_printtree);
if (!mHitTester) {
mHitTester = MakeUnique<WRHitTester>();
}
mHitTester->Initialize(this);
}
APZCTreeManager::~APZCTreeManager() = default;
void APZCTreeManager::SetSampler(APZSampler* aSampler) {
// We're either setting the sampler or clearing it
MOZ_ASSERT((mSampler == nullptr) != (aSampler == nullptr));
mSampler = aSampler;
}
void APZCTreeManager::SetUpdater(APZUpdater* aUpdater) {
// We're either setting the updater or clearing it
MOZ_ASSERT((mUpdater == nullptr) != (aUpdater == nullptr));
mUpdater = aUpdater;
}
void APZCTreeManager::NotifyLayerTreeAdopted(
LayersId aLayersId, const RefPtr<APZCTreeManager>& aOldApzcTreeManager) {
AssertOnUpdaterThread();
if (aOldApzcTreeManager) {
aOldApzcTreeManager->mFocusState.RemoveFocusTarget(aLayersId);
// While we could move the focus target information from the old APZC tree
// manager into this one, it's safer to not do that, as we'll probably have
// that information repopulated soon anyway (on the next layers update).
}
UniquePtr<APZTestData> adoptedData;
if (aOldApzcTreeManager) {
MutexAutoLock lock(aOldApzcTreeManager->mTestDataLock);
auto it = aOldApzcTreeManager->mTestData.find(aLayersId);
if (it != aOldApzcTreeManager->mTestData.end()) {
adoptedData = std::move(it->second);
aOldApzcTreeManager->mTestData.erase(it);
}
}
if (adoptedData) {
MutexAutoLock lock(mTestDataLock);
mTestData[aLayersId] = std::move(adoptedData);
}
}
void APZCTreeManager::NotifyLayerTreeRemoved(LayersId aLayersId) {
AssertOnUpdaterThread();
mFocusState.RemoveFocusTarget(aLayersId);
{ // scope lock
MutexAutoLock lock(mTestDataLock);
mTestData.erase(aLayersId);
}
}
AsyncPanZoomController* APZCTreeManager::NewAPZCInstance(
LayersId aLayersId, GeckoContentController* aController) {
return new AsyncPanZoomController(
aLayersId, this, mInputQueue, aController,
AsyncPanZoomController::USE_GESTURE_DETECTOR);
}
void APZCTreeManager::SetTestSampleTime(const Maybe<TimeStamp>& aTime) {
auto testSampleTime = mTestSampleTime.Lock();
testSampleTime.ref() = aTime;
}
SampleTime APZCTreeManager::GetFrameTime() {
auto testSampleTime = mTestSampleTime.Lock();
if (testSampleTime.ref()) {
return SampleTime::FromTest(*testSampleTime.ref());
}
return SampleTime::FromNow();
}
void APZCTreeManager::SetAllowedTouchBehavior(
uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aValues) {
if (!APZThreadUtils::IsControllerThread()) {
APZThreadUtils::RunOnControllerThread(
NewRunnableMethod<uint64_t,
StoreCopyPassByLRef<nsTArray<TouchBehaviorFlags>>>(
"layers::APZCTreeManager::SetAllowedTouchBehavior", this,
&APZCTreeManager::SetAllowedTouchBehavior, aInputBlockId,
aValues.Clone()));
return;
}
APZThreadUtils::AssertOnControllerThread();
mInputQueue->SetAllowedTouchBehavior(aInputBlockId, aValues);
}
void APZCTreeManager::UpdateHitTestingTree(
const WebRenderScrollDataWrapper& aRoot, bool aIsFirstPaint,
LayersId aOriginatingLayersId, uint32_t aPaintSequenceNumber) {
AssertOnUpdaterThread();
RecursiveMutexAutoLock lock(mTreeLock);
// For testing purposes, we log some data to the APZTestData associated with
// the layers id that originated this update.
APZTestData* testData = nullptr;
if (StaticPrefs::apz_test_logging_enabled()) {
MutexAutoLock lock(mTestDataLock);
UniquePtr<APZTestData> ptr = MakeUnique<APZTestData>();
auto result =
mTestData.insert(std::make_pair(aOriginatingLayersId, std::move(ptr)));
testData = result.first->second.get();
testData->StartNewPaint(aPaintSequenceNumber);
}
TreeBuildingState state(mRootLayersId, aIsFirstPaint, aOriginatingLayersId,
testData, aPaintSequenceNumber);
// We do this business with collecting the entire tree into an array because
// otherwise it's very hard to determine which APZC instances need to be
// destroyed. In the worst case, there are two scenarios: (a) a layer with an
// APZC is removed from the layer tree and (b) a layer with an APZC is moved
// in the layer tree from one place to a completely different place. In
// scenario (a) we would want to destroy the APZC while walking the layer tree
// and noticing that the layer/APZC is no longer there. But if we do that then
// we run into a problem in scenario (b) because we might encounter that layer
// later during the walk. To handle both of these we have to 'remember' that
// the layer was not found, and then do the destroy only at the end of the
// tree walk after we are sure that the layer was removed and not just
// transplanted elsewhere. Doing that as part of a recursive tree walk is hard
// and so maintaining a list and removing APZCs that are still alive is much
// simpler.
ForEachNode<ReverseIterator>(mRootNode.get(),
[&state](HitTestingTreeNode* aNode) {
state.mNodesToDestroy.AppendElement(aNode);
});
mRootNode = nullptr;
mAsyncZoomContainerSubtree = Nothing();
int asyncZoomContainerNestingDepth = 0;
bool haveNestedAsyncZoomContainers = false;
nsTArray<LayersId> subtreesWithRootContentOutsideAsyncZoomContainer;
if (aRoot) {
std::unordered_set<LayersId, LayersId::HashFn> seenLayersIds;
std::stack<gfx::TreeAutoIndent<LOG_DEFAULT>> indents;
std::stack<AncestorTransform> ancestorTransforms;
HitTestingTreeNode* parent = nullptr;
HitTestingTreeNode* next = nullptr;
LayersId layersId = mRootLayersId;
seenLayersIds.insert(mRootLayersId);
ancestorTransforms.push(AncestorTransform());
state.mOverrideFlags.push(EventRegionsOverride::NoOverride);
nsTArray<Maybe<ZoomConstraints>> zoomConstraintsStack;
// push a nothing to be used for anything outside an async zoom container
zoomConstraintsStack.AppendElement(Nothing());
mApzcTreeLog << "[start]\n";
mTreeLock.AssertCurrentThreadIn();
ForEachNode<ReverseIterator>(
aRoot,
[&](ScrollNode aLayerMetrics) {
mApzcTreeLog << aLayerMetrics.Name() << '\t';
if (auto asyncZoomContainerId =
aLayerMetrics.GetAsyncZoomContainerId()) {
if (asyncZoomContainerNestingDepth > 0) {
haveNestedAsyncZoomContainers = true;
}
mAsyncZoomContainerSubtree = Some(layersId);
++asyncZoomContainerNestingDepth;
auto it = mZoomConstraints.find(
ScrollableLayerGuid(layersId, 0, *asyncZoomContainerId));
if (it != mZoomConstraints.end()) {
zoomConstraintsStack.AppendElement(Some(it->second));
} else {
zoomConstraintsStack.AppendElement(Nothing());
}
}
if (aLayerMetrics.Metrics().IsRootContent()) {
MutexAutoLock lock(mMapLock);
mGeckoFixedLayerMargins =
aLayerMetrics.Metrics().GetFixedLayerMargins();
} else {
MOZ_ASSERT(aLayerMetrics.Metrics().GetFixedLayerMargins() ==
ScreenMargin(),
"fixed-layer-margins should be 0 on non-root layer");
}
// Note that this check happens after the potential increment of
// asyncZoomContainerNestingDepth, to allow the root content
// metadata to be on the same node as the async zoom container.
if (aLayerMetrics.Metrics().IsRootContent() &&
asyncZoomContainerNestingDepth == 0) {
subtreesWithRootContentOutsideAsyncZoomContainer.AppendElement(
layersId);
}
HitTestingTreeNode* node = PrepareNodeForLayer(
lock, aLayerMetrics, aLayerMetrics.Metrics(), layersId,
zoomConstraintsStack.LastElement(), ancestorTransforms.top(),
parent, next, state);
MOZ_ASSERT(node);
AsyncPanZoomController* apzc = node->GetApzc();
aLayerMetrics.SetApzc(apzc);
// GetScrollbarAnimationId is only set when webrender is enabled,
// which limits the extra thumb mapping work to the webrender-enabled
// case where it is needed.
// Note also that when webrender is enabled, a "valid" animation id
// is always nonzero, so we don't need to worry about handling the
// case where WR is enabled and the animation id is zero.
if (node->GetScrollbarAnimationId()) {
if (node->IsScrollThumbNode()) {
state.mScrollThumbs.push_back(node);
} else if (node->IsScrollbarContainerNode()) {
// Only scrollbar containers for the root have an animation id.
state.mRootScrollbarInfo.emplace_back(
*(node->GetScrollbarAnimationId()),
node->GetScrollbarDirection());
}
}
// GetFixedPositionAnimationId is only set when webrender is enabled.
if (node->GetFixedPositionAnimationId().isSome()) {
state.mFixedPositionInfo.emplace_back(node);
}
// GetStickyPositionAnimationId is only set when webrender is enabled.
if (node->GetStickyPositionAnimationId().isSome()) {
state.mStickyPositionInfo.emplace_back(node);
}
if (apzc && node->IsPrimaryHolder()) {
state.mScrollTargets[apzc->GetGuid()] = node;
}
mApzcTreeLog << '\n';
// Accumulate the CSS transform between layers that have an APZC.
// In the terminology of the big comment above
// APZCTreeManager::GetScreenToApzcTransform, if we are at layer M,
// then aAncestorTransform is NC * OC * PC, and we left-multiply MC
// and compute ancestorTransform to be MC * NC * OC * PC. This gets
// passed down as the ancestor transform to layer L when we recurse
// into the children below. If we are at a layer with an APZC, such as
// P, then we reset the ancestorTransform to just PC, to start the new
// accumulation as we go down.
AncestorTransform currentTransform{
aLayerMetrics.GetTransform(),
aLayerMetrics.TransformIsPerspective()};
if (!apzc) {
currentTransform = currentTransform * ancestorTransforms.top();
}
ancestorTransforms.push(currentTransform);
// Note that |node| at this point will not have any children,
// otherwise we we would have to set next to node->GetFirstChild().
MOZ_ASSERT(!node->GetFirstChild());
parent = node;
next = nullptr;
// Update the layersId if we have a new one
if (Maybe<LayersId> newLayersId = aLayerMetrics.GetReferentId()) {
layersId = *newLayersId;
seenLayersIds.insert(layersId);
// Propagate any event region override flags down into all
// descendant nodes from the reflayer that has the flag. This is an
// optimization to avoid having to walk up the tree to check the
// override flags. Note that we don't keep the flags on the reflayer
// itself, because the semantics of the flags are that they apply
// to all content in the layer subtree being referenced. This
// matters with the WR hit-test codepath, because this reflayer may
// be just one of many nodes associated with a particular APZC, and
// calling GetTargetNode with a guid may return any one of the
// nodes. If different nodes have different flags on them that can
// make the WR hit-test result incorrect, but being strict about
// only putting the flags on descendant layers avoids this problem.
state.mOverrideFlags.push(state.mOverrideFlags.top() |
aLayerMetrics.GetEventRegionsOverride());
}
indents.push(gfx::TreeAutoIndent<LOG_DEFAULT>(mApzcTreeLog));
},
[&](ScrollNode aLayerMetrics) {
if (aLayerMetrics.GetAsyncZoomContainerId()) {
--asyncZoomContainerNestingDepth;
zoomConstraintsStack.RemoveLastElement();
}
if (aLayerMetrics.GetReferentId()) {
state.mOverrideFlags.pop();
}
next = parent;
parent = parent->GetParent();
layersId = next->GetLayersId();
ancestorTransforms.pop();
indents.pop();
});
mApzcTreeLog << "[end]\n";
MOZ_ASSERT(
!mAsyncZoomContainerSubtree ||
!subtreesWithRootContentOutsideAsyncZoomContainer.Contains(
*mAsyncZoomContainerSubtree),
"If there is an async zoom container, all scroll nodes with root "
"content scroll metadata should be inside it");
MOZ_ASSERT(!haveNestedAsyncZoomContainers,
"Should not have nested async zoom container");
// 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});
}
});
}
// Remove any layers ids for which we no longer have content from
// mDetachedLayersIds.
for (auto iter = mDetachedLayersIds.begin();
iter != mDetachedLayersIds.end();) {
// unordered_set::erase() invalidates the iterator pointing to the
// element being erased, but returns an iterator to the next element.
if (seenLayersIds.find(*iter) == seenLayersIds.end()) {
iter = mDetachedLayersIds.erase(iter);
} else {
++iter;
}
}
}
// We do not support tree structures where the root node has siblings.
MOZ_ASSERT(!(mRootNode && mRootNode->GetPrevSibling()));
{ // scope lock and update our mApzcMap before we destroy all the unused
// APZC instances
MutexAutoLock lock(mMapLock);
mApzcMap = std::move(state.mApzcMap);
for (auto& mapping : mApzcMap) {
AsyncPanZoomController* parent = mapping.second.apzc->GetParent();
mapping.second.parent = parent ? Some(parent->GetGuid()) : Nothing();
}
mScrollThumbInfo.clear();
// For non-webrender, state.mScrollThumbs will be empty so this will be a
// no-op.
for (HitTestingTreeNode* thumb : state.mScrollThumbs) {
MOZ_ASSERT(thumb->IsScrollThumbNode());
ScrollableLayerGuid targetGuid(thumb->GetLayersId(), 0,
thumb->GetScrollTargetId());
auto it = state.mScrollTargets.find(targetGuid);
if (it == state.mScrollTargets.end()) {
// It could be that |thumb| is a scrollthumb for content which didn't
// have an APZC, for example if the content isn't layerized. Regardless,
// we can't async-scroll it so we don't need to worry about putting it
// in mScrollThumbInfo.
continue;
}
HitTestingTreeNode* target = it->second;
mScrollThumbInfo.emplace_back(
*(thumb->GetScrollbarAnimationId()), thumb->GetTransform(),
thumb->GetScrollbarData(), targetGuid, target->GetTransform(),
target->IsAncestorOf(thumb));
}
mRootScrollbarInfo = std::move(state.mRootScrollbarInfo);
mFixedPositionInfo = std::move(state.mFixedPositionInfo);
mStickyPositionInfo = std::move(state.mStickyPositionInfo);
}
for (size_t i = 0; i < state.mNodesToDestroy.Length(); i++) {
APZCTM_LOG("Destroying node at %p with APZC %p\n",
state.mNodesToDestroy[i].get(),
state.mNodesToDestroy[i]->GetApzc());
state.mNodesToDestroy[i]->Destroy();
}
APZCTM_LOG("APZCTreeManager (%p)\n", this);
if (mRootNode && MOZ_LOG_TEST(sLog, LogLevel::Debug)) {
mRootNode->Dump(" ");
}
SendSubtreeTransformsToChromeMainThread(nullptr);
}
void APZCTreeManager::UpdateFocusState(LayersId aRootLayerTreeId,
LayersId aOriginatingLayersId,
const FocusTarget& aFocusTarget) {
AssertOnUpdaterThread();
if (!StaticPrefs::apz_keyboard_enabled_AtStartup()) {
return;
}
mFocusState.Update(aRootLayerTreeId, aOriginatingLayersId, aFocusTarget);
}
void APZCTreeManager::SampleForWebRender(const Maybe<VsyncId>& aVsyncId,
wr::TransactionWrapper& aTxn,
const SampleTime& aSampleTime) {
AssertOnSamplerThread();
MutexAutoLock lock(mMapLock);
RefPtr<WebRenderBridgeParent> wrBridgeParent;
RefPtr<CompositorController> controller;
CompositorBridgeParent::CallWithIndirectShadowTree(
mRootLayersId, [&](LayerTreeState& aState) -> void {
controller = aState.GetCompositorController();
wrBridgeParent = aState.mWrBridge;
});
bool activeAnimations = AdvanceAnimationsInternal(lock, aSampleTime);
if (activeAnimations && controller) {
controller->ScheduleRenderOnCompositorThread(
wr::RenderReasons::ANIMATED_PROPERTY);
}
nsTArray<wr::WrTransformProperty> transforms;
// Sample async transforms on scrollable layers.
for (const auto& mapping : mApzcMap) {
AsyncPanZoomController* apzc = mapping.second.apzc;
const AsyncTransformComponents asyncTransformComponents =
apzc->GetZoomAnimationId()
? AsyncTransformComponents{AsyncTransformComponent::eLayout}
: LayoutAndVisual;
ParentLayerPoint layerTranslation =
apzc->GetCurrentAsyncTransformWithOverscroll(
AsyncPanZoomController::eForCompositing,
asyncTransformComponents)
.TransformPoint(ParentLayerPoint(0, 0));
if (Maybe<CompositionPayload> payload = apzc->NotifyScrollSampling()) {
if (wrBridgeParent && aVsyncId) {
wrBridgeParent->AddPendingScrollPayload(*payload, *aVsyncId);
}
}
if (StaticPrefs::apz_test_logging_enabled()) {
MutexAutoLock lock(mTestDataLock);
ScrollableLayerGuid guid = apzc->GetGuid();
auto it = mTestData.find(guid.mLayersId);
if (it != mTestData.end()) {
it->second->RecordSampledResult(
apzc->GetCurrentAsyncScrollOffsetInCssPixels(
AsyncPanZoomController::eForCompositing),
(aSampleTime.Time() - TimeStamp::ProcessCreation(nullptr))
.ToMicroseconds(),
guid.mLayersId, guid.mScrollId);
}
}
if (Maybe<uint64_t> zoomAnimationId = apzc->GetZoomAnimationId()) {
// for now we only support zooming on root content APZCs
MOZ_ASSERT(apzc->IsRootContent());
LayoutDeviceToParentLayerScale zoom = apzc->GetCurrentPinchZoomScale(
AsyncPanZoomController::eForCompositing);
AsyncTransform asyncVisualTransform = apzc->GetCurrentAsyncTransform(
AsyncPanZoomController::eForCompositing,
AsyncTransformComponents{AsyncTransformComponent::eVisual});
transforms.AppendElement(wr::ToWrTransformProperty(
*zoomAnimationId, LayoutDeviceToParentLayerMatrix4x4::Scaling(
zoom.scale, zoom.scale, 1.0f) *
AsyncTransformComponentMatrix::Translation(
asyncVisualTransform.mTranslation) *
apzc->GetOverscrollTransform(
AsyncPanZoomController::eForCompositing)));
aTxn.UpdateIsTransformAsyncZooming(*zoomAnimationId,
apzc->IsAsyncZooming());
}
// If layerTranslation includes only the layout component of the async
// transform then it has not been scaled by the async zoom, so we want to
// divide it by the resolution. If layerTranslation includes the visual
// component, then we should use the pinch zoom scale, which includes the
// async zoom. However, we only use LayoutAndVisual for non-zoomable APZCs,
// so it makes no difference.
LayoutDeviceToParentLayerScale resolution =
apzc->GetCumulativeResolution() * LayerToParentLayerScale(1.0f);
// The positive translation means the painted content is supposed to
// move down (or to the right), and that corresponds to a reduction in
// the scroll offset. Since we are effectively giving WR the async
// scroll delta here, we want to negate the translation.
LayoutDevicePoint asyncScrollDelta = -layerTranslation / resolution;
aTxn.UpdateScrollPosition(wr::AsPipelineId(apzc->GetGuid().mLayersId),
apzc->GetGuid().mScrollId,
wr::ToLayoutPoint(asyncScrollDelta));
#if defined(MOZ_WIDGET_ANDROID)
// Send the root frame metrics to java through the UIController
RefPtr<UiCompositorControllerParent> uiController =
UiCompositorControllerParent::GetFromRootLayerTreeId(mRootLayersId);
if (uiController &&
apzc->UpdateRootFrameMetricsIfChanged(mLastRootMetrics)) {
uiController->NotifyUpdateScreenMetrics(mLastRootMetrics);
}
#endif
}
// Now collect all the async transforms needed for the scrollthumbs.
for (const ScrollThumbInfo& info : mScrollThumbInfo) {
auto it = mApzcMap.find(info.mTargetGuid);
if (it == mApzcMap.end()) {
// It could be that |info| is a scrollthumb for content which didn't
// have an APZC, for example if the content isn't layerized. Regardless,
// we can't async-scroll it so we don't need to worry about putting it
// in mScrollThumbInfo.
continue;
}
AsyncPanZoomController* scrollTargetApzc = it->second.apzc;
MOZ_ASSERT(scrollTargetApzc);
LayerToParentLayerMatrix4x4 transform =
scrollTargetApzc->CallWithLastContentPaintMetrics(
[&](const FrameMetrics& aMetrics) {
return ComputeTransformForScrollThumb(
info.mThumbTransform * AsyncTransformMatrix(),
info.mTargetTransform.ToUnknownMatrix(), scrollTargetApzc,
aMetrics, info.mThumbData, info.mTargetIsAncestor);
});
transforms.AppendElement(
wr::ToWrTransformProperty(info.mThumbAnimationId, transform));
}
// Move the root scrollbar in response to the dynamic toolbar transition.
for (const RootScrollbarInfo& info : mRootScrollbarInfo) {
// We only care about the horizontal scrollbar.
if (info.mScrollDirection == ScrollDirection::eHorizontal) {
ScreenPoint translation =
apz::ComputeFixedMarginsOffset(GetCompositorFixedLayerMargins(lock),
SideBits::eBottom, ScreenMargin());
LayerToParentLayerMatrix4x4 transform =
LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
translation, PixelCastJustification::ScreenIsParentLayerForRoot));
transforms.AppendElement(
wr::ToWrTransformProperty(info.mScrollbarAnimationId, transform));
}
}
for (const FixedPositionInfo& info : mFixedPositionInfo) {
MOZ_ASSERT(info.mFixedPositionAnimationId.isSome());
if (!IsFixedToRootContent(info, lock)) {
continue;
}
ScreenPoint translation = apz::ComputeFixedMarginsOffset(
GetCompositorFixedLayerMargins(lock), info.mFixedPosSides,
mGeckoFixedLayerMargins);
LayerToParentLayerMatrix4x4 transform =
LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
translation, PixelCastJustification::ScreenIsParentLayerForRoot));
transforms.AppendElement(
wr::ToWrTransformProperty(*info.mFixedPositionAnimationId, transform));
}
for (const StickyPositionInfo& info : mStickyPositionInfo) {
MOZ_ASSERT(info.mStickyPositionAnimationId.isSome());
SideBits sides = SidesStuckToRootContent(info, lock);
if (sides == SideBits::eNone) {
continue;
}
ScreenPoint translation = apz::ComputeFixedMarginsOffset(
GetCompositorFixedLayerMargins(lock), sides,
// For sticky layers, we don't need to factor
// mGeckoFixedLayerMargins because Gecko doesn't shift the
// position of sticky elements for dynamic toolbar movements.
ScreenMargin());
LayerToParentLayerMatrix4x4 transform =
LayerToParentLayerMatrix4x4::Translation(ViewAs<ParentLayerPixel>(
translation, PixelCastJustification::ScreenIsParentLayerForRoot));
transforms.AppendElement(
wr::ToWrTransformProperty(*info.mStickyPositionAnimationId, transform));
}
aTxn.AppendTransformProperties(transforms);
}
ParentLayerRect APZCTreeManager::ComputeClippedCompositionBounds(
const MutexAutoLock& aProofOfMapLock, ClippedCompositionBoundsMap& aDestMap,
ScrollableLayerGuid aGuid) {
if (auto iter = aDestMap.find(aGuid); iter != aDestMap.end()) {
// We already computed it for this one, early-exit. This might happen
// because on a later iteration of mApzcMap we might encounter an ancestor
// of an APZC that we processed on an earlier iteration. In this case we
// would have computed the ancestor's clipped composition bounds when
// recursing up on the earlier iteration.
return iter->second;
}
ParentLayerRect bounds = mApzcMap[aGuid].apzc->GetCompositionBounds();
const auto& mapEntry = mApzcMap.find(aGuid);
MOZ_ASSERT(mapEntry != mApzcMap.end());
if (mapEntry->second.parent.isNothing()) {
// Recursion base case, where the APZC with guid `aGuid` has no parent.
// In this case, we don't need to clip `bounds` any further and can just
// early exit.
aDestMap.emplace(aGuid, bounds);
return bounds;
}
ScrollableLayerGuid parentGuid = mapEntry->second.parent.value();
auto parentBoundsEntry = aDestMap.find(parentGuid);
// If aDestMap doesn't contain the parent entry yet, we recurse to compute
// that one first.
ParentLayerRect parentClippedBounds =
(parentBoundsEntry == aDestMap.end())
? ComputeClippedCompositionBounds(aProofOfMapLock, aDestMap,
parentGuid)
: parentBoundsEntry->second;
// The parent layer's async transform applies to the current layer to take
// `bounds` into the same coordinate space as `parentClippedBounds`. However,
// we're going to do the inverse operation and unapply this transform to
// `parentClippedBounds` to bring it into the same coordinate space as
// `bounds`.
AsyncTransform appliesToLayer =
mApzcMap[parentGuid].apzc->GetCurrentAsyncTransform(
AsyncPanZoomController::eForCompositing);
// Do the unapplication
LayerRect parentClippedBoundsInParentLayerSpace =
(parentClippedBounds - appliesToLayer.mTranslation) /
appliesToLayer.mScale;
// And then clip `bounds` by the parent's comp bounds in the current space.
bounds = bounds.Intersect(
ViewAs<ParentLayerPixel>(parentClippedBoundsInParentLayerSpace,
PixelCastJustification::MovingDownToChildren));
// Done!
aDestMap.emplace(aGuid, bounds);
return bounds;
}
bool APZCTreeManager::AdvanceAnimationsInternal(
const MutexAutoLock& aProofOfMapLock, const SampleTime& aSampleTime) {
ClippedCompositionBoundsMap clippedCompBounds;
bool activeAnimations = false;
for (const auto& mapping : mApzcMap) {
AsyncPanZoomController* apzc = mapping.second.apzc;
// Note that this call is recursive, but it early-exits if called again
// with the same guid. So this loop is still amortized O(n) with respect to
// the number of APZCs.
ParentLayerRect clippedBounds = ComputeClippedCompositionBounds(
aProofOfMapLock, clippedCompBounds, mapping.first);
apzc->ReportCheckerboard(aSampleTime, clippedBounds);
activeAnimations |= apzc->AdvanceAnimations(aSampleTime);
}
return activeAnimations;
}
void APZCTreeManager::PrintAPZCInfo(const ScrollNode& aLayer,
const AsyncPanZoomController* apzc) {
const FrameMetrics& metrics = aLayer.Metrics();
std::stringstream guidStr;
guidStr << apzc->GetGuid();
mApzcTreeLog << "APZC " << guidStr.str()
<< "\tcb=" << metrics.GetCompositionBounds()
<< "\tsr=" << metrics.GetScrollableRect()
<< (metrics.IsScrollInfoLayer() ? "\tscrollinfo" : "")
<< (apzc->HasScrollgrab() ? "\tscrollgrab" : "") << "\t"
<< aLayer.Metadata().GetContentDescription().get();
}
void APZCTreeManager::AttachNodeToTree(HitTestingTreeNode* aNode,
HitTestingTreeNode* aParent,
HitTestingTreeNode* aNextSibling) {
if (aNextSibling) {
aNextSibling->SetPrevSibling(aNode);
} else if (aParent) {
aParent->SetLastChild(aNode);
} else {
MOZ_ASSERT(!mRootNode);
mRootNode = aNode;
aNode->MakeRoot();
}
}
already_AddRefed<HitTestingTreeNode> APZCTreeManager::RecycleOrCreateNode(
const RecursiveMutexAutoLock& aProofOfTreeLock, TreeBuildingState& aState,
AsyncPanZoomController* aApzc, LayersId aLayersId) {
// Find a node without an APZC and return it. Note that unless the layer tree
// actually changes, this loop should generally do an early-return on the
// first iteration, so it should be cheap in the common case.
for (int32_t i = aState.mNodesToDestroy.Length() - 1; i >= 0; i--) {
RefPtr<HitTestingTreeNode> node = aState.mNodesToDestroy[i];
if (node->IsRecyclable(aProofOfTreeLock)) {
aState.mNodesToDestroy.RemoveElementAt(i);
node->RecycleWith(aProofOfTreeLock, aApzc, aLayersId);
return node.forget();
}
}
RefPtr<HitTestingTreeNode> node =
new HitTestingTreeNode(aApzc, false, aLayersId);
return node.forget();
}
void APZCTreeManager::StartScrollbarDrag(const ScrollableLayerGuid& aGuid,
const AsyncDragMetrics& aDragMetrics) {
if (!APZThreadUtils::IsControllerThread()) {
APZThreadUtils::RunOnControllerThread(
NewRunnableMethod<ScrollableLayerGuid, AsyncDragMetrics>(
"layers::APZCTreeManager::StartScrollbarDrag", this,
&APZCTreeManager::StartScrollbarDrag, aGuid, aDragMetrics));
return;
}
APZThreadUtils::AssertOnControllerThread();
RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
if (!apzc) {
NotifyScrollbarDragRejected(aGuid);
return;
}
uint64_t inputBlockId = aDragMetrics.mDragStartSequenceNumber;
mInputQueue->ConfirmDragBlock(inputBlockId, apzc, aDragMetrics);
}
bool APZCTreeManager::StartAutoscroll(const ScrollableLayerGuid& aGuid,
const ScreenPoint& aAnchorLocation) {
APZThreadUtils::AssertOnControllerThread();
RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
if (!apzc) {
if (XRE_IsGPUProcess()) {
// If we're in the compositor process, the "return false" will be
// ignored because the query comes over the PAPZCTreeManager protocol
// via an async message. In this case, send an explicit rejection
// message to content.
NotifyAutoscrollRejected(aGuid);
}
return false;
}
apzc->StartAutoscroll(aAnchorLocation);
return true;
}
void APZCTreeManager::StopAutoscroll(const ScrollableLayerGuid& aGuid) {
APZThreadUtils::AssertOnControllerThread();
if (RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid)) {
apzc->StopAutoscroll();
}
}
void APZCTreeManager::NotifyScrollbarDragInitiated(
uint64_t aDragBlockId, const ScrollableLayerGuid& aGuid,
ScrollDirection aDirection) const {
RefPtr<GeckoContentController> controller =
GetContentController(aGuid.mLayersId);
if (controller) {
controller->NotifyAsyncScrollbarDragInitiated(aDragBlockId, aGuid.mScrollId,
aDirection);
}
}
void APZCTreeManager::NotifyScrollbarDragRejected(
const ScrollableLayerGuid& aGuid) const {
RefPtr<GeckoContentController> controller =
GetContentController(aGuid.mLayersId);
if (controller) {
controller->NotifyAsyncScrollbarDragRejected(aGuid.mScrollId);
}
}
void APZCTreeManager::NotifyAutoscrollRejected(
const ScrollableLayerGuid& aGuid) const {
RefPtr<GeckoContentController> controller =
GetContentController(aGuid.mLayersId);
MOZ_ASSERT(controller);
controller->NotifyAsyncAutoscrollRejected(aGuid.mScrollId);
}
void SetHitTestData(HitTestingTreeNode* aNode,
const WebRenderScrollDataWrapper& aLayer,
const EventRegionsOverride& aOverrideFlags) {
aNode->SetHitTestData(aLayer.GetVisibleRegion(),
aLayer.GetRemoteDocumentSize(),
aLayer.GetTransformTyped(), aOverrideFlags,
aLayer.GetAsyncZoomContainerId());
}
HitTestingTreeNode* APZCTreeManager::PrepareNodeForLayer(
const RecursiveMutexAutoLock& aProofOfTreeLock, const ScrollNode& aLayer,
const FrameMetrics& aMetrics, LayersId aLayersId,
const Maybe<ZoomConstraints>& aZoomConstraints,
const AncestorTransform& aAncestorTransform, HitTestingTreeNode* aParent,
HitTestingTreeNode* aNextSibling, TreeBuildingState& aState) {
bool needsApzc = true;
if (!aMetrics.IsScrollable()) {
needsApzc = false;
}
// XXX: As a future optimization we can probably stick these things on the
// TreeBuildingState, and update them as we change layers id during the
// traversal
RefPtr<GeckoContentController> geckoContentController;
CompositorBridgeParent::CallWithIndirectShadowTree(
aLayersId, [&](LayerTreeState& lts) -> void {
geckoContentController = lts.mController;
});
if (!geckoContentController) {
needsApzc = false;
}
if (Maybe<uint64_t> zoomAnimationId = aLayer.GetZoomAnimationId()) {
aState.mZoomAnimationId = zoomAnimationId;
}
RefPtr<HitTestingTreeNode> node = nullptr;
if (!needsApzc) {
// Note: if layer properties must be propagated to nodes, RecvUpdate in
// LayerTransactionParent.cpp must ensure that APZ will be notified
// when those properties change.
node = RecycleOrCreateNode(aProofOfTreeLock, aState, nullptr, aLayersId);
AttachNodeToTree(node, aParent, aNextSibling);
SetHitTestData(node, aLayer, aState.mOverrideFlags.top());
node->SetScrollbarData(aLayer.GetScrollbarAnimationId(),
aLayer.GetScrollbarData());
node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(),
aLayer.GetFixedPositionSides(),
aLayer.GetFixedPositionAnimationId());
node->SetStickyPosData(aLayer.GetStickyScrollContainerId(),
aLayer.GetStickyScrollRangeOuter(),
aLayer.GetStickyScrollRangeInner(),
aLayer.GetStickyPositionAnimationId());
return node;
}
AsyncPanZoomController* apzc = nullptr;
// If we get here, aLayer is a scrollable layer and somebody
// has registered a GeckoContentController for it, so we need to ensure
// it has an APZC instance to manage its scrolling.
// aState.mApzcMap allows reusing the exact same APZC instance for different
// layers with the same FrameMetrics data. This is needed because in some
// cases content that is supposed to scroll together is split into multiple
// layers because of e.g. non-scrolling content interleaved in z-index order.
ScrollableLayerGuid guid(aLayersId, aMetrics.GetPresShellId(),
aMetrics.GetScrollId());
auto insertResult = aState.mApzcMap.insert(std::make_pair(
guid,
ApzcMapData{static_cast<AsyncPanZoomController*>(nullptr), Nothing()}));
if (!insertResult.second) {
apzc = insertResult.first->second.apzc;
PrintAPZCInfo(aLayer, apzc);
}
APZCTM_LOG("Found APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64
"\n",
apzc, aLayer.GetLayer(), uint64_t(guid.mLayersId), guid.mScrollId);
// If we haven't encountered a layer already with the same metrics, then we
// need to do the full reuse-or-make-an-APZC algorithm, which is contained
// inside the block below.
if (apzc == nullptr) {
apzc = aLayer.GetApzc();
// If the content represented by the scrollable layer has changed (which may
// be possible because of DLBI heuristics) then we don't want to keep using
// the same old APZC for the new content. Also, when reparenting a tab into
// a new window a layer might get moved to a different layer tree with a
// different APZCTreeManager. In these cases we don't want to reuse the same
// APZC, so null it out so we run through the code to find another one or
// create one.
if (apzc && (!apzc->Matches(guid) || !apzc->HasTreeManager(this))) {
apzc = nullptr;
}
// See if we can find an APZC from the previous tree that matches the
// ScrollableLayerGuid from this layer. If there is one, then we know that
// the layout of the page changed causing the layer tree to be rebuilt, but
// the underlying content for the APZC is still there somewhere. Therefore,
// we want to find the APZC instance and continue using it here.
//
// We particularly want to find the primary-holder node from the previous
// tree that matches, because we don't want that node to get destroyed. If
// it does get destroyed, then the APZC will get destroyed along with it by
// definition, but we want to keep that APZC around in the new tree.
// We leave non-primary-holder nodes in the destroy list because we don't
// care about those nodes getting destroyed.
for (size_t i = 0; i < aState.mNodesToDestroy.Length(); i++) {
RefPtr<HitTestingTreeNode> n = aState.mNodesToDestroy[i];
if (n->IsPrimaryHolder() && n->GetApzc() && n->GetApzc()->Matches(guid)) {
node = n;
if (apzc != nullptr) {
// If there is an APZC already then it should match the one from the
// old primary-holder node
MOZ_ASSERT(apzc == node->GetApzc());
}
apzc = node->GetApzc();
break;
}
}
// The APZC we get off the layer may have been destroyed previously if the
// layer was inactive or omitted from the layer tree for whatever reason
// from a layers update. If it later comes back it will have a reference to
// a destroyed APZC and so we need to throw that out and make a new one.
bool newApzc = (apzc == nullptr || apzc->IsDestroyed());
if (newApzc) {
apzc = NewAPZCInstance(aLayersId, geckoContentController);
apzc->SetCompositorController(aState.mCompositorController.get());
MOZ_ASSERT(node == nullptr);
node = new HitTestingTreeNode(apzc, true, aLayersId);
} else {
// If we are re-using a node for this layer clear the tree pointers
// so that it doesn't continue pointing to nodes that might no longer
// be in the tree. These pointers will get reset properly as we continue
// building the tree. Also remove it from the set of nodes that are going
// to be destroyed, because it's going to remain active.
aState.mNodesToDestroy.RemoveElement(node);
node->SetPrevSibling(nullptr);
node->SetLastChild(nullptr);
}
if (aMetrics.IsRootContent()) {
apzc->SetZoomAnimationId(aState.mZoomAnimationId);
aState.mZoomAnimationId = Nothing();
}
APZCTM_LOG(
"Using APZC %p for layer %p with identifiers %" PRIx64 " %" PRId64 "\n",
apzc, aLayer.GetLayer(), uint64_t(aLayersId), aMetrics.GetScrollId());
apzc->NotifyLayersUpdated(aLayer.Metadata(), aState.mIsFirstPaint,
aLayersId == aState.mOriginatingLayersId);
// Since this is the first time we are encountering an APZC with this guid,
// the node holding it must be the primary holder. It may be newly-created
// or not, depending on whether it went through the newApzc branch above.
MOZ_ASSERT(node->IsPrimaryHolder() && node->GetApzc() &&
node->GetApzc()->Matches(guid));
SetHitTestData(node, aLayer, aState.mOverrideFlags.top());
apzc->SetAncestorTransform(aAncestorTransform);
PrintAPZCInfo(aLayer, apzc);
// Bind the APZC instance into the tree of APZCs
AttachNodeToTree(node, aParent, aNextSibling);
// For testing, log the parent scroll id of every APZC that has a
// parent. This allows test code to reconstruct the APZC tree.
// Note that we currently only do this for APZCs in the layer tree
// that originated the update, because the only identifying information
// we are logging about APZCs is the scroll id, and otherwise we could
// confuse APZCs from different layer trees with the same scroll id.
if (aLayersId == aState.mOriginatingLayersId) {
if (apzc->HasNoParentWithSameLayersId()) {
aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
"hasNoParentWithSameLayersId", true);
} else {
MOZ_ASSERT(apzc->GetParent());
aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
"parentScrollId",
apzc->GetParent()->GetGuid().mScrollId);
}
if (aMetrics.IsRootContent()) {
aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(), "isRootContent",
true);
}
// Note that the async scroll offset is in ParentLayer pixels
aState.mPaintLogger.LogTestData(
aMetrics.GetScrollId(), "asyncScrollOffset",
apzc->GetCurrentAsyncScrollOffset(
AsyncPanZoomController::eForHitTesting));
aState.mPaintLogger.LogTestData(aMetrics.GetScrollId(),
"hasAsyncKeyScrolled",
apzc->TestHasAsyncKeyScrolled());
}
// We must update the zoom constraints even if the apzc isn't new because it
// might have moved.
if (node->IsPrimaryHolder()) {
if (aZoomConstraints) {
apzc->UpdateZoomConstraints(*aZoomConstraints);
#ifdef DEBUG
auto it = mZoomConstraints.find(guid);
if (it != mZoomConstraints.end()) {
MOZ_ASSERT(it->second == *aZoomConstraints);
}
} else {
// We'd like to assert these things (if the first doesn't hold then at
// least the second) but neither are not true because xul root content
// gets zoomable zoom constraints, but which is not zoomable because it
// doesn't have a root scroll frame.
// clang-format off
// MOZ_ASSERT(mZoomConstraints.find(guid) == mZoomConstraints.end());
// auto it = mZoomConstraints.find(guid);
// if (it != mZoomConstraints.end()) {
// MOZ_ASSERT(!it->second.mAllowZoom && !it->second.mAllowDoubleTapZoom);
// }
// clang-format on
#endif
}
}
// Add a guid -> APZC mapping for the newly created APZC.
insertResult.first->second.apzc = apzc;
} else {
// We already built an APZC earlier in this tree walk, but we have another
// layer now that will also be using that APZC. The hit-test region on the
// APZC needs to be updated to deal with the new layer's hit region.
node = RecycleOrCreateNode(aProofOfTreeLock, aState, apzc, aLayersId);
AttachNodeToTree(node, aParent, aNextSibling);
// Even though different layers associated with a given APZC may be at
// different levels in the layer tree (e.g. one being an uncle of another),
// we require from Layout that the CSS transforms up to their common
// ancestor be roughly the same. There are cases in which the transforms
// are not exactly the same, for example if the parent is container layer
// for an opacity, and this container layer has a resolution-induced scale
// as its base transform and a prescale that is supposed to undo that scale.
// 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; 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.ContainsPerspectiveTransform()) {
aState.mPerspectiveTransformsDeferredToChildren.insert(
PairType{apzc, apzc->GetAncestorTransformPerspective()});
apzc->SetAncestorTransform(aAncestorTransform);
} else {
aState.mPerspectiveTransformsDeferredToChildren.insert(
PairType{apzc, aAncestorTransform.GetPerspectiveTransform()});
}
}
SetHitTestData(node, aLayer, aState.mOverrideFlags.top());
}
// Note: if layer properties must be propagated to nodes, RecvUpdate in
// LayerTransactionParent.cpp must ensure that APZ will be notified
// when those properties change.
node->SetScrollbarData(aLayer.GetScrollbarAnimationId(),
aLayer.GetScrollbarData());
node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId(),
aLayer.GetFixedPositionSides(),
aLayer.GetFixedPositionAnimationId());
node->SetStickyPosData(aLayer.GetStickyScrollContainerId(),
aLayer.GetStickyScrollRangeOuter(),
aLayer.GetStickyScrollRangeInner(),
aLayer.GetStickyPositionAnimationId());
return node;
}
template <typename PanGestureOrScrollWheelInput>
static bool WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput) {
if (!XRE_IsParentProcess() || !NS_IsMainThread()) {
return true;
}
WidgetWheelEvent wheelEvent = aPanInput.ToWidgetEvent(nullptr);
return APZInputBridge::ActionForWheelEvent(&wheelEvent).isSome();
}
/*static*/
void APZCTreeManager::FlushApzRepaints(LayersId aLayersId) {
// Previously, paints were throttled and therefore this method was used to
// ensure any pending paints were flushed. Now, paints are flushed
// immediately, so it is safe to simply send a notification now.
APZCTM_LOG("Flushing repaints for layers id 0x%" PRIx64 "\n",
uint64_t(aLayersId));
RefPtr<GeckoContentController> controller = GetContentController(aLayersId);
#ifndef MOZ_WIDGET_ANDROID
// On Android, this code is run in production and may actually get a nullptr
// controller here. On other platforms this code is test-only and should never
// get a nullptr.
MOZ_ASSERT(controller);
#endif
if (controller) {
controller->DispatchToRepaintThread(NewRunnableMethod(
"layers::GeckoContentController::NotifyFlushComplete", controller,
&GeckoContentController::NotifyFlushComplete));
}
}
void APZCTreeManager::MarkAsDetached(LayersId aLayersId) {
RecursiveMutexAutoLock lock(mTreeLock);
mDetachedLayersIds.insert(aLayersId);
}
static bool HasNonLockModifier(Modifiers aModifiers) {
return (aModifiers & (MODIFIER_ALT | MODIFIER_ALTGRAPH | MODIFIER_CONTROL |
MODIFIER_FN | MODIFIER_META | MODIFIER_SHIFT |
MODIFIER_SYMBOL | MODIFIER_OS)) != 0;
}
APZEventResult APZCTreeManager::ReceiveInputEvent(InputData& aEvent) {
APZThreadUtils::AssertOnControllerThread();
InputHandlingState state{aEvent};
// Use a RAII class for updating the focus sequence number of this event
AutoFocusSequenceNumberSetter focusSetter(mFocusState, aEvent);
switch (aEvent.mInputType) {
case MULTITOUCH_INPUT: {
MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
ProcessTouchIn