summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt')
-rw-r--r--mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt727
1 files changed, 727 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt
new file mode 100644
index 0000000000..6a79df6173
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt
@@ -0,0 +1,727 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.geckoview.test
+
+import android.graphics.* // ktlint-disable no-wildcard-imports
+import android.graphics.Bitmap
+import android.os.SystemClock
+import android.util.Base64
+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.hamcrest.Matchers.closeTo
+import org.hamcrest.Matchers.equalTo
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mozilla.geckoview.GeckoResult
+import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.GeckoSession.ContentDelegate
+import org.mozilla.geckoview.GeckoSession.ScrollDelegate
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
+import java.io.ByteArrayOutputStream
+
+private const val SCREEN_WIDTH = 100
+private const val SCREEN_HEIGHT = 200
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class DynamicToolbarTest : BaseSessionTest() {
+ // Makes sure we can load a page when the dynamic toolbar is bigger than the whole content
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun outOfRangeValue() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT + 1
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(HELLO_HTML_PATH)
+ mainSession.waitForPageStop()
+ }
+
+ private fun assertScreenshotResult(result: GeckoResult<Bitmap>, comparisonImage: Bitmap) {
+ sessionRule.waitForResult(result).let {
+ assertThat(
+ "Screenshot is not null",
+ it,
+ notNullValue(),
+ )
+ assertThat("Widths are the same", comparisonImage.width, equalTo(it.width))
+ assertThat("Heights are the same", comparisonImage.height, equalTo(it.height))
+ assertThat("Byte counts are the same", comparisonImage.byteCount, equalTo(it.byteCount))
+ assertThat("Configs are the same", comparisonImage.config, equalTo(it.config))
+
+ if (!comparisonImage.sameAs(it)) {
+ val outputForComparison = ByteArrayOutputStream()
+ comparisonImage.compress(Bitmap.CompressFormat.PNG, 100, outputForComparison)
+
+ val outputForActual = ByteArrayOutputStream()
+ it.compress(Bitmap.CompressFormat.PNG, 100, outputForActual)
+ val actualString: String = Base64.encodeToString(outputForActual.toByteArray(), Base64.DEFAULT)
+ val comparisonString: String = Base64.encodeToString(outputForComparison.toByteArray(), Base64.DEFAULT)
+
+ assertThat("Encoded strings are the same", comparisonString, equalTo(actualString))
+ }
+
+ assertThat("Bytes are the same", comparisonImage.sameAs(it), equalTo(true))
+ }
+ }
+
+ /**
+ * Returns a whole green Bitmap.
+ * This Bitmap would be a reference image of tests in this file.
+ */
+ private fun getComparisonScreenshot(width: Int, height: Int): Bitmap {
+ val screenshotFile = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(screenshotFile)
+ val paint = Paint()
+ paint.color = Color.rgb(0, 128, 0)
+ canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
+ return screenshotFile
+ }
+
+ // With the dynamic toolbar max height vh units values exceed
+ // the top most window height. This is a test case that exceeded area
+ // is rendered properly (on the compositor).
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun positionFixedElementClipping() {
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(SCREEN_HEIGHT / 2) }
+
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ // FIXED_VH is an HTML file which has a position:fixed element whose
+ // style is "width: 100%; height: 200vh" and the document is scaled by
+ // minimum-scale 0.5, so that the height of the element exceeds the
+ // window height.
+ mainSession.loadTestPath(BaseSessionTest.FIXED_VH)
+ mainSession.waitForPageStop()
+
+ // Scroll down bit, if we correctly render the document, the position
+ // fixed element still covers whole the document area.
+ mainSession.evaluateJS("window.scrollTo({ top: 100, behavior: 'instant' })")
+
+ // Wait a while to make sure the scrolling result is composited on the compositor
+ // since capturePixels() takes a snapshot directly from the compositor without
+ // waiting for a corresponding MozAfterPaint on the main-thread so it's possible
+ // to take a stale snapshot even if it's a result of syncronous scrolling.
+ mainSession.evaluateJS("new Promise(resolve => window.setTimeout(resolve, 1000))")
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+
+ // Asynchronous scrolling with the dynamic toolbar max height causes
+ // situations where the visual viewport size gets bigger than the layout
+ // viewport on the compositor thread because of 200vh position:fixed
+ // elements. This is a test case that a 200vh position element is
+ // properly rendered its positions.
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun layoutViewportExpansion() {
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(SCREEN_HEIGHT / 2) }
+
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_VH)
+ mainSession.waitForPageStop()
+
+ mainSession.evaluateJS("window.scrollTo(0, 100)")
+
+ // Scroll back to the original position by asynchronous scrolling.
+ mainSession.evaluateJS("window.scrollTo({ top: 0, behavior: 'smooth' })")
+
+ mainSession.evaluateJS("new Promise(resolve => window.setTimeout(resolve, 1000))")
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun visualViewportEvents() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_VH)
+ mainSession.waitForPageStop()
+
+ val pixelRatio = mainSession.evaluateJS("window.devicePixelRatio") as Double
+ val scale = mainSession.evaluateJS("window.visualViewport.scale") as Double
+
+ for (i in 1..dynamicToolbarMaxHeight) {
+ // Simulate the dynamic toolbar is going to be hidden.
+ sessionRule.display?.run { setVerticalClipping(-i) }
+
+ val expectedViewportHeight = (SCREEN_HEIGHT - dynamicToolbarMaxHeight + i) / scale / pixelRatio
+ val promise = mainSession.evaluatePromiseJS(
+ """
+ new Promise(resolve => {
+ window.visualViewport.addEventListener('resize', resolve(window.visualViewport.height));
+ });
+ """.trimIndent(),
+ )
+
+ assertThat(
+ "The visual viewport height should be changed in response to the dynamc toolbar transition",
+ promise.value as Double,
+ closeTo(expectedViewportHeight, .01),
+ )
+ }
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun percentBaseValueOnPositionFixedElement() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_PERCENT)
+ mainSession.waitForPageStop()
+
+ val originalHeight = mainSession.evaluateJS(
+ """
+ getComputedStyle(document.querySelector('#fixed-element')).height
+ """.trimIndent(),
+ ) as String
+
+ // Set the vertical clipping value to the middle of toolbar transition.
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight / 2) }
+
+ var height = mainSession.evaluateJS(
+ """
+ getComputedStyle(document.querySelector('#fixed-element')).height
+ """.trimIndent(),
+ ) as String
+
+ assertThat(
+ "The %-based height should be the static in the middle of toolbar tansition",
+ height,
+ equalTo(originalHeight),
+ )
+
+ // Set the vertical clipping value to hide the toolbar completely.
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+ height = mainSession.evaluateJS(
+ """
+ getComputedStyle(document.querySelector('#fixed-element')).height
+ """.trimIndent(),
+ ) as String
+
+ val scale = mainSession.evaluateJS("window.visualViewport.scale") as Double
+ val expectedHeight = (SCREEN_HEIGHT / scale).toInt()
+ assertThat(
+ "The %-based height should be now recomputed based on the screen height",
+ height,
+ equalTo(expectedHeight.toString() + "px"),
+ )
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun resizeEvents() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_VH)
+ mainSession.waitForPageStop()
+
+ for (i in 1..dynamicToolbarMaxHeight - 1) {
+ val promise = mainSession.evaluatePromiseJS(
+ """
+ new Promise(resolve => {
+ let fired = false;
+ window.addEventListener('resize', () => { fired = true; }, { once: true });
+ // Note that `resize` event is fired just before rAF callbacks, so under ideal
+ // circumstances waiting for a rAF should be sufficient, even if it's not sufficient
+ // unexpected resize event(s) will be caught in the next loop.
+ requestAnimationFrame(() => { resolve(fired); });
+ });
+ """.trimIndent(),
+ )
+
+ // Simulate the dynamic toolbar is going to be hidden.
+ sessionRule.display?.run { setVerticalClipping(-i) }
+ assertThat(
+ "'resize' event on window should not be fired in response to the dynamc toolbar transition",
+ promise.value as Boolean,
+ equalTo(false),
+ )
+ }
+
+ val promise = mainSession.evaluatePromiseJS(
+ """
+ new Promise(resolve => {
+ window.addEventListener('resize', () => { resolve(true); }, { once: true });
+ });
+ """.trimIndent(),
+ )
+
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+ assertThat(
+ "'resize' event on window should be fired when the dynamc toolbar is completely hidden",
+ promise.value as Boolean,
+ equalTo(true),
+ )
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun windowInnerHeight() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ // We intentionally use FIXED_BOTTOM instead of FIXED_VH in this test since
+ // FIXED_VH has `minimum-scale=0.5` thus we can't properly test window.innerHeight
+ // with FXIED_VH for now due to bug 1598487.
+ mainSession.loadTestPath(BaseSessionTest.FIXED_BOTTOM)
+ mainSession.waitForPageStop()
+
+ val pixelRatio = mainSession.evaluateJS("window.devicePixelRatio") as Double
+
+ for (i in 1..dynamicToolbarMaxHeight - 1) {
+ val promise = mainSession.evaluatePromiseJS(
+ """
+ new Promise(resolve => {
+ window.visualViewport.addEventListener('resize', resolve(window.innerHeight));
+ });
+ """.trimIndent(),
+ )
+
+ // Simulate the dynamic toolbar is going to be hidden.
+ sessionRule.display?.run { setVerticalClipping(-i) }
+ assertThat(
+ "window.innerHeight should not be changed in response to the dynamc toolbar transition",
+ promise.value as Double,
+ closeTo(SCREEN_HEIGHT / 2 / pixelRatio, .01),
+ )
+ }
+
+ val promise = mainSession.evaluatePromiseJS(
+ """
+ new Promise(resolve => {
+ window.addEventListener('resize', () => { resolve(window.innerHeight); }, { once: true });
+ });
+ """.trimIndent(),
+ )
+
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+ assertThat(
+ "window.innerHeight should be changed when the dynamc toolbar is completely hidden",
+ promise.value as Double,
+ closeTo(SCREEN_HEIGHT / pixelRatio, .01),
+ )
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun notCrashOnResizeEvent() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_VH)
+ mainSession.waitForPageStop()
+
+ val promise = mainSession.evaluatePromiseJS(
+ """
+ new Promise(resolve => window.addEventListener('resize', () => resolve(true)));
+ """.trimIndent(),
+ )
+
+ // Do some setVerticalClipping calls that we might try to queue two window resize events.
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight + 1) }
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ assertThat("Got a rezie event", promise.value as Boolean, equalTo(true))
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun showDynamicToolbar() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(SHOW_DYNAMIC_TOOLBAR_HTML_PATH)
+ mainSession.waitForPageStop()
+ mainSession.evaluateJS("window.scrollTo(0, " + dynamicToolbarMaxHeight + ")")
+ mainSession.waitUntilCalled(object : ScrollDelegate {
+ @AssertCalled(count = 1)
+ override fun onScrollChanged(session: GeckoSession, scrollX: Int, scrollY: Int) {
+ }
+ })
+
+ // Simulate the dynamic toolbar being hidden by the scroll
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ mainSession.synthesizeTap(5, 25)
+
+ mainSession.waitUntilCalled(object : ContentDelegate {
+ @AssertCalled(count = 1)
+ override fun onShowDynamicToolbar(session: GeckoSession) {
+ }
+ })
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun showDynamicToolbarOnOverflowHidden() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(SHOW_DYNAMIC_TOOLBAR_HTML_PATH)
+ mainSession.waitForPageStop()
+ mainSession.evaluateJS("window.scrollTo(0, " + dynamicToolbarMaxHeight + ")")
+ mainSession.waitUntilCalled(object : ScrollDelegate {
+ @AssertCalled(count = 1)
+ override fun onScrollChanged(session: GeckoSession, scrollX: Int, scrollY: Int) {
+ }
+ })
+
+ // Simulate the dynamic toolbar being hidden by the scroll
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ mainSession.evaluateJS("document.documentElement.style.overflow = 'hidden'")
+
+ mainSession.waitUntilCalled(object : ContentDelegate {
+ @AssertCalled(count = 1)
+ override fun onShowDynamicToolbar(session: GeckoSession) {
+ }
+ })
+ }
+
+ private fun getComputedViewportHeight(style: String): Double {
+ val viewportHeight = mainSession.evaluateJS(
+ """
+ const target = document.createElement('div');
+ target.style.height = '$style';
+ document.body.appendChild(target);
+ parseFloat(getComputedStyle(target).height);
+ """.trimIndent(),
+ ) as Double
+
+ return viewportHeight
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun viewportVariants() {
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.VIEWPORT_PATH)
+ mainSession.waitForPageStop()
+
+ val pixelRatio = mainSession.evaluateJS("window.devicePixelRatio") as Double
+ val scale = mainSession.evaluateJS("window.visualViewport.scale") as Double
+
+ var smallViewportHeight = getComputedViewportHeight("100svh")
+ assertThat(
+ "svh value at the initial state",
+ smallViewportHeight,
+ closeTo((SCREEN_HEIGHT - dynamicToolbarMaxHeight) / scale / pixelRatio, 0.1),
+ )
+
+ var largeViewportHeight = getComputedViewportHeight("100lvh")
+ assertThat(
+ "lvh value at the initial state",
+ largeViewportHeight,
+ closeTo(SCREEN_HEIGHT / scale / pixelRatio, 0.1),
+ )
+
+ var dynamicViewportHeight = getComputedViewportHeight("100dvh")
+ assertThat(
+ "dvh value at the initial state",
+ dynamicViewportHeight,
+ closeTo((SCREEN_HEIGHT - dynamicToolbarMaxHeight) / scale / pixelRatio, 0.1),
+ )
+
+ // Move down the toolbar at a fourth of its position.
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight / 4) }
+
+ smallViewportHeight = getComputedViewportHeight("100svh")
+ assertThat(
+ "svh value during toolbar transition",
+ smallViewportHeight,
+ closeTo((SCREEN_HEIGHT - dynamicToolbarMaxHeight) / scale / pixelRatio, 0.1),
+ )
+
+ largeViewportHeight = getComputedViewportHeight("100lvh")
+ assertThat(
+ "lvh value during toolbar transition",
+ largeViewportHeight,
+ closeTo(SCREEN_HEIGHT / scale / pixelRatio, 0.1),
+ )
+
+ dynamicViewportHeight = getComputedViewportHeight("100dvh")
+ assertThat(
+ "dvh value during toolbar transition",
+ dynamicViewportHeight,
+ closeTo((SCREEN_HEIGHT - dynamicToolbarMaxHeight + dynamicToolbarMaxHeight / 4) / scale / pixelRatio, 0.1),
+ )
+ }
+
+ // With dynamic toolbar, there was a floating point rounding error in Gecko layout side.
+ // The error was appeared by user interactive async scrolling, not by programatic async
+ // scrolling, e.g. scrollTo() method. If the error happens there will appear 1px gap
+ // between <body> and an element which covers up the <body> element.
+ // This test simulates the situation.
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun noGapAppearsBetweenBodyAndElementFullyCoveringBody() {
+ // Bug 1764219 - disable the test to reduce intermittent failure rate
+ assumeThat(sessionRule.env.isDebugBuild, equalTo(false))
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ mainSession.loadTestPath(BaseSessionTest.BODY_FULLY_COVERED_BY_GREEN_ELEMENT)
+ mainSession.waitForPageStop()
+ mainSession.flushApzRepaints()
+
+ // Scrolling down by touch events.
+ var downTime = SystemClock.uptimeMillis()
+ var down = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_DOWN,
+ 50f,
+ 70f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(down)
+ var move = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 50f,
+ 30f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(move)
+ var up = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 50f,
+ 10f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(up)
+ mainSession.flushApzRepaints()
+
+ // Scrolling up by touch events to restore the original position.
+ downTime = SystemClock.uptimeMillis()
+ down = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_DOWN,
+ 50f,
+ 10f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(down)
+ move = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 50f,
+ 30f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(move)
+ up = MotionEvent.obtain(
+ downTime,
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 50f,
+ 70f,
+ 0,
+ )
+ mainSession.panZoomController.onTouchEvent(up)
+ mainSession.flushApzRepaints()
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun zoomedOverflowHidden() {
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for foreground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_BOTTOM)
+ mainSession.waitForPageStop()
+
+ // Change the body background color to match the reference image's background color.
+ mainSession.evaluateJS("document.body.style.background = 'rgb(0, 128, 0)'")
+
+ // Hide the vertical scrollbar.
+ mainSession.evaluateJS("document.documentElement.style.scrollbarWidth = 'none'")
+
+ // Zoom in the content so that the content's visual viewport can be scrollable.
+ mainSession.setResolutionAndScaleTo(10.0f)
+
+ // Simulate the dynamic toolbar being hidden by the scroll
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ mainSession.flushApzRepaints()
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun zoomedPositionFixedRoot() {
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for foreground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.FIXED_BOTTOM)
+ mainSession.waitForPageStop()
+
+ // Change the body background color to match the reference image's background color.
+ mainSession.evaluateJS("document.body.style.background = 'rgb(0, 128, 0)'")
+
+ // Change the root `overlow` style to make it scrollable and change the position style
+ // to `fixed` so that the root container is not scrollable.
+ mainSession.evaluateJS("document.body.style.overflow = 'scroll'")
+ mainSession.evaluateJS("document.documentElement.style.position = 'fixed'")
+
+ // Hide the vertical scrollbar.
+ mainSession.evaluateJS("document.documentElement.style.scrollbarWidth = 'none'")
+
+ // Zoom in the content so that the content's visual viewport can be scrollable.
+ mainSession.setResolutionAndScaleTo(10.0f)
+
+ // Simulate the dynamic toolbar being hidden by the scroll
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ mainSession.flushApzRepaints()
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun backgroundImageFixed() {
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.TOUCH_ACTION_HTML_PATH)
+ mainSession.waitForPageStop()
+
+ // Specify the root background-color to match the reference image color and specify
+ // `background-attachment: fixed`.
+ mainSession.evaluateJS("document.documentElement.style.background = 'linear-gradient(green, green) fixed'")
+
+ // Make the root element scrollable.
+ mainSession.evaluateJS("document.documentElement.style.height = '100vh'")
+
+ // Hide the vertical scrollbar.
+ mainSession.evaluateJS("document.documentElement.style.scrollbarWidth = 'none'")
+
+ mainSession.flushApzRepaints()
+
+ // Simulate the dynamic toolbar being hidden by the scroll
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ mainSession.flushApzRepaints()
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+
+ @WithDisplay(height = SCREEN_HEIGHT, width = SCREEN_WIDTH)
+ @Test
+ fun backgroundAttachmentFixed() {
+ val reference = getComparisonScreenshot(SCREEN_WIDTH, SCREEN_HEIGHT)
+
+ val dynamicToolbarMaxHeight = SCREEN_HEIGHT / 2
+ sessionRule.display?.run { setDynamicToolbarMaxHeight(dynamicToolbarMaxHeight) }
+
+ // Set active since setVerticalClipping call affects only for forground tab.
+ mainSession.setActive(true)
+
+ mainSession.loadTestPath(BaseSessionTest.TOUCH_ACTION_HTML_PATH)
+ mainSession.waitForPageStop()
+
+ // Specify the root background-color to match the reference image color and specify
+ // `background-attachment: fixed`.
+ mainSession.evaluateJS("document.documentElement.style.background = 'rgb(0, 128, 0) fixed'")
+
+ // Make the root element scrollable.
+ mainSession.evaluateJS("document.documentElement.style.height = '100vh'")
+
+ // Hide the vertical scrollbar.
+ mainSession.evaluateJS("document.documentElement.style.scrollbarWidth = 'none'")
+
+ mainSession.flushApzRepaints()
+
+ // Simulate the dynamic toolbar being hidden by the scroll
+ sessionRule.display?.run { setVerticalClipping(-dynamicToolbarMaxHeight) }
+
+ mainSession.flushApzRepaints()
+
+ sessionRule.display?.let {
+ assertScreenshotResult(it.capturePixels(), reference)
+ }
+ }
+}