gecko-dev/layout/generic/nsAbsoluteContainingBlock.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

770 строки
34 KiB
C++
Исходник Обычный вид История

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
2012-05-21 15:12:37 +04:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* code for managing absolutely positioned children of a rendering
* object that is a containing block for them
*/
1999-04-16 05:39:45 +04:00
#include "nsAbsoluteContainingBlock.h"
1999-04-16 05:39:45 +04:00
#include "nsContainerFrame.h"
#include "nsGkAtoms.h"
#include "mozilla/CSSAlignUtils.h"
#include "mozilla/PresShell.h"
#include "mozilla/ReflowInput.h"
#include "nsPlaceholderFrame.h"
#include "nsPresContext.h"
#include "nsCSSFrameConstructor.h"
#include "nsGridContainerFrame.h"
1999-04-16 05:39:45 +04:00
#include "mozilla/Sprintf.h"
#ifdef DEBUG
# include "nsBlockFrame.h"
static void PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
if (NS_UNCONSTRAINEDSIZE == aSize) {
strcpy(aBuf, "UC");
} else {
if ((int32_t)0xdeadbeef == aSize) {
strcpy(aBuf, "deadbeef");
} else {
snprintf(aBuf, aBufSize, "%d", aSize);
}
}
}
#endif
using namespace mozilla;
typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
ChildListID aListID,
nsFrameList& aChildList) {
MOZ_ASSERT(mChildListID == aListID, "unexpected child list name");
#ifdef DEBUG
nsFrame::VerifyDirtyBitSet(aChildList);
for (nsIFrame* f : aChildList) {
MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
}
#endif
1999-04-16 05:39:45 +04:00
mAbsoluteFrames.SetFrames(aChildList);
}
void nsAbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
ChildListID aListID,
nsFrameList& aFrameList) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
// Append the frames to our list of absolutely positioned frames
#ifdef DEBUG
nsFrame::VerifyDirtyBitSet(aFrameList);
#endif
mAbsoluteFrames.AppendFrames(nullptr, aFrameList);
// no damage to intrinsic widths, since absolutely positioned frames can't
// change them
aDelegatingFrame->PresShell()->FrameNeedsReflow(
aDelegatingFrame, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
}
void nsAbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
ChildListID aListID,
nsIFrame* aPrevFrame,
nsFrameList& aFrameList) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
"inserting after sibling frame with different parent");
#ifdef DEBUG
nsFrame::VerifyDirtyBitSet(aFrameList);
#endif
mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
// no damage to intrinsic widths, since absolutely positioned frames can't
// change them
aDelegatingFrame->PresShell()->FrameNeedsReflow(
aDelegatingFrame, nsIPresShell::eResize, NS_FRAME_HAS_DIRTY_CHILDREN);
}
void nsAbsoluteContainingBlock::RemoveFrame(nsIFrame* aDelegatingFrame,
ChildListID aListID,
nsIFrame* aOldFrame) {
NS_ASSERTION(mChildListID == aListID, "unexpected child list");
nsIFrame* nif = aOldFrame->GetNextInFlow();
if (nif) {
nif->GetParent()->DeleteNextInFlowChild(nif, false);
}
mAbsoluteFrames.DestroyFrame(aOldFrame);
}
void nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
nsPresContext* aPresContext,
const ReflowInput& aReflowInput,
nsReflowStatus& aReflowStatus,
const nsRect& aContainingBlock,
AbsPosReflowFlags aFlags,
nsOverflowAreas* aOverflowAreas) {
nsReflowStatus reflowStatus;
const bool reflowAll = aReflowInput.ShouldReflowAllKids();
const bool isGrid = !!(aFlags & AbsPosReflowFlags::eIsGridContainerCB);
1999-04-16 05:39:45 +04:00
nsIFrame* kidFrame;
nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
2003-06-26 15:30:17 +04:00
for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame;
kidFrame = kidFrame->GetNextSibling()) {
bool kidNeedsReflow =
reflowAll || NS_SUBTREE_DIRTY(kidFrame) ||
FrameDependsOnContainer(
kidFrame, !!(aFlags & AbsPosReflowFlags::eCBWidthChanged),
!!(aFlags & AbsPosReflowFlags::eCBHeightChanged));
nscoord availBSize = aReflowInput.AvailableBSize();
const nsRect& cb =
isGrid ? nsGridContainerFrame::GridItemCB(kidFrame) : aContainingBlock;
WritingMode containerWM = aReflowInput.GetWritingMode();
if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
// If we need to redo pagination on the kid, we need to reflow it.
// This can happen either if the available height shrunk and the
// kid (or its overflow that creates overflow containers) is now
// too large to fit in the available height, or if the available
// height has increased and the kid has a next-in-flow that we
// might need to pull from.
WritingMode kidWM = kidFrame->GetWritingMode();
if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
// Not sure what the right test would be here.
kidNeedsReflow = true;
} else {
nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
nscoord kidOverflowBEnd =
LogicalRect(containerWM,
// Use ...RelativeToSelf to ignore transforms
kidFrame->GetScrollableOverflowRectRelativeToSelf() +
kidFrame->GetPosition(),
aContainingBlock.Size())
.BEnd(containerWM);
MOZ_ASSERT(kidOverflowBEnd >= kidBEnd);
if (kidOverflowBEnd > availBSize ||
(kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
kidNeedsReflow = true;
}
}
}
if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
// Reflow the frame
nsReflowStatus kidStatus;
ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
aFlags, kidFrame, kidStatus, aOverflowAreas);
MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
"ShouldAvoidBreakInside should prevent this from happening");
nsIFrame* nextFrame = kidFrame->GetNextInFlow();
if (!kidStatus.IsFullyComplete() &&
aDelegatingFrame->IsFrameOfType(
nsIFrame::eCanContainOverflowContainers)) {
// Need a continuation
if (!nextFrame) {
nextFrame = aPresContext->PresShell()
->FrameConstructor()
->CreateContinuingFrame(aPresContext, kidFrame,
aDelegatingFrame);
}
// Add it as an overflow container.
// XXXfr This is a hack to fix some of our printing dataloss.
// See bug 154892. Not sure how to do it "right" yet; probably want
// to keep continuations within an nsAbsoluteContainingBlock eventually.
tracker.Insert(nextFrame, kidStatus);
reflowStatus.MergeCompletionStatusFrom(kidStatus);
} else {
// Delete any continuations
if (nextFrame) {
2013-06-10 22:31:59 +04:00
nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
nextFrame->GetParent()->DeleteNextInFlowChild(nextFrame, true);
}
}
} else {
tracker.Skip(kidFrame, reflowStatus);
if (aOverflowAreas) {
aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
}
}
// Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That
// will make sure that we end up reflowing aDelegatingFrame in cases when
// one of our kids interrupted. Otherwise we'd set the dirty or
// dirty-children bit on the kid in the condition below, and then when
// reflow completes and we go to mark dirty bits on all ancestors of that
// kid we'll immediately bail out, because the kid already has a dirty bit.
// In particular, we won't set any dirty bits on aDelegatingFrame, so when
// the following reflow happens we won't reflow the kid in question. This
// might be slightly suboptimal in cases where |kidFrame| itself did not
// interrupt, since we'll trigger a reflow of it too when it's not strictly
// needed. But the logic to not do that is enough more complicated, and
// the case enough of an edge case, that this is probably better.
if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
if (aDelegatingFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
} else {
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
1999-04-16 05:39:45 +04:00
}
// Abspos frames can't cause their parent to be incomplete,
// only overflow incomplete.
if (reflowStatus.IsIncomplete()) reflowStatus.SetOverflowIncomplete();
aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
1999-04-16 05:39:45 +04:00
}
static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
return aCoord.ConvertsToLength();
}
static inline bool IsFixedMarginSize(const LengthPercentageOrAuto& aCoord) {
return aCoord.ConvertsToLength();
}
static inline bool IsFixedOffset(const LengthPercentageOrAuto& aCoord) {
return aCoord.ConvertsToLength();
}
bool nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
bool aCBWidthChanged,
bool aCBHeightChanged) {
const nsStylePosition* pos = f->StylePosition();
// See if f's position might have changed because it depends on a
// placeholder's position
// This can happen in the following cases:
// 1) Vertical positioning. "top" must be auto and "bottom" must be auto
// (otherwise the vertical position is completely determined by
// whichever of them is not auto and the height).
// 2) Horizontal positioning. "left" must be auto and "right" must be auto
// (otherwise the horizontal position is completely determined by
// whichever of them is not auto and the width).
// See ReflowInput::InitAbsoluteConstraints -- these are the
// only cases when we call CalculateHypotheticalBox().
if ((pos->mOffset.Get(eSideTop).IsAuto() &&
pos->mOffset.Get(eSideBottom).IsAuto()) ||
(pos->mOffset.Get(eSideLeft).IsAuto() ||
pos->mOffset.Get(eSideRight).IsAuto())) {
return true;
}
if (!aCBWidthChanged && !aCBHeightChanged) {
// skip getting style data
return false;
}
const nsStylePadding* padding = f->StylePadding();
const nsStyleMargin* margin = f->StyleMargin();
WritingMode wm = f->GetWritingMode();
if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
// See if f's inline-size might have changed.
// If margin-inline-start/end, padding-inline-start/end,
// inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
// then our frame isize does not depend on the parent isize.
// Note that borders never depend on the parent isize.
// XXX All of the enumerated values except -moz-available are ok too.
if (pos->ISizeDependsOnContainer(wm) ||
pos->MinISizeDependsOnContainer(wm) ||
pos->MaxISizeDependsOnContainer(wm) ||
!IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
!IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
return true;
}
// See if f's position might have changed. If we're RTL then the
// rules are slightly different. We'll assume percentage or auto
// margins will always induce a dependency on the size
if (!IsFixedMarginSize(margin->mMargin.GetIStart(wm)) ||
!IsFixedMarginSize(margin->mMargin.GetIEnd(wm))) {
return true;
}
}
if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
// See if f's block-size might have changed.
// If margin-block-start/end, padding-block-start/end,
// min-block-size, and max-block-size are all lengths or 'none',
// and bsize is a length or bsize and bend are auto and bstart is not auto,
// then our frame bsize does not depend on the parent bsize.
// Note that borders never depend on the parent bsize.
//
// FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
// lengths?
if ((pos->BSizeDependsOnContainer(wm) &&
!(pos->BSize(wm).IsAuto() && pos->mOffset.GetBEnd(wm).IsAuto() &&
!pos->mOffset.GetBStart(wm).IsAuto())) ||
pos->MinBSizeDependsOnContainer(wm) ||
pos->MaxBSizeDependsOnContainer(wm) ||
!IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
!IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
return true;
}
// See if f's position might have changed.
if (!IsFixedMarginSize(margin->mMargin.GetBStart(wm)) ||
!IsFixedMarginSize(margin->mMargin.GetBEnd(wm))) {
return true;
}
Bug 1347759 - Fix conditions under which we reflow absolutely positioned element due to size change of its container for everything other than horizontal LTR. r=jfkthame I found this problem because I was debugging the failure of layout/reftests/w3c-css/received/css-writing-modes-3/clearance-calculations-vrl-008.xht with my patch for bug 1308876. It was failing because the red reference box that was intended to be covered up was being mispositioned leftwards by the width of the scrollbar, since we were not reflowing it when we decided that the viewport did not need scrollbars. This patch fixes that failure. This led me to this bug, where nsAbsoluteContainingBlock::FrameDependsOnContainer was incorrectly testing conditions for when the values of 'top', 'right', 'bottom', and 'left' require reflow due to changes in the size of the containing block. The old code is incorrect in a number of cases, such as: 1. in RTL, with 'right: 100px', it will say that the frame does not depend on its container's width since 'right' (offset-inline-start) is a fixed offset and 'left' is 'auto'. However, since the positioning is relative to the right edge, a change in container size does require that the absolutely positioned element be repositioned relative to the container's left edge. 2. In vertical-rl, again with 'right: 100px', it will make the same mistake, since 'right' (now offset-block-start) is a fixed offset. This is the case from the test I was debugging. 3. In vertical-rl with rtl direction and 'bottom: 100px', we will make the same mistake because 'bottom' (inline-start) is fixed and 'top' is 'auto', and we use 'bottom' rather than 'top'. However, in cases (1) and (3) we actually avoid hitting the bug in these simple-ish cases because ReflowInput::ShouldReflowAllKids() returns true whenever IsIResize() is true, which means that nsAbsoluteContainingBlock::Reflow doesn't even call FrameDependsOnContainer. However, FrameDependsOnContainer should still do the right thing because it's needed for nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty, which is only used (from nsBlockFrame) when we reflow again for clearance or for interruptible reflow. I haven't attempted to write a testcase for that because it seems likely to require spending hours in the debugger trying to trigger the right code. This means that the only test that fails prior to the patch is dynamic-offset-vrl-001.html, which exercises case (2), and also happens to be the most similar to problem in clearance-calculations-vrl-008.xht. This patch also makes the tests stricter so that we do optimize away resizes in some cases where we're able to do so, such as 'left: 100px; right: auto' in RTL. (Or, rather, we would if it weren't for the IsIResize() in ShouldReflowAllKids().) MozReview-Commit-ID: 8xm1AHC21oh --HG-- extra : transplant_source : %06%B4%40%EB%A9%C8M%F3%99%80%A9%DE%1F%1E%90%D3%F1%04W.
2017-03-16 19:39:19 +03:00
}
// Since we store coordinates relative to top and left, the position
// of a frame depends on that of its container if it is fixed relative
// to the right or bottom, or if it is positioned using percentages
// relative to the left or top. Because of the dependency on the
// sides (left and top) that we use to store coordinates, these tests
// are easier to do using physical coordinates rather than logical.
if (aCBWidthChanged) {
if (!IsFixedOffset(pos->mOffset.Get(eSideLeft))) {
Bug 1347759 - Fix conditions under which we reflow absolutely positioned element due to size change of its container for everything other than horizontal LTR. r=jfkthame I found this problem because I was debugging the failure of layout/reftests/w3c-css/received/css-writing-modes-3/clearance-calculations-vrl-008.xht with my patch for bug 1308876. It was failing because the red reference box that was intended to be covered up was being mispositioned leftwards by the width of the scrollbar, since we were not reflowing it when we decided that the viewport did not need scrollbars. This patch fixes that failure. This led me to this bug, where nsAbsoluteContainingBlock::FrameDependsOnContainer was incorrectly testing conditions for when the values of 'top', 'right', 'bottom', and 'left' require reflow due to changes in the size of the containing block. The old code is incorrect in a number of cases, such as: 1. in RTL, with 'right: 100px', it will say that the frame does not depend on its container's width since 'right' (offset-inline-start) is a fixed offset and 'left' is 'auto'. However, since the positioning is relative to the right edge, a change in container size does require that the absolutely positioned element be repositioned relative to the container's left edge. 2. In vertical-rl, again with 'right: 100px', it will make the same mistake, since 'right' (now offset-block-start) is a fixed offset. This is the case from the test I was debugging. 3. In vertical-rl with rtl direction and 'bottom: 100px', we will make the same mistake because 'bottom' (inline-start) is fixed and 'top' is 'auto', and we use 'bottom' rather than 'top'. However, in cases (1) and (3) we actually avoid hitting the bug in these simple-ish cases because ReflowInput::ShouldReflowAllKids() returns true whenever IsIResize() is true, which means that nsAbsoluteContainingBlock::Reflow doesn't even call FrameDependsOnContainer. However, FrameDependsOnContainer should still do the right thing because it's needed for nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty, which is only used (from nsBlockFrame) when we reflow again for clearance or for interruptible reflow. I haven't attempted to write a testcase for that because it seems likely to require spending hours in the debugger trying to trigger the right code. This means that the only test that fails prior to the patch is dynamic-offset-vrl-001.html, which exercises case (2), and also happens to be the most similar to problem in clearance-calculations-vrl-008.xht. This patch also makes the tests stricter so that we do optimize away resizes in some cases where we're able to do so, such as 'left: 100px; right: auto' in RTL. (Or, rather, we would if it weren't for the IsIResize() in ShouldReflowAllKids().) MozReview-Commit-ID: 8xm1AHC21oh --HG-- extra : transplant_source : %06%B4%40%EB%A9%C8M%F3%99%80%A9%DE%1F%1E%90%D3%F1%04W.
2017-03-16 19:39:19 +03:00
return true;
}
// Note that even if 'left' is a length, our position can still
// depend on the containing block width, because if our direction or
// writing-mode moves from right to left (in either block or inline
// progression) and 'right' is not 'auto', we will discard 'left'
// and be positioned relative to the containing block right edge.
// 'left' length and 'right' auto is the only combination we can be
// sure of.
if ((wm.GetInlineDir() == WritingMode::eInlineRTL ||
wm.GetBlockDir() == WritingMode::eBlockRL) &&
!pos->mOffset.Get(eSideRight).IsAuto()) {
return true;
}
}
Bug 1347759 - Fix conditions under which we reflow absolutely positioned element due to size change of its container for everything other than horizontal LTR. r=jfkthame I found this problem because I was debugging the failure of layout/reftests/w3c-css/received/css-writing-modes-3/clearance-calculations-vrl-008.xht with my patch for bug 1308876. It was failing because the red reference box that was intended to be covered up was being mispositioned leftwards by the width of the scrollbar, since we were not reflowing it when we decided that the viewport did not need scrollbars. This patch fixes that failure. This led me to this bug, where nsAbsoluteContainingBlock::FrameDependsOnContainer was incorrectly testing conditions for when the values of 'top', 'right', 'bottom', and 'left' require reflow due to changes in the size of the containing block. The old code is incorrect in a number of cases, such as: 1. in RTL, with 'right: 100px', it will say that the frame does not depend on its container's width since 'right' (offset-inline-start) is a fixed offset and 'left' is 'auto'. However, since the positioning is relative to the right edge, a change in container size does require that the absolutely positioned element be repositioned relative to the container's left edge. 2. In vertical-rl, again with 'right: 100px', it will make the same mistake, since 'right' (now offset-block-start) is a fixed offset. This is the case from the test I was debugging. 3. In vertical-rl with rtl direction and 'bottom: 100px', we will make the same mistake because 'bottom' (inline-start) is fixed and 'top' is 'auto', and we use 'bottom' rather than 'top'. However, in cases (1) and (3) we actually avoid hitting the bug in these simple-ish cases because ReflowInput::ShouldReflowAllKids() returns true whenever IsIResize() is true, which means that nsAbsoluteContainingBlock::Reflow doesn't even call FrameDependsOnContainer. However, FrameDependsOnContainer should still do the right thing because it's needed for nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty, which is only used (from nsBlockFrame) when we reflow again for clearance or for interruptible reflow. I haven't attempted to write a testcase for that because it seems likely to require spending hours in the debugger trying to trigger the right code. This means that the only test that fails prior to the patch is dynamic-offset-vrl-001.html, which exercises case (2), and also happens to be the most similar to problem in clearance-calculations-vrl-008.xht. This patch also makes the tests stricter so that we do optimize away resizes in some cases where we're able to do so, such as 'left: 100px; right: auto' in RTL. (Or, rather, we would if it weren't for the IsIResize() in ShouldReflowAllKids().) MozReview-Commit-ID: 8xm1AHC21oh --HG-- extra : transplant_source : %06%B4%40%EB%A9%C8M%F3%99%80%A9%DE%1F%1E%90%D3%F1%04W.
2017-03-16 19:39:19 +03:00
if (aCBHeightChanged) {
if (!IsFixedOffset(pos->mOffset.Get(eSideTop))) {
Bug 1347759 - Fix conditions under which we reflow absolutely positioned element due to size change of its container for everything other than horizontal LTR. r=jfkthame I found this problem because I was debugging the failure of layout/reftests/w3c-css/received/css-writing-modes-3/clearance-calculations-vrl-008.xht with my patch for bug 1308876. It was failing because the red reference box that was intended to be covered up was being mispositioned leftwards by the width of the scrollbar, since we were not reflowing it when we decided that the viewport did not need scrollbars. This patch fixes that failure. This led me to this bug, where nsAbsoluteContainingBlock::FrameDependsOnContainer was incorrectly testing conditions for when the values of 'top', 'right', 'bottom', and 'left' require reflow due to changes in the size of the containing block. The old code is incorrect in a number of cases, such as: 1. in RTL, with 'right: 100px', it will say that the frame does not depend on its container's width since 'right' (offset-inline-start) is a fixed offset and 'left' is 'auto'. However, since the positioning is relative to the right edge, a change in container size does require that the absolutely positioned element be repositioned relative to the container's left edge. 2. In vertical-rl, again with 'right: 100px', it will make the same mistake, since 'right' (now offset-block-start) is a fixed offset. This is the case from the test I was debugging. 3. In vertical-rl with rtl direction and 'bottom: 100px', we will make the same mistake because 'bottom' (inline-start) is fixed and 'top' is 'auto', and we use 'bottom' rather than 'top'. However, in cases (1) and (3) we actually avoid hitting the bug in these simple-ish cases because ReflowInput::ShouldReflowAllKids() returns true whenever IsIResize() is true, which means that nsAbsoluteContainingBlock::Reflow doesn't even call FrameDependsOnContainer. However, FrameDependsOnContainer should still do the right thing because it's needed for nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty, which is only used (from nsBlockFrame) when we reflow again for clearance or for interruptible reflow. I haven't attempted to write a testcase for that because it seems likely to require spending hours in the debugger trying to trigger the right code. This means that the only test that fails prior to the patch is dynamic-offset-vrl-001.html, which exercises case (2), and also happens to be the most similar to problem in clearance-calculations-vrl-008.xht. This patch also makes the tests stricter so that we do optimize away resizes in some cases where we're able to do so, such as 'left: 100px; right: auto' in RTL. (Or, rather, we would if it weren't for the IsIResize() in ShouldReflowAllKids().) MozReview-Commit-ID: 8xm1AHC21oh --HG-- extra : transplant_source : %06%B4%40%EB%A9%C8M%F3%99%80%A9%DE%1F%1E%90%D3%F1%04W.
2017-03-16 19:39:19 +03:00
return true;
}
// See comment above for width changes.
if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
!pos->mOffset.Get(eSideBottom).IsAuto()) {
Bug 1347759 - Fix conditions under which we reflow absolutely positioned element due to size change of its container for everything other than horizontal LTR. r=jfkthame I found this problem because I was debugging the failure of layout/reftests/w3c-css/received/css-writing-modes-3/clearance-calculations-vrl-008.xht with my patch for bug 1308876. It was failing because the red reference box that was intended to be covered up was being mispositioned leftwards by the width of the scrollbar, since we were not reflowing it when we decided that the viewport did not need scrollbars. This patch fixes that failure. This led me to this bug, where nsAbsoluteContainingBlock::FrameDependsOnContainer was incorrectly testing conditions for when the values of 'top', 'right', 'bottom', and 'left' require reflow due to changes in the size of the containing block. The old code is incorrect in a number of cases, such as: 1. in RTL, with 'right: 100px', it will say that the frame does not depend on its container's width since 'right' (offset-inline-start) is a fixed offset and 'left' is 'auto'. However, since the positioning is relative to the right edge, a change in container size does require that the absolutely positioned element be repositioned relative to the container's left edge. 2. In vertical-rl, again with 'right: 100px', it will make the same mistake, since 'right' (now offset-block-start) is a fixed offset. This is the case from the test I was debugging. 3. In vertical-rl with rtl direction and 'bottom: 100px', we will make the same mistake because 'bottom' (inline-start) is fixed and 'top' is 'auto', and we use 'bottom' rather than 'top'. However, in cases (1) and (3) we actually avoid hitting the bug in these simple-ish cases because ReflowInput::ShouldReflowAllKids() returns true whenever IsIResize() is true, which means that nsAbsoluteContainingBlock::Reflow doesn't even call FrameDependsOnContainer. However, FrameDependsOnContainer should still do the right thing because it's needed for nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty, which is only used (from nsBlockFrame) when we reflow again for clearance or for interruptible reflow. I haven't attempted to write a testcase for that because it seems likely to require spending hours in the debugger trying to trigger the right code. This means that the only test that fails prior to the patch is dynamic-offset-vrl-001.html, which exercises case (2), and also happens to be the most similar to problem in clearance-calculations-vrl-008.xht. This patch also makes the tests stricter so that we do optimize away resizes in some cases where we're able to do so, such as 'left: 100px; right: auto' in RTL. (Or, rather, we would if it weren't for the IsIResize() in ShouldReflowAllKids().) MozReview-Commit-ID: 8xm1AHC21oh --HG-- extra : transplant_source : %06%B4%40%EB%A9%C8M%F3%99%80%A9%DE%1F%1E%90%D3%F1%04W.
2017-03-16 19:39:19 +03:00
return true;
}
}
return false;
}
void nsAbsoluteContainingBlock::DestroyFrames(
nsIFrame* aDelegatingFrame, nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
mAbsoluteFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
}
void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
DoMarkFramesDirty(false);
}
void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
DoMarkFramesDirty(true);
}
void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
for (nsIFrame* kidFrame : mAbsoluteFrames) {
if (aMarkAllDirty) {
kidFrame->AddStateBits(NS_FRAME_IS_DIRTY);
} else if (FrameDependsOnContainer(kidFrame, true, true)) {
// Add the weakest flags that will make sure we reflow this frame later
kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
// Given an out-of-flow frame, this method returns the parent frame of its
// placeholder frame or null if it doesn't have a placeholder for some reason.
static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
return placeholder ? placeholder->GetParent() : nullptr;
}
/**
* This function returns the offset of an abs/fixed-pos child's static
* position, with respect to the "start" corner of its alignment container,
* according to CSS Box Alignment. This function only operates in a single
* axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
* parameter.
*
* @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
* @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
* the opportunity to reflow), in terms of
* aAbsPosCBWM.
* @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
* @param aPlaceholderContainer The parent of the child frame's corresponding
* placeholder frame, cast to a nsContainerFrame.
* (This will help us choose which alignment enum
* we should use for the child.)
* @param aAbsPosCBWM The child frame's containing block's WritingMode.
* @param aAbsPosCBAxis The axis (of the containing block) that we should
* be doing this computation for.
*/
static nscoord OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
const LogicalSize& aKidSizeInAbsPosCBWM,
const LogicalSize& aAbsPosCBSize,
nsContainerFrame* aPlaceholderContainer,
WritingMode aAbsPosCBWM,
LogicalAxis aAbsPosCBAxis) {
if (!aPlaceholderContainer) {
// (The placeholder container should be the thing that kicks this whole
// process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
// should exist... but bail gracefully if it doesn't.)
NS_ERROR(
"Missing placeholder-container when computing a "
"CSS Box Alignment static position");
return 0;
}
// (Most of this function is simply preparing args that we'll pass to
// AlignJustifySelf at the end.)
// NOTE: Our alignment container is aPlaceholderContainer's content-box
// (or an area within it, if aPlaceholderContainer is a grid). So, we'll
// perform most of our arithmetic/alignment in aPlaceholderContainer's
// WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
// container" in variables below.
WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
// Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
// writing-mode.
LogicalAxis pcAxis =
(pcWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
: aAbsPosCBAxis);
const bool placeholderContainerIsContainingBlock =
aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
LayoutFrameType parentType = aPlaceholderContainer->Type();
LogicalSize alignAreaSize(pcWM);
if (parentType == LayoutFrameType::FlexContainer) {
// We store the frame rect in FinishAndStoreOverflow, which runs _after_
// reflowing the absolute frames, so handle the special case of the frame
// being the actual containing block here, by getting the size from
// aAbsPosCBSize.
//
// The alignment container is the flex container's content box.
if (placeholderContainerIsContainingBlock) {
alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
// aAbsPosCBSize is the padding-box, so substract the padding to get the
// content box.
alignAreaSize -=
aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
} else {
alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
LogicalMargin pcBorderPadding =
aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
alignAreaSize -= pcBorderPadding.Size(pcWM);
}
} else if (parentType == LayoutFrameType::GridContainer) {
// This abspos elem's parent is a grid container. Per CSS Grid 10.1 & 10.2:
// - If the grid container *also* generates the abspos containing block (a
// grid area) for this abspos child, we use that abspos containing block as
// the alignment container, too. (And its size is aAbsPosCBSize.)
// - Otherwise, we use the grid's padding box as the alignment container.
// https://drafts.csswg.org/css-grid/#static-position
if (placeholderContainerIsContainingBlock) {
// The alignment container is the grid area that we're using as the
// absolute containing block.
alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
} else {
// The alignment container is a the grid container's padding box (which
// we can get by subtracting away its border from frame's size):
alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
LogicalMargin pcBorder =
aPlaceholderContainer->GetLogicalUsedBorder(pcWM);
alignAreaSize -= pcBorder.Size(pcWM);
}
} else {
NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
return 0; // (leave the child at the start of its alignment container)
}
nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
? alignAreaSize.ISize(pcWM)
: alignAreaSize.BSize(pcWM);
AlignJustifyFlags flags = AlignJustifyFlags::eIgnoreAutoMargins;
uint16_t alignConst = aPlaceholderContainer->CSSAlignmentForAbsPosChild(
aKidReflowInput, pcAxis);
// If the safe bit in alignConst is set, set the safe flag in |flags|.
// Note: If no <overflow-position> is specified, we behave as 'unsafe'.
// This doesn't quite match the css-align spec, which has an [at-risk]
// "smart default" behavior with some extra nuance about scroll containers.
if (alignConst & NS_STYLE_ALIGN_SAFE) {
flags |= AlignJustifyFlags::eOverflowSafe;
}
alignConst &= ~NS_STYLE_ALIGN_FLAG_BITS;
// Find out if placeholder-container & the OOF child have the same start-sides
// in the placeholder-container's pcAxis.
WritingMode kidWM = aKidReflowInput.GetWritingMode();
if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
flags |= AlignJustifyFlags::eSameSide;
}
// (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
// converted 'baseline'/'last baseline' enums to their fallback values.)
const nscoord baselineAdjust = nscoord(0);
// AlignJustifySelf operates in the kid's writing mode, so we need to
// represent the child's size and the desired axis in that writing mode:
LogicalSize kidSizeInOwnWM =
aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
LogicalAxis kidAxis =
(kidWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
: aAbsPosCBAxis);
nscoord offset = CSSAlignUtils::AlignJustifySelf(
alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
aKidReflowInput, kidSizeInOwnWM);
// "offset" is in terms of the CSS Box Alignment container (i.e. it's in
// terms of pcWM). But our return value needs to in terms of the containing
// block's writing mode, which might have the opposite directionality in the
// given axis. In that case, we just need to negate "offset" when returning,
// to make it have the right effect as an offset for coordinates in the
// containing block's writing mode.
if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
return -offset;
}
return offset;
}
void nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
nsPresContext* aPresContext, ReflowInput& aKidReflowInput,
const LogicalSize& aKidSize, const LogicalMargin& aMargin,
LogicalMargin* aOffsets, LogicalSize* aLogicalCBSize) {
WritingMode wm = aKidReflowInput.GetWritingMode();
WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
// Now that we know the child's size, we resolve any sentinel values in its
// IStart/BStart offset coordinates that depend on that size.
// * NS_AUTOOFFSET indicates that the child's position in the given axis
// is determined by its end-wards offset property, combined with its size and
// available space. e.g.: "top: auto; height: auto; bottom: 50px"
// * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
// static position in that axis, *and* its static position is determined by
// the axis-appropriate css-align property (which may require the child's
// size, e.g. to center it within the parent).
if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
(NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
if (-1 == aLogicalCBSize->ISize(wm)) {
// Get the containing block width/height
const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
*aLogicalCBSize = aKidReflowInput.ComputeContainingBlockRectangle(
aPresContext, parentRI);
}
const LogicalSize logicalCBSizeOuterWM =
aLogicalCBSize->ConvertTo(outerWM, wm);
// placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
// clauses. We declare it at this scope so we can avoid having to look
// it up twice (and only look it up if it's needed).
nsContainerFrame* placeholderContainer = nullptr;
if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
"Can't solve for both start and end");
aOffsets->IStart(outerWM) =
logicalCBSizeOuterWM.ISize(outerWM) - aOffsets->IEnd(outerWM) -
aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
} else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
nscoord offset = OffsetToAlignedStaticPos(
aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
outerWM, eLogicalAxisInline);
// Shift IStart from its current position (at start corner of the
// alignment container) by the returned offset. And set IEnd to the
// distance between the kid's end edge to containing block's end edge.
aOffsets->IStart(outerWM) += offset;
aOffsets->IEnd(outerWM) =
logicalCBSizeOuterWM.ISize(outerWM) -
(aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
}
if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
aOffsets->BStart(outerWM) =
logicalCBSizeOuterWM.BSize(outerWM) - aOffsets->BEnd(outerWM) -
aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
} else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
if (!placeholderContainer) {
placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
}
nscoord offset = OffsetToAlignedStaticPos(
aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
outerWM, eLogicalAxisBlock);
// Shift BStart from its current position (at start corner of the
// alignment container) by the returned offset. And set BEnd to the
// distance between the kid's end edge to containing block's end edge.
aOffsets->BStart(outerWM) += offset;
aOffsets->BEnd(outerWM) =
logicalCBSizeOuterWM.BSize(outerWM) -
(aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
}
aKidReflowInput.SetComputedLogicalOffsets(aOffsets->ConvertTo(wm, outerWM));
}
}
1999-04-16 05:39:45 +04:00
// XXX Optimize the case where it's a resize reflow and the absolutely
// positioned child has the exact same size and position and skip the
// reflow...
// When bug 154892 is checked in, make sure that when
// mChildListID == kFixedList, the height is unconstrained.
// since we don't allow replicated frames to split.
void nsAbsoluteContainingBlock::ReflowAbsoluteFrame(
nsIFrame* aDelegatingFrame, nsPresContext* aPresContext,
const ReflowInput& aReflowInput, const nsRect& aContainingBlock,
AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
nsOverflowAreas* aOverflowAreas) {
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
printf("abs pos ");
nsAutoString name;
aKidFrame->GetFrameName(name);
printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
char width[16];
char height[16];
PrettyUC(aReflowInput.AvailableWidth(), width, 16);
PrettyUC(aReflowInput.AvailableHeight(), height, 16);
printf(" a=%s,%s ", width, height);
PrettyUC(aReflowInput.ComputedWidth(), width, 16);
PrettyUC(aReflowInput.ComputedHeight(), height, 16);
printf("c=%s,%s \n", width, height);
}
AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
#endif // DEBUG
WritingMode wm = aKidFrame->GetWritingMode();
LogicalSize logicalCBSize(wm, aContainingBlock.Size());
nscoord availISize = logicalCBSize.ISize(wm);
if (availISize == -1) {
NS_ASSERTION(
aReflowInput.ComputedSize(wm).ISize(wm) != NS_UNCONSTRAINEDSIZE,
"Must have a useful inline-size _somewhere_");
availISize = aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
}
uint32_t rsFlags = 0;
if (aFlags & AbsPosReflowFlags::eIsGridContainerCB) {
// When a grid container generates the abs.pos. CB for a *child* then
// the static position is determined via CSS Box Alignment within the
// abs.pos. CB (a grid area, i.e. a piece of the grid). In this scenario,
// due to the multiple coordinate spaces in play, we use a convenience flag
// to simply have the child's ReflowInput give it a static position at its
// abs.pos. CB origin, and then we'll align & offset it from there.
nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
rsFlags |= ReflowInput::STATIC_POS_IS_CB_ORIGIN;
}
}
ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE),
&logicalCBSize, rsFlags);
// Get the border values
WritingMode outerWM = aReflowInput.GetWritingMode();
const LogicalMargin border(outerWM, aDelegatingFrame->GetUsedBorder());
LogicalMargin margin =
kidReflowInput.ComputedLogicalMargin().ConvertTo(outerWM, wm);
// If we're doing CSS Box Alignment in either axis, that will apply the
// margin for us in that axis (since the thing that's aligned is the margin
// box). So, we clear out the margin here to avoid applying it twice.
if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
}
if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
}
bool constrainBSize =
(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
(aFlags & AbsPosReflowFlags::eConstrainHeight) &&
// Don't split if told not to (e.g. for fixed frames)
!aDelegatingFrame->IsInlineFrame() &&
// XXX we don't handle splitting frames for inline absolute containing
// blocks yet
(aKidFrame->GetLogicalRect(aContainingBlock.Size()).BStart(wm) <=
aReflowInput.AvailableBSize());
// Don't split things below the fold. (Ideally we shouldn't *have*
// anything totally below the fold, but we can't position frames
// across next-in-flow breaks yet.
if (constrainBSize) {
kidReflowInput.AvailableBSize() =
aReflowInput.AvailableBSize() -
border.ConvertTo(wm, outerWM).BStart(wm) -
kidReflowInput.ComputedLogicalMargin().BStart(wm);
if (NS_AUTOOFFSET != kidReflowInput.ComputedLogicalOffsets().BStart(wm)) {
kidReflowInput.AvailableBSize() -=
kidReflowInput.ComputedLogicalOffsets().BStart(wm);
}
}
// Do the reflow
ReflowOutput kidDesiredSize(kidReflowInput);
aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
const LogicalSize kidSize = kidDesiredSize.Size(wm).ConvertTo(outerWM, wm);
LogicalMargin offsets =
kidReflowInput.ComputedLogicalOffsets().ConvertTo(outerWM, wm);
// If we're solving for start in either inline or block direction,
// then compute it now that we know the dimensions.
ResolveSizeDependentOffsets(aPresContext, kidReflowInput, kidSize, margin,
&offsets, &logicalCBSize);
// Position the child relative to our padding edge
LogicalRect rect(
outerWM,
border.IStart(outerWM) + offsets.IStart(outerWM) + margin.IStart(outerWM),
border.BStart(outerWM) + offsets.BStart(outerWM) + margin.BStart(outerWM),
kidSize.ISize(outerWM), kidSize.BSize(outerWM));
nsRect r = rect.GetPhysicalRect(
outerWM, logicalCBSize.GetPhysicalSize(wm) +
border.Size(outerWM).GetPhysicalSize(outerWM));
// Offset the frame rect by the given origin of the absolute containing block.
r.x += aContainingBlock.x;
r.y += aContainingBlock.y;
aKidFrame->SetRect(r);
nsView* view = aKidFrame->GetView();
if (view) {
// Size and position the view and set its opacity, visibility, content
// transparency, and clip
nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
kidDesiredSize.VisualOverflow());
} else {
nsContainerFrame::PositionChildViews(aKidFrame);
}
aKidFrame->DidReflow(aPresContext, &kidReflowInput);
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
printf("abs pos ");
nsAutoString name;
aKidFrame->GetFrameName(name);
printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame), r.x, r.y,
r.width, r.height);
}
#endif
if (aOverflowAreas) {
aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + r.TopLeft());
}
1999-04-16 05:39:45 +04:00
}