Bug 1906475 - Improve scroll{Width,Height} implementation for overflow: visible frames. r=dholbert

Differential Revision: https://phabricator.services.mozilla.com/D218782
This commit is contained in:
Emilio Cobos Álvarez
2024-08-15 15:02:00 +00:00
parent c90d4837db
commit a012cd6770
16 changed files with 292 additions and 128 deletions

View File

@@ -15,8 +15,6 @@
#include <inttypes.h>
#include <initializer_list>
#include <new>
#include "DOMIntersectionObserver.h"
#include "DOMMatrix.h"
#include "ExpandedPrincipal.h"
#include "PresShellInlines.h"
@@ -929,24 +927,34 @@ int32_t Element::ScrollLeftMax() {
static nsSize GetScrollRectSizeForOverflowVisibleFrame(nsIFrame* aFrame) {
if (!aFrame || aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
return nsSize(0, 0);
return nsSize();
}
nsRect paddingRect = aFrame->GetPaddingRectRelativeToSelf();
// This matches WebKit and Blink, which in turn (apparently, according to
// their source) matched old IE.
const nsRect paddingRect = aFrame->GetPaddingRectRelativeToSelf();
const nsRect overflowRect = [&] {
OverflowAreas overflowAreas(paddingRect, paddingRect);
// Add the scrollable overflow areas of children (if any) to the paddingRect.
// It's important to start with the paddingRect, otherwise if there are no
// children the overflow rect will be 0,0,0,0 which will force the point 0,0
// to be included in the final rect.
nsLayoutUtils::UnionChildOverflow(aFrame, overflowAreas);
// Add the scrollable overflow areas of children (if any) to the
// paddingRect, as if aFrame was a scrolled frame. It's important to start
// with the paddingRect, otherwise if there are no children the overflow
// rect will be 0,0,0,0 which will force the point 0,0 to be included in the
// final rect.
aFrame->UnionChildOverflow(overflowAreas, /* aAsIfScrolled = */ true);
// Make sure that an empty padding-rect's edges are included, by adding
// the padding-rect in again with UnionEdges.
nsRect overflowRect =
overflowAreas.ScrollableOverflow().UnionEdges(paddingRect);
return nsLayoutUtils::GetScrolledRect(aFrame, overflowRect,
paddingRect.Size(),
aFrame->StyleVisibility()->mDirection)
.Size();
return overflowAreas.ScrollableOverflow().UnionEdges(paddingRect);
}();
auto directions =
ScrollContainerFrame::ComputePerAxisScrollDirections(aFrame);
const nscoord height = directions.mToBottom
? overflowRect.YMost() - paddingRect.Y()
: paddingRect.YMost() - overflowRect.Y();
const nscoord width = directions.mToRight
? overflowRect.XMost() - paddingRect.X()
: paddingRect.XMost() - overflowRect.X();
return nsSize(width, height);
}
nsSize Element::GetScrollSize() {

View File

@@ -1378,94 +1378,6 @@ nsIFrame* nsLayoutUtils::GetNearestOverflowClipFrame(nsIFrame* aFrame) {
});
}
// static
nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
const nsRect& aScrolledFrameOverflowArea,
const nsSize& aScrollPortSize,
StyleDirection aDirection) {
WritingMode wm = aScrolledFrame->GetWritingMode();
// Potentially override the frame's direction to use the direction found
// by ScrollContainerFrame::GetScrolledFrameDir()
wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl
? mozilla::intl::BidiEmbeddingLevel::RTL()
: mozilla::intl::BidiEmbeddingLevel::LTR());
nscoord x1 = aScrolledFrameOverflowArea.x,
x2 = aScrolledFrameOverflowArea.XMost(),
y1 = aScrolledFrameOverflowArea.y,
y2 = aScrolledFrameOverflowArea.YMost();
const bool isHorizontalWM = !wm.IsVertical();
const bool isVerticalWM = wm.IsVertical();
bool isInlineFlowFromTopOrLeft = !wm.IsInlineReversed();
bool isBlockFlowFromTopOrLeft = isHorizontalWM || wm.IsVerticalLR();
if (aScrolledFrame->IsFlexContainerFrame()) {
// In a flex container, the children flow (and overflow) along the flex
// container's main axis and cross axis. These are analogous to the
// inline/block axes, and by default they correspond exactly to those axes;
// but the flex container's CSS (e.g. flex-direction: column-reverse) may
// have swapped and/or reversed them, and we need to account for that here.
FlexboxAxisInfo info(aScrolledFrame);
if (info.mIsRowOriented) {
// The flex container's inline axis is the main axis.
isInlineFlowFromTopOrLeft =
isInlineFlowFromTopOrLeft == !info.mIsMainAxisReversed;
isBlockFlowFromTopOrLeft =
isBlockFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
} else {
// The flex container's block axis is the main axis.
isBlockFlowFromTopOrLeft =
isBlockFlowFromTopOrLeft == !info.mIsMainAxisReversed;
isInlineFlowFromTopOrLeft =
isInlineFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
}
}
// Clamp the horizontal start-edge (x1 or x2, depending whether the logical
// axis that corresponds to horizontal progresses from left-to-right or
// right-to-left).
if ((isHorizontalWM && isInlineFlowFromTopOrLeft) ||
(isVerticalWM && isBlockFlowFromTopOrLeft)) {
if (x1 < 0) {
x1 = 0;
}
} else {
if (x2 > aScrollPortSize.width) {
x2 = aScrollPortSize.width;
}
// When the scrolled frame chooses a size larger than its available width
// (because its padding alone is larger than the available width), we need
// to keep the start-edge of the scroll frame anchored to the start-edge of
// the scrollport.
// When the scrolled frame is RTL, this means moving it in our left-based
// coordinate system, so we need to compensate for its extra width here by
// effectively repositioning the frame.
nscoord extraWidth =
std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width);
x2 += extraWidth;
}
// Similarly, clamp the vertical start-edge (y1 or y2, depending whether the
// logical axis that corresponds to vertical progresses from top-to-bottom or
// buttom-to-top).
if ((isHorizontalWM && isBlockFlowFromTopOrLeft) ||
(isVerticalWM && isInlineFlowFromTopOrLeft)) {
if (y1 < 0) {
y1 = 0;
}
} else {
if (y2 > aScrollPortSize.height) {
y2 = aScrollPortSize.height;
}
nscoord extraHeight =
std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height);
y2 += extraHeight;
}
return nsRect(x1, y1, x2 - x1, y2 - y1);
}
// static
bool nsLayoutUtils::HasPseudoStyle(nsIContent* aContent,
ComputedStyle* aComputedStyle,

View File

@@ -25,6 +25,7 @@
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/gfx/gfxVars.h"
#include "nsFontMetrics.h"
#include "nsFlexContainerFrame.h"
#include "mozilla/dom/NodeInfo.h"
#include "nsScrollbarFrame.h"
#include "nsINode.h"
@@ -6893,24 +6894,107 @@ nsRect ScrollContainerFrame::GetScrolledRect() const {
}
StyleDirection ScrollContainerFrame::GetScrolledFrameDir() const {
return GetScrolledFrameDir(mScrolledFrame);
}
StyleDirection ScrollContainerFrame::GetScrolledFrameDir(
const nsIFrame* aScrolledFrame) {
// If the scrolled frame has unicode-bidi: plaintext, the paragraph
// direction set by the text content overrides the direction of the frame
if (mScrolledFrame->StyleTextReset()->mUnicodeBidi ==
if (aScrolledFrame->StyleTextReset()->mUnicodeBidi ==
StyleUnicodeBidi::Plaintext) {
if (nsIFrame* child = mScrolledFrame->PrincipalChildList().FirstChild()) {
if (nsIFrame* child = aScrolledFrame->PrincipalChildList().FirstChild()) {
return nsBidiPresUtils::ParagraphDirection(child) ==
mozilla::intl::BidiDirection::LTR
intl::BidiDirection::LTR
? StyleDirection::Ltr
: StyleDirection::Rtl;
}
}
return IsBidiLTR() ? StyleDirection::Ltr : StyleDirection::Rtl;
return aScrolledFrame->GetWritingMode().IsBidiLTR() ? StyleDirection::Ltr
: StyleDirection::Rtl;
}
auto ScrollContainerFrame::ComputePerAxisScrollDirections(
const nsIFrame* aScrolledFrame) -> PerAxisScrollDirections {
auto wm = aScrolledFrame->GetWritingMode();
auto dir = GetScrolledFrameDir(aScrolledFrame);
wm.SetDirectionFromBidiLevel(dir == StyleDirection::Rtl
? intl::BidiEmbeddingLevel::RTL()
: intl::BidiEmbeddingLevel::LTR());
bool scrollToRight = wm.IsPhysicalLTR();
bool scrollToBottom =
!wm.IsVertical() || wm.GetInlineDir() == WritingMode::InlineDir::TTB;
if (aScrolledFrame->IsFlexContainerFrame()) {
// In a flex container, the children flow (and overflow) along the flex
// container's main axis and cross axis. These are analogous to the
// inline/block axes, and by default they correspond exactly to those axes;
// but the flex container's CSS (e.g. flex-direction: column-reverse) may
// have swapped and/or reversed them, and we need to account for that here.
const FlexboxAxisInfo info(aScrolledFrame);
const bool isMainAxisVertical = info.mIsRowOriented == wm.IsVertical();
if (info.mIsMainAxisReversed) {
if (isMainAxisVertical) {
scrollToBottom = !scrollToBottom;
} else {
scrollToRight = !scrollToRight;
}
}
if (info.mIsCrossAxisReversed) {
if (isMainAxisVertical) {
scrollToRight = !scrollToRight;
} else {
scrollToBottom = !scrollToBottom;
}
}
}
return {scrollToRight, scrollToBottom};
}
nsRect ScrollContainerFrame::GetUnsnappedScrolledRectInternal(
const nsRect& aScrolledOverflowArea, const nsSize& aScrollPortSize) const {
return nsLayoutUtils::GetScrolledRect(mScrolledFrame, aScrolledOverflowArea,
aScrollPortSize, GetScrolledFrameDir());
nscoord x1 = aScrolledOverflowArea.x, x2 = aScrolledOverflowArea.XMost(),
y1 = aScrolledOverflowArea.y, y2 = aScrolledOverflowArea.YMost();
auto dirs = ComputePerAxisScrollDirections(mScrolledFrame);
// Clamp the horizontal start-edge (x1 or x2, depending whether the logical
// axis that corresponds to horizontal progresses from left-to-right or
// right-to-left).
if (dirs.mToRight) {
if (x1 < 0) {
x1 = 0;
}
} else {
if (x2 > aScrollPortSize.width) {
x2 = aScrollPortSize.width;
}
// When the scrolled frame chooses a size larger than its available width
// (because its padding alone is larger than the available width), we need
// to keep the start-edge of the scroll frame anchored to the start-edge of
// the scrollport.
// When the scrolled frame is RTL, this means moving it in our left-based
// coordinate system, so we need to compensate for its extra width here by
// effectively repositioning the frame.
nscoord extraWidth =
std::max(0, mScrolledFrame->GetSize().width - aScrollPortSize.width);
x2 += extraWidth;
}
// Similarly, clamp the vertical start-edge (y1 or y2, depending whether the
// logical axis that corresponds to vertical progresses from top-to-bottom or
// buttom-to-top).
if (dirs.mToBottom) {
if (y1 < 0) {
y1 = 0;
}
} else {
if (y2 > aScrollPortSize.height) {
y2 = aScrollPortSize.height;
}
nscoord extraHeight =
std::max(0, mScrolledFrame->GetSize().height - aScrollPortSize.height);
y2 += extraHeight;
}
return nsRect(x1, y1, x2 - x1, y2 - y1);
}
nsMargin ScrollContainerFrame::GetActualScrollbarSizes(

View File

@@ -195,6 +195,14 @@ class ScrollContainerFrame : public nsContainerFrame,
return GetCurrentAnonymousContent().contains(GetNeededAnonymousContent());
}
struct PerAxisScrollDirections {
bool mToRight = false;
bool mToBottom = false;
};
static PerAxisScrollDirections ComputePerAxisScrollDirections(
const nsIFrame* aScrolledFrame);
/**
* Get the overscroll-behavior styles.
*/
@@ -1240,6 +1248,7 @@ class ScrollContainerFrame : public nsContainerFrame,
bool HasPerspective() const { return ChildrenHavePerspective(); }
bool HasBgAttachmentLocal() const;
StyleDirection GetScrolledFrameDir() const;
static StyleDirection GetScrolledFrameDir(const nsIFrame*);
// Ask APZ to smooth scroll to |aDestination|.
// This method does not clamp the destination; callers should clamp it to

View File

@@ -2538,7 +2538,8 @@ void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
#endif
}
void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas,
bool aAsIfScrolled) {
// We need to update the overflow areas of lines manually, as they
// get cached and re-used otherwise. Lines aren't exposed as normal
// frame children, so calling UnionChildOverflow alone will end up

View File

@@ -630,7 +630,7 @@ class nsBlockFrame : public nsContainerFrame {
bool ComputeCustomOverflow(mozilla::OverflowAreas&) override;
void UnionChildOverflow(mozilla::OverflowAreas&) override;
void UnionChildOverflow(mozilla::OverflowAreas&, bool aAsIfScrolled) override;
/**
* Load all of aFrame's floats into the float manager iff aFrame is not a

View File

@@ -4864,7 +4864,7 @@ Maybe<nscoord> nsFlexContainerFrame::GetNaturalBaselineBOffset(
}
void nsFlexContainerFrame::UnionInFlowChildOverflow(
OverflowAreas& aOverflowAreas) {
OverflowAreas& aOverflowAreas, bool aAsIfScrolled) {
// The CSS Overflow spec [1] requires that a scrollable container's
// scrollable overflow should include the following areas.
//
@@ -4882,6 +4882,7 @@ void nsFlexContainerFrame::UnionInFlowChildOverflow(
//
// [1] https://drafts.csswg.org/css-overflow-3/#scrollable.
const bool isScrolledContent =
aAsIfScrolled ||
Style()->GetPseudoType() == PseudoStyleType::scrolledContent;
bool anyScrolledContentItem = false;
// Union of normal-positioned margin boxes for all the items.
@@ -4931,8 +4932,9 @@ void nsFlexContainerFrame::UnionInFlowChildOverflow(
}
}
void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
UnionInFlowChildOverflow(aOverflowAreas);
void nsFlexContainerFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas,
bool aAsIfScrolled) {
UnionInFlowChildOverflow(aOverflowAreas, aAsIfScrolled);
// Union with child frames, skipping the principal list since we already
// handled those above.
nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas,

View File

@@ -159,10 +159,11 @@ class nsFlexContainerFrame final : public nsContainerFrame,
BaselineExportContext) const override;
// Unions the child overflow from our in-flow children.
void UnionInFlowChildOverflow(mozilla::OverflowAreas&);
void UnionInFlowChildOverflow(mozilla::OverflowAreas&,
bool aAsIfScrolled = false);
// Unions the child overflow from all our children, including out of flows.
void UnionChildOverflow(mozilla::OverflowAreas&) final;
void UnionChildOverflow(mozilla::OverflowAreas&, bool aAsIfScrolled) final;
// nsContainerFrame overrides
bool DrainSelfOverflowList() override;

View File

@@ -488,8 +488,8 @@ void nsHTMLCanvasFrame::AppendDirectlyOwnedAnonBoxes(
aResult.AppendElement(OwnedAnonBox(mFrames.FirstChild()));
}
void nsHTMLCanvasFrame::UnionChildOverflow(
mozilla::OverflowAreas& aOverflowAreas) {
void nsHTMLCanvasFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas,
bool) {
// Our one child (the canvas content anon box) is unpainted and isn't relevant
// for child-overflow purposes. So we need to provide our own trivial impl to
// avoid receiving the child-considering impl that we would otherwise inherit.

View File

@@ -57,7 +57,7 @@ class nsHTMLCanvasFrame final : public nsContainerFrame {
virtual mozilla::IntrinsicSize GetIntrinsicSize() override;
mozilla::AspectRatio GetIntrinsicRatio() const override;
void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas) override;
void UnionChildOverflow(mozilla::OverflowAreas&, bool aAsIfScrolled) override;
SizeComputationResult ComputeSize(
gfxContext* aRenderingContext, mozilla::WritingMode aWM,

View File

@@ -8045,8 +8045,9 @@ bool nsIFrame::DoesClipChildrenInBothAxes() const {
}
/* virtual */
void nsIFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
if (!DoesClipChildrenInBothAxes()) {
void nsIFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas,
bool aAsIfScrolled) {
if (aAsIfScrolled || !DoesClipChildrenInBothAxes()) {
nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas);
}
}

View File

@@ -3071,9 +3071,11 @@ class nsIFrame : public nsQueryFrame {
/**
* Computes any overflow area created by children of this frame and
* includes it into aOverflowAreas.
* includes it into aOverflowAreas. If aAsIfScrolled is true, then it behaves
* as if we were the scrolled content frame.
*/
virtual void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas);
virtual void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas,
bool aAsIfScrolled = false);
// Returns the applicable overflow-clip-margin values.
nsSize OverflowClipMargin(mozilla::PhysicalAxes aClipAxes) const;

View File

@@ -466,7 +466,8 @@ void SVGOuterSVGFrame::DidReflow(nsPresContext* aPresContext,
}
/* virtual */
void SVGOuterSVGFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas) {
void SVGOuterSVGFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas,
bool aAsIfScrolled) {
// See the comments in Reflow above.
// WARNING!! Keep this in sync with Reflow above!

View File

@@ -65,7 +65,8 @@ class SVGOuterSVGFrame final : public SVGDisplayContainerFrame,
void DidReflow(nsPresContext* aPresContext,
const ReflowInput* aReflowInput) override;
void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas) override;
void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas,
bool aAsIfScrolled) override;
void BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists) override;

View File

@@ -0,0 +1,42 @@
<!doctype html>
<meta charset="utf-8">
<title>scroll{Width,Height} with visible overflow and negative margins.</title>
<link rel="help" href="https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface">
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1906475">
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<link rel="author" title="Mozilla" href="https://mozilla.org">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<style>
body { margin: 0 }
.wrapper {
width: 90px;
border: 10px solid #d1d1d2;
}
.inner {
margin: -10px;
height: 100px;
width: 100px;
background-color: blue;
}
</style>
<div class="wrapper" style="overflow: visible">
<div class="inner"></div>
</div>
<div class="wrapper" style="overflow: hidden">
<div class="inner"></div>
</div>
<div class="wrapper" style="overflow: auto">
<div class="inner"></div>
</div>
<div class="wrapper" style="overflow: clip">
<div class="inner"></div>
</div>
<script>
for (let wrapper of document.querySelectorAll(".wrapper")) {
test(function() {
assert_equals(wrapper.scrollWidth, 90, "scrollWidth");
assert_equals(wrapper.scrollHeight, 90, "scrollHeight");
}, "scrollWidth/Height with negative margins: " + wrapper.style.cssText);
}
</script>

View File

@@ -0,0 +1,100 @@
<!doctype html>
<meta charset="utf-8">
<title>scroll{Width,Height} with visible overflow and negative margins.</title>
<link rel="help" href="https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface">
<link rel="help" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1906475">
<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
<link rel="author" title="Mozilla" href="https://mozilla.org">
<script src=/resources/testharness.js></script>
<script src=/resources/testharnessreport.js></script>
<style>
body { margin: 0 }
.wrapper {
width: 80px;
height: 80px;
border: 1px solid #d1d1d2;
padding: 1px 4px 8px 16px;
border-width: 1px 2px 3px 4px;
border-right-width: 50px;
border-bottom-width: 40px;
}
.inner {
margin: -100px;
height: 300px;
width: 300px;
background-color: blue;
}
</style>
<div class="wrapper">
<div class="inner"></div>
</div>
<script>
const wrapper = document.querySelector(".wrapper");
const contentBox = {
width: 80,
height: 80,
};
const paddingBox = {
width: contentBox.width + 4 + 16,
height: contentBox.height + 1 + 8,
};
for (let display of ["block", "flex", "grid"]) {
for (let flexDirection of ["row", "row-reverse", "column", "column-reverse"]) {
if (flexDirection != "row" && display != "flex") {
// Don't bother retesting with all flexDirection values unless we're actually a flex container
continue;
}
for (let flexWrap of ["", "wrap-reverse"]) {
if (flexWrap != "" && display != "flex") {
// Don't bother retesting with all flexWrap values unless we're actually a flex container
continue;
}
for (let direction of ["ltr", "rtl"]) {
for (let writingMode of ["horizontal-tb", "vertical-lr", "vertical-rl"]) {
for (let overflow of ["visible", "hidden", "auto", "clip", "scroll"]) {
wrapper.style.display = display;
wrapper.style.overflow = overflow;
wrapper.style.direction = direction;
wrapper.style.writingMode = writingMode;
wrapper.style.flexDirection = flexDirection;
wrapper.style.flexWrap = flexWrap;
// Suppress scrollbars because they get added to the padding are
// and would need to account for them in flexbox.
wrapper.style.scrollbarWidth = display == "flex" ? "none" : "";
let vertical = writingMode.startsWith("vertical");
let scrollToTop = vertical && direction == "rtl";
let scrollToLeft = (!vertical && direction == "rtl") || writingMode == "vertical-rl";
let flexMainAxisIsVertical = flexDirection.startsWith("row") == vertical;
if (display == "flex") {
if (flexDirection.endsWith("-reverse")) {
if (flexMainAxisIsVertical) {
scrollToTop = !scrollToTop;
} else {
scrollToLeft = !scrollToLeft;
}
}
if (flexWrap == "wrap-reverse") {
if (flexMainAxisIsVertical) {
scrollToLeft = !scrollToLeft;
} else {
scrollToTop = !scrollToTop;
}
}
}
test(function() {
assert_greater_than_equal(wrapper.scrollWidth, paddingBox.width, "scrollWidth should be at least padding box width");
let padding = display == "flex" && !flexMainAxisIsVertical ? paddingBox.width - contentBox.width : 0;
assert_equals(wrapper.scrollWidth, (scrollToLeft ? 204 : 216) - padding, "scrollWidth");
}, "scrollWidth with negative margins: " + wrapper.style.cssText);
test(function() {
assert_greater_than_equal(wrapper.scrollHeight, paddingBox.height, "scrollHeight should be at least padding box height");
let padding = display == "flex" && flexMainAxisIsVertical ? paddingBox.width - contentBox.width : 0;
assert_equals(wrapper.scrollHeight, (scrollToTop ? 208 : 201) - padding, "scrollHeight");
}, "scrollHeight with negative margins: " + wrapper.style.cssText);
}
}
}
}
}
}
</script>