Bug 1586144 - Expand the clipt rect for visual viewport scrollable display item. r=botond

Without this change, the junit test in this commit fail, the failure
rendered result is the area of the position:fixed element covered by
the dynamic toolbar before scrolling is rendered as blank.

Depends on D50418

Differential Revision: https://phabricator.services.mozilla.com/D50419

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Hiroyuki Ikezoe 2019-11-14 06:00:25 +00:00
Родитель f5f6f60da5
Коммит 4f001373b7
6 изменённых файлов: 143 добавлений и 2 удалений

Просмотреть файл

@ -2996,6 +2996,16 @@ class nsLayoutUtils {
static bool FrameIsMostlyScrolledOutOfViewInCrossProcess(
const nsIFrame* aFrame, nscoord aMargin);
/**
* Expand the height of |aSize| to the size of `vh` units.
*
* With dynamic toolbar(s) the height for `vh` units is greater than the
* ICB height, we need to expand it in some places.
**/
template <typename SizeType>
static SizeType ExpandHeightForViewportUnits(nsPresContext* aPresContext,
const SizeType& aSize);
private:
/**
* Helper function for LogTestDataForPaint().
@ -3048,6 +3058,23 @@ template <typename PointType, typename RectType, typename CoordType>
return false;
}
template <typename SizeType>
/* static */ SizeType nsLayoutUtils::ExpandHeightForViewportUnits(
nsPresContext* aPresContext, const SizeType& aSize) {
nsSize sizeForViewportUnits = aPresContext->GetSizeForViewportUnits();
// |aSize| might be the size expanded to the minimum-scale size whereas the
// size for viewport units is not scaled so that we need to expand the |aSize|
// height with the aspect ratio of the size for viewport units instead of just
// expanding to the size for viewport units.
float ratio = (float)sizeForViewportUnits.height / sizeForViewportUnits.width;
MOZ_ASSERT(aSize.height <=
NSCoordSaturatingNonnegativeMultiply(aSize.width, ratio));
return SizeType(aSize.width,
NSCoordSaturatingNonnegativeMultiply(aSize.width, ratio));
}
namespace mozilla {
/**

Просмотреть файл

@ -371,6 +371,8 @@ class nsPresContext : public nsISupports,
*/
void SetVisibleArea(const nsRect& r);
nsSize GetSizeForViewportUnits() const { return mSizeForViewportUnits; }
/**
* Set the maximum height of the dynamic toolbar in nscoord units.
*/

Просмотреть файл

@ -3544,6 +3544,17 @@ void ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder,
aBuilder->ShouldBuildAsyncZoomContainer() && isRootContent;
nsRect scrollPortClip = mScrollPort + aBuilder->ToReferenceFrame(mOuter);
// Expand the clip rect to the size including the area covered by dynamic
// toolbar in the case where the dynamic toolbar is being used since
// position:fixed elements attached to this root scroller might be taller than
// its scroll port (e.g 100vh). Even if the dynamic toolbar covers the taller
// area, it doesn't mean the area is clipped by the toolbar because the
// dynamic toolbar is laid out outside of our topmost window and it
// transitions without changing our topmost window size.
if (isRootContent && mOuter->PresContext()->HasDynamicToolbar()) {
scrollPortClip.SizeTo(nsLayoutUtils::ExpandHeightForViewportUnits(
mOuter->PresContext(), scrollPortClip.Size()));
}
nsRect clipRect = scrollPortClip;
// Our override of GetBorderRadii ensures we never have a radius at
// the corners where we have a scrollbar.

Просмотреть файл

@ -0,0 +1,24 @@
<!DOCTYPE html>
<meta name="viewport" content="width=device-width, minimum-scale=0.5">
<style>
html {
width: 100%;
height: 100%;
scrollbar-width: none;
}
body {
width: 200%;
height: 2000px;
margin: 0;
padding: 0;
}
#fixed-element {
width: 100%;
height: 200vh;
position: fixed;
top: 0px;
background-color: green;
}
</style>
<div id="fixed-element"></div>

Просмотреть файл

@ -59,6 +59,7 @@ open class BaseSessionTest(noErrorCollector: Boolean = false) {
const val SCROLL_TEST_PATH = "/assets/www/scroll.html"
const val COLORS_HTML_PATH = "/assets/www/colors.html"
const val FIXED_BOTTOM = "/assets/www/fixedbottom.html"
const val FIXED_VH = "/assets/www/fixedvh.html"
const val STORAGE_TITLE_HTML_PATH = "/assets/www/reflect_local_storage_into_title.html"
const val HUNG_SCRIPT = "/assets/www/hungScript.html"
const val PUSH_HTML_PATH = "/assets/www/push/push.html"

Просмотреть файл

@ -4,16 +4,22 @@
package org.mozilla.geckoview.test
import android.graphics.*
import android.graphics.Bitmap
import android.support.test.filters.MediumTest
import android.support.test.runner.AndroidJUnit4
import android.util.Base64
import java.io.ByteArrayOutputStream
import org.hamcrest.Matchers.*
import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.hamcrest.Matchers.equalTo
private const val SCREEN_HEIGHT = 100
private const val SCREEN_WIDTH = 200
private const val SCREEN_WIDTH = 100
private const val SCREEN_HEIGHT = 200
@RunWith(AndroidJUnit4::class)
@MediumTest
@ -29,4 +35,74 @@ class DynamicToolbarTest : BaseSessionTest() {
e.toString(), containsString("maximum height of the dynamic toolbar"))
}
}
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, behevior: '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)
}
}
}