diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:34:42 +0000 |
commit | da4c7e7ed675c3bf405668739c3012d140856109 (patch) | |
tree | cdd868dba063fecba609a1d819de271f0d51b23e /gfx/layers/apz | |
parent | Adding upstream version 125.0.3. (diff) | |
download | firefox-da4c7e7ed675c3bf405668739c3012d140856109.tar.xz firefox-da4c7e7ed675c3bf405668739c3012d140856109.zip |
Adding upstream version 126.0.upstream/126.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/layers/apz')
29 files changed, 591 insertions, 181 deletions
diff --git a/gfx/layers/apz/public/APZPublicUtils.h b/gfx/layers/apz/public/APZPublicUtils.h index 6433008b4c..47a07c8fd6 100644 --- a/gfx/layers/apz/public/APZPublicUtils.h +++ b/gfx/layers/apz/public/APZPublicUtils.h @@ -55,9 +55,8 @@ const ScreenMargin CalculatePendingDisplayPort( * between 1 and 8 inclusive. The multiplier is chosen based on the provided * base size, such that multiplier is larger when the base size is larger. * The exact details are somewhat arbitrary and tuned by hand. - * This function is intended to only be used with WebRender, because that is - * the codepath that wants to use a larger displayport alignment, because - * moving the displayport is relatively expensive with WebRender. + * We use a large displayport alignment because moving the displayport is + * relatively expensive with WebRender. */ gfx::IntSize GetDisplayportAlignmentMultiplier(const ScreenSize& aBaseSize); diff --git a/gfx/layers/apz/public/GeckoContentControllerTypes.h b/gfx/layers/apz/public/GeckoContentControllerTypes.h index 8ab478eab5..8616455137 100644 --- a/gfx/layers/apz/public/GeckoContentControllerTypes.h +++ b/gfx/layers/apz/public/GeckoContentControllerTypes.h @@ -24,7 +24,7 @@ MOZ_DEFINE_ENUM_CLASS(GeckoContentController_APZStateChange, ( eTransformEnd, /** * APZ started a touch. - * |aArg| is 1 if touch can be a pan, 0 otherwise. + * |aArg| is 1 if touch can be a pan or zoom, 0 otherwise. */ eStartTouch, /** @@ -33,7 +33,7 @@ MOZ_DEFINE_ENUM_CLASS(GeckoContentController_APZStateChange, ( eStartPanning, /** * APZ finished processing a touch. - * |aArg| is 1 if touch was a click, 0 otherwise. + * |aArg| is a `apz::SingleTapState` defined in APZUtils.h. */ eEndTouch )); diff --git a/gfx/layers/apz/src/APZCTreeManager.cpp b/gfx/layers/apz/src/APZCTreeManager.cpp index ef3cde3596..bc0b6a8dc6 100644 --- a/gfx/layers/apz/src/APZCTreeManager.cpp +++ b/gfx/layers/apz/src/APZCTreeManager.cpp @@ -145,8 +145,7 @@ struct APZCTreeManager::TreeBuildingState { // 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. + // is cleared back to Nothing(). Maybe<uint64_t> mZoomAnimationId; // See corresponding members of APZCTreeManager. These are the same thing, but @@ -538,12 +537,9 @@ APZCTreeManager::UpdateHitTestingTree(const WebRenderScrollDataWrapper& aRoot, 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. + // Note that a "valid" animation id is always nonzero, so we don't + // need to worry about handling the case where the animation id is + // zero. if (node->GetScrollbarAnimationId()) { if (node->IsScrollThumbNode()) { state.mScrollThumbs.push_back(node); @@ -555,11 +551,9 @@ APZCTreeManager::UpdateHitTestingTree(const WebRenderScrollDataWrapper& aRoot, } } - // 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); } @@ -2290,8 +2284,7 @@ void APZCTreeManager::SetupScrollbarDrag( // Under some conditions, we can confirm the drag block right away. // Otherwise, we have to wait for a main-thread confirmation. - if (StaticPrefs::apz_drag_initial_enabled() && - // check that the scrollbar's target scroll frame is layerized + if (/* check that the scrollbar's target scroll frame is layerized */ aScrollThumbNode->GetScrollTargetId() == aApzc->GetGuid().mScrollId && !aApzc->IsScrollInfoLayer()) { uint64_t dragBlockId = dragBlock->GetBlockId(); @@ -3538,12 +3531,6 @@ already_AddRefed<AsyncPanZoomController> APZCTreeManager::CommonAncestor( } bool APZCTreeManager::IsFixedToRootContent( - const HitTestingTreeNode* aNode) const { - MutexAutoLock lock(mMapLock); - return IsFixedToRootContent(FixedPositionInfo(aNode), lock); -} - -bool APZCTreeManager::IsFixedToRootContent( const FixedPositionInfo& aFixedInfo, const MutexAutoLock& aProofOfMapLock) const { ScrollableLayerGuid::ViewID fixedTarget = aFixedInfo.mFixedPosTarget; diff --git a/gfx/layers/apz/src/APZCTreeManager.h b/gfx/layers/apz/src/APZCTreeManager.h index 71d35fd5a6..939e7572a4 100644 --- a/gfx/layers/apz/src/APZCTreeManager.h +++ b/gfx/layers/apz/src/APZCTreeManager.h @@ -199,12 +199,10 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { LayersId aOriginatingLayersId, uint32_t aPaintSequenceNumber); /** - * Called when webrender is enabled, from the sampler thread. This function - * populates the provided transaction with any async scroll offsets needed. - * It also advances APZ animations to the specified sample time, and requests - * another composite if there are still active animations. - * In effect it is the webrender equivalent of (part of) the code in - * AsyncCompositionManager. + * Called from the sampler thread. This function populates the provided + * transaction with any async scroll offsets needed. It also advances APZ + * animations to the specified sample time, and requests another composite if + * there are still active animations. */ void SampleForWebRender(const Maybe<VsyncId>& aVsyncId, wr::TransactionWrapper& aTxn, @@ -509,7 +507,7 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { void AssertOnUpdaterThread(); // Returns a pointer to the WebRenderAPI this APZCTreeManager is for. - // This might be null (for example, if WebRender is not enabled). + // This might be null (for example, during GTests). already_AddRefed<wr::WebRenderAPI> GetWebRenderAPI() const; protected: @@ -679,12 +677,8 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { struct FixedPositionInfo; struct StickyPositionInfo; - // Returns true if |aNode| is a fixed layer that is fixed to the root content - // APZC. - // The map lock is required within these functions; if the map lock is already - // being held by the caller, the second overload should be used. If the map - // lock is not being held at the call site, the first overload should be used. - bool IsFixedToRootContent(const HitTestingTreeNode* aNode) const; + // Returns true if |aFixedInfo| represents a layer that is fixed to the root + // content APZC. bool IsFixedToRootContent(const FixedPositionInfo& aFixedInfo, const MutexAutoLock& aProofOfMapLock) const; @@ -919,15 +913,14 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { } }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on scroll thumbs. This information - * is extracted from the HitTestingTreeNodes for the WebRender case because - * accessing the HitTestingTreeNodes requires holding the tree lock which - * we cannot do on the WR sampler thread. mScrollThumbInfo, however, can + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on scroll + * thumbs. This information is extracted from the HitTestingTreeNodes because + * accessing the HitTestingTreeNodes requires holding the tree lock which we + * cannot do on the WebRender sampler thread. mScrollThumbInfo, however, can * be accessed while just holding the mMapLock which is safe to do on the - * sampler thread. - * mMapLock must be acquired while accessing or modifying mScrollThumbInfo. + * sampler thread. mMapLock must be acquired while accessing or modifying + * mScrollThumbInfo. */ std::vector<ScrollThumbInfo> mScrollThumbInfo; @@ -945,12 +938,11 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { mScrollDirection(aScrollDirection) {} }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on root scrollbars. This - * information is extracted from the HitTestingTreeNodes for the WebRender - * case because accessing the HitTestingTreeNodes requires holding the tree - * lock which we cannot do on the WR sampler thread. mRootScrollbarInfo, + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on root + * scrollbars. This information is extracted from the HitTestingTreeNodes + * because accessing the HitTestingTreeNodes requires holding the tree lock + * which we cannot do on the WebRender sampler thread. mRootScrollbarInfo, * however, can be accessed while just holding the mMapLock which is safe to * do on the sampler thread. * mMapLock must be acquired while accessing or modifying mRootScrollbarInfo. @@ -970,15 +962,14 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { explicit FixedPositionInfo(const HitTestingTreeNode* aNode); }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on fixed position content. This - * information is extracted from the HitTestingTreeNodes for the WebRender - * case because accessing the HitTestingTreeNodes requires holding the tree - * lock which we cannot do on the WR sampler thread. mFixedPositionInfo, - * however, can be accessed while just holding the mMapLock which is safe to - * do on the sampler thread. mMapLock must be acquired while accessing or - * modifying mFixedPositionInfo. + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on fixed + * position content. This information is extracted from the + * HitTestingTreeNodes because accessing the HitTestingTreeNodes requires + * holding the tree lock which we cannot do on the WebRender sampler thread. + * mFixedPositionInfo, however, can be accessed while just holding the + * mMapLock which is safe to do on the sampler thread. mMapLock must be + * acquired while accessing or modifying mFixedPositionInfo. */ std::vector<FixedPositionInfo> mFixedPositionInfo; @@ -997,15 +988,14 @@ class APZCTreeManager : public IAPZCTreeManager, public APZInputBridge { explicit StickyPositionInfo(const HitTestingTreeNode* aNode); }; /** - * If this APZCTreeManager is being used with WebRender, this vector gets - * populated during a layers update. It holds a package of information needed - * to compute and set the async transforms on sticky position content. This - * information is extracted from the HitTestingTreeNodes for the WebRender - * case because accessing the HitTestingTreeNodes requires holding the tree - * lock which we cannot do on the WR sampler thread. mStickyPositionInfo, - * however, can be accessed while just holding the mMapLock which is safe to - * do on the sampler thread. mMapLock must be acquired while accessing or - * modifying mStickyPositionInfo. + * This vector gets populated during a layers update. It holds a package of + * information needed to compute and set the async transforms on sticky + * position content. This information is extracted from the + * HitTestingTreeNodes because accessing the HitTestingTreeNodes requires + * holding the tree lock which we cannot do on the WebRender sampler thread. + * mStickyPositionInfo, however, can be accessed while just holding the + * mMapLock which is safe to do on the sampler thread. mMapLock must be + * acquired while accessing or modifying mStickyPositionInfo. */ std::vector<StickyPositionInfo> mStickyPositionInfo; diff --git a/gfx/layers/apz/src/APZSampler.cpp b/gfx/layers/apz/src/APZSampler.cpp index d0e251cec4..e14535da0d 100644 --- a/gfx/layers/apz/src/APZSampler.cpp +++ b/gfx/layers/apz/src/APZSampler.cpp @@ -125,19 +125,16 @@ AsyncTransform APZSampler::GetCurrentAsyncTransform( ParentLayerRect APZSampler::GetCompositionBounds( const LayersId& aLayersId, const ScrollableLayerGuid::ViewID& aScrollId, const MutexAutoLock& aProofOfMapLock) const { - // This function can get called on the compositor in case of non WebRender - // get called on the sampler thread in case of WebRender. AssertOnSamplerThread(); RefPtr<AsyncPanZoomController> apzc = mApz->GetTargetAPZC(aLayersId, aScrollId, aProofOfMapLock); if (!apzc) { - // On WebRender it's possible that this function can get called even after - // the target APZC has been already destroyed because destroying the - // animation which triggers this function call is basically processed later - // than the APZC one, i.e. queue mCompositorAnimationsToDelete in - // WebRenderBridgeParent and then remove them in - // WebRenderBridgeParent::RemoveEpochDataPriorTo. + // It's possible that this function can get called even after the target + // APZC has been already destroyed because destroying the animation which + // triggers this function call is basically processed later than the APZC + // one, i.e. queue mCompositorAnimationsToDelete in WebRenderBridgeParent + // and then remove them in WebRenderBridgeParent::RemoveEpochDataPriorTo. return ParentLayerRect(); } diff --git a/gfx/layers/apz/src/APZUtils.h b/gfx/layers/apz/src/APZUtils.h index 6614b6eeae..f36a01f65d 100644 --- a/gfx/layers/apz/src/APZUtils.h +++ b/gfx/layers/apz/src/APZUtils.h @@ -233,6 +233,14 @@ bool AboutToCheckerboard(const FrameMetrics& aPaintedMetrics, */ SideBits GetOverscrollSideBits(const ParentLayerPoint& aOverscrollAmount); +// Represents tri-state when a touch-end event received. +enum class SingleTapState : uint8_t { + NotClick, // The touch-block doesn't trigger a click event + WasClick, // The touch-block did trigger a click event + NotYetDetermined, // It's not yet determined whether the touch-block trigger + // a click event or not since double-tapping might happen +}; + } // namespace apz } // namespace layers diff --git a/gfx/layers/apz/src/AsyncPanZoomController.cpp b/gfx/layers/apz/src/AsyncPanZoomController.cpp index edbd2ecffa..a070340421 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.cpp +++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp @@ -242,11 +242,6 @@ typedef PlatformSpecificStateBase * Setting this pref to true will cause APZ to handle mouse-dragging of * scrollbar thumbs. * - * \li\b apz.drag.initial.enabled - * Setting this pref to true will cause APZ to try to handle mouse-dragging - * of scrollbar thumbs without an initial round-trip to content to start it - * if possible. Only has an effect if apz.drag.enabled is also true. - * * \li\b apz.drag.touch.enabled * Setting this pref to true will cause APZ to handle touch-dragging of * scrollbar thumbs. Only has an effect if apz.drag.enabled is also true. @@ -1310,10 +1305,13 @@ nsEventStatus AsyncPanZoomController::OnTouchStart( if (RefPtr<GeckoContentController> controller = GetGeckoContentController()) { MOZ_ASSERT(GetCurrentTouchBlock()); - controller->NotifyAPZStateChange( - GetGuid(), APZStateChange::eStartTouch, + const bool canBePanOrZoom = GetCurrentTouchBlock()->GetOverscrollHandoffChain()->CanBePanned( - this), + this) || + (ZoomConstraintsAllowDoubleTapZoom() && + GetCurrentTouchBlock()->TouchActionAllowsDoubleTapZoom()); + controller->NotifyAPZStateChange( + GetGuid(), APZStateChange::eStartTouch, canBePanOrZoom, Some(GetCurrentTouchBlock()->GetBlockId())); } mLastTouch.mTimeStamp = mTouchStartTime = aEvent.mTimeStamp; @@ -3112,7 +3110,7 @@ nsEventStatus AsyncPanZoomController::GenerateSingleTap( // touch block caused a `click` event or not, thus for long-tap events, // it's not necessary. if (aType != TapType::eLongTapUp) { - touch->SetSingleTapOccurred(); + touch->SetSingleTapState(apz::SingleTapState::WasClick); } } // Because this may be being running as part of @@ -3143,7 +3141,7 @@ void AsyncPanZoomController::OnTouchEndOrCancel() { MOZ_ASSERT(GetCurrentTouchBlock()); controller->NotifyAPZStateChange( GetGuid(), APZStateChange::eEndTouch, - GetCurrentTouchBlock()->SingleTapOccurred(), + static_cast<int>(GetCurrentTouchBlock()->SingleTapState()), Some(GetCurrentTouchBlock()->GetBlockId())); } } @@ -3160,6 +3158,21 @@ nsEventStatus AsyncPanZoomController::OnSingleTapUp( return GenerateSingleTap(TapType::eSingleTap, aEvent.mPoint, aEvent.modifiers); } + + // Ignore the event if it does not have valid local coordinates. + // GenerateSingleTap will not send a tap in this case. + if (!ConvertToGecko(aEvent.mPoint)) { + return nsEventStatus_eIgnore; + } + + // Here we need to wait for the call to OnSingleTapConfirmed, we need to tell + // it to ActiveElementManager so that we can do element activation once + // ActiveElementManager got a single tap event later. + if (TouchBlockState* touch = GetCurrentTouchBlock()) { + if (!touch->IsDuringFastFling()) { + touch->SetSingleTapState(apz::SingleTapState::NotYetDetermined); + } + } return nsEventStatus_eIgnore; } @@ -5304,10 +5317,10 @@ void AsyncPanZoomController::UpdateCheckerboardEvent( const MutexAutoLock& aProofOfLock, uint32_t aMagnitude) { if (mCheckerboardEvent && mCheckerboardEvent->RecordFrameInfo(aMagnitude)) { // This checkerboard event is done. Report some metrics to telemetry. - mozilla::glean::gfx_checkerboard::severity.AccumulateSamples( - {mCheckerboardEvent->GetSeverity()}); - mozilla::glean::gfx_checkerboard::peak_pixel_count.AccumulateSamples( - {mCheckerboardEvent->GetPeak()}); + mozilla::glean::gfx_checkerboard::severity.AccumulateSingleSample( + mCheckerboardEvent->GetSeverity()); + mozilla::glean::gfx_checkerboard::peak_pixel_count.AccumulateSingleSample( + mCheckerboardEvent->GetPeak()); mozilla::glean::gfx_checkerboard::duration.AccumulateRawDuration( mCheckerboardEvent->GetDuration()); diff --git a/gfx/layers/apz/src/AsyncPanZoomController.h b/gfx/layers/apz/src/AsyncPanZoomController.h index d0c4537a66..5db831d1bf 100644 --- a/gfx/layers/apz/src/AsyncPanZoomController.h +++ b/gfx/layers/apz/src/AsyncPanZoomController.h @@ -1133,13 +1133,12 @@ class AsyncPanZoomController { UniquePtr<OverscrollEffectBase> mOverscrollEffect; - // Zoom animation id, used for zooming in WebRender. This should only be - // set on the APZC instance for the root content document (i.e. the one we - // support zooming on), and is only used if WebRender is enabled. The - // animation id itself refers to the transform animation id that was set on - // the stacking context in the WR display list. By changing the transform - // associated with this id, we can adjust the scaling that WebRender applies, - // thereby controlling the zoom. + // Zoom animation id, used for zooming. This should only be set on the APZC + // instance for the root content document (i.e. the one we support zooming + // on). The animation id itself refers to the transform animation id that was + // set on the stacking context in the WR display list. By changing the + // transform associated with this id, we can adjust the scaling that WebRender + // applies, thereby controlling the zoom. Maybe<uint64_t> mZoomAnimationId; // Position on screen where user first put their finger down. diff --git a/gfx/layers/apz/src/HitTestingTreeNode.h b/gfx/layers/apz/src/HitTestingTreeNode.h index a4958b1af5..b76f317825 100644 --- a/gfx/layers/apz/src/HitTestingTreeNode.h +++ b/gfx/layers/apz/src/HitTestingTreeNode.h @@ -182,16 +182,16 @@ class HitTestingTreeNode { LayersId mLayersId; - // This is only set if WebRender is enabled, and only for HTTNs - // where IsScrollThumbNode() returns true. It holds the animation id that we - // use to move the thumb node to reflect async scrolling. + // This is only set for HTTNs where IsScrollThumbNode() returns true. It holds + // the animation id that we use to move the thumb node to reflect async + // scrolling. Maybe<uint64_t> mScrollbarAnimationId; // This is set for scrollbar Container and Thumb layers. ScrollbarData mScrollbarData; - // This is only set if WebRender is enabled. It holds the animation id that - // we use to adjust fixed position content for the toolbar. + // This holds the animation id that we use to adjust fixed position content + // for the toolbar. Maybe<uint64_t> mFixedPositionAnimationId; ScrollableLayerGuid::ViewID mFixedPosTarget; @@ -200,8 +200,8 @@ class HitTestingTreeNode { ScrollableLayerGuid::ViewID mStickyPosTarget; LayerRectAbsolute mStickyScrollRangeOuter; LayerRectAbsolute mStickyScrollRangeInner; - // This is only set if WebRender is enabled. It holds the animation id that - // we use to adjust sticky position content for the toolbar. + // This holds the animation id that we use to adjust sticky position content + // for the toolbar. Maybe<uint64_t> mStickyPositionAnimationId; LayerIntRect mVisibleRect; diff --git a/gfx/layers/apz/src/InputBlockState.cpp b/gfx/layers/apz/src/InputBlockState.cpp index 367fbf9e90..785df56c10 100644 --- a/gfx/layers/apz/src/InputBlockState.cpp +++ b/gfx/layers/apz/src/InputBlockState.cpp @@ -16,6 +16,7 @@ #include "mozilla/StaticPrefs_test.h" #include "mozilla/Telemetry.h" // for Telemetry #include "mozilla/ToString.h" +#include "mozilla/layers/APZEventState.h" #include "mozilla/layers/IAPZCTreeManager.h" // for AllowedTouchBehavior #include "OverscrollHandoffState.h" #include "QueuedInput.h" @@ -636,12 +637,12 @@ TouchBlockState::TouchBlockState( : CancelableBlockState(aTargetApzc, aFlags), mAllowedTouchBehaviorSet(false), mDuringFastFling(false), - mSingleTapOccurred(false), mInSlop(false), mForLongTap(false), mLongTapWasProcessed(false), mIsWaitingLongTapResult(false), mNeedsWaitTouchMove(false), + mSingleTapState(apz::SingleTapState::NotClick), mTouchCounter(aCounter), mStartTime(GetTargetApzc()->GetFrameTime().Time()) { mOriginalTargetConfirmedState = mTargetConfirmed; @@ -700,13 +701,12 @@ void TouchBlockState::SetDuringFastFling() { bool TouchBlockState::IsDuringFastFling() const { return mDuringFastFling; } -void TouchBlockState::SetSingleTapOccurred() { - TBS_LOG("%p setting single-tap-occurred flag\n", this); - mSingleTapOccurred = true; +void TouchBlockState::SetSingleTapState(apz::SingleTapState aState) { + TBS_LOG("%p setting single-tap-state: %d\n", this, + static_cast<uint8_t>(aState)); + mSingleTapState = aState; } -bool TouchBlockState::SingleTapOccurred() const { return mSingleTapOccurred; } - bool TouchBlockState::MustStayActive() { // If this touch block is for long-tap, it doesn't need to be active after the // block was processed, it will be taken over by the original touch block diff --git a/gfx/layers/apz/src/InputBlockState.h b/gfx/layers/apz/src/InputBlockState.h index f20a4a5901..d65b1cb57b 100644 --- a/gfx/layers/apz/src/InputBlockState.h +++ b/gfx/layers/apz/src/InputBlockState.h @@ -454,14 +454,12 @@ class TouchBlockState : public CancelableBlockState { */ bool IsDuringFastFling() const; /** - * Set the single-tap-occurred flag that indicates that this touch block - * triggered a single tap event. + * Set the single-tap state flag that indicates that this touch block + * triggered (1) a click, (2) not a click, or (3) not yet sure it will trigger + * a click or not. */ - void SetSingleTapOccurred(); - /** - * @return true iff the single-tap-occurred flag is set on this block. - */ - bool SingleTapOccurred() const; + void SetSingleTapState(apz::SingleTapState aState); + apz::SingleTapState SingleTapState() const { return mSingleTapState; } /** * @return false iff touch-action is enabled and the allowed touch behaviors @@ -537,7 +535,6 @@ class TouchBlockState : public CancelableBlockState { nsTArray<TouchBehaviorFlags> mAllowedTouchBehaviors; bool mAllowedTouchBehaviorSet; bool mDuringFastFling; - bool mSingleTapOccurred; bool mInSlop; // A long tap involves two touch blocks: the original touch // block containing the `touchstart`, and a second one @@ -557,6 +554,7 @@ class TouchBlockState : public CancelableBlockState { // content response for a touch move event. It will be set just before // triggering a long-press event. bool mNeedsWaitTouchMove; + apz::SingleTapState mSingleTapState; ScreenIntPoint mSlopOrigin; // A reference to the InputQueue's touch counter TouchCounter& mTouchCounter; diff --git a/gfx/layers/apz/src/SampledAPZCState.cpp b/gfx/layers/apz/src/SampledAPZCState.cpp index 712a46a3b1..8cdd905aba 100644 --- a/gfx/layers/apz/src/SampledAPZCState.cpp +++ b/gfx/layers/apz/src/SampledAPZCState.cpp @@ -86,6 +86,8 @@ void SampledAPZCState::RemoveFractionalAsyncDelta() { // a snapshot of APZ state (decoupling it from APZ assumptions) and provides // it as an input to the compositor (so all compositor state should be // internally consistent based on this input). + // TODO(bug 1889267): Now that we use WebRender everywhere, can this hack be + // removed? if (mLayoutViewport.TopLeft() == mVisualScrollOffset) { return; } diff --git a/gfx/layers/apz/test/mochitest/helper_bug1669625.html b/gfx/layers/apz/test/mochitest/helper_bug1669625.html index 95d2a4bc2c..4a91c7f0b6 100644 --- a/gfx/layers/apz/test/mochitest/helper_bug1669625.html +++ b/gfx/layers/apz/test/mochitest/helper_bug1669625.html @@ -11,8 +11,7 @@ <script type="application/javascript"> async function test() { - if (SpecialPowers.getBoolPref("apz.force_disable_desktop_zooming_scrollbars") || - getPlatform() == "android") { + if (getPlatform() == "android") { return; } diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400-2.html b/gfx/layers/apz/test/mochitest/helper_bug1806400-2.html new file mode 100644 index 0000000000..8d2eeca58d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400-2.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=0.4"> +<title>Tests that double-tap-to-zoom never activates elements inside a scrollable container</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> +#scrollable { + height: 50vh; + width: 50vw; + background: yellow; + overflow: scroll; +} + +#scrollabletarget { + height: 200vh; + width: 200vh; + background: green; +} + +#scrollabletarget:active { + background: red; +} + +</style> +<div id="scrollable"> + <div id="scrollabletarget"> + </div> +</div> +<script> +async function test() { + ok(!scrollabletarget.matches(":active"), "should not be active initially"); + + let rAFID = requestAnimationFrame(function ensureInactive() { + let isActive = scrollabletarget.matches(":active"); + ok(!isActive, "Element activation should never happen!"); + if (!isActive) { + rAFID = requestAnimationFrame(ensureInactive); + } + }); + + await doubleTapOn(scrollabletarget, 50, 50, false /* useTouchpad */); + + cancelAnimationFrame(rAFID); +} + +if (getPlatform() != "mac" && getPlatform() != "android") { + ok(true, "Skipping test because double-tap-zoom isn't allowed on " + getPlatform()); + subtestDone(); +} else { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400-3.html b/gfx/layers/apz/test/mochitest/helper_bug1806400-3.html new file mode 100644 index 0000000000..2e5398119c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400-3.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=1.0"> +<title>Tests that :active state is changed on a scrollable container without any touch event listeners</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> +#scrollable { + height: 50vh; + width: 50vw; + background: yellow; + overflow: scroll; +} + +#scrollabletarget { + height: 200vh; + width: 200vh; + background: green; +} + +#scrollabletarget:active { + background: red; +} + +</style> +<div id="scrollable"> + <div id="scrollabletarget"> + </div> +</div> +<script> +async function test() { + ok(!scrollabletarget.matches(":active"), "should not be active initially"); + + await synthesizeNativeTap(scrollabletarget, 50, 50); + + // In JS there's no way to ensure `APZStateChange::eStartTouch` notification + // has been processed. So we wait for `:active` state change here. + await SimpleTest.promiseWaitForCondition( + () => scrollabletarget.matches(":active"), + "Waiting for :active state change"); + ok(scrollabletarget.matches(":active"), "should be active"); +} +waitUntilApzStable() +.then(test) +.then(subtestDone, subtestFailed); +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400-4.html b/gfx/layers/apz/test/mochitest/helper_bug1806400-4.html new file mode 100644 index 0000000000..46fa95b651 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400-4.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=0.4"> +<title>Tests that double-tap-to-zoom never activates elements inside non scrollable container</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> +#nonscrollabletarget { + height: 300px; + width: 300px; + background: green; +} + +#nonscrollabletarget:active { + background: red; +} + +</style> +<div id="nonscrollabletarget"> +</div> +<script> +async function test() { + ok(!nonscrollabletarget.matches(":active"), "should not be active initially"); + + let rAFID = requestAnimationFrame(function ensureInactive() { + let isActive = nonscrollabletarget.matches(":active"); + ok(!isActive, "Element activation should never happen!"); + if (!isActive) { + rAFID = requestAnimationFrame(ensureInactive); + } + }); + + await doubleTapOn(nonscrollabletarget, 50, 50, false /* useTouchpad */); + + cancelAnimationFrame(rAFID); +} + +if (getPlatform() != "mac" && getPlatform() != "android") { + ok(true, "Skipping test because double-tap-zoom isn't allowed on " + getPlatform()); + subtestDone(); +} else { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_bug1806400.html b/gfx/layers/apz/test/mochitest/helper_bug1806400.html new file mode 100644 index 0000000000..03be0c8535 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1806400.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<html> +<meta name="viewport" content="width=device-width; initial-scale=1.0"> +<title>Tests that :active state is changed with `touchstart` event listener</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> +<script src="apz_test_utils.js"></script> +<script src="apz_test_native_event_utils.js"></script> +<style> + #button { + width: 100px; + height: 100px; + } +</style> +<button id="button">Button</button> +<script> +async function test() { + // Set up an active touchstart event listner. + let eventPromise = promiseOneEvent(document.documentElement, "touchstart"); + await promiseApzFlushedRepaints(); + + await synthesizeNativeTouch(button, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT); + await eventPromise; + + // In JS there's no way to ensure `APZStateChange::eStartTouch` notification + // has been processed. So we wait for `:active` state change here. + await SimpleTest.promiseWaitForCondition( + () => button.matches(":active"), + "Waiting for :active state change"); + ok(button.matches(":active"), "should be active"); + + eventPromise = promiseOneEvent(button, "touchend"); + await synthesizeNativeTouch(button, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE); + await eventPromise; + + // Same as above. We need to wait for not `:active` state here. + await SimpleTest.promiseWaitForCondition( + () => !button.matches(":active"), + "Waiting for :active state change"); + ok(!button.matches(":active"), "should not be active"); +} + +if (getPlatform() == "windows") { + // Bug 1875916. On Windows synthesizeNativeTouch(TOUCH_REMOVE) causes + // `InjectTouchInput failure` with ERROR_TIMEOUT. + ok(true, "Test doesn't need to run on Windows"); + subtestDone(); +} else { + waitUntilApzStable() + .then(test) + .then(subtestDone, subtestFailed); +} +</script> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html new file mode 100644 index 0000000000..96ea0d3b09 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1888904 +--> +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width; initial-scale=1.0"> + <title>Test that events are delivered with correct coordinates to an iframe inide a no-op perspective transform</title> + <script src="apz_test_native_event_utils.js"></script> + <script src="apz_test_utils.js"></script> + <script src="/tests/SimpleTest/paint_listener.js"></script> + <style> + html, body { + margin: 0; + padding: 0; + } + iframe { + border: 0; + background-color: blue; + } + .modal-dialog { + position: absolute; + top: 500px; + left: 500px; + transform: translate(-50%, -50%); + border: 1px solid black; + } + .item { + perspective: 1000px; + transform: translate3d(0, 0, 0); + } + .g-recaptcha { + transform-origin: 0 0; + transform: scale(0.91); + } + </style> +</head> +<body> + <div class="modal-dialog"> + <div class="item"> + <div class="g-recaptcha"> + <iframe id="iframe" src="https://example.com/tests/gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective_child.html"></iframe> + </div> + </div> + </div> + </div> + </div> + <script type="application/javascript"> +async function test() { + // Wait for iframe to receive content transforms. + await SpecialPowers.spawn(iframe, [], async () => { + await SpecialPowers.contentTransformsReceived(content.window); + }); + + let clickCoordsInChild = { + offsetX: 0, + offsetY: 0 + }; + let childMessagePromise = new Promise(resolve => { + window.addEventListener("message", event => { + let data = JSON.parse(event.data); + if ("type" in data && data.type == "got-mouse-down") { + clickCoordsInChild = data.coords; + resolve(); + } + }) + }); + await synthesizeNativeMouseEventWithAPZ({ + type: "click", + target: iframe, + offsetX: 100, + offsetY: 100 + }); + await childMessagePromise; + is(clickCoordsInChild.offsetX, 110 /* 100 / 0.91 */, "x coordinate is correct"); + is(clickCoordsInChild.offsetY, 110 /* 100 / 0.91 */, "y coordinate is correct"); +} + +waitUntilApzStable() +.then(test) +.then(subtestDone, subtestFailed); + + </script> +</body> +</html> diff --git a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html index 22b880736d..3b8a7cef3e 100644 --- a/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html +++ b/gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html @@ -132,10 +132,10 @@ async function test() { `bottom left of scroller in testcase ${testId}`); } - // With the first two cases (circle masks) both WebRender and non-WebRender - // emit dispatch-to-content regions for the right side, so for now we just - // test for that. Eventually WebRender should be able to stop emitting DTC - // and we can update this test to be more precise in that case. + // With the first two cases (circle masks) WebRender emits dispatch-to-content + // regions for the right side, so for now we just test for that. + // Eventually WebRender should be able to stop emitting DTC + // and we can update this test to be more precise. // For the two rectangular test cases we get precise results rather than // dispatch-to-content. if (testId == 1 || testId == 2) { diff --git a/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html index 1947a89a8f..c3f02d23d9 100644 --- a/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html +++ b/gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html @@ -41,9 +41,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1151663 let config = getHitTestConfig(); let heightMultiplier = SpecialPowers.getCharPref("apz.y_stationary_size_multiplier"); - // With WebRender, the effective height multiplier can be reduced - // for alignment reasons. The reduction should be no more than a - // factor of two. + // The effective height multiplier can be reduced for alignment reasons. + // The reduction should be no more than a factor of two. heightMultiplier /= 2; info("effective displayport height multipler is " + heightMultiplier); diff --git a/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html b/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html index 3930cec3c3..b3d7b4352a 100644 --- a/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html +++ b/gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html @@ -76,6 +76,13 @@ async function test() { promiseOneEvent(targetElem, "click"), ]; + // Create a promise for :active state change since in the case where the + // target element is inside a scrollable container, APZ delays :active state + // change, it sometimes happens after all the relavant events above. + const activePromise = SimpleTest.promiseWaitForCondition( + () => targetElem.matches(":active"), + "Waiting for :active state change"); + // Perform a tap gesture await synthesizeNativeTap(targetElem, 50, 50); @@ -88,7 +95,7 @@ async function test() { // The value of ui.touch_activation.duration_ms should be set to // a large value. If we did not delay sending the synthesized // mouse events, this test will not timeout. - await Promise.all(mouseEventPromises); + await Promise.all([...mouseEventPromises, activePromise]); clearTimeout(failTimeout); diff --git a/gfx/layers/apz/test/mochitest/test_group_hittest-3.html b/gfx/layers/apz/test/mochitest/test_group_hittest-3.html index f5675ee790..eac0348b89 100644 --- a/gfx/layers/apz/test/mochitest/test_group_hittest-3.html +++ b/gfx/layers/apz/test/mochitest/test_group_hittest-3.html @@ -33,6 +33,7 @@ var prefs = [ var subtests = [ {"file": "helper_hittest_iframe_perspective.html", "prefs": prefs}, {"file": "helper_hittest_iframe_perspective-3.html", "prefs": prefs}, + {"file": "helper_hittest_iframe_perspective-4.html", "prefs": prefs}, ]; if (isApzEnabled()) { diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html index 0eee77a3ae..e6e4eb40fb 100644 --- a/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html +++ b/gfx/layers/apz/test/mochitest/test_group_touchevents-5.html @@ -9,6 +9,12 @@ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> <script type="application/javascript"> +// Increase the tap timeouts so the double-tap is still detected in case of +// random delays during testing. +var doubletap_prefs = [ + ["ui.click_hold_context_menus.delay", 10000], + ["apz.max_tap_time", 10000], +]; var subtests = [ // tests that scrolling doesn't cause extra SchedulePaint calls @@ -23,6 +29,18 @@ var subtests = [ {"file": "helper_bug1719855.html?prevent=contextmenu"}, {"file": "helper_bug1719855.html"}, {"file": "helper_bug1724759.html"}, + {"file": "helper_bug1806400.html", "prefs": [ + // This test uses `SimpleTest.promiseWaitForCondition` which waits for the + // given condition up to 3s, to avoid opening context menu during the time + // span use a longer `ui.click_hold_context_menus.delay` here. + ["ui.click_hold_context_menus.delay", 10000], + ["ui.touch_activation.duration_ms", 1000] + ]}, + {"file": "helper_bug1806400-2.html", "prefs": doubletap_prefs}, + {"file": "helper_bug1806400-3.html", "prefs": [ + ["ui.touch_activation.duration_ms", 90000] + ]}, + {"file": "helper_bug1806400-4.html", "prefs": doubletap_prefs}, // Add new subtests here. If this starts timing out because it's taking too // long, create a test_group_touchevents-6.html file. Refer to 1423011#c57 // for more details. diff --git a/gfx/layers/apz/test/mochitest/test_layerization.html b/gfx/layers/apz/test/mochitest/test_layerization.html index 0ff76de317..e97971b456 100644 --- a/gfx/layers/apz/test/mochitest/test_layerization.html +++ b/gfx/layers/apz/test/mochitest/test_layerization.html @@ -64,9 +64,8 @@ let config = getHitTestConfig(); let activateAllScrollFrames = config.activateAllScrollFrames; let heightMultiplier = SpecialPowers.getCharPref("apz.y_stationary_size_multiplier"); -// With WebRender, the effective height multiplier can be reduced -// for alignment reasons. The reduction should be no more than a -// factor of two. +// The effective height multiplier can be reduced for alignment reasons. +// The reduction should be no more than a factor of two. heightMultiplier /= 2; info("effective displayport height multipler is " + heightMultiplier); diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html index de54cf93fe..dd18e078b6 100644 --- a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html @@ -523,8 +523,7 @@ async function test() { // Scroll inner again // Tick the refresh driver once to make sure the compositor has sent the // updated scroll offset for the outer scroller to WebRender, so that the - // hit-test in sendWheelAndPaint takes it into account. (This isn't needed - // if using non-WR layers, but doesn't hurt either). + // hit-test in sendWheelAndPaint takes it into account. var dwu = SpecialPowers.getDOMWindowUtils(window); dwu.advanceTimeAndRefresh(16); dwu.restoreNormalRefresh(); diff --git a/gfx/layers/apz/util/APZEventState.cpp b/gfx/layers/apz/util/APZEventState.cpp index 5db6a08429..c205b09ca2 100644 --- a/gfx/layers/apz/util/APZEventState.cpp +++ b/gfx/layers/apz/util/APZEventState.cpp @@ -27,6 +27,7 @@ #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/MouseEventBinding.h" #include "mozilla/layers/APZCCallbackHelper.h" +#include "mozilla/layers/APZUtils.h" #include "mozilla/layers/IAPZCTreeManager.h" #include "mozilla/widget/nsAutoRollup.h" #include "nsCOMPtr.h" @@ -101,7 +102,7 @@ APZEventState::APZEventState(nsIWidget* aWidget, mContentReceivedInputBlockCallback(std::move(aCallback)), mPendingTouchPreventedResponse(false), mPendingTouchPreventedBlockId(0), - mEndTouchIsClick(false), + mEndTouchState(apz::SingleTapState::NotClick), mFirstTouchCancelled(false), mTouchEndCancelled(false), mReceivedNonTouchStart(false), @@ -349,11 +350,13 @@ void APZEventState::ProcessTouchEvent( case eTouchEnd: if (isTouchPrevented) { mTouchEndCancelled = true; - mEndTouchIsClick = false; + mEndTouchState = apz::SingleTapState::NotClick; } [[fallthrough]]; case eTouchCancel: - mActiveElementManager->HandleTouchEndEvent(mEndTouchIsClick); + if (mActiveElementManager->HandleTouchEndEvent(mEndTouchState)) { + mEndTouchState = apz::SingleTapState::NotClick; + } [[fallthrough]]; case eTouchMove: { if (!mReceivedNonTouchStart) { @@ -515,13 +518,13 @@ void APZEventState::ProcessAPZStateChange(ViewID aViewId, break; } case APZStateChange::eStartTouch: { - bool canBePan = aArg; - mActiveElementManager->HandleTouchStart(canBePan); + bool canBePanOrZoom = aArg; + mActiveElementManager->HandleTouchStart(canBePanOrZoom); // If this is a non-scrollable content, set a timer for the amount of // time specified by ui.touch_activation.duration_ms to clear the // active element state. - APZES_LOG("%s: can-be-pan=%d", __FUNCTION__, aArg); - if (!canBePan) { + APZES_LOG("%s: can-be-pan-or-zoom=%d", __FUNCTION__, aArg); + if (!canBePanOrZoom) { MOZ_ASSERT(aInputBlockId.isSome()); } break; @@ -532,8 +535,10 @@ void APZEventState::ProcessAPZStateChange(ViewID aViewId, break; } case APZStateChange::eEndTouch: { - mEndTouchIsClick = aArg; - mActiveElementManager->HandleTouchEnd(); + mEndTouchState = static_cast<apz::SingleTapState>(aArg); + if (mActiveElementManager->HandleTouchEnd(mEndTouchState)) { + mEndTouchState = apz::SingleTapState::NotClick; + } break; } } diff --git a/gfx/layers/apz/util/APZEventState.h b/gfx/layers/apz/util/APZEventState.h index 52febc0424..3a57e9e6c6 100644 --- a/gfx/layers/apz/util/APZEventState.h +++ b/gfx/layers/apz/util/APZEventState.h @@ -39,6 +39,10 @@ namespace layers { class ActiveElementManager; +namespace apz { +enum class SingleTapState : uint8_t; +} // namespace apz + typedef std::function<void(uint64_t /* input block id */, bool /* prevent default */)> ContentReceivedInputBlockCallback; @@ -106,7 +110,7 @@ class APZEventState final { bool mPendingTouchPreventedResponse; ScrollableLayerGuid mPendingTouchPreventedGuid; uint64_t mPendingTouchPreventedBlockId; - bool mEndTouchIsClick; + apz::SingleTapState mEndTouchState; bool mFirstTouchCancelled; bool mTouchEndCancelled; // Set to true when we have received any one of diff --git a/gfx/layers/apz/util/ActiveElementManager.cpp b/gfx/layers/apz/util/ActiveElementManager.cpp index f2d981e9f0..c92fec783a 100644 --- a/gfx/layers/apz/util/ActiveElementManager.cpp +++ b/gfx/layers/apz/util/ActiveElementManager.cpp @@ -10,6 +10,8 @@ #include "mozilla/StaticPrefs_ui.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Document.h" +#include "mozilla/layers/APZEventState.h" +#include "mozilla/layers/APZUtils.h" #include "nsITimer.h" static mozilla::LazyLogModule sApzAemLog("apz.activeelement"); @@ -21,7 +23,7 @@ namespace layers { class DelayedClearElementActivation final : public nsITimerCallback, public nsINamed { private: - explicit DelayedClearElementActivation(nsCOMPtr<dom::Element>& aTarget, + explicit DelayedClearElementActivation(RefPtr<dom::Element>& aTarget, const nsCOMPtr<nsITimer>& aTimer) : mTarget(aTarget) // Hold the reference count until we are called back. @@ -33,7 +35,7 @@ class DelayedClearElementActivation final : public nsITimerCallback, NS_DECL_ISUPPORTS static RefPtr<DelayedClearElementActivation> Create( - nsCOMPtr<dom::Element>& aTarget); + RefPtr<dom::Element>& aTarget); NS_IMETHOD Notify(nsITimer*) override; @@ -56,11 +58,12 @@ class DelayedClearElementActivation final : public nsITimerCallback, mTimer = nullptr; } } + dom::Element* GetTarget() const { return mTarget; } private: ~DelayedClearElementActivation() = default; - nsCOMPtr<dom::Element> mTarget; + RefPtr<dom::Element> mTarget; nsCOMPtr<nsITimer> mTimer; bool mProcessedSingleTap; }; @@ -77,7 +80,7 @@ static nsPresContext* GetPresContextFor(nsIContent* aContent) { } RefPtr<DelayedClearElementActivation> DelayedClearElementActivation::Create( - nsCOMPtr<dom::Element>& aTarget) { + RefPtr<dom::Element>& aTarget) { nsCOMPtr<nsITimer> timer = NS_NewTimer(); if (!timer) { return nullptr; @@ -137,7 +140,11 @@ void DelayedClearElementActivation::ClearGlobalActiveContent() { NS_IMPL_ISUPPORTS(DelayedClearElementActivation, nsITimerCallback, nsINamed) ActiveElementManager::ActiveElementManager() - : mCanBePan(false), mCanBePanSet(false), mSetActiveTask(nullptr) {} + : mCanBePanOrZoom(false), + mCanBePanOrZoomSet(false), + mSingleTapBeforeActivation(false), + mSingleTapState(apz::SingleTapState::NotClick), + mSetActiveTask(nullptr) {} ActiveElementManager::~ActiveElementManager() = default; @@ -156,9 +163,9 @@ void ActiveElementManager::SetTargetElement(dom::EventTarget* aTarget) { TriggerElementActivation(); } -void ActiveElementManager::HandleTouchStart(bool aCanBePan) { - AEM_LOG("Touch start, aCanBePan: %d\n", aCanBePan); - if (mCanBePanSet) { +void ActiveElementManager::HandleTouchStart(bool aCanBePanOrZoom) { + AEM_LOG("Touch start, aCanBePanOrZoom: %d\n", aCanBePanOrZoom); + if (mCanBePanOrZoomSet) { // Multiple fingers on screen (since HandleTouchEnd clears mCanBePanSet). AEM_LOG("Multiple fingers on-screen, clearing touch block state\n"); CancelTask(); @@ -167,16 +174,29 @@ void ActiveElementManager::HandleTouchStart(bool aCanBePan) { return; } - mCanBePan = aCanBePan; - mCanBePanSet = true; + mCanBePanOrZoom = aCanBePanOrZoom; + mCanBePanOrZoomSet = true; TriggerElementActivation(); } void ActiveElementManager::TriggerElementActivation() { + // Reset mSingleTapState here either when HandleTouchStart() or + // SetTargetElement() gets called. + // NOTE: It's possible that ProcessSingleTap() gets called in between + // HandleTouchStart() and SetTargetElement() calls. I.e., + // mSingleTapBeforeActivation is true, in such cases it doesn't matter that + // mSingleTapState was reset once and referred it in ProcessSingleTap() and + // then reset here again because in ProcessSingleTap() `NotYetDetermined` is + // the only one state we need to care, and it should NOT happen in the + // scenario. In other words the case where we need to care `NotYetDetermined` + // is when ProcessSingleTap() gets called later than any other events and + // notifications. + mSingleTapState = apz::SingleTapState::NotClick; + // Both HandleTouchStart() and SetTargetElement() call this. They can be - // called in either order. One will set mCanBePanSet, and the other, mTarget. - // We want to actually trigger the activation once both are set. - if (!(mTarget && mCanBePanSet)) { + // called in either order. One will set mCanBePanOrZoomSet, and the other, + // mTarget. We want to actually trigger the activation once both are set. + if (!(mTarget && mCanBePanOrZoomSet)) { return; } @@ -190,10 +210,13 @@ void ActiveElementManager::TriggerElementActivation() { // If the touch cannot be a pan, make mTarget :active right away. // Otherwise, wait a bit to see if the user will pan or not. - if (!mCanBePan) { + if (!mCanBePanOrZoom) { SetActive(mTarget); if (mDelayedClearElementActivation) { + if (mSingleTapBeforeActivation) { + mDelayedClearElementActivation->MarkSingleTapProcessed(); + } mDelayedClearElementActivation->StartTimer(); } } else { @@ -210,6 +233,10 @@ void ActiveElementManager::TriggerElementActivation() { task.forget(), StaticPrefs::ui_touch_activation_delay_ms()); AEM_LOG("Scheduling mSetActiveTask %p\n", mSetActiveTask.get()); } + AEM_LOG( + "Got both touch-end event and end touch notiication, clearing pan " + "state\n"); + mCanBePanOrZoomSet = false; } void ActiveElementManager::ClearActivation() { @@ -218,43 +245,70 @@ void ActiveElementManager::ClearActivation() { ResetActive(); } -void ActiveElementManager::HandleTouchEndEvent(bool aWasClick) { - AEM_LOG("Touch end event, aWasClick: %d\n", aWasClick); +bool ActiveElementManager::HandleTouchEndEvent(apz::SingleTapState aState) { + AEM_LOG("Touch end event, state: %hhu\n", static_cast<uint8_t>(aState)); + + mTouchEndState += TouchEndState::GotTouchEndEvent; + return MaybeChangeActiveState(aState); +} + +bool ActiveElementManager::HandleTouchEnd(apz::SingleTapState aState) { + AEM_LOG("Touch end\n"); + + mTouchEndState += TouchEndState::GotTouchEndNotification; + return MaybeChangeActiveState(aState); +} + +bool ActiveElementManager::MaybeChangeActiveState(apz::SingleTapState aState) { + if (mTouchEndState != + TouchEndStates(TouchEndState::GotTouchEndEvent, + TouchEndState::GotTouchEndNotification)) { + return false; + } - // If the touch was a click, make mTarget :active right away. - // nsEventStateManager will reset the active element when processing - // the mouse-down event generated by the click. CancelTask(); - if (aWasClick) { + + mSingleTapState = aState; + + if (aState == apz::SingleTapState::WasClick) { // Scrollbar thumbs use a different mechanism for their active // highlight (the "active" attribute), so don't set the active state // on them because nothing will clear it. - if (!(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) { + if (mCanBePanOrZoom && + !(mTarget && mTarget->IsXULElement(nsGkAtoms::thumb))) { SetActive(mTarget); } } else { - // We might reach here if mCanBePan was false on touch-start and + // We might reach here if mCanBePanOrZoom was false on touch-start and // so we set the element active right away. Now it turns out the // action was not a click so we need to reset the active element. ResetActive(); } ResetTouchBlockState(); -} - -void ActiveElementManager::HandleTouchEnd() { - AEM_LOG("Touch end, clearing pan state\n"); - mCanBePanSet = false; + return true; } void ActiveElementManager::ProcessSingleTap() { if (!mDelayedClearElementActivation) { + // We have not received touch-start notification yet. We will have to run + // MarkSingleTapProcessed() when we receive the touch-start notification. + mSingleTapBeforeActivation = true; return; } + if (mSingleTapState == apz::SingleTapState::NotYetDetermined) { + // If we got `NotYetDetermined`, which means at the moment we don't know for + // sure whether double-tapping will be incoming or not, but now we are sure + // that no double-tapping will happen, thus it's time to activate the target + // element. + if (auto* target = mDelayedClearElementActivation->GetTarget()) { + SetActive(target); + } + } mDelayedClearElementActivation->MarkSingleTapProcessed(); - if (mCanBePan) { + if (mCanBePanOrZoom) { // In the case that we have not started the delayed reset of the element // activation state, start the timer now. mDelayedClearElementActivation->StartTimer(); @@ -297,7 +351,14 @@ void ActiveElementManager::ResetActive() { void ActiveElementManager::ResetTouchBlockState() { mTarget = nullptr; - mCanBePanSet = false; + mCanBePanOrZoomSet = false; + mTouchEndState.clear(); + mSingleTapBeforeActivation = false; + // NOTE: Do not reset mSingleTapState here since it will be necessary in + // ProcessSingleTap() to tell whether we need to activate the target element + // because on environments where double-tap is enabled ProcessSingleTap() + // gets called after both of touch-end event and end touch notiication + // arrived. } void ActiveElementManager::SetActiveTask( diff --git a/gfx/layers/apz/util/ActiveElementManager.h b/gfx/layers/apz/util/ActiveElementManager.h index f8a6f07261..1f2e1e4aad 100644 --- a/gfx/layers/apz/util/ActiveElementManager.h +++ b/gfx/layers/apz/util/ActiveElementManager.h @@ -9,6 +9,7 @@ #include "nsCOMPtr.h" #include "nsISupportsImpl.h" +#include "mozilla/EnumSet.h" namespace mozilla { @@ -23,6 +24,10 @@ namespace layers { class DelayedClearElementActivation; +namespace apz { +enum class SingleTapState : uint8_t; +} // namespace apz + /** * Manages setting and clearing the ':active' CSS pseudostate in the presence * of touch input. @@ -46,9 +51,9 @@ class ActiveElementManager final { /** * Handle a touch-start state notification from APZ. This notification * may be delayed until after touch listeners have responded to the APZ. - * @param aCanBePan whether the touch can be a pan + * @param aCanBePanOrZoom whether the touch can be a pan or double-tap-to-zoom */ - void HandleTouchStart(bool aCanBePan); + void HandleTouchStart(bool aCanBePanOrZoom); /** * Clear the active element. */ @@ -57,12 +62,12 @@ class ActiveElementManager final { * Handle a touch-end or touch-cancel event. * @param aWasClick whether the touch was a click */ - void HandleTouchEndEvent(bool aWasClick); + bool HandleTouchEndEvent(apz::SingleTapState aState); /** * Handle a touch-end state notification from APZ. This notification may be * delayed until after touch listeners have responded to the APZ. */ - void HandleTouchEnd(); + bool HandleTouchEnd(apz::SingleTapState aState); /** * Possibly clear active element sate in response to a single tap. */ @@ -76,17 +81,39 @@ class ActiveElementManager final { /** * The target of the first touch point in the current touch block. */ - nsCOMPtr<dom::Element> mTarget; + RefPtr<dom::Element> mTarget; /** - * Whether the current touch block can be a pan. Set in HandleTouchStart(). + * Whether the current touch block can be a pan or double-tap-to-zoom. Set in + * HandleTouchStart(). */ - bool mCanBePan; + bool mCanBePanOrZoom; /** - * Whether mCanBePan has been set for the current touch block. + * Whether mCanBePanOrZoom has been set for the current touch block. * We need to keep track of this to allow HandleTouchStart() and * SetTargetElement() to be called in either order. */ - bool mCanBePanSet; + bool mCanBePanOrZoomSet; + + bool mSingleTapBeforeActivation; + + enum class TouchEndState : uint8_t { + GotTouchEndNotification, + GotTouchEndEvent, + }; + using TouchEndStates = EnumSet<TouchEndState>; + + /** + * A flag tracks whether `APZStateChange::eEndTouch` notification has arrived + * and whether `eTouchEnd` event has arrived. + */ + TouchEndStates mTouchEndState; + + /** + * A tri-state variable to represent the single tap state when both of + * `APZStateChange::eEndTouch` notification and `eTouchEnd` event arrived. + */ + apz::SingleTapState mSingleTapState; + /** * A task for calling SetActive() after a timeout. */ @@ -103,6 +130,8 @@ class ActiveElementManager final { void ResetTouchBlockState(); void SetActiveTask(const nsCOMPtr<dom::Element>& aTarget); void CancelTask(); + // Returns true if the function changed the active element state. + bool MaybeChangeActiveState(apz::SingleTapState aState); }; } // namespace layers |