1 // Copyright 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.android_webview.robolectric; 6 7 import android.graphics.Rect; 8 9 import androidx.test.filters.SmallTest; 10 11 import org.junit.Assert; 12 import org.junit.Test; 13 import org.junit.runner.RunWith; 14 import org.robolectric.annotation.Config; 15 16 import org.chromium.android_webview.AwScrollOffsetManager; 17 import org.chromium.base.test.util.Feature; 18 import org.chromium.testing.local.LocalRobolectricTestRunner; 19 20 /** 21 * Integration tests for ScrollOffsetManager. 22 */ 23 @RunWith(LocalRobolectricTestRunner.class) 24 @Config(manifest = Config.NONE) 25 public class AwScrollOffsetManagerTest { 26 private static class TestScrollOffsetManagerDelegate implements AwScrollOffsetManager.Delegate { 27 private int mOverScrollDeltaX; 28 private int mOverScrollDeltaY; 29 private int mOverScrollCallCount; 30 private int mScrollX; 31 private int mScrollY; 32 private int mNativeScrollX; 33 private int mNativeScrollY; 34 private int mInvalidateCount; 35 getOverScrollDeltaX()36 public int getOverScrollDeltaX() { 37 return mOverScrollDeltaX; 38 } 39 getOverScrollDeltaY()40 public int getOverScrollDeltaY() { 41 return mOverScrollDeltaY; 42 } 43 getOverScrollCallCount()44 public int getOverScrollCallCount() { 45 return mOverScrollCallCount; 46 } 47 getScrollX()48 public int getScrollX() { 49 return mScrollX; 50 } 51 getScrollY()52 public int getScrollY() { 53 return mScrollY; 54 } 55 getNativeScrollX()56 public int getNativeScrollX() { 57 return mNativeScrollX; 58 } 59 getNativeScrollY()60 public int getNativeScrollY() { 61 return mNativeScrollY; 62 } 63 getInvalidateCount()64 public int getInvalidateCount() { 65 return mInvalidateCount; 66 } 67 68 @Override overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, boolean isTouchEvent)69 public void overScrollContainerViewBy(int deltaX, int deltaY, int scrollX, int scrollY, 70 int scrollRangeX, int scrollRangeY, boolean isTouchEvent) { 71 mOverScrollDeltaX = deltaX; 72 mOverScrollDeltaY = deltaY; 73 mOverScrollCallCount += 1; 74 } 75 76 @Override scrollContainerViewTo(int x, int y)77 public void scrollContainerViewTo(int x, int y) { 78 mScrollX = x; 79 mScrollY = y; 80 } 81 82 @Override scrollNativeTo(int x, int y)83 public void scrollNativeTo(int x, int y) { 84 mNativeScrollX = x; 85 mNativeScrollY = y; 86 } 87 88 @Override getContainerViewScrollX()89 public int getContainerViewScrollX() { 90 return mScrollX; 91 } 92 93 @Override getContainerViewScrollY()94 public int getContainerViewScrollY() { 95 return mScrollY; 96 } 97 98 @Override invalidate()99 public void invalidate() { 100 mInvalidateCount += 1; 101 } 102 103 @Override cancelFling()104 public void cancelFling() {} 105 106 @Override smoothScroll(int targetX, int targetY, long durationMs)107 public void smoothScroll(int targetX, int targetY, long durationMs) {} 108 } 109 simulateScrolling(AwScrollOffsetManager offsetManager, TestScrollOffsetManagerDelegate delegate, int scrollX, int scrollY)110 private void simulateScrolling(AwScrollOffsetManager offsetManager, 111 TestScrollOffsetManagerDelegate delegate, int scrollX, int scrollY) { 112 // Scrolling is a two-phase action. First we ask the manager to scroll 113 int callCount = delegate.getOverScrollCallCount(); 114 offsetManager.scrollContainerViewTo(scrollX, scrollY); 115 // The manager then asks the delegate to overscroll the view. 116 Assert.assertEquals(callCount + 1, delegate.getOverScrollCallCount()); 117 Assert.assertEquals(scrollX, delegate.getOverScrollDeltaX() + delegate.getScrollX()); 118 Assert.assertEquals(scrollY, delegate.getOverScrollDeltaY() + delegate.getScrollY()); 119 // As a response to that the menager expects the view to call back with the new scroll. 120 offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false); 121 } 122 simlateOverScrollPropagation( AwScrollOffsetManager offsetManager, TestScrollOffsetManagerDelegate delegate)123 private void simlateOverScrollPropagation( 124 AwScrollOffsetManager offsetManager, TestScrollOffsetManagerDelegate delegate) { 125 Assert.assertTrue(delegate.getOverScrollCallCount() > 0); 126 127 offsetManager.onContainerViewOverScrolled( 128 delegate.getOverScrollDeltaX() + delegate.getScrollX(), 129 delegate.getOverScrollDeltaY() + delegate.getScrollY(), false, false); 130 } 131 132 @Test 133 @SmallTest 134 @Feature({"AndroidWebView"}) testWhenContentSizeMatchesView()135 public void testWhenContentSizeMatchesView() { 136 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 137 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 138 139 final int width = 132; 140 final int height = 212; 141 final int scrollX = 11; 142 final int scrollY = 13; 143 144 offsetManager.setMaxScrollOffset(0, 0); 145 offsetManager.setContainerViewSize(width, height); 146 147 Assert.assertEquals(width, offsetManager.computeHorizontalScrollRange()); 148 Assert.assertEquals(height, offsetManager.computeVerticalScrollRange()); 149 150 // Since the view size and contents size are equal no scrolling should be possible. 151 Assert.assertEquals(0, offsetManager.computeMaximumHorizontalScrollOffset()); 152 Assert.assertEquals(0, offsetManager.computeMaximumVerticalScrollOffset()); 153 154 // Scrolling should generate overscroll but not update the scroll offset. 155 simulateScrolling(offsetManager, delegate, scrollX, scrollY); 156 Assert.assertEquals(scrollX, delegate.getOverScrollDeltaX()); 157 Assert.assertEquals(scrollY, delegate.getOverScrollDeltaY()); 158 Assert.assertEquals(0, delegate.getScrollX()); 159 Assert.assertEquals(0, delegate.getScrollY()); 160 Assert.assertEquals(0, delegate.getNativeScrollX()); 161 Assert.assertEquals(0, delegate.getNativeScrollY()); 162 163 // Scrolling to 0,0 should result in no deltas. 164 simulateScrolling(offsetManager, delegate, 0, 0); 165 Assert.assertEquals(0, delegate.getOverScrollDeltaX()); 166 Assert.assertEquals(0, delegate.getOverScrollDeltaY()); 167 168 // Negative scrolling should result in negative deltas but no scroll offset update. 169 simulateScrolling(offsetManager, delegate, -scrollX, -scrollY); 170 Assert.assertEquals(-scrollX, delegate.getOverScrollDeltaX()); 171 Assert.assertEquals(-scrollY, delegate.getOverScrollDeltaY()); 172 Assert.assertEquals(0, delegate.getScrollX()); 173 Assert.assertEquals(0, delegate.getScrollY()); 174 Assert.assertEquals(0, delegate.getNativeScrollX()); 175 Assert.assertEquals(0, delegate.getNativeScrollY()); 176 } 177 178 private static final int VIEW_WIDTH = 211; 179 private static final int VIEW_HEIGHT = 312; 180 private static final int MAX_HORIZONTAL_OFFSET = 757; 181 private static final int MAX_VERTICAL_OFFSET = 127; 182 private static final int CONTENT_WIDTH = VIEW_WIDTH + MAX_HORIZONTAL_OFFSET; 183 private static final int CONTENT_HEIGHT = VIEW_HEIGHT + MAX_VERTICAL_OFFSET; 184 185 @Test 186 @SmallTest 187 @Feature({"AndroidWebView"}) testScrollRangeAndMaxOffset()188 public void testScrollRangeAndMaxOffset() { 189 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 190 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 191 192 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 193 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 194 195 Assert.assertEquals(CONTENT_WIDTH, offsetManager.computeHorizontalScrollRange()); 196 Assert.assertEquals(CONTENT_HEIGHT, offsetManager.computeVerticalScrollRange()); 197 198 Assert.assertEquals( 199 MAX_HORIZONTAL_OFFSET, offsetManager.computeMaximumHorizontalScrollOffset()); 200 Assert.assertEquals( 201 MAX_VERTICAL_OFFSET, offsetManager.computeMaximumVerticalScrollOffset()); 202 203 // Scrolling beyond the maximum should be clamped. 204 final int scrollX = MAX_HORIZONTAL_OFFSET + 10; 205 final int scrollY = MAX_VERTICAL_OFFSET + 11; 206 207 simulateScrolling(offsetManager, delegate, scrollX, scrollY); 208 Assert.assertEquals(scrollX, delegate.getOverScrollDeltaX()); 209 Assert.assertEquals(scrollY, delegate.getOverScrollDeltaY()); 210 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX()); 211 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY()); 212 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX()); 213 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY()); 214 215 // Scrolling to negative coordinates should be clamped back to 0,0. 216 simulateScrolling(offsetManager, delegate, -scrollX, -scrollY); 217 Assert.assertEquals(0, delegate.getScrollX()); 218 Assert.assertEquals(0, delegate.getScrollY()); 219 Assert.assertEquals(0, delegate.getNativeScrollX()); 220 Assert.assertEquals(0, delegate.getNativeScrollY()); 221 222 // The onScrollChanged method is callable by third party code and should also be clamped 223 offsetManager.onContainerViewScrollChanged(scrollX, scrollY); 224 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX()); 225 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY()); 226 227 offsetManager.onContainerViewScrollChanged(-scrollX, -scrollY); 228 Assert.assertEquals(0, delegate.getNativeScrollX()); 229 Assert.assertEquals(0, delegate.getNativeScrollY()); 230 } 231 232 @Test 233 @SmallTest 234 @Feature({"AndroidWebView"}) testDelegateCanOverrideScroll()235 public void testDelegateCanOverrideScroll() { 236 final int overrideScrollX = 10; 237 final int overrideScrollY = 10; 238 239 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() { 240 @Override 241 public int getContainerViewScrollX() { 242 return overrideScrollX; 243 } 244 245 @Override 246 public int getContainerViewScrollY() { 247 return overrideScrollY; 248 } 249 }; 250 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 251 252 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 253 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 254 255 offsetManager.onContainerViewOverScrolled(0, 0, false, false); 256 Assert.assertEquals(overrideScrollX, delegate.getNativeScrollX()); 257 Assert.assertEquals(overrideScrollY, delegate.getNativeScrollY()); 258 } 259 260 @Test 261 @SmallTest 262 @Feature({"AndroidWebView"}) testDelegateOverridenScrollsDontExceedBounds()263 public void testDelegateOverridenScrollsDontExceedBounds() { 264 final int overrideScrollX = MAX_HORIZONTAL_OFFSET + 10; 265 final int overrideScrollY = MAX_VERTICAL_OFFSET + 20; 266 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate() { 267 @Override 268 public int getContainerViewScrollX() { 269 return overrideScrollX; 270 } 271 272 @Override 273 public int getContainerViewScrollY() { 274 return overrideScrollY; 275 } 276 }; 277 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 278 279 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 280 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 281 282 offsetManager.onContainerViewOverScrolled(0, 0, false, false); 283 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getNativeScrollX()); 284 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getNativeScrollY()); 285 } 286 287 @Test 288 @SmallTest 289 @Feature({"AndroidWebView"}) testScrollContainerViewTo()290 public void testScrollContainerViewTo() { 291 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 292 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 293 294 final int scrollX = 31; 295 final int scrollY = 41; 296 297 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 298 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 299 300 Assert.assertEquals(0, delegate.getOverScrollDeltaX()); 301 Assert.assertEquals(0, delegate.getOverScrollDeltaY()); 302 int callCount = delegate.getOverScrollCallCount(); 303 304 offsetManager.scrollContainerViewTo(scrollX, scrollY); 305 Assert.assertEquals(callCount + 1, delegate.getOverScrollCallCount()); 306 Assert.assertEquals(scrollX, delegate.getOverScrollDeltaX()); 307 Assert.assertEquals(scrollY, delegate.getOverScrollDeltaY()); 308 } 309 310 @Test 311 @SmallTest 312 @Feature({"AndroidWebView"}) testOnContainerViewOverScrolled()313 public void testOnContainerViewOverScrolled() { 314 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 315 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 316 317 final int scrollX = 31; 318 final int scrollY = 41; 319 320 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 321 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 322 323 Assert.assertEquals(0, delegate.getScrollX()); 324 Assert.assertEquals(0, delegate.getScrollY()); 325 Assert.assertEquals(0, delegate.getNativeScrollX()); 326 Assert.assertEquals(0, delegate.getNativeScrollY()); 327 328 offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false); 329 Assert.assertEquals(scrollX, delegate.getScrollX()); 330 Assert.assertEquals(scrollY, delegate.getScrollY()); 331 Assert.assertEquals(scrollX, delegate.getNativeScrollX()); 332 Assert.assertEquals(scrollY, delegate.getNativeScrollY()); 333 } 334 335 @Test 336 @SmallTest 337 @Feature({"AndroidWebView"}) testDefersScrollUntilTouchEnd()338 public void testDefersScrollUntilTouchEnd() { 339 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 340 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 341 342 final int scrollX = 31; 343 final int scrollY = 41; 344 345 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 346 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 347 348 offsetManager.setProcessingTouchEvent(true); 349 offsetManager.onContainerViewOverScrolled(scrollX, scrollY, false, false); 350 Assert.assertEquals(scrollX, delegate.getScrollX()); 351 Assert.assertEquals(scrollY, delegate.getScrollY()); 352 Assert.assertEquals(0, delegate.getNativeScrollX()); 353 Assert.assertEquals(0, delegate.getNativeScrollY()); 354 355 offsetManager.setProcessingTouchEvent(false); 356 Assert.assertEquals(scrollX, delegate.getScrollX()); 357 Assert.assertEquals(scrollY, delegate.getScrollY()); 358 Assert.assertEquals(scrollX, delegate.getNativeScrollX()); 359 Assert.assertEquals(scrollY, delegate.getNativeScrollY()); 360 } 361 362 @Test 363 @SmallTest 364 @Feature({"AndroidWebView"}) testRequestChildRectangleOnScreenDontScrollIfAlreadyThere()365 public void testRequestChildRectangleOnScreenDontScrollIfAlreadyThere() { 366 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 367 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 368 369 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 370 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 371 372 offsetManager.requestChildRectangleOnScreen( 373 0, 0, new Rect(0, 0, VIEW_WIDTH / 4, VIEW_HEIGHT / 4), true); 374 Assert.assertEquals(0, delegate.getOverScrollDeltaX()); 375 Assert.assertEquals(0, delegate.getOverScrollDeltaY()); 376 Assert.assertEquals(0, delegate.getScrollX()); 377 Assert.assertEquals(0, delegate.getScrollY()); 378 379 offsetManager.requestChildRectangleOnScreen(3 * VIEW_WIDTH / 4, 3 * VIEW_HEIGHT / 4, 380 new Rect(0, 0, VIEW_WIDTH / 4, VIEW_HEIGHT / 4), true); 381 Assert.assertEquals(0, delegate.getOverScrollDeltaX()); 382 Assert.assertEquals(0, delegate.getOverScrollDeltaY()); 383 Assert.assertEquals(0, delegate.getScrollX()); 384 Assert.assertEquals(0, delegate.getScrollY()); 385 } 386 387 @Test 388 @SmallTest 389 @Feature({"AndroidWebView"}) testRequestChildRectangleOnScreenScrollToBottom()390 public void testRequestChildRectangleOnScreenScrollToBottom() { 391 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 392 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 393 394 final int rectWidth = 2; 395 final int rectHeight = 3; 396 397 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 398 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 399 400 offsetManager.requestChildRectangleOnScreen(CONTENT_WIDTH - rectWidth, 401 CONTENT_HEIGHT - rectHeight, new Rect(0, 0, rectWidth, rectHeight), true); 402 simlateOverScrollPropagation(offsetManager, delegate); 403 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getOverScrollDeltaX()); 404 Assert.assertEquals( 405 CONTENT_HEIGHT - rectHeight - VIEW_HEIGHT / 3, delegate.getOverScrollDeltaY()); 406 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX()); 407 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY()); 408 } 409 410 @Test 411 @SmallTest 412 @Feature({"AndroidWebView"}) testRequestChildRectangleOnScreenScrollToBottomLargeRect()413 public void testRequestChildRectangleOnScreenScrollToBottomLargeRect() { 414 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 415 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 416 417 final int rectWidth = VIEW_WIDTH; 418 final int rectHeight = VIEW_HEIGHT; 419 420 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 421 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 422 423 offsetManager.requestChildRectangleOnScreen(CONTENT_WIDTH - rectWidth, 424 CONTENT_HEIGHT - rectHeight, new Rect(0, 0, rectWidth, rectHeight), true); 425 simlateOverScrollPropagation(offsetManager, delegate); 426 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getOverScrollDeltaX()); 427 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getOverScrollDeltaY()); 428 Assert.assertEquals(MAX_HORIZONTAL_OFFSET, delegate.getScrollX()); 429 Assert.assertEquals(MAX_VERTICAL_OFFSET, delegate.getScrollY()); 430 } 431 432 @Test 433 @SmallTest 434 @Feature({"AndroidWebView"}) testRequestChildRectangleOnScreenScrollToTop()435 public void testRequestChildRectangleOnScreenScrollToTop() { 436 TestScrollOffsetManagerDelegate delegate = new TestScrollOffsetManagerDelegate(); 437 AwScrollOffsetManager offsetManager = new AwScrollOffsetManager(delegate); 438 439 final int rectWidth = 2; 440 final int rectHeight = 3; 441 442 offsetManager.setMaxScrollOffset(MAX_HORIZONTAL_OFFSET, MAX_VERTICAL_OFFSET); 443 offsetManager.setContainerViewSize(VIEW_WIDTH, VIEW_HEIGHT); 444 simulateScrolling( 445 offsetManager, delegate, CONTENT_WIDTH - VIEW_WIDTH, CONTENT_HEIGHT - VIEW_HEIGHT); 446 447 offsetManager.requestChildRectangleOnScreen( 448 0, 0, new Rect(0, 0, rectWidth, rectHeight), true); 449 simlateOverScrollPropagation(offsetManager, delegate); 450 Assert.assertEquals(-CONTENT_WIDTH + VIEW_WIDTH, delegate.getOverScrollDeltaX()); 451 Assert.assertEquals(-CONTENT_HEIGHT + VIEW_HEIGHT, delegate.getOverScrollDeltaY()); 452 Assert.assertEquals(0, delegate.getScrollX()); 453 Assert.assertEquals(0, delegate.getScrollX()); 454 } 455 } 456