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