/src/mozilla-central/gfx/layers/apz/test/gtest/TestHitTesting.cpp
Line | Count | Source (jump to first uncovered line) |
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 "APZCTreeManagerTester.h" |
8 | | #include "APZTestCommon.h" |
9 | | #include "gfxPrefs.h" |
10 | | #include "InputUtils.h" |
11 | | |
12 | | class APZHitTestingTester : public APZCTreeManagerTester { |
13 | | protected: |
14 | | ScreenToParentLayerMatrix4x4 transformToApzc; |
15 | | ParentLayerToScreenMatrix4x4 transformToGecko; |
16 | | |
17 | 0 | already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint) { |
18 | 0 | RefPtr<AsyncPanZoomController> hit = manager->GetTargetAPZC(aPoint, nullptr); |
19 | 0 | if (hit) { |
20 | 0 | transformToApzc = manager->GetScreenToApzcTransform(hit.get()); |
21 | 0 | transformToGecko = manager->GetApzcToGeckoTransform(hit.get()); |
22 | 0 | } |
23 | 0 | return hit.forget(); |
24 | 0 | } |
25 | | |
26 | | protected: |
27 | 0 | void CreateHitTesting1LayerTree() { |
28 | 0 | const char* layerTreeSyntax = "c(tttt)"; |
29 | 0 | // LayerID 0 1234 |
30 | 0 | nsIntRegion layerVisibleRegion[] = { |
31 | 0 | nsIntRegion(IntRect(0,0,100,100)), |
32 | 0 | nsIntRegion(IntRect(0,0,100,100)), |
33 | 0 | nsIntRegion(IntRect(10,10,20,20)), |
34 | 0 | nsIntRegion(IntRect(10,10,20,20)), |
35 | 0 | nsIntRegion(IntRect(5,5,20,20)), |
36 | 0 | }; |
37 | 0 | root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); |
38 | 0 | } |
39 | | |
40 | 0 | void CreateHitTesting2LayerTree() { |
41 | 0 | const char* layerTreeSyntax = "c(tc(t))"; |
42 | 0 | // LayerID 0 12 3 |
43 | 0 | nsIntRegion layerVisibleRegion[] = { |
44 | 0 | nsIntRegion(IntRect(0,0,100,100)), |
45 | 0 | nsIntRegion(IntRect(10,10,40,40)), |
46 | 0 | nsIntRegion(IntRect(10,60,40,40)), |
47 | 0 | nsIntRegion(IntRect(10,60,40,40)), |
48 | 0 | }; |
49 | 0 | Matrix4x4 transforms[] = { |
50 | 0 | Matrix4x4(), |
51 | 0 | Matrix4x4(), |
52 | 0 | Matrix4x4::Scaling(2, 1, 1), |
53 | 0 | Matrix4x4(), |
54 | 0 | }; |
55 | 0 | root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers); |
56 | 0 |
|
57 | 0 | SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); |
58 | 0 | SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 80, 80)); |
59 | 0 | SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 2, CSSRect(0, 0, 80, 80)); |
60 | 0 | } |
61 | | |
62 | 0 | void DisableApzOn(Layer* aLayer) { |
63 | 0 | ScrollMetadata m = aLayer->GetScrollMetadata(0); |
64 | 0 | m.SetForceDisableApz(true); |
65 | 0 | aLayer->SetScrollMetadata(m); |
66 | 0 | } |
67 | | |
68 | 0 | void CreateComplexMultiLayerTree() { |
69 | 0 | const char* layerTreeSyntax = "c(tc(t)tc(c(t)tt))"; |
70 | 0 | // LayerID 0 12 3 45 6 7 89 |
71 | 0 | nsIntRegion layerVisibleRegion[] = { |
72 | 0 | nsIntRegion(IntRect(0,0,300,400)), // root(0) |
73 | 0 | nsIntRegion(IntRect(0,0,100,100)), // thebes(1) in top-left |
74 | 0 | nsIntRegion(IntRect(50,50,200,300)), // container(2) centered in root(0) |
75 | 0 | nsIntRegion(IntRect(50,50,200,300)), // thebes(3) fully occupying parent container(2) |
76 | 0 | nsIntRegion(IntRect(0,200,100,100)), // thebes(4) in bottom-left |
77 | 0 | nsIntRegion(IntRect(200,0,100,400)), // container(5) along the right 100px of root(0) |
78 | 0 | nsIntRegion(IntRect(200,0,100,200)), // container(6) taking up the top half of parent container(5) |
79 | 0 | nsIntRegion(IntRect(200,0,100,200)), // thebes(7) fully occupying parent container(6) |
80 | 0 | nsIntRegion(IntRect(200,200,100,100)), // thebes(8) in bottom-right (below (6)) |
81 | 0 | nsIntRegion(IntRect(200,300,100,100)), // thebes(9) in bottom-right (below (8)) |
82 | 0 | }; |
83 | 0 | root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); |
84 | 0 | SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); |
85 | 0 | SetScrollableFrameMetrics(layers[2], FrameMetrics::START_SCROLL_ID); |
86 | 0 | SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 1); |
87 | 0 | SetScrollableFrameMetrics(layers[6], FrameMetrics::START_SCROLL_ID + 1); |
88 | 0 | SetScrollableFrameMetrics(layers[7], FrameMetrics::START_SCROLL_ID + 2); |
89 | 0 | SetScrollableFrameMetrics(layers[8], FrameMetrics::START_SCROLL_ID + 1); |
90 | 0 | SetScrollableFrameMetrics(layers[9], FrameMetrics::START_SCROLL_ID + 3); |
91 | 0 | } |
92 | | |
93 | 0 | void CreateBug1148350LayerTree() { |
94 | 0 | const char* layerTreeSyntax = "c(t)"; |
95 | 0 | // LayerID 0 1 |
96 | 0 | nsIntRegion layerVisibleRegion[] = { |
97 | 0 | nsIntRegion(IntRect(0,0,200,200)), |
98 | 0 | nsIntRegion(IntRect(0,0,200,200)), |
99 | 0 | }; |
100 | 0 | root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); |
101 | 0 | SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID); |
102 | 0 | } |
103 | | }; |
104 | | |
105 | | // A simple hit testing test that doesn't involve any transforms on layers. |
106 | 0 | TEST_F(APZHitTestingTester, HitTesting1) { |
107 | 0 | SCOPED_GFX_VAR(UseWebRender, bool, false); |
108 | 0 |
|
109 | 0 | CreateHitTesting1LayerTree(); |
110 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
111 | 0 |
|
112 | 0 | // No APZC attached so hit testing will return no APZC at (20,20) |
113 | 0 | RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(20, 20)); |
114 | 0 | TestAsyncPanZoomController* nullAPZC = nullptr; |
115 | 0 | EXPECT_EQ(nullAPZC, hit.get()); |
116 | 0 | EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); |
117 | 0 | EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); |
118 | 0 |
|
119 | 0 | uint32_t paintSequenceNumber = 0; |
120 | 0 |
|
121 | 0 | // Now we have a root APZC that will match the page |
122 | 0 | SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID); |
123 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, paintSequenceNumber++); |
124 | 0 | hit = GetTargetAPZC(ScreenPoint(15, 15)); |
125 | 0 | EXPECT_EQ(ApzcOf(root), hit.get()); |
126 | 0 | // expect hit point at LayerIntPoint(15, 15) |
127 | 0 | EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc.TransformPoint(ScreenPoint(15, 15))); |
128 | 0 | EXPECT_EQ(ScreenPoint(15, 15), transformToGecko.TransformPoint(ParentLayerPoint(15, 15))); |
129 | 0 |
|
130 | 0 | // Now we have a sub APZC with a better fit |
131 | 0 | SetScrollableFrameMetrics(layers[3], FrameMetrics::START_SCROLL_ID + 1); |
132 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, paintSequenceNumber++); |
133 | 0 | EXPECT_NE(ApzcOf(root), ApzcOf(layers[3])); |
134 | 0 | hit = GetTargetAPZC(ScreenPoint(25, 25)); |
135 | 0 | EXPECT_EQ(ApzcOf(layers[3]), hit.get()); |
136 | 0 | // expect hit point at LayerIntPoint(25, 25) |
137 | 0 | EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc.TransformPoint(ScreenPoint(25, 25))); |
138 | 0 | EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); |
139 | 0 |
|
140 | 0 | // At this point, layers[4] obscures layers[3] at the point (15, 15) so |
141 | 0 | // hitting there should hit the root APZC |
142 | 0 | hit = GetTargetAPZC(ScreenPoint(15, 15)); |
143 | 0 | EXPECT_EQ(ApzcOf(root), hit.get()); |
144 | 0 |
|
145 | 0 | // Now test hit testing when we have two scrollable layers |
146 | 0 | SetScrollableFrameMetrics(layers[4], FrameMetrics::START_SCROLL_ID + 2); |
147 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, paintSequenceNumber++); |
148 | 0 | hit = GetTargetAPZC(ScreenPoint(15, 15)); |
149 | 0 | EXPECT_EQ(ApzcOf(layers[4]), hit.get()); |
150 | 0 | // expect hit point at LayerIntPoint(15, 15) |
151 | 0 | EXPECT_EQ(ParentLayerPoint(15, 15), transformToApzc.TransformPoint(ScreenPoint(15, 15))); |
152 | 0 | EXPECT_EQ(ScreenPoint(15, 15), transformToGecko.TransformPoint(ParentLayerPoint(15, 15))); |
153 | 0 |
|
154 | 0 | // Hit test ouside the reach of layer[3,4] but inside root |
155 | 0 | hit = GetTargetAPZC(ScreenPoint(90, 90)); |
156 | 0 | EXPECT_EQ(ApzcOf(root), hit.get()); |
157 | 0 | // expect hit point at LayerIntPoint(90, 90) |
158 | 0 | EXPECT_EQ(ParentLayerPoint(90, 90), transformToApzc.TransformPoint(ScreenPoint(90, 90))); |
159 | 0 | EXPECT_EQ(ScreenPoint(90, 90), transformToGecko.TransformPoint(ParentLayerPoint(90, 90))); |
160 | 0 |
|
161 | 0 | // Hit test ouside the reach of any layer |
162 | 0 | hit = GetTargetAPZC(ScreenPoint(1000, 10)); |
163 | 0 | EXPECT_EQ(nullAPZC, hit.get()); |
164 | 0 | EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); |
165 | 0 | EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); |
166 | 0 | hit = GetTargetAPZC(ScreenPoint(-1000, 10)); |
167 | 0 | EXPECT_EQ(nullAPZC, hit.get()); |
168 | 0 | EXPECT_EQ(ScreenToParentLayerMatrix4x4(), transformToApzc); |
169 | 0 | EXPECT_EQ(ParentLayerToScreenMatrix4x4(), transformToGecko); |
170 | 0 | } |
171 | | |
172 | | // A more involved hit testing test that involves css and async transforms. |
173 | 0 | TEST_F(APZHitTestingTester, HitTesting2) { |
174 | 0 | SCOPED_GFX_VAR(UseWebRender, bool, false); |
175 | 0 | SCOPED_GFX_PREF(APZVelocityBias, float, 0.0); // Velocity bias can cause extra repaint requests |
176 | 0 |
|
177 | 0 | CreateHitTesting2LayerTree(); |
178 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
179 | 0 |
|
180 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
181 | 0 |
|
182 | 0 | // At this point, the following holds (all coordinates in screen pixels): |
183 | 0 | // layers[0] has content from (0,0)-(200,200), clipped by composition bounds (0,0)-(100,100) |
184 | 0 | // layers[1] has content from (10,10)-(90,90), clipped by composition bounds (10,10)-(50,50) |
185 | 0 | // layers[2] has content from (20,60)-(100,100). no clipping as it's not a scrollable layer |
186 | 0 | // layers[3] has content from (20,60)-(180,140), clipped by composition bounds (20,60)-(100,100) |
187 | 0 |
|
188 | 0 | RefPtr<TestAsyncPanZoomController> apzcroot = ApzcOf(root); |
189 | 0 | TestAsyncPanZoomController* apzc1 = ApzcOf(layers[1]); |
190 | 0 | TestAsyncPanZoomController* apzc3 = ApzcOf(layers[3]); |
191 | 0 |
|
192 | 0 | // Hit an area that's clearly on the root layer but not any of the child layers. |
193 | 0 | RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 25)); |
194 | 0 | EXPECT_EQ(apzcroot, hit.get()); |
195 | 0 | EXPECT_EQ(ParentLayerPoint(75, 25), transformToApzc.TransformPoint(ScreenPoint(75, 25))); |
196 | 0 | EXPECT_EQ(ScreenPoint(75, 25), transformToGecko.TransformPoint(ParentLayerPoint(75, 25))); |
197 | 0 |
|
198 | 0 | // Hit an area on the root that would be on layers[3] if layers[2] |
199 | 0 | // weren't transformed. |
200 | 0 | // Note that if layers[2] were scrollable, then this would hit layers[2] |
201 | 0 | // because its composition bounds would be at (10,60)-(50,100) (and the |
202 | 0 | // scale-only transform that we set on layers[2] would be invalid because |
203 | 0 | // it would place the layer into overscroll, as its composition bounds |
204 | 0 | // start at x=10 but its content at x=20). |
205 | 0 | hit = GetTargetAPZC(ScreenPoint(15, 75)); |
206 | 0 | EXPECT_EQ(apzcroot, hit.get()); |
207 | 0 | EXPECT_EQ(ParentLayerPoint(15, 75), transformToApzc.TransformPoint(ScreenPoint(15, 75))); |
208 | 0 | EXPECT_EQ(ScreenPoint(15, 75), transformToGecko.TransformPoint(ParentLayerPoint(15, 75))); |
209 | 0 |
|
210 | 0 | // Hit an area on layers[1]. |
211 | 0 | hit = GetTargetAPZC(ScreenPoint(25, 25)); |
212 | 0 | EXPECT_EQ(apzc1, hit.get()); |
213 | 0 | EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc.TransformPoint(ScreenPoint(25, 25))); |
214 | 0 | EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); |
215 | 0 |
|
216 | 0 | // Hit an area on layers[3]. |
217 | 0 | hit = GetTargetAPZC(ScreenPoint(25, 75)); |
218 | 0 | EXPECT_EQ(apzc3, hit.get()); |
219 | 0 | // transformToApzc should unapply layers[2]'s transform |
220 | 0 | EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc.TransformPoint(ScreenPoint(25, 75))); |
221 | 0 | // and transformToGecko should reapply it |
222 | 0 | EXPECT_EQ(ScreenPoint(25, 75), transformToGecko.TransformPoint(ParentLayerPoint(12.5, 75))); |
223 | 0 |
|
224 | 0 | // Hit an area on layers[3] that would be on the root if layers[2] |
225 | 0 | // weren't transformed. |
226 | 0 | hit = GetTargetAPZC(ScreenPoint(75, 75)); |
227 | 0 | EXPECT_EQ(apzc3, hit.get()); |
228 | 0 | // transformToApzc should unapply layers[2]'s transform |
229 | 0 | EXPECT_EQ(ParentLayerPoint(37.5, 75), transformToApzc.TransformPoint(ScreenPoint(75, 75))); |
230 | 0 | // and transformToGecko should reapply it |
231 | 0 | EXPECT_EQ(ScreenPoint(75, 75), transformToGecko.TransformPoint(ParentLayerPoint(37.5, 75))); |
232 | 0 |
|
233 | 0 | // Pan the root layer upward by 50 pixels. |
234 | 0 | // This causes layers[1] to scroll out of view, and an async transform |
235 | 0 | // of -50 to be set on the root layer. |
236 | 0 | EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); |
237 | 0 |
|
238 | 0 | // This first pan will move the APZC by 50 pixels, and dispatch a paint request. |
239 | 0 | // Since this paint request is in the queue to Gecko, transformToGecko will |
240 | 0 | // take it into account. |
241 | 0 | Pan(apzcroot, 100, 50, PanOptions::NoFling); |
242 | 0 |
|
243 | 0 | // Hit where layers[3] used to be. It should now hit the root. |
244 | 0 | hit = GetTargetAPZC(ScreenPoint(75, 75)); |
245 | 0 | EXPECT_EQ(apzcroot, hit.get()); |
246 | 0 | // transformToApzc doesn't unapply the root's own async transform |
247 | 0 | EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc.TransformPoint(ScreenPoint(75, 75))); |
248 | 0 | // and transformToGecko unapplies it and then reapplies it, because by the |
249 | 0 | // time the event being transformed reaches Gecko the new paint request will |
250 | 0 | // have been handled. |
251 | 0 | EXPECT_EQ(ScreenPoint(75, 75), transformToGecko.TransformPoint(ParentLayerPoint(75, 75))); |
252 | 0 |
|
253 | 0 | // Hit where layers[1] used to be and where layers[3] should now be. |
254 | 0 | hit = GetTargetAPZC(ScreenPoint(25, 25)); |
255 | 0 | EXPECT_EQ(apzc3, hit.get()); |
256 | 0 | // transformToApzc unapplies both layers[2]'s css transform and the root's |
257 | 0 | // async transform |
258 | 0 | EXPECT_EQ(ParentLayerPoint(12.5, 75), transformToApzc.TransformPoint(ScreenPoint(25, 25))); |
259 | 0 | // transformToGecko reapplies both the css transform and the async transform |
260 | 0 | // because we have already issued a paint request with it. |
261 | 0 | EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(12.5, 75))); |
262 | 0 |
|
263 | 0 | // This second pan will move the APZC by another 50 pixels. |
264 | 0 | EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1); |
265 | 0 | Pan(apzcroot, 100, 50, PanOptions::NoFling); |
266 | 0 |
|
267 | 0 | // Hit where layers[3] used to be. It should now hit the root. |
268 | 0 | hit = GetTargetAPZC(ScreenPoint(75, 75)); |
269 | 0 | EXPECT_EQ(apzcroot, hit.get()); |
270 | 0 | // transformToApzc doesn't unapply the root's own async transform |
271 | 0 | EXPECT_EQ(ParentLayerPoint(75, 75), transformToApzc.TransformPoint(ScreenPoint(75, 75))); |
272 | 0 | // transformToGecko unapplies the full async transform of -100 pixels |
273 | 0 | EXPECT_EQ(ScreenPoint(75, 75), transformToGecko.TransformPoint(ParentLayerPoint(75, 75))); |
274 | 0 |
|
275 | 0 | // Hit where layers[1] used to be. It should now hit the root. |
276 | 0 | hit = GetTargetAPZC(ScreenPoint(25, 25)); |
277 | 0 | EXPECT_EQ(apzcroot, hit.get()); |
278 | 0 | // transformToApzc doesn't unapply the root's own async transform |
279 | 0 | EXPECT_EQ(ParentLayerPoint(25, 25), transformToApzc.TransformPoint(ScreenPoint(25, 25))); |
280 | 0 | // transformToGecko unapplies the full async transform of -100 pixels |
281 | 0 | EXPECT_EQ(ScreenPoint(25, 25), transformToGecko.TransformPoint(ParentLayerPoint(25, 25))); |
282 | 0 | } |
283 | | |
284 | 0 | TEST_F(APZHitTestingTester, HitTesting3) { |
285 | 0 | SCOPED_GFX_VAR(UseWebRender, bool, false); |
286 | 0 |
|
287 | 0 | const char* layerTreeSyntax = "c(t)"; |
288 | 0 | // LayerID 0 1 |
289 | 0 | nsIntRegion layerVisibleRegions[] = { |
290 | 0 | nsIntRegion(IntRect(0,0,200,200)), |
291 | 0 | nsIntRegion(IntRect(0,0,50,50)) |
292 | 0 | }; |
293 | 0 | Matrix4x4 transforms[] = { |
294 | 0 | Matrix4x4(), |
295 | 0 | Matrix4x4::Scaling(2, 2, 1) |
296 | 0 | }; |
297 | 0 | root = CreateLayerTree(layerTreeSyntax, layerVisibleRegions, transforms, lm, layers); |
298 | 0 | // No actual room to scroll |
299 | 0 | SetScrollableFrameMetrics(root, FrameMetrics::START_SCROLL_ID, CSSRect(0, 0, 200, 200)); |
300 | 0 | SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID + 1, CSSRect(0, 0, 50, 50)); |
301 | 0 |
|
302 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
303 | 0 |
|
304 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
305 | 0 |
|
306 | 0 | RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(75, 75)); |
307 | 0 | EXPECT_EQ(ApzcOf(layers[1]), hit.get()); |
308 | 0 | } |
309 | | |
310 | 0 | TEST_F(APZHitTestingTester, ComplexMultiLayerTree) { |
311 | 0 | SCOPED_GFX_VAR(UseWebRender, bool, false); |
312 | 0 |
|
313 | 0 | CreateComplexMultiLayerTree(); |
314 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
315 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
316 | 0 |
|
317 | 0 | /* The layer tree looks like this: |
318 | 0 |
|
319 | 0 | 0 |
320 | 0 | |----|--+--|----| |
321 | 0 | 1 2 4 5 |
322 | 0 | | /|\ |
323 | 0 | 3 6 8 9 |
324 | 0 | | |
325 | 0 | 7 |
326 | 0 |
|
327 | 0 | Layers 1,2 have the same APZC |
328 | 0 | Layers 4,6,8 have the same APZC |
329 | 0 | Layer 7 has an APZC |
330 | 0 | Layer 9 has an APZC |
331 | 0 | */ |
332 | 0 |
|
333 | 0 | TestAsyncPanZoomController* nullAPZC = nullptr; |
334 | 0 | // Ensure all the scrollable layers have an APZC |
335 | 0 | EXPECT_FALSE(layers[0]->HasScrollableFrameMetrics()); |
336 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[1])); |
337 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[2])); |
338 | 0 | EXPECT_FALSE(layers[3]->HasScrollableFrameMetrics()); |
339 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[4])); |
340 | 0 | EXPECT_FALSE(layers[5]->HasScrollableFrameMetrics()); |
341 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[6])); |
342 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[7])); |
343 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[8])); |
344 | 0 | EXPECT_NE(nullAPZC, ApzcOf(layers[9])); |
345 | 0 | // Ensure those that scroll together have the same APZCs |
346 | 0 | EXPECT_EQ(ApzcOf(layers[1]), ApzcOf(layers[2])); |
347 | 0 | EXPECT_EQ(ApzcOf(layers[4]), ApzcOf(layers[6])); |
348 | 0 | EXPECT_EQ(ApzcOf(layers[8]), ApzcOf(layers[6])); |
349 | 0 | // Ensure those that don't scroll together have different APZCs |
350 | 0 | EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[4])); |
351 | 0 | EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[7])); |
352 | 0 | EXPECT_NE(ApzcOf(layers[1]), ApzcOf(layers[9])); |
353 | 0 | EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[7])); |
354 | 0 | EXPECT_NE(ApzcOf(layers[4]), ApzcOf(layers[9])); |
355 | 0 | EXPECT_NE(ApzcOf(layers[7]), ApzcOf(layers[9])); |
356 | 0 | // Ensure the APZC parent chains are set up correctly |
357 | 0 | TestAsyncPanZoomController* layers1_2 = ApzcOf(layers[1]); |
358 | 0 | TestAsyncPanZoomController* layers4_6_8 = ApzcOf(layers[4]); |
359 | 0 | TestAsyncPanZoomController* layer7 = ApzcOf(layers[7]); |
360 | 0 | TestAsyncPanZoomController* layer9 = ApzcOf(layers[9]); |
361 | 0 | EXPECT_EQ(nullptr, layers1_2->GetParent()); |
362 | 0 | EXPECT_EQ(nullptr, layers4_6_8->GetParent()); |
363 | 0 | EXPECT_EQ(layers4_6_8, layer7->GetParent()); |
364 | 0 | EXPECT_EQ(nullptr, layer9->GetParent()); |
365 | 0 | // Ensure the hit-testing tree looks like the layer tree |
366 | 0 | RefPtr<HitTestingTreeNode> root = manager->GetRootNode(); |
367 | 0 | RefPtr<HitTestingTreeNode> node5 = root->GetLastChild(); |
368 | 0 | RefPtr<HitTestingTreeNode> node4 = node5->GetPrevSibling(); |
369 | 0 | RefPtr<HitTestingTreeNode> node2 = node4->GetPrevSibling(); |
370 | 0 | RefPtr<HitTestingTreeNode> node1 = node2->GetPrevSibling(); |
371 | 0 | RefPtr<HitTestingTreeNode> node3 = node2->GetLastChild(); |
372 | 0 | RefPtr<HitTestingTreeNode> node9 = node5->GetLastChild(); |
373 | 0 | RefPtr<HitTestingTreeNode> node8 = node9->GetPrevSibling(); |
374 | 0 | RefPtr<HitTestingTreeNode> node6 = node8->GetPrevSibling(); |
375 | 0 | RefPtr<HitTestingTreeNode> node7 = node6->GetLastChild(); |
376 | 0 | EXPECT_EQ(nullptr, node1->GetPrevSibling()); |
377 | 0 | EXPECT_EQ(nullptr, node3->GetPrevSibling()); |
378 | 0 | EXPECT_EQ(nullptr, node6->GetPrevSibling()); |
379 | 0 | EXPECT_EQ(nullptr, node7->GetPrevSibling()); |
380 | 0 | EXPECT_EQ(nullptr, node1->GetLastChild()); |
381 | 0 | EXPECT_EQ(nullptr, node3->GetLastChild()); |
382 | 0 | EXPECT_EQ(nullptr, node4->GetLastChild()); |
383 | 0 | EXPECT_EQ(nullptr, node7->GetLastChild()); |
384 | 0 | EXPECT_EQ(nullptr, node8->GetLastChild()); |
385 | 0 | EXPECT_EQ(nullptr, node9->GetLastChild()); |
386 | 0 |
|
387 | 0 | RefPtr<AsyncPanZoomController> hit = GetTargetAPZC(ScreenPoint(25, 25)); |
388 | 0 | EXPECT_EQ(ApzcOf(layers[1]), hit.get()); |
389 | 0 | hit = GetTargetAPZC(ScreenPoint(275, 375)); |
390 | 0 | EXPECT_EQ(ApzcOf(layers[9]), hit.get()); |
391 | 0 | hit = GetTargetAPZC(ScreenPoint(250, 100)); |
392 | 0 | EXPECT_EQ(ApzcOf(layers[7]), hit.get()); |
393 | 0 | } |
394 | | |
395 | 0 | TEST_F(APZHitTestingTester, TestRepaintFlushOnNewInputBlock) { |
396 | 0 | SCOPED_GFX_PREF(TouchActionEnabled, bool, false); |
397 | 0 |
|
398 | 0 | // The main purpose of this test is to verify that touch-start events (or anything |
399 | 0 | // that starts a new input block) don't ever get untransformed. This should always |
400 | 0 | // hold because the APZ code should flush repaints when we start a new input block |
401 | 0 | // and the transform to gecko space should be empty. |
402 | 0 |
|
403 | 0 | CreateSimpleScrollingLayer(); |
404 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
405 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
406 | 0 | RefPtr<TestAsyncPanZoomController> apzcroot = ApzcOf(root); |
407 | 0 |
|
408 | 0 | // At this point, the following holds (all coordinates in screen pixels): |
409 | 0 | // layers[0] has content from (0,0)-(500,500), clipped by composition bounds (0,0)-(200,200) |
410 | 0 |
|
411 | 0 | MockFunction<void(std::string checkPointName)> check; |
412 | 0 |
|
413 | 0 | { |
414 | 0 | InSequence s; |
415 | 0 |
|
416 | 0 | EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); |
417 | 0 | EXPECT_CALL(check, Call("post-first-touch-start")); |
418 | 0 | EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); |
419 | 0 | EXPECT_CALL(check, Call("post-second-fling")); |
420 | 0 | EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(1)); |
421 | 0 | EXPECT_CALL(check, Call("post-second-touch-start")); |
422 | 0 | } |
423 | 0 |
|
424 | 0 | // This first pan will move the APZC by 50 pixels, and dispatch a paint request. |
425 | 0 | Pan(apzcroot, 100, 50, PanOptions::NoFling); |
426 | 0 |
|
427 | 0 | // Verify that a touch start doesn't get untransformed |
428 | 0 | ScreenIntPoint touchPoint(50, 50); |
429 | 0 | MultiTouchInput mti = CreateMultiTouchInput(MultiTouchInput::MULTITOUCH_START, mcc->Time()); |
430 | 0 | mti.mTouches.AppendElement(SingleTouchData(0, touchPoint, ScreenSize(0, 0), 0, 0)); |
431 | 0 |
|
432 | 0 | EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); |
433 | 0 | EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); |
434 | 0 | check.Call("post-first-touch-start"); |
435 | 0 |
|
436 | 0 | // Send a touchend to clear state |
437 | 0 | mti.mType = MultiTouchInput::MULTITOUCH_END; |
438 | 0 | manager->ReceiveInputEvent(mti, nullptr, nullptr); |
439 | 0 |
|
440 | 0 | mcc->AdvanceByMillis(1000); |
441 | 0 |
|
442 | 0 | // Now do two pans. The first of these will dispatch a repaint request, as above. |
443 | 0 | // The second will get stuck in the paint throttler because the first one doesn't |
444 | 0 | // get marked as "completed", so this will result in a non-empty LD transform. |
445 | 0 | // (Note that any outstanding repaint requests from the first half of this test |
446 | 0 | // don't impact this half because we advance the time by 1 second, which will trigger |
447 | 0 | // the max-wait-exceeded codepath in the paint throttler). |
448 | 0 | Pan(apzcroot, 100, 50, PanOptions::NoFling); |
449 | 0 | check.Call("post-second-fling"); |
450 | 0 | Pan(apzcroot, 100, 50, PanOptions::NoFling); |
451 | 0 |
|
452 | 0 | // Ensure that a touch start again doesn't get untransformed by flushing |
453 | 0 | // a repaint |
454 | 0 | mti.mType = MultiTouchInput::MULTITOUCH_START; |
455 | 0 | EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); |
456 | 0 | EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); |
457 | 0 | check.Call("post-second-touch-start"); |
458 | 0 |
|
459 | 0 | mti.mType = MultiTouchInput::MULTITOUCH_END; |
460 | 0 | EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(mti, nullptr, nullptr)); |
461 | 0 | EXPECT_EQ(touchPoint, mti.mTouches[0].mScreenPoint); |
462 | 0 | } |
463 | | |
464 | 0 | TEST_F(APZHitTestingTester, TestRepaintFlushOnWheelEvents) { |
465 | 0 | // The purpose of this test is to ensure that wheel events trigger a repaint |
466 | 0 | // flush as per bug 1166871, and that the wheel event untransform is a no-op. |
467 | 0 |
|
468 | 0 | CreateSimpleScrollingLayer(); |
469 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
470 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
471 | 0 | TestAsyncPanZoomController* apzcroot = ApzcOf(root); |
472 | 0 |
|
473 | 0 | EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(AtLeast(3)); |
474 | 0 | ScreenPoint origin(100, 50); |
475 | 0 | for (int i = 0; i < 3; i++) { |
476 | 0 | ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, |
477 | 0 | ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, |
478 | 0 | origin, 0, 10, false, WheelDeltaAdjustmentStrategy::eNone); |
479 | 0 | EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr)); |
480 | 0 | EXPECT_EQ(origin, swi.mOrigin); |
481 | 0 |
|
482 | 0 | AsyncTransform viewTransform; |
483 | 0 | ParentLayerPoint point; |
484 | 0 | apzcroot->SampleContentTransformForFrame(&viewTransform, point); |
485 | 0 | EXPECT_EQ(0, point.x); |
486 | 0 | EXPECT_EQ((i + 1) * 10, point.y); |
487 | 0 | EXPECT_EQ(0, viewTransform.mTranslation.x); |
488 | 0 | EXPECT_EQ((i + 1) * -10, viewTransform.mTranslation.y); |
489 | 0 |
|
490 | 0 | mcc->AdvanceByMillis(5); |
491 | 0 | } |
492 | 0 | } |
493 | | |
494 | 0 | TEST_F(APZHitTestingTester, TestForceDisableApz) { |
495 | 0 | CreateSimpleScrollingLayer(); |
496 | 0 | DisableApzOn(root); |
497 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
498 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
499 | 0 | TestAsyncPanZoomController* apzcroot = ApzcOf(root); |
500 | 0 |
|
501 | 0 | ScreenPoint origin(100, 50); |
502 | 0 | ScrollWheelInput swi(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, |
503 | 0 | ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, |
504 | 0 | origin, 0, 10, false, WheelDeltaAdjustmentStrategy::eNone); |
505 | 0 | EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr)); |
506 | 0 | EXPECT_EQ(origin, swi.mOrigin); |
507 | 0 |
|
508 | 0 | AsyncTransform viewTransform; |
509 | 0 | ParentLayerPoint point; |
510 | 0 | apzcroot->SampleContentTransformForFrame(&viewTransform, point); |
511 | 0 | // Since APZ is force-disabled, we expect to see the async transform via |
512 | 0 | // the NORMAL AsyncMode, but not via the RESPECT_FORCE_DISABLE AsyncMode. |
513 | 0 | EXPECT_EQ(0, point.x); |
514 | 0 | EXPECT_EQ(10, point.y); |
515 | 0 | EXPECT_EQ(0, viewTransform.mTranslation.x); |
516 | 0 | EXPECT_EQ(-10, viewTransform.mTranslation.y); |
517 | 0 | viewTransform = apzcroot->GetCurrentAsyncTransform(AsyncPanZoomController::eForCompositing); |
518 | 0 | point = apzcroot->GetCurrentAsyncScrollOffset(AsyncPanZoomController::eForCompositing); |
519 | 0 | EXPECT_EQ(0, point.x); |
520 | 0 | EXPECT_EQ(0, point.y); |
521 | 0 | EXPECT_EQ(0, viewTransform.mTranslation.x); |
522 | 0 | EXPECT_EQ(0, viewTransform.mTranslation.y); |
523 | 0 |
|
524 | 0 | mcc->AdvanceByMillis(10); |
525 | 0 |
|
526 | 0 | // With untransforming events we should get normal behaviour (in this case, |
527 | 0 | // no noticeable untransform, because the repaint request already got |
528 | 0 | // flushed). |
529 | 0 | swi = ScrollWheelInput(MillisecondsSinceStartup(mcc->Time()), mcc->Time(), 0, |
530 | 0 | ScrollWheelInput::SCROLLMODE_INSTANT, ScrollWheelInput::SCROLLDELTA_PIXEL, |
531 | 0 | origin, 0, 0, false, WheelDeltaAdjustmentStrategy::eNone); |
532 | 0 | EXPECT_EQ(nsEventStatus_eConsumeDoDefault, manager->ReceiveInputEvent(swi, nullptr, nullptr)); |
533 | 0 | EXPECT_EQ(origin, swi.mOrigin); |
534 | 0 | } |
535 | | |
536 | 0 | TEST_F(APZHitTestingTester, Bug1148350) { |
537 | 0 | CreateBug1148350LayerTree(); |
538 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
539 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
540 | 0 |
|
541 | 0 | MockFunction<void(std::string checkPointName)> check; |
542 | 0 | { |
543 | 0 | InSequence s; |
544 | 0 | EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, ApzcOf(layers[1])->GetGuid(), _)).Times(1); |
545 | 0 | EXPECT_CALL(check, Call("Tapped without transform")); |
546 | 0 | EXPECT_CALL(*mcc, HandleTap(TapType::eSingleTap, LayoutDevicePoint(100, 100), 0, ApzcOf(layers[1])->GetGuid(), _)).Times(1); |
547 | 0 | EXPECT_CALL(check, Call("Tapped with interleaved transform")); |
548 | 0 | } |
549 | 0 |
|
550 | 0 | Tap(manager, ScreenIntPoint(100, 100), TimeDuration::FromMilliseconds(100)); |
551 | 0 | mcc->RunThroughDelayedTasks(); |
552 | 0 | check.Call("Tapped without transform"); |
553 | 0 |
|
554 | 0 | uint64_t blockId; |
555 | 0 | TouchDown(manager, ScreenIntPoint(100, 100), mcc->Time(), &blockId); |
556 | 0 | if (gfxPrefs::TouchActionEnabled()) { |
557 | 0 | SetDefaultAllowedTouchBehavior(manager, blockId); |
558 | 0 | } |
559 | 0 | mcc->AdvanceByMillis(100); |
560 | 0 |
|
561 | 0 | layers[0]->SetVisibleRegion(LayerIntRegion(LayerIntRect(0,50,200,150))); |
562 | 0 | layers[0]->SetBaseTransform(Matrix4x4::Translation(0, 50, 0)); |
563 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
564 | 0 |
|
565 | 0 | TouchUp(manager, ScreenIntPoint(100, 100), mcc->Time()); |
566 | 0 | mcc->RunThroughDelayedTasks(); |
567 | 0 | check.Call("Tapped with interleaved transform"); |
568 | 0 | } |
569 | | |
570 | 0 | TEST_F(APZHitTestingTester, HitTestingRespectsScrollClip_Bug1257288) { |
571 | 0 | // Create the layer tree. |
572 | 0 | const char* layerTreeSyntax = "c(tt)"; |
573 | 0 | // LayerID 0 12 |
574 | 0 | nsIntRegion layerVisibleRegion[] = { |
575 | 0 | nsIntRegion(IntRect(0,0,200,200)), |
576 | 0 | nsIntRegion(IntRect(0,0,200,200)), |
577 | 0 | nsIntRegion(IntRect(0,0,200,100)) |
578 | 0 | }; |
579 | 0 | root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, nullptr, lm, layers); |
580 | 0 |
|
581 | 0 | // Add root scroll metadata to the first painted layer. |
582 | 0 | SetScrollableFrameMetrics(layers[1], FrameMetrics::START_SCROLL_ID, CSSRect(0,0,200,200)); |
583 | 0 |
|
584 | 0 | // Add root and subframe scroll metadata to the second painted layer. |
585 | 0 | // Give the subframe metadata a scroll clip corresponding to the subframe's |
586 | 0 | // composition bounds. |
587 | 0 | // Importantly, give the layer a layer clip which leaks outside of the |
588 | 0 | // subframe's composition bounds. |
589 | 0 | ScrollMetadata rootMetadata = BuildScrollMetadata( |
590 | 0 | FrameMetrics::START_SCROLL_ID, CSSRect(0,0,200,200), |
591 | 0 | ParentLayerRect(0,0,200,200)); |
592 | 0 | ScrollMetadata subframeMetadata = BuildScrollMetadata( |
593 | 0 | FrameMetrics::START_SCROLL_ID + 1, CSSRect(0,0,200,200), |
594 | 0 | ParentLayerRect(0,0,200,100)); |
595 | 0 | subframeMetadata.SetScrollClip(Some(LayerClip(ParentLayerIntRect(0,0,200,100)))); |
596 | 0 | layers[2]->SetScrollMetadata({subframeMetadata, rootMetadata}); |
597 | 0 | layers[2]->SetClipRect(Some(ParentLayerIntRect(0,0,200,200))); |
598 | 0 | SetEventRegionsBasedOnBottommostMetrics(layers[2]); |
599 | 0 |
|
600 | 0 | // Build the hit testing tree. |
601 | 0 | ScopedLayerTreeRegistration registration(manager, LayersId{0}, root, mcc); |
602 | 0 | manager->UpdateHitTestingTree(LayersId{0}, root, false, LayersId{0}, 0); |
603 | 0 |
|
604 | 0 | // Pan on a region that's inside layers[2]'s layer clip, but outside |
605 | 0 | // its subframe metadata's scroll clip. |
606 | 0 | Pan(manager, 120, 110); |
607 | 0 |
|
608 | 0 | // Test that the subframe hasn't scrolled. |
609 | 0 | EXPECT_EQ(CSSPoint(0,0), ApzcOf(layers[2], 0)->GetFrameMetrics().GetScrollOffset()); |
610 | 0 | } |