From def92d1b8e9d373e2f6f27c366d578d97d8960c6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:50 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- gfx/layers/apz/util/APZEventState.cpp | 23 ++++-- gfx/layers/apz/util/APZEventState.h | 6 +- gfx/layers/apz/util/ActiveElementManager.cpp | 119 ++++++++++++++++++++------- gfx/layers/apz/util/ActiveElementManager.h | 47 +++++++++-- 4 files changed, 147 insertions(+), 48 deletions(-) (limited to 'gfx/layers/apz/util') 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(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 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& aTarget, + explicit DelayedClearElementActivation(RefPtr& aTarget, const nsCOMPtr& 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 Create( - nsCOMPtr& aTarget); + RefPtr& 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 mTarget; + RefPtr mTarget; nsCOMPtr mTimer; bool mProcessedSingleTap; }; @@ -77,7 +80,7 @@ static nsPresContext* GetPresContextFor(nsIContent* aContent) { } RefPtr DelayedClearElementActivation::Create( - nsCOMPtr& aTarget) { + RefPtr& aTarget) { nsCOMPtr 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(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 mTarget; + RefPtr 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; + + /** + * 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& aTarget); void CancelTask(); + // Returns true if the function changed the active element state. + bool MaybeChangeActiveState(apz::SingleTapState aState); }; } // namespace layers -- cgit v1.2.3