1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "APZCBasicTester.h"
8 #include "APZTestCommon.h"
9
10 #include "InputUtils.h"
11
TEST_F(APZCBasicTester,Overzoom)12 TEST_F(APZCBasicTester, Overzoom) {
13 // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
14 FrameMetrics fm;
15 fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
16 fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
17 fm.SetScrollOffset(CSSPoint(10, 0));
18 fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
19 fm.SetIsRootContent(true);
20 apzc->SetFrameMetrics(fm);
21
22 MakeApzcZoomable();
23
24 EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
25
26 PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true);
27
28 fm = apzc->GetFrameMetrics();
29 EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
30 // bug 936721 - PGO builds introduce rounding error so
31 // use a fuzzy match instead
32 EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5);
33 EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5);
34 }
35
TEST_F(APZCBasicTester,SimpleTransform)36 TEST_F(APZCBasicTester, SimpleTransform) {
37 ParentLayerPoint pointOut;
38 AsyncTransform viewTransformOut;
39 apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
40
41 EXPECT_EQ(ParentLayerPoint(), pointOut);
42 EXPECT_EQ(AsyncTransform(), viewTransformOut);
43 }
44
TEST_F(APZCBasicTester,ComplexTransform)45 TEST_F(APZCBasicTester, ComplexTransform) {
46 // This test assumes there is a page that gets rendered to
47 // two layers. In CSS pixels, the first layer is 50x50 and
48 // the second layer is 25x50. The widget scale factor is 3.0
49 // and the presShell resolution is 2.0. Therefore, these layers
50 // end up being 300x300 and 150x300 in layer pixels.
51 //
52 // The second (child) layer has an additional CSS transform that
53 // stretches it by 2.0 on the x-axis. Therefore, after applying
54 // CSS transforms, the two layers are the same size in screen
55 // pixels.
56 //
57 // The screen itself is 24x24 in screen pixels (therefore 4x4 in
58 // CSS pixels). The displayport is 1 extra CSS pixel on all
59 // sides.
60
61 RefPtr<TestAsyncPanZoomController> childApzc =
62 new TestAsyncPanZoomController(LayersId{0}, mcc, tm);
63
64 const char* layerTreeSyntax = "c(c)";
65 // LayerID 0 1
66 nsIntRegion layerVisibleRegion[] = {
67 nsIntRegion(IntRect(0, 0, 300, 300)),
68 nsIntRegion(IntRect(0, 0, 150, 300)),
69 };
70 Matrix4x4 transforms[] = {
71 Matrix4x4(),
72 Matrix4x4(),
73 };
74 transforms[0].PostScale(
75 0.5f, 0.5f,
76 1.0f); // this results from the 2.0 resolution on the root layer
77 transforms[1].PostScale(
78 2.0f, 1.0f,
79 1.0f); // this is the 2.0 x-axis CSS transform on the child layer
80
81 nsTArray<RefPtr<Layer> > layers;
82 RefPtr<LayerManager> lm;
83 RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion,
84 transforms, lm, layers);
85
86 ScrollMetadata metadata;
87 FrameMetrics& metrics = metadata.GetMetrics();
88 metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
89 metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
90 metrics.SetScrollOffset(CSSPoint(10, 10));
91 metrics.SetLayoutViewport(CSSRect(10, 10, 8, 8));
92 metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
93 metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
94 metrics.SetPresShellResolution(2.0f);
95 metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
96 metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
97 metrics.SetScrollId(ScrollableLayerGuid::START_SCROLL_ID);
98
99 ScrollMetadata childMetadata = metadata;
100 FrameMetrics& childMetrics = childMetadata.GetMetrics();
101 childMetrics.SetScrollId(ScrollableLayerGuid::START_SCROLL_ID + 1);
102
103 layers[0]->SetScrollMetadata(metadata);
104 layers[1]->SetScrollMetadata(childMetadata);
105
106 ParentLayerPoint pointOut;
107 AsyncTransform viewTransformOut;
108
109 // Both the parent and child layer should behave exactly the same here,
110 // because the CSS transform on the child layer does not affect the
111 // SampleContentTransformForFrame code
112
113 // initial transform
114 apzc->SetFrameMetrics(metrics);
115 apzc->NotifyLayersUpdated(metadata, true, true);
116 apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
117 EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()),
118 viewTransformOut);
119 EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
120
121 childApzc->SetFrameMetrics(childMetrics);
122 childApzc->NotifyLayersUpdated(childMetadata, true, true);
123 childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
124 EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()),
125 viewTransformOut);
126 EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);
127
128 // do an async scroll by 5 pixels and check the transform
129 metrics.ScrollBy(CSSPoint(5, 0));
130 apzc->SetFrameMetrics(metrics);
131 apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
132 EXPECT_EQ(
133 AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)),
134 viewTransformOut);
135 EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
136
137 childMetrics.ScrollBy(CSSPoint(5, 0));
138 childApzc->SetFrameMetrics(childMetrics);
139 childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
140 EXPECT_EQ(
141 AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)),
142 viewTransformOut);
143 EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);
144
145 // do an async zoom of 1.5x and check the transform
146 metrics.ZoomBy(1.5f);
147 apzc->SetFrameMetrics(metrics);
148 apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
149 EXPECT_EQ(
150 AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)),
151 viewTransformOut);
152 EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
153
154 childMetrics.ZoomBy(1.5f);
155 childApzc->SetFrameMetrics(childMetrics);
156 childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
157 EXPECT_EQ(
158 AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)),
159 viewTransformOut);
160 EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);
161
162 childApzc->Destroy();
163 }
164
165 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
TEST_F(APZCBasicTester,Fling)166 TEST_F(APZCBasicTester, Fling) {
167 SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f);
168 int touchStart = 50;
169 int touchEnd = 10;
170 ParentLayerPoint pointOut;
171 AsyncTransform viewTransformOut;
172
173 // Fling down. Each step scroll further down
174 Pan(apzc, touchStart, touchEnd);
175 ParentLayerPoint lastPoint;
176 for (int i = 1; i < 50; i += 1) {
177 apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut,
178 TimeDuration::FromMilliseconds(1));
179 EXPECT_GT(pointOut.y, lastPoint.y);
180 lastPoint = pointOut;
181 }
182 }
183 #endif
184
185 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
TEST_F(APZCBasicTester,FlingIntoOverscroll)186 TEST_F(APZCBasicTester, FlingIntoOverscroll) {
187 // Enable overscrolling.
188 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
189 SCOPED_GFX_PREF_FLOAT("apz.fling_min_velocity_threshold", 0.0f);
190
191 // Scroll down by 25 px. Don't fling for simplicity.
192 Pan(apzc, 50, 25, PanOptions::NoFling);
193
194 // Now scroll back up by 20px, this time flinging after.
195 // The fling should cover the remaining 5 px of room to scroll, then
196 // go into overscroll, and finally snap-back to recover from overscroll.
197 Pan(apzc, 25, 45);
198 const TimeDuration increment = TimeDuration::FromMilliseconds(1);
199 bool reachedOverscroll = false;
200 bool recoveredFromOverscroll = false;
201 while (apzc->AdvanceAnimations(mcc->Time())) {
202 if (!reachedOverscroll && apzc->IsOverscrolled()) {
203 reachedOverscroll = true;
204 }
205 if (reachedOverscroll && !apzc->IsOverscrolled()) {
206 recoveredFromOverscroll = true;
207 }
208 mcc->AdvanceBy(increment);
209 }
210 EXPECT_TRUE(reachedOverscroll);
211 EXPECT_TRUE(recoveredFromOverscroll);
212 }
213 #endif
214
TEST_F(APZCBasicTester,PanningTransformNotifications)215 TEST_F(APZCBasicTester, PanningTransformNotifications) {
216 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
217
218 // Scroll down by 25 px. Ensure we only get one set of
219 // state change notifications.
220 //
221 // Then, scroll back up by 20px, this time flinging after.
222 // The fling should cover the remaining 5 px of room to scroll, then
223 // go into overscroll, and finally snap-back to recover from overscroll.
224 // Again, ensure we only get one set of state change notifications for
225 // this entire procedure.
226
227 MockFunction<void(std::string checkPointName)> check;
228 {
229 InSequence s;
230 EXPECT_CALL(check, Call("Simple pan"));
231 EXPECT_CALL(*mcc,
232 NotifyAPZStateChange(
233 _, GeckoContentController::APZStateChange::eStartTouch, _))
234 .Times(1);
235 EXPECT_CALL(
236 *mcc,
237 NotifyAPZStateChange(
238 _, GeckoContentController::APZStateChange::eTransformBegin, _))
239 .Times(1);
240 EXPECT_CALL(
241 *mcc, NotifyAPZStateChange(
242 _, GeckoContentController::APZStateChange::eStartPanning, _))
243 .Times(1);
244 EXPECT_CALL(*mcc,
245 NotifyAPZStateChange(
246 _, GeckoContentController::APZStateChange::eEndTouch, _))
247 .Times(1);
248 EXPECT_CALL(
249 *mcc, NotifyAPZStateChange(
250 _, GeckoContentController::APZStateChange::eTransformEnd, _))
251 .Times(1);
252 EXPECT_CALL(check, Call("Complex pan"));
253 EXPECT_CALL(*mcc,
254 NotifyAPZStateChange(
255 _, GeckoContentController::APZStateChange::eStartTouch, _))
256 .Times(1);
257 EXPECT_CALL(
258 *mcc,
259 NotifyAPZStateChange(
260 _, GeckoContentController::APZStateChange::eTransformBegin, _))
261 .Times(1);
262 EXPECT_CALL(
263 *mcc, NotifyAPZStateChange(
264 _, GeckoContentController::APZStateChange::eStartPanning, _))
265 .Times(1);
266 EXPECT_CALL(*mcc,
267 NotifyAPZStateChange(
268 _, GeckoContentController::APZStateChange::eEndTouch, _))
269 .Times(1);
270 EXPECT_CALL(
271 *mcc, NotifyAPZStateChange(
272 _, GeckoContentController::APZStateChange::eTransformEnd, _))
273 .Times(1);
274 EXPECT_CALL(check, Call("Done"));
275 }
276
277 check.Call("Simple pan");
278 Pan(apzc, 50, 25, PanOptions::NoFling);
279 check.Call("Complex pan");
280 Pan(apzc, 25, 45);
281 apzc->AdvanceAnimationsUntilEnd();
282 check.Call("Done");
283 }
284
PanIntoOverscroll()285 void APZCBasicTester::PanIntoOverscroll() {
286 int touchStart = 500;
287 int touchEnd = 10;
288 Pan(apzc, touchStart, touchEnd);
289 EXPECT_TRUE(apzc->IsOverscrolled());
290 }
291
TestOverscroll()292 void APZCBasicTester::TestOverscroll() {
293 // Pan sufficiently to hit overscroll behavior
294 PanIntoOverscroll();
295
296 // Check that we recover from overscroll via an animation.
297 ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
298 SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
299 }
300
301 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
TEST_F(APZCBasicTester,OverScrollPanning)302 TEST_F(APZCBasicTester, OverScrollPanning) {
303 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
304
305 TestOverscroll();
306 }
307 #endif
308
309 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
310 // Tests that an overscroll animation doesn't trigger an assertion failure
311 // in the case where a sample has a velocity of zero.
TEST_F(APZCBasicTester,OverScroll_Bug1152051a)312 TEST_F(APZCBasicTester, OverScroll_Bug1152051a) {
313 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
314
315 // Doctor the prefs to make the velocity zero at the end of the first sample.
316
317 // This ensures our incoming velocity to the overscroll animation is
318 // a round(ish) number, 4.9 (that being the distance of the pan before
319 // overscroll, which is 500 - 10 = 490 pixels, divided by the duration of
320 // the pan, which is 100 ms).
321 SCOPED_GFX_PREF_FLOAT("apz.fling_friction", 0);
322
323 TestOverscroll();
324 }
325 #endif
326
327 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
328 // Tests that ending an overscroll animation doesn't leave around state that
329 // confuses the next overscroll animation.
TEST_F(APZCBasicTester,OverScroll_Bug1152051b)330 TEST_F(APZCBasicTester, OverScroll_Bug1152051b) {
331 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
332 SCOPED_GFX_PREF_FLOAT("apz.overscroll.stop_distance_threshold", 0.1f);
333
334 // Pan sufficiently to hit overscroll behavior
335 PanIntoOverscroll();
336
337 // Sample animations once, to give the fling animation started on touch-up
338 // a chance to realize it's overscrolled, and schedule a call to
339 // HandleFlingOverscroll().
340 SampleAnimationOnce();
341
342 // This advances the time and runs the HandleFlingOverscroll task scheduled in
343 // the previous call, which starts an overscroll animation. It then samples
344 // the overscroll animation once, to get it to initialize the first overscroll
345 // sample.
346 SampleAnimationOnce();
347
348 // Do a touch-down to cancel the overscroll animation, and then a touch-up
349 // to schedule a new one since we're still overscrolled. We don't pan because
350 // panning can trigger functions that clear the overscroll animation state
351 // in other ways.
352 APZEventResult result = TouchDown(apzc, ScreenIntPoint(10, 10), mcc->Time());
353 if (StaticPrefs::layout_css_touch_action_enabled() &&
354 result.mStatus != nsEventStatus_eConsumeNoDefault) {
355 SetDefaultAllowedTouchBehavior(apzc, result.mInputBlockId);
356 }
357 TouchUp(apzc, ScreenIntPoint(10, 10), mcc->Time());
358
359 // Sample the second overscroll animation to its end.
360 // If the ending of the first overscroll animation fails to clear state
361 // properly, this will assert.
362 ParentLayerPoint expectedScrollOffset(0, GetScrollRange().YMost());
363 SampleAnimationUntilRecoveredFromOverscroll(expectedScrollOffset);
364 }
365 #endif
366
367 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
368 // Tests that the page doesn't get stuck in an
369 // overscroll animation after a low-velocity pan.
TEST_F(APZCBasicTester,OverScrollAfterLowVelocityPan_Bug1343775)370 TEST_F(APZCBasicTester, OverScrollAfterLowVelocityPan_Bug1343775) {
371 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
372
373 // Pan into overscroll with a velocity less than the
374 // apz.fling_min_velocity_threshold preference.
375 Pan(apzc, 10, 30);
376
377 EXPECT_TRUE(apzc->IsOverscrolled());
378
379 apzc->AdvanceAnimationsUntilEnd();
380
381 // Check that we recovered from overscroll.
382 EXPECT_FALSE(apzc->IsOverscrolled());
383 }
384 #endif
385
386 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
TEST_F(APZCBasicTester,OverScrollAbort)387 TEST_F(APZCBasicTester, OverScrollAbort) {
388 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
389
390 // Pan sufficiently to hit overscroll behavior
391 int touchStart = 500;
392 int touchEnd = 10;
393 Pan(apzc, touchStart, touchEnd);
394 EXPECT_TRUE(apzc->IsOverscrolled());
395
396 ParentLayerPoint pointOut;
397 AsyncTransform viewTransformOut;
398
399 // This sample call will run to the end of the fling animation
400 // and will schedule the overscroll animation.
401 apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut,
402 TimeDuration::FromMilliseconds(10000));
403 EXPECT_TRUE(apzc->IsOverscrolled());
404
405 // At this point, we have an active overscroll animation.
406 // Check that cancelling the animation clears the overscroll.
407 apzc->CancelAnimation();
408 EXPECT_FALSE(apzc->IsOverscrolled());
409 apzc->AssertStateIsReset();
410 }
411 #endif
412
413 #ifndef MOZ_WIDGET_ANDROID // Currently fails on Android
TEST_F(APZCBasicTester,OverScrollPanningAbort)414 TEST_F(APZCBasicTester, OverScrollPanningAbort) {
415 SCOPED_GFX_PREF_BOOL("apz.overscroll.enabled", true);
416
417 // Pan sufficiently to hit overscroll behaviour. Keep the finger down so
418 // the pan does not end.
419 int touchStart = 500;
420 int touchEnd = 10;
421 Pan(apzc, touchStart, touchEnd, PanOptions::KeepFingerDown);
422 EXPECT_TRUE(apzc->IsOverscrolled());
423
424 // Check that calling CancelAnimation() while the user is still panning
425 // (and thus no fling or snap-back animation has had a chance to start)
426 // clears the overscroll.
427 apzc->CancelAnimation();
428 EXPECT_FALSE(apzc->IsOverscrolled());
429 apzc->AssertStateIsReset();
430 }
431 #endif
432
433 #ifndef MOZ_WIDGET_ANDROID // Maybe fails on Android
TEST_F(APZCBasicTester,ResumeInterruptedTouchDrag_Bug1592435)434 TEST_F(APZCBasicTester, ResumeInterruptedTouchDrag_Bug1592435) {
435 // Start a touch-drag and scroll some amount, not lifting the finger.
436 SCOPED_GFX_PREF_FLOAT("apz.touch_start_tolerance", 1.0f / 1000.0f);
437 ScreenIntPoint touchPos(10, 50);
438 uint64_t touchBlock = TouchDown(apzc, touchPos, mcc->Time()).mInputBlockId;
439 SetDefaultAllowedTouchBehavior(apzc, touchBlock);
440 for (int i = 0; i < 20; ++i) {
441 touchPos.y -= 1;
442 mcc->AdvanceByMillis(1);
443 TouchMove(apzc, touchPos, mcc->Time());
444 }
445
446 // Take note of the scroll offset before the interruption.
447 CSSPoint scrollOffsetBeforeInterruption =
448 apzc->GetFrameMetrics().GetScrollOffset();
449
450 // Have the main thread interrupt the touch-drag by sending
451 // a main thread scroll update to a nearby location.
452 CSSPoint mainThreadOffset = scrollOffsetBeforeInterruption;
453 mainThreadOffset.y -= 5;
454 ScrollMetadata metadata = apzc->GetScrollMetadata();
455 metadata.GetMetrics().SetScrollOffset(mainThreadOffset);
456 metadata.GetMetrics().SetScrollGeneration(1);
457 metadata.GetMetrics().SetScrollOffsetUpdateType(FrameMetrics::eMainThread);
458 apzc->NotifyLayersUpdated(metadata, false, true);
459
460 // Continue and finish the touch-drag gesture.
461 for (int i = 0; i < 20; ++i) {
462 touchPos.y -= 1;
463 mcc->AdvanceByMillis(1);
464 TouchMove(apzc, touchPos, mcc->Time());
465 }
466
467 // Check that the portion of the touch-drag that occurred after
468 // the interruption caused additional scrolling.
469 CSSPoint finalScrollOffset = apzc->GetFrameMetrics().GetScrollOffset();
470 EXPECT_GT(finalScrollOffset.y, scrollOffsetBeforeInterruption.y);
471
472 // Now do the same thing, but for a visual scroll update.
473 scrollOffsetBeforeInterruption = apzc->GetFrameMetrics().GetScrollOffset();
474 mainThreadOffset = scrollOffsetBeforeInterruption;
475 mainThreadOffset.y -= 5;
476 metadata = apzc->GetScrollMetadata();
477 metadata.GetMetrics().SetVisualViewportOffset(mainThreadOffset);
478 metadata.GetMetrics().SetScrollGeneration(2);
479 metadata.GetMetrics().SetVisualScrollUpdateType(FrameMetrics::eMainThread);
480 apzc->NotifyLayersUpdated(metadata, false, true);
481 for (int i = 0; i < 20; ++i) {
482 touchPos.y -= 1;
483 mcc->AdvanceByMillis(1);
484 TouchMove(apzc, touchPos, mcc->Time());
485 }
486 finalScrollOffset = apzc->GetFrameMetrics().GetScrollOffset();
487 EXPECT_GT(finalScrollOffset.y, scrollOffsetBeforeInterruption.y);
488
489 // Clean up by ending the touch gesture.
490 mcc->AdvanceByMillis(1);
491 TouchUp(apzc, touchPos, mcc->Time());
492 }
493 #endif
494