Bug 1746098 - Reflow scrolled inner frame in TryLayout() only when sizes of scrollbar gutter change. r=emilio

We now support scrollbar-gutter property. So for example, assume the scroll
container has "scrollbar-gutter:stable". When toggling the visibility of
inline-end scrollbar, we can skip the reflow for the scroll inner frame because
the available inline-size for it cannot change.

This patch teaches TryLayout() to consider the sizes of scrollbar gutter rather
than the (assumed) visibility of scrollbars when deciding the need to call
ReflowScrolledFrame().

Also, TryLayout() doesn't need to report an inconsistent layout unless
the (showHScrollbar, showVScrollbar) pair changes the sizes of scrollbar
gutters.

Differential Revision: https://phabricator.services.mozilla.com/D134373
This commit is contained in:
Ting-Yu Lin 2021-12-21 22:05:43 +00:00
Родитель 3adbed7d01
Коммит d570e88e1f
4 изменённых файлов: 188 добавлений и 35 удалений

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

@ -701,7 +701,7 @@ load 1474768.html
load 1478178.html
load 1483972.html
load 1486457.html
asserts(2-4) load 1488762-1.html # asserts from integer overflow & bogus sizes
asserts(1-4) load 1488762-1.html # asserts from integer overflow & bogus sizes
load 1489287.html
load 1489863.html
load 1489770.html
@ -798,5 +798,5 @@ pref(layout.css.aspect-ratio.enabled,true) load 1682032.html
pref(layout.css.aspect-ratio.enabled,true) load 1699263.html
pref(layout.css.aspect-ratio.enabled,true) load 1699468.html
load 1728319.html
asserts(4-8) load 1730506.html # asserts from integer overflow & bogus sizes
asserts(2-8) load 1730506.html # asserts from integer overflow & bogus sizes
asserts(1-4) load 1730570.html # asserts from integer overflow & bogus sizes

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

@ -351,6 +351,10 @@ struct MOZ_STACK_CLASS ScrollReflowInput {
// === Filled in by ReflowScrolledFrame ===
OverflowAreas mContentsOverflowAreas;
// The scrollbar gutter sizes used in the most recent reflow of
// mHelper.mScrolledFrame. The writing-mode is the same as the scroll
// container.
LogicalMargin mScrollbarGutterFromLastReflow;
// True if the most recent reflow of mHelper.mScrolledFrame is with the
// horizontal scrollbar.
bool mReflowedContentsWithHScrollbar = false;
@ -410,6 +414,8 @@ struct MOZ_STACK_CLASS ScrollReflowInput {
nsSize mVScrollbarPrefSize;
nsSize mHScrollbarMinSize;
nsSize mHScrollbarPrefSize;
// The scrollbar gutter sizes resolved from the scrollbar-gutter and
// scrollbar-width property.
nsMargin mScrollbarGutter;
};
@ -421,7 +427,8 @@ ScrollReflowInput::ScrollReflowInput(nsHTMLScrollFrame* aFrame,
mBoxState(aReflowInput.mFrame->PresContext(),
aReflowInput.mRenderingContext),
mComputedBorder(aReflowInput.ComputedPhysicalBorderPadding() -
aReflowInput.ComputedPhysicalPadding()) {
aReflowInput.ComputedPhysicalPadding()),
mScrollbarGutterFromLastReflow(aFrame->GetWritingMode()) {
ScrollStyles styles = aFrame->GetScrollStyles();
mHScrollbar = ShouldShowScrollbar(styles.mHorizontal);
mVScrollbar = ShouldShowScrollbar(styles.mVertical);
@ -546,23 +553,23 @@ bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
return false;
}
const bool assumeVScrollChanged =
aAssumeVScroll != aState.mReflowedContentsWithVScrollbar;
const bool assumeHScrollChanged =
aAssumeHScroll != aState.mReflowedContentsWithHScrollbar;
const bool isVertical = GetWritingMode().IsVertical();
const auto wm = GetWritingMode();
const nsMargin scrollbarGutter = aState.ScrollbarGutter(
aAssumeVScroll, aAssumeHScroll, IsScrollbarOnRight());
const LogicalMargin logicalScrollbarGutter(wm, scrollbarGutter);
const bool shouldReflowScolledFrame = [=]() {
if (isVertical) {
return assumeHScrollChanged ||
(assumeVScrollChanged && ScrolledContentDependsOnBSize(aState));
}
return assumeVScrollChanged ||
(assumeHScrollChanged && ScrolledContentDependsOnBSize(aState));
}();
const bool inlineEndsGutterChanged =
aState.mScrollbarGutterFromLastReflow.IStartEnd(wm) !=
logicalScrollbarGutter.IStartEnd(wm);
const bool blockEndsGutterChanged =
aState.mScrollbarGutterFromLastReflow.BStartEnd(wm) !=
logicalScrollbarGutter.BStartEnd(wm);
const bool shouldReflowScrolledFrame =
inlineEndsGutterChanged ||
(blockEndsGutterChanged && ScrolledContentDependsOnBSize(aState));
if (shouldReflowScolledFrame) {
if (isVertical ? assumeVScrollChanged : assumeHScrollChanged) {
if (shouldReflowScrolledFrame) {
if (blockEndsGutterChanged) {
nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(
mHelper.mScrolledFrame);
}
@ -573,8 +580,6 @@ bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
ReflowScrolledFrame(aState, aAssumeHScroll, aAssumeVScroll, aKidMetrics);
}
const nsMargin scrollbarGutter = aState.ScrollbarGutter(
aAssumeVScroll, aAssumeHScroll, IsScrollbarOnRight());
const nsSize scrollbarGutterSize(scrollbarGutter.LeftRight(),
scrollbarGutter.TopBottom());
@ -644,15 +649,17 @@ bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
ToString(scrollPortSize).c_str());
nscoord oneDevPixel = aState.mBoxState.PresContext()->DevPixelsToAppUnits(1);
bool showHScrollbar = aAssumeHScroll;
bool showVScrollbar = aAssumeVScroll;
if (!aForce) {
nsSize sizeToCompare = visualViewportSize;
if (gfxPlatform::UseDesktopZoomingScrollbars()) {
sizeToCompare = scrollPortSize;
}
// If the style is HIDDEN then we already know that aAssumeHScroll is false
// No need to compute showHScrollbar if we got ShowScrollbar::Never.
if (aState.mHScrollbar != ShowScrollbar::Never) {
bool wantHScrollbar =
showHScrollbar =
aState.mHScrollbar == ShowScrollbar::Always ||
scrolledRect.XMost() >= sizeToCompare.width + oneDevPixel ||
scrolledRect.x <= -oneDevPixel;
@ -660,18 +667,15 @@ bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
// in both axes, for consistency?
if (aState.mHScrollbar == ShowScrollbar::Auto &&
scrollPortSize.width < aState.HScrollbarMinWidth()) {
wantHScrollbar = false;
showHScrollbar = false;
}
ROOT_SCROLLBAR_LOG("TryLayout wants H Scrollbar: %d =? %d\n",
wantHScrollbar, aAssumeHScroll);
if (wantHScrollbar != aAssumeHScroll) {
return false;
}
showHScrollbar, aAssumeHScroll);
}
// If the style is HIDDEN then we already know that aAssumeVScroll is false
// No need to compute showVScrollbar if we got ShowScrollbar::Never.
if (aState.mVScrollbar != ShowScrollbar::Never) {
bool wantVScrollbar =
showVScrollbar =
aState.mVScrollbar == ShowScrollbar::Always ||
scrolledRect.YMost() >= sizeToCompare.height + oneDevPixel ||
scrolledRect.y <= -oneDevPixel;
@ -679,18 +683,27 @@ bool nsHTMLScrollFrame::TryLayout(ScrollReflowInput& aState,
// in both axes, for consistency?
if (aState.mVScrollbar == ShowScrollbar::Auto &&
scrollPortSize.height < aState.VScrollbarMinHeight()) {
wantVScrollbar = false;
showVScrollbar = false;
}
ROOT_SCROLLBAR_LOG("TryLayout wants V Scrollbar: %d =? %d\n",
wantVScrollbar, aAssumeVScroll);
if (wantVScrollbar != aAssumeVScroll) {
showVScrollbar, aAssumeVScroll);
}
if (showHScrollbar != aAssumeHScroll || showVScrollbar != aAssumeVScroll) {
const nsMargin wantedScrollbarGutter = aState.ScrollbarGutter(
showVScrollbar, showHScrollbar, IsScrollbarOnRight());
// We report an inconsistent layout only when the desired visibility of
// the scrollbars can change the size of the scrollbar gutters.
if (scrollbarGutter != wantedScrollbarGutter) {
return false;
}
}
}
aState.mShowHScrollbar = aAssumeHScroll;
aState.mShowVScrollbar = aAssumeVScroll;
// If we reach here, the layout is consistent. Record the desired visibility
// of the scrollbars.
aState.mShowHScrollbar = showHScrollbar;
aState.mShowVScrollbar = showVScrollbar;
const nsPoint scrollPortOrigin(
aState.mComputedBorder.left + scrollbarGutter.left,
aState.mComputedBorder.top + scrollbarGutter.top);
@ -752,7 +765,7 @@ void nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput& aState,
bool aAssumeHScroll,
bool aAssumeVScroll,
ReflowOutput* aMetrics) {
WritingMode wm = mHelper.mScrolledFrame->GetWritingMode();
const WritingMode wm = GetWritingMode();
// these could be NS_UNCONSTRAINEDSIZE ... std::min arithmetic should
// be OK
@ -884,6 +897,7 @@ void nsHTMLScrollFrame::ReflowScrolledFrame(ScrollReflowInput& aState,
}
aState.mContentsOverflowAreas = aMetrics->mOverflowAreas;
aState.mScrollbarGutterFromLastReflow = scrollbarGutter;
aState.mReflowedContentsWithHScrollbar = aAssumeHScroll;
aState.mReflowedContentsWithVScrollbar = aAssumeVScroll;
}

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

@ -0,0 +1,24 @@
[scrollbar-gutter-reflow-counts-001.html]
[Enlarge the child's block-size to 200%]
expected:
if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
[Enlarge the child's block-size to 300px]
expected:
if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
[Enlarge the child's block-size to 200% in a vertical-lr scroll container]
expected:
if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
[Enlarge the child's block-size to 300px in a vertical-lr scroll container]
expected:
if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
[Enlarge the child's block-size to 200% in a vertical-rl scroll container]
expected:
if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.
[Enlarge the child's block-size to 300px in a vertical-rl scroll container]
expected:
if os == "android": FAIL # Overlay scrollbars do not create scrollbar gutters.

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

@ -0,0 +1,115 @@
<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>CSS Overflow: Test scrollbar-gutter reflow counts</title>
<link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1746098">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#target {
inline-size: 200px;
block-size: 100px;
background: lightgray;
overflow: auto;
}
#targetChild {
inline-size: 100%;
block-size: 100%;
background: orange;
}
</style>
<p>Here is a scroll contaier for testing:</p>
<div id="target">
<div id="targetChild"></div>
</div>
<script>
let gUtils = SpecialPowers.getDOMWindowUtils(window);
let gTarget = document.getElementById("target");
let gTargetChild = document.getElementById("targetChild");
function getReflowCount()
{
document.documentElement.offsetHeight; // flush layout
return gUtils.framesReflowed;
}
function cleanUp() {
gTarget.style.writingMode = "";
gTarget.style.scrollbarGutter = "";
gTargetChild.style.blockSize = "";
}
function tweakStyleAndCountReflows(aAddStyle, aAddScrollbarGutter)
{
let beforeCount = getReflowCount();
if (aAddScrollbarGutter) {
gTarget.style.scrollbarGutter = "stable";
}
aAddStyle();
let afterCount = getReflowCount();
cleanUp();
let numReflows = afterCount - beforeCount;
assert_greater_than(numReflows, 0, "We should've reflowed *something* after changing styles:");
return numReflows;
}
let gTestCases = [
{
name : "Enlarge the child's block-size to 200%",
addStyle : function () {
gTargetChild.style.blockSize = "200%";
},
},
{
name : "Enlarge the child's block-size to 300px",
addStyle : function () {
gTargetChild.style.blockSize = "300px";
},
},
{
name : "Enlarge the child's block-size to 200% in a vertical-lr scroll container",
addStyle : function () {
gTarget.style.writingMode = "vertical-lr";
gTargetChild.style.blockSize = "200%";
},
},
{
name : "Enlarge the child's block-size to 300px in a vertical-lr scroll container",
addStyle : function () {
gTarget.style.writingMode = "vertical-lr";
gTargetChild.style.blockSize = "300px";
},
},
{
name : "Enlarge the child's block-size to 200% in a vertical-rl scroll container",
addStyle : function () {
gTarget.style.writingMode = "vertical-rl";
gTargetChild.style.blockSize = "200%";
},
},
{
name : "Enlarge the child's block-size to 300px in a vertical-rl scroll container",
addStyle : function () {
gTarget.style.writingMode = "vertical-rl";
gTargetChild.style.blockSize = "300px";
},
},
];
for (let testcase of gTestCases) {
test(function () {
let numTestReflows = tweakStyleAndCountReflows(testcase.addStyle, true);
let numReferenceReflows = tweakStyleAndCountReflows(testcase.addStyle, false);
assert_less_than(numTestReflows, numReferenceReflows,
"A scroll container with 'scrollbar-gutter:stable' should have less reflow counts:");
}, testcase.name)
}
</script>
</html>