Bug 1198135 - Part 2: Compute the scrolled rect stored by ScrollFrameHelper as what will actually be scrollable. r=dbaron

This commit is contained in:
Matt Woodrow 2016-08-25 11:15:46 +12:00
parent c38bfdabb3
commit bdbef762ca
4 changed files with 237 additions and 2 deletions

View File

@ -727,13 +727,20 @@ nsHTMLScrollFrame::ReflowContents(ScrollReflowInput* aState,
}
void
nsHTMLScrollFrame::PlaceScrollArea(const ScrollReflowInput& aState,
nsHTMLScrollFrame::PlaceScrollArea(ScrollReflowInput& aState,
const nsPoint& aScrollPosition)
{
nsIFrame *scrolledFrame = mHelper.mScrolledFrame;
// Set the x,y of the scrolled frame to the correct value
scrolledFrame->SetPosition(mHelper.mScrollPort.TopLeft() - aScrollPosition);
// Recompute our scrollable overflow, taking perspective children into
// account. Note that this only recomputes the overflow areas stored on the
// helper (which are used to compute scrollable length and scrollbar thumb
// sizes) but not the overflow areas stored on the frame. This seems to work
// for now, but it's possible that we may need to update both in the future.
AdjustForPerspective(aState.mContentsOverflowAreas.ScrollableOverflow());
nsRect scrolledArea;
// Preserve the width or height of empty rects
nsSize portSize = mHelper.mScrollPort.Size();
@ -836,6 +843,140 @@ GetBrowserRoot(nsIContent* aContent)
return nullptr;
}
// When we have perspective set on the outer scroll frame, and transformed
// children (possibly with preserve-3d) then the effective transform on the
// child depends on the offset to the scroll frame, which changes as we scroll.
// This perspective transform can cause the element to move relative to the
// scrolled inner frame, which would cause the scrollable length changes during
// scrolling if we didn't account for it. Since we don't want scrollHeight/Width
// and the size of scrollbar thumbs to change during scrolling, we compute the
// scrollable overflow by determining the scroll position at which the child
// becomes completely visible within the scrollport rather than using the union
// of the overflow areas at their current position.
void
GetScrollableOverflowForPerspective(nsIFrame* aScrolledFrame,
nsIFrame* aCurrentFrame,
const nsRect aScrollPort,
nsPoint aOffset,
nsRect& aScrolledFrameOverflowArea)
{
// Iterate over all children except pop-ups.
FrameChildListIDs skip = nsIFrame::kSelectPopupList | nsIFrame::kPopupList;
for (nsIFrame::ChildListIterator childLists(aCurrentFrame);
!childLists.IsDone(); childLists.Next()) {
if (skip.Contains(childLists.CurrentID())) {
continue;
}
for (nsIFrame* child : childLists.CurrentList()) {
nsPoint offset = aOffset;
// When we reach a direct child of the scroll, then we record the offset
// to convert from that frame's coordinate into the scroll frame's
// coordinates. Preserve-3d descendant frames use the same offset as their
// ancestors, since TransformRect already converts us into the coordinate
// space of the preserve-3d root.
if (aScrolledFrame == aCurrentFrame) {
offset = child->GetPosition();
}
if (child->Extend3DContext()) {
// If we're a preserve-3d frame, then recurse and include our
// descendants since overflow of preserve-3d frames is only included
// in the post-transform overflow area of the preserve-3d root frame.
GetScrollableOverflowForPerspective(aScrolledFrame, child, aScrollPort,
offset, aScrolledFrameOverflowArea);
}
// If we're transformed, then we want to consider the possibility that
// this frame might move relative to the scrolled frame when scrolling.
// For preserve-3d, leaf frames have correct overflow rects relative to
// themselves. preserve-3d 'nodes' (intermediate frames and the root) have
// only their untransformed children included in their overflow relative
// to self, which is what we want to include here.
if (child->IsTransformed()) {
// Compute the overflow rect for this leaf transform frame in the
// coordinate space of the scrolled frame.
nsPoint scrollPos = aScrolledFrame->GetPosition();
nsRect preScroll = nsDisplayTransform::TransformRect(
child->GetScrollableOverflowRectRelativeToSelf(), child);
// Temporarily override the scroll position of the scrolled frame by
// 10 CSS pixels, and then recompute what the overflow rect would be.
// This scroll position may not be valid, but that shouldn't matter
// for our calculations.
aScrolledFrame->SetPosition(scrollPos + nsPoint(600, 600));
nsRect postScroll = nsDisplayTransform::TransformRect(
child->GetScrollableOverflowRectRelativeToSelf(), child);
aScrolledFrame->SetPosition(scrollPos);
// Compute how many app units the overflow rects moves by when we adjust
// the scroll position by 1 app unit.
double rightDelta =
(postScroll.XMost() - preScroll.XMost() + 600.0) / 600.0;
double bottomDelta =
(postScroll.YMost() - preScroll.YMost() + 600.0) / 600.0;
// We can't ever have negative scrolling.
NS_ASSERTION(rightDelta > 0.0f && bottomDelta > 0.0f,
"Scrolling can't be reversed!");
// Move preScroll into the coordinate space of the scrollport.
preScroll += offset + scrollPos;
// For each of the four edges of preScroll, figure out how far they
// extend beyond the scrollport. Ignore negative values since that means
// that side is already scrolled in to view and we don't need to add
// overflow to account for it.
nsMargin overhang(std::max(0, aScrollPort.Y() - preScroll.Y()),
std::max(0, preScroll.XMost() - aScrollPort.XMost()),
std::max(0, preScroll.YMost() - aScrollPort.YMost()),
std::max(0, aScrollPort.X() - preScroll.X()));
// Scale according to rightDelta/bottomDelta to adjust for the different
// scroll rates.
overhang.top /= bottomDelta;
overhang.right /= rightDelta;
overhang.bottom /= bottomDelta;
overhang.left /= rightDelta;
// Take the minimum overflow rect that would allow the current scroll
// position, using the size of the scroll port and offset by the
// inverse of the scroll position.
nsRect overflow(0, 0, aScrollPort.width, aScrollPort.height);
// Expand it by our margins to get an overflow rect that would allow all
// edges of our transformed content to be scrolled into view.
overflow.Inflate(overhang);
// Merge it with the combined overflow
aScrolledFrameOverflowArea.UnionRect(aScrolledFrameOverflowArea,
overflow);
} else if (aCurrentFrame == aScrolledFrame) {
aScrolledFrameOverflowArea.UnionRect(
aScrolledFrameOverflowArea,
child->GetScrollableOverflowRectRelativeToParent());
}
}
}
}
void
nsHTMLScrollFrame::AdjustForPerspective(nsRect& aScrollableOverflow)
{
// If we have perspective that is being applied to our children, then
// the effective transform on the child depends on the relative position
// of the child to us and changes during scrolling.
if (!ChildrenHavePerspective()) {
return;
}
aScrollableOverflow.SetEmpty();
GetScrollableOverflowForPerspective(mHelper.mScrolledFrame,
mHelper.mScrolledFrame,
mHelper.mScrollPort,
nsPoint(), aScrollableOverflow);
}
void
nsHTMLScrollFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
@ -4712,6 +4853,10 @@ nsXULScrollFrame::LayoutScrollArea(nsBoxLayoutState& aState,
if (minSize.width > childRect.width)
childRect.width = minSize.width;
// TODO: Handle transformed children that inherit perspective
// from this frame. See AdjustForPerspective for how we handle
// this for HTML scroll frames.
aState.SetLayoutFlags(flags);
ClampAndSetBounds(aState, childRect, aScrollPosition);
mHelper.mScrolledFrame->XULLayout(aState);

View File

@ -687,7 +687,7 @@ public:
bool aFirstPass);
void ReflowContents(ScrollReflowInput* aState,
const ReflowOutput& aDesiredSize);
void PlaceScrollArea(const ScrollReflowInput& aState,
void PlaceScrollArea(ScrollReflowInput& aState,
const nsPoint& aScrollPosition);
nscoord GetIntrinsicVScrollbarWidth(nsRenderingContext *aRenderingContext);
@ -710,6 +710,11 @@ public:
return mHelper.ComputeCustomOverflow(aOverflowAreas);
}
// Recomputes the scrollable overflow area we store in the helper to take children
// that are affected by perpsective set on the outer frame and scroll at different
// rates.
void AdjustForPerspective(nsRect& aScrollableOverflow);
// Called to set the child frames. We typically have three: the scroll area,
// the vertical scrollbar, and the horizontal scrollbar.
virtual void SetInitialChildList(ChildListID aListID,

View File

@ -101,6 +101,7 @@ skip-if = buildapp == 'b2g' # b2g(Target should not have scrolled - got 114.1000
[test_bug970363.html]
[test_bug1062406.html]
[test_bug1174521.html]
[test_bug1198135.html]
[test_contained_plugin_transplant.html]
skip-if = os=='win'
[test_image_selection.html]

View File

@ -0,0 +1,84 @@
<!doctype html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1198135
-->
<html><head>
<title>Test for Bug 1198135</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1198135">Mozilla Bug 1198135</a>
<style>
.example {
perspective: 100px;
height: 300px;
width: 300px;
overflow-x: hidden;
overflow-y: auto;
border: 1px solid blue;
}
.example__group {
position: relative;
transform-style: preserve-3d;
height: 300px;
top: 0;
}
.example__layer {
position: absolute;
top:0;
left: 0;
right: 0;
bottom: 0;
}
.layer--a {
box-shadow: inset 0 0 0 1px red;
}
.layer--b {
box-shadow: inset 0 0 0 1px blue;
transform: translateZ(50px) scale(.45);
}
.layer--c {
box-shadow: inset 0 0 0 1px green;
}
.layer--d {
box-shadow: inset 00px 0 0px 1px orange;
transform: translateZ(50px) scale(.45);
}
.layer--e {
box-shadow: inset 00px 0 0px 1px orange;
transform: translateZ(50px) scale(.45) translateY(300px);
}
</style>
</head>
<body>
<div class="example" id="first">
<div class="example__group">
<div class="example__layer layer--a"></div>
<div class="example__layer layer--b"></div>
</div>
<div class="example__group">
<div class="example__layer layer--c"></div>
<div class="example__layer layer--d"></div>
</div>
</div>
<div class="example" id="second">
<div class="example__group">
<div class="example__layer layer--a"></div>
<div class="example__layer layer--b"></div>
</div>
<div class="example__group">
<div class="example__layer layer--c"></div>
<div class="example__layer layer--e"></div>
</div>
</div>
<script>
is(document.getElementById("first").scrollHeight, 600, "Scroll height should be computed correctly");
// The true height is 727.5 and we don't always snap the same direction.
isfuzzy(document.getElementById("second").scrollHeight, 728, 1, "Scroll height should be computed correctly");
</script>
</body></html>