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']

View file

@ -0,0 +1,261 @@
// Utilities for synthesizing of native events.
function getPlatform() {
if (navigator.platform.indexOf("Win") == 0) {
return "windows";
}
if (navigator.platform.indexOf("Mac") == 0) {
return "mac";
}
// Check for Android before Linux
if (navigator.appVersion.indexOf("Android") >= 0) {
return "android"
}
if (navigator.platform.indexOf("Linux") == 0) {
return "linux";
}
return "unknown";
}
function nativeVerticalWheelEventMsg() {
switch (getPlatform()) {
case "windows": return 0x020A; // WM_MOUSEWHEEL
case "mac": return 0; // value is unused, can be anything
case "linux": return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway
}
throw "Native wheel events not supported on platform " + getPlatform();
}
function nativeHorizontalWheelEventMsg() {
switch (getPlatform()) {
case "windows": return 0x020E; // WM_MOUSEHWHEEL
case "mac": return 0; // value is unused, can be anything
case "linux": return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway
}
throw "Native wheel events not supported on platform " + getPlatform();
}
// Given a pixel scrolling delta, converts it to the platform's native units.
function nativeScrollUnits(aElement, aDimen) {
switch (getPlatform()) {
case "linux": {
// GTK deltas are treated as line height divided by 3 by gecko.
var targetWindow = aElement.ownerDocument.defaultView;
var lineHeight = targetWindow.getComputedStyle(aElement)["font-size"];
return aDimen / (parseInt(lineHeight) * 3);
}
}
return aDimen;
}
function nativeMouseDownEventMsg() {
switch (getPlatform()) {
case "windows": return 2; // MOUSEEVENTF_LEFTDOWN
case "mac": return 1; // NSLeftMouseDown
case "linux": return 4; // GDK_BUTTON_PRESS
case "android": return 5; // ACTION_POINTER_DOWN
}
throw "Native mouse-down events not supported on platform " + getPlatform();
}
function nativeMouseMoveEventMsg() {
switch (getPlatform()) {
case "windows": return 1; // MOUSEEVENTF_MOVE
case "mac": return 5; // NSMouseMoved
case "linux": return 3; // GDK_MOTION_NOTIFY
case "android": return 7; // ACTION_HOVER_MOVE
}
throw "Native mouse-move events not supported on platform " + getPlatform();
}
function nativeMouseUpEventMsg() {
switch (getPlatform()) {
case "windows": return 4; // MOUSEEVENTF_LEFTUP
case "mac": return 2; // NSLeftMouseUp
case "linux": return 7; // GDK_BUTTON_RELEASE
case "android": return 6; // ACTION_POINTER_UP
}
throw "Native mouse-up events not supported on platform " + getPlatform();
}
// Convert (aX, aY), in CSS pixels relative to aElement's bounding rect,
// to device pixels relative to the screen.
function coordinatesRelativeToScreen(aX, aY, aElement) {
var targetWindow = aElement.ownerDocument.defaultView;
var scale = targetWindow.devicePixelRatio;
var rect = aElement.getBoundingClientRect();
return {
x: (targetWindow.mozInnerScreenX + rect.left + aX) * scale,
y: (targetWindow.mozInnerScreenY + rect.top + aY) * scale
};
}
// Get the bounding box of aElement, and return it in device pixels
// relative to the screen.
function rectRelativeToScreen(aElement) {
var targetWindow = aElement.ownerDocument.defaultView;
var scale = targetWindow.devicePixelRatio;
var rect = aElement.getBoundingClientRect();
return {
x: (targetWindow.mozInnerScreenX + rect.left) * scale,
y: (targetWindow.mozInnerScreenY + rect.top) * scale,
w: (rect.width * scale),
h: (rect.height * scale)
};
}
// Synthesizes a native mousewheel event and returns immediately. This does not
// guarantee anything; you probably want to use one of the other functions below
// which actually wait for results.
// aX and aY are relative to the top-left of |aElement|'s containing window.
// aDeltaX and aDeltaY are pixel deltas, and aObserver can be left undefined
// if not needed.
function synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, aObserver) {
var pt = coordinatesRelativeToScreen(aX, aY, aElement);
if (aDeltaX && aDeltaY) {
throw "Simultaneous wheeling of horizontal and vertical is not supported on all platforms.";
}
aDeltaX = nativeScrollUnits(aElement, aDeltaX);
aDeltaY = nativeScrollUnits(aElement, aDeltaY);
var msg = aDeltaX ? nativeHorizontalWheelEventMsg() : nativeVerticalWheelEventMsg();
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
utils.sendNativeMouseScrollEvent(pt.x, pt.y, msg, aDeltaX, aDeltaY, 0, 0, 0, aElement, aObserver);
return true;
}
// Synthesizes a native mousewheel event and invokes the callback once the
// request has been successfully made to the OS. This does not necessarily
// guarantee that the OS generates the event we requested. See
// synthesizeNativeWheel for details on the parameters.
function synthesizeNativeWheelAndWaitForObserver(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
var observer = {
observe: function(aSubject, aTopic, aData) {
if (aCallback && aTopic == "mousescrollevent") {
setTimeout(aCallback, 0);
}
}
};
return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY, observer);
}
// Synthesizes a native mousewheel event and invokes the callback once the
// wheel event is dispatched to |aElement|'s containing window. If the event
// targets content in a subdocument, |aElement| should be inside the
// subdocument. See synthesizeNativeWheel for details on the other parameters.
function synthesizeNativeWheelAndWaitForWheelEvent(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
var targetWindow = aElement.ownerDocument.defaultView;
targetWindow.addEventListener("wheel", function wheelWaiter(e) {
targetWindow.removeEventListener("wheel", wheelWaiter);
setTimeout(aCallback, 0);
});
return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY);
}
// Synthesizes a native mousewheel event and invokes the callback once the
// first resulting scroll event is dispatched to |aElement|'s containing window.
// If the event targets content in a subdocument, |aElement| should be inside
// the subdocument. See synthesizeNativeWheel for details on the other
// parameters.
function synthesizeNativeWheelAndWaitForScrollEvent(aElement, aX, aY, aDeltaX, aDeltaY, aCallback) {
var targetWindow = aElement.ownerDocument.defaultView;
var useCapture = true; // scroll events don't always bubble
targetWindow.addEventListener("scroll", function scrollWaiter(e) {
targetWindow.removeEventListener("scroll", scrollWaiter, useCapture);
setTimeout(aCallback, 0);
}, useCapture);
return synthesizeNativeWheel(aElement, aX, aY, aDeltaX, aDeltaY);
}
// Synthesizes a native mouse move event and returns immediately.
// aX and aY are relative to the top-left of |aElement|'s containing window.
function synthesizeNativeMouseMove(aElement, aX, aY) {
var pt = coordinatesRelativeToScreen(aX, aY, aElement);
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseMoveEventMsg(), 0, aElement);
return true;
}
// Synthesizes a native mouse move event and invokes the callback once the
// mouse move event is dispatched to |aElement|'s containing window. If the event
// targets content in a subdocument, |aElement| should be inside the
// subdocument. See synthesizeNativeMouseMove for details on the other
// parameters.
function synthesizeNativeMouseMoveAndWaitForMoveEvent(aElement, aX, aY, aCallback) {
var targetWindow = aElement.ownerDocument.defaultView;
targetWindow.addEventListener("mousemove", function mousemoveWaiter(e) {
targetWindow.removeEventListener("mousemove", mousemoveWaiter);
setTimeout(aCallback, 0);
});
return synthesizeNativeMouseMove(aElement, aX, aY);
}
// Synthesizes a native touch event and dispatches it. aX and aY in CSS pixels
// relative to the top-left of |aElement|'s bounding rect.
function synthesizeNativeTouch(aElement, aX, aY, aType, aObserver = null, aTouchId = 0) {
var pt = coordinatesRelativeToScreen(aX, aY, aElement);
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
utils.sendNativeTouchPoint(aTouchId, aType, pt.x, pt.y, 1, 90, aObserver);
return true;
}
// A handy constant when synthesizing native touch drag events with the pref
// "apz.touch_start_tolerance" set to 0. In this case, the first touchmove with
// a nonzero pixel movement is consumed by the APZ to transition from the
// "touching" state to the "panning" state, so calls to synthesizeNativeTouchDrag
// should add an extra pixel pixel for this purpose. The TOUCH_SLOP provides
// a constant that can be used for this purpose. Note that if the touch start
// tolerance is set to something higher, the touch slop amount used must be
// correspondingly increased so as to be higher than the tolerance.
const TOUCH_SLOP = 1;
function synthesizeNativeTouchDrag(aElement, aX, aY, aDeltaX, aDeltaY, aObserver = null, aTouchId = 0) {
synthesizeNativeTouch(aElement, aX, aY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
var steps = Math.max(Math.abs(aDeltaX), Math.abs(aDeltaY));
for (var i = 1; i < steps; i++) {
var dx = i * (aDeltaX / steps);
var dy = i * (aDeltaY / steps);
synthesizeNativeTouch(aElement, aX + dx, aY + dy, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
}
synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, aTouchId);
return synthesizeNativeTouch(aElement, aX + aDeltaX, aY + aDeltaY, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, aObserver, aTouchId);
}
function synthesizeNativeTap(aElement, aX, aY, aObserver = null) {
var pt = coordinatesRelativeToScreen(aX, aY, aElement);
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
utils.sendNativeTouchTap(pt.x, pt.y, false, aObserver);
return true;
}
function synthesizeNativeMouseEvent(aElement, aX, aY, aType, aObserver = null) {
var pt = coordinatesRelativeToScreen(aX, aY, aElement);
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
utils.sendNativeMouseEvent(pt.x, pt.y, aType, 0, aElement, aObserver);
return true;
}
function synthesizeNativeClick(aElement, aX, aY, aObserver = null) {
var pt = coordinatesRelativeToScreen(aX, aY, aElement);
var utils = SpecialPowers.getDOMWindowUtils(aElement.ownerDocument.defaultView);
utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseDownEventMsg(), 0, aElement, function() {
utils.sendNativeMouseEvent(pt.x, pt.y, nativeMouseUpEventMsg(), 0, aElement, aObserver);
});
return true;
}
// Move the mouse to (dx, dy) relative to |element|, and scroll the wheel
// at that location.
// Moving the mouse is necessary to avoid wheel events from two consecutive
// moveMouseAndScrollWheelOver() calls on different elements being incorrectly
// considered as part of the same wheel transaction.
// We also wait for the mouse move event to be processed before sending the
// wheel event, otherwise there is a chance they might get reordered, and
// we have the transaction problem again.
function moveMouseAndScrollWheelOver(element, dx, dy, testDriver, waitForScroll = true) {
return synthesizeNativeMouseMoveAndWaitForMoveEvent(element, dx, dy, function() {
if (waitForScroll) {
synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, testDriver);
} else {
synthesizeNativeWheelAndWaitForWheelEvent(element, dx, dy, 0, -10, testDriver);
}
});
}

View file

@ -0,0 +1,403 @@
// Utilities for writing APZ tests using the framework added in bug 961289
// ----------------------------------------------------------------------
// Functions that convert the APZ test data into a more usable form.
// Every place we have a WebIDL sequence whose elements are dictionaries
// with two elements, a key, and a value, we convert this into a JS
// object with a property for each key/value pair. (This is the structure
// we really want, but we can't express in directly in WebIDL.)
// ----------------------------------------------------------------------
function convertEntries(entries) {
var result = {};
for (var i = 0; i < entries.length; ++i) {
result[entries[i].key] = entries[i].value;
}
return result;
}
function getPropertyAsRect(scrollFrames, scrollId, prop) {
SimpleTest.ok(scrollId in scrollFrames,
'expected scroll frame data for scroll id ' + scrollId);
var scrollFrameData = scrollFrames[scrollId];
SimpleTest.ok('displayport' in scrollFrameData,
'expected a ' + prop + ' for scroll id ' + scrollId);
var value = scrollFrameData[prop];
var pieces = value.replace(/[()\s]+/g, '').split(',');
SimpleTest.is(pieces.length, 4, "expected string of form (x,y,w,h)");
return { x: parseInt(pieces[0]),
y: parseInt(pieces[1]),
w: parseInt(pieces[2]),
h: parseInt(pieces[3]) };
}
function convertScrollFrameData(scrollFrames) {
var result = {};
for (var i = 0; i < scrollFrames.length; ++i) {
result[scrollFrames[i].scrollId] = convertEntries(scrollFrames[i].entries);
}
return result;
}
function convertBuckets(buckets) {
var result = {};
for (var i = 0; i < buckets.length; ++i) {
result[buckets[i].sequenceNumber] = convertScrollFrameData(buckets[i].scrollFrames);
}
return result;
}
function convertTestData(testData) {
var result = {};
result.paints = convertBuckets(testData.paints);
result.repaintRequests = convertBuckets(testData.repaintRequests);
return result;
}
// Given APZ test data for a single paint on the compositor side,
// reconstruct the APZC tree structure from the 'parentScrollId'
// entries that were logged. More specifically, the subset of the
// APZC tree structure corresponding to the layer subtree for the
// content process that triggered the paint, is reconstructed (as
// the APZ test data only contains information abot this subtree).
function buildApzcTree(paint) {
// The APZC tree can potentially have multiple root nodes,
// so we invent a node that is the parent of all roots.
// This 'root' does not correspond to an APZC.
var root = {scrollId: -1, children: []};
for (var scrollId in paint) {
paint[scrollId].children = [];
paint[scrollId].scrollId = scrollId;
}
for (var scrollId in paint) {
var parentNode = null;
if ("hasNoParentWithSameLayersId" in paint[scrollId]) {
parentNode = root;
} else if ("parentScrollId" in paint[scrollId]) {
parentNode = paint[paint[scrollId].parentScrollId];
}
parentNode.children.push(paint[scrollId]);
}
return root;
}
// Given an APZC tree produced by buildApzcTree, return the RCD node in
// the tree, or null if there was none.
function findRcdNode(apzcTree) {
if (!!apzcTree.isRootContent) { // isRootContent will be undefined or "1"
return apzcTree;
}
for (var i = 0; i < apzcTree.children.length; i++) {
var rcd = findRcdNode(apzcTree.children[i]);
if (rcd != null) {
return rcd;
}
}
return null;
}
// Return whether an element whose id includes |elementId| has been layerized.
// Assumes |elementId| will be present in the content description for the
// element, and not in the content descriptions of other elements.
function isLayerized(elementId) {
var contentTestData = SpecialPowers.getDOMWindowUtils(window).getContentAPZTestData();
ok(contentTestData.paints.length > 0, "expected at least one paint");
var seqno = contentTestData.paints[contentTestData.paints.length - 1].sequenceNumber;
contentTestData = convertTestData(contentTestData);
var paint = contentTestData.paints[seqno];
for (var scrollId in paint) {
if ("contentDescription" in paint[scrollId]) {
if (paint[scrollId]["contentDescription"].includes(elementId)) {
return true;
}
}
}
return false;
}
function flushApzRepaints(aCallback, aWindow = window) {
if (!aCallback) {
throw "A callback must be provided!";
}
var repaintDone = function() {
SpecialPowers.Services.obs.removeObserver(repaintDone, "apz-repaints-flushed", false);
setTimeout(aCallback, 0);
};
SpecialPowers.Services.obs.addObserver(repaintDone, "apz-repaints-flushed", false);
if (SpecialPowers.getDOMWindowUtils(aWindow).flushApzRepaints()) {
dump("Flushed APZ repaints, waiting for callback...\n");
} else {
dump("Flushing APZ repaints was a no-op, triggering callback directly...\n");
repaintDone();
}
}
// Flush repaints, APZ pending repaints, and any repaints resulting from that
// flush. This is particularly useful if the test needs to reach some sort of
// "idle" state in terms of repaints. Usually just doing waitForAllPaints
// followed by flushApzRepaints is sufficient to flush all APZ state back to
// the main thread, but it can leave a paint scheduled which will get triggered
// at some later time. For tests that specifically test for painting at
// specific times, this method is the way to go. Even if in doubt, this is the
// preferred method as the extra step is "safe" and shouldn't interfere with
// most tests.
function waitForApzFlushedRepaints(aCallback) {
// First flush the main-thread paints and send transactions to the APZ
waitForAllPaints(function() {
// Then flush the APZ to make sure any repaint requests have been sent
// back to the main thread
flushApzRepaints(function() {
// Then flush the main-thread again to process the repaint requests.
// Once this is done, we should be in a stable state with nothing
// pending, so we can trigger the callback.
waitForAllPaints(aCallback);
});
});
}
// This function takes a set of subtests to run one at a time in new top-level
// windows, and returns a Promise that is resolved once all the subtests are
// done running.
//
// The aSubtests array is an array of objects with the following keys:
// file: required, the filename of the subtest.
// prefs: optional, an array of arrays containing key-value prefs to set.
// dp_suppression: optional, a boolean on whether or not to respect displayport
// suppression during the test.
// onload: optional, a function that will be registered as a load event listener
// for the child window that will hold the subtest. the function will be
// passed exactly one argument, which will be the child window.
// An example of an array is:
// aSubtests = [
// { 'file': 'test_file_name.html' },
// { 'file': 'test_file_2.html', 'prefs': [['pref.name', true], ['other.pref', 1000]], 'dp_suppression': false }
// { 'file': 'file_3.html', 'onload': function(w) { w.subtestDone(); } }
// ];
//
// Each subtest should call the subtestDone() function when it is done, to
// indicate that the window should be torn down and the next text should run.
// The subtestDone() function is injected into the subtest's window by this
// function prior to loading the subtest. For convenience, the |is| and |ok|
// functions provided by SimpleTest are also mapped into the subtest's window.
// For other things from the parent, the subtest can use window.opener.<whatever>
// to access objects.
function runSubtestsSeriallyInFreshWindows(aSubtests) {
return new Promise(function(resolve, reject) {
var testIndex = -1;
var w = null;
function advanceSubtestExecution() {
var test = aSubtests[testIndex];
if (w) {
if (typeof test.dp_suppression != 'undefined') {
// We modified the suppression when starting the test, so now undo that.
SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(!test.dp_suppression);
}
if (test.prefs) {
// We pushed some prefs for this test, pop them, and re-invoke
// advanceSubtestExecution() after that's been processed
SpecialPowers.popPrefEnv(function() {
w.close();
w = null;
advanceSubtestExecution();
});
return;
}
w.close();
}
testIndex++;
if (testIndex >= aSubtests.length) {
resolve();
return;
}
test = aSubtests[testIndex];
if (typeof test.dp_suppression != 'undefined') {
// Normally during a test, the displayport will get suppressed during page
// load, and unsuppressed at a non-deterministic time during the test. The
// unsuppression can trigger a repaint which interferes with the test, so
// to avoid that we can force the displayport to be unsuppressed for the
// entire test which is more deterministic.
SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(test.dp_suppression);
}
function spawnTest(aFile) {
w = window.open('', "_blank");
w.subtestDone = advanceSubtestExecution;
w.SimpleTest = SimpleTest;
w.is = function(a, b, msg) { return is(a, b, aFile + " | " + msg); };
w.ok = function(cond, name, diag) { return ok(cond, aFile + " | " + name, diag); };
if (test.onload) {
w.addEventListener('load', function(e) { test.onload(w); }, { once: true });
}
w.location = location.href.substring(0, location.href.lastIndexOf('/') + 1) + aFile;
return w;
}
if (test.prefs) {
// Got some prefs for this subtest, push them
SpecialPowers.pushPrefEnv({"set": test.prefs}, function() {
w = spawnTest(test.file);
});
} else {
w = spawnTest(test.file);
}
}
advanceSubtestExecution();
});
}
function pushPrefs(prefs) {
return SpecialPowers.pushPrefEnv({'set': prefs});
}
function waitUntilApzStable() {
return new Promise(function(resolve, reject) {
SimpleTest.waitForFocus(function() {
waitForAllPaints(function() {
flushApzRepaints(resolve);
});
}, window);
});
}
function isApzEnabled() {
var enabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
if (!enabled) {
// All tests are required to have at least one assertion. Since APZ is
// disabled, and the main test is presumably not going to run, we stick in
// a dummy assertion here to keep the test passing.
SimpleTest.ok(true, "APZ is not enabled; this test will be skipped");
}
return enabled;
}
// Despite what this function name says, this does not *directly* run the
// provided continuation testFunction. Instead, it returns a function that
// can be used to run the continuation. The extra level of indirection allows
// it to be more easily added to a promise chain, like so:
// waitUntilApzStable().then(runContinuation(myTest));
//
// If you want to run the continuation directly, outside of a promise chain,
// you can invoke the return value of this function, like so:
// runContinuation(myTest)();
function runContinuation(testFunction) {
// We need to wrap this in an extra function, so that the call site can
// be more readable without running the promise too early. In other words,
// if we didn't have this extra function, the promise would start running
// during construction of the promise chain, concurrently with the first
// promise in the chain.
return function() {
return new Promise(function(resolve, reject) {
var testContinuation = null;
function driveTest() {
if (!testContinuation) {
testContinuation = testFunction(driveTest);
}
var ret = testContinuation.next();
if (ret.done) {
resolve();
}
}
driveTest();
});
};
}
// Take a snapshot of the given rect, *including compositor transforms* (i.e.
// includes async scroll transforms applied by APZ). If you don't need the
// compositor transforms, you can probably get away with using
// SpecialPowers.snapshotWindowWithOptions or one of the friendlier wrappers.
// The rect provided is expected to be relative to the screen, for example as
// returned by rectRelativeToScreen in apz_test_native_event_utils.js.
// Example usage:
// var snapshot = getSnapshot(rectRelativeToScreen(myDiv));
// which will take a snapshot of the 'myDiv' element. Note that if part of the
// element is obscured by other things on top, the snapshot will include those
// things. If it is clipped by a scroll container, the snapshot will include
// that area anyway, so you will probably get parts of the scroll container in
// the snapshot. If the rect extends outside the browser window then the
// results are undefined.
// The snapshot is returned in the form of a data URL.
function getSnapshot(rect) {
function parentProcessSnapshot() {
addMessageListener('snapshot', function(rect) {
Components.utils.import('resource://gre/modules/Services.jsm');
var topWin = Services.wm.getMostRecentWindow('navigator:browser');
// reposition the rect relative to the top-level browser window
rect = JSON.parse(rect);
rect.x -= topWin.mozInnerScreenX;
rect.y -= topWin.mozInnerScreenY;
// take the snapshot
var canvas = topWin.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = rect.w;
canvas.height = rect.h;
var ctx = canvas.getContext("2d");
ctx.drawWindow(topWin, rect.x, rect.y, rect.w, rect.h, 'rgb(255,255,255)', ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS | ctx.DRAWWINDOW_DRAW_CARET);
return canvas.toDataURL();
});
}
if (typeof getSnapshot.chromeHelper == 'undefined') {
// This is the first time getSnapshot is being called; do initialization
getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(parentProcessSnapshot);
SimpleTest.registerCleanupFunction(function() { getSnapshot.chromeHelper.destroy() });
}
return getSnapshot.chromeHelper.sendSyncMessage('snapshot', JSON.stringify(rect)).toString();
}
// Takes the document's query string and parses it, assuming the query string
// is composed of key-value pairs where the value is in JSON format. The object
// returned contains the various values indexed by their respective keys. In
// case of duplicate keys, the last value be used.
// Examples:
// ?key="value"&key2=false&key3=500
// produces { "key": "value", "key2": false, "key3": 500 }
// ?key={"x":0,"y":50}&key2=[1,2,true]
// produces { "key": { "x": 0, "y": 0 }, "key2": [1, 2, true] }
function getQueryArgs() {
var args = {};
if (location.search.length > 0) {
var params = location.search.substr(1).split('&');
for (var p of params) {
var [k, v] = p.split('=');
args[k] = JSON.parse(v);
}
}
return args;
}
// Return a function that returns a promise to create a script element with the
// given URI and append it to the head of the document in the given window.
// As with runContinuation(), the extra function wrapper is for convenience
// at the call site, so that this can be chained with other promises:
// waitUntilApzStable().then(injectScript('foo'))
// .then(injectScript('bar'));
// If you want to do the injection right away, run the function returned by
// this function:
// injectScript('foo')();
function injectScript(aScript, aWindow = window) {
return function() {
return new Promise(function(resolve, reject) {
var e = aWindow.document.createElement('script');
e.type = 'text/javascript';
e.onload = function() {
resolve();
};
e.onerror = function() {
dump('Script [' + aScript + '] errored out\n');
reject();
};
e.src = aScript;
aWindow.document.getElementsByTagName('head')[0].appendChild(e);
});
};
}

View file

@ -0,0 +1,9 @@
[DEFAULT]
support-files =
apz_test_native_event_utils.js
tags = apz-chrome
[test_smoothness.html]
# hardware vsync only on win/mac
# e10s only since APZ is only enabled on e10s
skip-if = debug || (os != 'mac' && os != 'win') || !e10s

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity panning test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function scrollPage() {
var transformEnd = function() {
SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
dump("Transform complete; flushing repaints...\n");
flushApzRepaints(checkScroll);
};
SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
synthesizeNativeTouchDrag(document.body, 10, 100, 0, -(50 + TOUCH_SLOP));
dump("Finished native drag, waiting for transform-end observer...\n");
}
function checkScroll() {
is(window.scrollY, 50, "check that the window scrolled");
subtestDone();
}
waitUntilApzStable().then(scrollPage);
</script>
</head>
<body>
<div style="height: 5000px; background-color: lightgreen;">
This div makes the page scrollable.
</div>
</body>
</html>

View file

@ -0,0 +1,83 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1151663
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1151663, helper page</title>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript">
// -------------------------------------------------------------------
// Infrastructure to get the test assertions to run at the right time.
// -------------------------------------------------------------------
var SimpleTest = window.opener.SimpleTest;
window.onload = function() {
window.addEventListener("MozAfterPaint", afterPaint, false);
};
var utils = SpecialPowers.getDOMWindowUtils(window);
function afterPaint(e) {
// If there is another paint pending, wait for it.
if (utils.isMozAfterPaintPending) {
return;
}
// Once there are no more paints pending, remove the
// MozAfterPaint listener and run the test logic.
window.removeEventListener("MozAfterPaint", afterPaint, false);
testBug1151663();
}
// --------------------------------------------------------------------
// The actual logic for testing bug 1151663.
//
// In this test we have a simple page which is scrollable, with a
// scrollable <div> which is also scrollable. We test that the
// <div> does not get an initial APZC, since primary scrollable
// frame is the page's root scroll frame.
// --------------------------------------------------------------------
function testBug1151663() {
// Get the content- and compositor-side test data from nsIDOMWindowUtils.
var contentTestData = utils.getContentAPZTestData();
var compositorTestData = utils.getCompositorAPZTestData();
// Get the sequence number of the last paint on the compositor side.
// We do this before converting the APZ test data because the conversion
// loses the order of the paints.
SimpleTest.ok(compositorTestData.paints.length > 0,
"expected at least one paint in compositor test data");
var lastCompositorPaint = compositorTestData.paints[compositorTestData.paints.length - 1];
var lastCompositorPaintSeqNo = lastCompositorPaint.sequenceNumber;
// Convert the test data into a representation that's easier to navigate.
contentTestData = convertTestData(contentTestData);
compositorTestData = convertTestData(compositorTestData);
var paint = compositorTestData.paints[lastCompositorPaintSeqNo];
// Reconstruct the APZC tree structure in the last paint.
var apzcTree = buildApzcTree(paint);
// The apzc tree for this page should consist of a single root APZC,
// which either is the RCD with no child APZCs (e10s/B2G case) or has a
// single child APZC which is for the RCD (fennec case).
var rcd = findRcdNode(apzcTree);
SimpleTest.ok(rcd != null, "found the RCD node");
SimpleTest.is(rcd.children.length, 0, "expected no children on the RCD");
window.opener.finishTest();
}
</script>
</head>
<body style="height: 500px; overflow: scroll">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151663">Mozilla Bug 1151663</a>
<div style="height: 50px; width: 50px; overflow: scroll">
<!-- Put enough content into the subframe to make it have a nonzero scroll range -->
<div style="height: 100px; width: 50px"></div>
</div>
<!-- Put enough content into the page to make it have a nonzero scroll range -->
<div style="height: 1000px"></div>
</body>
</html>

View file

@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Test for touchend on media elements</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
var v = document.getElementById('video');
var a = document.getElementById('audio');
var d = document.getElementById('div');
document.body.ontouchstart = function(e) {
if (e.target === v || e.target === a || e.target === d) {
e.target.style.display = 'none';
ok(true, 'Set display to none on #' + e.target.id);
} else {
ok(false, 'Got unexpected touchstart on ' + e.target);
}
waitForAllPaints(testDriver);
};
document.body.ontouchend = function(e) {
if (e.target === v || e.target === a || e.target === d) {
e.target._gotTouchend = true;
ok(true, 'Got touchend event on #' + e.target.id);
}
testDriver();
};
var utils = SpecialPowers.getDOMWindowUtils(window);
var pt = coordinatesRelativeToScreen(25, 5, v);
yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, pt.x, pt.y, 1, 90, null);
yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, pt.x, pt.y, 1, 90, null);
ok(v._gotTouchend, 'Touchend was received on video element');
pt = coordinatesRelativeToScreen(25, 5, a);
yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, pt.x, pt.y, 1, 90, null);
yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, pt.x, pt.y, 1, 90, null);
ok(a._gotTouchend, 'Touchend was received on audio element');
pt = coordinatesRelativeToScreen(25, 5, d);
yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, pt.x, pt.y, 1, 90, null);
yield utils.sendNativeTouchPoint(0, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, pt.x, pt.y, 1, 90, null);
ok(d._gotTouchend, 'Touchend was received on div element');
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
<style>
* {
font-size: 24px;
box-sizing: border-box;
}
#video {
display:block;
position:absolute;
top: 100px;
left:0;
width: 33%;
height: 100px;
border:solid black 1px;
background-color: #8a8;
}
#audio {
display:block;
position:absolute;
top: 100px;
left:33%;
width: 33%;
height: 100px;
border:solid black 1px;
background-color: #a88;
}
#div {
display:block;
position:absolute;
top: 100px;
left: 66%;
width: 34%;
height: 100px;
border:solid black 1px;
background-color: #88a;
}
</style>
</head>
<body>
<p>Tap on the colored boxes to hide them.</p>
<video id="video"></video>
<audio id="audio" controls></audio>
<div id="div"></div>
</body>
</html>

View file

@ -0,0 +1,574 @@
<head>
<title>Ensure that the hit region doesn't get unexpectedly expanded</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
var scroller = document.getElementById('scroller');
var scrollerPos = scroller.scrollTop;
var dx = 100, dy = 50;
is(window.scrollY, 0, "Initial page scroll position should be 0");
is(scrollerPos, 0, "Initial scroller position should be 0");
yield moveMouseAndScrollWheelOver(scroller, dx, dy, testDriver);
is(window.scrollY, 0, "Page scroll position should still be 0");
ok(scroller.scrollTop > scrollerPos, "Scroller should have scrolled");
// wait for it to layerize fully and then try again
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
scrollerPos = scroller.scrollTop;
yield moveMouseAndScrollWheelOver(scroller, dx, dy, testDriver);
is(window.scrollY, 0, "Page scroll position should still be 0 after layerization");
ok(scroller.scrollTop > scrollerPos, "Scroller should have continued scrolling");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
<style>
a#with_after_content {
background-color: #F16725;
opacity: 0.8;
display: inline-block;
margin-top: 40px;
margin-left: 40px;
}
a#with_after_content::after {
content: " ";
position: absolute;
width: 0px;
height: 0px;
bottom: 40px;
z-index: -1;
right: 40px;
background-color: transparent;
border-style: solid;
border-width: 15px 15px 15px 0;
border-color: #d54e0e transparent transparent transparent;
box-shadow: none;
box-sizing: border-box;
}
div#scroller {
overflow-y: scroll;
width: 50%;
height: 50%;
}
</style>
</head>
<body>
<a id="with_after_content">Some text</a>
<div id="scroller">
Scrolling on the very left edge of this div will work.
Scrolling on the right side of this div (starting with the left edge of the orange box above) should work, but doesn't.<br/>
0<br>
1<br>
2<br>
3<br>
4<br>
5<br>
6<br>
7<br>
8<br>
9<br>
10<br>
11<br>
12<br>
13<br>
14<br>
15<br>
16<br>
17<br>
18<br>
19<br>
20<br>
21<br>
22<br>
23<br>
24<br>
25<br>
26<br>
27<br>
28<br>
29<br>
30<br>
31<br>
32<br>
33<br>
34<br>
35<br>
36<br>
37<br>
38<br>
39<br>
40<br>
41<br>
42<br>
43<br>
44<br>
45<br>
46<br>
47<br>
48<br>
49<br>
50<br>
51<br>
52<br>
53<br>
54<br>
55<br>
56<br>
57<br>
58<br>
59<br>
60<br>
61<br>
62<br>
63<br>
64<br>
65<br>
66<br>
67<br>
68<br>
69<br>
70<br>
71<br>
72<br>
73<br>
74<br>
75<br>
76<br>
77<br>
78<br>
79<br>
80<br>
81<br>
82<br>
83<br>
84<br>
85<br>
86<br>
87<br>
88<br>
89<br>
90<br>
91<br>
92<br>
93<br>
94<br>
95<br>
96<br>
97<br>
98<br>
99<br>
100<br>
101<br>
102<br>
103<br>
104<br>
105<br>
106<br>
107<br>
108<br>
109<br>
110<br>
111<br>
112<br>
113<br>
114<br>
115<br>
116<br>
117<br>
118<br>
119<br>
120<br>
121<br>
122<br>
123<br>
124<br>
125<br>
126<br>
127<br>
128<br>
129<br>
130<br>
131<br>
132<br>
133<br>
134<br>
135<br>
136<br>
137<br>
138<br>
139<br>
140<br>
141<br>
142<br>
143<br>
144<br>
145<br>
146<br>
147<br>
148<br>
149<br>
150<br>
151<br>
152<br>
153<br>
154<br>
155<br>
156<br>
157<br>
158<br>
159<br>
160<br>
161<br>
162<br>
163<br>
164<br>
165<br>
166<br>
167<br>
168<br>
169<br>
170<br>
171<br>
172<br>
173<br>
174<br>
175<br>
176<br>
177<br>
178<br>
179<br>
180<br>
181<br>
182<br>
183<br>
184<br>
185<br>
186<br>
187<br>
188<br>
189<br>
190<br>
191<br>
192<br>
193<br>
194<br>
195<br>
196<br>
197<br>
198<br>
199<br>
200<br>
201<br>
202<br>
203<br>
204<br>
205<br>
206<br>
207<br>
208<br>
209<br>
210<br>
211<br>
212<br>
213<br>
214<br>
215<br>
216<br>
217<br>
218<br>
219<br>
220<br>
221<br>
222<br>
223<br>
224<br>
225<br>
226<br>
227<br>
228<br>
229<br>
230<br>
231<br>
232<br>
233<br>
234<br>
235<br>
236<br>
237<br>
238<br>
239<br>
240<br>
241<br>
242<br>
243<br>
244<br>
245<br>
246<br>
247<br>
248<br>
249<br>
250<br>
251<br>
252<br>
253<br>
254<br>
255<br>
256<br>
257<br>
258<br>
259<br>
260<br>
261<br>
262<br>
263<br>
264<br>
265<br>
266<br>
267<br>
268<br>
269<br>
270<br>
271<br>
272<br>
273<br>
274<br>
275<br>
276<br>
277<br>
278<br>
279<br>
280<br>
281<br>
282<br>
283<br>
284<br>
285<br>
286<br>
287<br>
288<br>
289<br>
290<br>
291<br>
292<br>
293<br>
294<br>
295<br>
296<br>
297<br>
298<br>
299<br>
300<br>
301<br>
302<br>
303<br>
304<br>
305<br>
306<br>
307<br>
308<br>
309<br>
310<br>
311<br>
312<br>
313<br>
314<br>
315<br>
316<br>
317<br>
318<br>
319<br>
320<br>
321<br>
322<br>
323<br>
324<br>
325<br>
326<br>
327<br>
328<br>
329<br>
330<br>
331<br>
332<br>
333<br>
334<br>
335<br>
336<br>
337<br>
338<br>
339<br>
340<br>
341<br>
342<br>
343<br>
344<br>
345<br>
346<br>
347<br>
348<br>
349<br>
350<br>
351<br>
352<br>
353<br>
354<br>
355<br>
356<br>
357<br>
358<br>
359<br>
360<br>
361<br>
362<br>
363<br>
364<br>
365<br>
366<br>
367<br>
368<br>
369<br>
370<br>
371<br>
372<br>
373<br>
374<br>
375<br>
376<br>
377<br>
378<br>
379<br>
380<br>
381<br>
382<br>
383<br>
384<br>
385<br>
386<br>
387<br>
388<br>
389<br>
390<br>
391<br>
392<br>
393<br>
394<br>
395<br>
396<br>
397<br>
398<br>
399<br>
400<br>
401<br>
402<br>
403<br>
404<br>
405<br>
406<br>
407<br>
408<br>
409<br>
410<br>
411<br>
412<br>
413<br>
414<br>
415<br>
416<br>
417<br>
418<br>
419<br>
420<br>
421<br>
422<br>
423<br>
424<br>
425<br>
426<br>
427<br>
428<br>
429<br>
430<br>
431<br>
432<br>
433<br>
434<br>
435<br>
436<br>
437<br>
438<br>
439<br>
440<br>
441<br>
442<br>
443<br>
444<br>
445<br>
446<br>
447<br>
448<br>
449<br>
450<br>
451<br>
452<br>
453<br>
454<br>
455<br>
456<br>
457<br>
458<br>
459<br>
460<br>
461<br>
462<br>
463<br>
464<br>
465<br>
466<br>
467<br>
468<br>
469<br>
470<br>
471<br>
472<br>
473<br>
474<br>
475<br>
476<br>
477<br>
478<br>
479<br>
480<br>
481<br>
482<br>
483<br>
484<br>
485<br>
486<br>
487<br>
488<br>
489<br>
490<br>
491<br>
492<br>
493<br>
494<br>
495<br>
496<br>
497<br>
498<br>
499<br>
</div>
<div style="height: 1000px">this div makes the page scrollable</div>
</body>

View file

@ -0,0 +1,74 @@
<!DOCTYPE HTML>
<html style="overflow:hidden">
<head>
<meta charset="utf-8">
<!-- The viewport tag will result in APZ being in a "zoomed-in" state, assuming
the device width is less than 980px. -->
<meta name="viewport" content="width=980; initial-scale=1.0">
<title>Test for bug 1280013</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
SimpleTest.ok(screen.height > 500, "Screen height must be at least 500 pixels for this test to work");
// This listener will trigger the test to continue once APZ is done with
// processing the scroll.
SpecialPowers.Services.obs.addObserver(testDriver, "APZ:TransformEnd", false);
// Scroll down to the iframe. Do it in two drags instead of one in case the
// device screen is short
yield synthesizeNativeTouchDrag(document.body, 10, 400, 0, -(350 + TOUCH_SLOP));
yield synthesizeNativeTouchDrag(document.body, 10, 400, 0, -(350 + TOUCH_SLOP));
// Now the top of the visible area should be at y=700 of the top-level page,
// so if the screen is >= 500px tall, the entire iframe should be visible, at
// least vertically.
// However, because of the overflow:hidden on the root elements, all this
// scrolling is happening in APZ and is not reflected in the main-thread
// scroll position (it is stored in the callback transform instead). We check
// this by checking the scroll offset.
yield flushApzRepaints(testDriver);
SimpleTest.is(window.scrollY, 0, "Main-thread scroll position is still at 0");
// Scroll the iframe by 300px. Note that since the main-thread scroll position
// is still 0, the subframe's getBoundingClientRect is going to be off by
// 700 pixels, so we compensate for that here.
var subframe = document.getElementById('subframe');
yield synthesizeNativeTouchDrag(subframe, 10, 200 - 700, 0, -(300 + TOUCH_SLOP));
// Remove the observer, we don't need it any more.
SpecialPowers.Services.obs.removeObserver(testDriver, "APZ:TransformEnd", false);
// Flush any pending paints
yield flushApzRepaints(testDriver);
// get the displayport for the subframe
var utils = SpecialPowers.getDOMWindowUtils(window);
var contentPaints = utils.getContentAPZTestData().paints;
var lastPaint = convertScrollFrameData(contentPaints[contentPaints.length - 1].scrollFrames);
var foundIt = 0;
for (var scrollId in lastPaint) {
if (('contentDescription' in lastPaint[scrollId]) &&
(lastPaint[scrollId]['contentDescription'].includes('tall_html'))) {
var dp = getPropertyAsRect(lastPaint, scrollId, 'criticalDisplayport');
SimpleTest.ok(dp.y <= 0, 'The critical displayport top should be less than or equal to zero to cover the visible part of the subframe; it is ' + dp.y);
SimpleTest.ok(dp.y + dp.h >= subframe.clientHeight, 'The critical displayport bottom should be greater than the clientHeight; it is ' + (dp.y + dp.h));
foundIt++;
}
}
SimpleTest.is(foundIt, 1, "Found exactly one critical displayport for the subframe we were interested in.");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
</head>
<body style="overflow:hidden">
The iframe below is at (0, 800). Scroll it into view, and then scroll the contents. The content should be fully rendered in high-resolution.
<iframe id="subframe" style="position:absolute; left: 0px; top: 800px; width: 600px; height: 350px" src="helper_tall.html"></iframe>
</body>
</html>

View file

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Test pointer events are dispatched once for touch tap</title>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript">
function test() {
let pointerEventsList = ["pointerover", "pointerenter", "pointerdown",
"pointerup", "pointerleave", "pointerout"];
let pointerEventsCount = {};
pointerEventsList.forEach((eventName) => {
pointerEventsCount[eventName] = 0;
document.getElementById('div1').addEventListener(eventName, (event) => {
dump("Received event " + event.type + "\n");
++pointerEventsCount[event.type];
}, false);
});
document.addEventListener("click", (event) => {
is(event.target, document.getElementById('div1'), "Clicked on div (at " + event.clientX + "," + event.clientY + ")");
for (var key in pointerEventsCount) {
is(pointerEventsCount[key], 1, "Event " + key + " should be generated once");
}
subtestDone();
}, false);
synthesizeNativeTap(document.getElementById('div1'), 100, 100, () => {
dump("Finished synthesizing tap, waiting for div to be clicked...\n");
});
}
waitUntilApzStable().then(test);
</script>
</head>
<body>
<div id="div1" style="width: 200px; height: 200px; background: black"></div>
</body>
</html>

View file

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Test pointer events are dispatched once for touch tap</title>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript">
/** Test for Bug 1299195 **/
function runTests() {
let target0 = document.getElementById("target0");
let mouseup_count = 0;
let mousedown_count = 0;
let pointerup_count = 0;
let pointerdown_count = 0;
target0.addEventListener("mouseup", () => {
++mouseup_count;
if (mouseup_count == 2) {
is(mousedown_count, 2, "Double tap with touch should fire 2 mousedown events");
is(mouseup_count, 2, "Double tap with touch should fire 2 mouseup events");
is(pointerdown_count, 2, "Double tap with touch should fire 2 pointerdown events");
is(pointerup_count, 2, "Double tap with touch should fire 2 pointerup events");
subtestDone();
}
});
target0.addEventListener("mousedown", () => {
++mousedown_count;
});
target0.addEventListener("pointerup", () => {
++pointerup_count;
});
target0.addEventListener("pointerdown", () => {
++pointerdown_count;
});
synthesizeNativeTap(document.getElementById('target0'), 100, 100);
synthesizeNativeTap(document.getElementById('target0'), 100, 100);
}
waitUntilApzStable().then(runTests);
</script>
</head>
<body>
<div id="target0" style="width: 200px; height: 200px; background: green"></div>
</body>
</html>

View file

@ -0,0 +1,149 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=982141
-->
<head>
<meta charset="utf-8">
<meta name="viewport" content="user-scalable=no">
<title>Test for Bug 982141, helper page</title>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript">
// -------------------------------------------------------------------
// Infrastructure to get the test assertions to run at the right time.
// -------------------------------------------------------------------
var SimpleTest = window.opener.SimpleTest;
window.onload = function() {
window.addEventListener("MozAfterPaint", afterPaint, false);
};
var utils = SpecialPowers.getDOMWindowUtils(window);
function afterPaint(e) {
// If there is another paint pending, wait for it.
if (utils.isMozAfterPaintPending) {
return;
}
// Once there are no more paints pending, remove the
// MozAfterPaint listener and run the test logic.
window.removeEventListener("MozAfterPaint", afterPaint, false);
testBug982141();
}
// --------------------------------------------------------------------
// The actual logic for testing bug 982141.
//
// In this test we have a simple page with a scrollable <div> which has
// enough content to make it scrollable. We test that this <div> got
// a displayport.
// --------------------------------------------------------------------
function testBug982141() {
// Get the content- and compositor-side test data from nsIDOMWindowUtils.
var contentTestData = utils.getContentAPZTestData();
var compositorTestData = utils.getCompositorAPZTestData();
// Get the sequence number of the last paint on the compositor side.
// We do this before converting the APZ test data because the conversion
// loses the order of the paints.
SimpleTest.ok(compositorTestData.paints.length > 0,
"expected at least one paint in compositor test data");
var lastCompositorPaint = compositorTestData.paints[compositorTestData.paints.length - 1];
var lastCompositorPaintSeqNo = lastCompositorPaint.sequenceNumber;
// Convert the test data into a representation that's easier to navigate.
contentTestData = convertTestData(contentTestData);
compositorTestData = convertTestData(compositorTestData);
// Reconstruct the APZC tree structure in the last paint.
var apzcTree = buildApzcTree(compositorTestData.paints[lastCompositorPaintSeqNo]);
// The apzc tree for this page should consist of a single child APZC on
// the RCD node (the child is for scrollable <div>). Note that in e10s/B2G
// cases the RCD will be the root of the tree but on Fennec it will not.
var rcd = findRcdNode(apzcTree);
SimpleTest.ok(rcd != null, "found the RCD node");
SimpleTest.is(rcd.children.length, 1, "expected a single child APZC");
var childScrollId = rcd.children[0].scrollId;
// We should have content-side data for the same paint.
SimpleTest.ok(lastCompositorPaintSeqNo in contentTestData.paints,
"expected a content paint with sequence number" + lastCompositorPaintSeqNo);
var correspondingContentPaint = contentTestData.paints[lastCompositorPaintSeqNo];
var dp = getPropertyAsRect(correspondingContentPaint, childScrollId, 'displayport');
var subframe = document.getElementById('subframe');
// The clientWidth and clientHeight may be less than 50 if there are scrollbars showing.
// In general they will be (50 - <scrollbarwidth>, 50 - <scrollbarheight>).
SimpleTest.ok(subframe.clientWidth > 0, "Expected a non-zero clientWidth, got: " + subframe.clientWidth);
SimpleTest.ok(subframe.clientHeight > 0, "Expected a non-zero clientHeight, got: " + subframe.clientHeight);
SimpleTest.ok(dp.w >= subframe.clientWidth && dp.h >= subframe.clientHeight,
"expected a displayport at least as large as the scrollable element, got " + JSON.stringify(dp));
window.opener.finishTest();
}
</script>
</head>
<body style="overflow: hidden;"><!-- This combined with the user-scalable=no ensures the root frame is not scrollable -->
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982141">Mozilla Bug 982141</a>
<!-- A scrollable subframe, with enough content to make it have a nonzero scroll range -->
<div id="subframe" style="height: 50px; width: 50px; overflow: scroll">
<div style="width: 100px">
Wide content so that the vertical scrollbar for the parent div
doesn't eat into the 50px width and reduce the width of the
displayport.
</div>
Line 1<br>
Line 2<br>
Line 3<br>
Line 4<br>
Line 5<br>
Line 6<br>
Line 7<br>
Line 8<br>
Line 9<br>
Line 10<br>
Line 11<br>
Line 12<br>
Line 13<br>
Line 14<br>
Line 15<br>
Line 16<br>
Line 17<br>
Line 18<br>
Line 19<br>
Line 20<br>
Line 21<br>
Line 22<br>
Line 23<br>
Line 24<br>
Line 25<br>
Line 26<br>
Line 27<br>
Line 28<br>
Line 29<br>
Line 30<br>
Line 31<br>
Line 32<br>
Line 33<br>
Line 34<br>
Line 35<br>
Line 36<br>
Line 37<br>
Line 38<br>
Line 39<br>
Line 40<br>
Line 41<br>
Line 42<br>
Line 43<br>
Line 44<br>
Line 45<br>
Line 46<br>
Line 40<br>
Line 48<br>
Line 49<br>
Line 50<br>
</div>
</body>
</html>

View file

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity mouse-clicking test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* clickButton(testDriver) {
document.addEventListener('click', clicked, false);
if (getQueryArgs()['dtc']) {
// force a dispatch-to-content region on the document
document.addEventListener('wheel', function() { /* no-op */ }, { passive: false });
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
}
synthesizeNativeClick(document.getElementById('b'), 5, 5, function() {
dump("Finished synthesizing click, waiting for button to be clicked...\n");
});
}
function clicked(e) {
is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
subtestDone();
}
waitUntilApzStable()
.then(runContinuation(clickButton));
</script>
</head>
<body>
<button id="b" style="width: 10px; height: 10px"></button>
</body>
</html>

View file

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity panning test for scrollable div</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function scrollOuter() {
var transformEnd = function() {
SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
dump("Transform complete; flushing repaints...\n");
flushApzRepaints(checkScroll);
};
SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
synthesizeNativeTouchDrag(document.getElementById('outer'), 10, 100, 0, -(50 + TOUCH_SLOP));
dump("Finished native drag, waiting for transform-end observer...\n");
}
function checkScroll() {
var outerScroll = document.getElementById('outer').scrollTop;
is(outerScroll, 50, "check that the div scrolled");
subtestDone();
}
waitUntilApzStable().then(scrollOuter);
</script>
</head>
<body>
<div id="outer" style="height: 250px; border: solid 1px black; overflow:scroll">
<div style="height: 5000px; background-color: lightblue">
This div makes the |outer| div scrollable.
</div>
</div>
<div style="height: 5000px; background-color: lightgreen;">
This div makes the top-level page scrollable.
</div>
</body>
</html>

View file

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity mouse-drag click test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
document.addEventListener('click', clicked, false);
// Ensure the pointer is inside the window
yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseMoveEventMsg(), testDriver);
// mouse down, move it around, and release it near where it went down. this
// should generate a click at the release point
yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseDownEventMsg(), testDriver);
yield synthesizeNativeMouseEvent(document.getElementById('b'), 100, 100, nativeMouseMoveEventMsg(), testDriver);
yield synthesizeNativeMouseEvent(document.getElementById('b'), 10, 10, nativeMouseMoveEventMsg(), testDriver);
yield synthesizeNativeMouseEvent(document.getElementById('b'), 8, 8, nativeMouseUpEventMsg(), testDriver);
dump("Finished synthesizing click with a drag in the middle\n");
}
function clicked(e) {
// The mouse down at (5, 5) should not have generated a click, but the up
// at (8, 8) should have.
is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
is(e.clientX, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().left), 'x-coord of click event looks sane');
is(e.clientY, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().top), 'y-coord of click event looks sane');
subtestDone();
}
waitUntilApzStable()
.then(runContinuation(test));
</script>
</head>
<body>
<button id="b" style="width: 10px; height: 10px"></button>
</body>
</html>

View file

@ -0,0 +1,603 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Dragging the mouse on a content-implemented scrollbar</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<style>
body {
background: linear-gradient(135deg, red, blue);
}
#scrollbar {
position:fixed;
top: 0;
right: 10px;
height: 100%;
width: 150px;
background-color: gray;
}
</style>
<script type="text/javascript">
var bar = null;
var mouseDown = false;
function moveTo(mouseY, testDriver) {
var fraction = (mouseY - bar.getBoundingClientRect().top) / bar.getBoundingClientRect().height;
fraction = Math.max(0, fraction);
fraction = Math.min(1, fraction);
var oldScrollPos = document.scrollingElement.scrollTop;
var newScrollPos = fraction * window.scrollMaxY;
SimpleTest.ok(newScrollPos > oldScrollPos, "Scroll position strictly increased");
// split the scroll in two with a paint in between, just to increase the
// complexity of the simulated web content, and to ensure this works as well.
document.scrollingElement.scrollTop = (oldScrollPos + newScrollPos) / 2;
waitForAllPaints(function() {
document.scrollingElement.scrollTop = newScrollPos;
testDriver();
});
}
function setupDragging(testDriver) {
bar = document.getElementById('scrollbar');
mouseDown = false;
bar.addEventListener('mousedown', function(e) {
mouseDown = true;
moveTo(e.clientY, testDriver);
}, true);
bar.addEventListener('mousemove', function(e) {
if (mouseDown) {
dump("Got mousemove clientY " + e.clientY + "\n");
moveTo(e.clientY, testDriver);
e.stopPropagation();
}
}, true);
bar.addEventListener('mouseup', function(e) {
mouseDown = false;
}, true);
window.addEventListener('mousemove', function(e) {
if (mouseDown) {
SimpleTest.ok(false, "The mousemove at " + e.clientY + " was not stopped by the bar listener, and is a glitchy event!");
setTimeout(testDriver, 0);
}
}, false);
}
function* test(testDriver) {
setupDragging(testDriver);
// Move the mouse to the "scrollbar" (the div upon which dragging changes scroll position)
yield synthesizeNativeMouseEvent(bar, 10, 10, nativeMouseMoveEventMsg(), testDriver);
// mouse down
yield synthesizeNativeMouseEvent(bar, 10, 10, nativeMouseDownEventMsg());
// drag vertically by 400px, in 50px increments
yield synthesizeNativeMouseEvent(bar, 10, 60, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 110, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 160, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 210, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 260, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 310, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 360, nativeMouseMoveEventMsg());
yield synthesizeNativeMouseEvent(bar, 10, 410, nativeMouseMoveEventMsg());
// and release
yield synthesizeNativeMouseEvent(bar, 10, 410, nativeMouseUpEventMsg(), testDriver);
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
</head>
<body>
<div id="scrollbar">Drag up and down on this bar. The background/scrollbar shouldn't glitch</div>
This is a tall page<br/>
1<br/>
2<br/>
3<br/>
4<br/>
5<br/>
6<br/>
7<br/>
8<br/>
9<br/>
10<br/>
11<br/>
12<br/>
13<br/>
14<br/>
15<br/>
16<br/>
17<br/>
18<br/>
19<br/>
20<br/>
21<br/>
22<br/>
23<br/>
24<br/>
25<br/>
26<br/>
27<br/>
28<br/>
29<br/>
30<br/>
31<br/>
32<br/>
33<br/>
34<br/>
35<br/>
36<br/>
37<br/>
38<br/>
39<br/>
40<br/>
41<br/>
42<br/>
43<br/>
44<br/>
45<br/>
46<br/>
47<br/>
48<br/>
49<br/>
50<br/>
51<br/>
52<br/>
53<br/>
54<br/>
55<br/>
56<br/>
57<br/>
58<br/>
59<br/>
60<br/>
61<br/>
62<br/>
63<br/>
64<br/>
65<br/>
66<br/>
67<br/>
68<br/>
69<br/>
70<br/>
71<br/>
72<br/>
73<br/>
74<br/>
75<br/>
76<br/>
77<br/>
78<br/>
79<br/>
80<br/>
81<br/>
82<br/>
83<br/>
84<br/>
85<br/>
86<br/>
87<br/>
88<br/>
89<br/>
90<br/>
91<br/>
92<br/>
93<br/>
94<br/>
95<br/>
96<br/>
97<br/>
98<br/>
99<br/>
100<br/>
101<br/>
102<br/>
103<br/>
104<br/>
105<br/>
106<br/>
107<br/>
108<br/>
109<br/>
110<br/>
111<br/>
112<br/>
113<br/>
114<br/>
115<br/>
116<br/>
117<br/>
118<br/>
119<br/>
120<br/>
121<br/>
122<br/>
123<br/>
124<br/>
125<br/>
126<br/>
127<br/>
128<br/>
129<br/>
130<br/>
131<br/>
132<br/>
133<br/>
134<br/>
135<br/>
136<br/>
137<br/>
138<br/>
139<br/>
140<br/>
141<br/>
142<br/>
143<br/>
144<br/>
145<br/>
146<br/>
147<br/>
148<br/>
149<br/>
150<br/>
151<br/>
152<br/>
153<br/>
154<br/>
155<br/>
156<br/>
157<br/>
158<br/>
159<br/>
160<br/>
161<br/>
162<br/>
163<br/>
164<br/>
165<br/>
166<br/>
167<br/>
168<br/>
169<br/>
170<br/>
171<br/>
172<br/>
173<br/>
174<br/>
175<br/>
176<br/>
177<br/>
178<br/>
179<br/>
180<br/>
181<br/>
182<br/>
183<br/>
184<br/>
185<br/>
186<br/>
187<br/>
188<br/>
189<br/>
190<br/>
191<br/>
192<br/>
193<br/>
194<br/>
195<br/>
196<br/>
197<br/>
198<br/>
199<br/>
200<br/>
201<br/>
202<br/>
203<br/>
204<br/>
205<br/>
206<br/>
207<br/>
208<br/>
209<br/>
210<br/>
211<br/>
212<br/>
213<br/>
214<br/>
215<br/>
216<br/>
217<br/>
218<br/>
219<br/>
220<br/>
221<br/>
222<br/>
223<br/>
224<br/>
225<br/>
226<br/>
227<br/>
228<br/>
229<br/>
230<br/>
231<br/>
232<br/>
233<br/>
234<br/>
235<br/>
236<br/>
237<br/>
238<br/>
239<br/>
240<br/>
241<br/>
242<br/>
243<br/>
244<br/>
245<br/>
246<br/>
247<br/>
248<br/>
249<br/>
250<br/>
251<br/>
252<br/>
253<br/>
254<br/>
255<br/>
256<br/>
257<br/>
258<br/>
259<br/>
260<br/>
261<br/>
262<br/>
263<br/>
264<br/>
265<br/>
266<br/>
267<br/>
268<br/>
269<br/>
270<br/>
271<br/>
272<br/>
273<br/>
274<br/>
275<br/>
276<br/>
277<br/>
278<br/>
279<br/>
280<br/>
281<br/>
282<br/>
283<br/>
284<br/>
285<br/>
286<br/>
287<br/>
288<br/>
289<br/>
290<br/>
291<br/>
292<br/>
293<br/>
294<br/>
295<br/>
296<br/>
297<br/>
298<br/>
299<br/>
300<br/>
301<br/>
302<br/>
303<br/>
304<br/>
305<br/>
306<br/>
307<br/>
308<br/>
309<br/>
310<br/>
311<br/>
312<br/>
313<br/>
314<br/>
315<br/>
316<br/>
317<br/>
318<br/>
319<br/>
320<br/>
321<br/>
322<br/>
323<br/>
324<br/>
325<br/>
326<br/>
327<br/>
328<br/>
329<br/>
330<br/>
331<br/>
332<br/>
333<br/>
334<br/>
335<br/>
336<br/>
337<br/>
338<br/>
339<br/>
340<br/>
341<br/>
342<br/>
343<br/>
344<br/>
345<br/>
346<br/>
347<br/>
348<br/>
349<br/>
350<br/>
351<br/>
352<br/>
353<br/>
354<br/>
355<br/>
356<br/>
357<br/>
358<br/>
359<br/>
360<br/>
361<br/>
362<br/>
363<br/>
364<br/>
365<br/>
366<br/>
367<br/>
368<br/>
369<br/>
370<br/>
371<br/>
372<br/>
373<br/>
374<br/>
375<br/>
376<br/>
377<br/>
378<br/>
379<br/>
380<br/>
381<br/>
382<br/>
383<br/>
384<br/>
385<br/>
386<br/>
387<br/>
388<br/>
389<br/>
390<br/>
391<br/>
392<br/>
393<br/>
394<br/>
395<br/>
396<br/>
397<br/>
398<br/>
399<br/>
400<br/>
401<br/>
402<br/>
403<br/>
404<br/>
405<br/>
406<br/>
407<br/>
408<br/>
409<br/>
410<br/>
411<br/>
412<br/>
413<br/>
414<br/>
415<br/>
416<br/>
417<br/>
418<br/>
419<br/>
420<br/>
421<br/>
422<br/>
423<br/>
424<br/>
425<br/>
426<br/>
427<br/>
428<br/>
429<br/>
430<br/>
431<br/>
432<br/>
433<br/>
434<br/>
435<br/>
436<br/>
437<br/>
438<br/>
439<br/>
440<br/>
441<br/>
442<br/>
443<br/>
444<br/>
445<br/>
446<br/>
447<br/>
448<br/>
449<br/>
450<br/>
451<br/>
452<br/>
453<br/>
454<br/>
455<br/>
456<br/>
457<br/>
458<br/>
459<br/>
460<br/>
461<br/>
462<br/>
463<br/>
464<br/>
465<br/>
466<br/>
467<br/>
468<br/>
469<br/>
470<br/>
471<br/>
472<br/>
473<br/>
474<br/>
475<br/>
476<br/>
477<br/>
478<br/>
479<br/>
480<br/>
481<br/>
482<br/>
483<br/>
484<br/>
485<br/>
486<br/>
487<br/>
488<br/>
489<br/>
490<br/>
491<br/>
492<br/>
493<br/>
494<br/>
495<br/>
496<br/>
497<br/>
498<br/>
499<br/>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<!-- The purpose of the 'id' on the HTML element is to get something
identifiable to show up in the root scroll frame's content description,
so we can check it for layerization. -->
<html id="outer3">
<head>
<link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
</head>
<body>
<div id="inner3" class="inner-frame">
<div class="inner-content"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<!-- The purpose of the 'id' on the HTML element is to get something
identifiable to show up in the root scroll frame's content description,
so we can check it for layerization. -->
<html id="outer4">
<head>
<link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
</head>
<body>
<div id="inner4" class="inner-frame">
<div class="inner-content"></div>
</div>
</body>
</html>

View file

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity panning test for scrollable div</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function scrollOuter() {
var outer = document.getElementById('outer');
var transformEnd = function() {
SpecialPowers.Services.obs.removeObserver(transformEnd, "APZ:TransformEnd", false);
dump("Transform complete; flushing repaints...\n");
flushApzRepaints(checkScroll, outer.contentWindow);
};
SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
synthesizeNativeTouchDrag(outer.contentDocument.body, 10, 100, 0, -(50 + TOUCH_SLOP));
dump("Finished native drag, waiting for transform-end observer...\n");
}
function checkScroll() {
var outerScroll = document.getElementById('outer').contentWindow.scrollY;
is(outerScroll, 50, "check that the iframe scrolled");
subtestDone();
}
waitUntilApzStable().then(scrollOuter);
</script>
</head>
<body>
<iframe id="outer" style="height: 250px; border: solid 1px black" src="data:text/html,<body style='height:5000px'>"></iframe>
<div style="height: 5000px; background-color: lightgreen;">
This div makes the top-level page scrollable.
</div>
</body>
</html>

View file

@ -0,0 +1,81 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Ensure we get a touch-cancel after a contextmenu comes up</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function longPressLink() {
synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
dump("Finished synthesizing touch-start, waiting for events...\n");
});
}
var eventsFired = 0;
function recordEvent(e) {
if (getPlatform() == "windows") {
// On Windows we get a mouselongtap event once the long-tap has been detected
// by APZ, and that's what we use as the trigger to lift the finger. That then
// triggers the contextmenu. This matches the platform convention.
switch (eventsFired) {
case 0: is(e.type, 'touchstart', 'Got a touchstart'); break;
case 1:
is(e.type, 'mouselongtap', 'Got a mouselongtap');
synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE);
break;
case 2: is(e.type, 'touchend', 'Got a touchend'); break;
case 3: is(e.type, 'contextmenu', 'Got a contextmenu'); e.preventDefault(); break;
default: ok(false, 'Got an unexpected event of type ' + e.type); break;
}
eventsFired++;
if (eventsFired == 4) {
dump("Finished waiting for events, doing an APZ flush to see if any more unexpected events come through...\n");
flushApzRepaints(function() {
dump("Done APZ flush, ending test...\n");
subtestDone();
});
}
} else {
// On non-Windows platforms we get a contextmenu event once the long-tap has
// been detected. Since we prevent-default that, we don't get a mouselongtap
// event at all, and instead get a touchcancel.
switch (eventsFired) {
case 0: is(e.type, 'touchstart', 'Got a touchstart'); break;
case 1: is(e.type, 'contextmenu', 'Got a contextmenu'); e.preventDefault(); break;
case 2: is(e.type, 'touchcancel', 'Got a touchcancel'); break;
default: ok(false, 'Got an unexpected event of type ' + e.type); break;
}
eventsFired++;
if (eventsFired == 3) {
synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
dump("Finished synthesizing touch-end, doing an APZ flush to see if any more unexpected events come through...\n");
flushApzRepaints(function() {
dump("Done APZ flush, ending test...\n");
subtestDone();
});
});
}
}
}
window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
window.addEventListener('touchend', recordEvent, { passive: true, capture: true });
window.addEventListener('touchcancel', recordEvent, true);
window.addEventListener('contextmenu', recordEvent, true);
SpecialPowers.addChromeEventListener('mouselongtap', recordEvent, true);
waitUntilApzStable()
.then(longPressLink);
</script>
</head>
<body>
<a id="b" href="#">Link to nowhere</a>
</body>
</html>

View file

@ -0,0 +1,46 @@
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Wheel-scrolling over inactive subframe with perspective</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
var subframe = document.getElementById('scroll');
// scroll over the middle of the subframe, to make sure it scrolls,
// not the page
var scrollPos = subframe.scrollTop;
yield moveMouseAndScrollWheelOver(subframe, 100, 100, testDriver);
dump("after scroll, subframe.scrollTop = " + subframe.scrollTop + "\n");
ok(subframe.scrollTop > scrollPos, "subframe scrolled after wheeling over it");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
<style>
#scroll {
width: 200px;
height: 200px;
overflow: scroll;
perspective: 400px;
}
#scrolled {
width: 200px;
height: 1000px; /* so the subframe has room to scroll */
background: linear-gradient(red, blue); /* so you can see it scroll */
transform: translateZ(0px); /* so the perspective makes it to the display list */
}
</style>
</head>
<body>
<div id="scroll">
<div id="scrolled"></div>
</div>
<div style="height: 5000px;"></div><!-- So the page is scrollable as well -->
</body>
</head>

View file

@ -0,0 +1,47 @@
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Wheel-scrolling over inactive subframe with z-index</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
var subframe = document.getElementById('scroll');
// scroll over the middle of the subframe, and make sure that it scrolls,
// not the page
var scrollPos = subframe.scrollTop;
yield moveMouseAndScrollWheelOver(subframe, 100, 100, testDriver);
dump("after scroll, subframe.scrollTop = " + subframe.scrollTop + "\n");
ok(subframe.scrollTop > scrollPos, "subframe scrolled after wheeling over it");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
<style>
#scroll {
width: 200px;
height: 200px;
overflow: scroll;
}
#scrolled {
width: 200px;
height: 1000px; /* so the subframe has room to scroll */
z-index: 2;
background: linear-gradient(red, blue); /* so you can see it scroll */
transform: translateZ(0px); /* to force active layers */
will-change: transform; /* to force active layers */
}
</style>
</head>
<body>
<div id="scroll">
<div id="scrolled"></div>
</div>
<div style="height: 5000px;"></div><!-- So the page is scrollable as well -->
</body>
</head>

View file

@ -0,0 +1,62 @@
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function* test(testDriver) {
var iframeWin = document.getElementById('iframe').contentWindow;
// scroll over the middle of the iframe's position:sticky element, check
// that it scrolls the iframe
var scrollPos = iframeWin.scrollY;
yield moveMouseAndScrollWheelOver(iframeWin.document.body, 50, 150, testDriver);
ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:sticky element");
// same, but using the iframe's position:fixed element
scrollPos = iframeWin.scrollY;
yield moveMouseAndScrollWheelOver(iframeWin.document.body, 250, 150, testDriver);
ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:fixed element");
// same, but scrolling the scrollable frame *inside* the position:fixed item
var fpos = document.getElementById('fpos_scrollable');
scrollPos = fpos.scrollTop;
yield moveMouseAndScrollWheelOver(fpos, 50, 150, testDriver);
ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled");
// wait for it to layerize fully and then try again
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
scrollPos = fpos.scrollTop;
yield moveMouseAndScrollWheelOver(fpos, 50, 150, testDriver);
ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled after layerization");
// same, but using the top-level window's position:sticky element
scrollPos = window.scrollY;
yield moveMouseAndScrollWheelOver(document.body, 50, 150, testDriver);
ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:sticky element");
// same, but using the top-level window's position:fixed element
scrollPos = window.scrollY;
yield moveMouseAndScrollWheelOver(document.body, 250, 150, testDriver);
ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:fixed element");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
</head>
<body style="height:5000px; margin:0">
<div style="position:sticky; width: 100px; height: 300px; top: 0; background-color:red">sticky</div>
<div style="position:fixed; width: 100px; height: 300px; top: 0; left: 200px; background-color: green">fixed</div>
<iframe id='iframe' width="300" height="400" src="data:text/html,<body style='height:5000px; margin:0'><div style='position:sticky; width:100px; height:300px; top: 0; background-color:red'>sticky</div><div style='position:fixed; right:0; top: 0; width:100px; height:300px; background-color:green'>fixed</div>"></iframe>
<div id="fpos_scrollable" style="position:fixed; width: 100px; height: 300px; top: 0; left: 400px; background-color: red; overflow:scroll">
<div style="background-color: blue; height: 1000px; margin: 3px">scrollable content inside a fixed-pos item</div>
</div>
</body>
</head>

View file

@ -0,0 +1,61 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity touch-tapping test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function startTest() {
if (window.scrollY == 0) {
// the scrollframe is not yet marked as APZ-scrollable. Mark it so and
// start over.
window.scrollTo(0, 1);
waitForApzFlushedRepaints(startTest);
return;
}
// This is a scroll by 20px that should use paint-skipping if possible.
// If paint-skipping is enabled, this should not trigger a paint, but go
// directly to the compositor using an empty transaction. We check for this
// by ensuring the document element did not get painted.
var utils = window.opener.SpecialPowers.getDOMWindowUtils(window);
var elem = document.documentElement;
var skipping = location.search == '?true';
utils.checkAndClearPaintedState(elem);
window.scrollTo(0, 20);
waitForAllPaints(function() {
if (skipping) {
is(utils.checkAndClearPaintedState(elem), false, "Document element didn't get painted");
}
// After that's done, we click on the button to make sure the
// skipped-paint codepath still has working APZ event transformations.
clickButton();
});
}
function clickButton() {
document.addEventListener('click', clicked, false);
synthesizeNativeTap(document.getElementById('b'), 5, 5, function() {
dump("Finished synthesizing tap, waiting for button to be clicked...\n");
});
}
function clicked(e) {
is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
subtestDone();
}
waitUntilApzStable().then(startTest);
</script>
</head>
<body style="height: 5000px">
<div style="height: 50px">spacer</div>
<button id="b" style="width: 10px; height: 10px"></button>
</body>
</html>

View file

@ -0,0 +1,15 @@
body {
height: 500px;
}
.inner-frame {
margin-top: 50px; /* this should be at least 30px */
height: 200%;
width: 75%;
overflow: scroll;
}
.inner-content {
height: 200%;
width: 200%;
background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
}

View file

@ -0,0 +1,504 @@
<html id="tall_html">
<body>
This is a tall page<br/>
1<br/>
2<br/>
3<br/>
4<br/>
5<br/>
6<br/>
7<br/>
8<br/>
9<br/>
10<br/>
11<br/>
12<br/>
13<br/>
14<br/>
15<br/>
16<br/>
17<br/>
18<br/>
19<br/>
20<br/>
21<br/>
22<br/>
23<br/>
24<br/>
25<br/>
26<br/>
27<br/>
28<br/>
29<br/>
30<br/>
31<br/>
32<br/>
33<br/>
34<br/>
35<br/>
36<br/>
37<br/>
38<br/>
39<br/>
40<br/>
41<br/>
42<br/>
43<br/>
44<br/>
45<br/>
46<br/>
47<br/>
48<br/>
49<br/>
50<br/>
51<br/>
52<br/>
53<br/>
54<br/>
55<br/>
56<br/>
57<br/>
58<br/>
59<br/>
60<br/>
61<br/>
62<br/>
63<br/>
64<br/>
65<br/>
66<br/>
67<br/>
68<br/>
69<br/>
70<br/>
71<br/>
72<br/>
73<br/>
74<br/>
75<br/>
76<br/>
77<br/>
78<br/>
79<br/>
80<br/>
81<br/>
82<br/>
83<br/>
84<br/>
85<br/>
86<br/>
87<br/>
88<br/>
89<br/>
90<br/>
91<br/>
92<br/>
93<br/>
94<br/>
95<br/>
96<br/>
97<br/>
98<br/>
99<br/>
100<br/>
101<br/>
102<br/>
103<br/>
104<br/>
105<br/>
106<br/>
107<br/>
108<br/>
109<br/>
110<br/>
111<br/>
112<br/>
113<br/>
114<br/>
115<br/>
116<br/>
117<br/>
118<br/>
119<br/>
120<br/>
121<br/>
122<br/>
123<br/>
124<br/>
125<br/>
126<br/>
127<br/>
128<br/>
129<br/>
130<br/>
131<br/>
132<br/>
133<br/>
134<br/>
135<br/>
136<br/>
137<br/>
138<br/>
139<br/>
140<br/>
141<br/>
142<br/>
143<br/>
144<br/>
145<br/>
146<br/>
147<br/>
148<br/>
149<br/>
150<br/>
151<br/>
152<br/>
153<br/>
154<br/>
155<br/>
156<br/>
157<br/>
158<br/>
159<br/>
160<br/>
161<br/>
162<br/>
163<br/>
164<br/>
165<br/>
166<br/>
167<br/>
168<br/>
169<br/>
170<br/>
171<br/>
172<br/>
173<br/>
174<br/>
175<br/>
176<br/>
177<br/>
178<br/>
179<br/>
180<br/>
181<br/>
182<br/>
183<br/>
184<br/>
185<br/>
186<br/>
187<br/>
188<br/>
189<br/>
190<br/>
191<br/>
192<br/>
193<br/>
194<br/>
195<br/>
196<br/>
197<br/>
198<br/>
199<br/>
200<br/>
201<br/>
202<br/>
203<br/>
204<br/>
205<br/>
206<br/>
207<br/>
208<br/>
209<br/>
210<br/>
211<br/>
212<br/>
213<br/>
214<br/>
215<br/>
216<br/>
217<br/>
218<br/>
219<br/>
220<br/>
221<br/>
222<br/>
223<br/>
224<br/>
225<br/>
226<br/>
227<br/>
228<br/>
229<br/>
230<br/>
231<br/>
232<br/>
233<br/>
234<br/>
235<br/>
236<br/>
237<br/>
238<br/>
239<br/>
240<br/>
241<br/>
242<br/>
243<br/>
244<br/>
245<br/>
246<br/>
247<br/>
248<br/>
249<br/>
250<br/>
251<br/>
252<br/>
253<br/>
254<br/>
255<br/>
256<br/>
257<br/>
258<br/>
259<br/>
260<br/>
261<br/>
262<br/>
263<br/>
264<br/>
265<br/>
266<br/>
267<br/>
268<br/>
269<br/>
270<br/>
271<br/>
272<br/>
273<br/>
274<br/>
275<br/>
276<br/>
277<br/>
278<br/>
279<br/>
280<br/>
281<br/>
282<br/>
283<br/>
284<br/>
285<br/>
286<br/>
287<br/>
288<br/>
289<br/>
290<br/>
291<br/>
292<br/>
293<br/>
294<br/>
295<br/>
296<br/>
297<br/>
298<br/>
299<br/>
300<br/>
301<br/>
302<br/>
303<br/>
304<br/>
305<br/>
306<br/>
307<br/>
308<br/>
309<br/>
310<br/>
311<br/>
312<br/>
313<br/>
314<br/>
315<br/>
316<br/>
317<br/>
318<br/>
319<br/>
320<br/>
321<br/>
322<br/>
323<br/>
324<br/>
325<br/>
326<br/>
327<br/>
328<br/>
329<br/>
330<br/>
331<br/>
332<br/>
333<br/>
334<br/>
335<br/>
336<br/>
337<br/>
338<br/>
339<br/>
340<br/>
341<br/>
342<br/>
343<br/>
344<br/>
345<br/>
346<br/>
347<br/>
348<br/>
349<br/>
350<br/>
351<br/>
352<br/>
353<br/>
354<br/>
355<br/>
356<br/>
357<br/>
358<br/>
359<br/>
360<br/>
361<br/>
362<br/>
363<br/>
364<br/>
365<br/>
366<br/>
367<br/>
368<br/>
369<br/>
370<br/>
371<br/>
372<br/>
373<br/>
374<br/>
375<br/>
376<br/>
377<br/>
378<br/>
379<br/>
380<br/>
381<br/>
382<br/>
383<br/>
384<br/>
385<br/>
386<br/>
387<br/>
388<br/>
389<br/>
390<br/>
391<br/>
392<br/>
393<br/>
394<br/>
395<br/>
396<br/>
397<br/>
398<br/>
399<br/>
400<br/>
401<br/>
402<br/>
403<br/>
404<br/>
405<br/>
406<br/>
407<br/>
408<br/>
409<br/>
410<br/>
411<br/>
412<br/>
413<br/>
414<br/>
415<br/>
416<br/>
417<br/>
418<br/>
419<br/>
420<br/>
421<br/>
422<br/>
423<br/>
424<br/>
425<br/>
426<br/>
427<br/>
428<br/>
429<br/>
430<br/>
431<br/>
432<br/>
433<br/>
434<br/>
435<br/>
436<br/>
437<br/>
438<br/>
439<br/>
440<br/>
441<br/>
442<br/>
443<br/>
444<br/>
445<br/>
446<br/>
447<br/>
448<br/>
449<br/>
450<br/>
451<br/>
452<br/>
453<br/>
454<br/>
455<br/>
456<br/>
457<br/>
458<br/>
459<br/>
460<br/>
461<br/>
462<br/>
463<br/>
464<br/>
465<br/>
466<br/>
467<br/>
468<br/>
469<br/>
470<br/>
471<br/>
472<br/>
473<br/>
474<br/>
475<br/>
476<br/>
477<br/>
478<br/>
479<br/>
480<br/>
481<br/>
482<br/>
483<br/>
484<br/>
485<br/>
486<br/>
487<br/>
488<br/>
489<br/>
490<br/>
491<br/>
492<br/>
493<br/>
494<br/>
495<br/>
496<br/>
497<br/>
498<br/>
499<br/>
</body>
</html>

View file

@ -0,0 +1,32 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity touch-tapping test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function clickButton() {
document.addEventListener('click', clicked, false);
synthesizeNativeTap(document.getElementById('b'), 5, 5, function() {
dump("Finished synthesizing tap, waiting for button to be clicked...\n");
});
}
function clicked(e) {
is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
subtestDone();
}
waitUntilApzStable().then(clickButton);
</script>
</head>
<body>
<button id="b" style="width: 10px; height: 10px"></button>
</body>
</html>

View file

@ -0,0 +1,33 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity touch-tapping test with fullzoom</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function clickButton() {
document.addEventListener('click', clicked, false);
synthesizeNativeTap(document.getElementById('b'), 5, 5, function() {
dump("Finished synthesizing tap, waiting for button to be clicked...\n");
});
}
function clicked(e) {
is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
subtestDone();
}
SpecialPowers.setFullZoom(window, 2.0);
waitUntilApzStable().then(clickButton);
</script>
</head>
<body>
<button id="b" style="width: 10px; height: 10px; position: relative; top: 100px"></button>
</body>
</html>

View file

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Ensure APZ doesn't wait for passive listeners</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
var touchdownTime;
function longPressLink() {
synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, function() {
dump("Finished synthesizing touch-start, waiting for events...\n");
});
}
var touchstartReceived = false;
function recordEvent(e) {
if (!touchstartReceived) {
touchstartReceived = true;
is(e.type, 'touchstart', 'Got a touchstart');
e.preventDefault(); // should be a no-op because it's a passive listener
return;
}
// If APZ decides to wait for the content response on a particular input block,
// it needs to wait until both the touchstart and touchmove event are handled
// by the main thread. In this case there is no touchmove at all, so APZ would
// end up waiting indefinitely and time out the test. The fact that we get this
// contextmenu event (mouselongtap on Windows) at all means that APZ decided
// not to wait for the content response, which is the desired behaviour, since
// the touchstart listener was registered as a passive listener.
if (getPlatform() == "windows") {
is(e.type, 'mouselongtap', 'Got a mouselongtap');
} else {
is(e.type, 'contextmenu', 'Got a contextmenu');
}
e.preventDefault();
synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
dump("Finished synthesizing touch-end to clear state; finishing test...\n");
subtestDone();
});
}
window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
if (getPlatform() == "windows") {
SpecialPowers.addChromeEventListener('mouselongtap', recordEvent, true);
} else {
window.addEventListener('contextmenu', recordEvent, true);
}
waitUntilApzStable()
.then(longPressLink);
</script>
</head>
<body>
<a id="b" href="#">Link to nowhere</a>
</body>
</html>

View file

@ -0,0 +1,115 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Sanity touch-action test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function checkScroll(x, y, desc) {
is(window.scrollX, x, desc + " - x axis");
is(window.scrollY, y, desc + " - y axis");
}
function* test(testDriver) {
var target = document.getElementById('target');
document.body.addEventListener('touchend', testDriver, { passive: true });
// drag the page up to scroll down by 50px
yield ok(synthesizeNativeTouchDrag(target, 10, 100, 0, -(50 + TOUCH_SLOP)),
"Synthesized native vertical drag (1), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(0, 50, "After first vertical drag, with pan-y" );
// switch style to pan-x
document.body.style.touchAction = 'pan-x';
ok(true, "Waiting for pan-x to propagate...");
yield waitForAllPaintsFlushed(function() {
flushApzRepaints(testDriver);
});
// drag the page up to scroll down by 50px, but it won't happen because pan-x
yield ok(synthesizeNativeTouchDrag(target, 10, 100, 0, -(50 + TOUCH_SLOP)),
"Synthesized native vertical drag (2), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(0, 50, "After second vertical drag, with pan-x");
// drag the page left to scroll right by 50px
yield ok(synthesizeNativeTouchDrag(target, 100, 10, -(50 + TOUCH_SLOP), 0),
"Synthesized horizontal drag (1), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(50, 50, "After first horizontal drag, with pan-x");
// drag the page diagonally right/down to scroll up/left by 40px each axis;
// only the x-axis will actually scroll because pan-x
yield ok(synthesizeNativeTouchDrag(target, 10, 10, (40 + TOUCH_SLOP), (40 + TOUCH_SLOP)),
"Synthesized diagonal drag (1), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(10, 50, "After first diagonal drag, with pan-x");
// switch style back to pan-y
document.body.style.touchAction = 'pan-y';
ok(true, "Waiting for pan-y to propagate...");
yield waitForAllPaintsFlushed(function() {
flushApzRepaints(testDriver);
});
// drag the page diagonally right/down to scroll up/left by 40px each axis;
// only the y-axis will actually scroll because pan-y
yield ok(synthesizeNativeTouchDrag(target, 10, 10, (40 + TOUCH_SLOP), (40 + TOUCH_SLOP)),
"Synthesized diagonal drag (2), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(10, 10, "After second diagonal drag, with pan-y");
// switch style to none
document.body.style.touchAction = 'none';
ok(true, "Waiting for none to propagate...");
yield waitForAllPaintsFlushed(function() {
flushApzRepaints(testDriver);
});
// drag the page diagonally up/left to scroll down/right by 40px each axis;
// neither will scroll because of touch-action
yield ok(synthesizeNativeTouchDrag(target, 100, 100, -(40 + TOUCH_SLOP), -(40 + TOUCH_SLOP)),
"Synthesized diagonal drag (3), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(10, 10, "After third diagonal drag, with none");
document.body.style.touchAction = 'manipulation';
ok(true, "Waiting for manipulation to propagate...");
yield waitForAllPaintsFlushed(function() {
flushApzRepaints(testDriver);
});
// drag the page diagonally up/left to scroll down/right by 40px each axis;
// both will scroll because of touch-action
yield ok(synthesizeNativeTouchDrag(target, 100, 100, -(40 + TOUCH_SLOP), -(40 + TOUCH_SLOP)),
"Synthesized diagonal drag (4), waiting for touch-end event...");
yield flushApzRepaints(testDriver);
checkScroll(50, 50, "After fourth diagonal drag, with manipulation");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
</head>
<body style="touch-action: pan-y">
<div style="width: 5000px; height: 5000px; background-color: lightgreen;">
This div makes the page scrollable on both axes.<br>
This is the second line of text.<br>
This is the third line of text.<br>
This is the fourth line of text.
</div>
<!-- This fixed-position div remains in the same place relative to the browser chrome, so we
can use it as a targeting device for synthetic touch events. The body will move around
as we scroll, so we'd have to be constantly adjusting the synthetic drag coordinates
if we used that as the target element. -->
<div style="position:fixed; left: 10px; top: 10px; width: 1px; height: 1px" id="target"></div>
</body>
</html>

View file

@ -0,0 +1,143 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Complex touch-action test</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function checkScroll(target, x, y, desc) {
is(target.scrollLeft, x, desc + " - x axis");
is(target.scrollTop, y, desc + " - y axis");
}
function resetConfiguration(config, testDriver) {
// Cycle through all the configuration_X elements, setting them to display:none
// except for when X == config, in which case set it to display:block
var i = 0;
while (true) {
i++;
var element = document.getElementById('configuration_' + i);
if (element == null) {
if (i <= config) {
ok(false, "The configuration requested was not encountered!");
}
break;
}
if (i == config) {
element.style.display = 'block';
} else {
element.style.display = 'none';
}
}
// Also reset the scroll position on the scrollframe
var s = document.getElementById('scrollframe');
s.scrollLeft = 0;
s.scrollTop = 0;
return waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
}
function* test(testDriver) {
var scrollframe = document.getElementById('scrollframe');
document.body.addEventListener('touchend', testDriver, { passive: true });
// Helper function for the tests below.
// Touch-pan configuration |configuration| towards scroll offset (dx, dy) with
// the pan touching down at (x, y). Check that the final scroll offset is
// (ex, ey). |desc| is some description string.
function* scrollAndCheck(configuration, x, y, dx, dy, ex, ey, desc) {
// Start with a clean slate
yield resetConfiguration(configuration, testDriver);
// Figure out the panning deltas
if (dx != 0) {
dx = -(dx + TOUCH_SLOP);
}
if (dy != 0) {
dy = -(dy + TOUCH_SLOP);
}
// Do the pan
yield ok(synthesizeNativeTouchDrag(scrollframe, x, y, dx, dy),
"Synthesized drag of (" + dx + ", " + dy + ") on configuration " + configuration);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Check for expected scroll position
checkScroll(scrollframe, ex, ey, 'configuration ' + configuration + ' ' + desc);
}
// Test configuration_1, which contains two sibling elements that are
// overlapping. The touch-action from the second sibling (which is on top)
// should be used for the overlapping area.
yield* scrollAndCheck(1, 25, 75, 20, 0, 20, 0, "first element horizontal scroll");
yield* scrollAndCheck(1, 25, 75, 0, 50, 0, 0, "first element vertical scroll");
yield* scrollAndCheck(1, 75, 75, 50, 0, 0, 0, "overlap horizontal scroll");
yield* scrollAndCheck(1, 75, 75, 0, 50, 0, 50, "overlap vertical scroll");
yield* scrollAndCheck(1, 125, 75, 20, 0, 0, 0, "second element horizontal scroll");
yield* scrollAndCheck(1, 125, 75, 0, 50, 0, 50, "second element vertical scroll");
// Test configuration_2, which contains two overlapping elements with a
// parent/child relationship. The parent has pan-x and the child has pan-y,
// which means that panning on the parent should work horizontally only, and
// on the child no panning should occur at all.
yield* scrollAndCheck(2, 125, 125, 50, 50, 0, 0, "child scroll");
yield* scrollAndCheck(2, 75, 75, 50, 50, 0, 0, "overlap scroll");
yield* scrollAndCheck(2, 25, 75, 0, 50, 0, 0, "parent vertical scroll");
yield* scrollAndCheck(2, 75, 25, 50, 0, 50, 0, "parent horizontal scroll");
// Test configuration_3, which is the same as configuration_2, except the child
// has a rotation transform applied. This forces the event regions on the two
// elements to be built separately and then get merged.
yield* scrollAndCheck(3, 125, 125, 50, 50, 0, 0, "child scroll");
yield* scrollAndCheck(3, 75, 75, 50, 50, 0, 0, "overlap scroll");
yield* scrollAndCheck(3, 25, 75, 0, 50, 0, 0, "parent vertical scroll");
yield* scrollAndCheck(3, 75, 25, 50, 0, 50, 0, "parent horizontal scroll");
// Test configuration_4 has two elements, one above the other, not overlapping,
// and the second element is a child of the first. The parent has pan-x, the
// child has pan-y, but that means panning horizontally on the parent should
// work and panning in any direction on the child should not do anything.
yield* scrollAndCheck(4, 75, 75, 50, 50, 50, 0, "parent diagonal scroll");
yield* scrollAndCheck(4, 75, 150, 50, 50, 0, 0, "child diagonal scroll");
}
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
</script>
</head>
<body>
<div id="scrollframe" style="width: 300px; height: 300px; overflow:scroll">
<div id="scrolled_content" style="width: 1000px; height: 1000px; background-color: green">
</div>
<div id="configuration_1" style="display:none; position: relative; top: -1000px">
<div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue"></div>
<div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: -100px; left: 50px; background-color: yellow"></div>
</div>
<div id="configuration_2" style="display:none; position: relative; top: -1000px">
<div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue">
<div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: 50px; left: 50px; background-color: yellow"></div>
</div>
</div>
<div id="configuration_3" style="display:none; position: relative; top: -1000px">
<div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue">
<div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: 50px; left: 50px; background-color: yellow; transform: rotate(90deg)"></div>
</div>
</div>
<div id="configuration_4" style="display:none; position: relative; top: -1000px">
<div style="touch-action: pan-x; width: 100px; height: 100px; background-color: blue">
<div style="touch-action: pan-y; width: 100px; height: 100px; position: relative; top: 125px; background-color: yellow"></div>
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,246 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<title>Test to ensure APZ doesn't always wait for touch-action</title>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript">
function failure(e) {
ok(false, "This event listener should not have triggered: " + e.type);
}
function success(e) {
success.triggered = true;
}
// This helper function provides a way for the child process to synchronously
// check how many touch events the chrome process main-thread has processed. This
// function can be called with three values: 'start', 'report', and 'end'.
// The 'start' invocation sets up the listeners, and should be invoked before
// the touch events of interest are generated. This should only be called once.
// This returns true on success, and false on failure.
// The 'report' invocation can be invoked multiple times, and returns an object
// (in JSON string format) containing the counters.
// The 'end' invocation tears down the listeners, and should be invoked once
// at the end to clean up. Returns true on success, false on failure.
function chromeTouchEventCounter(operation) {
function chromeProcessCounter() {
addMessageListener('start', function() {
Components.utils.import('resource://gre/modules/Services.jsm');
var topWin = Services.wm.getMostRecentWindow('navigator:browser');
if (typeof topWin.eventCounts != 'undefined') {
dump('Found pre-existing eventCounts object on the top window!\n');
return false;
}
topWin.eventCounts = { 'touchstart': 0, 'touchmove': 0, 'touchend': 0 };
topWin.counter = function(e) {
topWin.eventCounts[e.type]++;
}
topWin.addEventListener('touchstart', topWin.counter, { passive: true });
topWin.addEventListener('touchmove', topWin.counter, { passive: true });
topWin.addEventListener('touchend', topWin.counter, { passive: true });
return true;
});
addMessageListener('report', function() {
Components.utils.import('resource://gre/modules/Services.jsm');
var topWin = Services.wm.getMostRecentWindow('navigator:browser');
return JSON.stringify(topWin.eventCounts);
});
addMessageListener('end', function() {
Components.utils.import('resource://gre/modules/Services.jsm');
var topWin = Services.wm.getMostRecentWindow('navigator:browser');
if (typeof topWin.eventCounts == 'undefined') {
dump('The eventCounts object was not found on the top window!\n');
return false;
}
topWin.removeEventListener('touchstart', topWin.counter);
topWin.removeEventListener('touchmove', topWin.counter);
topWin.removeEventListener('touchend', topWin.counter);
delete topWin.counter;
delete topWin.eventCounts;
return true;
});
}
if (typeof chromeTouchEventCounter.chromeHelper == 'undefined') {
// This is the first time getSnapshot is being called; do initialization
chromeTouchEventCounter.chromeHelper = SpecialPowers.loadChromeScript(chromeProcessCounter);
SimpleTest.registerCleanupFunction(function() { chromeTouchEventCounter.chromeHelper.destroy() });
}
return chromeTouchEventCounter.chromeHelper.sendSyncMessage(operation, "");
}
// Simple wrapper that waits until the chrome process has seen |count| instances
// of the |eventType| event. Returns true on success, and false if 10 seconds
// go by without the condition being satisfied.
function waitFor(eventType, count) {
var start = Date.now();
while (JSON.parse(chromeTouchEventCounter('report'))[eventType] != count) {
if (Date.now() - start > 10000) {
// It's taking too long, let's abort
return false;
}
}
return true;
}
function* test(testDriver) {
// The main part of this test should run completely before the child process'
// main-thread deals with the touch event, so check to make sure that happens.
document.body.addEventListener('touchstart', failure, { passive: true });
// What we want here is to synthesize all of the touch events (from this code in
// the child process), and have the chrome process generate and process them,
// but not allow the events to be dispatched back into the child process until
// later. This allows us to ensure that the APZ in the chrome process is not
// waiting for the child process to send notifications upon processing the
// events. If it were doing so, the APZ would block and this test would fail.
// In order to actually implement this, we call the synthesize functions with
// a async callback in between. The synthesize functions just queue up a
// runnable on the child process main thread and return immediately, so with
// the async callbacks, the child process main thread queue looks like
// this after we're done setting it up:
// synthesizeTouchStart
// callback testDriver
// synthesizeTouchMove
// callback testDriver
// ...
// synthesizeTouchEnd
// callback testDriver
//
// If, after setting up this queue, we yield once, the first synthesization and
// callback will run - this will send a synthesization message to the chrome
// process, and return control back to us right away. When the chrome process
// processes with the synthesized event, it will dispatch the DOM touch event
// back to the child process over IPC, which will go into the end of the child
// process main thread queue, like so:
// synthesizeTouchStart (done)
// invoke testDriver (done)
// synthesizeTouchMove
// invoke testDriver
// ...
// synthesizeTouchEnd
// invoke testDriver
// handle DOM touchstart <-- touchstart goes at end of queue
//
// As we continue yielding one at a time, the synthesizations run, and the
// touch events get added to the end of the queue. As we yield, we take
// snapshots in the chrome process, to make sure that the APZ has started
// scrolling even though we know we haven't yet processed the DOM touch events
// in the child process yet.
//
// Note that the "async callback" we use here is SpecialPowers.executeSoon,
// because nothing else does exactly what we want:
// - setTimeout(..., 0) does not maintain ordering, because it respects the
// time delta provided (i.e. the callback can jump the queue to meet its
// deadline).
// - SpecialPowers.spinEventLoop and SpecialPowers.executeAfterFlushingMessageQueue
// are not e10s friendly, and can get arbitrarily delayed due to IPC
// round-trip time.
// - SimpleTest.executeSoon has a codepath that delegates to setTimeout, so
// is less reliable if it ever decides to switch to that codepath.
// The other problem we need to deal with is the asynchronicity in the chrome
// process. That is, we might request a snapshot before the chrome process has
// actually synthesized the event and processed it. To guard against this, we
// register a thing in the chrome process that counts the touch events that
// have been dispatched, and poll that thing synchronously in order to make
// sure we only snapshot after the event in question has been processed.
// That's what the chromeTouchEventCounter business is all about. The sync
// polling looks bad but in practice only ends up needing to poll once or
// twice before the condition is satisfied, and as an extra precaution we add
// a time guard so it fails after 10s of polling.
// So, here we go...
// Set up the chrome process touch listener
ok(chromeTouchEventCounter('start'), "Chrome touch counter registered");
// Set up the child process events and callbacks
var scroller = document.getElementById('scroller');
synthesizeNativeTouch(scroller, 10, 110, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
SpecialPowers.executeSoon(testDriver);
for (var i = 1; i < 10; i++) {
synthesizeNativeTouch(scroller, 10, 110 - (i * 10), SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
SpecialPowers.executeSoon(testDriver);
}
synthesizeNativeTouch(scroller, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, null, 0);
SpecialPowers.executeSoon(testDriver);
ok(true, "Finished setting up event queue");
// Get our baseline snapshot
var rect = rectRelativeToScreen(scroller);
var lastSnapshot = getSnapshot(rect);
ok(true, "Got baseline snapshot");
yield; // this will tell the chrome process to synthesize the touchstart event
// and then we wait to make sure it got processed:
ok(waitFor('touchstart', 1), "Touchstart processed in chrome process");
// Loop through the touchmove events
for (var i = 1; i < 10; i++) {
yield;
ok(waitFor('touchmove', i), "Touchmove processed in chrome process");
var snapshot = getSnapshot(rect);
if (i == 1) {
// The first touchmove is consumed to get us into the panning state, so
// no actual panning occurs
ok(lastSnapshot == snapshot, "Snapshot 1 was the same as baseline");
} else {
ok(lastSnapshot != snapshot, "Snapshot " + i + " was different from the previous one");
}
lastSnapshot = snapshot;
}
// Wait for the touchend as well, just for good form
yield;
ok(waitFor('touchend', 1), "Touchend processed in chrome process");
// Clean up the chrome process hooks
chromeTouchEventCounter('end');
// Now we are going to release our grip on the child process main thread,
// so that all the DOM events that were queued up can be processed. We
// register a touchstart listener to make sure this happens.
document.body.removeEventListener('touchstart', failure);
document.body.addEventListener('touchstart', success, { passive: true });
yield flushApzRepaints(testDriver);
ok(success.triggered, "The touchstart event handler was triggered after snapshotting completed");
document.body.removeEventListener('touchstart', success);
}
if (SpecialPowers.isMainProcess()) {
// This is probably android, where everything is single-process. The
// test structure depends on e10s, so the test won't run properly on
// this platform. Skip it
ok(true, "Skipping test because it is designed to run from the content process");
subtestDone();
} else {
waitUntilApzStable()
.then(runContinuation(test))
.then(subtestDone);
}
</script>
</head>
<body>
<div id="scroller" style="width: 400px; height: 400px; overflow: scroll; touch-action: pan-y">
<div style="width: 200px; height: 200px; background-color: lightgreen;">
This is a colored div that will move on the screen as the scroller scrolls.
</div>
<div style="width: 1000px; height: 1000px; background-color: lightblue">
This is a large div to make the scroller scrollable.
</div>
</body>
</html>

View file

@ -0,0 +1,67 @@
[DEFAULT]
support-files =
apz_test_native_event_utils.js
apz_test_utils.js
helper_basic_pan.html
helper_bug982141.html
helper_bug1151663.html
helper_bug1162771.html
helper_bug1271432.html
helper_bug1280013.html
helper_bug1285070.html
helper_bug1299195.html
helper_click.html
helper_div_pan.html
helper_drag_click.html
helper_drag_scroll.html
helper_iframe_pan.html
helper_iframe1.html
helper_iframe2.html
helper_long_tap.html
helper_scroll_inactive_perspective.html
helper_scroll_inactive_zindex.html
helper_scroll_on_position_fixed.html
helper_scrollto_tap.html
helper_subframe_style.css
helper_tall.html
helper_tap.html
helper_tap_fullzoom.html
helper_tap_passive.html
helper_touch_action.html
helper_touch_action_regions.html
helper_touch_action_complex.html
tags = apz
[test_bug982141.html]
[test_bug1151663.html]
[test_bug1151667.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_bug1253683.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_bug1277814.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_bug1304689.html]
[test_bug1304689-2.html]
[test_frame_reconstruction.html]
[test_group_mouseevents.html]
skip-if = (toolkit == 'android') # mouse events not supported on mobile
[test_group_pointerevents.html]
[test_group_touchevents.html]
[test_group_wheelevents.html]
skip-if = (toolkit == 'android') # wheel events not supported on mobile
[test_group_zoom.html]
skip-if = (toolkit != 'android') # only android supports zoom
[test_interrupted_reflow.html]
[test_layerization.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_scroll_inactive_bug1190112.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_scroll_inactive_flattened_frame.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_scroll_subframe_scrollbar.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_touch_listeners_impacting_wheel.html]
skip-if = (toolkit == 'android') || (toolkit == 'cocoa') # wheel events not supported on mobile, and synthesized wheel smooth-scrolling not supported on OS X
[test_wheel_scroll.html]
skip-if = (os == 'android') # wheel events not supported on mobile
[test_wheel_transactions.html]
skip-if = (os == 'android') # wheel events not supported on mobile

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1151663
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1151663</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
// Run the actual test in its own window, because it requires that the
// root APZC be scrollable. Mochitest pages themselves often run
// inside an iframe which means we have no control over the root APZC.
var w = null;
window.onload = function() {
pushPrefs([["apz.test.logging_enabled", true]]).then(function() {
w = window.open("helper_bug1151663.html", "_blank");
});
};
}
function finishTest() {
w.close();
SimpleTest.finish();
};
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151663">Mozilla Bug 1151663</a>
</body>
</html>

View file

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1151667
-->
<head>
<title>Test for Bug 1151667</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
#subframe {
margin-top: 100px;
height: 500px;
width: 500px;
overflow: scroll;
}
#subframe-content {
height: 1000px;
width: 500px;
/* the background is so that we can see it scroll*/
background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
}
#page-content {
height: 5000px;
width: 500px;
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1151667">Mozilla Bug 1151667</a>
<p id="display"></p>
<div id="subframe">
<!-- This makes sure the subframe is scrollable -->
<div id="subframe-content"></div>
</div>
<!-- This makes sure the page is also scrollable, so it (rather than the subframe)
is considered the primary async-scrollable frame, and so the subframe isn't
layerized upon page load. -->
<div id="page-content"></div>
<pre id="test">
<script type="application/javascript;version=1.7">
function startTest() {
var subframe = document.getElementById('subframe');
synthesizeNativeWheelAndWaitForScrollEvent(subframe, 100, 150, 0, -10, continueTest);
}
function continueTest() {
var subframe = document.getElementById('subframe');
is(subframe.scrollTop > 0, true, "We should have scrolled the subframe down");
is(document.documentElement.scrollTop, 0, "We should not have scrolled the page");
SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
waitUntilApzStable().then(startTest);
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,59 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1253683
-->
<head>
<title>Test to ensure non-scrollable frames don't get layerized</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<p id="display"></p>
<div id="container" style="height: 500px; overflow:scroll">
<pre id="no_layer" style="background-color: #f5f5f5; margin: 15px; padding: 15px; margin-top: 100px; border: 1px solid #eee; overflow:scroll">sample code here</pre>
<div style="height: 5000px">spacer to make the 'container' div the root scrollable element</div>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
function* test(testDriver) {
var container = document.getElementById('container');
var no_layer = document.getElementById('no_layer');
// Check initial state
is(container.scrollTop, 0, "Initial scrollY should be 0");
ok(!isLayerized('no_layer'), "initially 'no_layer' should not be layerized");
// Scrolling over outer1 should layerize outer1, but not inner1.
yield moveMouseAndScrollWheelOver(no_layer, 10, 10, testDriver, true);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
ok(container.scrollTop > 0, "We should have scrolled the body");
ok(!isLayerized('no_layer'), "no_layer should still not be layerized");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
// Turn off displayport expiry so that we don't miss failures where the
// displayport is set and expired before we check for layerization.
// Also enable APZ test logging, since we use that data to determine whether
// a scroll frame was layerized.
pushPrefs([["apz.displayport_expiry_ms", 0],
["apz.test.logging_enabled", true]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,106 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1277814
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1277814</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
function* test(testDriver) {
// Trigger the buggy scenario
var subframe = document.getElementById('bug1277814-div');
subframe.classList.add('a');
// The transform change is animated, so let's step through 1s of animation
var utils = SpecialPowers.getDOMWindowUtils(window);
for (var i = 0; i < 60; i++) {
utils.advanceTimeAndRefresh(16);
}
utils.restoreNormalRefresh();
// Wait for the layer tree with any updated dispatch-to-content region to
// get pushed over to the APZ
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Trigger layerization of the subframe by scrolling the wheel over it
yield moveMouseAndScrollWheelOver(subframe, 10, 10, testDriver);
// Give APZ the chance to compute a displayport, and content
// to render based on it.
yield waitForApzFlushedRepaints(testDriver);
// Examine the content-side APZ test data
var contentTestData = utils.getContentAPZTestData();
// Test that the scroll frame for the div 'bug1277814-div' appears in
// the APZ test data. The bug this test is for causes the displayport
// calculation for this scroll frame to go wrong, causing it not to
// become layerized.
contentTestData = convertTestData(contentTestData);
var foundIt = false;
for (var seqNo in contentTestData.paints) {
var paint = contentTestData.paints[seqNo];
for (var scrollId in paint) {
var scrollFrame = paint[scrollId];
if ('contentDescription' in scrollFrame &&
scrollFrame['contentDescription'].includes('bug1277814-div')) {
foundIt = true;
}
}
}
SimpleTest.ok(foundIt, "expected to find APZ test data for bug1277814-div");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
pushPrefs([["apz.test.logging_enabled", true]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
<style>
#bug1277814-div
{
position: absolute;
left: 0;
top: 0;
padding: .5em;
overflow: auto;
color: white;
background: green;
max-width: 30em;
max-height: 6em;
visibility: hidden;
transform: scaleY(0);
transition: transform .15s ease-out, visibility 0s ease .15s;
}
#bug1277814-div.a
{
visibility: visible;
transform: scaleY(1);
transition: transform .15s ease-out;
}
</style>
</head>
<body>
<!-- Use a unique id because we'll be checking for it in the content
description logged in the APZ test data -->
<div id="bug1277814-div">
CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
CoolCmd<br>CoolCmd<br>CoolCmd<br>CoolCmd<br>
<button>click me</button>
</body>
</html>

View file

@ -0,0 +1,131 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1304689
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1285070</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style type="text/css">
#outer {
height: 400px;
width: 415px;
overflow: scroll;
position: relative;
scroll-behavior: smooth;
}
#outer.contentBefore::before {
top: 0;
content: '';
display: block;
height: 2px;
position: absolute;
width: 100%;
z-index: 99;
}
</style>
<script type="application/javascript">
function* test(testDriver) {
var utils = SpecialPowers.DOMWindowUtils;
var elm = document.getElementById('outer');
// Set margins on the element, to ensure it is layerized
utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, /*priority*/ 1);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Take control of the refresh driver
utils.advanceTimeAndRefresh(0);
// Start a smooth-scroll animation in the compositor and let it go a few
// frames, so that there is some "user scrolling" going on (per the comment
// in AsyncPanZoomController::NotifyLayersUpdated)
elm.scrollTop = 10;
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
// Do another scroll update but also do a frame reconstruction within the same
// tick of the refresh driver.
elm.scrollTop = 100;
elm.classList.add('contentBefore');
// Now let everything settle and all the animations run out
for (var i = 0; i < 60; i++) {
utils.advanceTimeAndRefresh(16);
}
utils.restoreNormalRefresh();
yield flushApzRepaints(testDriver);
is(elm.scrollTop, 100, "The scrollTop now should be y=100");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
pushPrefs([["apz.displayport_expiry_ms", 0]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</head>
<body>
<div id="outer">
<div id="inner">
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,135 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1304689
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1285070</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style type="text/css">
#outer {
height: 400px;
width: 415px;
overflow: scroll;
position: relative;
scroll-behavior: smooth;
}
#outer.instant {
scroll-behavior: auto;
}
#outer.contentBefore::before {
top: 0;
content: '';
display: block;
height: 2px;
position: absolute;
width: 100%;
z-index: 99;
}
</style>
<script type="application/javascript">
function* test(testDriver) {
var utils = SpecialPowers.DOMWindowUtils;
var elm = document.getElementById('outer');
// Set margins on the element, to ensure it is layerized
utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, /*priority*/ 1);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Take control of the refresh driver
utils.advanceTimeAndRefresh(0);
// Start a smooth-scroll animation in the compositor and let it go a few
// frames, so that there is some "user scrolling" going on (per the comment
// in AsyncPanZoomController::NotifyLayersUpdated)
elm.scrollTop = 10;
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
// Do another scroll update but also do a frame reconstruction within the same
// tick of the refresh driver.
elm.classList.add('instant');
elm.scrollTop = 100;
elm.classList.add('contentBefore');
// Now let everything settle and all the animations run out
for (var i = 0; i < 60; i++) {
utils.advanceTimeAndRefresh(16);
}
utils.restoreNormalRefresh();
yield flushApzRepaints(testDriver);
is(elm.scrollTop, 100, "The scrollTop now should be y=100");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
pushPrefs([["apz.displayport_expiry_ms", 0]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</head>
<body>
<div id="outer">
<div id="inner">
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
this is some scrollable text.<br>
this is a second line to make the scrolling more obvious.<br>
and a third for good measure.<br>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=982141
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 982141</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
// Run the actual test in its own window, because it requires that the
// root APZC not be scrollable. Mochitest pages themselves often run
// inside an iframe which means we have no control over the root APZC.
var w = null;
window.onload = function() {
pushPrefs([["apz.test.logging_enabled", true]]).then(function() {
w = window.open("helper_bug982141.html", "_blank");
});
};
}
function finishTest() {
w.close();
SimpleTest.finish();
};
</script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=982141">Mozilla Bug 982141</a>
</body>
</html>

View file

@ -0,0 +1,218 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1235899
-->
<head>
<title>Test for bug 1235899</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
.outer {
height: 400px;
width: 415px;
overflow: hidden;
position: relative;
}
.inner {
height: 100%;
outline: none;
overflow-x: hidden;
overflow-y: scroll;
position: relative;
scroll-behavior: smooth;
}
.outer.contentBefore::before {
top: 0;
content: '';
display: block;
height: 2px;
position: absolute;
width: 100%;
z-index: 99;
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1235899">Mozilla Bug 1235899</a>
<p id="display"></p>
<div id="content">
<p>You should be able to fling this list without it stopping abruptly</p>
<div class="outer">
<div class="inner">
<ol>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
<li>Some text</li>
</ol>
</div>
</div>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
function* test(testDriver) {
var elm = document.getElementsByClassName('inner')[0];
elm.scrollTop = 0;
yield flushApzRepaints(testDriver);
// Take over control of the refresh driver and compositor
var utils = SpecialPowers.DOMWindowUtils;
utils.advanceTimeAndRefresh(0);
// Kick off an APZ smooth-scroll to 0,200
elm.scrollTo(0, 200);
yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
// Let's do a couple of frames of the animation, and make sure it's going
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
yield flushApzRepaints(testDriver);
ok(elm.scrollTop > 0, "APZ animation in progress", "scrollTop is now " + elm.scrollTop);
ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
var frameReconstructionTriggered = 0;
// Register the listener that triggers the frame reconstruction
elm.onscroll = function() {
// Do the reconstruction
elm.parentNode.classList.add('contentBefore');
frameReconstructionTriggered++;
// schedule a thing to undo the changes above
setTimeout(function() {
elm.parentNode.classList.remove('contentBefore');
}, 0);
}
// and do a few more frames of the animation, this should trigger the listener
// and the frame reconstruction
utils.advanceTimeAndRefresh(16);
utils.advanceTimeAndRefresh(16);
yield flushApzRepaints(testDriver);
ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
ok(frameReconstructionTriggered > 0, "Frame reconstruction triggered", "reconstruction triggered " + frameReconstructionTriggered + " times");
// and now run to completion
for (var i = 0; i < 100; i++) {
utils.advanceTimeAndRefresh(16);
}
utils.restoreNormalRefresh();
yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
yield flushApzRepaints(testDriver);
is(elm.scrollTop, 200, "Element should have scrolled by 200px");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
SimpleTest.expectAssertions(0, 1); // this test triggers an assertion, see bug 1247050
waitUntilApzStable()
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</body>
</html>

View file

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Various mouse tests that spawn in new windows</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
var subtests = [
// Sanity test to synthesize a mouse click
{'file': 'helper_click.html?dtc=false'},
// Same as above, but with a dispatch-to-content region that exercises the
// main-thread notification codepaths for mouse events
{'file': 'helper_click.html?dtc=true'},
// Sanity test for click but with some mouse movement between the down and up
{'file': 'helper_drag_click.html'},
// Test for dragging on a fake-scrollbar element that scrolls the page
{'file': 'helper_drag_scroll.html'}
];
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
window.onload = function() {
runSubtestsSeriallyInFreshWindows(subtests)
.then(SimpleTest.finish);
};
}
</script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1285070
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1285070</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
var subtests = [
{'file': 'helper_bug1285070.html', 'prefs': [["dom.w3c_pointer_events.enabled", true]]},
{'file': 'helper_bug1299195.html', 'prefs': [["dom.w3c_pointer_events.enabled", true]]}
];
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
window.onload = function() {
runSubtestsSeriallyInFreshWindows(subtests)
.then(SimpleTest.finish);
};
}
</script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Various touch tests that spawn in new windows</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
var basic_pan_prefs = [
// Dropping the touch slop to 0 makes the tests easier to write because
// we can just do a one-pixel drag to get over the pan threshold rather
// than having to hard-code some larger value.
["apz.touch_start_tolerance", "0.0"],
// The touchstart from the drag can turn into a long-tap if the touch-move
// events get held up. Try to prevent that by making long-taps require
// a 10 second hold. Note that we also cannot enable chaos mode on this
// test for this reason, since chaos mode can cause the long-press timer
// to fire sooner than the pref dictates.
["ui.click_hold_context_menus.delay", 10000],
// The subtests in this test do touch-drags to pan the page, but we don't
// want those pans to turn into fling animations, so we increase the
// fling min velocity requirement absurdly high.
["apz.fling_min_velocity_threshold", "10000"],
// The helper_div_pan's div gets a displayport on scroll, but if the
// test takes too long the displayport can expire before the new scroll
// position is synced back to the main thread. So we disable displayport
// expiry for these tests.
["apz.displayport_expiry_ms", 0],
];
var touch_action_prefs = basic_pan_prefs.slice(); // make a copy
touch_action_prefs.push(["layout.css.touch_action.enabled", true]);
var isWindows = (getPlatform() == "windows");
var subtests = [
// Simple tests to exercise basic panning behaviour
{'file': 'helper_basic_pan.html', 'prefs': basic_pan_prefs},
{'file': 'helper_div_pan.html', 'prefs': basic_pan_prefs},
{'file': 'helper_iframe_pan.html', 'prefs': basic_pan_prefs},
// Simple test to exercise touch-tapping behaviour
{'file': 'helper_tap.html'},
// Tapping, but with a full-zoom applied
{'file': 'helper_tap_fullzoom.html'},
// For the following two tests, disable displayport suppression to make sure it
// doesn't interfere with the test by scheduling paints non-deterministically.
{'file': 'helper_scrollto_tap.html?true', 'prefs': [["apz.paint_skipping.enabled", true]], 'dp_suppression': false},
{'file': 'helper_scrollto_tap.html?false', 'prefs': [["apz.paint_skipping.enabled", false]], 'dp_suppression': false},
// Taps on media elements to make sure the touchend event is delivered
// properly. We increase the long-tap timeout to ensure it doesn't get trip
// during the tap.
// Also this test (on Windows) cannot satisfy the OS requirement of providing
// an injected touch event every 100ms, because it waits for a paint between
// the touchstart and the touchend, so we have to use the "fake injection"
// code instead.
{'file': 'helper_bug1162771.html', 'prefs': [["ui.click_hold_context_menus.delay", 10000],
["apz.test.fails_with_native_injection", isWindows]]},
// As with the previous test, this test cannot inject touch events every 100ms
// because it waits for a long-tap, so we have to use the "fake injection" code
// instead.
{'file': 'helper_long_tap.html', 'prefs': [["apz.test.fails_with_native_injection", isWindows]]},
// For the following test, we want to make sure APZ doesn't wait for a content
// response that is never going to arrive. To detect this we set the content response
// timeout to a day, so that the entire test times out and fails if APZ does
// end up waiting.
{'file': 'helper_tap_passive.html', 'prefs': [["apz.content_response_timeout", 24 * 60 * 60 * 1000],
["apz.test.fails_with_native_injection", isWindows]]},
// Simple test to exercise touch-action CSS property
{'file': 'helper_touch_action.html', 'prefs': touch_action_prefs},
// More complex touch-action tests, with overlapping regions and such
{'file': 'helper_touch_action_complex.html', 'prefs': touch_action_prefs},
// Tests that touch-action CSS properties are handled in APZ without waiting
// on the main-thread, when possible
{'file': 'helper_touch_action_regions.html', 'prefs': touch_action_prefs},
];
if (isApzEnabled()) {
ok(window.TouchEvent, "Check if TouchEvent is supported (it should be, the test harness forces it on everywhere)");
if (getPlatform() == "android") {
// This has a lot of subtests, and Android emulators are slow.
SimpleTest.requestLongerTimeout(2);
}
SimpleTest.waitForExplicitFinish();
window.onload = function() {
runSubtestsSeriallyInFreshWindows(subtests)
.then(SimpleTest.finish);
};
}
</script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Various wheel-scrolling tests that spawn in new windows</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
var prefs = [
// turn off smooth scrolling so that we don't have to wait for
// APZ animations to finish before sampling the scroll offset
['general.smoothScroll', false],
// ensure that any mouse movement will trigger a new wheel transaction,
// because in this test we move the mouse a bunch and want to recalculate
// the target APZC after each such movement.
['mousewheel.transaction.ignoremovedelay', 0],
['mousewheel.transaction.timeout', 0]
]
var subtests = [
{'file': 'helper_scroll_on_position_fixed.html', 'prefs': prefs},
{'file': 'helper_bug1271432.html', 'prefs': prefs},
{'file': 'helper_scroll_inactive_perspective.html', 'prefs': prefs},
{'file': 'helper_scroll_inactive_zindex.html', 'prefs': prefs}
];
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
window.onload = function() {
runSubtestsSeriallyInFreshWindows(subtests)
.then(SimpleTest.finish);
};
}
</script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Various zoom-related tests that spawn in new windows</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript">
var prefs = [
// We need the APZ paint logging information
["apz.test.logging_enabled", true],
// Dropping the touch slop to 0 makes the tests easier to write because
// we can just do a one-pixel drag to get over the pan threshold rather
// than having to hard-code some larger value.
["apz.touch_start_tolerance", "0.0"],
// The subtests in this test do touch-drags to pan the page, but we don't
// want those pans to turn into fling animations, so we increase the
// fling-stop threshold velocity to absurdly high.
["apz.fling_stopped_threshold", "10000"],
// The helper_bug1280013's div gets a displayport on scroll, but if the
// test takes too long the displayport can expire before we read the value
// out of the test. So we disable displayport expiry for these tests.
["apz.displayport_expiry_ms", 0],
];
var subtests = [
{'file': 'helper_bug1280013.html', 'prefs': prefs},
];
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
window.onload = function() {
runSubtestsSeriallyInFreshWindows(subtests)
.then(SimpleTest.finish);
};
}
</script>
</head>
<body>
</body>
</html>

View file

@ -0,0 +1,719 @@
<!DOCTYPE html>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1292781
-->
<head>
<title>Test for bug 1292781</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
.outer {
height: 400px;
width: 415px;
overflow: hidden;
position: relative;
}
.inner {
height: 100%;
outline: none;
overflow-x: hidden;
overflow-y: scroll;
position: relative;
}
.inner div:nth-child(even) {
background-color: lightblue;
}
.inner div:nth-child(odd) {
background-color: lightgreen;
}
.outer.contentBefore::before {
top: 0;
content: '';
display: block;
height: 2px;
position: absolute;
width: 100%;
z-index: 99;
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1292781">Mozilla Bug 1292781</a>
<p id="display"></p>
<div id="content">
<p>The frame reconstruction should not leave this scrollframe in a bad state</p>
<div class="outer">
<div class="inner">
this is the top of the scrollframe.
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
this is near the top of the scrollframe.
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
this is near the bottom of the scrollframe.
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
<div>this is a box</div>
this is the bottom of the scrollframe.
</div>
</div>
</div>
<pre id="test">
<script type="text/javascript">
// Returns a list of async scroll offsets that the |inner| element had, one for
// each paint.
function getAsyncScrollOffsets(aPaintsToIgnore) {
var offsets = [];
var compositorTestData = SpecialPowers.getDOMWindowUtils(window).getCompositorAPZTestData();
var buckets = compositorTestData.paints.slice(aPaintsToIgnore);
ok(buckets.length >= 3, "Expected at least three paints in the compositor test data");
var childIsLayerized = false;
for (var i = 0; i < buckets.length; ++i) {
var apzcTree = buildApzcTree(convertScrollFrameData(buckets[i].scrollFrames));
var rcd = findRcdNode(apzcTree);
if (rcd == null) {
continue;
}
if (rcd.children.length > 0) {
// The child may not be layerized in the first few paints, but once it is
// layerized, it should stay layerized.
childIsLayerized = true;
}
if (!childIsLayerized) {
continue;
}
ok(rcd.children.length == 1, "Root content APZC has exactly one child");
var scroll = rcd.children[0].asyncScrollOffset;
var pieces = scroll.replace(/[()\s]+/g, '').split(',');
is(pieces.length, 2, "expected string of form (x,y)");
offsets.push({ x: parseInt(pieces[0]),
y: parseInt(pieces[1]) });
}
return offsets;
}
function* test(testDriver) {
var utils = SpecialPowers.DOMWindowUtils;
// The APZ test data accumulates whenever a test turns it on. We just want
// the data for this test, so we check how many frames are already recorded
// and discard those later.
var framesToSkip = SpecialPowers.getDOMWindowUtils(window).getCompositorAPZTestData().paints.length;
var elm = document.getElementsByClassName('inner')[0];
// Set a zero-margin displayport to ensure that the element is async-scrollable
// otherwise on Fennec it is not
utils.setDisplayPortMarginsForElement(0, 0, 0, 0, elm, 0);
var maxScroll = elm.scrollTopMax;
elm.scrollTop = maxScroll;
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Take control of the refresh driver
utils.advanceTimeAndRefresh(0);
// Force the next reflow to get interrupted
utils.forceReflowInterrupt();
// Make a change that triggers frame reconstruction, and then tick the refresh
// driver so that layout processes the pending restyles and then runs an
// interruptible reflow. That reflow *will* be interrupted (because of the flag
// we set above), and we should end up with a transient 0,0 scroll offset
// being sent to the compositor.
elm.parentNode.classList.add('contentBefore');
utils.advanceTimeAndRefresh(0);
// On android, and maybe non-e10s platforms generally, we need to manually
// kick the paint to send the layer transaction to the compositor.
yield waitForAllPaints(function() { setTimeout(testDriver, 0) });
// Read the main-thread scroll offset; although this is temporarily 0,0 that
// temporary value is never exposed to content - instead reading this value
// will finish doing the interrupted reflow from above and then report the
// correct scroll offset.
is(elm.scrollTop, maxScroll, "Main-thread scroll position was restored");
// .. and now flush everything to make sure the state gets pushed over to the
// compositor and APZ as well.
utils.restoreNormalRefresh();
yield waitForApzFlushedRepaints(testDriver);
// Now we pull the compositor data and check it. What we expect to see is that
// the scroll position goes to maxScroll, then drops to 0 and then goes back
// to maxScroll. This test is specifically testing that last bit - that it
// properly gets restored from 0 to maxScroll.
// The one hitch is that on Android this page is loaded with some amount of
// zoom, and the async scroll is in ParentLayerPixel coordinates, so it will
// not match maxScroll exactly. Since we can't reliably compute what that
// ParentLayer scroll will be, we just make sure the async scroll is nonzero
// and use the first value we encounter to verify that it got restored properly.
// The other alternative is to spawn this test into a new window with 1.0 zoom
// but I'm tired of doing that for pretty much every test.
var state = 0;
var asyncScrollOffsets = getAsyncScrollOffsets(framesToSkip);
dump("Got scroll offsets: " + JSON.stringify(asyncScrollOffsets) + "\n");
var maxScrollParentLayerPixels = maxScroll;
while (asyncScrollOffsets.length > 0) {
let offset = asyncScrollOffsets.shift();
switch (state) {
// 0 is the initial state, the scroll offset might be zero but should
// become non-zero from when we set scrollTop to scrollTopMax
case 0:
if (offset.y == 0) {
break;
}
if (getPlatform() == "android") {
ok(offset.y > 0, "Async scroll y of scrollframe is " + offset.y);
maxScrollParentLayerPixels = offset.y;
} else {
is(offset.y, maxScrollParentLayerPixels, "Async scroll y of scrollframe is " + offset.y);
}
state = 1;
break;
// state 1 starts out at maxScrollParentLayerPixels, should drop to 0
// because of the interrupted reflow putting the scroll into a transient
// zero state
case 1:
if (offset.y == maxScrollParentLayerPixels) {
break;
}
is(offset.y, 0, "Async scroll position was temporarily 0");
state = 2;
break;
// state 2 starts out the transient 0 scroll offset, and we expect the
// scroll position to get restored back to maxScrollParentLayerPixels
case 2:
if (offset.y == 0) {
break;
}
is(offset.y, maxScrollParentLayerPixels, "Async scroll y of scrollframe restored to " + offset.y);
state = 3;
break;
// Terminal state. The scroll position should stay at maxScrollParentLayerPixels
case 3:
is(offset.y, maxScrollParentLayerPixels, "Scroll position maintained");
break;
}
}
is(state, 3, "The scroll position did drop to 0 and then get restored properly");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
pushPrefs([["apz.test.logging_enabled", true],
["apz.displayport_expiry_ms", 0]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</body>
</html>

View file

@ -0,0 +1,214 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1173580
-->
<head>
<title>Test for layerization</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<link rel="stylesheet" type="text/css" href="helper_subframe_style.css"/>
<style>
#container {
display: flex;
overflow: scroll;
height: 500px;
}
.outer-frame {
height: 500px;
overflow: scroll;
flex-basis: 100%;
background: repeating-linear-gradient(#CCC, #CCC 100px, #BBB 100px, #BBB 200px);
}
#container-content {
height: 200%;
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1173580">APZ layerization tests</a>
<p id="display"></p>
<div id="container">
<div id="outer1" class="outer-frame">
<div id="inner1" class="inner-frame">
<div class="inner-content"></div>
</div>
</div>
<div id="outer2" class="outer-frame">
<div id="inner2" class="inner-frame">
<div class="inner-content"></div>
</div>
</div>
<iframe id="outer3" class="outer-frame" src="helper_iframe1.html"></iframe>
<iframe id="outer4" class="outer-frame" src="helper_iframe2.html"></iframe>
<!-- The container-content div ensures 'container' is scrollable, so the
optimization that layerizes the primary async-scrollable frame on page
load layerizes it rather than its child subframes. -->
<div id="container-content"></div>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
// Scroll the mouse wheel over |element|.
function scrollWheelOver(element, waitForScroll, testDriver) {
moveMouseAndScrollWheelOver(element, 10, 10, testDriver, waitForScroll);
}
const DISPLAYPORT_EXPIRY = 100;
// This helper function produces another helper function, which, when invoked,
// invokes the provided testDriver argument in a setTimeout 0. This is really
// just useful in cases when there are no paints pending, because then
// waitForAllPaints will invoke its callback synchronously. If we did
// waitForAllPaints(testDriver) that might cause reentrancy into the testDriver
// which is bad. This function works around that.
function asyncWrapper(testDriver) {
return function() {
setTimeout(testDriver, 0);
};
}
function* test(testDriver) {
// Initially, nothing should be layerized.
ok(!isLayerized('outer1'), "initially 'outer1' should not be layerized");
ok(!isLayerized('inner1'), "initially 'inner1' should not be layerized");
ok(!isLayerized('outer2'), "initially 'outer2' should not be layerized");
ok(!isLayerized('inner2'), "initially 'inner2' should not be layerized");
ok(!isLayerized('outer3'), "initially 'outer3' should not be layerized");
ok(!isLayerized('inner3'), "initially 'inner3' should not be layerized");
ok(!isLayerized('outer4'), "initially 'outer4' should not be layerized");
ok(!isLayerized('inner4'), "initially 'inner4' should not be layerized");
// Scrolling over outer1 should layerize outer1, but not inner1.
yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
ok(isLayerized('outer1'), "scrolling 'outer1' should cause it to be layerized");
ok(!isLayerized('inner1'), "scrolling 'outer1' should not cause 'inner1' to be layerized");
// Scrolling over inner2 should layerize both outer2 and inner2.
yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
ok(isLayerized('inner2'), "scrolling 'inner2' should cause it to be layerized");
ok(isLayerized('outer2'), "scrolling 'inner2' should also cause 'outer2' to be layerized");
// The second half of the test repeats the same checks as the first half,
// but with an iframe as the outer scrollable frame.
// Scrolling over outer3 should layerize outer3, but not inner3.
yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement, true, testDriver);
ok(isLayerized('outer3'), "scrolling 'outer3' should cause it to be layerized");
ok(!isLayerized('inner3'), "scrolling 'outer3' should not cause 'inner3' to be layerized");
// Scrolling over outer4 should layerize both outer4 and inner4.
yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'), true, testDriver);
ok(isLayerized('inner4'), "scrolling 'inner4' should cause it to be layerized");
ok(isLayerized('outer4'), "scrolling 'inner4' should also cause 'outer4' to be layerized");
// Now we enable displayport expiry, and verify that things are still
// layerized as they were before.
yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, testDriver);
ok(isLayerized('outer1'), "outer1 is still layerized after enabling expiry");
ok(!isLayerized('inner1'), "inner1 is still not layerized after enabling expiry");
ok(isLayerized('outer2'), "outer2 is still layerized after enabling expiry");
ok(isLayerized('inner2'), "inner2 is still layerized after enabling expiry");
ok(isLayerized('outer3'), "outer3 is still layerized after enabling expiry");
ok(!isLayerized('inner3'), "inner3 is still not layerized after enabling expiry");
ok(isLayerized('outer4'), "outer4 is still layerized after enabling expiry");
ok(isLayerized('inner4'), "inner4 is still layerized after enabling expiry");
// Now we trigger a scroll on some of the things still layerized, so that
// the displayport expiry gets triggered.
// Expire displayport with scrolling on outer1
yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
yield waitForAllPaints(asyncWrapper(testDriver));
ok(!isLayerized('outer1'), "outer1 is no longer layerized after displayport expiry");
ok(!isLayerized('inner1'), "inner1 is still not layerized after displayport expiry");
// Expire displayport with scrolling on inner2
yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Once the expiry elapses, it will trigger expiry on outer2, so we check
// both, one at a time.
yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
yield waitForAllPaints(asyncWrapper(testDriver));
ok(!isLayerized('inner2'), "inner2 is no longer layerized after displayport expiry");
yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
yield waitForAllPaints(asyncWrapper(testDriver));
ok(!isLayerized('outer2'), "outer2 got de-layerized with inner2");
// Scroll on inner3. inner3 isn't layerized, and this will cause it to
// get layerized, but it will also trigger displayport expiration for inner3
// which will eventually trigger displayport expiration on inner3 and outer3.
// Note that the displayport expiration might actually happen before the wheel
// input is processed in the compositor (see bug 1246480 comment 3), and so
// we make sure not to wait for a scroll event here, since it may never fire.
// However, if we do get a scroll event while waiting for the expiry, we need
// to restart the expiry timer because the displayport expiry got reset. There's
// no good way that I can think of to deterministically avoid doing this.
let inner3 = document.getElementById('outer3').contentDocument.getElementById('inner3');
yield scrollWheelOver(inner3, false, testDriver);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
var timerId = setTimeout(testDriver, DISPLAYPORT_EXPIRY);
var timeoutResetter = function() {
ok(true, "Got a scroll event; resetting timer...");
clearTimeout(timerId);
setTimeout(testDriver, DISPLAYPORT_EXPIRY);
// by not updating timerId we ensure that this listener resets the timeout
// at most once.
};
inner3.addEventListener('scroll', timeoutResetter, false);
yield; // wait for the setTimeout to elapse
inner3.removeEventListener('scroll', timeoutResetter, false);
yield waitForAllPaints(asyncWrapper(testDriver));
ok(!isLayerized('inner3'), "inner3 becomes unlayerized after expiry");
yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
yield waitForAllPaints(asyncWrapper(testDriver));
ok(!isLayerized('outer3'), "outer3 is no longer layerized after inner3 triggered expiry");
// Scroll outer4 and wait for the expiry. It should NOT get expired because
// inner4 is still layerized
yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement, true, testDriver);
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Wait for the expiry to elapse
yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
yield waitForAllPaints(asyncWrapper(testDriver));
ok(isLayerized('inner4'), "inner4 is still layerized because it never expired");
ok(isLayerized('outer4'), "outer4 is still layerized because inner4 is still layerized");
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
// Disable smooth scrolling, because it results in long-running scroll
// animations that can result in a 'scroll' event triggered by an earlier
// wheel event as corresponding to a later wheel event.
// Also enable APZ test logging, since we use that data to determine whether
// a scroll frame was layerized.
pushPrefs([["general.smoothScroll", false],
["apz.displayport_expiry_ms", 0],
["apz.test.logging_enabled", true]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,541 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test scrolling flattened inactive frames</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
p {
width:200px;
height:200px;
border:solid 1px black;
overflow:auto;
}
</style>
</head>
<body>
<div id="iframe-body" style="overflow: auto; height: 1000px">
<hr>
<hr>
<hr>
<p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p id="subframe">
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p><p>
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p>
</div>
<script clss="testbody" type="text/javascript;version=1.7">
function ScrollTops() {
this.outerScrollTop = document.getElementById('iframe-body').scrollTop;
this.innerScrollTop = document.getElementById('subframe').scrollTop;
}
var DefaultEvent = {
deltaMode: WheelEvent.DOM_DELTA_LINE,
deltaX: 0, deltaY: 1,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 1,
};
function test() {
var subframe = document.getElementById('subframe');
var oldpos = new ScrollTops();
sendWheelAndPaint(subframe, 10, 10, DefaultEvent, function () {
var newpos = new ScrollTops();
ok(oldpos.outerScrollTop == newpos.outerScrollTop, "viewport should not have scrolled");
ok(oldpos.innerScrollTop != newpos.innerScrollTop, "subframe should have scrolled");
doOuterScroll(subframe, newpos);
});
}
function doOuterScroll(subframe, oldpos) {
var outer = document.getElementById('iframe-body');
sendWheelAndPaint(outer, 20, 5, DefaultEvent, function () {
var newpos = new ScrollTops();
ok(oldpos.outerScrollTop != newpos.outerScrollTop, "viewport should have scrolled");
ok(oldpos.innerScrollTop == newpos.innerScrollTop, "subframe should not have scrolled");
doInnerScrollAgain(subframe, newpos);
});
}
function doInnerScrollAgain(subframe, oldpos) {
sendWheelAndPaint(subframe, 10, 10, DefaultEvent, function () {
var newpos = new ScrollTops();
ok(oldpos.outerScrollTop == newpos.outerScrollTop, "viewport should not have scrolled");
ok(oldpos.innerScrollTop != newpos.innerScrollTop, "subframe should have scrolled");
SimpleTest.finish();
});
}
SimpleTest.testInChaosMode();
SimpleTest.waitForExplicitFinish();
pushPrefs([['general.smoothScroll', false],
['mousewheel.transaction.timeout', 0],
['mousewheel.transaction.ignoremovedelay', 0]])
.then(waitUntilApzStable)
.then(test);
</script>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test scrolling flattened inactive frames</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<div id="container" style="height: 300px; width: 600px; overflow: auto; background: yellow">
<div id="outer" style="height: 400px; width: 500px; overflow: auto; background: black">
<div id="inner" style="mix-blend-mode: screen; height: 800px; overflow: auto; background: purple">
</div>
</div>
</div>
<script class="testbody" type="text/javascript;version=1.7">
function test() {
var container = document.getElementById('container');
var outer = document.getElementById('outer');
var inner = document.getElementById('inner');
var outerScrollTop = outer.scrollTop;
var containerScrollTop = container.scrollTop;
var event = {
deltaMode: WheelEvent.DOM_DELTA_LINE,
deltaX: 0,
deltaY: 10,
lineOrPageDeltaX: 0,
lineOrPageDeltaY: 10,
};
sendWheelAndPaint(inner, 20, 30, event, function () {
ok(container.scrollTop == containerScrollTop, "container scrollframe should not have scrolled");
ok(outer.scrollTop > outerScrollTop, "nested scrollframe should have scrolled");
SimpleTest.finish();
});
}
SimpleTest.testInChaosMode();
SimpleTest.waitForExplicitFinish();
pushPrefs([['general.smoothScroll', false],
['mousewheel.transaction.timeout', 1000000]])
.then(waitUntilApzStable)
.then(test);
</script>
</body>
</html>

View file

@ -0,0 +1,117 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test scrolling subframe scrollbars</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
<style>
p {
width:200px;
height:200px;
border:solid 1px black;
}
</style>
</head>
<body>
<p id="subframe">
1 <br>
2 <br>
3 <br>
4 <br>
5 <br>
6 <br>
7 <br>
8 <br>
9 <br>
10 <br>
11 <br>
12 <br>
13 <br>
14 <br>
15 <br>
16 <br>
17 <br>
18 <br>
19 <br>
20 <br>
21 <br>
22 <br>
23 <br>
24 <br>
25 <br>
26 <br>
27 <br>
28 <br>
29 <br>
30 <br>
31 <br>
32 <br>
33 <br>
34 <br>
35 <br>
36 <br>
37 <br>
38 <br>
39 <br>
40 <br>
</p>
<script clss="testbody" type="text/javascript;version=1.7">
var DefaultEvent = {
deltaMode: WheelEvent.DOM_DELTA_LINE,
deltaX: 0, deltaY: 1,
lineOrPageDeltaX: 0, lineOrPageDeltaY: 1,
};
var ScrollbarWidth = 0;
function test() {
var subframe = document.getElementById('subframe');
var oldClientWidth = subframe.clientWidth;
subframe.style.overflow = 'auto';
subframe.getBoundingClientRect();
waitForAllPaintsFlushed(function () {
ScrollbarWidth = oldClientWidth - subframe.clientWidth;
if (!ScrollbarWidth) {
// Probably we have overlay scrollbars - abort the test.
ok(true, "overlay scrollbars - skipping test");
SimpleTest.finish();
return;
}
ok(subframe.scrollHeight > subframe.clientHeight, "subframe should have scrollable content");
testScrolling(subframe);
});
}
function testScrolling(subframe) {
// Send a wheel event roughly to where we think the trackbar is. We pick a
// point at the bottom, in the middle of the trackbar, where the slider is
// unlikely to be (since it starts at the top).
var posX = subframe.clientWidth + (ScrollbarWidth / 2);
var posY = subframe.clientHeight - 20;
var oldScrollTop = subframe.scrollTop;
sendWheelAndPaint(subframe, posX, posY, DefaultEvent, function () {
ok(subframe.scrollTop > oldScrollTop, "subframe should have scrolled");
SimpleTest.finish();
});
}
SimpleTest.waitForExplicitFinish();
pushPrefs([['general.smoothScroll', false],
['mousewheel.transaction.timeout', 0],
['mousewheel.transaction.ignoremovedelay', 0]])
.then(waitUntilApzStable)
.then(test);
</script>
</body>
</html>

View file

@ -0,0 +1,77 @@
<html>
<head>
<title>Test Frame Uniformity While Scrolling</title>
<script type="text/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<style>
#content {
height: 5000px;
background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
}
</style>
<script type="text/javascript">
var scrollEvents = 100;
var i = 0;
var testPref = "gfx.vsync.collect-scroll-transforms";
// Scroll points
var x = 100;
var y = 150;
SimpleTest.waitForExplicitFinish();
var utils = _getDOMWindowUtils(window);
function sendScrollEvent(aRafTimestamp) {
var scrollDiv = document.getElementById("content");
if (i < scrollEvents) {
i++;
// Scroll diff
var dx = 0;
var dy = -10; // Negative to scroll down
synthesizeNativeWheelAndWaitForWheelEvent(scrollDiv, x, y, dx, dy);
window.requestAnimationFrame(sendScrollEvent);
} else {
// Locally, with silk and apz + e10s, retina 15" mbp usually get ~1.0 - 1.5
// w/o silk + e10s + apz, I get up to 7. Lower is better.
// Windows, I get ~3. Values are not valid w/o hardware vsync
var uniformities = _getDOMWindowUtils().getFrameUniformityTestData();
for (var j = 0; j < uniformities.layerUniformities.length; j++) {
var layerResult = uniformities.layerUniformities[j];
var layerAddr = layerResult.layerAddress;
var uniformity = layerResult.frameUniformity;
var msg = "Layer: " + layerAddr.toString(16) + " Uniformity: " + uniformity;
SimpleTest.ok((uniformity >= 0) && (uniformity < 4.0), msg);
}
SimpleTest.finish();
}
}
function startTest() {
window.requestAnimationFrame(sendScrollEvent);
}
window.onload = function() {
var apzEnabled = SpecialPowers.getBoolPref("layers.async-pan-zoom.enabled");
if (!apzEnabled) {
SimpleTest.ok(true, "APZ not enabled, skipping test");
SimpleTest.finish();
}
SpecialPowers.pushPrefEnv({
"set" : [
[testPref, true]
]
}, startTest);
}
</script>
</head>
<body>
<div id="content">
</div>
</body>
</html>

View file

@ -0,0 +1,114 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1203140
-->
<head>
<title>Test for Bug 1203140</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1203140">Mozilla Bug 1203140</a>
<p id="display"></p>
<div id="content" style="overflow-y:scroll; height: 400px">
<p>The box below has a touch listener and a passive wheel listener. With touch events disabled, APZ shouldn't wait for any listeners.</p>
<div id="box" style="width: 200px; height: 200px; background-color: blue"></div>
<div style="height: 1000px; width: 10px">Div to make 'content' scrollable</div>
</div>
<pre id="test">
<script type="application/javascript">
const kResponseTimeoutMs = 2 * 60 * 1000; // 2 minutes
function takeSnapshots(e) {
// Grab some snapshots, and make sure some of them are different (i.e. check
// the page is scrolling in the compositor, concurrently with this wheel
// listener running).
// Note that we want this function to take less time than the content response
// timeout, otherwise the scrolling will start even if we haven't returned,
// and that would invalidate purpose of the test.
var start = Date.now();
var lastSnapshot = null;
var success = false;
// Get the position of the 'content' div relative to the screen
var rect = rectRelativeToScreen(document.getElementById('content'));
for (var i = 0; i < 10; i++) {
SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(16);
var snapshot = getSnapshot(rect);
//dump("Took snapshot " + snapshot + "\n"); // this might help with debugging
if (lastSnapshot && lastSnapshot != snapshot) {
ok(true, "Found some different pixels in snapshot " + i + " compared to previous");
success = true;
}
lastSnapshot = snapshot;
}
ok(success, "Found some snapshots that were different");
ok((Date.now() - start) < kResponseTimeoutMs, "Snapshotting ran quickly enough");
// Until now, no scroll events will have been dispatched to content. That's
// because scroll events are dispatched on the main thread, which we've been
// hogging with the code above. At this point we restore the normal refresh
// behaviour and let the main thread go back to C++ code, so the scroll events
// fire and we unwind from the main test continuation.
SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
}
function* test(testDriver) {
var box = document.getElementById('box');
// Ensure the div is layerized by scrolling it
yield moveMouseAndScrollWheelOver(box, 10, 10, testDriver);
box.addEventListener('touchstart', function(e) {
ok(false, "This should never be run");
}, false);
box.addEventListener('wheel', takeSnapshots, { capture: false, passive: true });
// Let the event regions and layerization propagate to the APZ
yield waitForAllPaints(function() {
flushApzRepaints(testDriver);
});
// Take over control of the refresh driver and compositor
var utils = SpecialPowers.DOMWindowUtils;
utils.advanceTimeAndRefresh(0);
// Trigger an APZ scroll using a wheel event. If APZ is waiting for a
// content response, it will wait for takeSnapshots to finish running before
// it starts scrolling, which will cause the checks in takeSnapshots to fail.
yield synthesizeNativeMouseMoveAndWaitForMoveEvent(box, 10, 10, testDriver);
yield synthesizeNativeWheelAndWaitForScrollEvent(box, 10, 10, 0, -50, testDriver);
}
if (isApzEnabled()) {
SimpleTest.waitForExplicitFinish();
// Disable touch events, so that APZ knows not to wait for touch listeners.
// Also explicitly set the content response timeout, so we know how long it
// is (see comment in takeSnapshots).
// Finally, enable smooth scrolling, so that the wheel-scroll we do as part
// of the test triggers an APZ animation rather than doing an instant scroll.
// Note that this pref doesn't work for the synthesized wheel events on OS X,
// those are hard-coded to be instant scrolls.
pushPrefs([["dom.w3c_touch_events.enabled", 0],
["apz.content_response_timeout", kResponseTimeoutMs],
["general.smoothscroll", true]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
}
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,106 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
-->
<head>
<title>Test for Bug 1013412</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
#content {
height: 800px;
overflow: scroll;
}
#scroller {
height: 2000px;
background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
}
#scrollbox {
margin-top: 200px;
width: 500px;
height: 500px;
border-radius: 250px;
box-shadow: inset 0 0 0 60px #555;
background: #777;
}
#circle {
position: relative;
left: 240px;
top: 20px;
border: 10px solid white;
border-radius: 10px;
width: 0px;
height: 0px;
transform-origin: 10px 230px;
will-change: transform;
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1161206">Mozilla Bug 1161206</a>
<p id="display"></p>
<div id="content">
<p>Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.</p>
<div id="scroller">
<div id="scrollbox">
<div id="circle"></div>
</div>
</div>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
var rotation = 0;
var rotationAdjusted = false;
var incrementForMode = function (mode) {
switch (mode) {
case WheelEvent.DOM_DELTA_PIXEL: return 1;
case WheelEvent.DOM_DELTA_LINE: return 15;
case WheelEvent.DOM_DELTA_PAGE: return 400;
}
return 0;
};
document.getElementById("scrollbox").addEventListener("wheel", function (e) {
rotation += e.deltaY * incrementForMode(e.deltaMode) * 0.2;
document.getElementById("circle").style.transform = "rotate(" + rotation + "deg)";
rotationAdjusted = true;
e.preventDefault();
});
function* test(testDriver) {
var content = document.getElementById('content');
for (i = 0; i < 300; i++) { // enough iterations that we would scroll to the bottom of 'content'
yield synthesizeNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5, testDriver);
}
var scrollbox = document.getElementById('scrollbox');
is(content.scrollTop > 0, true, "We should have scrolled down somewhat");
is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
is(rotationAdjusted, true, "The rotation should have been adjusted");
}
SimpleTest.testInChaosMode();
SimpleTest.waitForExplicitFinish();
// If we allow smooth scrolling the "smooth" scrolling may cause the page to
// glide past the scrollbox (which is supposed to stop the scrolling) and so
// we might end up at the bottom of the page.
pushPrefs([["general.smoothScroll", false]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,137 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1175585
-->
<head>
<title>Test for Bug 1175585</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
<script type="application/javascript" src="apz_test_native_event_utils.js"></script>
<script type="application/javascript" src="apz_test_utils.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<style>
#outer-frame {
height: 500px;
overflow: scroll;
background: repeating-linear-gradient(#CCC, #CCC 100px, #BBB 100px, #BBB 200px);
}
#inner-frame {
margin-top: 25%;
height: 200%;
width: 75%;
overflow: scroll;
}
#inner-content {
height: 200%;
width: 200%;
background: repeating-linear-gradient(#EEE, #EEE 100px, #DDD 100px, #DDD 200px);
}
</style>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175585">APZ wheel transactions test</a>
<p id="display"></p>
<div id="outer-frame">
<div id="inner-frame">
<div id="inner-content"></div>
</div>
</div>
<pre id="test">
<script type="application/javascript;version=1.7">
function scrollWheelOver(element, deltaY, testDriver) {
synthesizeNativeWheelAndWaitForScrollEvent(element, 10, 10, 0, deltaY, testDriver);
}
function* test(testDriver) {
var outer = document.getElementById('outer-frame');
var inner = document.getElementById('inner-frame');
var innerContent = document.getElementById('inner-content');
// Register a wheel event listener that records the target of
// the last wheel event, so that we can make assertions about it.
var lastWheelTarget;
var wheelTargetRecorder = function(e) { lastWheelTarget = e.target; };
window.addEventListener("wheel", wheelTargetRecorder);
// Scroll |outer| to the bottom.
while (outer.scrollTop < outer.scrollTopMax) {
yield scrollWheelOver(outer, -10, testDriver);
}
// Verify that this has brought |inner| under the wheel.
is(lastWheelTarget, innerContent, "'inner-content' should have been brought under the wheel");
window.removeEventListener("wheel", wheelTargetRecorder);
// Immediately after, scroll it back up a bit.
yield scrollWheelOver(outer, 10, testDriver);
// Check that it was |outer| that scrolled back, and |inner| didn't
// scroll at all, as all the above scrolls should be in the same
// transaction.
ok(outer.scrollTop < outer.scrollTopMax, "'outer' should have scrolled back a bit");
is(inner.scrollTop, 0, "'inner' should not have scrolled");
// The next part of the test is related to the transaction timeout.
// Turn it down a bit so waiting for the timeout to elapse doesn't
// slow down the test harness too much.
var timeout = 5;
yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, testDriver);
SimpleTest.requestFlakyTimeout("we are testing code that measures actual elapsed time between two events");
// Scroll up a bit more. It's still |outer| scrolling because
// |inner| is still scrolled all the way to the top.
yield scrollWheelOver(outer, 10, testDriver);
// Wait for the transaction timeout to elapse.
// timeout * 5 is used to make it less likely that the timeout is less than
// the system timestamp resolution
yield window.setTimeout(testDriver, timeout * 5);
// Now scroll down. The transaction having timed out, the event
// should pick up a new target, and that should be |inner|.
yield scrollWheelOver(outer, -10, testDriver);
ok(inner.scrollTop > 0, "'inner' should have been scrolled");
// Finally, test scroll handoff after a timeout.
// Continue scrolling |inner| down to the bottom.
var prevScrollTop = inner.scrollTop;
while (inner.scrollTop < inner.scrollTopMax) {
yield scrollWheelOver(outer, -10, testDriver);
// Avoid a failure getting us into an infinite loop.
ok(inner.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
prevScrollTop = inner.scrollTop;
}
// Wait for the transaction timeout to elapse.
// timeout * 5 is used to make it less likely that the timeout is less than
// the system timestamp resolution
yield window.setTimeout(testDriver, timeout * 5);
// Continued downward scrolling should scroll |outer| to the bottom.
prevScrollTop = outer.scrollTop;
while (outer.scrollTop < outer.scrollTopMax) {
yield scrollWheelOver(outer, -10, testDriver);
// Avoid a failure getting us into an infinite loop.
ok(outer.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
prevScrollTop = outer.scrollTop;
}
}
SimpleTest.waitForExplicitFinish();
// Disable smooth scrolling because it makes the test flaky (we don't have a good
// way of detecting when the scrolling is finished).
pushPrefs([["general.smoothScroll", false]])
.then(waitUntilApzStable)
.then(runContinuation(test))
.then(SimpleTest.finish);
</script>
</pre>
</body>
</html>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
</head>
<body onload="scrollTo(450,0); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 10px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
<style> html { direction: rtl; } </style>
</head>
<body onload="scrollTo(-450,0); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 10px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="-449" reftest-async-scroll-y="0"><head>
<meta name="viewport" content="width=device-width">
<style> html { direction: rtl; } </style>
</head>
<!-- Doing scrollTo(-1,0) is to activate the right arrow in the scrollbar
for non-overlay scrollbar environments -->
<body onload="scrollTo(-1,0); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 10px; background: white"></div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="449" reftest-async-scroll-y="0"><head>
<meta name="viewport" content="width=device-width">
</head>
<!-- Doing scrollTo(1,0) is to activate the left arrow in the scrollbar
for non-overlay scrollbar environments -->
<body onload="scrollTo(1,0); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 10px; background: white"></div>
</body>
</html>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
</head>
<body onload="scrollTo(0,10000); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 10px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
<style> html { direction: rtl; } </style>
</head>
<body onload="scrollTo(0,10000); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 10px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="0" reftest-async-scroll-y="9999"><head>
<meta name="viewport" content="width=device-width">
<style> html { direction: rtl; } </style>
</head>
<!-- Doing scrollTo(0,1) is to activate the up arrow in the scrollbar
for non-overlay scrollbar environments -->
<body onload="scrollTo(0,1); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 10px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="0" reftest-async-scroll-y="9999"><head>
<meta name="viewport" content="width=device-width">
</head>
<!-- Doing scrollTo(0,1) is to activate the up arrow in the scrollbar
for non-overlay scrollbar environments -->
<body onload="scrollTo(0,1); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 10px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
</head>
<body onload="scrollTo(450,8000); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
<style> html { direction: rtl; } </style>
</head>
<body onload="scrollTo(-450,8000); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="-440" reftest-async-scroll-y="7999"><head>
<meta name="viewport" content="width=device-width">
<style> html { direction: rtl; } </style>
</head>
<!-- Doing scrollTo(-10,1) is to activate the right/up arrows in the scrollbars
for non-overlay scrollbar environments -->
<body onload="scrollTo(-10,1); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="449" reftest-async-scroll-y="7999"><head>
<meta name="viewport" content="width=device-width">
</head>
<!-- Doing scrollTo(1,1) is to activate the left/up arrows in the scrollbars
for non-overlay scrollbar environments -->
<body onload="scrollTo(1,1); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
</head>
<body onload="scrollTo(450,10000); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="224" reftest-async-scroll-y="4999"
reftest-async-zoom="2.0"><head>
<meta name="viewport" content="width=device-width">
</head>
<!-- Doing scrollTo(1,1) is to activate the left/up arrows in the scrollbars
for non-overlay scrollbar environments -->
<body onload="scrollTo(1,1); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 4500px; height: 10000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html class="reftest-wait"><head>
<meta name="viewport" content="width=device-width">
</head>
<body onload="scrollTo(450,10000); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 9000px; height: 20000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html class="reftest-wait"
reftest-async-scroll
reftest-async-scroll-x="899" reftest-async-scroll-y="19999"
reftest-async-zoom="0.5"><head>
<meta name="viewport" content="width=device-width">
</head>
<!-- Doing scrollTo(1,1) is to activate the left/up arrows in the scrollbars
for non-overlay scrollbar environments -->
<body onload="scrollTo(1,1); document.documentElement.classList.remove('reftest-wait')">
<div style="width: 18000px; height: 40000px; background: white;"></div>
</body>
</html>

View file

@ -0,0 +1,27 @@
<html>
<script>
function run() {
document.body.classList.toggle('noscroll');
document.getElementById('spacer').style.height = '100%';
// Scroll to the very end, including any fractional pixels
document.body.scrollTop = document.body.scrollTopMax + 1;
}
</script>
<style>
html, body {
margin: 0;
padding: 0;
background-color: green;
}
.noscroll {
overflow: hidden;
height: 100%;
}
</style>
<body onload="run()">
<div id="spacer" style="height: 5000px">
This is the top of the page.
</div>
This is the bottom of the page.
</body>

View file

@ -0,0 +1,53 @@
<html class="reftest-wait">
<!--
For bug 1266833; syncing the scroll offset to APZ properly when the scroll
position is clamped to a smaller value during a frame reconstruction.
-->
<script>
function run() {
document.body.scrollTop = document.body.scrollTopMax;
// Let the scroll position propagate to APZ before we do the frame
// reconstruction. Ideally we would wait for flushApzRepaints here but
// we don't have access to DOMWindowUtils in a reftest, so we just wait
// 100ms to approximate it. With bug 1266833 fixed, this test should
// never fail regardless of what this timeout value is.
setTimeout(frameReconstruction, 100);
}
function frameReconstruction() {
document.body.classList.toggle('noscroll');
document.documentElement.classList.toggle('reconstruct-body');
document.getElementById('spacer').style.height = '100%';
document.documentElement.classList.remove('reftest-wait');
}
</script>
<style>
html, body {
margin: 0;
padding: 0;
background-color: green;
}
.noscroll {
overflow: hidden;
height: 100%;
}
/* Toggling this on and off triggers a frame reconstruction on the <body> */
html.reconstruct-body::before {
top: 0;
content: '';
display: block;
height: 2px;
position: absolute;
width: 100%;
z-index: 99;
}
</style>
<body onload="setTimeout(run, 0)">
<div id="spacer" style="height: 5000px">
This is the top of the page.
</div>
This is the bottom of the page.
</body>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html><head>
<meta name="viewport" content="width=device-width">
</head>
<body>
This tests that an initial-scale of 0 (i.e. garbage) is overridden<br/>
with something a little more sane.
</body>
</html>

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html><head>
<meta name="viewport" content="initial-scale=0; width=device-width">
</head>
<body>
This tests that an initial-scale of 0 (i.e. garbage) is overridden<br/>
with something a little more sane.
</body>
</html>

View file

@ -0,0 +1,20 @@
# DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
# The following tests test the async positioning of the scrollbars.
# Basic root-frame scrollbar with async scrolling
skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-v.html async-scrollbar-1-v.html
skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-h.html async-scrollbar-1-h.html
skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-vh.html async-scrollbar-1-vh.html
skip-if(!asyncPan) fuzzy-if(Android,6,8) == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl.html
skip-if(!asyncPan) fuzzy-if(Android,13,8) == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl.html
skip-if(!asyncPan) fuzzy-if(Android,8,10) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl.html
# Different async zoom levels. Since the scrollthumb gets async-scaled in the
# compositor, the border-radius ends of the scrollthumb are going to be a little
# off, hence the fuzzy-if clauses.
skip-if(!asyncZoom) fuzzy-if(B2G,98,82) == async-scrollbar-zoom-1.html async-scrollbar-zoom-1.html
skip-if(!asyncZoom) fuzzy-if(B2G,94,146) == async-scrollbar-zoom-2.html async-scrollbar-zoom-2.html
# Meta-viewport tag support
skip-if(!asyncZoom) == initial-scale-1.html initial-scale-1.html
skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping.html

View file

@ -0,0 +1,19 @@
# The following tests test the async positioning of the scrollbars.
# Basic root-frame scrollbar with async scrolling
fuzzy-if(Android,1,2) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-v.html async-scrollbar-1-v-ref.html
fuzzy-if(Android,4,5) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-h.html async-scrollbar-1-h-ref.html
fuzzy-if(Android,3,5) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-vh.html async-scrollbar-1-vh-ref.html
fuzzy-if(Android,1,2) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-v-rtl.html async-scrollbar-1-v-rtl-ref.html
fuzzy-if(Android,4,5) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-h-rtl.html async-scrollbar-1-h-rtl-ref.html
fuzzy-if(Android,3,7) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-1-vh-rtl.html async-scrollbar-1-vh-rtl-ref.html
# Different async zoom levels. Since the scrollthumb gets async-scaled in the
# compositor, the border-radius ends of the scrollthumb are going to be a little
# off, hence the fuzzy-if clauses.
fuzzy-if(Android,54,18) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-zoom-1.html async-scrollbar-zoom-1-ref.html
fuzzy-if(Android,45,21) skip-if(!Android) pref(apz.allow_zooming,true) == async-scrollbar-zoom-2.html async-scrollbar-zoom-2-ref.html
# Meta-viewport tag support
skip-if(!Android) pref(apz.allow_zooming,true) == initial-scale-1.html initial-scale-1-ref.html
skip-if(!asyncPan) == frame-reconstruction-scroll-clamping.html frame-reconstruction-scroll-clamping-ref.html