Bug 1678505 - Add overscroll-behavior and scrollable directions into APZHandledResult. r=botond

Differential Revision: https://phabricator.services.mozilla.com/D103419
This commit is contained in:
Hiroyuki Ikezoe
2021-03-02 08:06:28 +00:00
parent bebf4afb9d
commit 9f727bae2a
12 changed files with 638 additions and 150 deletions

View File

@@ -387,12 +387,12 @@ class CompositableHandle final {
};
// clang-format off
MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollDirection, uint32_t, (
MOZ_DEFINE_ENUM_CLASS_WITH_BASE(ScrollDirection, uint8_t, (
eVertical,
eHorizontal
));
typedef EnumSet<ScrollDirection> ScrollDirections;
using ScrollDirections = EnumSet<ScrollDirection, uint8_t>;
constexpr ScrollDirections EitherScrollDirection(ScrollDirection::eVertical,ScrollDirection::eHorizontal);
constexpr ScrollDirections HorizontalScrollDirection(ScrollDirection::eHorizontal);

View File

@@ -9,6 +9,7 @@
#include "mozilla/EventForwards.h" // for WidgetInputEvent, nsEventStatus
#include "mozilla/layers/APZPublicUtils.h" // for APZWheelAction
#include "mozilla/layers/LayersTypes.h" // for ScrollDirections
#include "mozilla/layers/ScrollableLayerGuid.h" // for ScrollableLayerGuid
#include "Units.h" // for LayoutDeviceIntPoint
@@ -24,7 +25,7 @@ class InputBlockState;
struct ScrollableLayerGuid;
struct TargetConfirmationFlags;
enum class APZHandledResult : uint8_t {
enum class APZHandledPlace : uint8_t {
Unhandled = 0, // we know for sure that the event will not be handled
// by either the root APZC or others
HandledByRoot = 1, // we know for sure that the event will be handled
@@ -36,6 +37,33 @@ enum class APZHandledResult : uint8_t {
Last = Invalid
};
struct APZHandledResult {
APZHandledPlace mPlace = APZHandledPlace::Invalid;
SideBits mScrollableDirections = SideBits::eNone;
ScrollDirections mOverscrollDirections = ScrollDirections();
APZHandledResult() = default;
APZHandledResult(APZHandledPlace aPlace,
const AsyncPanZoomController* aTarget);
APZHandledResult(APZHandledPlace aPlace, SideBits aScrollableDirections,
ScrollDirections aOverscrollDirections)
: mPlace(aPlace),
mScrollableDirections(aScrollableDirections),
mOverscrollDirections(aOverscrollDirections) {}
bool IsHandledByContent() const {
return mPlace == APZHandledPlace::HandledByContent;
}
bool IsHandledByRoot() const {
return mPlace == APZHandledPlace::HandledByRoot;
}
bool operator==(const APZHandledResult& aOther) const {
return mPlace == aOther.mPlace &&
mScrollableDirections == aOther.mScrollableDirections &&
mOverscrollDirections == aOther.mOverscrollDirections;
}
};
/**
* Represents the outcome of APZ receiving and processing an input event.
* This is returned from APZInputBridge::ReceiveInputEvent() and related APIs.

View File

@@ -39,7 +39,7 @@ enum ZoomToRectBehavior : uint32_t {
};
class AsyncDragMetrics;
enum class APZHandledResult : uint8_t;
struct APZHandledResult;
class IAPZCTreeManager {
NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(IAPZCTreeManager)
@@ -149,7 +149,7 @@ class IAPZCTreeManager {
* the APZCTreeManager.
*/
using InputBlockCallback = std::function<void(
uint64_t aInputBlockId, APZHandledResult aHandledResult)>;
uint64_t aInputBlockId, const APZHandledResult& aHandledResult)>;
virtual void AddInputBlockCallback(uint64_t aInputBlockId,
InputBlockCallback&& aCallback) = 0;

View File

@@ -38,13 +38,15 @@ APZEventResult::APZEventResult(
// If the initial target is not the root, this will definitely not be
// handled by the root. (The confirmed target is either the initial
// target, or a descendant.)
return Some(APZHandledResult::HandledByContent);
return Some(
APZHandledResult{APZHandledPlace::HandledByContent, aInitialTarget});
}
if (!aFlags.mDispatchToContent) {
// If the initial target is the root and we don't need to dispatch to
// content, the event will definitely be handled by the root.
return Some(APZHandledResult::HandledByRoot);
return Some(
APZHandledResult{APZHandledPlace::HandledByRoot, aInitialTarget});
}
// Otherwise, we're not sure.
@@ -62,8 +64,9 @@ void APZEventResult::SetStatusAsConsumeDoDefault(
const RefPtr<AsyncPanZoomController>& aTarget) {
mStatus = nsEventStatus_eConsumeDoDefault;
mHandledResult =
Some(aTarget->IsRootContent() ? APZHandledResult::HandledByRoot
: APZHandledResult::HandledByContent);
Some(aTarget && aTarget->IsRootContent()
? APZHandledResult{APZHandledPlace::HandledByRoot, aTarget}
: APZHandledResult{APZHandledPlace::HandledByContent, aTarget});
}
void APZEventResult::SetStatusAsConsumeDoDefaultWithTargetConfirmationFlags(
@@ -84,9 +87,10 @@ void APZEventResult::SetStatusAsConsumeDoDefaultWithTargetConfirmationFlags(
// mDispatchToContent, we need to change it to Nothing() so that
// GeckoView can properly wait for results from the content on the
// main-thread.
mHandledResult = aFlags.mDispatchToContent
mHandledResult =
aFlags.mDispatchToContent
? Nothing()
: Some(APZHandledResult::HandledByRoot);
: Some(APZHandledResult{APZHandledPlace::HandledByRoot, &aTarget});
}
}
@@ -252,21 +256,94 @@ APZEventResult APZInputBridge::ReceiveInputEvent(WidgetInputEvent& aEvent) {
return result;
}
APZHandledResult::APZHandledResult(APZHandledPlace aPlace,
const AsyncPanZoomController* aTarget)
: mPlace(aPlace) {
MOZ_ASSERT(aTarget);
switch (aPlace) {
case APZHandledPlace::Unhandled:
break;
case APZHandledPlace::HandledByContent:
if (aTarget) {
mScrollableDirections = aTarget->ScrollableDirections();
mOverscrollDirections = aTarget->GetAllowedHandoffDirections();
}
break;
case APZHandledPlace::HandledByRoot: {
// The only way we can have mPlace == HandledByRoot but target is not the
// root, is if scroll is handed off immediately from target to the root
// because target and its ancestors (other than the root) do not have a
// scroll range. Therefore, it's the scroll directions of the root which
// are relevant.
const AsyncPanZoomController* target = aTarget;
while (target && !target->IsRootContent()) {
target = target->GetParent();
}
MOZ_ASSERT(target && target->IsRootContent());
if (target) {
mScrollableDirections = target->ScrollableDirections();
mOverscrollDirections = target->GetAllowedHandoffDirections();
}
break;
}
default:
MOZ_ASSERT_UNREACHABLE("Invalid APZHandledPlace");
break;
}
}
std::ostream& operator<<(std::ostream& aOut, const SideBits& aSideBits) {
if ((aSideBits & SideBits::eAll) == SideBits::eAll) {
aOut << "all";
} else {
AutoTArray<nsCString, 4> strings;
if (aSideBits & SideBits::eTop) {
strings.AppendElement("top"_ns);
}
if (aSideBits & SideBits::eRight) {
strings.AppendElement("right"_ns);
}
if (aSideBits & SideBits::eBottom) {
strings.AppendElement("bottom"_ns);
}
if (aSideBits & SideBits::eLeft) {
strings.AppendElement("left"_ns);
}
aOut << strings;
}
return aOut;
}
std::ostream& operator<<(std::ostream& aOut,
const APZHandledResult& aHandledResult) {
switch (aHandledResult) {
case APZHandledResult::Unhandled:
const ScrollDirections& aScrollDirections) {
if (aScrollDirections.contains(EitherScrollDirection)) {
aOut << "either";
} else if (aScrollDirections.contains(HorizontalScrollDirection)) {
aOut << "horizontal";
} else if (aScrollDirections.contains(VerticalScrollDirection)) {
aOut << "vertical";
} else {
aOut << "none";
}
return aOut;
}
std::ostream& operator<<(std::ostream& aOut,
const APZHandledPlace& aHandledPlace) {
switch (aHandledPlace) {
case APZHandledPlace::Unhandled:
aOut << "unhandled";
break;
case APZHandledResult::HandledByRoot: {
case APZHandledPlace::HandledByRoot: {
aOut << "handled-by-root";
break;
}
case APZHandledResult::HandledByContent: {
case APZHandledPlace::HandledByContent: {
aOut << "handled-by-content";
break;
}
case APZHandledResult::Invalid: {
case APZHandledPlace::Invalid: {
aOut << "INVALID";
break;
}
@@ -274,5 +351,13 @@ std::ostream& operator<<(std::ostream& aOut,
return aOut;
}
std::ostream& operator<<(std::ostream& aOut,
const APZHandledResult& aHandledResult) {
aOut << "handled: " << aHandledResult.mPlace << ", ";
aOut << "scrollable: " << aHandledResult.mScrollableDirections << ", ";
aOut << "overscroll: " << aHandledResult.mOverscrollDirections << std::endl;
return aOut;
}
} // namespace layers
} // namespace mozilla

View File

@@ -874,21 +874,21 @@ static APZHandledResult GetHandledResultFor(
const AsyncPanZoomController* aApzc,
const InputBlockState& aCurrentInputBlock) {
if (aCurrentInputBlock.ShouldDropEvents()) {
return APZHandledResult::HandledByContent;
return APZHandledResult{APZHandledPlace::HandledByContent, aApzc};
}
if (aApzc && aApzc->IsRootContent()) {
return aApzc->CanVerticalScrollWithDynamicToolbar()
? APZHandledResult::HandledByRoot
: APZHandledResult::Unhandled;
? APZHandledResult{APZHandledPlace::HandledByRoot, aApzc}
: APZHandledResult{APZHandledPlace::Unhandled, aApzc};
}
// Return `HandledByRoot` if scroll positions in all relevant APZC are at the
// bottom edge and if there are contents covered by the dynamic toolbar.
return aApzc && aCurrentInputBlock.GetOverscrollHandoffChain()
->ScrollingDownWillMoveDynamicToolbar(aApzc)
? APZHandledResult::HandledByRoot
: APZHandledResult::HandledByContent;
? APZHandledResult{APZHandledPlace::HandledByRoot, aApzc}
: APZHandledResult{APZHandledPlace::HandledByContent, aApzc};
}
void InputQueue::ProcessQueue() {

View File

@@ -37,8 +37,8 @@ class PinchGestureBlockState;
class KeyboardBlockState;
class AsyncDragMetrics;
class QueuedInput;
enum class APZHandledResult : uint8_t;
struct APZEventResult;
struct APZHandledResult;
/**
* This class stores incoming input events, associated with "input blocks",

View File

@@ -408,8 +408,8 @@ class APZCTesterBase : public ::testing::Test {
};
template <class InputReceiver>
void Tap(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeDuration aTapLength,
APZEventResult Tap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint, TimeDuration aTapLength,
nsEventStatus (*aOutEventStatuses)[2] = nullptr,
uint64_t* aOutInputBlockId = nullptr);
@@ -503,30 +503,32 @@ MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PanOptions)
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PinchOptions)
template <class InputReceiver>
void APZCTesterBase::Tap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint, TimeDuration aTapLength,
APZEventResult APZCTesterBase::Tap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
TimeDuration aTapLength,
nsEventStatus (*aOutEventStatuses)[2],
uint64_t* aOutInputBlockId) {
APZEventResult result = TouchDown(aTarget, aPoint, mcc->Time());
APZEventResult touchDownResult = TouchDown(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = result.GetStatus();
(*aOutEventStatuses)[0] = touchDownResult.GetStatus();
}
if (aOutInputBlockId) {
*aOutInputBlockId = result.mInputBlockId;
*aOutInputBlockId = touchDownResult.mInputBlockId;
}
mcc->AdvanceBy(aTapLength);
// If touch-action is enabled then simulate the allowed touch behaviour
// notification that the main thread is supposed to deliver.
if (StaticPrefs::layout_css_touch_action_enabled() &&
result.GetStatus() != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId);
touchDownResult.GetStatus() != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, touchDownResult.mInputBlockId);
}
result = TouchUp(aTarget, aPoint, mcc->Time());
APZEventResult touchUpResult = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = result.GetStatus();
(*aOutEventStatuses)[1] = touchUpResult.GetStatus();
}
return touchDownResult;
}
template <class InputReceiver>

View File

@@ -311,101 +311,3 @@ TEST_F(APZEventRegionsTester, Bug1117712) {
targets.AppendElement(apzc2->GetGuid());
manager->SetTargetAPZC(inputBlockId, targets);
}
// Test that APZEventResult::GetHandledResult() is correctly
// populated.
TEST_F(APZEventRegionsTesterLayersOnly, HandledByRootApzcFlag) {
// Create simple layer tree containing a dispatch-to-content region
// that covers part but not all of its area.
const char* layerTreeSyntax = "c";
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm,
layers);
SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
CSSRect(0, 0, 100, 200));
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetIsRootContent(true);
});
// away from the scrolling container layer.
EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
// bottom half is dispatch-to-content
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 50, 100, 50));
root->SetEventRegions(regions);
registration =
MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
UpdateHitTestingTree();
// Tap the top half and check that we report that the event was
// handled by the root APZC.
APZEventResult result =
TouchDown(manager, ScreenIntPoint(50, 25), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 25), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Some(APZHandledResult::HandledByRoot));
// Tap the bottom half and check that we report that we're not
// sure whether the event was handled by the root APZC.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Nothing());
// Register an input block callback that will tell us the
// delayed answer.
APZHandledResult delayedAnswer = APZHandledResult::Invalid;
manager->AddInputBlockCallback(result.mInputBlockId,
[&](uint64_t id, APZHandledResult answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
// Send APZ the relevant notifications to allow it to process the
// input block.
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*preventDefault=*/false);
// Check that we received the delayed answer and it is what we expect.
EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByRoot);
// Now repeat the tap on the bottom half, but simulate a prevent-default.
// This time, we expect a delayed answer of `HandledByContent`.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Nothing());
manager->AddInputBlockCallback(result.mInputBlockId,
[&](uint64_t id, APZHandledResult answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*preventDefault=*/true);
EXPECT_EQ(delayedAnswer, APZHandledResult::HandledByContent);
// Shrink the scrollable area, now it's no longer scrollable.
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetScrollableRect(CSSRect(0, 0, 100, 100));
});
UpdateHitTestingTree();
// Now repeat the tap on the bottom half with an event handler.
// This time, we expect a delayed answer of `Unhandled`.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Nothing());
manager->AddInputBlockCallback(result.mInputBlockId,
[&](uint64_t id, APZHandledResult answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*preventDefault=*/false);
EXPECT_EQ(delayedAnswer, APZHandledResult::Unhandled);
}

View File

@@ -0,0 +1,435 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "APZCTreeManagerTester.h"
#include "APZTestCommon.h"
#include "InputUtils.h"
#include "mozilla/layers/LayersTypes.h"
#include <tuple>
class APZEventResultTester : public APZCTreeManagerTester {
protected:
UniquePtr<ScopedLayerTreeRegistration> registration;
void UpdateOverscrollBehavior(OverscrollBehavior aX, OverscrollBehavior aY) {
ModifyFrameMetrics(root, [aX, aY](ScrollMetadata& sm, FrameMetrics& _) {
OverscrollBehaviorInfo overscroll;
overscroll.mBehaviorX = aX;
overscroll.mBehaviorY = aY;
sm.SetOverscrollBehavior(overscroll);
});
UpdateHitTestingTree();
}
void SetScrollOffsetOnMainThread(const CSSPoint& aPoint) {
RefPtr<TestAsyncPanZoomController> apzc = ApzcOf(root);
ScrollMetadata metadata = apzc->GetScrollMetadata();
metadata.GetMetrics().SetLayoutScrollOffset(aPoint);
nsTArray<ScrollPositionUpdate> scrollUpdates;
scrollUpdates.AppendElement(ScrollPositionUpdate::NewScroll(
ScrollOrigin::Other, CSSPoint::ToAppUnits(aPoint)));
metadata.SetScrollUpdates(scrollUpdates);
metadata.GetMetrics().SetScrollGeneration(
scrollUpdates.LastElement().GetGeneration());
apzc->NotifyLayersUpdated(metadata, /*aIsFirstPaint=*/false,
/*aThisLayerTreeUpdated=*/true);
}
void CreateScrollableRootLayer() {
const char* layerTreeSyntax = "c";
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm,
layers);
SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
CSSRect(0, 0, 200, 200));
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetIsRootContent(true);
});
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0},
root, mcc);
UpdateHitTestingTree();
}
enum class PreventDefaultFlag { No, Yes };
std::tuple<APZEventResult, APZHandledResult> TapDispatchToContent(
const ScreenIntPoint& aPoint, PreventDefaultFlag aPreventDefaultFlag) {
APZEventResult result =
Tap(manager, aPoint, TimeDuration::FromMilliseconds(100));
APZHandledResult delayedAnswer{APZHandledPlace::Invalid, SideBits::eNone,
ScrollDirections()};
manager->AddInputBlockCallback(
result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(
result.mInputBlockId, aPreventDefaultFlag == PreventDefaultFlag::Yes);
return {result, delayedAnswer};
}
void OverscrollDirectionsWithEventHandlerTest(
PreventDefaultFlag aPreventDefaultFlag) {
EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
root->SetEventRegions(regions);
UpdateHitTestingTree();
APZHandledPlace expectedPlace =
aPreventDefaultFlag == PreventDefaultFlag::No
? APZHandledPlace::HandledByRoot
: APZHandledPlace::HandledByContent;
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight,
EitherScrollDirection}));
}
// overscroll-behavior: contain, contain.
UpdateOverscrollBehavior(OverscrollBehavior::Contain,
OverscrollBehavior::Contain);
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight,
ScrollDirections()}));
}
// overscroll-behavior: none, none.
UpdateOverscrollBehavior(OverscrollBehavior::None,
OverscrollBehavior::None);
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight,
ScrollDirections()}));
}
// overscroll-behavior: auto, none.
UpdateOverscrollBehavior(OverscrollBehavior::Auto,
OverscrollBehavior::None);
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight,
HorizontalScrollDirection}));
}
// overscroll-behavior: none, auto.
UpdateOverscrollBehavior(OverscrollBehavior::None,
OverscrollBehavior::Auto);
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight,
VerticalScrollDirection}));
}
}
void ScrollableDirectionsWithEventHandlerTest(
PreventDefaultFlag aPreventDefaultFlag) {
EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
root->SetEventRegions(regions);
UpdateHitTestingTree();
APZHandledPlace expectedPlace =
aPreventDefaultFlag == PreventDefaultFlag::No
? APZHandledPlace::HandledByRoot
: APZHandledPlace::HandledByContent;
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eBottom | SideBits::eRight,
EitherScrollDirection}));
}
// scroll down a bit.
SetScrollOffsetOnMainThread(CSSPoint(0, 10));
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(delayedHandledResult,
(APZHandledResult{
expectedPlace,
SideBits::eTop | SideBits::eBottom | SideBits::eRight,
EitherScrollDirection}));
}
// scroll to the bottom edge
SetScrollOffsetOnMainThread(CSSPoint(0, 100));
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eRight | SideBits::eTop,
EitherScrollDirection}));
}
// scroll to right a bit.
SetScrollOffsetOnMainThread(CSSPoint(10, 100));
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace,
SideBits::eLeft | SideBits::eRight | SideBits::eTop,
EitherScrollDirection}));
}
// scroll to the right edge.
SetScrollOffsetOnMainThread(CSSPoint(100, 100));
{
auto [result, delayedHandledResult] =
TapDispatchToContent(ScreenIntPoint(50, 50), aPreventDefaultFlag);
EXPECT_EQ(result.GetHandledResult(), Nothing());
EXPECT_EQ(
delayedHandledResult,
(APZHandledResult{expectedPlace, SideBits::eTop | SideBits::eLeft,
EitherScrollDirection}));
}
}
};
TEST_F(APZEventResultTester, OverscrollDirections) {
CreateScrollableRootLayer();
TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);
// The default value of overscroll-behavior is auto.
APZEventResult result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections,
EitherScrollDirection);
// overscroll-behavior: contain, contain.
UpdateOverscrollBehavior(OverscrollBehavior::Contain,
OverscrollBehavior::Contain);
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections,
ScrollDirections());
// overscroll-behavior: none, none.
UpdateOverscrollBehavior(OverscrollBehavior::None, OverscrollBehavior::None);
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections,
ScrollDirections());
// overscroll-behavior: auto, none.
UpdateOverscrollBehavior(OverscrollBehavior::Auto, OverscrollBehavior::None);
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections,
HorizontalScrollDirection);
// overscroll-behavior: none, auto.
UpdateOverscrollBehavior(OverscrollBehavior::None, OverscrollBehavior::Auto);
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mOverscrollDirections,
VerticalScrollDirection);
}
TEST_F(APZEventResultTester, ScrollableDirections) {
CreateScrollableRootLayer();
TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);
APZEventResult result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
// scrollable to down/right.
EXPECT_EQ(result.GetHandledResult()->mScrollableDirections,
SideBits::eBottom | SideBits::eRight);
// scroll down a bit.
SetScrollOffsetOnMainThread(CSSPoint(0, 10));
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
// also scrollable toward top.
EXPECT_EQ(result.GetHandledResult()->mScrollableDirections,
SideBits::eTop | SideBits::eBottom | SideBits::eRight);
// scroll to the bottom edge
SetScrollOffsetOnMainThread(CSSPoint(0, 100));
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mScrollableDirections,
SideBits::eRight | SideBits::eTop);
// scroll to right a bit.
SetScrollOffsetOnMainThread(CSSPoint(10, 100));
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mScrollableDirections,
SideBits::eLeft | SideBits::eRight | SideBits::eTop);
// scroll to the right edge.
SetScrollOffsetOnMainThread(CSSPoint(100, 100));
result = Tap(manager, ScreenIntPoint(50, 50), tapDuration);
EXPECT_EQ(result.GetHandledResult()->mScrollableDirections,
SideBits::eLeft | SideBits::eTop);
}
class APZEventResultTesterLayersOnly : public APZEventResultTester {
public:
APZEventResultTesterLayersOnly() { mLayersOnly = true; }
};
TEST_F(APZEventResultTesterLayersOnly, OverscrollDirectionsWithEventHandler) {
CreateScrollableRootLayer();
OverscrollDirectionsWithEventHandlerTest(PreventDefaultFlag::No);
}
TEST_F(APZEventResultTesterLayersOnly,
OverscrollDirectionsWithPreventDefaultEventHandler) {
CreateScrollableRootLayer();
OverscrollDirectionsWithEventHandlerTest(PreventDefaultFlag::Yes);
}
TEST_F(APZEventResultTesterLayersOnly, ScrollableDirectionsWithEventHandler) {
CreateScrollableRootLayer();
ScrollableDirectionsWithEventHandlerTest(PreventDefaultFlag::No);
}
TEST_F(APZEventResultTesterLayersOnly,
ScrollableDirectionsWithPreventDefaultEventHandler) {
CreateScrollableRootLayer();
ScrollableDirectionsWithEventHandlerTest(PreventDefaultFlag::Yes);
}
// Test that APZEventResult::GetHandledResult() is correctly
// populated.
TEST_F(APZEventResultTesterLayersOnly, HandledByRootApzcFlag) {
// Create simple layer tree containing a dispatch-to-content region
// that covers part but not all of its area.
const char* layerTreeSyntax = "c";
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm,
layers);
SetScrollableFrameMetrics(root, ScrollableLayerGuid::START_SCROLL_ID,
CSSRect(0, 0, 100, 200));
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetIsRootContent(true);
});
// away from the scrolling container layer.
EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
// bottom half is dispatch-to-content
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 50, 100, 50));
root->SetEventRegions(regions);
registration =
MakeUnique<ScopedLayerTreeRegistration>(manager, LayersId{0}, root, mcc);
UpdateHitTestingTree();
// Tap the top half and check that we report that the event was
// handled by the root APZC.
APZEventResult result =
TouchDown(manager, ScreenIntPoint(50, 25), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 25), mcc->Time());
EXPECT_EQ(result.GetHandledResult(),
Some(APZHandledResult{APZHandledPlace::HandledByRoot,
SideBits::eBottom, EitherScrollDirection}));
// Tap the bottom half and check that we report that we're not
// sure whether the event was handled by the root APZC.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Nothing());
// Register an input block callback that will tell us the
// delayed answer.
APZHandledResult delayedAnswer{APZHandledPlace::Invalid, SideBits::eNone,
ScrollDirections()};
manager->AddInputBlockCallback(
result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
// Send APZ the relevant notifications to allow it to process the
// input block.
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*aPreventDefault=*/false);
// Check that we received the delayed answer and it is what we expect.
EXPECT_EQ(delayedAnswer,
(APZHandledResult{APZHandledPlace::HandledByRoot, SideBits::eBottom,
EitherScrollDirection}));
// Now repeat the tap on the bottom half, but simulate a prevent-default.
// This time, we expect a delayed answer of `HandledByContent`.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Nothing());
manager->AddInputBlockCallback(
result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*aPreventDefault=*/true);
EXPECT_EQ(delayedAnswer,
(APZHandledResult{APZHandledPlace::HandledByContent,
SideBits::eBottom, EitherScrollDirection}));
// Shrink the scrollable area, now it's no longer scrollable.
ModifyFrameMetrics(root, [](ScrollMetadata& sm, FrameMetrics& metrics) {
metrics.SetScrollableRect(CSSRect(0, 0, 100, 100));
});
UpdateHitTestingTree();
// Now repeat the tap on the bottom half with an event handler.
// This time, we expect a delayed answer of `Unhandled`.
result = TouchDown(manager, ScreenIntPoint(50, 75), mcc->Time());
TouchUp(manager, ScreenIntPoint(50, 75), mcc->Time());
EXPECT_EQ(result.GetHandledResult(), Nothing());
manager->AddInputBlockCallback(
result.mInputBlockId, [&](uint64_t id, const APZHandledResult& answer) {
EXPECT_EQ(id, result.mInputBlockId);
delayedAnswer = answer;
});
manager->SetAllowedTouchBehavior(result.mInputBlockId,
{AllowedTouchBehavior::VERTICAL_PAN});
manager->SetTargetAPZC(result.mInputBlockId, {result.mTargetGuid});
manager->ContentReceivedInputBlock(result.mInputBlockId,
/*aPreventDefault=*/false);
EXPECT_EQ(delayedAnswer,
(APZHandledResult{APZHandledPlace::Unhandled, SideBits::eNone,
ScrollDirections()}));
}

View File

@@ -7,6 +7,7 @@
UNIFIED_SOURCES += [
"TestBasic.cpp",
"TestEventRegions.cpp",
"TestEventResult.cpp",
"TestFlingAcceleration.cpp",
"TestGestureDetector.cpp",
"TestHitTesting.cpp",

View File

@@ -577,11 +577,48 @@ struct ParamTraits<nsEventStatus>
nsEventStatus_eSentinel> {};
template <>
struct ParamTraits<mozilla::layers::APZHandledResult>
struct ParamTraits<mozilla::layers::APZHandledPlace>
: public ContiguousEnumSerializer<
mozilla::layers::APZHandledResult,
mozilla::layers::APZHandledResult::Unhandled,
mozilla::layers::APZHandledResult::Last> {};
mozilla::layers::APZHandledPlace,
mozilla::layers::APZHandledPlace::Unhandled,
mozilla::layers::APZHandledPlace::Last> {};
template <>
struct ParamTraits<mozilla::layers::ScrollDirections> {
typedef mozilla::layers::ScrollDirections paramType;
static void Write(Message* aMsg, const paramType& aParam) {
WriteParam(aMsg, aParam.serialize());
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
uint8_t value;
if (!ReadParam(aMsg, aIter, &value)) {
return false;
}
aResult->deserialize(value);
return true;
}
};
template <>
struct ParamTraits<mozilla::layers::APZHandledResult> {
typedef mozilla::layers::APZHandledResult paramType;
static void Write(Message* aMsg, const paramType& aParam) {
WriteParam(aMsg, aParam.mPlace);
WriteParam(aMsg, aParam.mScrollableDirections);
WriteParam(aMsg, aParam.mOverscrollDirections);
}
static bool Read(const Message* aMsg, PickleIterator* aIter,
paramType* aResult) {
return (ReadParam(aMsg, aIter, &aResult->mPlace) &&
ReadParam(aMsg, aIter, &aResult->mScrollableDirections) &&
ReadParam(aMsg, aIter, &aResult->mOverscrollDirections));
}
};
template <>
struct ParamTraits<mozilla::layers::APZEventResult> {

View File

@@ -405,8 +405,7 @@ class NPZCSupport final
case nsEventStatus_eIgnore:
return INPUT_RESULT_UNHANDLED;
case nsEventStatus_eConsumeDoDefault:
return (result.GetHandledResult() ==
Some(APZHandledResult::HandledByRoot))
return result.GetHandledResult()->IsHandledByRoot()
? INPUT_RESULT_HANDLED
: INPUT_RESULT_HANDLED_CONTENT;
default:
@@ -459,14 +458,14 @@ class NPZCSupport final
}
static int32_t ConvertAPZHandledResult(APZHandledResult aHandledResult) {
switch (aHandledResult) {
case APZHandledResult::Unhandled:
switch (aHandledResult.mPlace) {
case APZHandledPlace::Unhandled:
return INPUT_RESULT_UNHANDLED;
case APZHandledResult::HandledByRoot:
case APZHandledPlace::HandledByRoot:
return INPUT_RESULT_HANDLED;
case APZHandledResult::HandledByContent:
case APZHandledPlace::HandledByContent:
return INPUT_RESULT_HANDLED_CONTENT;
case APZHandledResult::Invalid:
case APZHandledPlace::Invalid:
MOZ_ASSERT_UNREACHABLE("The handled result should NOT be Invalid");
return INPUT_RESULT_UNHANDLED;
}
@@ -546,8 +545,7 @@ class NPZCSupport final
case nsEventStatus_eIgnore:
return INPUT_RESULT_UNHANDLED;
case nsEventStatus_eConsumeDoDefault:
return (result.GetHandledResult() ==
Some(APZHandledResult::HandledByRoot))
return result.GetHandledResult()->IsHandledByRoot()
? INPUT_RESULT_HANDLED
: INPUT_RESULT_HANDLED_CONTENT;
default: