From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- gfx/layers/apz/test/gtest/APZCBasicTester.h | 120 ++++ gfx/layers/apz/test/gtest/APZCTreeManagerTester.h | 194 ++++++ gfx/layers/apz/test/gtest/APZTestCommon.h | 609 +++++++++++++++++ gfx/layers/apz/test/gtest/InputUtils.h | 297 +++++++++ gfx/layers/apz/test/gtest/TestBasic.cpp | 356 ++++++++++ gfx/layers/apz/test/gtest/TestEventRegions.cpp | 272 ++++++++ gfx/layers/apz/test/gtest/TestGestureDetector.cpp | 638 ++++++++++++++++++ gfx/layers/apz/test/gtest/TestHitTesting.cpp | 578 +++++++++++++++++ gfx/layers/apz/test/gtest/TestInputQueue.cpp | 44 ++ gfx/layers/apz/test/gtest/TestPanning.cpp | 128 ++++ gfx/layers/apz/test/gtest/TestPinching.cpp | 294 +++++++++ gfx/layers/apz/test/gtest/TestScrollHandoff.cpp | 521 +++++++++++++++ gfx/layers/apz/test/gtest/TestSnapping.cpp | 64 ++ gfx/layers/apz/test/gtest/TestTreeManager.cpp | 112 ++++ gfx/layers/apz/test/gtest/moz.build | 33 + .../test/mochitest/apz_test_native_event_utils.js | 261 ++++++++ gfx/layers/apz/test/mochitest/apz_test_utils.js | 403 ++++++++++++ gfx/layers/apz/test/mochitest/chrome.ini | 9 + .../apz/test/mochitest/helper_basic_pan.html | 38 ++ .../apz/test/mochitest/helper_bug1151663.html | 83 +++ .../apz/test/mochitest/helper_bug1162771.html | 104 +++ .../apz/test/mochitest/helper_bug1271432.html | 574 ++++++++++++++++ .../apz/test/mochitest/helper_bug1280013.html | 74 +++ .../apz/test/mochitest/helper_bug1285070.html | 44 ++ .../apz/test/mochitest/helper_bug1299195.html | 47 ++ .../apz/test/mochitest/helper_bug982141.html | 149 +++++ gfx/layers/apz/test/mochitest/helper_click.html | 41 ++ gfx/layers/apz/test/mochitest/helper_div_pan.html | 44 ++ .../apz/test/mochitest/helper_drag_click.html | 43 ++ .../apz/test/mochitest/helper_drag_scroll.html | 603 +++++++++++++++++ gfx/layers/apz/test/mochitest/helper_iframe1.html | 14 + gfx/layers/apz/test/mochitest/helper_iframe2.html | 14 + .../apz/test/mochitest/helper_iframe_pan.html | 41 ++ gfx/layers/apz/test/mochitest/helper_long_tap.html | 81 +++ .../helper_scroll_inactive_perspective.html | 46 ++ .../mochitest/helper_scroll_inactive_zindex.html | 47 ++ .../mochitest/helper_scroll_on_position_fixed.html | 62 ++ .../apz/test/mochitest/helper_scrollto_tap.html | 61 ++ .../apz/test/mochitest/helper_subframe_style.css | 15 + gfx/layers/apz/test/mochitest/helper_tall.html | 504 +++++++++++++++ gfx/layers/apz/test/mochitest/helper_tap.html | 32 + .../apz/test/mochitest/helper_tap_fullzoom.html | 33 + .../apz/test/mochitest/helper_tap_passive.html | 64 ++ .../apz/test/mochitest/helper_touch_action.html | 115 ++++ .../mochitest/helper_touch_action_complex.html | 143 ++++ .../mochitest/helper_touch_action_regions.html | 246 +++++++ gfx/layers/apz/test/mochitest/mochitest.ini | 67 ++ gfx/layers/apz/test/mochitest/test_bug1151663.html | 38 ++ gfx/layers/apz/test/mochitest/test_bug1151667.html | 65 ++ gfx/layers/apz/test/mochitest/test_bug1253683.html | 59 ++ gfx/layers/apz/test/mochitest/test_bug1277814.html | 106 +++ .../apz/test/mochitest/test_bug1304689-2.html | 131 ++++ gfx/layers/apz/test/mochitest/test_bug1304689.html | 135 ++++ gfx/layers/apz/test/mochitest/test_bug982141.html | 38 ++ .../test/mochitest/test_frame_reconstruction.html | 218 +++++++ .../apz/test/mochitest/test_group_mouseevents.html | 35 + .../test/mochitest/test_group_pointerevents.html | 31 + .../apz/test/mochitest/test_group_touchevents.html | 104 +++ .../apz/test/mochitest/test_group_wheelevents.html | 41 ++ gfx/layers/apz/test/mochitest/test_group_zoom.html | 44 ++ .../test/mochitest/test_interrupted_reflow.html | 719 +++++++++++++++++++++ .../apz/test/mochitest/test_layerization.html | 214 ++++++ .../mochitest/test_scroll_inactive_bug1190112.html | 541 ++++++++++++++++ .../test_scroll_inactive_flattened_frame.html | 49 ++ .../mochitest/test_scroll_subframe_scrollbar.html | 117 ++++ gfx/layers/apz/test/mochitest/test_smoothness.html | 77 +++ .../test_touch_listeners_impacting_wheel.html | 114 ++++ .../apz/test/mochitest/test_wheel_scroll.html | 106 +++ .../test/mochitest/test_wheel_transactions.html | 137 ++++ .../apz/test/reftest/async-scrollbar-1-h-ref.html | 9 + .../test/reftest/async-scrollbar-1-h-rtl-ref.html | 10 + .../apz/test/reftest/async-scrollbar-1-h-rtl.html | 14 + .../apz/test/reftest/async-scrollbar-1-h.html | 13 + .../apz/test/reftest/async-scrollbar-1-v-ref.html | 9 + .../test/reftest/async-scrollbar-1-v-rtl-ref.html | 10 + .../apz/test/reftest/async-scrollbar-1-v-rtl.html | 14 + .../apz/test/reftest/async-scrollbar-1-v.html | 13 + .../apz/test/reftest/async-scrollbar-1-vh-ref.html | 9 + .../test/reftest/async-scrollbar-1-vh-rtl-ref.html | 10 + .../apz/test/reftest/async-scrollbar-1-vh-rtl.html | 14 + .../apz/test/reftest/async-scrollbar-1-vh.html | 13 + .../test/reftest/async-scrollbar-zoom-1-ref.html | 9 + .../apz/test/reftest/async-scrollbar-zoom-1.html | 14 + .../test/reftest/async-scrollbar-zoom-2-ref.html | 9 + .../apz/test/reftest/async-scrollbar-zoom-2.html | 14 + .../frame-reconstruction-scroll-clamping-ref.html | 27 + .../frame-reconstruction-scroll-clamping.html | 53 ++ .../apz/test/reftest/initial-scale-1-ref.html | 10 + gfx/layers/apz/test/reftest/initial-scale-1.html | 10 + gfx/layers/apz/test/reftest/reftest-stylo.list | 20 + gfx/layers/apz/test/reftest/reftest.list | 19 + 91 files changed, 11804 insertions(+) create mode 100644 gfx/layers/apz/test/gtest/APZCBasicTester.h create mode 100644 gfx/layers/apz/test/gtest/APZCTreeManagerTester.h create mode 100644 gfx/layers/apz/test/gtest/APZTestCommon.h create mode 100644 gfx/layers/apz/test/gtest/InputUtils.h create mode 100644 gfx/layers/apz/test/gtest/TestBasic.cpp create mode 100644 gfx/layers/apz/test/gtest/TestEventRegions.cpp create mode 100644 gfx/layers/apz/test/gtest/TestGestureDetector.cpp create mode 100644 gfx/layers/apz/test/gtest/TestHitTesting.cpp create mode 100644 gfx/layers/apz/test/gtest/TestInputQueue.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPanning.cpp create mode 100644 gfx/layers/apz/test/gtest/TestPinching.cpp create mode 100644 gfx/layers/apz/test/gtest/TestScrollHandoff.cpp create mode 100644 gfx/layers/apz/test/gtest/TestSnapping.cpp create mode 100644 gfx/layers/apz/test/gtest/TestTreeManager.cpp create mode 100644 gfx/layers/apz/test/gtest/moz.build create mode 100644 gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js create mode 100644 gfx/layers/apz/test/mochitest/apz_test_utils.js create mode 100644 gfx/layers/apz/test/mochitest/chrome.ini create mode 100644 gfx/layers/apz/test/mochitest/helper_basic_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1151663.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1162771.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1271432.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1280013.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1285070.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug1299195.html create mode 100644 gfx/layers/apz/test/mochitest/helper_bug982141.html create mode 100644 gfx/layers/apz/test/mochitest/helper_click.html create mode 100644 gfx/layers/apz/test/mochitest/helper_div_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_click.html create mode 100644 gfx/layers/apz/test/mochitest/helper_drag_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe1.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe2.html create mode 100644 gfx/layers/apz/test/mochitest/helper_iframe_pan.html create mode 100644 gfx/layers/apz/test/mochitest/helper_long_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html create mode 100644 gfx/layers/apz/test/mochitest/helper_scrollto_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_subframe_style.css create mode 100644 gfx/layers/apz/test/mochitest/helper_tall.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html create mode 100644 gfx/layers/apz/test/mochitest/helper_tap_passive.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_complex.html create mode 100644 gfx/layers/apz/test/mochitest/helper_touch_action_regions.html create mode 100644 gfx/layers/apz/test/mochitest/mochitest.ini create mode 100644 gfx/layers/apz/test/mochitest/test_bug1151663.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1151667.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1253683.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1277814.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1304689-2.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug1304689.html create mode 100644 gfx/layers/apz/test/mochitest/test_bug982141.html create mode 100644 gfx/layers/apz/test/mochitest/test_frame_reconstruction.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_mouseevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_pointerevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_touchevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_wheelevents.html create mode 100644 gfx/layers/apz/test/mochitest/test_group_zoom.html create mode 100644 gfx/layers/apz/test/mochitest/test_interrupted_reflow.html create mode 100644 gfx/layers/apz/test/mochitest/test_layerization.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html create mode 100644 gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html create mode 100644 gfx/layers/apz/test/mochitest/test_smoothness.html create mode 100644 gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html create mode 100644 gfx/layers/apz/test/mochitest/test_wheel_scroll.html create mode 100644 gfx/layers/apz/test/mochitest/test_wheel_transactions.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-h.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-v.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html create mode 100644 gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html create mode 100644 gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html create mode 100644 gfx/layers/apz/test/reftest/initial-scale-1-ref.html create mode 100644 gfx/layers/apz/test/reftest/initial-scale-1.html create mode 100644 gfx/layers/apz/test/reftest/reftest-stylo.list create mode 100644 gfx/layers/apz/test/reftest/reftest.list (limited to 'gfx/layers/apz/test') diff --git a/gfx/layers/apz/test/gtest/APZCBasicTester.h b/gfx/layers/apz/test/gtest/APZCBasicTester.h new file mode 100644 index 000000000..79a69301f --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCBasicTester.h @@ -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 tm; + RefPtr apzc; +}; + +#endif // mozilla_layers_APZCBasicTester_h diff --git a/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h new file mode 100644 index 000000000..4eeed1e7e --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZCTreeManagerTester.h @@ -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 : layers) { + if (TestAsyncPanZoomController* apzc = ApzcOf(layer)) { + apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut); + } + } + } + + nsTArray > layers; + RefPtr lm; + RefPtr root; + + RefPtr 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( + 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 diff --git a/gfx/layers/apz/test/gtest/APZTestCommon.h b/gfx/layers/apz/test/gtest/APZTestCommon.h new file mode 100644 index 000000000..6e259ab60 --- /dev/null +++ b/gfx/layers/apz/test/gtest/APZTestCommon.h @@ -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 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 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 aTask, int aDelayMs) { + RefPtr task = aTask; + } + bool IsRepaintThread() { + return NS_IsMainThread(); + } + void DispatchToRepaintThread(already_AddRefed 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 aTask, int aDelayMs) { + RefPtr 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, 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, 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, TimeStamp>> mTaskQueue; + TimeStamp mTime; +}; + +class TestAPZCTreeManager : public APZCTreeManager { +public: + explicit TestAPZCTreeManager(MockContentControllerDelayed* aMcc) : mcc(aMcc) {} + + RefPtr 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 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 target = this; + GetInputQueue()->SetConfirmedTargetApzc(aInputBlockId, target); + } + + void SetAllowedTouchBehavior(uint64_t aInputBlockId, const nsTArray& 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(); + } + + template + void Tap(const RefPtr& aTarget, const ScreenIntPoint& aPoint, + TimeDuration aTapLength, + nsEventStatus (*aOutEventStatuses)[2] = nullptr, + uint64_t* aOutInputBlockId = nullptr); + + template + void TapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, TimeDuration aTapLength); + + template + void Pan(const RefPtr& aTarget, + const ScreenIntPoint& aTouchStart, + const ScreenIntPoint& aTouchEnd, + bool aKeepFingerDown = false, + nsTArray* 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 + void Pan(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, bool aKeepFingerDown = false, + nsTArray* 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 + void PanAndCheckStatus(const RefPtr& aTarget, int aTouchStartY, + int aTouchEndY, + bool aExpectConsumed, + nsTArray* aAllowedTouchBehaviors, + uint64_t* aOutInputBlockId = nullptr); + + void ApzcPanNoFling(const RefPtr& aApzc, + int aTouchStartY, + int aTouchEndY, + uint64_t* aOutInputBlockId = nullptr); + + template + void DoubleTap(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + nsEventStatus (*aOutEventStatuses)[4] = nullptr, + uint64_t (*aOutInputBlockIds)[2] = nullptr); + + template + void DoubleTapAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aPoint, + uint64_t (*aOutInputBlockIds)[2] = nullptr); + +protected: + RefPtr mcc; +}; + +template +void +APZCTesterBase::Tap(const RefPtr& 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 +void +APZCTesterBase::TapAndCheckStatus(const RefPtr& 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 +void +APZCTesterBase::Pan(const RefPtr& aTarget, + const ScreenIntPoint& aTouchStart, + const ScreenIntPoint& aTouchEnd, + bool aKeepFingerDown, + nsTArray* 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 +void +APZCTesterBase::Pan(const RefPtr& aTarget, + int aTouchStartY, int aTouchEndY, bool aKeepFingerDown, + nsTArray* aAllowedTouchBehaviors, + nsEventStatus (*aOutEventStatuses)[4], + uint64_t* aOutInputBlockId) +{ + Pan(aTarget, ScreenIntPoint(10, aTouchStartY), ScreenIntPoint(10, aTouchEndY), + aKeepFingerDown, aAllowedTouchBehaviors, aOutEventStatuses, aOutInputBlockId); +} + +template +void +APZCTesterBase::PanAndCheckStatus(const RefPtr& aTarget, + int aTouchStartY, + int aTouchEndY, + bool aExpectConsumed, + nsTArray* 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& aApzc, + int aTouchStartY, int aTouchEndY, + uint64_t* aOutInputBlockId) +{ + Pan(aApzc, aTouchStartY, aTouchEndY, false, nullptr, nullptr, aOutInputBlockId); + aApzc->CancelAnimation(); +} + +template +void +APZCTesterBase::DoubleTap(const RefPtr& 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 +void +APZCTesterBase::DoubleTapAndCheckStatus(const RefPtr& 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(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 diff --git a/gfx/layers/apz/test/gtest/InputUtils.h b/gfx/layers/apz/test/gtest/InputUtils.h new file mode 100644 index 000000000..a1bd2851e --- /dev/null +++ b/gfx/layers/apz/test/gtest/InputUtils.h @@ -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& 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 +void +SetDefaultAllowedTouchBehavior(const RefPtr& aTarget, + uint64_t aInputBlockId, + int touchPoints = 1) +{ + nsTArray 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 +nsEventStatus +TouchDown(const RefPtr& 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 +nsEventStatus +TouchMove(const RefPtr& 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 +nsEventStatus +TouchUp(const RefPtr& 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 +void +PinchWithPinchInput(const RefPtr& 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 +void +PinchWithPinchInputAndCheckStatus(const RefPtr& 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 +void +PinchWithTouchInput(const RefPtr& aTarget, + const ScreenIntPoint& aFocus, float aScale, + int& inputId, + nsTArray* 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 +void +PinchWithTouchInputAndCheckStatus(const RefPtr& aTarget, + const ScreenIntPoint& aFocus, float aScale, + int& inputId, bool aShouldTriggerPinch, + nsTArray* 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 +nsEventStatus +Wheel(const RefPtr& 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 +nsEventStatus +SmoothWheel(const RefPtr& 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 +nsEventStatus +MouseDown(const RefPtr& 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 +nsEventStatus +MouseMove(const RefPtr& 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 +nsEventStatus +MouseUp(const RefPtr& 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 diff --git a/gfx/layers/apz/test/gtest/TestBasic.cpp b/gfx/layers/apz/test/gtest/TestBasic.cpp new file mode 100644 index 000000000..921ea4080 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestBasic.cpp @@ -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 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 > layers; + RefPtr lm; + RefPtr 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 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(); +} diff --git a/gfx/layers/apz/test/gtest/TestEventRegions.cpp b/gfx/layers/apz/test/gtest/TestEventRegions.cpp new file mode 100644 index 000000000..8b3aac348 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestEventRegions.cpp @@ -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 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(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(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(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(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(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 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 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 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 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 targets; + targets.AppendElement(apzc2->GetGuid()); + manager->SetTargetAPZC(inputBlockId, targets); +} diff --git a/gfx/layers/apz/test/gtest/TestGestureDetector.cpp b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp new file mode 100644 index 000000000..fcbc250f7 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestGestureDetector.cpp @@ -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 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 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 allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction 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 allowedTouchBehaviors; + allowedTouchBehaviors.AppendElement(aBehavior); + apzc->SetAllowedTouchBehavior(blockId, allowedTouchBehaviors); + } + // Have content "respond" to the touchstart + apzc->ContentReceivedInputBlock(blockId, false); + + MockFunction 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()); +} diff --git a/gfx/layers/apz/test/gtest/TestHitTesting.cpp b/gfx/layers/apz/test/gtest/TestHitTesting.cpp new file mode 100644 index 000000000..182194208 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestHitTesting.cpp @@ -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 GetTargetAPZC(const ScreenPoint& aPoint) { + RefPtr 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 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 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 root = manager->GetRootNode(); + RefPtr node5 = root->GetLastChild(); + RefPtr node4 = node5->GetPrevSibling(); + RefPtr node2 = node4->GetPrevSibling(); + RefPtr node1 = node2->GetPrevSibling(); + RefPtr node3 = node2->GetLastChild(); + RefPtr node9 = node5->GetLastChild(); + RefPtr node8 = node9->GetPrevSibling(); + RefPtr node6 = node8->GetPrevSibling(); + RefPtr 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 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 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 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()); +} diff --git a/gfx/layers/apz/test/gtest/TestInputQueue.cpp b/gfx/layers/apz/test/gtest/TestInputQueue.cpp new file mode 100644 index 000000000..d05b6d66e --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestInputQueue.cpp @@ -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 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 +} diff --git a/gfx/layers/apz/test/gtest/TestPanning.cpp b/gfx/layers/apz/test/gtest/TestPanning.cpp new file mode 100644 index 000000000..3ee19a58d --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPanning.cpp @@ -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 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 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(); +} diff --git a/gfx/layers/apz/test/gtest/TestPinching.cpp b/gfx/layers/apz/test/gtest/TestPinching.cpp new file mode 100644 index 000000000..54fb7a757 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestPinching.cpp @@ -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 *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 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 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 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 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(); +} diff --git a/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp new file mode 100644 index 000000000..d57d09ead --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestScrollHandoff.cpp @@ -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 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(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(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(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 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 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 parent = ApzcOf(root); + RefPtr 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 parent1 = ApzcOf(layers[1]); + RefPtr child1 = ApzcOf(layers[2]); + RefPtr parent2 = ApzcOf(layers[3]); + RefPtr 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 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 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 parentApzc = ApzcOf(root); + RefPtr 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 parentApzc = ApzcOf(root); + RefPtr 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); +} diff --git a/gfx/layers/apz/test/gtest/TestSnapping.cpp b/gfx/layers/apz/test/gtest/TestSnapping.cpp new file mode 100644 index 000000000..95c21ca44 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestSnapping.cpp @@ -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 registration = MakeUnique(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); +} diff --git a/gfx/layers/apz/test/gtest/TestTreeManager.cpp b/gfx/layers/apz/test/gtest/TestTreeManager.cpp new file mode 100644 index 000000000..80a7d0579 --- /dev/null +++ b/gfx/layers/apz/test/gtest/TestTreeManager.cpp @@ -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 root = manager->GetRootNode(); + RefPtr node2 = root->GetFirstChild()->GetFirstChild(); + RefPtr 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 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); +} + diff --git a/gfx/layers/apz/test/gtest/moz.build b/gfx/layers/apz/test/gtest/moz.build new file mode 100644 index 000000000..f3dc8c3dc --- /dev/null +++ b/gfx/layers/apz/test/gtest/moz.build @@ -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'] diff --git a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js new file mode 100644 index 000000000..7f820a936 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js @@ -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); + } + }); +} diff --git a/gfx/layers/apz/test/mochitest/apz_test_utils.js b/gfx/layers/apz/test/mochitest/apz_test_utils.js new file mode 100644 index 000000000..c97738434 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js @@ -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. +// 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); + }); + }; +} diff --git a/gfx/layers/apz/test/mochitest/chrome.ini b/gfx/layers/apz/test/mochitest/chrome.ini new file mode 100644 index 000000000..d52da5928 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/chrome.ini @@ -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 diff --git a/gfx/layers/apz/test/mochitest/helper_basic_pan.html b/gfx/layers/apz/test/mochitest/helper_basic_pan.html new file mode 100644 index 000000000..c33258da8 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_basic_pan.html @@ -0,0 +1,38 @@ + + + + + + Sanity panning test + + + + + + +
+ This div makes the page scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1151663.html b/gfx/layers/apz/test/mochitest/helper_bug1151663.html new file mode 100644 index 000000000..ef2fde9a9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1151663.html @@ -0,0 +1,83 @@ + + + + + + Test for Bug 1151663, helper page + + + + + Mozilla Bug 1151663 +
+ +
+
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1162771.html b/gfx/layers/apz/test/mochitest/helper_bug1162771.html new file mode 100644 index 000000000..18e4a2f05 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1162771.html @@ -0,0 +1,104 @@ + + + + + + Test for touchend on media elements + + + + + + + +

Tap on the colored boxes to hide them.

+ + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1271432.html b/gfx/layers/apz/test/mochitest/helper_bug1271432.html new file mode 100644 index 000000000..8234b8232 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1271432.html @@ -0,0 +1,574 @@ + + Ensure that the hit region doesn't get unexpectedly expanded + + + + + + + +Some text + +
+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.
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+
+
this div makes the page scrollable
+ diff --git a/gfx/layers/apz/test/mochitest/helper_bug1280013.html b/gfx/layers/apz/test/mochitest/helper_bug1280013.html new file mode 100644 index 000000000..0c602901a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1280013.html @@ -0,0 +1,74 @@ + + + + + + + Test for bug 1280013 + + + + + + + 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. + + + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1285070.html b/gfx/layers/apz/test/mochitest/helper_bug1285070.html new file mode 100644 index 000000000..3a4879034 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1285070.html @@ -0,0 +1,44 @@ + + + + + + Test pointer events are dispatched once for touch tap + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug1299195.html b/gfx/layers/apz/test/mochitest/helper_bug1299195.html new file mode 100644 index 000000000..8e746749c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug1299195.html @@ -0,0 +1,47 @@ + + + + + + Test pointer events are dispatched once for touch tap + + + + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_bug982141.html b/gfx/layers/apz/test/mochitest/helper_bug982141.html new file mode 100644 index 000000000..5d2f15397 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_bug982141.html @@ -0,0 +1,149 @@ + + + + + + + Test for Bug 982141, helper page + + + + + Mozilla Bug 982141 + +
+
+ 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. +
+ Line 1
+ Line 2
+ Line 3
+ Line 4
+ Line 5
+ Line 6
+ Line 7
+ Line 8
+ Line 9
+ Line 10
+ Line 11
+ Line 12
+ Line 13
+ Line 14
+ Line 15
+ Line 16
+ Line 17
+ Line 18
+ Line 19
+ Line 20
+ Line 21
+ Line 22
+ Line 23
+ Line 24
+ Line 25
+ Line 26
+ Line 27
+ Line 28
+ Line 29
+ Line 30
+ Line 31
+ Line 32
+ Line 33
+ Line 34
+ Line 35
+ Line 36
+ Line 37
+ Line 38
+ Line 39
+ Line 40
+ Line 41
+ Line 42
+ Line 43
+ Line 44
+ Line 45
+ Line 46
+ Line 40
+ Line 48
+ Line 49
+ Line 50
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_click.html b/gfx/layers/apz/test/mochitest/helper_click.html new file mode 100644 index 000000000..b74f175fe --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_click.html @@ -0,0 +1,41 @@ + + + + + + Sanity mouse-clicking test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_div_pan.html b/gfx/layers/apz/test/mochitest/helper_div_pan.html new file mode 100644 index 000000000..f37be8ba6 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_div_pan.html @@ -0,0 +1,44 @@ + + + + + + Sanity panning test for scrollable div + + + + + + +
+
+ This div makes the |outer| div scrollable. +
+
+
+ This div makes the top-level page scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_click.html b/gfx/layers/apz/test/mochitest/helper_drag_click.html new file mode 100644 index 000000000..cf7117339 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html @@ -0,0 +1,43 @@ + + + + + + Sanity mouse-drag click test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_drag_scroll.html b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html new file mode 100644 index 000000000..3c06a5b7e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_drag_scroll.html @@ -0,0 +1,603 @@ + + + + + + Dragging the mouse on a content-implemented scrollbar + + + + + + + + +
Drag up and down on this bar. The background/scrollbar shouldn't glitch
+This is a tall page
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe1.html b/gfx/layers/apz/test/mochitest/helper_iframe1.html new file mode 100644 index 000000000..047da96bd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe1.html @@ -0,0 +1,14 @@ + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe2.html b/gfx/layers/apz/test/mochitest/helper_iframe2.html new file mode 100644 index 000000000..fee3883e9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe2.html @@ -0,0 +1,14 @@ + + + + + + + +
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_iframe_pan.html b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html new file mode 100644 index 000000000..47213f33a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html @@ -0,0 +1,41 @@ + + + + + + Sanity panning test for scrollable div + + + + + + + +
+ This div makes the top-level page scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_long_tap.html b/gfx/layers/apz/test/mochitest/helper_long_tap.html new file mode 100644 index 000000000..604d03d64 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html @@ -0,0 +1,81 @@ + + + + + + Ensure we get a touch-cancel after a contextmenu comes up + + + + + + + Link to nowhere + + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html new file mode 100644 index 000000000..da866c1ce --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_perspective.html @@ -0,0 +1,46 @@ + + + Wheel-scrolling over inactive subframe with perspective + + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html new file mode 100644 index 000000000..763aaf92b --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_inactive_zindex.html @@ -0,0 +1,47 @@ + + + Wheel-scrolling over inactive subframe with z-index + + + + + + + +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html new file mode 100644 index 000000000..b9d187faf --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html @@ -0,0 +1,62 @@ + + + Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes + + + + + + +
sticky
+
fixed
+ + +
+
scrollable content inside a fixed-pos item
+
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html new file mode 100644 index 000000000..fc444f2b7 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html @@ -0,0 +1,61 @@ + + + + + + Sanity touch-tapping test + + + + + + +
spacer
+ + + diff --git a/gfx/layers/apz/test/mochitest/helper_subframe_style.css b/gfx/layers/apz/test/mochitest/helper_subframe_style.css new file mode 100644 index 000000000..5af964080 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_subframe_style.css @@ -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); +} diff --git a/gfx/layers/apz/test/mochitest/helper_tall.html b/gfx/layers/apz/test/mochitest/helper_tall.html new file mode 100644 index 000000000..7fde795fd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tall.html @@ -0,0 +1,504 @@ + + +This is a tall page
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+204
+205
+206
+207
+208
+209
+210
+211
+212
+213
+214
+215
+216
+217
+218
+219
+220
+221
+222
+223
+224
+225
+226
+227
+228
+229
+230
+231
+232
+233
+234
+235
+236
+237
+238
+239
+240
+241
+242
+243
+244
+245
+246
+247
+248
+249
+250
+251
+252
+253
+254
+255
+256
+257
+258
+259
+260
+261
+262
+263
+264
+265
+266
+267
+268
+269
+270
+271
+272
+273
+274
+275
+276
+277
+278
+279
+280
+281
+282
+283
+284
+285
+286
+287
+288
+289
+290
+291
+292
+293
+294
+295
+296
+297
+298
+299
+300
+301
+302
+303
+304
+305
+306
+307
+308
+309
+310
+311
+312
+313
+314
+315
+316
+317
+318
+319
+320
+321
+322
+323
+324
+325
+326
+327
+328
+329
+330
+331
+332
+333
+334
+335
+336
+337
+338
+339
+340
+341
+342
+343
+344
+345
+346
+347
+348
+349
+350
+351
+352
+353
+354
+355
+356
+357
+358
+359
+360
+361
+362
+363
+364
+365
+366
+367
+368
+369
+370
+371
+372
+373
+374
+375
+376
+377
+378
+379
+380
+381
+382
+383
+384
+385
+386
+387
+388
+389
+390
+391
+392
+393
+394
+395
+396
+397
+398
+399
+400
+401
+402
+403
+404
+405
+406
+407
+408
+409
+410
+411
+412
+413
+414
+415
+416
+417
+418
+419
+420
+421
+422
+423
+424
+425
+426
+427
+428
+429
+430
+431
+432
+433
+434
+435
+436
+437
+438
+439
+440
+441
+442
+443
+444
+445
+446
+447
+448
+449
+450
+451
+452
+453
+454
+455
+456
+457
+458
+459
+460
+461
+462
+463
+464
+465
+466
+467
+468
+469
+470
+471
+472
+473
+474
+475
+476
+477
+478
+479
+480
+481
+482
+483
+484
+485
+486
+487
+488
+489
+490
+491
+492
+493
+494
+495
+496
+497
+498
+499
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_tap.html b/gfx/layers/apz/test/mochitest/helper_tap.html new file mode 100644 index 000000000..6fde9387d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap.html @@ -0,0 +1,32 @@ + + + + + + Sanity touch-tapping test + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html new file mode 100644 index 000000000..494363b9c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_fullzoom.html @@ -0,0 +1,33 @@ + + + + + + Sanity touch-tapping test with fullzoom + + + + + + + + + diff --git a/gfx/layers/apz/test/mochitest/helper_tap_passive.html b/gfx/layers/apz/test/mochitest/helper_tap_passive.html new file mode 100644 index 000000000..dc3d85ed2 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_tap_passive.html @@ -0,0 +1,64 @@ + + + + + + Ensure APZ doesn't wait for passive listeners + + + + + + + Link to nowhere + + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action.html b/gfx/layers/apz/test/mochitest/helper_touch_action.html new file mode 100644 index 000000000..4495dc76e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action.html @@ -0,0 +1,115 @@ + + + + + + Sanity touch-action test + + + + + + +
+ This div makes the page scrollable on both axes.
+ This is the second line of text.
+ This is the third line of text.
+ This is the fourth line of text. +
+ +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html new file mode 100644 index 000000000..11d6e66e1 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_complex.html @@ -0,0 +1,143 @@ + + + + + + Complex touch-action test + + + + + + +
+
+
+ + + + +
+ + diff --git a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html new file mode 100644 index 000000000..cbd4cd61d --- /dev/null +++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html @@ -0,0 +1,246 @@ + + + + + + Test to ensure APZ doesn't always wait for touch-action + + + + + + +
+
+ This is a colored div that will move on the screen as the scroller scrolls. +
+
+ This is a large div to make the scroller scrollable. +
+ + diff --git a/gfx/layers/apz/test/mochitest/mochitest.ini b/gfx/layers/apz/test/mochitest/mochitest.ini new file mode 100644 index 000000000..09e62428c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/mochitest.ini @@ -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 diff --git a/gfx/layers/apz/test/mochitest/test_bug1151663.html b/gfx/layers/apz/test/mochitest/test_bug1151663.html new file mode 100644 index 000000000..10810c6ca --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1151663.html @@ -0,0 +1,38 @@ + + + + + + Test for Bug 1151663 + + + + + + + Mozilla Bug 1151663 + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1151667.html b/gfx/layers/apz/test/mochitest/test_bug1151667.html new file mode 100644 index 000000000..88facf6e9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1151667.html @@ -0,0 +1,65 @@ + + + + + Test for Bug 1151667 + + + + + + + + + +Mozilla Bug 1151667 +

+
+ +
+
+ +
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug1253683.html b/gfx/layers/apz/test/mochitest/test_bug1253683.html new file mode 100644 index 000000000..52c8e4a96 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1253683.html @@ -0,0 +1,59 @@ + + + + + Test to ensure non-scrollable frames don't get layerized + + + + + + + + +

+
+
sample code here
+
spacer to make the 'container' div the root scrollable element
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug1277814.html b/gfx/layers/apz/test/mochitest/test_bug1277814.html new file mode 100644 index 000000000..877286468 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1277814.html @@ -0,0 +1,106 @@ + + + + + + Test for Bug 1277814 + + + + + + + + + + +
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ CoolCmd
CoolCmd
CoolCmd
CoolCmd
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689-2.html b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html new file mode 100644 index 000000000..356d7bcb3 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1304689-2.html @@ -0,0 +1,131 @@ + + + + + + Test for Bug 1285070 + + + + + + + + +
+
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug1304689.html b/gfx/layers/apz/test/mochitest/test_bug1304689.html new file mode 100644 index 000000000..a64f8a34e --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug1304689.html @@ -0,0 +1,135 @@ + + + + + + Test for Bug 1285070 + + + + + + + + +
+
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+ this is some scrollable text.
+ this is a second line to make the scrolling more obvious.
+ and a third for good measure.
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_bug982141.html b/gfx/layers/apz/test/mochitest/test_bug982141.html new file mode 100644 index 000000000..9984b79ff --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_bug982141.html @@ -0,0 +1,38 @@ + + + + + + Test for Bug 982141 + + + + + + + Mozilla Bug 982141 + + diff --git a/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html new file mode 100644 index 000000000..589fb2843 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html @@ -0,0 +1,218 @@ + + + + + Test for bug 1235899 + + + + + + + + + +Mozilla Bug 1235899 +

+
+

You should be able to fling this list without it stopping abruptly

+
+
+
    +
  1. Some text
  2. +
  3. Some text
  4. +
  5. Some text
  6. +
  7. Some text
  8. +
  9. Some text
  10. +
  11. Some text
  12. +
  13. Some text
  14. +
  15. Some text
  16. +
  17. Some text
  18. +
  19. Some text
  20. +
  21. Some text
  22. +
  23. Some text
  24. +
  25. Some text
  26. +
  27. Some text
  28. +
  29. Some text
  30. +
  31. Some text
  32. +
  33. Some text
  34. +
  35. Some text
  36. +
  37. Some text
  38. +
  39. Some text
  40. +
  41. Some text
  42. +
  43. Some text
  44. +
  45. Some text
  46. +
  47. Some text
  48. +
  49. Some text
  50. +
  51. Some text
  52. +
  53. Some text
  54. +
  55. Some text
  56. +
  57. Some text
  58. +
  59. Some text
  60. +
  61. Some text
  62. +
  63. Some text
  64. +
  65. Some text
  66. +
  67. Some text
  68. +
  69. Some text
  70. +
  71. Some text
  72. +
  73. Some text
  74. +
  75. Some text
  76. +
  77. Some text
  78. +
  79. Some text
  80. +
  81. Some text
  82. +
  83. Some text
  84. +
  85. Some text
  86. +
  87. Some text
  88. +
  89. Some text
  90. +
  91. Some text
  92. +
  93. Some text
  94. +
  95. Some text
  96. +
  97. Some text
  98. +
  99. Some text
  100. +
  101. Some text
  102. +
  103. Some text
  104. +
  105. Some text
  106. +
  107. Some text
  108. +
  109. Some text
  110. +
  111. Some text
  112. +
  113. Some text
  114. +
  115. Some text
  116. +
  117. Some text
  118. +
  119. Some text
  120. +
  121. Some text
  122. +
  123. Some text
  124. +
  125. Some text
  126. +
  127. Some text
  128. +
  129. Some text
  130. +
  131. Some text
  132. +
  133. Some text
  134. +
  135. Some text
  136. +
  137. Some text
  138. +
  139. Some text
  140. +
  141. Some text
  142. +
  143. Some text
  144. +
  145. Some text
  146. +
  147. Some text
  148. +
  149. Some text
  150. +
  151. Some text
  152. +
  153. Some text
  154. +
  155. Some text
  156. +
  157. Some text
  158. +
  159. Some text
  160. +
  161. Some text
  162. +
  163. Some text
  164. +
  165. Some text
  166. +
  167. Some text
  168. +
  169. Some text
  170. +
  171. Some text
  172. +
  173. Some text
  174. +
  175. Some text
  176. +
  177. Some text
  178. +
  179. Some text
  180. +
  181. Some text
  182. +
  183. Some text
  184. +
  185. Some text
  186. +
  187. Some text
  188. +
  189. Some text
  190. +
  191. Some text
  192. +
  193. Some text
  194. +
  195. Some text
  196. +
  197. Some text
  198. +
  199. Some text
  200. +
  201. Some text
  202. +
  203. Some text
  204. +
+
+
+
+ +
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_mouseevents.html b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
new file mode 100644
index 000000000..dcf71f0cc
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
@@ -0,0 +1,35 @@
+
+
+
+  
+  Various mouse tests that spawn in new windows
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_pointerevents.html b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
new file mode 100644
index 000000000..2e8d7c240
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_pointerevents.html
@@ -0,0 +1,31 @@
+
+
+
+
+  
+  Test for Bug 1285070
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_touchevents.html b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
new file mode 100644
index 000000000..bc0261d46
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
@@ -0,0 +1,104 @@
+
+
+
+  
+  Various touch tests that spawn in new windows
+  
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
new file mode 100644
index 000000000..98c36f320
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
@@ -0,0 +1,41 @@
+
+
+
+  
+  Various wheel-scrolling tests that spawn in new windows
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_group_zoom.html b/gfx/layers/apz/test/mochitest/test_group_zoom.html
new file mode 100644
index 000000000..4bf9c0bed
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_group_zoom.html
@@ -0,0 +1,44 @@
+
+
+
+  
+  Various zoom-related tests that spawn in new windows
+  
+  
+  
+  
+
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
new file mode 100644
index 000000000..05c5e5478
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_interrupted_reflow.html
@@ -0,0 +1,719 @@
+
+
+ 
+ 
+  Test for bug 1292781
+  
+  
+  
+  
+  
+  
+  
+ 
+ 
+Mozilla Bug 1292781
+

+
+

The frame reconstruction should not leave this scrollframe in a bad state

+
+
+ this is the top of the scrollframe. +
this is a box
+
this is a box
+
this is a box
+
this is a box
+ this is near the top of the scrollframe. +
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+ this is near the bottom of the scrollframe. +
this is a box
+
this is a box
+
this is a box
+
this is a box
+
this is a box
+ this is the bottom of the scrollframe. +
+
+
+ +
+
+
+
diff --git a/gfx/layers/apz/test/mochitest/test_layerization.html b/gfx/layers/apz/test/mochitest/test_layerization.html
new file mode 100644
index 000000000..c74b181bd
--- /dev/null
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -0,0 +1,214 @@
+
+
+
+
+  Test for layerization
+  
+  
+  
+  
+  
+  
+  
+  
+
+
+APZ layerization tests
+

+
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html new file mode 100644 index 000000000..3349ef1ab --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html @@ -0,0 +1,541 @@ + + + + Test scrolling flattened inactive frames + + + + + + + + +
+
+
+
+

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+ +

+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html new file mode 100644 index 000000000..51e16aab9 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html @@ -0,0 +1,49 @@ + + + + Test scrolling flattened inactive frames + + + + + + + +
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html new file mode 100644 index 000000000..4d9da8c2c --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html @@ -0,0 +1,117 @@ + + + + Test scrolling subframe scrollbars + + + + + + + + +

+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+

+ + + diff --git a/gfx/layers/apz/test/mochitest/test_smoothness.html b/gfx/layers/apz/test/mochitest/test_smoothness.html new file mode 100644 index 000000000..88373957a --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_smoothness.html @@ -0,0 +1,77 @@ + + + Test Frame Uniformity While Scrolling + + + + + + + + + +
+
+ + diff --git a/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html new file mode 100644 index 000000000..913269a67 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html @@ -0,0 +1,114 @@ + + + + + Test for Bug 1203140 + + + + + + + + + +Mozilla Bug 1203140 +

+
+

The box below has a touch listener and a passive wheel listener. With touch events disabled, APZ shouldn't wait for any listeners.

+
+
Div to make 'content' scrollable
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_wheel_scroll.html b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html new file mode 100644 index 000000000..479478d42 --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html @@ -0,0 +1,106 @@ + + + + + Test for Bug 1013412 + + + + + + + + + +Mozilla Bug 1161206 +

+
+

Scrolling the page should be async, but scrolling over the dark circle should not scroll the page and instead rotate the white ball.

+
+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html new file mode 100644 index 000000000..e00e992cd --- /dev/null +++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html @@ -0,0 +1,137 @@ + + + + + Test for Bug 1175585 + + + + + + + + + +APZ wheel transactions test +

+
+
+
+
+
+
+
+
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html new file mode 100644 index 000000000..0e2698b86 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html new file mode 100644 index 000000000..ee2524a16 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl-ref.html @@ -0,0 +1,10 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html new file mode 100644 index 000000000..2f3d94639 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h-rtl.html @@ -0,0 +1,14 @@ + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html new file mode 100644 index 000000000..1eca19246 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-h.html @@ -0,0 +1,13 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html new file mode 100644 index 000000000..9ac5485bc --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html new file mode 100644 index 000000000..94fb501ba --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl-ref.html @@ -0,0 +1,10 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html new file mode 100644 index 000000000..9a2eb8818 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v-rtl.html @@ -0,0 +1,14 @@ + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html new file mode 100644 index 000000000..56fe23c28 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-v.html @@ -0,0 +1,13 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html new file mode 100644 index 000000000..564697b37 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html new file mode 100644 index 000000000..78cb0332c --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl-ref.html @@ -0,0 +1,10 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html new file mode 100644 index 000000000..397d1cf9b --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh-rtl.html @@ -0,0 +1,14 @@ + + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html new file mode 100644 index 000000000..a1d1527dd --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-1-vh.html @@ -0,0 +1,13 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html new file mode 100644 index 000000000..5ed970f76 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html new file mode 100644 index 000000000..09be51a79 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-1.html @@ -0,0 +1,14 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html new file mode 100644 index 000000000..5ed970f76 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2-ref.html @@ -0,0 +1,9 @@ + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html new file mode 100644 index 000000000..abe822c21 --- /dev/null +++ b/gfx/layers/apz/test/reftest/async-scrollbar-zoom-2.html @@ -0,0 +1,14 @@ + + + + + + +
+ + + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html new file mode 100644 index 000000000..3db9f2969 --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping-ref.html @@ -0,0 +1,27 @@ + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html new file mode 100644 index 000000000..479363f3f --- /dev/null +++ b/gfx/layers/apz/test/reftest/frame-reconstruction-scroll-clamping.html @@ -0,0 +1,53 @@ + + + + + +
+ This is the top of the page. +
+ This is the bottom of the page. + diff --git a/gfx/layers/apz/test/reftest/initial-scale-1-ref.html b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html new file mode 100644 index 000000000..dc99712d3 --- /dev/null +++ b/gfx/layers/apz/test/reftest/initial-scale-1-ref.html @@ -0,0 +1,10 @@ + + + + + +This tests that an initial-scale of 0 (i.e. garbage) is overridden
+with something a little more sane. + + + diff --git a/gfx/layers/apz/test/reftest/initial-scale-1.html b/gfx/layers/apz/test/reftest/initial-scale-1.html new file mode 100644 index 000000000..45bed0809 --- /dev/null +++ b/gfx/layers/apz/test/reftest/initial-scale-1.html @@ -0,0 +1,10 @@ + + + + + +This tests that an initial-scale of 0 (i.e. garbage) is overridden
+with something a little more sane. + + + diff --git a/gfx/layers/apz/test/reftest/reftest-stylo.list b/gfx/layers/apz/test/reftest/reftest-stylo.list new file mode 100644 index 000000000..cc2c76827 --- /dev/null +++ b/gfx/layers/apz/test/reftest/reftest-stylo.list @@ -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 diff --git a/gfx/layers/apz/test/reftest/reftest.list b/gfx/layers/apz/test/reftest/reftest.list new file mode 100644 index 000000000..4ab29420c --- /dev/null +++ b/gfx/layers/apz/test/reftest/reftest.list @@ -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 -- cgit v1.2.3