Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_layers_APZTestCommon_h
#define mozilla_layers_APZTestCommon_h
/**
* Defines a set of mock classes and utility functions/classes for
* writing APZ gtests.
*/
#include "gtest/gtest.h"
#include "gmock/gmock.h"
#include "mozilla/Attributes.h"
#include "mozilla/layers/GeckoContentController.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/DoubleTapToZoom.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/MatrixMessage.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/UniquePtr.h"
#include "apz/src/APZCTreeManager.h"
#include "apz/src/AsyncPanZoomController.h"
#include "apz/src/HitTestingTreeNode.h"
#include "base/task.h"
#include "gfxPlatform.h"
#include "TestWRScrollData.h"
#include "UnitTransforms.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using ::testing::_;
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::InSequence;
using ::testing::MockFunction;
using ::testing::NiceMock;
typedef mozilla::layers::GeckoContentController::TapType TapType;
inline TimeStamp GetStartupTime() {
static TimeStamp sStartupTime = TimeStamp::Now();
return sStartupTime;
}
inline uint32_t MillisecondsSinceStartup(TimeStamp aTime) {
return (aTime - GetStartupTime()).ToMilliseconds();
}
// Some helper functions for constructing input event objects suitable to be
// passed either to an APZC (which expects an transformed point), or to an APZTM
// (which expects an untransformed point). We handle both cases by setting both
// the transformed and untransformed fields to the same value.
inline SingleTouchData CreateSingleTouchData(int32_t aIdentifier,
const ScreenIntPoint& aPoint) {
SingleTouchData touch(aIdentifier, aPoint, ScreenSize(0, 0), 0, 0);
touch.mLocalScreenPoint = ParentLayerPoint(aPoint.x, aPoint.y);
return touch;
}
// Convenience wrapper for CreateSingleTouchData() that takes loose coordinates.
inline SingleTouchData CreateSingleTouchData(int32_t aIdentifier,
ScreenIntCoord aX,
ScreenIntCoord aY) {
return CreateSingleTouchData(aIdentifier, ScreenIntPoint(aX, aY));
}
inline PinchGestureInput CreatePinchGestureInput(
PinchGestureInput::PinchGestureType aType, const ScreenPoint& aFocus,
float aCurrentSpan, float aPreviousSpan, TimeStamp timestamp) {
ParentLayerPoint localFocus(aFocus.x, aFocus.y);
PinchGestureInput result(aType, PinchGestureInput::UNKNOWN, timestamp,
ExternalPoint(0, 0), aFocus, aCurrentSpan,
aPreviousSpan, 0);
return result;
}
template <class SetArg, class Storage>
class ScopedGfxSetting {
public:
ScopedGfxSetting(const std::function<SetArg(void)>& aGetPrefFunc,
const std::function<void(SetArg)>& aSetPrefFunc, SetArg aVal)
: mSetPrefFunc(aSetPrefFunc) {
mOldVal = aGetPrefFunc();
aSetPrefFunc(aVal);
}
~ScopedGfxSetting() { mSetPrefFunc(mOldVal); }
private:
std::function<void(SetArg)> mSetPrefFunc;
Storage mOldVal;
};
static inline constexpr auto kDefaultTouchBehavior =
AllowedTouchBehavior::VERTICAL_PAN | AllowedTouchBehavior::HORIZONTAL_PAN |
AllowedTouchBehavior::PINCH_ZOOM | AllowedTouchBehavior::ANIMATING_ZOOM;
#define FRESH_PREF_VAR_PASTE(id, line) id##line
#define FRESH_PREF_VAR_EXPAND(id, line) FRESH_PREF_VAR_PASTE(id, line)
#define FRESH_PREF_VAR FRESH_PREF_VAR_EXPAND(pref, __LINE__)
#define SCOPED_GFX_PREF_BOOL(prefName, prefValue) \
ScopedGfxSetting<bool, bool> FRESH_PREF_VAR( \
[=]() { return Preferences::GetBool(prefName); }, \
[=](bool aPrefValue) { Preferences::SetBool(prefName, aPrefValue); }, \
prefValue)
#define SCOPED_GFX_PREF_INT(prefName, prefValue) \
ScopedGfxSetting<int32_t, int32_t> FRESH_PREF_VAR( \
[=]() { return Preferences::GetInt(prefName); }, \
[=](int32_t aPrefValue) { Preferences::SetInt(prefName, aPrefValue); }, \
prefValue)
#define SCOPED_GFX_PREF_FLOAT(prefName, prefValue) \
ScopedGfxSetting<float, float> FRESH_PREF_VAR( \
[=]() { return Preferences::GetFloat(prefName); }, \
[=](float aPrefValue) { Preferences::SetFloat(prefName, aPrefValue); }, \
prefValue)
class MockContentController : public GeckoContentController {
public:
MOCK_METHOD1(NotifyLayerTransforms, void(nsTArray<MatrixMessage>&&));
MOCK_METHOD1(RequestContentRepaint, void(const RepaintRequest&));
MOCK_METHOD6(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers,
const ScrollableLayerGuid&, uint64_t,
const Maybe<DoubleTapToZoomMetrics>&));
MOCK_METHOD5(NotifyPinchGesture,
void(PinchGestureInput::PinchGestureType,
const ScrollableLayerGuid&, const LayoutDevicePoint&,
LayoutDeviceCoord, Modifiers));
// Can't use the macros with already_AddRefed :(
void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) {
RefPtr<Runnable> task = aTask;
}
bool IsRepaintThread() { return NS_IsMainThread(); }
void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) {
NS_DispatchToMainThread(std::move(aTask));
}
MOCK_METHOD4(NotifyAPZStateChange,
void(const ScrollableLayerGuid& aGuid, APZStateChange aChange,
int aArg, Maybe<uint64_t> aInputBlockId));
MOCK_METHOD0(NotifyFlushComplete, void());
MOCK_METHOD3(NotifyAsyncScrollbarDragInitiated,
void(uint64_t, const ScrollableLayerGuid::ViewID&,
ScrollDirection aDirection));
MOCK_METHOD1(NotifyAsyncScrollbarDragRejected,
void(const ScrollableLayerGuid::ViewID&));
MOCK_METHOD1(NotifyAsyncAutoscrollRejected,
void(const ScrollableLayerGuid::ViewID&));
MOCK_METHOD1(CancelAutoscroll, void(const ScrollableLayerGuid&));
MOCK_METHOD2(NotifyScaleGestureComplete,
void(const ScrollableLayerGuid&, float aScale));
MOCK_METHOD4(UpdateOverscrollVelocity,
void(const ScrollableLayerGuid&, float, float, bool));
MOCK_METHOD4(UpdateOverscrollOffset,
void(const ScrollableLayerGuid&, float, float, bool));
};
class MockContentControllerDelayed : public MockContentController {
public:
MockContentControllerDelayed()
: mTime(SampleTime::FromTest(GetStartupTime())) {}
const TimeStamp& Time() { return mTime.Time(); }
const SampleTime& GetSampleTime() { return mTime; }
void AdvanceByMillis(int aMillis) {
AdvanceBy(TimeDuration::FromMilliseconds(aMillis));
}
void AdvanceBy(const TimeDuration& aIncrement) {
SampleTime target = mTime + aIncrement;
while (mTaskQueue.Length() > 0 && mTaskQueue[0].second <= target) {
RunNextDelayedTask();
}
mTime = target;
}
void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) {
RefPtr<Runnable> task = aTask;
SampleTime runAtTime = mTime + TimeDuration::FromMilliseconds(aDelayMs);
int insIndex = mTaskQueue.Length();
while (insIndex > 0) {
if (mTaskQueue[insIndex - 1].second <= runAtTime) {
break;
}
insIndex--;
}
mTaskQueue.InsertElementAt(insIndex, std::make_pair(task, runAtTime));
}
// Run all the tasks in the queue, returning the number of tasks
// run. Note that if a task queues another task while running, that
// new task will not be run. Therefore, there may be still be tasks
// in the queue after this function is called. Only when the return
// value is 0 is the queue guaranteed to be empty.
int RunThroughDelayedTasks() {
nsTArray<std::pair<RefPtr<Runnable>, SampleTime>> runQueue =
std::move(mTaskQueue);
int numTasks = runQueue.Length();
for (int i = 0; i < numTasks; i++) {
mTime = runQueue[i].second;
runQueue[i].first->Run();
// Deleting the task is important in order to release the reference to
// the callee object.
runQueue[i].first = nullptr;
}
return numTasks;
}
private:
void RunNextDelayedTask() {
std::pair<RefPtr<Runnable>, SampleTime> next = mTaskQueue[0];
mTaskQueue.RemoveElementAt(0);
mTime = next.second;
next.first->Run();
// Deleting the task is important in order to release the reference to
// the callee object.
next.first = nullptr;
}
// The following array is sorted by timestamp (tasks are inserted in order by
// timestamp).
nsTArray<std::pair<RefPtr<Runnable>, SampleTime>> mTaskQueue;
SampleTime mTime;
};
class TestAPZCTreeManager : public APZCTreeManager {
public:
explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc,
UniquePtr<IAPZHitTester> aHitTester = nullptr)
: APZCTreeManager(LayersId{0}, std::move(aHitTester)), mcc(aMcc) {
Init();
}
RefPtr<InputQueue> GetInputQueue() const { return mInputQueue; }
void ClearContentController() { mcc = nullptr; }
/**
* This function is not currently implemented.
* See bug 1468804 for more information.
**/
void CancelAnimation() { EXPECT_TRUE(false); }
bool AdvanceAnimations(const SampleTime& aSampleTime) {
MutexAutoLock lock(mMapLock);
return AdvanceAnimationsInternal(lock, aSampleTime);
}
APZEventResult ReceiveInputEvent(
InputData& aEvent,
InputBlockCallback&& aCallback = InputBlockCallback()) override {
APZEventResult result =
APZCTreeManager::ReceiveInputEvent(aEvent, std::move(aCallback));
if (aEvent.mInputType == PANGESTURE_INPUT &&
// In the APZCTreeManager::ReceiveInputEvent some type of pan gesture
// events are marked as `mHandledByAPZ = false` (e.g. with Ctrl key
// modifier which causes reflow zoom), in such cases the events will
// never be processed by InputQueue so we shouldn't try to invoke
// AllowsSwipe() here.
aEvent.AsPanGestureInput().mHandledByAPZ &&
aEvent.AsPanGestureInput().AllowsSwipe()) {
SetBrowserGestureResponse(result.mInputBlockId,
BrowserGestureResponse::NotConsumed);
}
return result;
}
protected:
already_AddRefed<AsyncPanZoomController> NewAPZCInstance(
LayersId aLayersId, GeckoContentController* aController) override;
SampleTime GetFrameTime() override { return mcc->GetSampleTime(); }
private:
RefPtr<MockContentControllerDelayed> mcc;
};
class TestAsyncPanZoomController : public AsyncPanZoomController {
public:
TestAsyncPanZoomController(LayersId aLayersId,
MockContentControllerDelayed* aMcc,
TestAPZCTreeManager* aTreeManager,
GestureBehavior aBehavior = DEFAULT_GESTURES)
: AsyncPanZoomController(aLayersId, aTreeManager,
aTreeManager->GetInputQueue(), aMcc, aBehavior),
mWaitForMainThread(false),
mcc(aMcc) {}
APZEventResult ReceiveInputEvent(
InputData& aEvent,
const Maybe<nsTArray<uint32_t>>& aTouchBehaviors = Nothing()) {
// This is a function whose signature matches exactly the ReceiveInputEvent
// on APZCTreeManager. This allows us to templates for functions like
// TouchDown, TouchUp, etc so that we can reuse the code for dispatching
// events into both APZC and APZCTM.
APZEventResult result = GetInputQueue()->ReceiveInputEvent(
this, TargetConfirmationFlags{!mWaitForMainThread}, aEvent,
aTouchBehaviors);
if (aEvent.mInputType == PANGESTURE_INPUT &&
aEvent.AsPanGestureInput().AllowsSwipe()) {
GetInputQueue()->SetBrowserGestureResponse(
result.mInputBlockId, BrowserGestureResponse::NotConsumed);
}
return result;
}
void ContentReceivedInputBlock(uint64_t aInputBlockId, bool aPreventDefault) {
GetInputQueue()->ContentReceivedInputBlock(aInputBlockId, aPreventDefault);
}
void ConfirmTarget(uint64_t aInputBlockId) {
RefPtr<AsyncPanZoomController> target = this;
GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target);
}
void SetAllowedTouchBehavior(uint64_t aInputBlockId,
const nsTArray<TouchBehaviorFlags>& aBehaviors) {
GetInputQueue()->SetAllowedTouchBehavior(aInputBlockId, aBehaviors);
}
void SetFrameMetrics(const FrameMetrics& metrics) {
RecursiveMutexAutoLock lock(mRecursiveMutex);
Metrics() = metrics;
}
void SetScrollMetadata(const ScrollMetadata& aMetadata) {
RecursiveMutexAutoLock lock(mRecursiveMutex);
mScrollMetadata = aMetadata;
}
FrameMetrics& GetFrameMetrics() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
return mScrollMetadata.GetMetrics();
}
ScrollMetadata& GetScrollMetadata() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
return mScrollMetadata;
}
const FrameMetrics& GetFrameMetrics() const {
RecursiveMutexAutoLock lock(mRecursiveMutex);
return mScrollMetadata.GetMetrics();
}
using AsyncPanZoomController::GetOverscrollAmount;
using AsyncPanZoomController::GetVelocityVector;
void AssertStateIsReset() const {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(NOTHING, mState);
}
void AssertStateIsFling() const {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(FLING, mState);
}
void AssertStateIsSmoothScroll() const {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(SMOOTH_SCROLL, mState);
}
void AssertStateIsSmoothMsdScroll() const {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(SMOOTHMSD_SCROLL, mState);
}
void AssertStateIsPanningLockedY() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(PANNING_LOCKED_Y, mState);
}
void AssertStateIsPanningLockedX() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(PANNING_LOCKED_X, mState);
}
void AssertStateIsPanning() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(PANNING, mState);
}
void AssertStateIsPanMomentum() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(PAN_MOMENTUM, mState);
}
void AssertStateIsWheelScroll() {
RecursiveMutexAutoLock lock(mRecursiveMutex);
EXPECT_EQ(WHEEL_SCROLL, mState);
}
void SetAxisLocked(ScrollDirections aDirections, bool aLockValue) {
if (aDirections.contains(ScrollDirection::eVertical)) {
mY.SetAxisLocked(aLockValue);
}
if (aDirections.contains(ScrollDirection::eHorizontal)) {
mX.SetAxisLocked(aLockValue);
}
}
void AssertNotAxisLocked() const {
EXPECT_FALSE(mY.IsAxisLocked());
EXPECT_FALSE(mX.IsAxisLocked());
}
void AssertAxisLocked(ScrollDirection aDirection) const {
switch (aDirection) {
case ScrollDirection::eHorizontal:
EXPECT_TRUE(mY.IsAxisLocked());
EXPECT_FALSE(mX.IsAxisLocked());
break;
case ScrollDirection::eVertical:
EXPECT_TRUE(mX.IsAxisLocked());
EXPECT_FALSE(mY.IsAxisLocked());
break;
default:
FAIL() << "input direction must be either vertical or horizontal";
}
}
void AdvanceAnimationsUntilEnd(
const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) {
while (AdvanceAnimations(mcc->GetSampleTime())) {
mcc->AdvanceBy(aIncrement);
}
}
bool SampleContentTransformForFrame(
AsyncTransform* aOutTransform, ParentLayerPoint& aScrollOffset,
const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) {
mcc->AdvanceBy(aIncrement);
bool ret = AdvanceAnimations(mcc->GetSampleTime());
if (aOutTransform) {
*aOutTransform =
GetCurrentAsyncTransform(AsyncPanZoomController::eForEventHandling);
}
aScrollOffset =
GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForEventHandling);
return ret;
}
CSSPoint GetCompositedScrollOffset() const {
return GetCurrentAsyncScrollOffset(
AsyncPanZoomController::eForCompositing) /
GetFrameMetrics().GetZoom();
}
void SetWaitForMainThread() { mWaitForMainThread = true; }
bool IsOverscrollAnimationRunning() const {
return mState == PanZoomState::OVERSCROLL_ANIMATION;
}
bool IsWheelScrollAnimationRunning() const {
return mState == PanZoomState::WHEEL_SCROLL;
}
private:
bool mWaitForMainThread;
MockContentControllerDelayed* mcc;
};
class APZCTesterBase : public ::testing::Test {
public:
APZCTesterBase() { mcc = new NiceMock<MockContentControllerDelayed>(); }
void SetUp() override {
gfxPlatform::GetPlatform();
// This pref is changed in Pan() without using SCOPED_GFX_PREF
// because the modified value needs to be in place until the touch
// events are processed, which may not happen until the input queue
// is flushed in TearDown(). So, we save and restore its value here.
mTouchStartTolerance = StaticPrefs::apz_touch_start_tolerance();
}
void TearDown() override {
Preferences::SetFloat("apz.touch_start_tolerance", mTouchStartTolerance);
}
enum class PanOptions {
None = 0,
KeepFingerDown = 0x1,
/*
* Do not adjust the touch-start coordinates to overcome the touch-start
* tolerance threshold. If this option is passed, it's up to the caller
* to pass in coordinates that are sufficient to overcome the touch-start
* tolerance *and* cause the desired amount of scrolling.
*/
ExactCoordinates = 0x2,
NoFling = 0x4
};
enum class PinchFlags {
None = 0,
LiftFinger1 = 0x1,
LiftFinger2 = 0x2,
/*
* The bitwise OR result of (LiftFinger1 | LiftFinger2).
* Defined explicitly here because it is used as the default
* argument for PinchWithTouchInput which is defined BEFORE the
* definition of operator| for this class.
*/
LiftBothFingers = 0x3
};
template <class InputReceiver>
APZEventResult Tap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint, TimeDuration aTapLength,
nsEventStatus (*aOutEventStatuses)[2] = nullptr);
template <class InputReceiver>
void TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint, TimeDuration aTapLength);
template <class InputReceiver>
void Pan(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aTouchStart, const ScreenIntPoint& aTouchEnd,
PanOptions aOptions = PanOptions::None,
nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
uint64_t* aOutInputBlockId = nullptr);
/*
* A version of Pan() that only takes y coordinates rather than (x, y) points
* for the touch start and end points, and uses 10 for the x coordinates.
* This is for convenience, as most tests only need to pan in one direction.
*/
template <class InputReceiver>
void Pan(const RefPtr<InputReceiver>& aTarget, int aTouchStartY,
int aTouchEndY, PanOptions aOptions = PanOptions::None,
nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
uint64_t* aOutInputBlockId = nullptr);
/*
* Dispatches mock touch events to the apzc and checks whether apzc properly
* consumed them and triggered scrolling behavior.
*/
template <class InputReceiver>
void PanAndCheckStatus(const RefPtr<InputReceiver>& aTarget, int aTouchStartY,
int aTouchEndY, bool aExpectConsumed,
nsTArray<uint32_t>* aAllowedTouchBehaviors,
uint64_t* aOutInputBlockId = nullptr);
template <class InputReceiver>
void DoubleTap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
uint64_t (*aOutInputBlockIds)[2] = nullptr);
template <class InputReceiver>
void DoubleTapAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
uint64_t (*aOutInputBlockIds)[2] = nullptr);
struct PinchOptions {
nsTArray<uint32_t>* mAllowedTouchBehaviors = nullptr;
nsEventStatus (*mOutEventStatuses)[4] = nullptr;
uint64_t* mOutInputBlockId = nullptr;
PinchFlags mFlags = PinchFlags::LiftBothFingers;
bool mVertical = false;
int* mInputId = nullptr;
Maybe<ScreenIntPoint> mSecondFocus;
TimeDuration mTimeBetweenTouchEvents = TimeDuration::FromMilliseconds(20);
PinchOptions() {}
// Fluent interface
PinchOptions& AllowedTouchBehaviors(
nsTArray<uint32_t>* aAllowedTouchBehaviors) {
mAllowedTouchBehaviors = aAllowedTouchBehaviors;
return *this;
}
PinchOptions& OutEventStatuses(nsEventStatus (*aOutEventStatuses)[4]) {
mOutEventStatuses = aOutEventStatuses;
return *this;
}
PinchOptions& OutInputBlockId(uint64_t* aOutInputBlockId) {
mOutInputBlockId = aOutInputBlockId;
return *this;
}
PinchOptions& Flags(PinchFlags aFlags) {
mFlags = aFlags;
return *this;
}
PinchOptions& Vertical(bool aVertical) {
mVertical = aVertical;
return *this;
}
PinchOptions& InputId(int& aInputId) {
mInputId = &aInputId;
return *this;
}
PinchOptions& SecondFocus(const ScreenIntPoint& aSecondFocus) {
mSecondFocus = Some(aSecondFocus);
return *this;
}
PinchOptions& TimeBetweenTouchEvents(const TimeDuration& aDuration) {
mTimeBetweenTouchEvents = aDuration;
return *this;
}
};
// Pinch with one focus point. Zooms in place with no panning
template <class InputReceiver>
void PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aFocus, float aScale,
PinchOptions aOptions = PinchOptions());
template <class InputReceiver>
void PinchWithTouchInputAndCheckStatus(
const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aFocus,
float aScale, int& inputId, bool aShouldTriggerPinch,
nsTArray<uint32_t>* aAllowedTouchBehaviors);
template <class InputReceiver>
void PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aFocus,
const ScreenIntPoint& aSecondFocus, float aScale,
nsEventStatus (*aOutEventStatuses)[3] = nullptr);
template <class InputReceiver>
void PinchWithPinchInputAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aFocus,
float aScale,
bool aShouldTriggerPinch);
protected:
RefPtr<MockContentControllerDelayed> mcc;
private:
float mTouchStartTolerance;
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PanOptions)
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(APZCTesterBase::PinchFlags)
template <class InputReceiver>
APZEventResult APZCTesterBase::Tap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
TimeDuration aTapLength,
nsEventStatus (*aOutEventStatuses)[2]) {
APZEventResult touchDownResult = TouchDown(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = touchDownResult.GetStatus();
}
mcc->AdvanceBy(aTapLength);
// If touch-action is enabled then simulate the allowed touch behaviour
// notification that the main thread is supposed to deliver.
if (touchDownResult.GetStatus() != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, touchDownResult.mInputBlockId);
}
APZEventResult touchUpResult = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = touchUpResult.GetStatus();
}
return touchDownResult;
}
template <class InputReceiver>
void APZCTesterBase::TapAndCheckStatus(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
TimeDuration aTapLength) {
nsEventStatus statuses[2];
Tap(aTarget, aPoint, aTapLength, &statuses);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
}
template <class InputReceiver>
void APZCTesterBase::Pan(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aTouchStart,
const ScreenIntPoint& aTouchEnd, PanOptions aOptions,
nsTArray<uint32_t>* aAllowedTouchBehaviors,
nsEventStatus (*aOutEventStatuses)[4],
uint64_t* aOutInputBlockId) {
// Reduce the move tolerance to a tiny value.
// We can't use a scoped pref because this value might be read at some later
// time when the events are actually processed, rather than when we deliver
// them.
const float touchStartTolerance = 0.1f;
const float panThreshold = touchStartTolerance * aTarget->GetDPI();
Preferences::SetFloat("apz.touch_start_tolerance", touchStartTolerance);
Preferences::SetFloat("apz.touch_move_tolerance", 0.0f);
int overcomeTouchToleranceX = 0;
int overcomeTouchToleranceY = 0;
if (!(aOptions & PanOptions::ExactCoordinates)) {
// Have the direction of the adjustment to overcome the touch tolerance
// match the direction of the entire gesture, otherwise we run into
// trouble such as accidentally activating the axis lock.
if (aTouchStart.x != aTouchEnd.x && aTouchStart.y != aTouchEnd.y) {
// Tests that need to avoid rounding error here can arrange for
// panThreshold to be 10 (by setting the DPI to 100), which makes sure
// that these are the legs in a Pythagorean triple where panThreshold is
// the hypotenuse. Watch out for changes of APZCPinchTester::mDPI.
overcomeTouchToleranceX = panThreshold / 10 * 6;
overcomeTouchToleranceY = panThreshold / 10 * 8;
} else if (aTouchStart.x != aTouchEnd.x) {
overcomeTouchToleranceX = panThreshold;
} else if (aTouchStart.y != aTouchEnd.y) {
overcomeTouchToleranceY = panThreshold;
}
}
const TimeDuration TIME_BETWEEN_TOUCH_EVENT =
TimeDuration::FromMilliseconds(20);
// Even if the caller doesn't care about the block id, we need it to set the
// allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
uint64_t blockId;
if (!aOutInputBlockId) {
aOutInputBlockId = &blockId;
}
// Make sure the move is large enough to not be handled as a tap
APZEventResult result =
TouchDown(aTarget,
ScreenIntPoint(aTouchStart.x + overcomeTouchToleranceX,
aTouchStart.y + overcomeTouchToleranceY),
mcc->Time());
if (aOutInputBlockId) {
*aOutInputBlockId = result.mInputBlockId;
}
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = result.GetStatus();
}
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
// Allowed touch behaviours must be set after sending touch-start.
if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) {
if (aAllowedTouchBehaviors) {
EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length());
aTarget->SetAllowedTouchBehavior(*aOutInputBlockId,
*aAllowedTouchBehaviors);
} else {
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
}
}
result = TouchMove(aTarget, aTouchStart, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = result.GetStatus();
}
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
const int numSteps = 3;
auto stepVector = (aTouchEnd - aTouchStart) / numSteps;
for (int k = 1; k < numSteps; k++) {
auto stepPoint = aTouchStart + stepVector * k;
Unused << TouchMove(aTarget, stepPoint, mcc->Time());
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
}
result = TouchMove(aTarget, aTouchEnd, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = result.GetStatus();
}
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
if (!(aOptions & PanOptions::KeepFingerDown)) {
result = TouchUp(aTarget, aTouchEnd, mcc->Time());
} else {
result.SetStatusAsIgnore();
}
if (aOutEventStatuses) {
(*aOutEventStatuses)[3] = result.GetStatus();
}
if ((aOptions & PanOptions::NoFling)) {
aTarget->CancelAnimation();
}
// Don't increment the time here. Animations started on touch-up, such as
// flings, are affected by elapsed time, and we want to be able to sample
// them immediately after they start, without time having elapsed.
}
template <class InputReceiver>
void APZCTesterBase::Pan(const RefPtr<InputReceiver>& aTarget, int aTouchStartY,
int aTouchEndY, PanOptions aOptions,
nsTArray<uint32_t>* aAllowedTouchBehaviors,
nsEventStatus (*aOutEventStatuses)[4],
uint64_t* aOutInputBlockId) {
Pan(aTarget, ScreenIntPoint(10, aTouchStartY), ScreenIntPoint(10, aTouchEndY),
aOptions, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId);
}
template <class InputReceiver>
void APZCTesterBase::PanAndCheckStatus(
const RefPtr<InputReceiver>& aTarget, int aTouchStartY, int aTouchEndY,
bool aExpectConsumed, nsTArray<uint32_t>* aAllowedTouchBehaviors,
uint64_t* aOutInputBlockId) {
nsEventStatus statuses[4]; // down, move, move, up
Pan(aTarget, aTouchStartY, aTouchEndY, PanOptions::None,
aAllowedTouchBehaviors, &statuses, aOutInputBlockId);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
nsEventStatus touchMoveStatus;
if (aExpectConsumed) {
touchMoveStatus = nsEventStatus_eConsumeDoDefault;
} else {
touchMoveStatus = nsEventStatus_eIgnore;
}
EXPECT_EQ(touchMoveStatus, statuses[1]);
EXPECT_EQ(touchMoveStatus, statuses[2]);
}
template <class InputReceiver>
void APZCTesterBase::DoubleTap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
nsEventStatus (*aOutEventStatuses)[4],
uint64_t (*aOutInputBlockIds)[2]) {
APZEventResult result = TouchDown(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = result.GetStatus();
}
if (aOutInputBlockIds) {
(*aOutInputBlockIds)[0] = result.mInputBlockId;
}
mcc->AdvanceByMillis(10);
// If touch-action is enabled then simulate the allowed touch behaviour
// notification that the main thread is supposed to deliver.
if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId);
}
result = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = result.GetStatus();
}
mcc->AdvanceByMillis(10);
result = TouchDown(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = result.GetStatus();
}
if (aOutInputBlockIds) {
(*aOutInputBlockIds)[1] = result.mInputBlockId;
}
mcc->AdvanceByMillis(10);
if (result.GetStatus() != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, result.mInputBlockId);
}
result = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[3] = result.GetStatus();
}
}
template <class InputReceiver>
void APZCTesterBase::DoubleTapAndCheckStatus(
const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
uint64_t (*aOutInputBlockIds)[2]) {
nsEventStatus statuses[4];
DoubleTap(aTarget, aPoint, &statuses, aOutInputBlockIds);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[1]);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[2]);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[3]);
}
template <class InputReceiver>
void APZCTesterBase::PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aFocus,
float aScale, PinchOptions aOptions) {
// Having pinch coordinates in float type may cause problems with
// high-precision scale values since SingleTouchData accepts integer value.
// But for trivial tests it should be ok.
const float pinchLength = 100.0;
const float pinchLengthScaled = pinchLength * aScale;
const float pinchLengthX = aOptions.mVertical ? 0 : pinchLength;
const float pinchLengthScaledX = aOptions.mVertical ? 0 : pinchLengthScaled;
const float pinchLengthY = aOptions.mVertical ? pinchLength : 0;
const float pinchLengthScaledY = aOptions.mVertical ? pinchLengthScaled : 0;
// Even if the caller doesn't care about the block id, we need it to set the
// allowed touch behaviour below, so make sure aOutInputBlockId is non-null.
uint64_t blockId;
if (!aOptions.mOutInputBlockId) {
aOptions.mOutInputBlockId = &blockId;
}
int inputId = aOptions.mInputId ? *aOptions.mInputId : 0;
// If a second focus point is not specified in the pinch options, use the
// same focus point throughout the gesture.
ScreenIntPoint secondFocus =
aOptions.mSecondFocus.isSome() ? *aOptions.mSecondFocus : aFocus;
MultiTouchInput mtiStart =
MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, mcc->Time(), 0);
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus));
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus));
APZEventResult result;
result = aTarget->ReceiveInputEvent(mtiStart);
if (aOptions.mOutInputBlockId) {
*aOptions.mOutInputBlockId = result.mInputBlockId;
}
if (aOptions.mOutEventStatuses) {
(*aOptions.mOutEventStatuses)[0] = result.GetStatus();
}
if (aOptions.mAllowedTouchBehaviors) {
EXPECT_EQ(2UL, aOptions.mAllowedTouchBehaviors->Length());
aTarget->SetAllowedTouchBehavior(*aOptions.mOutInputBlockId,
*aOptions.mAllowedTouchBehaviors);
} else {
SetDefaultAllowedTouchBehavior(aTarget, *aOptions.mOutInputBlockId, 2);
}
mcc->AdvanceBy(aOptions.mTimeBetweenTouchEvents);
ScreenIntPoint pinchStartPoint1(aFocus.x - int32_t(pinchLengthX),
aFocus.y - int32_t(pinchLengthY));
ScreenIntPoint pinchStartPoint2(aFocus.x + int32_t(pinchLengthX),
aFocus.y + int32_t(pinchLengthY));
MultiTouchInput mtiMove1 =
MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0);
mtiMove1.mTouches.AppendElement(
CreateSingleTouchData(inputId, pinchStartPoint1));
mtiMove1.mTouches.AppendElement(
CreateSingleTouchData(inputId + 1, pinchStartPoint2));
result = aTarget->ReceiveInputEvent(mtiMove1);
if (aOptions.mOutEventStatuses) {
(*aOptions.mOutEventStatuses)[1] = result.GetStatus();
}
mcc->AdvanceBy(aOptions.mTimeBetweenTouchEvents);
// Pinch instantly but move in steps.
const int numSteps = 3;
auto stepVector = (secondFocus - aFocus) / numSteps;
for (int k = 1; k < numSteps; k++) {
ScreenIntPoint stepFocus = aFocus + stepVector * k;
ScreenIntPoint stepPoint1(stepFocus.x - int32_t(pinchLengthScaledX),
stepFocus.y - int32_t(pinchLengthScaledY));
ScreenIntPoint stepPoint2(stepFocus.x + int32_t(pinchLengthScaledX),
stepFocus.y + int32_t(pinchLengthScaledY));
MultiTouchInput mtiMoveStep =
MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0);
mtiMoveStep.mTouches.AppendElement(
CreateSingleTouchData(inputId, stepPoint1));
mtiMoveStep.mTouches.AppendElement(
CreateSingleTouchData(inputId + 1, stepPoint2));
Unused << aTarget->ReceiveInputEvent(mtiMoveStep);
mcc->AdvanceBy(aOptions.mTimeBetweenTouchEvents);
}
ScreenIntPoint pinchEndPoint1(secondFocus.x - int32_t(pinchLengthScaledX),
secondFocus.y - int32_t(pinchLengthScaledY));
ScreenIntPoint pinchEndPoint2(secondFocus.x + int32_t(pinchLengthScaledX),
secondFocus.y + int32_t(pinchLengthScaledY));
MultiTouchInput mtiMove2 =
MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, mcc->Time(), 0);
mtiMove2.mTouches.AppendElement(
CreateSingleTouchData(inputId, pinchEndPoint1));
mtiMove2.mTouches.AppendElement(
CreateSingleTouchData(inputId + 1, pinchEndPoint2));
result = aTarget->ReceiveInputEvent(mtiMove2);
if (aOptions.mOutEventStatuses) {
(*aOptions.mOutEventStatuses)[2] = result.GetStatus();
}
if (aOptions.mFlags & (PinchFlags::LiftFinger1 | PinchFlags::LiftFinger2)) {
mcc->AdvanceBy(aOptions.mTimeBetweenTouchEvents);
MultiTouchInput mtiEnd =
MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, mcc->Time(), 0);
if (aOptions.mFlags & PinchFlags::LiftFinger1) {
mtiEnd.mTouches.AppendElement(
CreateSingleTouchData(inputId, pinchEndPoint1));
}
if (aOptions.mFlags & PinchFlags::LiftFinger2) {
mtiEnd.mTouches.AppendElement(
CreateSingleTouchData(inputId + 1, pinchEndPoint2));
}
result = aTarget->ReceiveInputEvent(mtiEnd);
if (aOptions.mOutEventStatuses) {
(*aOptions.mOutEventStatuses)[3] = result.GetStatus();
}
}
inputId += 2;
if (aOptions.mInputId) {
*aOptions.mInputId = inputId;
}
}
template <class InputReceiver>
void APZCTesterBase::PinchWithTouchInputAndCheckStatus(
const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aFocus,
float aScale, int& inputId, bool aShouldTriggerPinch,
nsTArray<uint32_t>* aAllowedTouchBehaviors) {
nsEventStatus statuses[4]; // down, move, move, up
PinchWithTouchInput(aTarget, aFocus, aScale,
PinchOptions()
.AllowedTouchBehaviors(aAllowedTouchBehaviors)
.OutEventStatuses(&statuses)
.InputId(inputId));
nsEventStatus expectedMoveStatus = aShouldTriggerPinch
? nsEventStatus_eConsumeDoDefault
: nsEventStatus_eIgnore;
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, statuses[0]);
EXPECT_EQ(expectedMoveStatus, statuses[1]);
EXPECT_EQ(expectedMoveStatus, statuses[2]);
}
template <class InputReceiver>
void APZCTesterBase::PinchWithPinchInput(
const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aFocus,
const ScreenIntPoint& aSecondFocus, float aScale,
nsEventStatus (*aOutEventStatuses)[3]) {
const TimeDuration TIME_BETWEEN_PINCH_INPUT =
TimeDuration::FromMilliseconds(50);
auto event = CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
aFocus, 10.0, 10.0, mcc->Time());
APZEventResult actual = aTarget->ReceiveInputEvent(event);
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = actual.GetStatus();
}
mcc->AdvanceBy(TIME_BETWEEN_PINCH_INPUT);
event =
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
aSecondFocus, 10.0 * aScale, 10.0, mcc->Time());
actual = aTarget->ReceiveInputEvent(event);
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = actual.GetStatus();
}
mcc->AdvanceBy(TIME_BETWEEN_PINCH_INPUT);
event =
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END, aSecondFocus,
10.0 * aScale, 10.0 * aScale, mcc->Time());
actual = aTarget->ReceiveInputEvent(event);
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = actual.GetStatus();
}
}
template <class InputReceiver>
void APZCTesterBase::PinchWithPinchInputAndCheckStatus(
const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aFocus,
float aScale, bool aShouldTriggerPinch) {
nsEventStatus statuses[3]; // scalebegin, scale, scaleend
PinchWithPinchInput(aTarget, aFocus, aFocus, aScale, &statuses);
nsEventStatus expectedStatus = aShouldTriggerPinch
? nsEventStatus_eConsumeDoDefault
: nsEventStatus_eIgnore;
EXPECT_EQ(expectedStatus, statuses[0]);
EXPECT_EQ(expectedStatus, statuses[1]);
}
inline FrameMetrics TestFrameMetrics() {
FrameMetrics fm;
fm.SetDisplayPort(CSSRect(0, 0, 10, 10));
fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10));
fm.SetScrollableRect(CSSRect(0, 0, 100, 100));
return fm;
}
#endif // mozilla_layers_APZTestCommon_h