summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt')
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt613
1 files changed, 613 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt
new file mode 100644
index 0000000000..e40d047558
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/PanZoomControllerTest.kt
@@ -0,0 +1,613 @@
+package org.mozilla.geckoview.test
+
+import android.os.SystemClock
+import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
+import org.mozilla.geckoview.PanZoomController
+import org.mozilla.geckoview.ScreenLength
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
+import kotlin.math.roundToInt
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class PanZoomControllerTest : BaseSessionTest() {
+ private val errorEpsilon = 3.0
+ private val scrollWaitTimeout = 10000.0 // 10 seconds
+
+ private fun setupDocument(documentPath: String) {
+ mainSession.loadTestPath(documentPath)
+ mainSession.waitForPageStop()
+ mainSession.promiseAllPaintsDone()
+ mainSession.flushApzRepaints()
+ }
+
+ private fun setupScroll() {
+ setupDocument(SCROLL_TEST_PATH)
+ }
+
+ private fun waitForVisualScroll(offset: Double, timeout: Double, param: String) {
+ mainSession.evaluateJS(
+ """
+ new Promise((resolve, reject) => {
+ const start = Date.now();
+ function step() {
+ if (window.visualViewport.$param >= ($offset - $errorEpsilon)) {
+ resolve();
+ } else if ($timeout < (Date.now() - start)) {
+ reject();
+ } else {
+ window.requestAnimationFrame(step);
+ }
+ }
+ window.requestAnimationFrame(step);
+ });
+ """.trimIndent(),
+ )
+ }
+
+ private fun waitForHorizontalScroll(offset: Double, timeout: Double) {
+ waitForVisualScroll(offset, timeout, "pageLeft")
+ }
+
+ private fun waitForVerticalScroll(offset: Double, timeout: Double) {
+ waitForVisualScroll(offset, timeout, "pageTop")
+ }
+
+ private fun scrollByVertical(mode: Int) {
+ setupScroll()
+ val vh = mainSession.evaluateJS("window.visualViewport.height") as Double
+ assertThat("Visual viewport height is not zero", vh, greaterThan(0.0))
+ mainSession.panZoomController.scrollBy(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+ waitForVerticalScroll(vh, scrollWaitTimeout)
+ val scrollY = mainSession.evaluateJS("window.visualViewport.pageTop") as Double
+ assertThat("scrollBy should have scrolled along y axis one viewport", scrollY, closeTo(vh, errorEpsilon))
+ }
+
+ private fun scrollByHorizontal(mode: Int) {
+ setupScroll()
+ val vw = mainSession.evaluateJS("window.visualViewport.width") as Double
+ assertThat("Visual viewport width is not zero", vw, greaterThan(0.0))
+ mainSession.panZoomController.scrollBy(ScreenLength.fromVisualViewportWidth(1.0), ScreenLength.zero(), mode)
+ waitForHorizontalScroll(vw, scrollWaitTimeout)
+ val scrollX = mainSession.evaluateJS("window.visualViewport.pageLeft") as Double
+ assertThat("scrollBy should have scrolled along x axis one viewport", scrollX, closeTo(vw, errorEpsilon))
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollByHorizontalSmooth() {
+ scrollByHorizontal(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollByHorizontalAuto() {
+ scrollByHorizontal(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollByVerticalSmooth() {
+ scrollByVertical(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollByVerticalAuto() {
+ scrollByVertical(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ private fun scrollByVerticalTwice(mode: Int) {
+ setupScroll()
+ val vh = mainSession.evaluateJS("window.visualViewport.height") as Double
+ assertThat("Visual viewport height is not zero", vh, greaterThan(0.0))
+ mainSession.panZoomController.scrollBy(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+ waitForVerticalScroll(vh, scrollWaitTimeout)
+ mainSession.panZoomController.scrollBy(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+ waitForVerticalScroll(vh * 2.0, scrollWaitTimeout)
+ val scrollY = mainSession.evaluateJS("window.visualViewport.pageTop") as Double
+ assertThat("scrollBy should have scrolled along y axis one viewport", scrollY, closeTo(vh * 2.0, errorEpsilon))
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollByVerticalTwiceSmooth() {
+ scrollByVerticalTwice(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollByVerticalTwiceAuto() {
+ scrollByVerticalTwice(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ private fun scrollToVertical(mode: Int) {
+ setupScroll()
+ val vh = mainSession.evaluateJS("window.visualViewport.height") as Double
+ assertThat("Visual viewport height is not zero", vh, greaterThan(0.0))
+ mainSession.panZoomController.scrollTo(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+ waitForVerticalScroll(vh, scrollWaitTimeout)
+ val scrollY = mainSession.evaluateJS("window.visualViewport.pageTop") as Double
+ assertThat("scrollBy should have scrolled along y axis one viewport", scrollY, closeTo(vh, errorEpsilon))
+ }
+
+ private fun scrollToHorizontal(mode: Int) {
+ setupScroll()
+ val vw = mainSession.evaluateJS("window.visualViewport.width") as Double
+ assertThat("Visual viewport width is not zero", vw, greaterThan(0.0))
+ mainSession.panZoomController.scrollTo(ScreenLength.fromVisualViewportWidth(1.0), ScreenLength.zero(), mode)
+ waitForHorizontalScroll(vw, scrollWaitTimeout)
+ val scrollX = mainSession.evaluateJS("window.visualViewport.pageLeft") as Double
+ assertThat("scrollBy should have scrolled along x axis one viewport", scrollX, closeTo(vw, errorEpsilon))
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToHorizontalSmooth() {
+ scrollToHorizontal(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToHorizontalAuto() {
+ scrollToHorizontal(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToVerticalSmooth() {
+ scrollToVertical(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToVerticalAuto() {
+ scrollToVertical(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ private fun scrollToVerticalOnZoomedContent(mode: Int) {
+ setupScroll()
+
+ val originalVH = mainSession.evaluateJS("window.visualViewport.height") as Double
+ assertThat("Visual viewport height is not zero", originalVH, greaterThan(0.0))
+
+ val innerHeight = mainSession.evaluateJS("window.innerHeight") as Double
+ // Need to round due to dom.InnerSize.rounded=true
+ assertThat(
+ "Visual viewport height equals to window.innerHeight",
+ originalVH.roundToInt(),
+ equalTo(innerHeight.roundToInt()),
+ )
+
+ val originalScale = mainSession.evaluateJS("visualViewport.scale") as Double
+ assertThat("Visual viewport scale is the initial scale", originalScale, closeTo(0.5, 0.01))
+
+ // Change the resolution so that the visual viewport will be different from the layout viewport.
+ mainSession.setResolutionAndScaleTo(2.0f)
+
+ val scale = mainSession.evaluateJS("visualViewport.scale") as Double
+ assertThat("Visual viewport scale is now greater than the initial scale", scale, greaterThan(originalScale))
+
+ val vh = mainSession.evaluateJS("window.visualViewport.height") as Double
+ assertThat("Visual viewport height has been changed", vh, lessThan(originalVH))
+
+ mainSession.panZoomController.scrollTo(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+
+ waitForVerticalScroll(vh, scrollWaitTimeout)
+ val scrollY = mainSession.evaluateJS("window.visualViewport.pageTop") as Double
+ assertThat("scrollBy should have scrolled along y axis one viewport", scrollY, closeTo(vh, errorEpsilon))
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToVerticalOnZoomedContentSmooth() {
+ scrollToVerticalOnZoomedContent(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToVerticalOnZoomedContentAuto() {
+ scrollToVerticalOnZoomedContent(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ private fun scrollToVerticalTwice(mode: Int) {
+ setupScroll()
+ val vh = mainSession.evaluateJS("window.visualViewport.height") as Double
+ assertThat("Visual viewport height is not zero", vh, greaterThan(0.0))
+ mainSession.panZoomController.scrollTo(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+ waitForVerticalScroll(vh, scrollWaitTimeout)
+ mainSession.panZoomController.scrollTo(ScreenLength.zero(), ScreenLength.fromVisualViewportHeight(1.0), mode)
+ waitForVerticalScroll(vh, scrollWaitTimeout)
+ val scrollY = mainSession.evaluateJS("window.visualViewport.pageTop") as Double
+ assertThat("scrollBy should have scrolled along y axis one viewport", scrollY, closeTo(vh, errorEpsilon))
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToVerticalTwiceSmooth() {
+ scrollToVerticalTwice(PanZoomController.SCROLL_BEHAVIOR_SMOOTH)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun scrollToVerticalTwiceAuto() {
+ scrollToVerticalTwice(PanZoomController.SCROLL_BEHAVIOR_AUTO)
+ }
+
+ private fun setupTouch() {
+ setupDocument(TOUCH_HTML_PATH)
+ }
+
+ private fun sendDownEvent(x: Float, y: Float): GeckoResult<Int> {
+ val downTime = SystemClock.uptimeMillis()
+ val down = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_DOWN,
+ x,
+ y,
+ 0,
+ )
+
+ val result = mainSession.panZoomController.onTouchEventForDetailResult(down)
+ .map { value -> value!!.handledResult() }
+ val up = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ x,
+ y,
+ 0,
+ )
+
+ mainSession.panZoomController.onTouchEvent(up)
+
+ return result
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun touchEventForResultWithStaticToolbar() {
+ setupTouch()
+
+ // Non-scrollable page: value is always INPUT_RESULT_UNHANDLED
+
+ // No touch handler
+ var value = sessionRule.waitForResult(sendDownEvent(50f, 15f))
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_UNHANDLED))
+
+ // Touch handler with preventDefault
+ value = sessionRule.waitForResult(sendDownEvent(50f, 45f))
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT))
+
+ // Touch handler without preventDefault
+ value = sessionRule.waitForResult(sendDownEvent(50f, 75f))
+ // Nothing should have done in the event handler and the content is not scrollable,
+ // thus the input result should be UNHANDLED, i.e. the dynamic toolbar should NOT
+ // move in response to the event.
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_UNHANDLED))
+
+ // Scrollable page: value depends on the presence and type of touch handler
+ setupScroll()
+
+ // No touch handler
+ value = sessionRule.waitForResult(sendDownEvent(50f, 15f))
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED))
+
+ // Touch handler with preventDefault
+ value = sessionRule.waitForResult(sendDownEvent(50f, 45f))
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT))
+
+ // Touch handler without preventDefault
+ value = sessionRule.waitForResult(sendDownEvent(50f, 75f))
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED))
+ }
+
+ private fun setupTouchEventDocument(documentPath: String, withEventHandler: Boolean) {
+ setupDocument(documentPath + if (withEventHandler) "?event" else "")
+ }
+
+ private fun waitForScroll(timeout: Double) {
+ mainSession.evaluateJS(
+ """
+ const targetWindow = document.querySelector('iframe') ?
+ document.querySelector('iframe').contentWindow : window;
+ new Promise((resolve, reject) => {
+ const start = Date.now();
+ function step() {
+ if (targetWindow.scrollY == targetWindow.scrollMaxY) {
+ resolve();
+ } else if ($timeout < (Date.now() - start)) {
+ reject();
+ } else {
+ window.requestAnimationFrame(step);
+ }
+ }
+ window.requestAnimationFrame(step);
+ });
+ """.trimIndent(),
+ )
+ }
+
+ private fun testTouchEventForResult(withEventHandler: Boolean) {
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(20) }
+
+ // The content height is not greater than "screen height - the dynamic toolbar height".
+ setupTouchEventDocument(ROOT_100_PERCENT_HEIGHT_HTML_PATH, withEventHandler)
+ var value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ assertThat(
+ "The input result should be UNHANDLED in root_100_percent.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_UNHANDLED),
+ )
+
+ // There is a 100% height iframe which is not scrollable.
+ setupTouchEventDocument(IFRAME_100_PERCENT_HEIGHT_NO_SCROLLABLE_HTML_PATH, withEventHandler)
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ // The input result should NOT be handled in the iframe content,
+ // should NOT be handled in the root either.
+ assertThat(
+ "The input result should be UNHANDLED in iframe_100_percent_height_no_scrollable.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_UNHANDLED),
+ )
+
+ // There is a 100% height iframe which is scrollable.
+ setupTouchEventDocument(IFRAME_100_PERCENT_HEIGHT_SCROLLABLE_HTML_PATH, withEventHandler)
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ // The input result should be handled in the iframe content.
+ assertThat(
+ "The input result should be HANDLED_CONTENT in iframe_100_percent_height_scrollable.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT),
+ )
+
+ // Scroll to the bottom of the iframe
+ mainSession.evaluateJS(
+ """
+ const iframe = document.querySelector('iframe');
+ iframe.contentWindow.scrollTo({
+ left: 0,
+ top: iframe.contentWindow.scrollMaxY,
+ behavior: 'instant'
+ });
+ """.trimIndent(),
+ )
+ waitForScroll(scrollWaitTimeout)
+ mainSession.flushApzRepaints()
+
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ // The input result should still be handled in the iframe content.
+ assertThat(
+ "The input result should be HANDLED_CONTENT in iframe_100_percent_height_scrollable.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT),
+ )
+
+ // The content height is greater than "screen height - the dynamic toolbar height".
+ setupTouchEventDocument(ROOT_98VH_HTML_PATH, withEventHandler)
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ assertThat(
+ "The input result should be HANDLED in root_98vh.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED),
+ )
+
+ // The content height is equal to "screen height".
+ setupTouchEventDocument(ROOT_100VH_HTML_PATH, withEventHandler)
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ assertThat(
+ "The input result should be HANDLED in root_100vh.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED),
+ )
+
+ // There is a 98vh iframe which is not scrollable.
+ setupTouchEventDocument(IFRAME_98VH_NO_SCROLLABLE_HTML_PATH, withEventHandler)
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ // The input result should NOT be handled in the iframe content.
+ assertThat(
+ "The input result should be HANDLED in iframe_98vh_no_scrollable.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED),
+ )
+
+ // There is a 98vh iframe which is scrollable.
+ setupTouchEventDocument(IFRAME_98VH_SCROLLABLE_HTML_PATH, withEventHandler)
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ // The input result should be handled in the iframe content initially.
+ assertThat(
+ "The input result should be HANDLED_CONTENT initially in iframe_98vh_scrollable.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT),
+ )
+
+ // Scroll to the bottom of the iframe
+ mainSession.evaluateJS(
+ """
+ const iframe = document.querySelector('iframe');
+ iframe.contentWindow.scrollTo({
+ left: 0,
+ top: iframe.contentWindow.scrollMaxY,
+ behavior: 'instant'
+ });
+ """.trimIndent(),
+ )
+ waitForScroll(scrollWaitTimeout)
+ mainSession.flushApzRepaints()
+
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ // Now the input result should be handled in the root APZC.
+ assertThat(
+ "The input result should be HANDLED in iframe_98vh_scrollable.html",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED),
+ )
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun touchEventForResultWithEventHandler() {
+ testTouchEventForResult(true)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun touchEventForResultWithoutEventHandler() {
+ testTouchEventForResult(false)
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun touchEventForResultWithPreventDefault() {
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(20) }
+
+ // Entries are pairs of (filename, pageIsPannable)
+ // Note: "pageIsPannable" means "pannable" in the sense used in
+ // AsyncPanZoomController::ArePointerEventsConsumable().
+ // For example, in iframe_98vh_no_scrollable.html, even though
+ // the page does not have a scroll range, the page is "pannable"
+ // because the dynamic toolbar can be hidden.
+ var files = arrayOf(
+ ROOT_100_PERCENT_HEIGHT_HTML_PATH,
+ ROOT_98VH_HTML_PATH,
+ ROOT_100VH_HTML_PATH,
+ IFRAME_100_PERCENT_HEIGHT_NO_SCROLLABLE_HTML_PATH,
+ IFRAME_100_PERCENT_HEIGHT_SCROLLABLE_HTML_PATH,
+ IFRAME_98VH_SCROLLABLE_HTML_PATH,
+ IFRAME_98VH_NO_SCROLLABLE_HTML_PATH,
+ )
+ for (file in files) {
+ setupDocument(file + "?event-prevent")
+ var value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ assertThat(
+ "The input result should be HANDLED_CONTENT in " + file,
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT),
+ )
+
+ // Scroll to the bottom edge if it's possible.
+ mainSession.evaluateJS(
+ """
+ const targetWindow = document.querySelector('iframe') ?
+ document.querySelector('iframe').contentWindow : window;
+ targetWindow.scrollTo({
+ left: 0,
+ top: targetWindow.scrollMaxY,
+ behavior: 'instant'
+ });
+ """.trimIndent(),
+ )
+ waitForScroll(scrollWaitTimeout)
+ mainSession.flushApzRepaints()
+
+ value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ assertThat(
+ "The input result should be HANDLED_CONTENT in " + file,
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT),
+ )
+ }
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun touchActionWithWheelListener() {
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(20) }
+ setupDocument(TOUCH_ACTION_WHEEL_LISTENER_HTML_PATH)
+ var value = sessionRule.waitForResult(sendDownEvent(50f, 50f))
+ assertThat(
+ "The input result should be HANDLED_CONTENT",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT),
+ )
+ }
+
+ private fun fling(): GeckoResult<Int> {
+ val downTime = SystemClock.uptimeMillis()
+ val down = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_DOWN,
+ 50f,
+ 90f,
+ 0,
+ )
+
+ val result = mainSession.panZoomController.onTouchEventForDetailResult(down)
+ .map { value -> value!!.handledResult() }
+ var move = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 50f,
+ 70f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(move)
+ move = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 50f,
+ 30f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(move)
+
+ val up = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 50f,
+ 10f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(up)
+ return result
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun dontCrashDuringFastFling() {
+ setupDocument(TOUCHSTART_HTML_PATH)
+
+ fling()
+ fling()
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun inputResultForFastFling() {
+ setupDocument(TOUCHSTART_HTML_PATH)
+
+ var value = sessionRule.waitForResult(fling())
+ assertThat(
+ "The initial input result should be HANDLED",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED),
+ )
+ // Trigger the next fling during the initial scrolling.
+ value = sessionRule.waitForResult(fling())
+ assertThat(
+ "The input result should be IGNORED during the fast fling",
+ value,
+ equalTo(PanZoomController.INPUT_RESULT_HANDLED),
+ )
+ }
+
+ @WithDisplay(width = 100, height = 100)
+ @Test
+ fun touchEventWithXOrigin() {
+ setupDocument(TOUCH_XORIGIN_HTML_PATH)
+
+ // Touch handler with preventDefault
+ val value = sessionRule.waitForResult(sendDownEvent(50f, 45f))
+ assertThat("Value should match", value, equalTo(PanZoomController.INPUT_RESULT_HANDLED_CONTENT))
+ }
+}