summaryrefslogtreecommitdiffstats
path: root/gfx/layers/apz
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/apz')
-rw-r--r--gfx/layers/apz/public/APZPublicUtils.h5
-rw-r--r--gfx/layers/apz/public/GeckoContentControllerTypes.h4
-rw-r--r--gfx/layers/apz/src/APZCTreeManager.cpp23
-rw-r--r--gfx/layers/apz/src/APZCTreeManager.h80
-rw-r--r--gfx/layers/apz/src/APZSampler.cpp13
-rw-r--r--gfx/layers/apz/src/APZUtils.h8
-rw-r--r--gfx/layers/apz/src/AsyncPanZoomController.cpp41
-rw-r--r--gfx/layers/apz/src/AsyncPanZoomController.h13
-rw-r--r--gfx/layers/apz/src/HitTestingTreeNode.h14
-rw-r--r--gfx/layers/apz/src/InputBlockState.cpp12
-rw-r--r--gfx/layers/apz/src/InputBlockState.h14
-rw-r--r--gfx/layers/apz/src/SampledAPZCState.cpp2
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1669625.html3
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1806400-2.html58
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1806400-3.html49
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1806400-4.html49
-rw-r--r--gfx/layers/apz/test/mochitest/helper_bug1806400.html54
-rw-r--r--gfx/layers/apz/test/mochitest/helper_hittest_iframe_perspective-4.html86
-rw-r--r--gfx/layers/apz/test/mochitest/helper_hittest_pointerevents_svg.html8
-rw-r--r--gfx/layers/apz/test/mochitest/helper_scrollframe_activation_on_load.html5
-rw-r--r--gfx/layers/apz/test/mochitest/helper_touch_synthesized_mouseevents.html9
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_hittest-3.html1
-rw-r--r--gfx/layers/apz/test/mochitest/test_group_touchevents-5.html18
-rw-r--r--gfx/layers/apz/test/mochitest/test_layerization.html5
-rw-r--r--gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html3
-rw-r--r--gfx/layers/apz/util/APZEventState.cpp23
-rw-r--r--gfx/layers/apz/util/APZEventState.h6
-rw-r--r--gfx/layers/apz/util/ActiveElementManager.cpp119
-rw-r--r--gfx/layers/apz/util/ActiveElementManager.h47
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