import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View file

@ -0,0 +1,120 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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_APZCBasicTester_h
#define mozilla_layers_APZCBasicTester_h
/**
* Defines a test fixture used for testing a single APZC.
*/
#include "APZTestCommon.h"
class APZCBasicTester : public APZCTesterBase {
public:
explicit APZCBasicTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
: mGestureBehavior(aGestureBehavior)
{
}
protected:
virtual void SetUp()
{
gfxPrefs::GetSingleton();
APZThreadUtils::SetThreadAssertionsEnabled(false);
APZThreadUtils::SetControllerThread(MessageLoop::current());
tm = new TestAPZCTreeManager(mcc);
apzc = new TestAsyncPanZoomController(0, mcc, tm, mGestureBehavior);
apzc->SetFrameMetrics(TestFrameMetrics());
apzc->GetScrollMetadata().SetIsLayersIdRoot(true);
}
/**
* Get the APZC's scroll range in CSS pixels.
*/
CSSRect GetScrollRange() const
{
const FrameMetrics& metrics = apzc->GetFrameMetrics();
return CSSRect(
metrics.GetScrollableRect().TopLeft(),
metrics.GetScrollableRect().Size() - metrics.CalculateCompositedSizeInCssPixels());
}
virtual void TearDown()
{
while (mcc->RunThroughDelayedTasks());
apzc->Destroy();
tm->ClearTree();
tm->ClearContentController();
}
void MakeApzcWaitForMainThread()
{
apzc->SetWaitForMainThread();
}
void MakeApzcZoomable()
{
apzc->UpdateZoomConstraints(ZoomConstraints(true, true, CSSToParentLayerScale(0.25f), CSSToParentLayerScale(4.0f)));
}
void MakeApzcUnzoomable()
{
apzc->UpdateZoomConstraints(ZoomConstraints(false, false, CSSToParentLayerScale(1.0f), CSSToParentLayerScale(1.0f)));
}
void PanIntoOverscroll();
/**
* Sample animations once, 1 ms later than the last sample.
*/
void SampleAnimationOnce()
{
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
mcc->AdvanceBy(increment);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
}
/**
* Sample animations until we recover from overscroll.
* @param aExpectedScrollOffset the expected reported scroll offset
* throughout the animation
*/
void SampleAnimationUntilRecoveredFromOverscroll(const ParentLayerPoint& aExpectedScrollOffset)
{
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
bool recoveredFromOverscroll = false;
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
while (apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut)) {
// The reported scroll offset should be the same throughout.
EXPECT_EQ(aExpectedScrollOffset, pointOut);
// Trigger computation of the overscroll tranform, to make sure
// no assetions fire during the calculation.
apzc->GetOverscrollTransform(AsyncPanZoomController::NORMAL);
if (!apzc->IsOverscrolled()) {
recoveredFromOverscroll = true;
}
mcc->AdvanceBy(increment);
}
EXPECT_TRUE(recoveredFromOverscroll);
apzc->AssertStateIsReset();
}
void TestOverscroll();
AsyncPanZoomController::GestureBehavior mGestureBehavior;
RefPtr<TestAPZCTreeManager> tm;
RefPtr<TestAsyncPanZoomController> apzc;
};
#endif // mozilla_layers_APZCBasicTester_h

View file

@ -0,0 +1,194 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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_APZCTreeManagerTester_h
#define mozilla_layers_APZCTreeManagerTester_h
/**
* Defines a test fixture used for testing multiple APZCs interacting in
* an APZCTreeManager.
*/
#include "APZTestCommon.h"
#include "gfxPlatform.h"
class APZCTreeManagerTester : public APZCTesterBase {
protected:
virtual void SetUp() {
gfxPrefs::GetSingleton();
gfxPlatform::GetPlatform();
APZThreadUtils::SetThreadAssertionsEnabled(false);
APZThreadUtils::SetControllerThread(MessageLoop::current());
manager = new TestAPZCTreeManager(mcc);
}
virtual void TearDown() {
while (mcc->RunThroughDelayedTasks());
manager->ClearTree();
manager->ClearContentController();
}
/**
* Sample animations once for all APZCs, 1 ms later than the last sample.
*/
void SampleAnimationsOnce() {
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
mcc->AdvanceBy(increment);
for (const RefPtr<Layer>& layer : layers) {
if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) {
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
}
}
}
nsTArray<RefPtr<Layer> > layers;
RefPtr<LayerManager> lm;
RefPtr<Layer> root;
RefPtr<TestAPZCTreeManager> manager;
protected:
static ScrollMetadata BuildScrollMetadata(FrameMetrics::ViewID aScrollId,
const CSSRect& aScrollableRect,
const ParentLayerRect& aCompositionBounds)
{
ScrollMetadata metadata;
FrameMetrics& metrics = metadata.GetMetrics();
metrics.SetScrollId(aScrollId);
// By convention in this test file, START_SCROLL_ID is the root, so mark it as such.
if (aScrollId == FrameMetrics::START_SCROLL_ID) {
metadata.SetIsLayersIdRoot(true);
}
metrics.SetCompositionBounds(aCompositionBounds);
metrics.SetScrollableRect(aScrollableRect);
metrics.SetScrollOffset(CSSPoint(0, 0));
metadata.SetPageScrollAmount(LayoutDeviceIntSize(50, 100));
metadata.SetLineScrollAmount(LayoutDeviceIntSize(5, 10));
metadata.SetAllowVerticalScrollWithWheel(true);
return metadata;
}
static void SetEventRegionsBasedOnBottommostMetrics(Layer* aLayer)
{
const FrameMetrics& metrics = aLayer->GetScrollMetadata(0).GetMetrics();
CSSRect scrollableRect = metrics.GetScrollableRect();
if (!scrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
// The purpose of this is to roughly mimic what layout would do in the
// case of a scrollable frame with the event regions and clip. This lets
// us exercise the hit-testing code in APZCTreeManager
EventRegions er = aLayer->GetEventRegions();
IntRect scrollRect = RoundedToInt(
scrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect();
er.mHitRegion = nsIntRegion(IntRect(
RoundedToInt(metrics.GetCompositionBounds().TopLeft().ToUnknownPoint()),
scrollRect.Size()));
aLayer->SetEventRegions(er);
}
}
static void SetScrollableFrameMetrics(Layer* aLayer, FrameMetrics::ViewID aScrollId,
CSSRect aScrollableRect = CSSRect(-1, -1, -1, -1)) {
ParentLayerIntRect compositionBounds = ViewAs<ParentLayerPixel>(
aLayer->GetVisibleRegion().ToUnknownRegion().GetBounds());
ScrollMetadata metadata = BuildScrollMetadata(aScrollId, aScrollableRect,
ParentLayerRect(compositionBounds));
aLayer->SetScrollMetadata(metadata);
aLayer->SetClipRect(Some(compositionBounds));
SetEventRegionsBasedOnBottommostMetrics(aLayer);
}
void SetScrollHandoff(Layer* aChild, Layer* aParent) {
ScrollMetadata metadata = aChild->GetScrollMetadata(0);
metadata.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
aChild->SetScrollMetadata(metadata);
}
static TestAsyncPanZoomController* ApzcOf(Layer* aLayer) {
EXPECT_EQ(1u, aLayer->GetScrollMetadataCount());
return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(0);
}
static TestAsyncPanZoomController* ApzcOf(Layer* aLayer, uint32_t aIndex) {
EXPECT_LT(aIndex, aLayer->GetScrollMetadataCount());
return (TestAsyncPanZoomController*)aLayer->GetAsyncPanZoomController(aIndex);
}
void CreateSimpleScrollingLayer() {
const char* layerTreeSyntax = "t";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,200,200)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
}
void CreateSimpleDTCScrollingLayer() {
const char* layerTreeSyntax = "t";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,200,200)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 500, 500));
EventRegions regions;
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 200));
regions.mDispatchToContentHitRegion = regions.mHitRegion;
layers[0]->SetEventRegions(regions);
}
void CreateSimpleMultiLayerTree() {
const char* layerTreeSyntax = "c(tt)";
// LayerID 0 12
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,100,100)),
nsIntRegion(IntRect(0,0,100,50)),
nsIntRegion(IntRect(0,50,100,50)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
}
void CreatePotentiallyLeakingTree() {
const char* layerTreeSyntax = "c(c(c(t))c(c(t)))";
// LayerID 0 1 2 3 4 5 6
root = CreateLayerTree(layerTreeSyntax, nullptr, nullptr, lm, layers);
SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
SetScrollableFrameMetrics(layers[5], FrameMetrics::START_SCROLL_ID + 1);
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2);
SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 3);
}
void CreateBug1194876Tree() {
const char* layerTreeSyntax = "c(t)";
// LayerID 0 1
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,100,100)),
nsIntRegion(IntRect(0,0,100,100)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
SetScrollHandoff(layers[1], layers[0]);
// Make layers[1] the root content
ScrollMetadata childMetadata = layers[1]->GetScrollMetadata(0);
childMetadata.GetMetrics().SetIsRootContent(true);
layers[1]->SetScrollMetadata(childMetadata);
// Both layers are fully dispatch-to-content
EventRegions regions;
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
regions.mDispatchToContentHitRegion = regions.mHitRegion;
layers[0]->SetEventRegions(regions);
layers[1]->SetEventRegions(regions);
}
};
#endif // mozilla_layers_APZCTreeManagerTester_h

View file

@ -0,0 +1,609 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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/AsyncCompositionManager.h" // for ViewTransform
#include "mozilla/layers/GeckoContentController.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/APZCTreeManager.h"
#include "mozilla/layers/LayerMetricsWrapper.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/UniquePtr.h"
#include "apz/src/AsyncPanZoomController.h"
#include "apz/src/HitTestingTreeNode.h"
#include "base/task.h"
#include "Layers.h"
#include "TestLayers.h"
#include "UnitTransforms.h"
#include "gfxPrefs.h"
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::AtLeast;
using ::testing::AtMost;
using ::testing::MockFunction;
using ::testing::InSequence;
typedef mozilla::layers::GeckoContentController::TapType TapType;
template<class T>
class ScopedGfxPref {
public:
ScopedGfxPref(T (*aGetPrefFunc)(void), void (*aSetPrefFunc)(T), T aVal)
: mSetPrefFunc(aSetPrefFunc)
{
mOldVal = aGetPrefFunc();
aSetPrefFunc(aVal);
}
~ScopedGfxPref() {
mSetPrefFunc(mOldVal);
}
private:
void (*mSetPrefFunc)(T);
T mOldVal;
};
#define SCOPED_GFX_PREF(prefBase, prefType, prefValue) \
ScopedGfxPref<prefType> pref_##prefBase( \
&(gfxPrefs::prefBase), \
&(gfxPrefs::Set##prefBase), \
prefValue)
static TimeStamp GetStartupTime() {
static TimeStamp sStartupTime = TimeStamp::Now();
return sStartupTime;
}
class MockContentController : public GeckoContentController {
public:
MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
MOCK_METHOD5(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
MOCK_METHOD4(NotifyPinchGesture, void(PinchGestureInput::PinchGestureType, const ScrollableLayerGuid&, 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(Move(aTask));
}
MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
MOCK_METHOD0(NotifyFlushComplete, void());
};
class MockContentControllerDelayed : public MockContentController {
public:
MockContentControllerDelayed()
: mTime(GetStartupTime())
{
}
const TimeStamp& Time() {
return mTime;
}
void AdvanceByMillis(int aMillis) {
AdvanceBy(TimeDuration::FromMilliseconds(aMillis));
}
void AdvanceBy(const TimeDuration& aIncrement) {
TimeStamp 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;
TimeStamp 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>, TimeStamp>> runQueue;
runQueue.SwapElements(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>, TimeStamp> 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>, TimeStamp>> mTaskQueue;
TimeStamp mTime;
};
class TestAPZCTreeManager : public APZCTreeManager {
public:
explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {}
RefPtr<InputQueue> GetInputQueue() const {
return mInputQueue;
}
void ClearContentController() {
mcc = nullptr;
}
protected:
AsyncPanZoomController* NewAPZCInstance(uint64_t aLayersId,
GeckoContentController* aController) override;
TimeStamp GetFrameTime() override {
return mcc->Time();
}
private:
RefPtr<MockContentControllerDelayed> mcc;
};
class TestAsyncPanZoomController : public AsyncPanZoomController {
public:
TestAsyncPanZoomController(uint64_t aLayersId, MockContentControllerDelayed* aMcc,
TestAPZCTreeManager* aTreeManager,
GestureBehavior aBehavior = DEFAULT_GESTURES)
: AsyncPanZoomController(aLayersId, aTreeManager, aTreeManager->GetInputQueue(),
aMcc, aBehavior)
, mWaitForMainThread(false)
, mcc(aMcc)
{}
nsEventStatus ReceiveInputEvent(const InputData& aEvent, ScrollableLayerGuid* aDummy, uint64_t* aOutInputBlockId) {
// 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.
return ReceiveInputEvent(aEvent, aOutInputBlockId);
}
nsEventStatus ReceiveInputEvent(const InputData& aEvent, uint64_t* aOutInputBlockId) {
return GetInputQueue()->ReceiveInputEvent(this, !mWaitForMainThread, aEvent, aOutInputBlockId);
}
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) {
ReentrantMonitorAutoEnter lock(mMonitor);
mFrameMetrics = metrics;
}
FrameMetrics& GetFrameMetrics() {
ReentrantMonitorAutoEnter lock(mMonitor);
return mFrameMetrics;
}
ScrollMetadata& GetScrollMetadata() {
ReentrantMonitorAutoEnter lock(mMonitor);
return mScrollMetadata;
}
const FrameMetrics& GetFrameMetrics() const {
ReentrantMonitorAutoEnter lock(mMonitor);
return mFrameMetrics;
}
using AsyncPanZoomController::GetVelocityVector;
void AssertStateIsReset() const {
ReentrantMonitorAutoEnter lock(mMonitor);
EXPECT_EQ(NOTHING, mState);
}
void AssertStateIsFling() const {
ReentrantMonitorAutoEnter lock(mMonitor);
EXPECT_EQ(FLING, mState);
}
void AdvanceAnimationsUntilEnd(const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(10)) {
while (AdvanceAnimations(mcc->Time())) {
mcc->AdvanceBy(aIncrement);
}
}
bool SampleContentTransformForFrame(AsyncTransform* aOutTransform,
ParentLayerPoint& aScrollOffset,
const TimeDuration& aIncrement = TimeDuration::FromMilliseconds(0)) {
mcc->AdvanceBy(aIncrement);
bool ret = AdvanceAnimations(mcc->Time());
if (aOutTransform) {
*aOutTransform = GetCurrentAsyncTransform(AsyncPanZoomController::NORMAL);
}
aScrollOffset = GetCurrentAsyncScrollOffset(AsyncPanZoomController::NORMAL);
return ret;
}
void SetWaitForMainThread() {
mWaitForMainThread = true;
}
private:
bool mWaitForMainThread;
MockContentControllerDelayed* mcc;
};
class APZCTesterBase : public ::testing::Test {
public:
APZCTesterBase() {
mcc = new NiceMock<MockContentControllerDelayed>();
}
template<class InputReceiver>
void Tap(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeDuration aTapLength,
nsEventStatus (*aOutEventStatuses)[2] = nullptr,
uint64_t* aOutInputBlockId = 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,
bool aKeepFingerDown = false,
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, bool aKeepFingerDown = false,
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);
void ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc,
int aTouchStartY,
int aTouchEndY,
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);
protected:
RefPtr<MockContentControllerDelayed> mcc;
};
template<class InputReceiver>
void
APZCTesterBase::Tap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint, TimeDuration aTapLength,
nsEventStatus (*aOutEventStatuses)[2],
uint64_t* aOutInputBlockId)
{
// 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;
}
nsEventStatus status = TouchDown(aTarget, aPoint, mcc->Time(), aOutInputBlockId);
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = status;
}
mcc->AdvanceBy(aTapLength);
// If touch-action is enabled then simulate the allowed touch behaviour
// notification that the main thread is supposed to deliver.
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
}
status = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = status;
}
}
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,
bool aKeepFingerDown,
nsTArray<uint32_t>* aAllowedTouchBehaviors,
nsEventStatus (*aOutEventStatuses)[4],
uint64_t* aOutInputBlockId)
{
// Reduce the touch start and 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.
gfxPrefs::SetAPZTouchStartTolerance(1.0f / 1000.0f);
gfxPrefs::SetAPZTouchMoveTolerance(0.0f);
const int OVERCOME_TOUCH_TOLERANCE = 1;
const TimeDuration TIME_BETWEEN_TOUCH_EVENT = TimeDuration::FromMilliseconds(50);
// 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
nsEventStatus status = TouchDown(aTarget,
ScreenIntPoint(aTouchStart.x, aTouchStart.y + OVERCOME_TOUCH_TOLERANCE),
mcc->Time(), aOutInputBlockId);
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = status;
}
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
// Allowed touch behaviours must be set after sending touch-start.
if (status != nsEventStatus_eConsumeNoDefault) {
if (aAllowedTouchBehaviors) {
EXPECT_EQ(1UL, aAllowedTouchBehaviors->Length());
aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
} else if (gfxPrefs::TouchActionEnabled()) {
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId);
}
}
status = TouchMove(aTarget, aTouchStart, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = status;
}
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
status = TouchMove(aTarget, aTouchEnd, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = status;
}
mcc->AdvanceBy(TIME_BETWEEN_TOUCH_EVENT);
if (!aKeepFingerDown) {
status = TouchUp(aTarget, aTouchEnd, mcc->Time());
} else {
status = nsEventStatus_eIgnore;
}
if (aOutEventStatuses) {
(*aOutEventStatuses)[3] = status;
}
// 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, bool aKeepFingerDown,
nsTArray<uint32_t>* aAllowedTouchBehaviors,
nsEventStatus (*aOutEventStatuses)[4],
uint64_t* aOutInputBlockId)
{
Pan(aTarget, ScreenIntPoint(10, aTouchStartY), ScreenIntPoint(10, aTouchEndY),
aKeepFingerDown, 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, false, 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]);
}
void
APZCTesterBase::ApzcPanNoFling(const RefPtr<TestAsyncPanZoomController>& aApzc,
int aTouchStartY, int aTouchEndY,
uint64_t* aOutInputBlockId)
{
Pan(aApzc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId);
aApzc->CancelAnimation();
}
template<class InputReceiver>
void
APZCTesterBase::DoubleTap(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aPoint,
nsEventStatus (*aOutEventStatuses)[4],
uint64_t (*aOutInputBlockIds)[2])
{
uint64_t blockId;
nsEventStatus status = TouchDown(aTarget, aPoint, mcc->Time(), &blockId);
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = status;
}
if (aOutInputBlockIds) {
(*aOutInputBlockIds)[0] = blockId;
}
mcc->AdvanceByMillis(10);
// If touch-action is enabled then simulate the allowed touch behaviour
// notification that the main thread is supposed to deliver.
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, blockId);
}
status = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = status;
}
mcc->AdvanceByMillis(10);
status = TouchDown(aTarget, aPoint, mcc->Time(), &blockId);
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = status;
}
if (aOutInputBlockIds) {
(*aOutInputBlockIds)[1] = blockId;
}
mcc->AdvanceByMillis(10);
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(aTarget, blockId);
}
status = TouchUp(aTarget, aPoint, mcc->Time());
if (aOutEventStatuses) {
(*aOutEventStatuses)[3] = status;
}
}
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]);
}
AsyncPanZoomController*
TestAPZCTreeManager::NewAPZCInstance(uint64_t aLayersId,
GeckoContentController* aController)
{
MockContentControllerDelayed* mcc = static_cast<MockContentControllerDelayed*>(aController);
return new TestAsyncPanZoomController(aLayersId, mcc, this,
AsyncPanZoomController::USE_GESTURE_DETECTOR);
}
FrameMetrics
TestFrameMetrics()
{
FrameMetrics fm;
fm.SetDisplayPort(CSSRect(0, 0, 10, 10));
fm.SetCompositionBounds(ParentLayerRect(0, 0, 10, 10));
fm.SetCriticalDisplayPort(CSSRect(0, 0, 10, 10));
fm.SetScrollableRect(CSSRect(0, 0, 100, 100));
return fm;
}
uint32_t
MillisecondsSinceStartup(TimeStamp aTime)
{
return (aTime - GetStartupTime()).ToMilliseconds();
}
#endif // mozilla_layers_APZTestCommon_h

View file

@ -0,0 +1,297 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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_InputUtils_h
#define mozilla_layers_InputUtils_h
/**
* Defines a set of utility functions for generating input events
* to an APZC/APZCTM during APZ gtests.
*/
#include "APZTestCommon.h"
/* The InputReceiver template parameter used in the helper functions below needs
* to be a class that implements functions with the signatures:
* nsEventStatus ReceiveInputEvent(const InputData& aEvent,
* ScrollableLayerGuid* aGuid,
* uint64_t* aOutInputBlockId);
* void SetAllowedTouchBehavior(uint64_t aInputBlockId,
* const nsTArray<uint32_t>& aBehaviours);
* The classes that currently implement these are APZCTreeManager and
* TestAsyncPanZoomController. Using this template allows us to test individual
* APZC instances in isolation and also an entire APZ tree, while using the same
* code to dispatch input events.
*/
// 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.
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.
SingleTouchData
CreateSingleTouchData(int32_t aIdentifier, ScreenIntCoord aX, ScreenIntCoord aY)
{
return CreateSingleTouchData(aIdentifier, ScreenIntPoint(aX, aY));
}
PinchGestureInput
CreatePinchGestureInput(PinchGestureInput::PinchGestureType aType,
const ScreenIntPoint& aFocus,
float aCurrentSpan, float aPreviousSpan)
{
ParentLayerPoint localFocus(aFocus.x, aFocus.y);
PinchGestureInput result(aType, 0, TimeStamp(), localFocus,
aCurrentSpan, aPreviousSpan, 0);
result.mFocusPoint = aFocus;
return result;
}
template<class InputReceiver>
void
SetDefaultAllowedTouchBehavior(const RefPtr<InputReceiver>& aTarget,
uint64_t aInputBlockId,
int touchPoints = 1)
{
nsTArray<uint32_t> defaultBehaviors;
// use the default value where everything is allowed
for (int i = 0; i < touchPoints; i++) {
defaultBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM
| mozilla::layers::AllowedTouchBehavior::DOUBLE_TAP_ZOOM);
}
aTarget->SetAllowedTouchBehavior(aInputBlockId, defaultBehaviors);
}
MultiTouchInput
CreateMultiTouchInput(MultiTouchInput::MultiTouchType aType, TimeStamp aTime)
{
return MultiTouchInput(aType, MillisecondsSinceStartup(aTime), aTime, 0);
}
template<class InputReceiver>
nsEventStatus
TouchDown(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
{
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, aTime);
mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint));
return aTarget->ReceiveInputEvent(mti, nullptr, aOutInputBlockId);
}
template<class InputReceiver>
nsEventStatus
TouchMove(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeStamp aTime)
{
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, aTime);
mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint));
return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
}
template<class InputReceiver>
nsEventStatus
TouchUp(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeStamp aTime)
{
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, aTime);
mti.mTouches.AppendElement(CreateSingleTouchData(0, aPoint));
return aTarget->ReceiveInputEvent(mti, nullptr, nullptr);
}
template<class InputReceiver>
void
PinchWithPinchInput(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aFocus,
const ScreenIntPoint& aSecondFocus, float aScale,
nsEventStatus (*aOutEventStatuses)[3] = nullptr)
{
nsEventStatus actualStatus = aTarget->ReceiveInputEvent(
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
aFocus, 10.0, 10.0),
nullptr);
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = actualStatus;
}
actualStatus = aTarget->ReceiveInputEvent(
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
aSecondFocus, 10.0 * aScale, 10.0),
nullptr);
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = actualStatus;
}
actualStatus = aTarget->ReceiveInputEvent(
CreatePinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
// note: negative values here tell APZC
// not to turn the pinch into a pan
aFocus, -1.0, -1.0),
nullptr);
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = actualStatus;
}
}
template<class InputReceiver>
void
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_eConsumeNoDefault
: nsEventStatus_eIgnore;
EXPECT_EQ(expectedStatus, statuses[0]);
EXPECT_EQ(expectedStatus, statuses[1]);
}
template<class InputReceiver>
void
PinchWithTouchInput(const RefPtr<InputReceiver>& aTarget,
const ScreenIntPoint& aFocus, float aScale,
int& inputId,
nsTArray<uint32_t>* aAllowedTouchBehaviors = nullptr,
nsEventStatus (*aOutEventStatuses)[4] = nullptr,
uint64_t* aOutInputBlockId = nullptr)
{
// 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.
float pinchLength = 100.0;
float pinchLengthScaled = pinchLength * aScale;
// 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;
}
MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus));
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus));
nsEventStatus status = aTarget->ReceiveInputEvent(mtiStart, aOutInputBlockId);
if (aOutEventStatuses) {
(*aOutEventStatuses)[0] = status;
}
if (aAllowedTouchBehaviors) {
EXPECT_EQ(2UL, aAllowedTouchBehaviors->Length());
aTarget->SetAllowedTouchBehavior(*aOutInputBlockId, *aAllowedTouchBehaviors);
} else if (gfxPrefs::TouchActionEnabled()) {
SetDefaultAllowedTouchBehavior(aTarget, *aOutInputBlockId, 2);
}
MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus.x - pinchLength, aFocus.y));
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus.x + pinchLength, aFocus.y));
status = aTarget->ReceiveInputEvent(mtiMove1, nullptr);
if (aOutEventStatuses) {
(*aOutEventStatuses)[1] = status;
}
MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus.x - pinchLengthScaled, aFocus.y));
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus.x + pinchLengthScaled, aFocus.y));
status = aTarget->ReceiveInputEvent(mtiMove2, nullptr);
if (aOutEventStatuses) {
(*aOutEventStatuses)[2] = status;
}
MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, aFocus.x - pinchLengthScaled, aFocus.y));
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, aFocus.x + pinchLengthScaled, aFocus.y));
status = aTarget->ReceiveInputEvent(mtiEnd, nullptr);
if (aOutEventStatuses) {
(*aOutEventStatuses)[3] = status;
}
inputId += 2;
}
template<class InputReceiver>
void
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, inputId, aAllowedTouchBehaviors, &statuses);
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>
nsEventStatus
Wheel(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
const ScreenPoint& aDelta, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
{
ScrollWheelInput input(MillisecondsSinceStartup(aTime), aTime, 0,
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
aPoint, aDelta.x, aDelta.y, false);
return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
}
template<class InputReceiver>
nsEventStatus
SmoothWheel(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
const ScreenPoint& aDelta, TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
{
ScrollWheelInput input(MillisecondsSinceStartup(aTime), aTime, 0,
ScrollWheelInput::SCROLLMODE_SMOOTH, ScrollWheelInput::SCROLLDELTA_LINE,
aPoint, aDelta.x, aDelta.y, false);
return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
}
template<class InputReceiver>
nsEventStatus
MouseDown(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
{
MouseInput input(MouseInput::MOUSE_DOWN, MouseInput::ButtonType::LEFT_BUTTON,
0, 0, aPoint, MillisecondsSinceStartup(aTime), aTime, 0);
return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
}
template<class InputReceiver>
nsEventStatus
MouseMove(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
{
MouseInput input(MouseInput::MOUSE_MOVE, MouseInput::ButtonType::LEFT_BUTTON,
0, 0, aPoint, MillisecondsSinceStartup(aTime), aTime, 0);
return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
}
template<class InputReceiver>
nsEventStatus
MouseUp(const RefPtr<InputReceiver>& aTarget, const ScreenIntPoint& aPoint,
TimeStamp aTime, uint64_t* aOutInputBlockId = nullptr)
{
MouseInput input(MouseInput::MOUSE_UP, MouseInput::ButtonType::LEFT_BUTTON,
0, 0, aPoint, MillisecondsSinceStartup(aTime), aTime, 0);
return aTarget->ReceiveInputEvent(input, nullptr, aOutInputBlockId);
}
#endif // mozilla_layers_InputUtils_h

View file

@ -0,0 +1,356 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h"
#include "APZTestCommon.h"
#include "InputUtils.h"
TEST_F(APZCBasicTester, Overzoom) {
// the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
FrameMetrics fm;
fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
fm.SetScrollOffset(CSSPoint(10, 0));
fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
fm.SetIsRootContent(true);
apzc->SetFrameMetrics(fm);
MakeApzcZoomable();
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true);
fm = apzc->GetFrameMetrics();
EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
// bug 936721 - PGO builds introduce rounding error so
// use a fuzzy match instead
EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5);
EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5);
}
TEST_F(APZCBasicTester, SimpleTransform) {
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(ParentLayerPoint(), pointOut);
EXPECT_EQ(AsyncTransform(), viewTransformOut);
}
TEST_F(APZCBasicTester, ComplexTransform) {
// This test assumes there is a page that gets rendered to
// two layers. In CSS pixels, the first layer is 50x50 and
// the second layer is 25x50. The widget scale factor is 3.0
// and the presShell resolution is 2.0. Therefore, these layers
// end up being 300x300 and 150x300 in layer pixels.
//
// The second (child) layer has an additional CSS transform that
// stretches it by 2.0 on the x-axis. Therefore, after applying
// CSS transforms, the two layers are the same size in screen
// pixels.
//
// The screen itself is 24x24 in screen pixels (therefore 4x4 in
// CSS pixels). The displayport is 1 extra CSS pixel on all
// sides.
RefPtr<TestAsyncPanZoomController> childApzc =
new TestAsyncPanZoomController(0, mcc, tm);
const char* layerTreeSyntax = "c(c)";
// LayerID 0 1
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0, 0, 300, 300)),
nsIntRegion(IntRect(0, 0, 150, 300)),
};
Matrix4x4 transforms[] = {
Matrix4x4(),
Matrix4x4(),
};
transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer
transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer
nsTArray<RefPtr<Layer> > layers;
RefPtr<LayerManager> lm;
RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
ScrollMetadata metadata;
FrameMetrics& metrics = metadata.GetMetrics();
metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
metrics.SetScrollOffset(CSSPoint(10, 10));
metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
metrics.SetPresShellResolution(2.0f);
metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
metrics.SetScrollId(FrameMetrics::START_SCROLL_ID);
ScrollMetadata childMetadata = metadata;
FrameMetrics& childMetrics = childMetadata.GetMetrics();
childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1);
layers[0]->SetScrollMetadata(metadata);
layers[1]->SetScrollMetadata(childMetadata);
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
// Both the parent and child layer should behave exactly the same here, because
// the CSS transform on the child layer does not affect the SampleContentTransformForFrame code
// initial transform
apzc->SetFrameMetrics(metrics);
apzc->NotifyLayersUpdated(metadata, true, true);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
childApzc->SetFrameMetrics(childMetrics);
childApzc->NotifyLayersUpdated(childMetadata, true, true);
childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
// do an async scroll by 5 pixels and check the transform
metrics.ScrollBy(CSSPoint(5, 0));
apzc->SetFrameMetrics(metrics);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
childMetrics.ScrollBy(CSSPoint(5, 0));
childApzc->SetFrameMetrics(childMetrics);
childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
// do an async zoom of 1.5x and check the transform
metrics.ZoomBy(1.5f);
apzc->SetFrameMetrics(metrics);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
childMetrics.ZoomBy(1.5f);
childApzc->SetFrameMetrics(childMetrics);
childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
childApzc->Destroy();
}
TEST_F(APZCBasicTester, Fling) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
int touchStart = 50;
int touchEnd = 10;
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
// Fling down. Each step scroll further down
Pan(apzc, touchStart, touchEnd);
ParentLayerPoint lastPoint;
for (int i = 1; i < 50; i+=1) {
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(1));
EXPECT_GT(pointOut.y, lastPoint.y);
lastPoint = pointOut;
}
}
TEST_F(APZCBasicTester, FlingIntoOverscroll) {
// Enable overscrolling.
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
// Scroll down by 25 px. Don't fling for simplicity.
ApzcPanNoFling(apzc, 50, 25);
// Now scroll back up by 20px, this time flinging after.
// The fling should cover the remaining 5 px of room to scroll, then
// go into overscroll, and finally snap-back to recover from overscroll.
Pan(apzc, 25, 45);
const TimeDuration increment = TimeDuration::FromMilliseconds(1);
bool reachedOverscroll = false;
bool recoveredFromOverscroll = false;
while (apzc->AdvanceAnimations(mcc->Time())) {
if (!reachedOverscroll && apzc->IsOverscrolled()) {
reachedOverscroll = true;
}
if (reachedOverscroll && !apzc->IsOverscrolled()) {
recoveredFromOverscroll = true;
}
mcc->AdvanceBy(increment);
}
EXPECT_TRUE(reachedOverscroll);
EXPECT_TRUE(recoveredFromOverscroll);
}
TEST_F(APZCBasicTester, PanningTransformNotifications) {
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
// Scroll down by 25 px. Ensure we only get one set of
// state change notifications.
//
// Then, scroll back up by 20px, this time flinging after.
// The fling should cover the remaining 5 px of room to scroll, then
// go into overscroll, and finally snap-back to recover from overscroll.
// Again, ensure we only get one set of state change notifications for
// this entire procedure.
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
EXPECT_CALL(check, Call("Simple pan"));
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartTouch,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformBegin,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartPanning,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eEndTouch,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformEnd,_)).Times(1);
EXPECT_CALL(check, Call("Complex pan"));
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartTouch,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformBegin,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eStartPanning,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eEndTouch,_)).Times(1);
EXPECT_CALL(*mcc, NotifyAPZStateChange(_,GeckoContentController::APZStateChange::eTransformEnd,_)).Times(1);
EXPECT_CALL(check, Call("Done"));
}
check.Call("Simple pan");
ApzcPanNoFling(apzc, 50, 25);
check.Call("Complex pan");
Pan(apzc, 25, 45);
apzc->AdvanceAnimationsUntilEnd();
check.Call("Done");
}
void APZCBasicTester::PanIntoOverscroll()
{
int touchStart = 500;
int touchEnd = 10;
Pan(apzc, touchStart, touchEnd);
EXPECT_TRUE(apzc->IsOverscrolled());
}
void APZCBasicTester::TestOverscroll()
{
// Pan sufficiently to hit overscroll behavior
PanIntoOverscroll();
// Check that we recover from overscroll via an animation.
ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
}
TEST_F(APZCBasicTester, OverScrollPanning) {
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
TestOverscroll();
}
// Tests that an overscroll animation doesn't trigger an assertion failure
// in the case where a sample has a velocity of zero.
TEST_F(APZCBasicTester, OverScroll_Bug1152051a) {
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
// Doctor the prefs to make the velocity zero at the end of the first sample.
// This ensures our incoming velocity to the overscroll animation is
// a round(ish) number, 4.9 (that being the distance of the pan before
// overscroll, which is 500 - 10 = 490 pixels, divided by the duration of
// the pan, which is 100 ms).
SCOPED_GFX_PREF(APZFlingFriction, float, 0);
// To ensure the velocity after the first sample is 0, set the spring
// stiffness to the incoming velocity (4.9) divided by the overscroll
// (400 pixels) times the step duration (1 ms).
SCOPED_GFX_PREF(APZOverscrollSpringStiffness, float, 0.01225f);
TestOverscroll();
}
// Tests that ending an overscroll animation doesn't leave around state that
// confuses the next overscroll animation.
TEST_F(APZCBasicTester, OverScroll_Bug1152051b) {
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
SCOPED_GFX_PREF(APZOverscrollStopDistanceThreshold, float, 0.1f);
// Pan sufficiently to hit overscroll behavior
PanIntoOverscroll();
// Sample animations once, to give the fling animation started on touch-up
// a chance to realize it's overscrolled, and schedule a call to
// HandleFlingOverscroll().
SampleAnimationOnce();
// This advances the time and runs the HandleFlingOverscroll task scheduled in
// the previous call, which starts an overscroll animation. It then samples
// the overscroll animation once, to get it to initialize the first overscroll
// sample.
SampleAnimationOnce();
// Do a touch-down to cancel the overscroll animation, and then a touch-up
// to schedule a new one since we're still overscrolled. We don't pan because
// panning can trigger functions that clear the overscroll animation state
// in other ways.
uint64_t blockId;
nsEventStatus status = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &blockId);
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(apzc, blockId);
}
TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time());
// Sample the second overscroll animation to its end.
// If the ending of the first overscroll animation fails to clear state
// properly, this will assert.
ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
}
TEST_F(APZCBasicTester, OverScrollAbort) {
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
// Pan sufficiently to hit overscroll behavior
int touchStart = 500;
int touchEnd = 10;
Pan(apzc, touchStart, touchEnd);
EXPECT_TRUE(apzc->IsOverscrolled());
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
// This sample call will run to the end of the fling animation
// and will schedule the overscroll animation.
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(10000));
EXPECT_TRUE(apzc->IsOverscrolled());
// At this point, we have an active overscroll animation.
// Check that cancelling the animation clears the overscroll.
apzc->CancelAnimation();
EXPECT_FALSE(apzc->IsOverscrolled());
apzc->AssertStateIsReset();
}
TEST_F(APZCBasicTester, OverScrollPanningAbort) {
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
// Pan sufficiently to hit overscroll behaviour. Keep the finger down so
// the pan does not end.
int touchStart = 500;
int touchEnd = 10;
Pan(apzc, touchStart, touchEnd, true); // keep finger down
EXPECT_TRUE(apzc->IsOverscrolled());
// Check that calling CancelAnimation() while the user is still panning
// (and thus no fling or snap-back animation has had a chance to start)
// clears the overscroll.
apzc->CancelAnimation();
EXPECT_FALSE(apzc->IsOverscrolled());
apzc->AssertStateIsReset();
}

View file

@ -0,0 +1,272 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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"
class APZEventRegionsTester : public APZCTreeManagerTester {
protected:
UniquePtr<ScopedLayerTreeRegistration> registration;
TestAsyncPanZoomController* rootApzc;
void CreateEventRegionsLayerTree1() {
const char* layerTreeSyntax = "c(tt)";
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 200, 200)), // root
nsIntRegion(IntRect(0, 0, 100, 200)), // left half
nsIntRegion(IntRect(0, 100, 200, 100)), // bottom half
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2);
SetScrollHandoff(layers[1], root);
SetScrollHandoff(layers[2], root);
// Set up the event regions over a 200x200 area. The root layer has the
// whole 200x200 as the hit region; layers[1] has the left half and
// layers[2] has the bottom half. The bottom-left 100x100 area is also
// in the d-t-c region for both layers[1] and layers[2] (but layers[2] is
// on top so it gets the events by default if the main thread doesn't
// respond).
EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
root->SetEventRegions(regions);
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 100, 100, 100));
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 200));
layers[1]->SetEventRegions(regions);
regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
layers[2]->SetEventRegions(regions);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
rootApzc = ApzcOf(root);
}
void CreateEventRegionsLayerTree2() {
const char* layerTreeSyntax = "c(t)";
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 100, 500)),
nsIntRegion(IntRect(0, 150, 100, 100)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
// Set up the event regions so that the child thebes layer is positioned far
// away from the scrolling container layer.
EventRegions regions(nsIntRegion(IntRect(0, 0, 100, 100)));
root->SetEventRegions(regions);
regions.mHitRegion = nsIntRegion(IntRect(0, 150, 100, 100));
layers[1]->SetEventRegions(regions);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
rootApzc = ApzcOf(root);
}
void CreateObscuringLayerTree() {
const char* layerTreeSyntax = "c(c(t)t)";
// LayerID 0 1 2 3
// 0 is the root.
// 1 is a parent scrollable layer.
// 2 is a child scrollable layer.
// 3 is the Obscurer, who ruins everything.
nsIntRegion layerVisibleRegions[] = {
// x coordinates are uninteresting
nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200]
nsIntRegion(IntRect(0, 0, 200, 200)), // [0, 200]
nsIntRegion(IntRect(0, 100, 200, 50)), // [100, 150]
nsIntRegion(IntRect(0, 100, 200, 100)) // [100, 200]
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 200, 300));
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 200, 100));
SetScrollHandoff(layers[2], layers[1]);
SetScrollHandoff(layers[1], root);
EventRegions regions(nsIntRegion(IntRect(0, 0, 200, 200)));
root->SetEventRegions(regions);
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 200, 300));
layers[1]->SetEventRegions(regions);
regions.mHitRegion = nsIntRegion(IntRect(0, 100, 200, 100));
layers[2]->SetEventRegions(regions);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
rootApzc = ApzcOf(root);
}
void CreateBug1119497LayerTree() {
const char* layerTreeSyntax = "c(tt)";
// LayerID 0 12
// 0 is the root and has an APZC
// 1 is behind 2 and has an APZC
// 2 entirely covers 1 and should take all the input events, but has no APZC
// so hits to 2 should go to to the root APZC
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 0, 100, 100)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
}
void CreateBug1117712LayerTree() {
const char* layerTreeSyntax = "c(c(t)t)";
// LayerID 0 1 2 3
// 0 is the root
// 1 is a container layer whose sole purpose to make a non-empty ancestor
// transform for 2, so that 2's screen-to-apzc and apzc-to-gecko
// transforms are different from 3's.
// 2 is a small layer that is the actual target
// 3 is a big layer obscuring 2 with a dispatch-to-content region
nsIntRegion layerVisibleRegions[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 0, 0, 0)),
nsIntRegion(IntRect(0, 0, 10, 10)),
nsIntRegion(IntRect(0, 0, 100, 100)),
};
Matrix4x4 layerTransforms[] = {
Matrix4x4(),
Matrix4x4::Translation(50, 0, 0),
Matrix4x4(),
Matrix4x4(),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, layerTransforms, lm, layers);
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 10, 10));
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
SetScrollHandoff(layers[3], layers[2]);
EventRegions regions(nsIntRegion(IntRect(0, 0, 10, 10)));
layers[2]->SetEventRegions(regions);
regions.mHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
regions.mDispatchToContentHitRegion = nsIntRegion(IntRect(0, 0, 100, 100));
layers[3]->SetEventRegions(regions);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
}
};
TEST_F(APZEventRegionsTester, HitRegionImmediateResponse) {
CreateEventRegionsLayerTree1();
TestAsyncPanZoomController* root = ApzcOf(layers[0]);
TestAsyncPanZoomController* left = ApzcOf(layers[1]);
TestAsyncPanZoomController* bottom = ApzcOf(layers[2]);
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped on left"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped on bottom"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, root->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped on root"));
EXPECT_CALL(check, Call("Tap pending on d-t-c region"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, bottom->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped on bottom again"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, left->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped on left this time"));
}
TimeDuration tapDuration = TimeDuration::FromMilliseconds(100);
// Tap in the exposed hit regions of each of the layers once and ensure
// the clicks are dispatched right away
Tap(manager, ScreenIntPoint(10, 10), tapDuration);
mcc->RunThroughDelayedTasks(); // this runs the tap event
check.Call("Tapped on left");
Tap(manager, ScreenIntPoint(110, 110), tapDuration);
mcc->RunThroughDelayedTasks(); // this runs the tap event
check.Call("Tapped on bottom");
Tap(manager, ScreenIntPoint(110, 10), tapDuration);
mcc->RunThroughDelayedTasks(); // this runs the tap event
check.Call("Tapped on root");
// Now tap on the dispatch-to-content region where the layers overlap
Tap(manager, ScreenIntPoint(10, 110), tapDuration);
mcc->RunThroughDelayedTasks(); // this runs the main-thread timeout
check.Call("Tap pending on d-t-c region");
mcc->RunThroughDelayedTasks(); // this runs the tap event
check.Call("Tapped on bottom again");
// Now let's do that again, but simulate a main-thread response
uint64_t inputBlockId = 0;
Tap(manager, ScreenIntPoint(10, 110), tapDuration, nullptr, &inputBlockId);
nsTArray<ScrollableLayerGuid> targets;
targets.AppendElement(left->GetGuid());
manager->SetTargetAPZC(inputBlockId, targets);
while (mcc->RunThroughDelayedTasks()); // this runs the tap event
check.Call("Tapped on left this time");
}
TEST_F(APZEventRegionsTester, HitRegionAccumulatesChildren) {
CreateEventRegionsLayerTree2();
// Tap in the area of the child layer that's not directly included in the
// parent layer's hit region. Verify that it comes out of the APZC's
// content controller, which indicates the input events got routed correctly
// to the APZC.
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, _, rootApzc->GetGuid(), _)).Times(1);
Tap(manager, ScreenIntPoint(10, 160), TimeDuration::FromMilliseconds(100));
}
TEST_F(APZEventRegionsTester, Obscuration) {
CreateObscuringLayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TestAsyncPanZoomController* parent = ApzcOf(layers[1]);
TestAsyncPanZoomController* child = ApzcOf(layers[2]);
ApzcPanNoFling(parent, 75, 25);
HitTestResult result;
RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 75), &result);
EXPECT_EQ(child, hit.get());
EXPECT_EQ(HitTestResult::HitLayer, result);
}
TEST_F(APZEventRegionsTester, Bug1119497) {
CreateBug1119497LayerTree();
HitTestResult result;
RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(ScreenPoint(50, 50), &result);
// We should hit layers[2], so |result| will be HitLayer but there's no
// actual APZC on layers[2], so it will be the APZC of the root layer.
EXPECT_EQ(ApzcOf(layers[0]), hit.get());
EXPECT_EQ(HitTestResult::HitLayer, result);
}
TEST_F(APZEventRegionsTester, Bug1117712) {
CreateBug1117712LayerTree();
TestAsyncPanZoomController* apzc2 = ApzcOf(layers[2]);
// These touch events should hit the dispatch-to-content region of layers[3]
// and so get queued with that APZC as the tentative target.
uint64_t inputBlockId = 0;
Tap(manager, ScreenIntPoint(55, 5), TimeDuration::FromMilliseconds(100), nullptr, &inputBlockId);
// But now we tell the APZ that really it hit layers[2], and expect the tap
// to be delivered at the correct coordinates.
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(55, 5), 0, apzc2->GetGuid(), _)).Times(1);
nsTArray<ScrollableLayerGuid> targets;
targets.AppendElement(apzc2->GetGuid());
manager->SetTargetAPZC(inputBlockId, targets);
}

View file

@ -0,0 +1,638 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h"
#include "APZTestCommon.h"
class APZCGestureDetectorTester : public APZCBasicTester {
public:
APZCGestureDetectorTester()
: APZCBasicTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
{
}
protected:
FrameMetrics GetPinchableFrameMetrics()
{
FrameMetrics fm;
fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
fm.SetScrollOffset(CSSPoint(300, 300));
fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
// APZC only allows zooming on the root scrollable frame.
fm.SetIsRootContent(true);
// the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
return fm;
}
};
TEST_F(APZCGestureDetectorTester, Pan_After_Pinch) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
apzc->SetFrameMetrics(originalMetrics);
MakeApzcZoomable();
// Test parameters
float zoomAmount = 1.25;
float pinchLength = 100.0;
float pinchLengthScaled = pinchLength * zoomAmount;
int focusX = 250;
int focusY = 300;
int panDistance = 20;
int firstFingerId = 0;
int secondFingerId = firstFingerId + 1;
// Put fingers down
MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX, focusY));
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// Spread fingers out to enter the pinch state
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLength, focusY));
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLength, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// Do the actual pinch of 1.25x
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// Verify that the zoom changed, just to make sure our code above did what it
// was supposed to.
FrameMetrics zoomedMetrics = apzc->GetFrameMetrics();
float newZoom = zoomedMetrics.GetZoom().ToScaleFactor().scale;
EXPECT_EQ(originalMetrics.GetZoom().ToScaleFactor().scale * zoomAmount, newZoom);
// Now we lift one finger...
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, focusX + pinchLengthScaled, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// ... and pan with the remaining finger. This pan just breaks through the
// distance threshold.
focusY += 40;
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// This one does an actual pan of 20 pixels
focusY += panDistance;
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// Lift the remaining finger
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, focusX - pinchLengthScaled, focusY));
apzc->ReceiveInputEvent(mti, nullptr);
// Verify that we scrolled
FrameMetrics finalMetrics = apzc->GetFrameMetrics();
EXPECT_EQ(zoomedMetrics.GetScrollOffset().y - (panDistance / newZoom), finalMetrics.GetScrollOffset().y);
// Clear out any remaining fling animation and pending tasks
apzc->AdvanceAnimationsUntilEnd();
while (mcc->RunThroughDelayedTasks());
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, Pan_With_Tap) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
apzc->SetFrameMetrics(originalMetrics);
// Making the APZC zoomable isn't really needed for the correct operation of
// this test, but it could help catch regressions where we accidentally enter
// a pinch state.
MakeApzcZoomable();
// Test parameters
int touchX = 250;
int touchY = 300;
int panDistance = 20;
int firstFingerId = 0;
int secondFingerId = firstFingerId + 1;
// Put finger down
MultiTouchInput mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Start a pan, break through the threshold
touchY += 40;
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Do an actual pan for a bit
touchY += panDistance;
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Put a second finger down
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Lift the second finger
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(secondFingerId, touchX + 10, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Bust through the threshold again
touchY += 40;
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Do some more actual panning
touchY += panDistance;
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Lift the first finger
mti = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
mti.mTouches.AppendElement(CreateSingleTouchData(firstFingerId, touchX, touchY));
apzc->ReceiveInputEvent(mti, nullptr);
// Verify that we scrolled
FrameMetrics finalMetrics = apzc->GetFrameMetrics();
float zoom = finalMetrics.GetZoom().ToScaleFactor().scale;
EXPECT_EQ(originalMetrics.GetScrollOffset().y - (panDistance * 2 / zoom), finalMetrics.GetScrollOffset().y);
// Clear out any remaining fling animation and pending tasks
apzc->AdvanceAnimationsUntilEnd();
while (mcc->RunThroughDelayedTasks());
apzc->AssertStateIsReset();
}
class APZCFlingStopTester : public APZCGestureDetectorTester {
protected:
// Start a fling, and then tap while the fling is ongoing. When
// aSlow is false, the tap will happen while the fling is at a
// high velocity, and we check that the tap doesn't trigger sending a tap
// to content. If aSlow is true, the tap will happen while the fling
// is at a slow velocity, and we check that the tap does trigger sending
// a tap to content. See bug 1022956.
void DoFlingStopTest(bool aSlow) {
int touchStart = 50;
int touchEnd = 10;
// Start the fling down.
Pan(apzc, touchStart, touchEnd);
// The touchstart from the pan will leave some cancelled tasks in the queue, clear them out
// If we want to tap while the fling is fast, let the fling advance for 10ms only. If we want
// the fling to slow down more, advance to 2000ms. These numbers may need adjusting if our
// friction and threshold values change, but they should be deterministic at least.
int timeDelta = aSlow ? 2000 : 10;
int tapCallsExpected = aSlow ? 2 : 1;
// Advance the fling animation by timeDelta milliseconds.
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut, TimeDuration::FromMilliseconds(timeDelta));
// Deliver a tap to abort the fling. Ensure that we get a SingleTap
// call out of it if and only if the fling is slow.
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, _, 0, apzc->GetGuid(), _)).Times(tapCallsExpected);
Tap(apzc, ScreenIntPoint(10, 10), 0);
while (mcc->RunThroughDelayedTasks());
// Deliver another tap, to make sure that taps are flowing properly once
// the fling is aborted.
Tap(apzc, ScreenIntPoint(100, 100), 0);
while (mcc->RunThroughDelayedTasks());
// Verify that we didn't advance any further after the fling was aborted, in either case.
ParentLayerPoint finalPointOut;
apzc->SampleContentTransformForFrame(&viewTransformOut, finalPointOut);
EXPECT_EQ(pointOut.x, finalPointOut.x);
EXPECT_EQ(pointOut.y, finalPointOut.y);
apzc->AssertStateIsReset();
}
void DoFlingStopWithSlowListener(bool aPreventDefault) {
MakeApzcWaitForMainThread();
int touchStart = 50;
int touchEnd = 10;
uint64_t blockId = 0;
// Start the fling down.
Pan(apzc, touchStart, touchEnd, false, nullptr, nullptr, &blockId);
apzc->ConfirmTarget(blockId);
apzc->ContentReceivedInputBlock(blockId, false);
// Sample the fling a couple of times to ensure it's going.
ParentLayerPoint point, finalPoint;
AsyncTransform viewTransform;
apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
apzc->SampleContentTransformForFrame(&viewTransform, finalPoint, TimeDuration::FromMilliseconds(10));
EXPECT_GT(finalPoint.y, point.y);
// Now we put our finger down to stop the fling
TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &blockId);
// Re-sample to make sure it hasn't moved
apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(10));
EXPECT_EQ(finalPoint.x, point.x);
EXPECT_EQ(finalPoint.y, point.y);
// respond to the touchdown that stopped the fling.
// even if we do a prevent-default on it, the animation should remain stopped.
apzc->ContentReceivedInputBlock(blockId, aPreventDefault);
// Verify the page hasn't moved
apzc->SampleContentTransformForFrame(&viewTransform, point, TimeDuration::FromMilliseconds(70));
EXPECT_EQ(finalPoint.x, point.x);
EXPECT_EQ(finalPoint.y, point.y);
// clean up
TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time());
apzc->AssertStateIsReset();
}
};
TEST_F(APZCFlingStopTester, FlingStop) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
DoFlingStopTest(false);
}
TEST_F(APZCFlingStopTester, FlingStopTap) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
DoFlingStopTest(true);
}
TEST_F(APZCFlingStopTester, FlingStopSlowListener) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
DoFlingStopWithSlowListener(false);
}
TEST_F(APZCFlingStopTester, FlingStopPreventDefault) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
DoFlingStopWithSlowListener(true);
}
TEST_F(APZCGestureDetectorTester, ShortPress) {
MakeApzcUnzoomable();
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
// This verifies that the single tap notification is sent after the
// touchup is fully processed. The ordering here is important.
EXPECT_CALL(check, Call("pre-tap"));
EXPECT_CALL(check, Call("post-tap"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
}
check.Call("pre-tap");
TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100));
check.Call("post-tap");
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, MediumPress) {
MakeApzcUnzoomable();
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
// This verifies that the single tap notification is sent after the
// touchup is fully processed. The ordering here is important.
EXPECT_CALL(check, Call("pre-tap"));
EXPECT_CALL(check, Call("post-tap"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
}
check.Call("pre-tap");
TapAndCheckStatus(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(400));
check.Call("post-tap");
apzc->AssertStateIsReset();
}
class APZCLongPressTester : public APZCGestureDetectorTester {
protected:
void DoLongPressTest(uint32_t aBehavior) {
MakeApzcUnzoomable();
uint64_t blockId = 0;
nsEventStatus status = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &blockId);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
// SetAllowedTouchBehavior() must be called after sending touch-start.
nsTArray<uint32_t> allowedTouchBehaviors;
allowedTouchBehaviors.AppendElement(aBehavior);
apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
}
// Have content "respond" to the touchstart
apzc->ContentReceivedInputBlock(blockId, false);
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
EXPECT_CALL(check, Call("preHandleLongTap"));
blockId++;
EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), blockId)).Times(1);
EXPECT_CALL(check, Call("postHandleLongTap"));
EXPECT_CALL(check, Call("preHandleLongTapUp"));
EXPECT_CALL(*mcc, HandleTap(TapType::eLongTapUp, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("postHandleLongTapUp"));
}
// Manually invoke the longpress while the touch is currently down.
check.Call("preHandleLongTap");
mcc->RunThroughDelayedTasks();
check.Call("postHandleLongTap");
// Dispatching the longpress event starts a new touch block, which
// needs a new content response and also has a pending timeout task
// in the queue. Deal with those here. We do the content response first
// with preventDefault=false, and then we run the timeout task which
// "loses the race" and does nothing.
apzc->ContentReceivedInputBlock(blockId, false);
mcc->AdvanceByMillis(1000);
// Finally, simulate lifting the finger. Since the long-press wasn't
// prevent-defaulted, we should get a long-tap-up event.
check.Call("preHandleLongTapUp");
status = TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time());
mcc->RunThroughDelayedTasks();
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
check.Call("postHandleLongTapUp");
apzc->AssertStateIsReset();
}
void DoLongPressPreventDefaultTest(uint32_t aBehavior) {
MakeApzcUnzoomable();
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
int touchX = 10,
touchStartY = 10,
touchEndY = 50;
uint64_t blockId = 0;
nsEventStatus status = TouchDown(apzc, ScreenIntPoint(touchX, touchStartY), mcc->Time(), &blockId);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
// SetAllowedTouchBehavior() must be called after sending touch-start.
nsTArray<uint32_t> allowedTouchBehaviors;
allowedTouchBehaviors.AppendElement(aBehavior);
apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors);
}
// Have content "respond" to the touchstart
apzc->ContentReceivedInputBlock(blockId, false);
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
EXPECT_CALL(check, Call("preHandleLongTap"));
blockId++;
EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, LayoutDevicePoint(touchX, touchStartY), 0, apzc->GetGuid(), blockId)).Times(1);
EXPECT_CALL(check, Call("postHandleLongTap"));
}
// Manually invoke the longpress while the touch is currently down.
check.Call("preHandleLongTap");
mcc->RunThroughDelayedTasks();
check.Call("postHandleLongTap");
// There should be a TimeoutContentResponse task in the queue still,
// waiting for the response from the longtap event dispatched above.
// Send the signal that content has handled the long-tap, and then run
// the timeout task (it will be a no-op because the content "wins" the
// race. This takes the place of the "contextmenu" event.
apzc->ContentReceivedInputBlock(blockId, true);
mcc->AdvanceByMillis(1000);
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(touchX, touchEndY), ScreenSize(0, 0), 0, 0));
status = apzc->ReceiveInputEvent(mti, nullptr);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(touchX, touchEndY), 0, apzc->GetGuid(), _)).Times(0);
status = TouchUp(apzc, ScreenIntPoint(touchX, touchEndY), mcc->Time());
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, status);
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(ParentLayerPoint(), pointOut);
EXPECT_EQ(AsyncTransform(), viewTransformOut);
apzc->AssertStateIsReset();
}
};
TEST_F(APZCLongPressTester, LongPress) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
DoLongPressTest(mozilla::layers::AllowedTouchBehavior::NONE);
}
TEST_F(APZCLongPressTester, LongPressWithTouchAction) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
DoLongPressTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
}
TEST_F(APZCLongPressTester, LongPressPreventDefault) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::NONE);
}
TEST_F(APZCLongPressTester, LongPressPreventDefaultWithTouchAction) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
DoLongPressPreventDefaultTest(mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN
| mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
}
TEST_F(APZCGestureDetectorTester, DoubleTap) {
MakeApzcWaitForMainThread();
MakeApzcZoomable();
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(0);
EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
uint64_t blockIds[2];
DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds);
// responses to the two touchstarts
apzc->ContentReceivedInputBlock(blockIds[0], false);
apzc->ContentReceivedInputBlock(blockIds[1], false);
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, DoubleTapNotZoomable) {
MakeApzcWaitForMainThread();
MakeApzcUnzoomable();
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
EXPECT_CALL(*mcc, HandleTap(TapType::eSecondTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(0);
uint64_t blockIds[2];
DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds);
// responses to the two touchstarts
apzc->ContentReceivedInputBlock(blockIds[0], false);
apzc->ContentReceivedInputBlock(blockIds[1], false);
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultFirstOnly) {
MakeApzcWaitForMainThread();
MakeApzcZoomable();
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(0);
uint64_t blockIds[2];
DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds);
// responses to the two touchstarts
apzc->ContentReceivedInputBlock(blockIds[0], true);
apzc->ContentReceivedInputBlock(blockIds[1], false);
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, DoubleTapPreventDefaultBoth) {
MakeApzcWaitForMainThread();
MakeApzcZoomable();
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(0);
EXPECT_CALL(*mcc, HandleTap(TapType::eDoubleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(0);
uint64_t blockIds[2];
DoubleTapAndCheckStatus(apzc, ScreenIntPoint(10, 10), &blockIds);
// responses to the two touchstarts
apzc->ContentReceivedInputBlock(blockIds[0], true);
apzc->ContentReceivedInputBlock(blockIds[1], true);
apzc->AssertStateIsReset();
}
// Test for bug 947892
// We test whether we dispatch tap event when the tap is followed by pinch.
TEST_F(APZCGestureDetectorTester, TapFollowedByPinch) {
MakeApzcZoomable();
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100));
int inputId = 0;
MultiTouchInput mti;
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
apzc->ReceiveInputEvent(mti, nullptr);
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
apzc->ReceiveInputEvent(mti, nullptr);
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, TapFollowedByMultipleTouches) {
MakeApzcZoomable();
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100));
int inputId = 0;
MultiTouchInput mti;
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
apzc->ReceiveInputEvent(mti, nullptr);
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
apzc->ReceiveInputEvent(mti, nullptr);
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_END, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(inputId, ParentLayerPoint(20, 20), ScreenSize(0, 0), 0, 0));
mti.mTouches.AppendElement(SingleTouchData(inputId + 1, ParentLayerPoint(10, 10), ScreenSize(0, 0), 0, 0));
apzc->ReceiveInputEvent(mti, nullptr);
apzc->AssertStateIsReset();
}
TEST_F(APZCGestureDetectorTester, LongPressInterruptedByWheel) {
// Since we try to allow concurrent input blocks of different types to
// co-exist, the wheel block shouldn't interrupt the long-press detection.
// But more importantly, this shouldn't crash, which is what it did at one
// point in time.
EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(1);
uint64_t touchBlockId = 0;
uint64_t wheelBlockId = 0;
nsEventStatus status = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time(), &touchBlockId);
if (gfxPrefs::TouchActionEnabled() && status != nsEventStatus_eConsumeNoDefault) {
SetDefaultAllowedTouchBehavior(apzc, touchBlockId);
}
mcc->AdvanceByMillis(10);
Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId);
EXPECT_NE(touchBlockId, wheelBlockId);
mcc->AdvanceByMillis(1000);
}
TEST_F(APZCGestureDetectorTester, TapTimeoutInterruptedByWheel) {
// In this test, even though the wheel block comes right after the tap, the
// tap should still be dispatched because it completes fully before the wheel
// block arrived.
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(10, 10), 0, apzc->GetGuid(), _)).Times(1);
// We make the APZC zoomable so the gesture detector needs to wait to
// distinguish between tap and double-tap. During that timeout is when we
// insert the wheel event.
MakeApzcZoomable();
uint64_t touchBlockId = 0;
uint64_t wheelBlockId = 0;
Tap(apzc, ScreenIntPoint(10, 10), TimeDuration::FromMilliseconds(100),
nullptr, &touchBlockId);
mcc->AdvanceByMillis(10);
Wheel(apzc, ScreenIntPoint(10, 10), ScreenPoint(0, -10), mcc->Time(), &wheelBlockId);
EXPECT_NE(touchBlockId, wheelBlockId);
while (mcc->RunThroughDelayedTasks());
}

View file

@ -0,0 +1,578 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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"
class APZHitTestingTester : public APZCTreeManagerTester {
protected:
ScreenToParentLayerMatrix4x4 transformToApzc;
ParentLayerToScreenMatrix4x4 transformToGecko;
already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) {
RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr);
if (hit) {
transformToApzc = manager->GetScreenToApzcTransform(hit.get());
transformToGecko = manager->GetApzcToGeckoTransform(hit.get());
}
return hit.forget();
}
protected:
void CreateHitTesting1LayerTree() {
const char* layerTreeSyntax = "c(tttt)";
// LayerID 0 1234
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,100,100)),
nsIntRegion(IntRect(0,0,100,100)),
nsIntRegion(IntRect(10,10,20,20)),
nsIntRegion(IntRect(10,10,20,20)),
nsIntRegion(IntRect(5,5,20,20)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
}
void CreateHitTesting2LayerTree() {
const char* layerTreeSyntax = "c(tc(t))";
// LayerID 0 12 3
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,100,100)),
nsIntRegion(IntRect(10,10,40,40)),
nsIntRegion(IntRect(10,60,40,40)),
nsIntRegion(IntRect(10,60,40,40)),
};
Matrix4x4 transforms[] = {
Matrix4x4(),
Matrix4x4(),
Matrix4x4::Scaling(2, 1, 1),
Matrix4x4(),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80));
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80));
}
void DisableApzOn(Layer* aLayer) {
ScrollMetadata m = aLayer->GetScrollMetadata(0);
m.SetForceDisableApz(true);
aLayer->SetScrollMetadata(m);
}
void CreateComplexMultiLayerTree() {
const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))";
// LayerID 0 12 3 45 6 7 89
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,300,400)), // root(0)
nsIntRegion(IntRect(0,0,100,100)), // thebes(1) in top-left
nsIntRegion(IntRect(50,50,200,300)), // container(2) centered in root(0)
nsIntRegion(IntRect(50,50,200,300)), // thebes(3) fully occupying parent container(2)
nsIntRegion(IntRect(0,200,100,100)), // thebes(4) in bottom-left
nsIntRegion(IntRect(200,0,100,400)), // container(5) along the right 100px of root(0)
nsIntRegion(IntRect(200,0,100,200)), // container(6) taking up the top half of parent container(5)
nsIntRegion(IntRect(200,0,100,200)), // thebes(7) fully occupying parent container(6)
nsIntRegion(IntRect(200,200,100,100)), // thebes(8) in bottom-right (below (6))
nsIntRegion(IntRect(200,300,100,100)), // thebes(9) in bottom-right (below (8))
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1);
SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1);
SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2);
SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1);
SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3);
}
void CreateBug1148350LayerTree() {
const char* layerTreeSyntax = "c(t)";
// LayerID 0 1
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,200,200)),
nsIntRegion(IntRect(0,0,200,200)),
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
}
};
// A simple hit testing test that doesn't involve any transforms on layers.
TEST_F(APZHitTestingTester, HitTesting1) {
CreateHitTesting1LayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
// No APZC attached so hit testing will return no APZC at (20,20)
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20));
TestAsyncPanZoomController* nullAPZC = nullptr;
EXPECT_EQ(nullAPZC, hit.get());
EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
uint32_t paintSequenceNumber = 0;
// Now we have a root APZC that will match the page
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID);
manager->UpdateHitTestingTree(0, root, false, 0, paintSequenceNumber++);
hit = GetTargetAPZC(ScreenPoint(15, 15));
EXPECT_EQ(ApzcOf(root), hit.get());
// expect hit point at LayerIntPoint(15, 15)
EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc.TransformPoint(ScreenPoint(15, 15)));
EXPECT_EQ(ScreenPoint(15, 15), transformToGecko.TransformPoint(ParentLayerPoint(15, 15)));
// Now we have a sub APZC with a better fit
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1);
manager->UpdateHitTestingTree(0, root, false, 0, paintSequenceNumber++);
EXPECT_NE(ApzcOf(root), ApzcOf(layers[3]));
hit = GetTargetAPZC(ScreenPoint(25, 25));
EXPECT_EQ(ApzcOf(layers[3]), hit.get());
// expect hit point at LayerIntPoint(25, 25)
EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc.TransformPoint(ScreenPoint(25, 25)));
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25)));
// At this point, layers[4] obscures layers[3] at the point (15, 15) so
// hitting there should hit the root APZC
hit = GetTargetAPZC(ScreenPoint(15, 15));
EXPECT_EQ(ApzcOf(root), hit.get());
// Now test hit testing when we have two scrollable layers
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2);
manager->UpdateHitTestingTree(0, root, false, 0, paintSequenceNumber++);
hit = GetTargetAPZC(ScreenPoint(15, 15));
EXPECT_EQ(ApzcOf(layers[4]), hit.get());
// expect hit point at LayerIntPoint(15, 15)
EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc.TransformPoint(ScreenPoint(15, 15)));
EXPECT_EQ(ScreenPoint(15, 15), transformToGecko.TransformPoint(ParentLayerPoint(15, 15)));
// Hit test ouside the reach of layer[3,4] but inside root
hit = GetTargetAPZC(ScreenPoint(90, 90));
EXPECT_EQ(ApzcOf(root), hit.get());
// expect hit point at LayerIntPoint(90, 90)
EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc.TransformPoint(ScreenPoint(90, 90)));
EXPECT_EQ(ScreenPoint(90, 90), transformToGecko.TransformPoint(ParentLayerPoint(90, 90)));
// Hit test ouside the reach of any layer
hit = GetTargetAPZC(ScreenPoint(1000, 10));
EXPECT_EQ(nullAPZC, hit.get());
EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
hit = GetTargetAPZC(ScreenPoint(-1000, 10));
EXPECT_EQ(nullAPZC, hit.get());
EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc);
EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko);
}
// A more involved hit testing test that involves css and async transforms.
TEST_F(APZHitTestingTester, HitTesting2) {
SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
CreateHitTesting2LayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
// At this point, the following holds (all coordinates in screen pixels):
// layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100)
// layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50)
// layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer
// layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100)
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]);
TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]);
// Hit an area that's clearly on the root layer but not any of the child layers.
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25));
EXPECT_EQ(apzcroot, hit.get());
EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc.TransformPoint(ScreenPoint(75, 25)));
EXPECT_EQ(ScreenPoint(75, 25), transformToGecko.TransformPoint(ParentLayerPoint(75, 25)));
// Hit an area on the root that would be on layers[3] if layers[2]
// weren't transformed.
// Note that if layers[2] were scrollable, then this would hit layers[2]
// because its composition bounds would be at (10,60)-(50,100) (and the
// scale-only transform that we set on layers[2] would be invalid because
// it would place the layer into overscroll, as its composition bounds
// start at x=10 but its content at x=20).
hit = GetTargetAPZC(ScreenPoint(15, 75));
EXPECT_EQ(apzcroot, hit.get());
EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc.TransformPoint(ScreenPoint(15, 75)));
EXPECT_EQ(ScreenPoint(15, 75), transformToGecko.TransformPoint(ParentLayerPoint(15, 75)));
// Hit an area on layers[1].
hit = GetTargetAPZC(ScreenPoint(25, 25));
EXPECT_EQ(apzc1, hit.get());
EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc.TransformPoint(ScreenPoint(25, 25)));
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25)));
// Hit an area on layers[3].
hit = GetTargetAPZC(ScreenPoint(25, 75));
EXPECT_EQ(apzc3, hit.get());
// transformToApzc should unapply layers[2]'s transform
EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc.TransformPoint(ScreenPoint(25, 75)));
// and transformToGecko should reapply it
EXPECT_EQ(ScreenPoint(25, 75), transformToGecko.TransformPoint(ParentLayerPoint(12.5, 75)));
// Hit an area on layers[3] that would be on the root if layers[2]
// weren't transformed.
hit = GetTargetAPZC(ScreenPoint(75, 75));
EXPECT_EQ(apzc3, hit.get());
// transformToApzc should unapply layers[2]'s transform
EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc.TransformPoint(ScreenPoint(75, 75)));
// and transformToGecko should reapply it
EXPECT_EQ(ScreenPoint(75, 75), transformToGecko.TransformPoint(ParentLayerPoint(37.5, 75)));
// Pan the root layer upward by 50 pixels.
// This causes layers[1] to scroll out of view, and an async transform
// of -50 to be set on the root layer.
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
// This first pan will move the APZC by 50 pixels, and dispatch a paint request.
// Since this paint request is in the queue to Gecko, transformToGecko will
// take it into account.
ApzcPanNoFling(apzcroot, 100, 50);
// Hit where layers[3] used to be. It should now hit the root.
hit = GetTargetAPZC(ScreenPoint(75, 75));
EXPECT_EQ(apzcroot, hit.get());
// transformToApzc doesn't unapply the root's own async transform
EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc.TransformPoint(ScreenPoint(75, 75)));
// and transformToGecko unapplies it and then reapplies it, because by the
// time the event being transformed reaches Gecko the new paint request will
// have been handled.
EXPECT_EQ(ScreenPoint(75, 75), transformToGecko.TransformPoint(ParentLayerPoint(75, 75)));
// Hit where layers[1] used to be and where layers[3] should now be.
hit = GetTargetAPZC(ScreenPoint(25, 25));
EXPECT_EQ(apzc3, hit.get());
// transformToApzc unapplies both layers[2]'s css transform and the root's
// async transform
EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc.TransformPoint(ScreenPoint(25, 25)));
// transformToGecko reapplies both the css transform and the async transform
// because we have already issued a paint request with it.
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(12.5, 75)));
// This second pan will move the APZC by another 50 pixels.
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
ApzcPanNoFling(apzcroot, 100, 50);
// Hit where layers[3] used to be. It should now hit the root.
hit = GetTargetAPZC(ScreenPoint(75, 75));
EXPECT_EQ(apzcroot, hit.get());
// transformToApzc doesn't unapply the root's own async transform
EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc.TransformPoint(ScreenPoint(75, 75)));
// transformToGecko unapplies the full async transform of -100 pixels
EXPECT_EQ(ScreenPoint(75, 75), transformToGecko.TransformPoint(ParentLayerPoint(75, 75)));
// Hit where layers[1] used to be. It should now hit the root.
hit = GetTargetAPZC(ScreenPoint(25, 25));
EXPECT_EQ(apzcroot, hit.get());
// transformToApzc doesn't unapply the root's own async transform
EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc.TransformPoint(ScreenPoint(25, 25)));
// transformToGecko unapplies the full async transform of -100 pixels
EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25)));
}
TEST_F(APZHitTestingTester, ComplexMultiLayerTree) {
CreateComplexMultiLayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
/* The layer tree looks like this:
0
|----|--+--|----|
1 2 4 5
| /|\
3 6 8 9
|
7
Layers 1,2 have the same APZC
Layers 4,6,8 have the same APZC
Layer 7 has an APZC
Layer 9 has an APZC
*/
TestAsyncPanZoomController* nullAPZC = nullptr;
// Ensure all the scrollable layers have an APZC
EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics());
EXPECT_NE(nullAPZC, ApzcOf(layers[4]));
EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics());
EXPECT_NE(nullAPZC, ApzcOf(layers[6]));
EXPECT_NE(nullAPZC, ApzcOf(layers[7]));
EXPECT_NE(nullAPZC, ApzcOf(layers[8]));
EXPECT_NE(nullAPZC, ApzcOf(layers[9]));
// Ensure those that scroll together have the same APZCs
EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6]));
EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6]));
// Ensure those that don't scroll together have different APZCs
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4]));
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7]));
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9]));
EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7]));
EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9]));
EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9]));
// Ensure the APZC parent chains are set up correctly
TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]);
TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]);
TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]);
TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]);
EXPECT_EQ(nullptr, layers1_2->GetParent());
EXPECT_EQ(nullptr, layers4_6_8->GetParent());
EXPECT_EQ(layers4_6_8, layer7->GetParent());
EXPECT_EQ(nullptr, layer9->GetParent());
// Ensure the hit-testing tree looks like the layer tree
RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
RefPtr<HitTestingTreeNode> node5 = root->GetLastChild();
RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling();
RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling();
RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling();
RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild();
RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild();
RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling();
RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling();
RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild();
EXPECT_EQ(nullptr, node1->GetPrevSibling());
EXPECT_EQ(nullptr, node3->GetPrevSibling());
EXPECT_EQ(nullptr, node6->GetPrevSibling());
EXPECT_EQ(nullptr, node7->GetPrevSibling());
EXPECT_EQ(nullptr, node1->GetLastChild());
EXPECT_EQ(nullptr, node3->GetLastChild());
EXPECT_EQ(nullptr, node4->GetLastChild());
EXPECT_EQ(nullptr, node7->GetLastChild());
EXPECT_EQ(nullptr, node8->GetLastChild());
EXPECT_EQ(nullptr, node9->GetLastChild());
RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25));
EXPECT_EQ(ApzcOf(layers[1]), hit.get());
hit = GetTargetAPZC(ScreenPoint(275, 375));
EXPECT_EQ(ApzcOf(layers[9]), hit.get());
hit = GetTargetAPZC(ScreenPoint(250, 100));
EXPECT_EQ(ApzcOf(layers[7]), hit.get());
}
TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
// The main purpose of this test is to verify that touch-start events (or anything
// that starts a new input block) don't ever get untransformed. This should always
// hold because the APZ code should flush repaints when we start a new input block
// and the transform to gecko space should be empty.
CreateSimpleScrollingLayer();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
// At this point, the following holds (all coordinates in screen pixels):
// layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200)
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
EXPECT_CALL(check, Call("post-first-touch-start"));
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
EXPECT_CALL(check, Call("post-second-fling"));
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1));
EXPECT_CALL(check, Call("post-second-touch-start"));
}
// This first pan will move the APZC by 50 pixels, and dispatch a paint request.
ApzcPanNoFling(apzcroot, 100, 50);
// Verify that a touch start doesn't get untransformed
ScreenIntPoint touchPoint(50, 50);
MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0));
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
check.Call("post-first-touch-start");
// Send a touchend to clear state
mti.mType = MultiTouchInput::MULTITOUCH_END;
manager->ReceiveInputEvent(mti, nullptr, nullptr);
mcc->AdvanceByMillis(1000);
// Now do two pans. The first of these will dispatch a repaint request, as above.
// The second will get stuck in the paint throttler because the first one doesn't
// get marked as "completed", so this will result in a non-empty LD transform.
// (Note that any outstanding repaint requests from the first half of this test
// don't impact this half because we advance the time by 1 second, which will trigger
// the max-wait-exceeded codepath in the paint throttler).
ApzcPanNoFling(apzcroot, 100, 50);
check.Call("post-second-fling");
ApzcPanNoFling(apzcroot, 100, 50);
// Ensure that a touch start again doesn't get untransformed by flushing
// a repaint
mti.mType = MultiTouchInput::MULTITOUCH_START;
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
check.Call("post-second-touch-start");
mti.mType = MultiTouchInput::MULTITOUCH_END;
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr));
EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint);
}
TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) {
// The purpose of this test is to ensure that wheel events trigger a repaint
// flush as per bug 1166871, and that the wheel event untransform is a no-op.
CreateSimpleScrollingLayer();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3));
ScreenPoint origin(100, 50);
for (int i = 0; i < 3; i++) {
ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
origin, 0, 10, false);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
EXPECT_EQ(origin, swi.mOrigin);
AsyncTransform viewTransform;
ParentLayerPoint point;
apzcroot->SampleContentTransformForFrame(&viewTransform, point);
EXPECT_EQ(0, point.x);
EXPECT_EQ((i + 1) * 10, point.y);
EXPECT_EQ(0, viewTransform.mTranslation.x);
EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y);
mcc->AdvanceByMillis(5);
}
}
TEST_F(APZHitTestingTester, TestForceDisableApz) {
CreateSimpleScrollingLayer();
DisableApzOn(root);
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TestAsyncPanZoomController* apzcroot = ApzcOf(root);
ScreenPoint origin(100, 50);
ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
origin, 0, 10, false);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
EXPECT_EQ(origin, swi.mOrigin);
AsyncTransform viewTransform;
ParentLayerPoint point;
apzcroot->SampleContentTransformForFrame(&viewTransform, point);
// Since APZ is force-disabled, we expect to see the async transform via
// the NORMAL AsyncMode, but not via the RESPECT_FORCE_DISABLE AsyncMode.
EXPECT_EQ(0, point.x);
EXPECT_EQ(10, point.y);
EXPECT_EQ(0, viewTransform.mTranslation.x);
EXPECT_EQ(-10, viewTransform.mTranslation.y);
viewTransform = apzcroot->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
point = apzcroot->GetCurrentAsyncScrollOffset(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
EXPECT_EQ(0, point.x);
EXPECT_EQ(0, point.y);
EXPECT_EQ(0, viewTransform.mTranslation.x);
EXPECT_EQ(0, viewTransform.mTranslation.y);
mcc->AdvanceByMillis(10);
// With untransforming events we should get normal behaviour (in this case,
// no noticeable untransform, because the repaint request already got
// flushed).
swi = ScrollWheelInput(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
origin, 0, 0, false);
EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr));
EXPECT_EQ(origin, swi.mOrigin);
}
TEST_F(APZHitTestingTester, Bug1148350) {
CreateBug1148350LayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
MockFunction<void(std::string checkPointName)> check;
{
InSequence s;
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, ApzcOf(layers[1])->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped without transform"));
EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, ApzcOf(layers[1])->GetGuid(), _)).Times(1);
EXPECT_CALL(check, Call("Tapped with interleaved transform"));
}
Tap(manager, ScreenIntPoint(100, 100), TimeDuration::FromMilliseconds(100));
mcc->RunThroughDelayedTasks();
check.Call("Tapped without transform");
uint64_t blockId;
TouchDown(manager, ScreenIntPoint(100, 100), mcc->Time(), &blockId);
if (gfxPrefs::TouchActionEnabled()) {
SetDefaultAllowedTouchBehavior(manager, blockId);
}
mcc->AdvanceByMillis(100);
layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150)));
layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0));
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TouchUp(manager, ScreenIntPoint(100, 100), mcc->Time());
mcc->RunThroughDelayedTasks();
check.Call("Tapped with interleaved transform");
}
TEST_F(APZHitTestingTester, HitTestingRespectsScrollClip_Bug1257288) {
// Create the layer tree.
const char* layerTreeSyntax = "c(tt)";
// LayerID 0 12
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0,0,200,200)),
nsIntRegion(IntRect(0,0,200,200)),
nsIntRegion(IntRect(0,0,200,100))
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
// Add root scroll metadata to the first painted layer.
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0,0,200,200));
// Add root and subframe scroll metadata to the second painted layer.
// Give the subframe metadata a scroll clip corresponding to the subframe's
// composition bounds.
// Importantly, give the layer a layer clip which leaks outside of the
// subframe's composition bounds.
ScrollMetadata rootMetadata = BuildScrollMetadata(
FrameMetrics::START_SCROLL_ID, CSSRect(0,0,200,200),
ParentLayerRect(0,0,200,200));
ScrollMetadata subframeMetadata = BuildScrollMetadata(
FrameMetrics::START_SCROLL_ID + 1, CSSRect(0,0,200,200),
ParentLayerRect(0,0,200,100));
subframeMetadata.SetScrollClip(Some(LayerClip(ParentLayerIntRect(0,0,200,100))));
layers[2]->SetScrollMetadata({subframeMetadata, rootMetadata});
layers[2]->SetClipRect(Some(ParentLayerIntRect(0,0,200,200)));
SetEventRegionsBasedOnBottommostMetrics(layers[2]);
// Build the hit testing tree.
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
// Pan on a region that's inside layers[2]'s layer clip, but outside
// its subframe metadata's scroll clip.
Pan(manager, 120, 110);
// Test that the subframe hasn't scrolled.
EXPECT_EQ(CSSPoint(0,0), ApzcOf(layers[2], 0)->GetFrameMetrics().GetScrollOffset());
}

View file

@ -0,0 +1,44 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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"
// Test of scenario described in bug 1269067 - that a continuing mouse drag
// doesn't interrupt a wheel scrolling animation
TEST_F(APZCTreeManagerTester, WheelInterruptedByMouseDrag) {
// Set up a scrollable layer
CreateSimpleScrollingLayer();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
RefPtr<TestAsyncPanZoomController> apzc = ApzcOf(root);
uint64_t dragBlockId = 0;
uint64_t wheelBlockId = 0;
uint64_t tmpBlockId = 0;
// First start the mouse drag
MouseDown(apzc, ScreenIntPoint(5, 5), mcc->Time(), &dragBlockId);
MouseMove(apzc, ScreenIntPoint(6, 6), mcc->Time(), &tmpBlockId);
EXPECT_EQ(dragBlockId, tmpBlockId);
// Insert the wheel event, check that it has a new block id
SmoothWheel(apzc, ScreenIntPoint(6, 6), ScreenPoint(0, 1), mcc->Time(), &wheelBlockId);
EXPECT_NE(dragBlockId, wheelBlockId);
// Continue the drag, check that the block id is the same as before
MouseMove(apzc, ScreenIntPoint(7, 5), mcc->Time(), &tmpBlockId);
EXPECT_EQ(dragBlockId, tmpBlockId);
// Finish the wheel animation
apzc->AdvanceAnimationsUntilEnd();
// Check that it scrolled
ParentLayerPoint scroll = apzc->GetCurrentAsyncScrollOffset(AsyncPanZoomController::NORMAL);
EXPECT_EQ(scroll.x, 0);
EXPECT_EQ(scroll.y, 10); // We scrolled 1 "line" or 10 pixels
}

View file

@ -0,0 +1,128 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h"
#include "APZTestCommon.h"
#include "InputUtils.h"
class APZCPanningTester : public APZCBasicTester {
protected:
void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
{
if (aShouldTriggerScroll) {
// One repaint request for each pan.
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
} else {
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
}
int touchStart = 50;
int touchEnd = 10;
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
nsTArray<uint32_t> allowedTouchBehaviors;
allowedTouchBehaviors.AppendElement(aBehavior);
// Pan down
PanAndCheckStatus(apzc, touchStart, touchEnd, aShouldBeConsumed, &allowedTouchBehaviors);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
if (aShouldTriggerScroll) {
EXPECT_EQ(ParentLayerPoint(0, -(touchEnd-touchStart)), pointOut);
EXPECT_NE(AsyncTransform(), viewTransformOut);
} else {
EXPECT_EQ(ParentLayerPoint(), pointOut);
EXPECT_EQ(AsyncTransform(), viewTransformOut);
}
// Clear the fling from the previous pan, or stopping it will
// consume the next touchstart
apzc->CancelAnimation();
// Pan back
PanAndCheckStatus(apzc, touchEnd, touchStart, aShouldBeConsumed, &allowedTouchBehaviors);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(ParentLayerPoint(), pointOut);
EXPECT_EQ(AsyncTransform(), viewTransformOut);
}
void DoPanWithPreventDefaultTest()
{
MakeApzcWaitForMainThread();
int touchStart = 50;
int touchEnd = 10;
ParentLayerPoint pointOut;
AsyncTransform viewTransformOut;
uint64_t blockId = 0;
// Pan down
nsTArray<uint32_t> allowedTouchBehaviors;
allowedTouchBehaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
PanAndCheckStatus(apzc, touchStart, touchEnd, true, &allowedTouchBehaviors, &blockId);
// Send the signal that content has handled and preventDefaulted the touch
// events. This flushes the event queue.
apzc->ContentReceivedInputBlock(blockId, true);
apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
EXPECT_EQ(ParentLayerPoint(), pointOut);
EXPECT_EQ(AsyncTransform(), viewTransformOut);
apzc->AssertStateIsReset();
}
};
TEST_F(APZCPanningTester, Pan) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::NONE);
}
// In the each of the following 4 pan tests we are performing two pan gestures: vertical pan from top
// to bottom and back - from bottom to top.
// According to the pointer-events/touch-action spec AUTO and PAN_Y touch-action values allow vertical
// scrolling while NONE and PAN_X forbid it. The first parameter of DoPanTest method specifies this
// behavior.
// However, the events will be marked as consumed even if the behavior in PAN_X, because the user could
// move their finger horizontally too - APZ has no way of knowing beforehand and so must consume the
// events.
TEST_F(APZCPanningTester, PanWithTouchActionAuto) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN
| mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
}
TEST_F(APZCPanningTester, PanWithTouchActionNone) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
DoPanTest(false, false, 0);
}
TEST_F(APZCPanningTester, PanWithTouchActionPanX) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
DoPanTest(false, true, mozilla::layers::AllowedTouchBehavior::HORIZONTAL_PAN);
}
TEST_F(APZCPanningTester, PanWithTouchActionPanY) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests
DoPanTest(true, true, mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
}
TEST_F(APZCPanningTester, PanWithPreventDefaultAndTouchAction) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
DoPanWithPreventDefaultTest();
}
TEST_F(APZCPanningTester, PanWithPreventDefault) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
DoPanWithPreventDefaultTest();
}

View file

@ -0,0 +1,294 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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 "APZCBasicTester.h"
#include "APZTestCommon.h"
#include "InputUtils.h"
class APZCPinchTester : public APZCBasicTester {
public:
explicit APZCPinchTester(AsyncPanZoomController::GestureBehavior aGestureBehavior = AsyncPanZoomController::DEFAULT_GESTURES)
: APZCBasicTester(aGestureBehavior)
{
}
protected:
FrameMetrics GetPinchableFrameMetrics()
{
FrameMetrics fm;
fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
fm.SetScrollOffset(CSSPoint(300, 300));
fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
// APZC only allows zooming on the root scrollable frame.
fm.SetIsRootContent(true);
// the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
return fm;
}
void DoPinchTest(bool aShouldTriggerPinch,
nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
{
apzc->SetFrameMetrics(GetPinchableFrameMetrics());
MakeApzcZoomable();
if (aShouldTriggerPinch) {
// One repaint request for each gesture.
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
} else {
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
}
int touchInputId = 0;
if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25,
touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
} else {
PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 1.25,
aShouldTriggerPinch);
}
FrameMetrics fm = apzc->GetFrameMetrics();
if (aShouldTriggerPinch) {
// the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale);
EXPECT_EQ(305, fm.GetScrollOffset().x);
EXPECT_EQ(310, fm.GetScrollOffset().y);
} else {
// The frame metrics should stay the same since touch-action:none makes
// apzc ignore pinch gestures.
EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
EXPECT_EQ(300, fm.GetScrollOffset().x);
EXPECT_EQ(300, fm.GetScrollOffset().y);
}
// part 2 of the test, move to the top-right corner of the page and pinch and
// make sure we stay in the correct spot
fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
fm.SetScrollOffset(CSSPoint(930, 5));
apzc->SetFrameMetrics(fm);
// the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
PinchWithTouchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5,
touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
} else {
PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(250, 300), 0.5,
aShouldTriggerPinch);
}
fm = apzc->GetFrameMetrics();
if (aShouldTriggerPinch) {
// the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale);
EXPECT_EQ(880, fm.GetScrollOffset().x);
EXPECT_EQ(0, fm.GetScrollOffset().y);
} else {
EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
EXPECT_EQ(930, fm.GetScrollOffset().x);
EXPECT_EQ(5, fm.GetScrollOffset().y);
}
}
};
class APZCPinchGestureDetectorTester : public APZCPinchTester {
public:
APZCPinchGestureDetectorTester()
: APZCPinchTester(AsyncPanZoomController::USE_GESTURE_DETECTOR)
{
}
void DoPinchWithPreventDefaultTest() {
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
apzc->SetFrameMetrics(originalMetrics);
MakeApzcWaitForMainThread();
MakeApzcZoomable();
int touchInputId = 0;
uint64_t blockId = 0;
PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId,
nullptr, nullptr, &blockId);
// Send the prevent-default notification for the touch block
apzc->ContentReceivedInputBlock(blockId, true);
// verify the metrics didn't change (i.e. the pinch was ignored)
FrameMetrics fm = apzc->GetFrameMetrics();
EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
apzc->AssertStateIsReset();
}
};
TEST_F(APZCPinchTester, Pinch_DefaultGestures_NoTouchAction) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
DoPinchTest(true);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_NoTouchAction) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
DoPinchTest(true);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
mozilla::layers::AllowedTouchBehavior::NONE };
DoPinchTest(false, &behaviors);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionZoom) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
nsTArray<uint32_t> behaviors;
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
DoPinchTest(true, &behaviors);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNotAllowZoom) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
nsTArray<uint32_t> behaviors;
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::VERTICAL_PAN);
behaviors.AppendElement(mozilla::layers::AllowedTouchBehavior::PINCH_ZOOM);
DoPinchTest(false, &behaviors);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_UseGestureDetector_TouchActionNone_NoAPZZoom) {
SCOPED_GFX_PREF(TouchActionEnabled, bool, true);
SCOPED_GFX_PREF(APZAllowZooming, bool, false);
// Since we are preventing the pinch action via touch-action we should not be
// sending the pinch gesture notifications that would normally be sent when
// APZAllowZooming is false.
EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _)).Times(0);
nsTArray<uint32_t> behaviors = { mozilla::layers::AllowedTouchBehavior::NONE,
mozilla::layers::AllowedTouchBehavior::NONE };
DoPinchTest(false, &behaviors);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault) {
DoPinchWithPreventDefaultTest();
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_PreventDefault_NoAPZZoom) {
SCOPED_GFX_PREF(APZAllowZooming, bool, false);
// Since we are preventing the pinch action we should not be sending the pinch
// gesture notifications that would normally be sent when APZAllowZooming is
// false.
EXPECT_CALL(*mcc, NotifyPinchGesture(_, _, _, _)).Times(0);
DoPinchWithPreventDefaultTest();
}
TEST_F(APZCPinchTester, Panning_TwoFinger_ZoomDisabled) {
// set up APZ
apzc->SetFrameMetrics(GetPinchableFrameMetrics());
MakeApzcUnzoomable();
nsEventStatus statuses[3]; // scalebegin, scale, scaleend
PinchWithPinchInput(apzc, ScreenIntPoint(250, 350), ScreenIntPoint(200, 300),
10, &statuses);
FrameMetrics fm = apzc->GetFrameMetrics();
// It starts from (300, 300), then moves the focus point from (250, 350) to
// (200, 300) pans by (50, 50) screen pixels, but there is a 2x zoom, which
// causes the scroll offset to change by half of that (25, 25) pixels.
EXPECT_EQ(325, fm.GetScrollOffset().x);
EXPECT_EQ(325, fm.GetScrollOffset().y);
EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale);
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_APZZoom_Disabled) {
SCOPED_GFX_PREF(APZAllowZooming, bool, false);
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
apzc->SetFrameMetrics(originalMetrics);
// When APZAllowZooming is false, the ZoomConstraintsClient produces
// ZoomConstraints with mAllowZoom set to false.
MakeApzcUnzoomable();
// With APZAllowZooming false, we expect the NotifyPinchGesture function to
// get called as the pinch progresses, but the metrics shouldn't change.
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, apzc->GetGuid(), _, _)).Times(AtLeast(1));
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
int touchInputId = 0;
uint64_t blockId = 0;
PinchWithTouchInput(apzc, ScreenIntPoint(250, 300), 1.25, touchInputId,
nullptr, nullptr, &blockId);
// verify the metrics didn't change (i.e. the pinch was ignored inside APZ)
FrameMetrics fm = apzc->GetFrameMetrics();
EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
EXPECT_EQ(originalMetrics.GetScrollOffset().y, fm.GetScrollOffset().y);
apzc->AssertStateIsReset();
}
TEST_F(APZCPinchGestureDetectorTester, Pinch_NoSpan) {
SCOPED_GFX_PREF(APZAllowZooming, bool, false);
SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
FrameMetrics originalMetrics = GetPinchableFrameMetrics();
apzc->SetFrameMetrics(originalMetrics);
// When APZAllowZooming is false, the ZoomConstraintsClient produces
// ZoomConstraints with mAllowZoom set to false.
MakeApzcUnzoomable();
// With APZAllowZooming false, we expect the NotifyPinchGesture function to
// get called as the pinch progresses, but the metrics shouldn't change.
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_START, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_SCALE, apzc->GetGuid(), _, _)).Times(AtLeast(1));
EXPECT_CALL(*mcc, NotifyPinchGesture(PinchGestureInput::PINCHGESTURE_END, apzc->GetGuid(), LayoutDeviceCoord(0), _)).Times(1);
int inputId = 0;
ScreenIntPoint focus(250, 300);
// Do a pinch holding a zero span and moving the focus by y=100
MultiTouchInput mtiStart = MultiTouchInput(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
mtiStart.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
apzc->ReceiveInputEvent(mtiStart, nullptr);
focus.y -= 35 + 1; // this is to get over the PINCH_START_THRESHOLD in GestureEventListener.cpp
MultiTouchInput mtiMove1 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
mtiMove1.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
apzc->ReceiveInputEvent(mtiMove1, nullptr);
focus.y -= 100; // do a two-finger scroll of 100 screen pixels
MultiTouchInput mtiMove2 = MultiTouchInput(MultiTouchInput::MULTITOUCH_MOVE, 0, TimeStamp(), 0);
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
mtiMove2.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
apzc->ReceiveInputEvent(mtiMove2, nullptr);
MultiTouchInput mtiEnd = MultiTouchInput(MultiTouchInput::MULTITOUCH_END, 0, TimeStamp(), 0);
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId, focus));
mtiEnd.mTouches.AppendElement(CreateSingleTouchData(inputId + 1, focus));
apzc->ReceiveInputEvent(mtiEnd, nullptr);
// Done, check the metrics to make sure we scrolled by 100 screen pixels,
// which is 50 CSS pixels for the pinchable frame metrics.
FrameMetrics fm = apzc->GetFrameMetrics();
EXPECT_EQ(originalMetrics.GetZoom(), fm.GetZoom());
EXPECT_EQ(originalMetrics.GetScrollOffset().x, fm.GetScrollOffset().x);
EXPECT_EQ(originalMetrics.GetScrollOffset().y + 50, fm.GetScrollOffset().y);
apzc->AssertStateIsReset();
}

View file

@ -0,0 +1,521 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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"
class APZScrollHandoffTester : public APZCTreeManagerTester {
protected:
UniquePtr<ScopedLayerTreeRegistration> registration;
TestAsyncPanZoomController* rootApzc;
void CreateScrollHandoffLayerTree1() {
const char* layerTreeSyntax = "c(t)";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 50, 100, 50))
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
SetScrollHandoff(layers[1], root);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
rootApzc = ApzcOf(root);
rootApzc->GetFrameMetrics().SetIsRootContent(true); // make root APZC zoomable
}
void CreateScrollHandoffLayerTree2() {
const char* layerTreeSyntax = "c(c(t))";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 50, 100, 50))
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 2, CSSRect(-100, -100, 200, 200));
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
SetScrollHandoff(layers[1], root);
SetScrollHandoff(layers[2], layers[1]);
// No ScopedLayerTreeRegistration as that just needs to be done once per test
// and this is the second layer tree for a particular test.
MOZ_ASSERT(registration);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
rootApzc = ApzcOf(root);
}
void CreateScrollHandoffLayerTree3() {
const char* layerTreeSyntax = "c(c(t)c(t))";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0, 0, 100, 100)), // root
nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling parent 1
nsIntRegion(IntRect(0, 0, 100, 50)), // scrolling child 1
nsIntRegion(IntRect(0, 50, 100, 50)), // scrolling parent 2
nsIntRegion(IntRect(0, 50, 100, 50)) // scrolling child 2
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(layers[0], FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 100));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 100));
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 100, 100));
SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 3, CSSRect(0, 50, 100, 100));
SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 4, CSSRect(0, 50, 100, 100));
SetScrollHandoff(layers[1], layers[0]);
SetScrollHandoff(layers[3], layers[0]);
SetScrollHandoff(layers[2], layers[1]);
SetScrollHandoff(layers[4], layers[3]);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
}
void CreateScrollgrabLayerTree(bool makeParentScrollable = true) {
const char* layerTreeSyntax = "c(t)";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0, 0, 100, 100)), // scroll-grabbing parent
nsIntRegion(IntRect(0, 20, 100, 80)) // child
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
float parentHeight = makeParentScrollable ? 120 : 100;
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, parentHeight));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
SetScrollHandoff(layers[1], root);
registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
rootApzc = ApzcOf(root);
rootApzc->GetScrollMetadata().SetHasScrollgrab(true);
}
void TestFlingAcceleration() {
// Jack up the fling acceleration multiplier so we can easily determine
// whether acceleration occured.
const float kAcceleration = 100.0f;
SCOPED_GFX_PREF(APZFlingAccelBaseMultiplier, float, kAcceleration);
SCOPED_GFX_PREF(APZFlingAccelMinVelocity, float, 0.0);
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
// Pan once, enough to fully scroll the scrollgrab parent and then scroll
// and fling the child.
Pan(manager, 70, 40);
// Give the fling animation a chance to start.
SampleAnimationsOnce();
float childVelocityAfterFling1 = childApzc->GetVelocityVector().y;
// Pan again.
Pan(manager, 70, 40);
// Give the fling animation a chance to start.
// This time it should be accelerated.
SampleAnimationsOnce();
float childVelocityAfterFling2 = childApzc->GetVelocityVector().y;
// We should have accelerated once.
// The division by 2 is to account for friction.
EXPECT_GT(childVelocityAfterFling2,
childVelocityAfterFling1 * kAcceleration / 2);
// We should not have accelerated twice.
// The division by 4 is to account for friction.
EXPECT_LE(childVelocityAfterFling2,
childVelocityAfterFling1 * kAcceleration * kAcceleration / 4);
}
};
// Here we test that if the processing of a touch block is deferred while we
// wait for content to send a prevent-default message, overscroll is still
// handed off correctly when the block is processed.
TEST_F(APZScrollHandoffTester, DeferredInputEventProcessing) {
// Set up the APZC tree.
CreateScrollHandoffLayerTree1();
TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
// Enable touch-listeners so that we can separate the queueing of input
// events from them being processed.
childApzc->SetWaitForMainThread();
// Queue input events for a pan.
uint64_t blockId = 0;
ApzcPanNoFling(childApzc, 90, 30, &blockId);
// Allow the pan to be processed.
childApzc->ContentReceivedInputBlock(blockId, false);
childApzc->ConfirmTarget(blockId);
// Make sure overscroll was handed off correctly.
EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
}
// Here we test that if the layer structure changes in between two input
// blocks being queued, and the first block is only processed after the second
// one has been queued, overscroll handoff for the first block follows
// the original layer structure while overscroll handoff for the second block
// follows the new layer structure.
TEST_F(APZScrollHandoffTester, LayerStructureChangesWhileEventsArePending) {
// Set up an initial APZC tree.
CreateScrollHandoffLayerTree1();
TestAsyncPanZoomController* childApzc = ApzcOf(layers[1]);
// Enable touch-listeners so that we can separate the queueing of input
// events from them being processed.
childApzc->SetWaitForMainThread();
// Queue input events for a pan.
uint64_t blockId = 0;
ApzcPanNoFling(childApzc, 90, 30, &blockId);
// Modify the APZC tree to insert a new APZC 'middle' into the handoff chain
// between the child and the root.
CreateScrollHandoffLayerTree2();
RefPtr<Layer> middle = layers[1];
childApzc->SetWaitForMainThread();
TestAsyncPanZoomController* middleApzc = ApzcOf(middle);
// Queue input events for another pan.
uint64_t secondBlockId = 0;
ApzcPanNoFling(childApzc, 30, 90, &secondBlockId);
// Allow the first pan to be processed.
childApzc->ContentReceivedInputBlock(blockId, false);
childApzc->ConfirmTarget(blockId);
// Make sure things have scrolled according to the handoff chain in
// place at the time the touch-start of the first pan was queued.
EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(0, middleApzc->GetFrameMetrics().GetScrollOffset().y);
// Allow the second pan to be processed.
childApzc->ContentReceivedInputBlock(secondBlockId, false);
childApzc->ConfirmTarget(secondBlockId);
// Make sure things have scrolled according to the handoff chain in
// place at the time the touch-start of the second pan was queued.
EXPECT_EQ(0, childApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(10, rootApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(-10, middleApzc->GetFrameMetrics().GetScrollOffset().y);
}
// Test that putting a second finger down on an APZC while a down-chain APZC
// is overscrolled doesn't result in being stuck in overscroll.
TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1073250) {
// Enable overscrolling.
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
CreateScrollHandoffLayerTree1();
TestAsyncPanZoomController* child = ApzcOf(layers[1]);
// Pan, causing the parent APZC to overscroll.
Pan(manager, 10, 40, true /* keep finger down */);
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_TRUE(rootApzc->IsOverscrolled());
// Put a second finger down.
MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
// Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
// Release the fingers.
MultiTouchInput fingersUp = secondFingerDown;
fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
// Allow any animations to run their course.
child->AdvanceAnimationsUntilEnd();
rootApzc->AdvanceAnimationsUntilEnd();
// Make sure nothing is overscrolled.
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_FALSE(rootApzc->IsOverscrolled());
}
// This is almost exactly like StuckInOverscroll_Bug1073250, except the
// APZC receiving the input events for the first touch block is the child
// (and thus not the same APZC that overscrolls, which is the parent).
TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1231228) {
// Enable overscrolling.
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
CreateScrollHandoffLayerTree1();
TestAsyncPanZoomController* child = ApzcOf(layers[1]);
// Pan, causing the parent APZC to overscroll.
Pan(manager, 60, 90, true /* keep finger down */);
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_TRUE(rootApzc->IsOverscrolled());
// Put a second finger down.
MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
// Use the same touch identifier for the first touch (0) as Pan(). (A bit hacky.)
secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 40), ScreenSize(0, 0), 0, 0));
secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(30, 20), ScreenSize(0, 0), 0, 0));
manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
// Release the fingers.
MultiTouchInput fingersUp = secondFingerDown;
fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
// Allow any animations to run their course.
child->AdvanceAnimationsUntilEnd();
rootApzc->AdvanceAnimationsUntilEnd();
// Make sure nothing is overscrolled.
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_FALSE(rootApzc->IsOverscrolled());
}
TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1240202a) {
// Enable overscrolling.
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
CreateScrollHandoffLayerTree1();
TestAsyncPanZoomController* child = ApzcOf(layers[1]);
// Pan, causing the parent APZC to overscroll.
Pan(manager, 60, 90, true /* keep finger down */);
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_TRUE(rootApzc->IsOverscrolled());
// Lift the finger, triggering an overscroll animation
// (but don't allow it to run).
TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time());
// Put the finger down again, interrupting the animation
// and entering the TOUCHING state.
TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time());
// Lift the finger once again.
TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time());
// Allow any animations to run their course.
child->AdvanceAnimationsUntilEnd();
rootApzc->AdvanceAnimationsUntilEnd();
// Make sure nothing is overscrolled.
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_FALSE(rootApzc->IsOverscrolled());
}
TEST_F(APZScrollHandoffTester, StuckInOverscroll_Bug1240202b) {
// Enable overscrolling.
SCOPED_GFX_PREF(APZOverscrollEnabled, bool, true);
CreateScrollHandoffLayerTree1();
TestAsyncPanZoomController* child = ApzcOf(layers[1]);
// Pan, causing the parent APZC to overscroll.
Pan(manager, 60, 90, true /* keep finger down */);
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_TRUE(rootApzc->IsOverscrolled());
// Lift the finger, triggering an overscroll animation
// (but don't allow it to run).
TouchUp(manager, ScreenIntPoint(10, 90), mcc->Time());
// Put the finger down again, interrupting the animation
// and entering the TOUCHING state.
TouchDown(manager, ScreenIntPoint(10, 90), mcc->Time());
// Put a second finger down. Since we're in the TOUCHING state,
// the "are we panned into overscroll" check will fail and we
// will not ignore the second finger, instead entering the
// PINCHING state.
MultiTouchInput secondFingerDown(MultiTouchInput::MULTITOUCH_START, 0, TimeStamp(), 0);
// Use the same touch identifier for the first touch (0) as TouchDown(). (A bit hacky.)
secondFingerDown.mTouches.AppendElement(SingleTouchData(0, ScreenIntPoint(10, 90), ScreenSize(0, 0), 0, 0));
secondFingerDown.mTouches.AppendElement(SingleTouchData(1, ScreenIntPoint(10, 80), ScreenSize(0, 0), 0, 0));
manager->ReceiveInputEvent(secondFingerDown, nullptr, nullptr);
// Release the fingers.
MultiTouchInput fingersUp = secondFingerDown;
fingersUp.mType = MultiTouchInput::MULTITOUCH_END;
manager->ReceiveInputEvent(fingersUp, nullptr, nullptr);
// Allow any animations to run their course.
child->AdvanceAnimationsUntilEnd();
rootApzc->AdvanceAnimationsUntilEnd();
// Make sure nothing is overscrolled.
EXPECT_FALSE(child->IsOverscrolled());
EXPECT_FALSE(rootApzc->IsOverscrolled());
}
// Test that flinging in a direction where one component of the fling goes into
// overscroll but the other doesn't, results in just the one component being
// handed off to the parent, while the original APZC continues flinging in the
// other direction.
TEST_F(APZScrollHandoffTester, PartialFlingHandoff) {
CreateScrollHandoffLayerTree1();
// Fling up and to the left. The child APZC has room to scroll up, but not
// to the left, so the horizontal component of the fling should be handed
// off to the parent APZC.
Pan(manager, ScreenIntPoint(90, 90), ScreenIntPoint(55, 55));
RefPtr<TestAsyncPanZoomController> parent = ApzcOf(root);
RefPtr<TestAsyncPanZoomController> child = ApzcOf(layers[1]);
// Advance the child's fling animation once to give the partial handoff
// a chance to occur.
mcc->AdvanceByMillis(10);
child->AdvanceAnimations(mcc->Time());
// Assert that partial handoff has occurred.
child->AssertStateIsFling();
parent->AssertStateIsFling();
}
// Here we test that if two flings are happening simultaneously, overscroll
// is handed off correctly for each.
TEST_F(APZScrollHandoffTester, SimultaneousFlings) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
// Set up an initial APZC tree.
CreateScrollHandoffLayerTree3();
RefPtr<TestAsyncPanZoomController> parent1 = ApzcOf(layers[1]);
RefPtr<TestAsyncPanZoomController> child1 = ApzcOf(layers[2]);
RefPtr<TestAsyncPanZoomController> parent2 = ApzcOf(layers[3]);
RefPtr<TestAsyncPanZoomController> child2 = ApzcOf(layers[4]);
// Pan on the lower child.
Pan(child2, 45, 5);
// Pan on the upper child.
Pan(child1, 95, 55);
// Check that child1 and child2 are in a FLING state.
child1->AssertStateIsFling();
child2->AssertStateIsFling();
// Advance the animations on child1 and child2 until their end.
child1->AdvanceAnimationsUntilEnd();
child2->AdvanceAnimationsUntilEnd();
// Check that the flings have been handed off to the parents.
child1->AssertStateIsReset();
parent1->AssertStateIsFling();
child2->AssertStateIsReset();
parent2->AssertStateIsFling();
}
TEST_F(APZScrollHandoffTester, Scrollgrab) {
// Set up the layer tree
CreateScrollgrabLayerTree();
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
// Pan on the child, enough to fully scroll the scrollgrab parent (20 px)
// and leave some more (another 15 px) for the child.
Pan(childApzc, 80, 45);
// Check that the parent and child have scrolled as much as we expect.
EXPECT_EQ(20, rootApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(15, childApzc->GetFrameMetrics().GetScrollOffset().y);
}
TEST_F(APZScrollHandoffTester, ScrollgrabFling) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
// Set up the layer tree
CreateScrollgrabLayerTree();
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
// Pan on the child, not enough to fully scroll the scrollgrab parent.
Pan(childApzc, 80, 70);
// Check that it is the scrollgrab parent that's in a fling, not the child.
rootApzc->AssertStateIsFling();
childApzc->AssertStateIsReset();
}
TEST_F(APZScrollHandoffTester, ScrollgrabFlingAcceleration1) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
CreateScrollgrabLayerTree(true /* make parent scrollable */);
TestFlingAcceleration();
}
TEST_F(APZScrollHandoffTester, ScrollgrabFlingAcceleration2) {
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
CreateScrollgrabLayerTree(false /* do not make parent scrollable */);
TestFlingAcceleration();
}
TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Pan) {
SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
CreateScrollHandoffLayerTree1();
RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
// Pan on the child, enough to scroll it to its end and have scroll
// left to hand off. Since immediate handoff is disallowed, we expect
// the leftover scroll not to be handed off.
Pan(childApzc, 60, 5);
// Verify that the parent has not scrolled.
EXPECT_EQ(50, childApzc->GetFrameMetrics().GetScrollOffset().y);
EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
// Pan again on the child. This time, since the child was scrolled to
// its end when the gesture began, we expect the scroll to be handed off.
Pan(childApzc, 60, 50);
// Verify that the parent scrolled.
EXPECT_EQ(10, parentApzc->GetFrameMetrics().GetScrollOffset().y);
}
TEST_F(APZScrollHandoffTester, ImmediateHandoffDisallowed_Fling) {
SCOPED_GFX_PREF(APZAllowImmediateHandoff, bool, false);
SCOPED_GFX_PREF(APZFlingMinVelocityThreshold, float, 0.0f);
CreateScrollHandoffLayerTree1();
RefPtr<TestAsyncPanZoomController> parentApzc = ApzcOf(root);
RefPtr<TestAsyncPanZoomController> childApzc = ApzcOf(layers[1]);
// Pan on the child, enough to get very close to the end, so that the
// subsequent fling reaches the end and has leftover velocity to hand off.
Pan(childApzc, 60, 12);
// Allow the fling to run its course.
childApzc->AdvanceAnimationsUntilEnd();
parentApzc->AdvanceAnimationsUntilEnd();
// Verify that the parent has not scrolled.
// The first comparison needs to be an ASSERT_NEAR because the fling
// computations are such that the final scroll position can be within
// COORDINATE_EPSILON of the end rather than right at the end.
ASSERT_NEAR(50, childApzc->GetFrameMetrics().GetScrollOffset().y, COORDINATE_EPSILON);
EXPECT_EQ(0, parentApzc->GetFrameMetrics().GetScrollOffset().y);
// Pan again on the child. This time, since the child was scrolled to
// its end when the gesture began, we expect the scroll to be handed off.
Pan(childApzc, 60, 50);
// Allow the fling to run its course. The fling should also be handed off.
childApzc->AdvanceAnimationsUntilEnd();
parentApzc->AdvanceAnimationsUntilEnd();
// Verify that the parent scrolled from the fling.
EXPECT_GT(parentApzc->GetFrameMetrics().GetScrollOffset().y, 10);
}

View file

@ -0,0 +1,64 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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"
class APZCSnappingTester : public APZCTreeManagerTester
{
};
TEST_F(APZCSnappingTester, Bug1265510)
{
const char* layerTreeSyntax = "c(t)";
nsIntRegion layerVisibleRegion[] = {
nsIntRegion(IntRect(0, 0, 100, 100)),
nsIntRegion(IntRect(0, 100, 100, 100))
};
root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers);
SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 100, 200));
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 100, 200));
SetScrollHandoff(layers[1], root);
ScrollSnapInfo snap;
snap.mScrollSnapTypeY = NS_STYLE_SCROLL_SNAP_TYPE_MANDATORY;
snap.mScrollSnapIntervalY = Some(100 * AppUnitsPerCSSPixel());
ScrollMetadata metadata = root->GetScrollMetadata(0);
metadata.SetSnapInfo(ScrollSnapInfo(snap));
root->SetScrollMetadata(metadata);
UniquePtr<ScopedLayerTreeRegistration> registration = MakeUnique<ScopedLayerTreeRegistration>(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TestAsyncPanZoomController* outer = ApzcOf(layers[0]);
TestAsyncPanZoomController* inner = ApzcOf(layers[1]);
// Position the mouse near the bottom of the outer frame and scroll by 60px.
// (6 lines of 10px each). APZC will actually scroll to y=100 because of the
// mandatory snap coordinate there.
TimeStamp now = mcc->Time();
SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), now);
// Advance in 5ms increments until we've scrolled by 70px. At this point, the
// closest snap point is y=100, and the inner frame should be under the mouse
// cursor.
while (outer->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y < 70) {
mcc->AdvanceByMillis(5);
outer->AdvanceAnimations(mcc->Time());
}
// Now do another wheel in a new transaction. This should start scrolling the
// inner frame; we verify that it does by checking the inner scroll position.
TimeStamp newTransactionTime = now + TimeDuration::FromMilliseconds(gfxPrefs::MouseWheelTransactionTimeoutMs() + 100);
SmoothWheel(manager, ScreenIntPoint(50, 80), ScreenPoint(0, 6), newTransactionTime);
inner->AdvanceAnimationsUntilEnd();
EXPECT_LT(0.0f, inner->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y);
// However, the outer frame should also continue to the snap point, otherwise
// it is demonstrating incorrect behaviour by violating the mandatory snapping.
outer->AdvanceAnimationsUntilEnd();
EXPECT_EQ(100.0f, outer->GetCurrentAsyncScrollOffset(AsyncPanZoomController::AsyncMode::NORMAL).y);
}

View file

@ -0,0 +1,112 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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"
TEST_F(APZCTreeManagerTester, ScrollablePaintedLayers) {
CreateSimpleMultiLayerTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
// both layers have the same scrollId
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID);
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
TestAsyncPanZoomController* nullAPZC = nullptr;
// so they should have the same APZC
EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics());
EXPECT_NE(nullAPZC, ApzcOf(layers[1]));
EXPECT_NE(nullAPZC, ApzcOf(layers[2]));
EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
// Change the scrollId of layers[1], and verify the APZC changes
SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[2]));
// Change the scrollId of layers[2] to match that of layers[1], ensure we get the same
// APZC for both again
SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID + 1);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2]));
}
TEST_F(APZCTreeManagerTester, Bug1068268) {
CreatePotentiallyLeakingTree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
RefPtr<HitTestingTreeNode> root = manager->GetRootNode();
RefPtr<HitTestingTreeNode> node2 = root->GetFirstChild()->GetFirstChild();
RefPtr<HitTestingTreeNode> node5 = root->GetLastChild()->GetLastChild();
EXPECT_EQ(ApzcOf(layers[2]), node5->GetApzc());
EXPECT_EQ(ApzcOf(layers[2]), node2->GetApzc());
EXPECT_EQ(ApzcOf(layers[0]), ApzcOf(layers[2])->GetParent());
EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[5]));
EXPECT_EQ(node2->GetFirstChild(), node2->GetLastChild());
EXPECT_EQ(ApzcOf(layers[3]), node2->GetLastChild()->GetApzc());
EXPECT_EQ(node5->GetFirstChild(), node5->GetLastChild());
EXPECT_EQ(ApzcOf(layers[6]), node5->GetLastChild()->GetApzc());
EXPECT_EQ(ApzcOf(layers[2]), ApzcOf(layers[3])->GetParent());
EXPECT_EQ(ApzcOf(layers[5]), ApzcOf(layers[6])->GetParent());
}
TEST_F(APZCTreeManagerTester, Bug1194876) {
CreateBug1194876Tree();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
uint64_t blockId;
nsTArray<ScrollableLayerGuid> targets;
// First touch goes down, APZCTM will hit layers[1] because it is on top of
// layers[0], but we tell it the real target APZC is layers[0].
MultiTouchInput mti;
mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time());
mti.mTouches.AppendElement(SingleTouchData(0, ParentLayerPoint(25, 50), ScreenSize(0, 0), 0, 0));
manager->ReceiveInputEvent(mti, nullptr, &blockId);
manager->ContentReceivedInputBlock(blockId, false);
targets.AppendElement(ApzcOf(layers[0])->GetGuid());
manager->SetTargetAPZC(blockId, targets);
// Around here, the above touch will get processed by ApzcOf(layers[0])
// Second touch goes down (first touch remains down), APZCTM will again hit
// layers[1]. Again we tell it both touches landed on layers[0], but because
// layers[1] is the RCD layer, it will end up being the multitouch target.
mti.mTouches.AppendElement(SingleTouchData(1, ParentLayerPoint(75, 50), ScreenSize(0, 0), 0, 0));
manager->ReceiveInputEvent(mti, nullptr, &blockId);
manager->ContentReceivedInputBlock(blockId, false);
targets.AppendElement(ApzcOf(layers[0])->GetGuid());
manager->SetTargetAPZC(blockId, targets);
// Around here, the above multi-touch will get processed by ApzcOf(layers[1]).
// We want to ensure that ApzcOf(layers[0]) has had its state cleared, because
// otherwise it will do things like dispatch spurious long-tap events.
EXPECT_CALL(*mcc, HandleTap(TapType::eLongTap, _, _, _, _)).Times(0);
}
TEST_F(APZCTreeManagerTester, Bug1198900) {
// This is just a test that cancels a wheel event to make sure it doesn't
// crash.
CreateSimpleDTCScrollingLayer();
ScopedLayerTreeRegistration registration(manager, 0, root, mcc);
manager->UpdateHitTestingTree(0, root, false, 0, 0);
ScreenPoint origin(100, 50);
ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0,
ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL,
origin, 0, 10, false);
uint64_t blockId;
manager->ReceiveInputEvent(swi, nullptr, &blockId);
manager->ContentReceivedInputBlock(blockId, /* preventDefault= */ true);
}

View file

@ -0,0 +1,33 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
UNIFIED_SOURCES += [
'TestBasic.cpp',
'TestEventRegions.cpp',
'TestGestureDetector.cpp',
'TestHitTesting.cpp',
'TestInputQueue.cpp',
'TestPanning.cpp',
'TestPinching.cpp',
'TestScrollHandoff.cpp',
'TestSnapping.cpp',
'TestTreeManager.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
LOCAL_INCLUDES += [
'/gfx/2d',
'/gfx/layers',
'/gfx/tests/gtest' # for TestLayers.h, which is shared with the gfx gtests
]
FINAL_LIBRARY = 'xul-gtest'
CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wno-error=shadow']