diff options
Diffstat (limited to '')
-rw-r--r-- | mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/DynamicToolbarTest.kt | 727 |
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) + } + } +} |