diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /layout/generic/ReflowInput.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'layout/generic/ReflowInput.cpp')
-rw-r--r-- | layout/generic/ReflowInput.cpp | 3071 |
1 files changed, 3071 insertions, 0 deletions
diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp new file mode 100644 index 000000000..42f4a24b5 --- /dev/null +++ b/layout/generic/ReflowInput.cpp @@ -0,0 +1,3071 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* struct containing the input to nsIFrame::Reflow */ + +#include "mozilla/ReflowInput.h" + +#include "LayoutLogging.h" +#include "nsStyleConsts.h" +#include "nsCSSAnonBoxes.h" +#include "nsFrame.h" +#include "nsIContent.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsIPresShell.h" +#include "nsFontMetrics.h" +#include "nsBlockFrame.h" +#include "nsLineBox.h" +#include "nsImageFrame.h" +#include "nsTableFrame.h" +#include "nsTableCellFrame.h" +#include "nsIPercentBSizeObserver.h" +#include "nsLayoutUtils.h" +#include "mozilla/Preferences.h" +#include "nsFontInflationData.h" +#include "StickyScrollContainer.h" +#include "nsIFrameInlines.h" +#include "CounterStyleManager.h" +#include <algorithm> +#include "mozilla/dom/HTMLInputElement.h" + +#ifdef DEBUG +#undef NOISY_VERTICAL_ALIGN +#else +#undef NOISY_VERTICAL_ALIGN +#endif + +using namespace mozilla; +using namespace mozilla::css; +using namespace mozilla::dom; +using namespace mozilla::layout; + +enum eNormalLineHeightControl { + eUninitialized = -1, + eNoExternalLeading = 0, // does not include external leading + eIncludeExternalLeading, // use whatever value font vendor provides + eCompensateLeading // compensate leading if leading provided by font vendor is not enough +}; + +static eNormalLineHeightControl sNormalLineHeightControl = eUninitialized; + +// Initialize a <b>root</b> reflow state with a rendering context to +// use for measuring things. +ReflowInput::ReflowInput(nsPresContext* aPresContext, + nsIFrame* aFrame, + nsRenderingContext* aRenderingContext, + const LogicalSize& aAvailableSpace, + uint32_t aFlags) + : SizeComputationInput(aFrame, aRenderingContext) + , mBlockDelta(0) + , mOrthogonalLimit(NS_UNCONSTRAINEDSIZE) + , mReflowDepth(0) +{ + NS_PRECONDITION(aRenderingContext, "no rendering context"); + MOZ_ASSERT(aPresContext, "no pres context"); + MOZ_ASSERT(aFrame, "no frame"); + MOZ_ASSERT(aPresContext == aFrame->PresContext(), "wrong pres context"); + mParentReflowInput = nullptr; + AvailableISize() = aAvailableSpace.ISize(mWritingMode); + AvailableBSize() = aAvailableSpace.BSize(mWritingMode); + mFloatManager = nullptr; + mLineLayout = nullptr; + mDiscoveredClearance = nullptr; + mPercentBSizeObserver = nullptr; + + if (aFlags & DUMMY_PARENT_REFLOW_STATE) { + mFlags.mDummyParentReflowInput = true; + } + if (aFlags & COMPUTE_SIZE_SHRINK_WRAP) { + mFlags.mShrinkWrap = true; + } + if (aFlags & COMPUTE_SIZE_USE_AUTO_BSIZE) { + mFlags.mUseAutoBSize = true; + } + if (aFlags & STATIC_POS_IS_CB_ORIGIN) { + mFlags.mStaticPosIsCBOrigin = true; + } + if (aFlags & I_CLAMP_MARGIN_BOX_MIN_SIZE) { + mFlags.mIClampMarginBoxMinSize = true; + } + if (aFlags & B_CLAMP_MARGIN_BOX_MIN_SIZE) { + mFlags.mBClampMarginBoxMinSize = true; + } + + if (!(aFlags & CALLER_WILL_INIT)) { + Init(aPresContext); + } +} + +static bool CheckNextInFlowParenthood(nsIFrame* aFrame, nsIFrame* aParent) +{ + nsIFrame* frameNext = aFrame->GetNextInFlow(); + nsIFrame* parentNext = aParent->GetNextInFlow(); + return frameNext && parentNext && frameNext->GetParent() == parentNext; +} + +/** + * Adjusts the margin for a list (ol, ul), if necessary, depending on + * font inflation settings. Unfortunately, because bullets from a list are + * placed in the margin area, we only have ~40px in which to place the + * bullets. When they are inflated, however, this causes problems, since + * the text takes up more space than is available in the margin. + * + * This method will return a small amount (in app units) by which the + * margin can be adjusted, so that the space is available for list + * bullets to be rendered with font inflation enabled. + */ +static nscoord +FontSizeInflationListMarginAdjustment(const nsIFrame* aFrame) +{ + float inflation = nsLayoutUtils::FontSizeInflationFor(aFrame); + if (aFrame->IsFrameOfType(nsIFrame::eBlockFrame)) { + const nsBlockFrame* blockFrame = static_cast<const nsBlockFrame*>(aFrame); + + // We only want to adjust the margins if we're dealing with an ordered + // list. + if (inflation > 1.0f && + blockFrame->HasBullet() && + inflation > 1.0f) { + + auto listStyleType = aFrame->StyleList()->GetCounterStyle()->GetStyle(); + if (listStyleType != NS_STYLE_LIST_STYLE_NONE && + listStyleType != NS_STYLE_LIST_STYLE_DISC && + listStyleType != NS_STYLE_LIST_STYLE_CIRCLE && + listStyleType != NS_STYLE_LIST_STYLE_SQUARE && + listStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED && + listStyleType != NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN) { + // The HTML spec states that the default padding for ordered lists + // begins at 40px, indicating that we have 40px of space to place a + // bullet. When performing font inflation calculations, we add space + // equivalent to this, but simply inflated at the same amount as the + // text, in app units. + return nsPresContext::CSSPixelsToAppUnits(40) * (inflation - 1); + } + + } + } + + return 0; +} + +// NOTE: If we ever want to use SizeComputationInput for a flex item or a +// grid item, we need to make it take the containing-block block-size as +// well as the inline-size, since flex items and grid items resolve +// block-direction percent margins and padding against the +// containing-block block-size, rather than its inline-size. +SizeComputationInput::SizeComputationInput(nsIFrame *aFrame, + nsRenderingContext *aRenderingContext, + WritingMode aContainingBlockWritingMode, + nscoord aContainingBlockISize) + : mFrame(aFrame) + , mRenderingContext(aRenderingContext) + , mWritingMode(aFrame->GetWritingMode()) +{ + MOZ_ASSERT(!aFrame->IsFlexOrGridItem(), + "We're about to resolve percent margin & padding " + "values against CB inline size, which is incorrect for " + "flex/grid items. " + "Additionally for grid items, this path doesn't handle baseline " + "padding contribution - see SizeComputationInput::InitOffsets"); + LogicalSize cbSize(aContainingBlockWritingMode, aContainingBlockISize, + aContainingBlockISize); + ReflowInputFlags flags; + InitOffsets(aContainingBlockWritingMode, cbSize, mFrame->GetType(), flags); +} + +// Initialize a reflow state for a child frame's reflow. Some state +// is copied from the parent reflow state; the remaining state is +// computed. +ReflowInput::ReflowInput( + nsPresContext* aPresContext, + const ReflowInput& aParentReflowInput, + nsIFrame* aFrame, + const LogicalSize& aAvailableSpace, + const LogicalSize* aContainingBlockSize, + uint32_t aFlags) + : SizeComputationInput(aFrame, aParentReflowInput.mRenderingContext) + , mBlockDelta(0) + , mOrthogonalLimit(NS_UNCONSTRAINEDSIZE) + , mFlags(aParentReflowInput.mFlags) + , mReflowDepth(aParentReflowInput.mReflowDepth + 1) +{ + MOZ_ASSERT(aPresContext, "no pres context"); + MOZ_ASSERT(aFrame, "no frame"); + MOZ_ASSERT(aPresContext == aFrame->PresContext(), "wrong pres context"); + NS_PRECONDITION(!mFlags.mSpecialBSizeReflow || + !NS_SUBTREE_DIRTY(aFrame), + "frame should be clean when getting special bsize reflow"); + + mParentReflowInput = &aParentReflowInput; + + // If the parent is dirty, then the child is as well. + // XXX Are the other cases where the parent reflows a child a second + // time, as a resize? + if (!mFlags.mSpecialBSizeReflow) + mFrame->AddStateBits(mParentReflowInput->mFrame->GetStateBits() & + NS_FRAME_IS_DIRTY); + + AvailableISize() = aAvailableSpace.ISize(mWritingMode); + AvailableBSize() = aAvailableSpace.BSize(mWritingMode); + + if (mWritingMode.IsOrthogonalTo(aParentReflowInput.GetWritingMode())) { + // If we're setting up for an orthogonal flow, and the parent reflow state + // had a constrained ComputedBSize, we can use that as our AvailableISize + // in preference to leaving it unconstrained. + if (AvailableISize() == NS_UNCONSTRAINEDSIZE && + aParentReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE) { + AvailableISize() = aParentReflowInput.ComputedBSize(); + } + } + + mFloatManager = aParentReflowInput.mFloatManager; + if (mFrame->IsFrameOfType(nsIFrame::eLineParticipant)) + mLineLayout = aParentReflowInput.mLineLayout; + else + mLineLayout = nullptr; + + // Note: mFlags was initialized as a copy of aParentReflowInput.mFlags up in + // this constructor's init list, so the only flags that we need to explicitly + // initialize here are those that may need a value other than our parent's. + mFlags.mNextInFlowUntouched = aParentReflowInput.mFlags.mNextInFlowUntouched && + CheckNextInFlowParenthood(aFrame, aParentReflowInput.mFrame); + mFlags.mAssumingHScrollbar = mFlags.mAssumingVScrollbar = false; + mFlags.mIsColumnBalancing = false; + mFlags.mIsFlexContainerMeasuringHeight = false; + mFlags.mDummyParentReflowInput = false; + mFlags.mShrinkWrap = !!(aFlags & COMPUTE_SIZE_SHRINK_WRAP); + mFlags.mUseAutoBSize = !!(aFlags & COMPUTE_SIZE_USE_AUTO_BSIZE); + mFlags.mStaticPosIsCBOrigin = !!(aFlags & STATIC_POS_IS_CB_ORIGIN); + mFlags.mIOffsetsNeedCSSAlign = mFlags.mBOffsetsNeedCSSAlign = false; + mFlags.mIClampMarginBoxMinSize = !!(aFlags & I_CLAMP_MARGIN_BOX_MIN_SIZE); + mFlags.mBClampMarginBoxMinSize = !!(aFlags & B_CLAMP_MARGIN_BOX_MIN_SIZE); + + mDiscoveredClearance = nullptr; + mPercentBSizeObserver = (aParentReflowInput.mPercentBSizeObserver && + aParentReflowInput.mPercentBSizeObserver->NeedsToObserve(*this)) + ? aParentReflowInput.mPercentBSizeObserver : nullptr; + + if ((aFlags & DUMMY_PARENT_REFLOW_STATE) || + (mParentReflowInput->mFlags.mDummyParentReflowInput && + mFrame->GetType() == nsGkAtoms::tableFrame)) { + mFlags.mDummyParentReflowInput = true; + } + + if (!(aFlags & CALLER_WILL_INIT)) { + Init(aPresContext, aContainingBlockSize); + } +} + +inline nscoord +SizeComputationInput::ComputeISizeValue(nscoord aContainingBlockISize, + nscoord aContentEdgeToBoxSizing, + nscoord aBoxSizingToMarginEdge, + const nsStyleCoord& aCoord) const +{ + return mFrame->ComputeISizeValue(mRenderingContext, + aContainingBlockISize, + aContentEdgeToBoxSizing, + aBoxSizingToMarginEdge, + aCoord); +} + +nscoord +SizeComputationInput::ComputeISizeValue(nscoord aContainingBlockISize, + StyleBoxSizing aBoxSizing, + const nsStyleCoord& aCoord) const +{ + WritingMode wm = GetWritingMode(); + nscoord inside = 0, outside = ComputedLogicalBorderPadding().IStartEnd(wm) + + ComputedLogicalMargin().IStartEnd(wm); + if (aBoxSizing == StyleBoxSizing::Border) { + inside = ComputedLogicalBorderPadding().IStartEnd(wm); + } + outside -= inside; + + return ComputeISizeValue(aContainingBlockISize, inside, + outside, aCoord); +} + +nscoord +SizeComputationInput::ComputeBSizeValue(nscoord aContainingBlockBSize, + StyleBoxSizing aBoxSizing, + const nsStyleCoord& aCoord) const +{ + WritingMode wm = GetWritingMode(); + nscoord inside = 0; + if (aBoxSizing == StyleBoxSizing::Border) { + inside = ComputedLogicalBorderPadding().BStartEnd(wm); + } + return nsLayoutUtils::ComputeBSizeValue(aContainingBlockBSize, + inside, aCoord); +} + +void +ReflowInput::SetComputedWidth(nscoord aComputedWidth) +{ + NS_ASSERTION(mFrame, "Must have a frame!"); + // It'd be nice to assert that |frame| is not in reflow, but this fails for + // two reasons: + // + // 1) Viewport frames reset the computed width on a copy of their reflow + // state when reflowing fixed-pos kids. In that case we actually don't + // want to mess with the resize flags, because comparing the frame's rect + // to the munged computed width is pointless. + // 2) nsFrame::BoxReflow creates a reflow state for its parent. This reflow + // state is not used to reflow the parent, but just as a parent for the + // frame's own reflow state. So given a nsBoxFrame inside some non-XUL + // (like a text control, for example), we'll end up creating a reflow + // state for the parent while the parent is reflowing. + + NS_PRECONDITION(aComputedWidth >= 0, "Invalid computed width"); + if (ComputedWidth() != aComputedWidth) { + ComputedWidth() = aComputedWidth; + nsIAtom* frameType = mFrame->GetType(); + if (frameType != nsGkAtoms::viewportFrame || // Or check GetParent()? + mWritingMode.IsVertical()) { + InitResizeFlags(mFrame->PresContext(), frameType); + } + } +} + +void +ReflowInput::SetComputedHeight(nscoord aComputedHeight) +{ + NS_ASSERTION(mFrame, "Must have a frame!"); + // It'd be nice to assert that |frame| is not in reflow, but this fails + // because: + // + // nsFrame::BoxReflow creates a reflow state for its parent. This reflow + // state is not used to reflow the parent, but just as a parent for the + // frame's own reflow state. So given a nsBoxFrame inside some non-XUL + // (like a text control, for example), we'll end up creating a reflow + // state for the parent while the parent is reflowing. + + NS_PRECONDITION(aComputedHeight >= 0, "Invalid computed height"); + if (ComputedHeight() != aComputedHeight) { + ComputedHeight() = aComputedHeight; + nsIAtom* frameType = mFrame->GetType(); + if (frameType != nsGkAtoms::viewportFrame || !mWritingMode.IsVertical()) { + InitResizeFlags(mFrame->PresContext(), frameType); + } + } +} + +void +ReflowInput::Init(nsPresContext* aPresContext, + const LogicalSize* aContainingBlockSize, + const nsMargin* aBorder, + const nsMargin* aPadding) +{ + if (AvailableISize() == NS_UNCONSTRAINEDSIZE) { + // Look up the parent chain for an orthogonal inline limit, + // and reset AvailableISize() if found. + for (const ReflowInput *parent = mParentReflowInput; + parent != nullptr; parent = parent->mParentReflowInput) { + if (parent->GetWritingMode().IsOrthogonalTo(mWritingMode) && + parent->mOrthogonalLimit != NS_UNCONSTRAINEDSIZE) { + AvailableISize() = parent->mOrthogonalLimit; + break; + } + } + } + + LAYOUT_WARN_IF_FALSE(AvailableISize() != NS_UNCONSTRAINEDSIZE, + "have unconstrained inline-size; this should only " + "result from very large sizes, not attempts at " + "intrinsic inline-size calculation"); + + mStylePosition = mFrame->StylePosition(); + mStyleDisplay = mFrame->StyleDisplay(); + mStyleVisibility = mFrame->StyleVisibility(); + mStyleBorder = mFrame->StyleBorder(); + mStyleMargin = mFrame->StyleMargin(); + mStylePadding = mFrame->StylePadding(); + mStyleText = mFrame->StyleText(); + + nsIAtom* type = mFrame->GetType(); + + InitFrameType(type); + InitCBReflowInput(); + + LogicalSize cbSize(mWritingMode, -1, -1); + if (aContainingBlockSize) { + cbSize = *aContainingBlockSize; + } + + InitConstraints(aPresContext, cbSize, aBorder, aPadding, type); + + InitResizeFlags(aPresContext, type); + + nsIFrame *parent = mFrame->GetParent(); + if (parent && + (parent->GetStateBits() & NS_FRAME_IN_CONSTRAINED_BSIZE) && + !(parent->GetType() == nsGkAtoms::scrollFrame && + parent->StyleDisplay()->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN)) { + mFrame->AddStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE); + } else if (type == nsGkAtoms::svgForeignObjectFrame) { + // An SVG foreignObject frame is inherently constrained block-size. + mFrame->AddStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE); + } else { + const nsStyleCoord& bSizeCoord = mStylePosition->BSize(mWritingMode); + const nsStyleCoord& maxBSizeCoord = mStylePosition->MaxBSize(mWritingMode); + if ((bSizeCoord.GetUnit() != eStyleUnit_Auto || + maxBSizeCoord.GetUnit() != eStyleUnit_None) && + // Don't set NS_FRAME_IN_CONSTRAINED_BSIZE on body or html elements. + (mFrame->GetContent() && + !(mFrame->GetContent()->IsAnyOfHTMLElements(nsGkAtoms::body, + nsGkAtoms::html)))) { + + // If our block-size was specified as a percentage, then this could + // actually resolve to 'auto', based on: + // http://www.w3.org/TR/CSS21/visudet.html#the-height-property + nsIFrame* containingBlk = mFrame; + while (containingBlk) { + const nsStylePosition* stylePos = containingBlk->StylePosition(); + const nsStyleCoord& bSizeCoord = stylePos->BSize(mWritingMode); + const nsStyleCoord& maxBSizeCoord = stylePos->MaxBSize(mWritingMode); + if ((bSizeCoord.IsCoordPercentCalcUnit() && + !bSizeCoord.HasPercent()) || + (maxBSizeCoord.IsCoordPercentCalcUnit() && + !maxBSizeCoord.HasPercent())) { + mFrame->AddStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE); + break; + } else if ((bSizeCoord.IsCoordPercentCalcUnit() && + bSizeCoord.HasPercent()) || + (maxBSizeCoord.IsCoordPercentCalcUnit() && + maxBSizeCoord.HasPercent())) { + if (!(containingBlk = containingBlk->GetContainingBlock())) { + // If we've reached the top of the tree, then we don't have + // a constrained block-size. + mFrame->RemoveStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE); + break; + } + + continue; + } else { + mFrame->RemoveStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE); + break; + } + } + } else { + mFrame->RemoveStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE); + } + } + + if (mParentReflowInput && + mParentReflowInput->GetWritingMode().IsOrthogonalTo(mWritingMode)) { + // Orthogonal frames are always reflowed with an unconstrained + // dimension to avoid incomplete reflow across an orthogonal + // boundary. Normally this is the block-size, but for column sets + // with auto-height it's the inline-size, so that they can add + // columns in the container's block direction + if (type == nsGkAtoms::columnSetFrame && + eStyleUnit_Auto == mStylePosition->ISize(mWritingMode).GetUnit()) { + ComputedISize() = NS_UNCONSTRAINEDSIZE; + } else { + AvailableBSize() = NS_UNCONSTRAINEDSIZE; + } + } + + LAYOUT_WARN_IF_FALSE((mFrameType == NS_CSS_FRAME_TYPE_INLINE && + !mFrame->IsFrameOfType(nsIFrame::eReplaced)) || + type == nsGkAtoms::textFrame || + ComputedISize() != NS_UNCONSTRAINEDSIZE, + "have unconstrained inline-size; this should only " + "result from very large sizes, not attempts at " + "intrinsic inline-size calculation"); +} + +void ReflowInput::InitCBReflowInput() +{ + if (!mParentReflowInput) { + mCBReflowInput = nullptr; + return; + } + if (mParentReflowInput->mFlags.mDummyParentReflowInput) { + mCBReflowInput = mParentReflowInput; + return; + } + + if (mParentReflowInput->mFrame == mFrame->GetContainingBlock()) { + // Inner table frames need to use the containing block of the outer + // table frame. + if (mFrame->GetType() == nsGkAtoms::tableFrame) { + mCBReflowInput = mParentReflowInput->mCBReflowInput; + } else { + mCBReflowInput = mParentReflowInput; + } + } else { + mCBReflowInput = mParentReflowInput->mCBReflowInput; + } +} + +/* Check whether CalcQuirkContainingBlockHeight would stop on the + * given reflow state, using its block as a height. (essentially + * returns false for any case in which CalcQuirkContainingBlockHeight + * has a "continue" in its main loop.) + * + * XXX Maybe refactor CalcQuirkContainingBlockHeight so it uses + * this function as well + */ +static bool +IsQuirkContainingBlockHeight(const ReflowInput* rs, nsIAtom* aFrameType) +{ + if (nsGkAtoms::blockFrame == aFrameType || +#ifdef MOZ_XUL + nsGkAtoms::XULLabelFrame == aFrameType || +#endif + nsGkAtoms::scrollFrame == aFrameType) { + // Note: This next condition could change due to a style change, + // but that would cause a style reflow anyway, which means we're ok. + if (NS_AUTOHEIGHT == rs->ComputedHeight()) { + if (!rs->mFrame->IsAbsolutelyPositioned()) { + return false; + } + } + } + return true; +} + + +void +ReflowInput::InitResizeFlags(nsPresContext* aPresContext, nsIAtom* aFrameType) +{ + const WritingMode wm = mWritingMode; // just a shorthand + // We should report that we have a resize in the inline dimension if + // *either* the border-box size or the content-box size in that + // dimension has changed. It might not actually be necessary to do + // this if the border-box size has changed and the content-box size + // has not changed, but since we've historically used the flag to mean + // border-box size change, continue to do that. (It's possible for + // the content-box size to change without a border-box size change or + // a style change given (1) a fixed width (possibly fixed by max-width + // or min-width), (2) box-sizing:border-box or padding-box, and + // (3) percentage padding.) + // + // However, we don't actually have the information at this point to + // tell whether the content-box size has changed, since both style + // data and the UsedPaddingProperty() have already been updated. So, + // instead, we explicitly check for the case where it's possible for + // the content-box size to have changed without either (a) a change in + // the border-box size or (b) an nsChangeHint_NeedDirtyReflow change + // hint due to change in border or padding. Thus we test using the + // conditions from the previous paragraph, except without testing (1) + // since it's complicated to test properly and less likely to help + // with optimizing cases away. + bool isIResize = + // is the border-box resizing? + mFrame->ISize(wm) != + ComputedISize() + ComputedLogicalBorderPadding().IStartEnd(wm) || + // or is the content-box resizing? (see comment above) + (mStylePosition->mBoxSizing != StyleBoxSizing::Content && + mStylePadding->IsWidthDependent()); + + if ((mFrame->GetStateBits() & NS_FRAME_FONT_INFLATION_FLOW_ROOT) && + nsLayoutUtils::FontSizeInflationEnabled(aPresContext)) { + // Create our font inflation data if we don't have it already, and + // give it our current width information. + bool dirty = nsFontInflationData::UpdateFontInflationDataISizeFor(*this) && + // Avoid running this at the box-to-block interface + // (where we shouldn't be inflating anyway, and where + // reflow state construction is probably to construct a + // dummy parent reflow state anyway). + !mFlags.mDummyParentReflowInput; + + if (dirty || (!mFrame->GetParent() && isIResize)) { + // When font size inflation is enabled, a change in either: + // * the effective width of a font inflation flow root + // * the width of the frame + // needs to cause a dirty reflow since they change the font size + // inflation calculations, which in turn change the size of text, + // line-heights, etc. This is relatively similar to a classic + // case of style change reflow, except that because inflation + // doesn't affect the intrinsic sizing codepath, there's no need + // to invalidate intrinsic sizes. + // + // Note that this makes horizontal resizing a good bit more + // expensive. However, font size inflation is targeted at a set of + // devices (zoom-and-pan devices) where the main use case for + // horizontal resizing needing to be efficient (window resizing) is + // not present. It does still increase the cost of dynamic changes + // caused by script where a style or content change in one place + // causes a resize in another (e.g., rebalancing a table). + + // FIXME: This isn't so great for the cases where + // ReflowInput::SetComputedWidth is called, if the first time + // we go through InitResizeFlags we set IsHResize() to true, and then + // the second time we'd set it to false even without the + // NS_FRAME_IS_DIRTY bit already set. + if (mFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) { + // Foreign object frames use dirty bits in a special way. + mFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); + nsIFrame *kid = mFrame->PrincipalChildList().FirstChild(); + if (kid) { + kid->AddStateBits(NS_FRAME_IS_DIRTY); + } + } else { + mFrame->AddStateBits(NS_FRAME_IS_DIRTY); + } + + // Mark intrinsic widths on all descendants dirty. We need to do + // this (1) since we're changing the size of text and need to + // clear text runs on text frames and (2) since we actually are + // changing some intrinsic widths, but only those that live inside + // of containers. + + // It makes sense to do this for descendants but not ancestors + // (which is unusual) because we're only changing the unusual + // inflation-dependent intrinsic widths (i.e., ones computed with + // nsPresContext::mInflationDisabledForShrinkWrap set to false), + // which should never affect anything outside of their inflation + // flow root (or, for that matter, even their inflation + // container). + + // This is also different from what PresShell::FrameNeedsReflow + // does because it doesn't go through placeholders. It doesn't + // need to because we're actually doing something that cares about + // frame tree geometry (the width on an ancestor) rather than + // style. + + AutoTArray<nsIFrame*, 32> stack; + stack.AppendElement(mFrame); + + do { + nsIFrame *f = stack.ElementAt(stack.Length() - 1); + stack.RemoveElementAt(stack.Length() - 1); + + nsIFrame::ChildListIterator lists(f); + for (; !lists.IsDone(); lists.Next()) { + nsFrameList::Enumerator childFrames(lists.CurrentList()); + for (; !childFrames.AtEnd(); childFrames.Next()) { + nsIFrame* kid = childFrames.get(); + kid->MarkIntrinsicISizesDirty(); + stack.AppendElement(kid); + } + } + } while (stack.Length() != 0); + } + } + + SetIResize(!(mFrame->GetStateBits() & NS_FRAME_IS_DIRTY) && + isIResize); + + // XXX Should we really need to null check mCBReflowInput? (We do for + // at least nsBoxFrame). + if (IS_TABLE_CELL(aFrameType) && + (mFlags.mSpecialBSizeReflow || + (mFrame->FirstInFlow()->GetStateBits() & + NS_TABLE_CELL_HAD_SPECIAL_REFLOW)) && + (mFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_BSIZE)) { + // Need to set the bit on the cell so that + // mCBReflowInput->IsBResize() is set correctly below when + // reflowing descendant. + SetBResize(true); + } else if (mCBReflowInput && mFrame->IsBlockWrapper()) { + // XXX Is this problematic for relatively positioned inlines acting + // as containing block for absolutely positioned elements? + // Possibly; in that case we should at least be checking + // NS_SUBTREE_DIRTY, I'd think. + SetBResize(mCBReflowInput->IsBResizeForWM(wm)); + } else if (mCBReflowInput && !nsLayoutUtils::GetAsBlock(mFrame)) { + // Some non-block frames (e.g. table frames) aggressively optimize out their + // BSize recomputation when they don't have the BResize flag set. This + // means that if they go from having a computed non-auto height to having an + // auto height and don't have that flag set, they will not actually compute + // their auto height and will just remain at whatever size they already + // were. We can end up in that situation if the child has a percentage + // specified height and the parent changes from non-auto height to auto + // height. When that happens, the parent will typically have the BResize + // flag set, and we want to propagate that flag to the kid. + // + // Ideally it seems like we'd do this for blocks too, of course... but we'd + // really want to restrict it to the percentage height case or something, to + // avoid extra reflows in common cases. Maybe we should be examining + // mStylePosition->BSize(wm).GetUnit() for that purpose? + // + // Note that we _also_ need to set the BResize flag if we have auto + // ComputedBSize() and a dirty subtree, since that might require us to + // change BSize due to kids having been added or removed. + SetBResize(mCBReflowInput->IsBResizeForWM(wm)); + if (ComputedBSize() == NS_AUTOHEIGHT) { + SetBResize(IsBResize() || NS_SUBTREE_DIRTY(mFrame)); + } + } else if (ComputedBSize() == NS_AUTOHEIGHT) { + if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && + mCBReflowInput) { + SetBResize(mCBReflowInput->IsBResizeForWM(wm)); + } else { + SetBResize(IsIResize()); + } + SetBResize(IsBResize() || NS_SUBTREE_DIRTY(mFrame)); + } else { + // not 'auto' block-size + SetBResize(mFrame->BSize(wm) != + ComputedBSize() + ComputedLogicalBorderPadding().BStartEnd(wm)); + } + + bool dependsOnCBBSize = + (mStylePosition->BSizeDependsOnContainer(wm) && + // FIXME: condition this on not-abspos? + mStylePosition->BSize(wm).GetUnit() != eStyleUnit_Auto) || + mStylePosition->MinBSizeDependsOnContainer(wm) || + mStylePosition->MaxBSizeDependsOnContainer(wm) || + mStylePosition->OffsetHasPercent(wm.PhysicalSide(eLogicalSideBStart)) || + mStylePosition->mOffset.GetBEndUnit(wm) != eStyleUnit_Auto || + mFrame->IsXULBoxFrame(); + + if (mStyleText->mLineHeight.GetUnit() == eStyleUnit_Enumerated) { + NS_ASSERTION(mStyleText->mLineHeight.GetIntValue() == + NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT, + "bad line-height value"); + + // line-height depends on block bsize + mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + // but only on containing blocks if this frame is not a suitable block + dependsOnCBBSize |= !nsLayoutUtils::IsNonWrapperBlock(mFrame); + } + + // If we're the descendant of a table cell that performs special bsize + // reflows and we could be the child that requires them, always set + // the block-axis resize in case this is the first pass before the + // special bsize reflow. However, don't do this if it actually is + // the special bsize reflow, since in that case it will already be + // set correctly above if we need it set. + if (!IsBResize() && mCBReflowInput && + (IS_TABLE_CELL(mCBReflowInput->mFrame->GetType()) || + mCBReflowInput->mFlags.mHeightDependsOnAncestorCell) && + !mCBReflowInput->mFlags.mSpecialBSizeReflow && + dependsOnCBBSize) { + SetBResize(true); + mFlags.mHeightDependsOnAncestorCell = true; + } + + // Set NS_FRAME_CONTAINS_RELATIVE_BSIZE if it's needed. + + // It would be nice to check that |ComputedBSize != NS_AUTOHEIGHT| + // &&ed with the percentage bsize check. However, this doesn't get + // along with table special bsize reflows, since a special bsize + // reflow (a quirk that makes such percentage height work on children + // of table cells) can cause not just a single percentage height to + // become fixed, but an entire descendant chain of percentage height + // to become fixed. + if (dependsOnCBBSize && mCBReflowInput) { + const ReflowInput *rs = this; + bool hitCBReflowInput = false; + do { + rs = rs->mParentReflowInput; + if (!rs) { + break; + } + + if (rs->mFrame->GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_BSIZE) + break; // no need to go further + rs->mFrame->AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + + // Keep track of whether we've hit the containing block, because + // we need to go at least that far. + if (rs == mCBReflowInput) { + hitCBReflowInput = true; + } + + // XXX What about orthogonal flows? It doesn't make sense to + // keep propagating this bit across an orthogonal boundary, + // where the meaning of BSize changes. Bug 1175517. + } while (!hitCBReflowInput || + (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && + !IsQuirkContainingBlockHeight(rs, rs->mFrame->GetType()))); + // Note: We actually don't need to set the + // NS_FRAME_CONTAINS_RELATIVE_BSIZE bit for the cases + // where we hit the early break statements in + // CalcQuirkContainingBlockHeight. But it doesn't hurt + // us to set the bit in these cases. + + } + if (mFrame->GetStateBits() & NS_FRAME_IS_DIRTY) { + // If we're reflowing everything, then we'll find out if we need + // to re-set this. + mFrame->RemoveStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE); + } +} + +nscoord +ReflowInput::GetContainingBlockContentISize(WritingMode aWritingMode) const +{ + if (!mCBReflowInput) { + return 0; + } + return mCBReflowInput->GetWritingMode().IsOrthogonalTo(aWritingMode) + ? mCBReflowInput->ComputedBSize() + : mCBReflowInput->ComputedISize(); +} + +void +ReflowInput::InitFrameType(nsIAtom* aFrameType) +{ + const nsStyleDisplay *disp = mStyleDisplay; + nsCSSFrameType frameType; + + // Section 9.7 of the CSS2 spec indicates that absolute position + // takes precedence over float which takes precedence over display. + // XXXldb nsRuleNode::ComputeDisplayData should take care of this, right? + // Make sure the frame was actually moved out of the flow, and don't + // just assume what the style says, because we might not have had a + // useful float/absolute containing block + + DISPLAY_INIT_TYPE(mFrame, this); + + if (aFrameType == nsGkAtoms::tableFrame) { + mFrameType = NS_CSS_FRAME_TYPE_BLOCK; + return; + } + + NS_ASSERTION(mFrame->StyleDisplay()->IsAbsolutelyPositionedStyle() == + disp->IsAbsolutelyPositionedStyle(), + "Unexpected position style"); + NS_ASSERTION(mFrame->StyleDisplay()->IsFloatingStyle() == + disp->IsFloatingStyle(), "Unexpected float style"); + if (mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { + if (disp->IsAbsolutelyPositioned(mFrame)) { + frameType = NS_CSS_FRAME_TYPE_ABSOLUTE; + //XXXfr hack for making frames behave properly when in overflow container lists + // see bug 154892; need to revisit later + if (mFrame->GetPrevInFlow()) + frameType = NS_CSS_FRAME_TYPE_BLOCK; + } + else if (disp->IsFloating(mFrame)) { + frameType = NS_CSS_FRAME_TYPE_FLOATING; + } else { + NS_ASSERTION(disp->mDisplay == StyleDisplay::Popup, + "unknown out of flow frame type"); + frameType = NS_CSS_FRAME_TYPE_UNKNOWN; + } + } + else { + switch (GetDisplay()) { + case StyleDisplay::Block: + case StyleDisplay::ListItem: + case StyleDisplay::Table: + case StyleDisplay::TableCaption: + case StyleDisplay::Flex: + case StyleDisplay::WebkitBox: + case StyleDisplay::Grid: + case StyleDisplay::RubyTextContainer: + frameType = NS_CSS_FRAME_TYPE_BLOCK; + break; + + case StyleDisplay::Inline: + case StyleDisplay::InlineBlock: + case StyleDisplay::InlineTable: + case StyleDisplay::InlineBox: + case StyleDisplay::InlineXulGrid: + case StyleDisplay::InlineStack: + case StyleDisplay::InlineFlex: + case StyleDisplay::WebkitInlineBox: + case StyleDisplay::InlineGrid: + case StyleDisplay::Ruby: + case StyleDisplay::RubyBase: + case StyleDisplay::RubyText: + case StyleDisplay::RubyBaseContainer: + frameType = NS_CSS_FRAME_TYPE_INLINE; + break; + + case StyleDisplay::TableCell: + case StyleDisplay::TableRowGroup: + case StyleDisplay::TableColumn: + case StyleDisplay::TableColumnGroup: + case StyleDisplay::TableHeaderGroup: + case StyleDisplay::TableFooterGroup: + case StyleDisplay::TableRow: + frameType = NS_CSS_FRAME_TYPE_INTERNAL_TABLE; + break; + + case StyleDisplay::None: + default: + frameType = NS_CSS_FRAME_TYPE_UNKNOWN; + break; + } + } + + // See if the frame is replaced + if (mFrame->IsFrameOfType(nsIFrame::eReplacedContainsBlock)) { + frameType = NS_FRAME_REPLACED_CONTAINS_BLOCK(frameType); + } else if (mFrame->IsFrameOfType(nsIFrame::eReplaced)) { + frameType = NS_FRAME_REPLACED(frameType); + } + + mFrameType = frameType; +} + +/* static */ void +ReflowInput::ComputeRelativeOffsets(WritingMode aWM, + nsIFrame* aFrame, + const LogicalSize& aCBSize, + nsMargin& aComputedOffsets) +{ + LogicalMargin offsets(aWM); + mozilla::css::Side inlineStart = aWM.PhysicalSide(eLogicalSideIStart); + mozilla::css::Side inlineEnd = aWM.PhysicalSide(eLogicalSideIEnd); + mozilla::css::Side blockStart = aWM.PhysicalSide(eLogicalSideBStart); + mozilla::css::Side blockEnd = aWM.PhysicalSide(eLogicalSideBEnd); + + const nsStylePosition* position = aFrame->StylePosition(); + + // Compute the 'inlineStart' and 'inlineEnd' values. 'inlineStart' + // moves the boxes to the end of the line, and 'inlineEnd' moves the + // boxes to the start of the line. The computed values are always: + // inlineStart=-inlineEnd + bool inlineStartIsAuto = + eStyleUnit_Auto == position->mOffset.GetUnit(inlineStart); + bool inlineEndIsAuto = + eStyleUnit_Auto == position->mOffset.GetUnit(inlineEnd); + + // If neither 'inlineStart' nor 'inlineEnd' is auto, then we're + // over-constrained and we ignore one of them + if (!inlineStartIsAuto && !inlineEndIsAuto) { + inlineEndIsAuto = true; + } + + if (inlineStartIsAuto) { + if (inlineEndIsAuto) { + // If both are 'auto' (their initial values), the computed values are 0 + offsets.IStart(aWM) = offsets.IEnd(aWM) = 0; + } else { + // 'inlineEnd' isn't 'auto' so compute its value + offsets.IEnd(aWM) = nsLayoutUtils:: + ComputeCBDependentValue(aCBSize.ISize(aWM), + position->mOffset.Get(inlineEnd)); + + // Computed value for 'inlineStart' is minus the value of 'inlineEnd' + offsets.IStart(aWM) = -offsets.IEnd(aWM); + } + + } else { + NS_ASSERTION(inlineEndIsAuto, "unexpected specified constraint"); + + // 'InlineStart' isn't 'auto' so compute its value + offsets.IStart(aWM) = nsLayoutUtils:: + ComputeCBDependentValue(aCBSize.ISize(aWM), + position->mOffset.Get(inlineStart)); + + // Computed value for 'inlineEnd' is minus the value of 'inlineStart' + offsets.IEnd(aWM) = -offsets.IStart(aWM); + } + + // Compute the 'blockStart' and 'blockEnd' values. The 'blockStart' + // and 'blockEnd' properties move relatively positioned elements in + // the block progression direction. They also must be each other's + // negative + bool blockStartIsAuto = + eStyleUnit_Auto == position->mOffset.GetUnit(blockStart); + bool blockEndIsAuto = + eStyleUnit_Auto == position->mOffset.GetUnit(blockEnd); + + // Check for percentage based values and a containing block block-size + // that depends on the content block-size. Treat them like 'auto' + if (NS_AUTOHEIGHT == aCBSize.BSize(aWM)) { + if (position->OffsetHasPercent(blockStart)) { + blockStartIsAuto = true; + } + if (position->OffsetHasPercent(blockEnd)) { + blockEndIsAuto = true; + } + } + + // If neither is 'auto', 'block-end' is ignored + if (!blockStartIsAuto && !blockEndIsAuto) { + blockEndIsAuto = true; + } + + if (blockStartIsAuto) { + if (blockEndIsAuto) { + // If both are 'auto' (their initial values), the computed values are 0 + offsets.BStart(aWM) = offsets.BEnd(aWM) = 0; + } else { + // 'blockEnd' isn't 'auto' so compute its value + offsets.BEnd(aWM) = nsLayoutUtils:: + ComputeBSizeDependentValue(aCBSize.BSize(aWM), + position->mOffset.Get(blockEnd)); + + // Computed value for 'blockStart' is minus the value of 'blockEnd' + offsets.BStart(aWM) = -offsets.BEnd(aWM); + } + + } else { + NS_ASSERTION(blockEndIsAuto, "unexpected specified constraint"); + + // 'blockStart' isn't 'auto' so compute its value + offsets.BStart(aWM) = nsLayoutUtils:: + ComputeBSizeDependentValue(aCBSize.BSize(aWM), + position->mOffset.Get(blockStart)); + + // Computed value for 'blockEnd' is minus the value of 'blockStart' + offsets.BEnd(aWM) = -offsets.BStart(aWM); + } + + // Convert the offsets to physical coordinates and store them on the frame + aComputedOffsets = offsets.GetPhysicalMargin(aWM); + FrameProperties props = aFrame->Properties(); + nsMargin* physicalOffsets = props.Get(nsIFrame::ComputedOffsetProperty()); + if (physicalOffsets) { + *physicalOffsets = aComputedOffsets; + } else { + props.Set(nsIFrame::ComputedOffsetProperty(), + new nsMargin(aComputedOffsets)); + } +} + +/* static */ void +ReflowInput::ApplyRelativePositioning(nsIFrame* aFrame, + const nsMargin& aComputedOffsets, + nsPoint* aPosition) +{ + if (!aFrame->IsRelativelyPositioned()) { + NS_ASSERTION(!aFrame->Properties().Get(nsIFrame::NormalPositionProperty()), + "We assume that changing the 'position' property causes " + "frame reconstruction. If that ever changes, this code " + "should call " + "props.Delete(nsIFrame::NormalPositionProperty())"); + return; + } + + // Store the normal position + FrameProperties props = aFrame->Properties(); + nsPoint* normalPosition = props.Get(nsIFrame::NormalPositionProperty()); + if (normalPosition) { + *normalPosition = *aPosition; + } else { + props.Set(nsIFrame::NormalPositionProperty(), new nsPoint(*aPosition)); + } + + const nsStyleDisplay* display = aFrame->StyleDisplay(); + if (NS_STYLE_POSITION_RELATIVE == display->mPosition) { + *aPosition += nsPoint(aComputedOffsets.left, aComputedOffsets.top); + } else if (NS_STYLE_POSITION_STICKY == display->mPosition && + !aFrame->GetNextContinuation() && + !aFrame->GetPrevContinuation() && + !(aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) { + // Sticky positioning for elements with multiple frames needs to be + // computed all at once. We can't safely do that here because we might be + // partway through (re)positioning the frames, so leave it until the scroll + // container reflows and calls StickyScrollContainer::UpdatePositions. + // For single-frame sticky positioned elements, though, go ahead and apply + // it now to avoid unnecessary overflow updates later. + StickyScrollContainer* ssc = + StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame); + if (ssc) { + *aPosition = ssc->ComputePosition(aFrame); + } + } +} + +nsIFrame* +ReflowInput::GetHypotheticalBoxContainer(nsIFrame* aFrame, + nscoord& aCBIStartEdge, + LogicalSize& aCBSize) const +{ + aFrame = aFrame->GetContainingBlock(); + NS_ASSERTION(aFrame != mFrame, "How did that happen?"); + + /* Now aFrame is the containing block we want */ + + /* Check whether the containing block is currently being reflowed. + If so, use the info from the reflow state. */ + const ReflowInput* state; + if (aFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { + for (state = mParentReflowInput; state && state->mFrame != aFrame; + state = state->mParentReflowInput) { + /* do nothing */ + } + } else { + state = nullptr; + } + + if (state) { + WritingMode wm = state->GetWritingMode(); + NS_ASSERTION(wm == aFrame->GetWritingMode(), "unexpected writing mode"); + aCBIStartEdge = state->ComputedLogicalBorderPadding().IStart(wm); + aCBSize = state->ComputedSize(wm); + } else { + /* Didn't find a reflow state for aFrame. Just compute the information we + want, on the assumption that aFrame already knows its size. This really + ought to be true by now. */ + NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_IN_REFLOW), + "aFrame shouldn't be in reflow; we'll lie if it is"); + WritingMode wm = aFrame->GetWritingMode(); + LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(wm); + aCBIStartEdge = borderPadding.IStart(wm); + aCBSize = aFrame->GetLogicalSize(wm) - borderPadding.Size(wm); + } + + return aFrame; +} + +struct nsHypotheticalPosition { + // offset from inline-start edge of containing block (which is a padding edge) + nscoord mIStart; + // offset from block-start edge of containing block (which is a padding edge) + nscoord mBStart; + WritingMode mWritingMode; +}; + +static bool +GetIntrinsicSizeFor(nsIFrame* aFrame, nsSize& aIntrinsicSize, nsIAtom* aFrameType) +{ + // See if it is an image frame + bool success = false; + + // Currently the only type of replaced frame that we can get the intrinsic + // size for is an image frame + // XXX We should add back the GetReflowOutput() function and one of the + // things should be the intrinsic size... + if (aFrameType == nsGkAtoms::imageFrame) { + nsImageFrame* imageFrame = (nsImageFrame*)aFrame; + + if (NS_SUCCEEDED(imageFrame->GetIntrinsicImageSize(aIntrinsicSize))) { + success = (aIntrinsicSize != nsSize(0, 0)); + } + } + return success; +} + +/** + * aInsideBoxSizing returns the part of the padding, border, and margin + * in the aAxis dimension that goes inside the edge given by box-sizing; + * aOutsideBoxSizing returns the rest. + */ +void +ReflowInput::CalculateBorderPaddingMargin( + LogicalAxis aAxis, + nscoord aContainingBlockSize, + nscoord* aInsideBoxSizing, + nscoord* aOutsideBoxSizing) const +{ + WritingMode wm = GetWritingMode(); + mozilla::css::Side startSide = + wm.PhysicalSide(MakeLogicalSide(aAxis, eLogicalEdgeStart)); + mozilla::css::Side endSide = + wm.PhysicalSide(MakeLogicalSide(aAxis, eLogicalEdgeEnd)); + + nsMargin styleBorder = mStyleBorder->GetComputedBorder(); + nscoord borderStartEnd = + styleBorder.Side(startSide) + styleBorder.Side(endSide); + + nscoord paddingStartEnd, marginStartEnd; + + // See if the style system can provide us the padding directly + nsMargin stylePadding; + if (mStylePadding->GetPadding(stylePadding)) { + paddingStartEnd = + stylePadding.Side(startSide) + stylePadding.Side(endSide); + } else { + // We have to compute the start and end values + nscoord start, end; + start = nsLayoutUtils:: + ComputeCBDependentValue(aContainingBlockSize, + mStylePadding->mPadding.Get(startSide)); + end = nsLayoutUtils:: + ComputeCBDependentValue(aContainingBlockSize, + mStylePadding->mPadding.Get(endSide)); + paddingStartEnd = start + end; + } + + // See if the style system can provide us the margin directly + nsMargin styleMargin; + if (mStyleMargin->GetMargin(styleMargin)) { + marginStartEnd = + styleMargin.Side(startSide) + styleMargin.Side(endSide); + } else { + nscoord start, end; + // We have to compute the start and end values + if (eStyleUnit_Auto == mStyleMargin->mMargin.GetUnit(startSide)) { + // XXX FIXME (or does CalculateBlockSideMargins do this?) + start = 0; // just ignore + } else { + start = nsLayoutUtils:: + ComputeCBDependentValue(aContainingBlockSize, + mStyleMargin->mMargin.Get(startSide)); + } + if (eStyleUnit_Auto == mStyleMargin->mMargin.GetUnit(endSide)) { + // XXX FIXME (or does CalculateBlockSideMargins do this?) + end = 0; // just ignore + } else { + end = nsLayoutUtils:: + ComputeCBDependentValue(aContainingBlockSize, + mStyleMargin->mMargin.Get(endSide)); + } + marginStartEnd = start + end; + } + + nscoord outside = paddingStartEnd + borderStartEnd + marginStartEnd; + nscoord inside = 0; + if (mStylePosition->mBoxSizing == StyleBoxSizing::Border) { + inside = borderStartEnd + paddingStartEnd; + } + outside -= inside; + *aInsideBoxSizing = inside; + *aOutsideBoxSizing = outside; + return; +} + +/** + * Returns true iff a pre-order traversal of the normal child + * frames rooted at aFrame finds no non-empty frame before aDescendant. + */ +static bool AreAllEarlierInFlowFramesEmpty(nsIFrame* aFrame, + nsIFrame* aDescendant, bool* aFound) { + if (aFrame == aDescendant) { + *aFound = true; + return true; + } + if (!aFrame->IsSelfEmpty()) { + *aFound = false; + return false; + } + for (nsIFrame* f : aFrame->PrincipalChildList()) { + bool allEmpty = AreAllEarlierInFlowFramesEmpty(f, aDescendant, aFound); + if (*aFound || !allEmpty) { + return allEmpty; + } + } + *aFound = false; + return true; +} + +// Calculate the position of the hypothetical box that the element would have +// if it were in the flow. +// The values returned are relative to the padding edge of the absolute +// containing block. The writing-mode of the hypothetical box position will +// have the same block direction as the absolute containing block, but may +// differ in inline-bidi direction. +// In the code below, |cbrs->frame| is the absolute containing block, while +// |containingBlock| is the nearest block container of the placeholder frame, +// which may be different from the absolute containing block. +void +ReflowInput::CalculateHypotheticalPosition + (nsPresContext* aPresContext, + nsIFrame* aPlaceholderFrame, + const ReflowInput* cbrs, + nsHypotheticalPosition& aHypotheticalPos, + nsIAtom* aFrameType) const +{ + NS_ASSERTION(mStyleDisplay->mOriginalDisplay != StyleDisplay::None, + "mOriginalDisplay has not been properly initialized"); + + // Find the nearest containing block frame to the placeholder frame, + // and its inline-start edge and width. + nscoord blockIStartContentEdge; + // Dummy writing mode for blockContentSize, will be changed as needed by + // GetHypotheticalBoxContainer. + WritingMode cbwm = cbrs->GetWritingMode(); + LogicalSize blockContentSize(cbwm); + nsIFrame* containingBlock = + GetHypotheticalBoxContainer(aPlaceholderFrame, blockIStartContentEdge, + blockContentSize); + // Now blockContentSize is in containingBlock's writing mode. + + // If it's a replaced element and it has a 'auto' value for + //'inline size', see if we can get the intrinsic size. This will allow + // us to exactly determine both the inline edges + WritingMode wm = containingBlock->GetWritingMode(); + + nsStyleCoord styleISize = mStylePosition->ISize(wm); + bool isAutoISize = styleISize.GetUnit() == eStyleUnit_Auto; + nsSize intrinsicSize; + bool knowIntrinsicSize = false; + if (NS_FRAME_IS_REPLACED(mFrameType) && isAutoISize) { + // See if we can get the intrinsic size of the element + knowIntrinsicSize = GetIntrinsicSizeFor(mFrame, intrinsicSize, aFrameType); + } + + // See if we can calculate what the box inline size would have been if + // the element had been in the flow + nscoord boxISize; + bool knowBoxISize = false; + if ((StyleDisplay::Inline == mStyleDisplay->mOriginalDisplay) && + !NS_FRAME_IS_REPLACED(mFrameType)) { + // For non-replaced inline-level elements the 'inline size' property + // doesn't apply, so we don't know what the inline size would have + // been without reflowing it + + } else { + // It's either a replaced inline-level element or a block-level element + + // Determine the total amount of inline direction + // border/padding/margin that the element would have had if it had + // been in the flow. Note that we ignore any 'auto' and 'inherit' + // values + nscoord insideBoxSizing, outsideBoxSizing; + CalculateBorderPaddingMargin(eLogicalAxisInline, + blockContentSize.ISize(wm), + &insideBoxSizing, &outsideBoxSizing); + + if (NS_FRAME_IS_REPLACED(mFrameType) && isAutoISize) { + // It's a replaced element with an 'auto' inline size so the box + // inline size is its intrinsic size plus any border/padding/margin + if (knowIntrinsicSize) { + boxISize = LogicalSize(wm, intrinsicSize).ISize(wm) + + outsideBoxSizing + insideBoxSizing; + knowBoxISize = true; + } + + } else if (isAutoISize) { + // The box inline size is the containing block inline size + boxISize = blockContentSize.ISize(wm); + knowBoxISize = true; + + } else { + // We need to compute it. It's important we do this, because if it's + // percentage based this computed value may be different from the computed + // value calculated using the absolute containing block width + boxISize = ComputeISizeValue(blockContentSize.ISize(wm), + insideBoxSizing, outsideBoxSizing, + styleISize) + + insideBoxSizing + outsideBoxSizing; + knowBoxISize = true; + } + } + + // Get the placeholder x-offset and y-offset in the coordinate + // space of its containing block + // XXXbz the placeholder is not fully reflowed yet if our containing block is + // relatively positioned... + nsSize containerSize = containingBlock->GetStateBits() & NS_FRAME_IN_REFLOW + ? cbrs->ComputedSizeAsContainerIfConstrained() + : containingBlock->GetSize(); + LogicalPoint + placeholderOffset(wm, aPlaceholderFrame->GetOffsetTo(containingBlock), + containerSize); + + // First, determine the hypothetical box's mBStart. We want to check the + // content insertion frame of containingBlock for block-ness, but make + // sure to compute all coordinates in the coordinate system of + // containingBlock. + nsBlockFrame* blockFrame = + nsLayoutUtils::GetAsBlock(containingBlock->GetContentInsertionFrame()); + if (blockFrame) { + // Use a null containerSize to convert a LogicalPoint functioning as a + // vector into a physical nsPoint vector. + const nsSize nullContainerSize; + LogicalPoint blockOffset(wm, blockFrame->GetOffsetTo(containingBlock), + nullContainerSize); + bool isValid; + nsBlockInFlowLineIterator iter(blockFrame, aPlaceholderFrame, &isValid); + if (!isValid) { + // Give up. We're probably dealing with somebody using + // position:absolute inside native-anonymous content anyway. + aHypotheticalPos.mBStart = placeholderOffset.B(wm); + } else { + NS_ASSERTION(iter.GetContainer() == blockFrame, + "Found placeholder in wrong block!"); + nsBlockFrame::LineIterator lineBox = iter.GetLine(); + + // How we determine the hypothetical box depends on whether the element + // would have been inline-level or block-level + LogicalRect lineBounds = + lineBox->GetBounds().ConvertTo(wm, lineBox->mWritingMode, + lineBox->mContainerSize); + if (mStyleDisplay->IsOriginalDisplayInlineOutsideStyle()) { + // Use the block-start of the inline box which the placeholder lives in + // as the hypothetical box's block-start. + aHypotheticalPos.mBStart = lineBounds.BStart(wm) + blockOffset.B(wm); + } else { + // The element would have been block-level which means it would + // be below the line containing the placeholder frame, unless + // all the frames before it are empty. In that case, it would + // have been just before this line. + // XXXbz the line box is not fully reflowed yet if our + // containing block is relatively positioned... + if (lineBox != iter.End()) { + nsIFrame * firstFrame = lineBox->mFirstChild; + bool found = false; + bool allEmpty = true; + while (firstFrame) { // See bug 223064 + allEmpty = AreAllEarlierInFlowFramesEmpty(firstFrame, + aPlaceholderFrame, &found); + if (found || !allEmpty) + break; + firstFrame = firstFrame->GetNextSibling(); + } + NS_ASSERTION(firstFrame, "Couldn't find placeholder!"); + + if (allEmpty) { + // The top of the hypothetical box is the top of the line + // containing the placeholder, since there is nothing in the + // line before our placeholder except empty frames. + aHypotheticalPos.mBStart = + lineBounds.BStart(wm) + blockOffset.B(wm); + } else { + // The top of the hypothetical box is just below the line + // containing the placeholder. + aHypotheticalPos.mBStart = + lineBounds.BEnd(wm) + blockOffset.B(wm); + } + } else { + // Just use the placeholder's block-offset wrt the containing block + aHypotheticalPos.mBStart = placeholderOffset.B(wm); + } + } + } + } else { + // The containing block is not a block, so it's probably something + // like a XUL box, etc. + // Just use the placeholder's block-offset + aHypotheticalPos.mBStart = placeholderOffset.B(wm); + } + + // Second, determine the hypothetical box's mIStart. + // How we determine the hypothetical box depends on whether the element + // would have been inline-level or block-level + if (mStyleDisplay->IsOriginalDisplayInlineOutsideStyle() || + mFlags.mIOffsetsNeedCSSAlign) { + // The placeholder represents the IStart edge of the hypothetical box. + // (Or if mFlags.mIOffsetsNeedCSSAlign is set, it represents the IStart + // edge of the Alignment Container.) + aHypotheticalPos.mIStart = placeholderOffset.I(wm); + } else { + aHypotheticalPos.mIStart = blockIStartContentEdge; + } + + // The current coordinate space is that of the nearest block to the placeholder. + // Convert to the coordinate space of the absolute containing block + // One weird thing here is that for fixed-positioned elements we want to do + // the conversion incorrectly; specifically we want to ignore any scrolling + // that may have happened; + nsPoint cbOffset; + if (mStyleDisplay->mPosition == NS_STYLE_POSITION_FIXED && + // Exclude cases inside -moz-transform where fixed is like absolute. + nsLayoutUtils::IsReallyFixedPos(mFrame)) { + // In this case, cbrs->frame will likely be an ancestor of + // containingBlock, so can just walk our way up the frame tree. + // Make sure to not add positions of frames whose parent is a + // scrollFrame, since we're doing fixed positioning, which assumes + // everything is scrolled to (0,0). + cbOffset.MoveTo(0, 0); + do { + cbOffset += containingBlock->GetPositionIgnoringScrolling(); + nsContainerFrame* parent = containingBlock->GetParent(); + if (!parent) { + // Oops, our absolute containing block isn't an ancestor of the + // placeholder's containing block. This can happen if the placeholder + // is pushed to a different page in a printing context. 'cbOffset' is + // currently relative to the root frame (containingBlock) - so just + // subtract the offset to the absolute containing block to make it + // relative to that. + cbOffset -= containingBlock->GetOffsetTo(cbrs->mFrame); + break; + } + containingBlock = parent; + } while (containingBlock != cbrs->mFrame); + } else { + // XXXldb We need to either ignore scrolling for the absolute + // positioning case too (and take the incompatibility) or figure out + // how to make these positioned elements actually *move* when we + // scroll, and thus avoid the resulting incremental reflow bugs. + cbOffset = containingBlock->GetOffsetTo(cbrs->mFrame); + } + nsSize cbrsSize = cbrs->ComputedSizeAsContainerIfConstrained(); + LogicalPoint logCBOffs(wm, cbOffset, cbrsSize - containerSize); + aHypotheticalPos.mIStart += logCBOffs.I(wm); + aHypotheticalPos.mBStart += logCBOffs.B(wm); + + // The specified offsets are relative to the absolute containing block's + // padding edge and our current values are relative to the border edge, so + // translate. + LogicalMargin border = + cbrs->ComputedLogicalBorderPadding() - cbrs->ComputedLogicalPadding(); + border = border.ConvertTo(wm, cbrs->GetWritingMode()); + aHypotheticalPos.mIStart -= border.IStart(wm); + aHypotheticalPos.mBStart -= border.BStart(wm); + + // At this point, we have computed aHypotheticalPos using the writing mode + // of the placeholder's containing block. + + if (cbwm.GetBlockDir() != wm.GetBlockDir()) { + // If the block direction we used in calculating aHypotheticalPos does not + // match the absolute containing block's, we need to convert here so that + // aHypotheticalPos is usable in relation to the absolute containing block. + // This requires computing or measuring the abspos frame's block-size, + // which is not otherwise required/used here (as aHypotheticalPos + // records only the block-start coordinate). + + // This is similar to the inline-size calculation for a replaced + // inline-level element or a block-level element (above), except that + // 'auto' sizing is handled differently in the block direction for non- + // replaced elements and replaced elements lacking an intrinsic size. + + // Determine the total amount of block direction + // border/padding/margin that the element would have had if it had + // been in the flow. Note that we ignore any 'auto' and 'inherit' + // values. + nscoord insideBoxSizing, outsideBoxSizing; + CalculateBorderPaddingMargin(eLogicalAxisBlock, + blockContentSize.BSize(wm), + &insideBoxSizing, &outsideBoxSizing); + + nscoord boxBSize; + nsStyleCoord styleBSize = mStylePosition->BSize(wm); + bool isAutoBSize = styleBSize.GetUnit() == eStyleUnit_Auto; + if (isAutoBSize) { + if (NS_FRAME_IS_REPLACED(mFrameType) && knowIntrinsicSize) { + // It's a replaced element with an 'auto' block size so the box + // block size is its intrinsic size plus any border/padding/margin + boxBSize = LogicalSize(wm, intrinsicSize).BSize(wm) + + outsideBoxSizing + insideBoxSizing; + } else { + // XXX Bug 1191801 + // Figure out how to get the correct boxBSize here (need to reflow the + // positioned frame?) + boxBSize = 0; + } + } else { + // We need to compute it. It's important we do this, because if it's + // percentage-based this computed value may be different from the + // computed value calculated using the absolute containing block height. + boxBSize = nsLayoutUtils::ComputeBSizeValue(blockContentSize.BSize(wm), + insideBoxSizing, styleBSize) + + insideBoxSizing + outsideBoxSizing; + } + + LogicalSize boxSize(wm, knowBoxISize ? boxISize : 0, boxBSize); + + LogicalPoint origin(wm, aHypotheticalPos.mIStart, + aHypotheticalPos.mBStart); + origin = origin.ConvertTo(cbwm, wm, cbrsSize - + boxSize.GetPhysicalSize(wm)); + + aHypotheticalPos.mIStart = origin.I(cbwm); + aHypotheticalPos.mBStart = origin.B(cbwm); + aHypotheticalPos.mWritingMode = cbwm; + } else { + aHypotheticalPos.mWritingMode = wm; + } +} + +void +ReflowInput::InitAbsoluteConstraints(nsPresContext* aPresContext, + const ReflowInput* cbrs, + const LogicalSize& aCBSize, + nsIAtom* aFrameType) +{ + WritingMode wm = GetWritingMode(); + WritingMode cbwm = cbrs->GetWritingMode(); + NS_PRECONDITION(aCBSize.BSize(cbwm) != NS_AUTOHEIGHT, + "containing block bsize must be constrained"); + + NS_ASSERTION(aFrameType != nsGkAtoms::tableFrame, + "InitAbsoluteConstraints should not be called on table frames"); + NS_ASSERTION(mFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW, + "Why are we here?"); + + const auto& styleOffset = mStylePosition->mOffset; + bool iStartIsAuto = styleOffset.GetIStartUnit(cbwm) == eStyleUnit_Auto; + bool iEndIsAuto = styleOffset.GetIEndUnit(cbwm) == eStyleUnit_Auto; + bool bStartIsAuto = styleOffset.GetBStartUnit(cbwm) == eStyleUnit_Auto; + bool bEndIsAuto = styleOffset.GetBEndUnit(cbwm) == eStyleUnit_Auto; + + // If both 'left' and 'right' are 'auto' or both 'top' and 'bottom' are + // 'auto', then compute the hypothetical box position where the element would + // have been if it had been in the flow + nsHypotheticalPosition hypotheticalPos; + if ((iStartIsAuto && iEndIsAuto) || (bStartIsAuto && bEndIsAuto)) { + nsIFrame* placeholderFrame = + aPresContext->PresShell()->GetPlaceholderFrameFor(mFrame); + NS_ASSERTION(placeholderFrame, "no placeholder frame"); + + if (placeholderFrame->HasAnyStateBits( + PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) { + DebugOnly<nsIFrame*> placeholderParent = placeholderFrame->GetParent(); + MOZ_ASSERT(placeholderParent, "shouldn't have unparented placeholders"); + MOZ_ASSERT(placeholderParent->IsFlexOrGridContainer(), + "This flag should only be set on grid/flex children"); + + // If the (as-yet unknown) static position will determine the inline + // and/or block offsets, set flags to note those offsets aren't valid + // until we can do CSS Box Alignment on the OOF frame. + mFlags.mIOffsetsNeedCSSAlign = (iStartIsAuto && iEndIsAuto); + mFlags.mBOffsetsNeedCSSAlign = (bStartIsAuto && bEndIsAuto); + } + + if (mFlags.mStaticPosIsCBOrigin) { + hypotheticalPos.mWritingMode = cbwm; + hypotheticalPos.mIStart = nscoord(0); + hypotheticalPos.mBStart = nscoord(0); + } else { + CalculateHypotheticalPosition(aPresContext, placeholderFrame, cbrs, + hypotheticalPos, aFrameType); + } + } + + // Initialize the 'left' and 'right' computed offsets + // XXX Handle new 'static-position' value... + + // Size of the containing block in its writing mode + LogicalSize cbSize = aCBSize; + LogicalMargin offsets = ComputedLogicalOffsets().ConvertTo(cbwm, wm); + + if (iStartIsAuto) { + offsets.IStart(cbwm) = 0; + } else { + offsets.IStart(cbwm) = nsLayoutUtils:: + ComputeCBDependentValue(cbSize.ISize(cbwm), styleOffset.GetIStart(cbwm)); + } + if (iEndIsAuto) { + offsets.IEnd(cbwm) = 0; + } else { + offsets.IEnd(cbwm) = nsLayoutUtils:: + ComputeCBDependentValue(cbSize.ISize(cbwm), styleOffset.GetIEnd(cbwm)); + } + + if (iStartIsAuto && iEndIsAuto) { + if (cbwm.IsBidiLTR() != hypotheticalPos.mWritingMode.IsBidiLTR()) { + offsets.IEnd(cbwm) = hypotheticalPos.mIStart; + iEndIsAuto = false; + } else { + offsets.IStart(cbwm) = hypotheticalPos.mIStart; + iStartIsAuto = false; + } + } + + if (bStartIsAuto) { + offsets.BStart(cbwm) = 0; + } else { + offsets.BStart(cbwm) = nsLayoutUtils:: + ComputeBSizeDependentValue(cbSize.BSize(cbwm), + styleOffset.GetBStart(cbwm)); + } + if (bEndIsAuto) { + offsets.BEnd(cbwm) = 0; + } else { + offsets.BEnd(cbwm) = nsLayoutUtils:: + ComputeBSizeDependentValue(cbSize.BSize(cbwm), + styleOffset.GetBEnd(cbwm)); + } + + if (bStartIsAuto && bEndIsAuto) { + // Treat 'top' like 'static-position' + offsets.BStart(cbwm) = hypotheticalPos.mBStart; + bStartIsAuto = false; + } + + SetComputedLogicalOffsets(offsets.ConvertTo(wm, cbwm)); + + typedef nsIFrame::ComputeSizeFlags ComputeSizeFlags; + ComputeSizeFlags computeSizeFlags = ComputeSizeFlags::eDefault; + if (mFlags.mIClampMarginBoxMinSize) { + computeSizeFlags = ComputeSizeFlags(computeSizeFlags | + ComputeSizeFlags::eIClampMarginBoxMinSize); + } + if (mFlags.mBClampMarginBoxMinSize) { + computeSizeFlags = ComputeSizeFlags(computeSizeFlags | + ComputeSizeFlags::eBClampMarginBoxMinSize); + } + if (mFlags.mShrinkWrap) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + } + if (mFlags.mUseAutoBSize) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eUseAutoBSize); + } + if (wm.IsOrthogonalTo(cbwm)) { + if (bStartIsAuto || bEndIsAuto) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + } + } else { + if (iStartIsAuto || iEndIsAuto) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + } + } + + LogicalSize computedSize(wm); + { + AutoMaybeDisableFontInflation an(mFrame); + + computedSize = + mFrame->ComputeSize(mRenderingContext, wm, cbSize.ConvertTo(wm, cbwm), + cbSize.ConvertTo(wm, cbwm).ISize(wm), // XXX or AvailableISize()? + ComputedLogicalMargin().Size(wm) + + ComputedLogicalOffsets().Size(wm), + ComputedLogicalBorderPadding().Size(wm) - + ComputedLogicalPadding().Size(wm), + ComputedLogicalPadding().Size(wm), + computeSizeFlags); + ComputedISize() = computedSize.ISize(wm); + ComputedBSize() = computedSize.BSize(wm); + NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size"); + NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE || + ComputedBSize() >= 0, "Bogus block-size"); + } + computedSize = computedSize.ConvertTo(cbwm, wm); + + // XXX Now that we have ComputeSize, can we condense many of the + // branches off of widthIsAuto? + + LogicalMargin margin = ComputedLogicalMargin().ConvertTo(cbwm, wm); + const LogicalMargin borderPadding = + ComputedLogicalBorderPadding().ConvertTo(cbwm, wm); + + bool iSizeIsAuto = eStyleUnit_Auto == mStylePosition->ISize(cbwm).GetUnit(); + if (iStartIsAuto) { + // We know 'right' is not 'auto' anymore thanks to the hypothetical + // box code above. + // Solve for 'left'. + if (iSizeIsAuto) { + // XXXldb This, and the corresponding code in + // nsAbsoluteContainingBlock.cpp, could probably go away now that + // we always compute widths. + offsets.IStart(cbwm) = NS_AUTOOFFSET; + } else { + offsets.IStart(cbwm) = + cbSize.ISize(cbwm) - offsets.IEnd(cbwm) - + computedSize.ISize(cbwm) - margin.IStartEnd(cbwm) - + borderPadding.IStartEnd(cbwm); + } + } else if (iEndIsAuto) { + // We know 'left' is not 'auto' anymore thanks to the hypothetical + // box code above. + // Solve for 'right'. + if (iSizeIsAuto) { + // XXXldb This, and the corresponding code in + // nsAbsoluteContainingBlock.cpp, could probably go away now that + // we always compute widths. + offsets.IEnd(cbwm) = NS_AUTOOFFSET; + } else { + offsets.IEnd(cbwm) = + cbSize.ISize(cbwm) - offsets.IStart(cbwm) - + computedSize.ISize(cbwm) - margin.IStartEnd(cbwm) - + borderPadding.IStartEnd(cbwm); + } + } else { + // Neither 'inline-start' nor 'inline-end' is 'auto'. + + if (wm.IsOrthogonalTo(cbwm)) { + // For orthogonal blocks, we need to handle the case where the block had + // unconstrained block-size, which mapped to unconstrained inline-size + // in the containing block's writing mode. + nscoord autoISize = cbSize.ISize(cbwm) - margin.IStartEnd(cbwm) - + borderPadding.IStartEnd(cbwm) - offsets.IStartEnd(cbwm); + if (autoISize < 0) { + autoISize = 0; + } + + if (computedSize.ISize(cbwm) == NS_UNCONSTRAINEDSIZE) { + // For non-replaced elements with block-size auto, the block-size + // fills the remaining space. + computedSize.ISize(cbwm) = autoISize; + + // XXX Do these need box-sizing adjustments? + LogicalSize maxSize = ComputedMaxSize(cbwm); + LogicalSize minSize = ComputedMinSize(cbwm); + if (computedSize.ISize(cbwm) > maxSize.ISize(cbwm)) { + computedSize.ISize(cbwm) = maxSize.ISize(cbwm); + } + if (computedSize.ISize(cbwm) < minSize.ISize(cbwm)) { + computedSize.ISize(cbwm) = minSize.ISize(cbwm); + } + } + } + + // However, the inline-size might + // still not fill all the available space (even though we didn't + // shrink-wrap) in case: + // * inline-size was specified + // * we're dealing with a replaced element + // * width was constrained by min- or max-inline-size. + + nscoord availMarginSpace = + aCBSize.ISize(cbwm) - offsets.IStartEnd(cbwm) - margin.IStartEnd(cbwm) - + borderPadding.IStartEnd(cbwm) - computedSize.ISize(cbwm); + bool marginIStartIsAuto = + eStyleUnit_Auto == mStyleMargin->mMargin.GetIStartUnit(cbwm); + bool marginIEndIsAuto = + eStyleUnit_Auto == mStyleMargin->mMargin.GetIEndUnit(cbwm); + + if (marginIStartIsAuto) { + if (marginIEndIsAuto) { + if (availMarginSpace < 0) { + // Note that this case is different from the neither-'auto' + // case below, where the spec says to ignore 'left'/'right'. + // Ignore the specified value for 'margin-right'. + margin.IEnd(cbwm) = availMarginSpace; + } else { + // Both 'margin-left' and 'margin-right' are 'auto', so they get + // equal values + margin.IStart(cbwm) = availMarginSpace / 2; + margin.IEnd(cbwm) = availMarginSpace - margin.IStart(cbwm); + } + } else { + // Just 'margin-left' is 'auto' + margin.IStart(cbwm) = availMarginSpace; + } + } else { + if (marginIEndIsAuto) { + // Just 'margin-right' is 'auto' + margin.IEnd(cbwm) = availMarginSpace; + } else { + // We're over-constrained so use the direction of the containing + // block to dictate which value to ignore. (And note that the + // spec says to ignore 'left' or 'right' rather than + // 'margin-left' or 'margin-right'.) + // Note that this case is different from the both-'auto' case + // above, where the spec says to ignore + // 'margin-left'/'margin-right'. + // Ignore the specified value for 'right'. + offsets.IEnd(cbwm) += availMarginSpace; + } + } + } + + bool bSizeIsAuto = eStyleUnit_Auto == mStylePosition->BSize(cbwm).GetUnit(); + if (bStartIsAuto) { + // solve for block-start + if (bSizeIsAuto) { + offsets.BStart(cbwm) = NS_AUTOOFFSET; + } else { + offsets.BStart(cbwm) = cbSize.BSize(cbwm) - margin.BStartEnd(cbwm) - + borderPadding.BStartEnd(cbwm) - computedSize.BSize(cbwm) - + offsets.BEnd(cbwm); + } + } else if (bEndIsAuto) { + // solve for block-end + if (bSizeIsAuto) { + offsets.BEnd(cbwm) = NS_AUTOOFFSET; + } else { + offsets.BEnd(cbwm) = cbSize.BSize(cbwm) - margin.BStartEnd(cbwm) - + borderPadding.BStartEnd(cbwm) - computedSize.BSize(cbwm) - + offsets.BStart(cbwm); + } + } else { + // Neither block-start nor -end is 'auto'. + nscoord autoBSize = cbSize.BSize(cbwm) - margin.BStartEnd(cbwm) - + borderPadding.BStartEnd(cbwm) - offsets.BStartEnd(cbwm); + if (autoBSize < 0) { + autoBSize = 0; + } + + if (computedSize.BSize(cbwm) == NS_UNCONSTRAINEDSIZE) { + // For non-replaced elements with block-size auto, the block-size + // fills the remaining space. + computedSize.BSize(cbwm) = autoBSize; + + // XXX Do these need box-sizing adjustments? + LogicalSize maxSize = ComputedMaxSize(cbwm); + LogicalSize minSize = ComputedMinSize(cbwm); + if (computedSize.BSize(cbwm) > maxSize.BSize(cbwm)) { + computedSize.BSize(cbwm) = maxSize.BSize(cbwm); + } + if (computedSize.BSize(cbwm) < minSize.BSize(cbwm)) { + computedSize.BSize(cbwm) = minSize.BSize(cbwm); + } + } + + // The block-size might still not fill all the available space in case: + // * bsize was specified + // * we're dealing with a replaced element + // * bsize was constrained by min- or max-bsize. + nscoord availMarginSpace = autoBSize - computedSize.BSize(cbwm); + bool marginBStartIsAuto = + eStyleUnit_Auto == mStyleMargin->mMargin.GetBStartUnit(cbwm); + bool marginBEndIsAuto = + eStyleUnit_Auto == mStyleMargin->mMargin.GetBEndUnit(cbwm); + + if (marginBStartIsAuto) { + if (marginBEndIsAuto) { + // Both 'margin-top' and 'margin-bottom' are 'auto', so they get + // equal values + margin.BStart(cbwm) = availMarginSpace / 2; + margin.BEnd(cbwm) = availMarginSpace - margin.BStart(cbwm); + } else { + // Just margin-block-start is 'auto' + margin.BStart(cbwm) = availMarginSpace; + } + } else { + if (marginBEndIsAuto) { + // Just margin-block-end is 'auto' + margin.BEnd(cbwm) = availMarginSpace; + } else { + // We're over-constrained so ignore the specified value for + // block-end. (And note that the spec says to ignore 'bottom' + // rather than 'margin-bottom'.) + offsets.BEnd(cbwm) += availMarginSpace; + } + } + } + ComputedBSize() = computedSize.ConvertTo(wm, cbwm).BSize(wm); + ComputedISize() = computedSize.ConvertTo(wm, cbwm).ISize(wm); + + SetComputedLogicalOffsets(offsets.ConvertTo(wm, cbwm)); + SetComputedLogicalMargin(margin.ConvertTo(wm, cbwm)); +} + +// This will not be converted to abstract coordinates because it's only +// used in CalcQuirkContainingBlockHeight +nscoord +GetBlockMarginBorderPadding(const ReflowInput* aReflowInput) +{ + nscoord result = 0; + if (!aReflowInput) return result; + + // zero auto margins + nsMargin margin = aReflowInput->ComputedPhysicalMargin(); + if (NS_AUTOMARGIN == margin.top) + margin.top = 0; + if (NS_AUTOMARGIN == margin.bottom) + margin.bottom = 0; + + result += margin.top + margin.bottom; + result += aReflowInput->ComputedPhysicalBorderPadding().top + + aReflowInput->ComputedPhysicalBorderPadding().bottom; + + return result; +} + +/* Get the height based on the viewport of the containing block specified + * in aReflowInput when the containing block has mComputedHeight == NS_AUTOHEIGHT + * This will walk up the chain of containing blocks looking for a computed height + * until it finds the canvas frame, or it encounters a frame that is not a block, + * area, or scroll frame. This handles compatibility with IE (see bug 85016 and bug 219693) + * + * When we encounter scrolledContent block frames, we skip over them, + * since they are guaranteed to not be useful for computing the containing block. + * + * See also IsQuirkContainingBlockHeight. + */ +static nscoord +CalcQuirkContainingBlockHeight(const ReflowInput* aCBReflowInput) +{ + const ReflowInput* firstAncestorRI = nullptr; // a candidate for html frame + const ReflowInput* secondAncestorRI = nullptr; // a candidate for body frame + + // initialize the default to NS_AUTOHEIGHT as this is the containings block + // computed height when this function is called. It is possible that we + // don't alter this height especially if we are restricted to one level + nscoord result = NS_AUTOHEIGHT; + + const ReflowInput* rs = aCBReflowInput; + for (; rs; rs = rs->mParentReflowInput) { + nsIAtom* frameType = rs->mFrame->GetType(); + // if the ancestor is auto height then skip it and continue up if it + // is the first block frame and possibly the body/html + if (nsGkAtoms::blockFrame == frameType || +#ifdef MOZ_XUL + nsGkAtoms::XULLabelFrame == frameType || +#endif + nsGkAtoms::scrollFrame == frameType) { + + secondAncestorRI = firstAncestorRI; + firstAncestorRI = rs; + + // If the current frame we're looking at is positioned, we don't want to + // go any further (see bug 221784). The behavior we want here is: 1) If + // not auto-height, use this as the percentage base. 2) If auto-height, + // keep looking, unless the frame is positioned. + if (NS_AUTOHEIGHT == rs->ComputedHeight()) { + if (rs->mFrame->IsAbsolutelyPositioned()) { + break; + } else { + continue; + } + } + } + else if (nsGkAtoms::canvasFrame == frameType) { + // Always continue on to the height calculation + } + else if (nsGkAtoms::pageContentFrame == frameType) { + nsIFrame* prevInFlow = rs->mFrame->GetPrevInFlow(); + // only use the page content frame for a height basis if it is the first in flow + if (prevInFlow) + break; + } + else { + break; + } + + // if the ancestor is the page content frame then the percent base is + // the avail height, otherwise it is the computed height + result = (nsGkAtoms::pageContentFrame == frameType) + ? rs->AvailableHeight() : rs->ComputedHeight(); + // if unconstrained - don't sutract borders - would result in huge height + if (NS_AUTOHEIGHT == result) return result; + + // if we got to the canvas or page content frame, then subtract out + // margin/border/padding for the BODY and HTML elements + if ((nsGkAtoms::canvasFrame == frameType) || + (nsGkAtoms::pageContentFrame == frameType)) { + + result -= GetBlockMarginBorderPadding(firstAncestorRI); + result -= GetBlockMarginBorderPadding(secondAncestorRI); + +#ifdef DEBUG + // make sure the first ancestor is the HTML and the second is the BODY + if (firstAncestorRI) { + nsIContent* frameContent = firstAncestorRI->mFrame->GetContent(); + if (frameContent) { + NS_ASSERTION(frameContent->IsHTMLElement(nsGkAtoms::html), + "First ancestor is not HTML"); + } + } + if (secondAncestorRI) { + nsIContent* frameContent = secondAncestorRI->mFrame->GetContent(); + if (frameContent) { + NS_ASSERTION(frameContent->IsHTMLElement(nsGkAtoms::body), + "Second ancestor is not BODY"); + } + } +#endif + + } + // if we got to the html frame (a block child of the canvas) ... + else if (nsGkAtoms::blockFrame == frameType && + rs->mParentReflowInput && + nsGkAtoms::canvasFrame == + rs->mParentReflowInput->mFrame->GetType()) { + // ... then subtract out margin/border/padding for the BODY element + result -= GetBlockMarginBorderPadding(secondAncestorRI); + } + break; + } + + // Make sure not to return a negative height here! + return std::max(result, 0); +} + +// Called by InitConstraints() to compute the containing block rectangle for +// the element. Handles the special logic for absolutely positioned elements +LogicalSize +ReflowInput::ComputeContainingBlockRectangle( + nsPresContext* aPresContext, + const ReflowInput* aContainingBlockRI) const +{ + // Unless the element is absolutely positioned, the containing block is + // formed by the content edge of the nearest block-level ancestor + LogicalSize cbSize = aContainingBlockRI->ComputedSize(); + + WritingMode wm = aContainingBlockRI->GetWritingMode(); + + // mFrameType for abs-pos tables is NS_CSS_FRAME_TYPE_BLOCK, so we need to + // special case them here. + if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE || + (mFrame->GetType() == nsGkAtoms::tableFrame && + mFrame->IsAbsolutelyPositioned() && + (mFrame->GetParent()->GetStateBits() & NS_FRAME_OUT_OF_FLOW))) { + // See if the ancestor is block-level or inline-level + if (NS_FRAME_GET_TYPE(aContainingBlockRI->mFrameType) == NS_CSS_FRAME_TYPE_INLINE) { + // Base our size on the actual size of the frame. In cases when this is + // completely bogus (eg initial reflow), this code shouldn't even be + // called, since the code in nsInlineFrame::Reflow will pass in + // the containing block dimensions to our constructor. + // XXXbz we should be taking the in-flows into account too, but + // that's very hard. + + LogicalMargin computedBorder = + aContainingBlockRI->ComputedLogicalBorderPadding() - + aContainingBlockRI->ComputedLogicalPadding(); + cbSize.ISize(wm) = aContainingBlockRI->mFrame->ISize(wm) - + computedBorder.IStartEnd(wm); + NS_ASSERTION(cbSize.ISize(wm) >= 0, + "Negative containing block isize!"); + cbSize.BSize(wm) = aContainingBlockRI->mFrame->BSize(wm) - + computedBorder.BStartEnd(wm); + NS_ASSERTION(cbSize.BSize(wm) >= 0, + "Negative containing block bsize!"); + } else { + // If the ancestor is block-level, the containing block is formed by the + // padding edge of the ancestor + cbSize.ISize(wm) += + aContainingBlockRI->ComputedLogicalPadding().IStartEnd(wm); + cbSize.BSize(wm) += + aContainingBlockRI->ComputedLogicalPadding().BStartEnd(wm); + } + } else { + // an element in quirks mode gets a containing block based on looking for a + // parent with a non-auto height if the element has a percent height + // Note: We don't emulate this quirk for percents in calc() or in + // vertical writing modes. + if (!wm.IsVertical() && + NS_AUTOHEIGHT == cbSize.BSize(wm)) { + if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && + mStylePosition->mHeight.GetUnit() == eStyleUnit_Percent) { + cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(aContainingBlockRI); + } + } + } + + return cbSize.ConvertTo(GetWritingMode(), wm); +} + +static eNormalLineHeightControl GetNormalLineHeightCalcControl(void) +{ + if (sNormalLineHeightControl == eUninitialized) { + // browser.display.normal_lineheight_calc_control is not user + // changeable, so no need to register callback for it. + int32_t val = + Preferences::GetInt("browser.display.normal_lineheight_calc_control", + eNoExternalLeading); + sNormalLineHeightControl = static_cast<eNormalLineHeightControl>(val); + } + return sNormalLineHeightControl; +} + +static inline bool +IsSideCaption(nsIFrame* aFrame, const nsStyleDisplay* aStyleDisplay, + WritingMode aWM) +{ + if (aStyleDisplay->mDisplay != StyleDisplay::TableCaption) { + return false; + } + uint8_t captionSide = aFrame->StyleTableBorder()->mCaptionSide; + return captionSide == NS_STYLE_CAPTION_SIDE_LEFT || + captionSide == NS_STYLE_CAPTION_SIDE_RIGHT; +} + +// Flex/grid items resolve block-axis percentage margin & padding against the +// containing block block-size (also for abs/fixed-pos child frames). +// For everything else: the CSS21 spec requires that margin and padding +// percentage values are calculated with respect to the inline-size of the +// containing block, even for margin & padding in the block axis. +static LogicalSize +OffsetPercentBasis(const nsIFrame* aFrame, + WritingMode aWM, + const LogicalSize& aContainingBlockSize) +{ + LogicalSize offsetPercentBasis = aContainingBlockSize; + if (MOZ_LIKELY(!aFrame->GetParent() || + !aFrame->GetParent()->IsFlexOrGridContainer())) { + offsetPercentBasis.BSize(aWM) = offsetPercentBasis.ISize(aWM); + } else if (offsetPercentBasis.BSize(aWM) == NS_AUTOHEIGHT) { + offsetPercentBasis.BSize(aWM) = 0; + } + + return offsetPercentBasis; +} + +// XXX refactor this code to have methods for each set of properties +// we are computing: width,height,line-height; margin; offsets + +void +ReflowInput::InitConstraints(nsPresContext* aPresContext, + const LogicalSize& aContainingBlockSize, + const nsMargin* aBorder, + const nsMargin* aPadding, + nsIAtom* aFrameType) +{ + WritingMode wm = GetWritingMode(); + DISPLAY_INIT_CONSTRAINTS(mFrame, this, + aContainingBlockSize.ISize(wm), + aContainingBlockSize.BSize(wm), + aBorder, aPadding); + + // If this is a reflow root, then set the computed width and + // height equal to the available space + if (nullptr == mParentReflowInput || mFlags.mDummyParentReflowInput) { + // XXXldb This doesn't mean what it used to! + InitOffsets(wm, OffsetPercentBasis(mFrame, wm, aContainingBlockSize), + aFrameType, mFlags, aBorder, aPadding); + // Override mComputedMargin since reflow roots start from the + // frame's boundary, which is inside the margin. + ComputedPhysicalMargin().SizeTo(0, 0, 0, 0); + ComputedPhysicalOffsets().SizeTo(0, 0, 0, 0); + + ComputedISize() = + AvailableISize() - ComputedLogicalBorderPadding().IStartEnd(wm); + if (ComputedISize() < 0) { + ComputedISize() = 0; + } + if (AvailableBSize() != NS_UNCONSTRAINEDSIZE) { + ComputedBSize() = + AvailableBSize() - ComputedLogicalBorderPadding().BStartEnd(wm); + if (ComputedBSize() < 0) { + ComputedBSize() = 0; + } + } else { + ComputedBSize() = NS_UNCONSTRAINEDSIZE; + } + + ComputedMinWidth() = ComputedMinHeight() = 0; + ComputedMaxWidth() = ComputedMaxHeight() = NS_UNCONSTRAINEDSIZE; + } else { + // Get the containing block reflow state + const ReflowInput* cbrs = mCBReflowInput; + NS_ASSERTION(nullptr != cbrs, "no containing block"); + + // If we weren't given a containing block width and height, then + // compute one + LogicalSize cbSize = (aContainingBlockSize == LogicalSize(wm, -1, -1)) + ? ComputeContainingBlockRectangle(aPresContext, cbrs) + : aContainingBlockSize; + + // See if the containing block height is based on the size of its + // content + nsIAtom* fType; + if (NS_AUTOHEIGHT == cbSize.BSize(wm)) { + // See if the containing block is a cell frame which needs + // to use the mComputedHeight of the cell instead of what the cell block passed in. + // XXX It seems like this could lead to bugs with min-height and friends + if (cbrs->mParentReflowInput) { + fType = cbrs->mFrame->GetType(); + if (IS_TABLE_CELL(fType)) { + // use the cell's computed block size + cbSize.BSize(wm) = cbrs->ComputedSize(wm).BSize(wm); + } + } + } + + // XXX Might need to also pass the CB height (not width) for page boxes, + // too, if we implement them. + + // For calculating positioning offsets, margins, borders and + // padding, we use the writing mode of the containing block + WritingMode cbwm = cbrs->GetWritingMode(); + InitOffsets(cbwm, OffsetPercentBasis(mFrame, cbwm, + cbSize.ConvertTo(cbwm, wm)), + aFrameType, mFlags, aBorder, aPadding); + + // For calculating the size of this box, we use its own writing mode + const nsStyleCoord &blockSize = mStylePosition->BSize(wm); + nsStyleUnit blockSizeUnit = blockSize.GetUnit(); + + // Check for a percentage based block size and a containing block + // block size that depends on the content block size + // XXX twiddling blockSizeUnit doesn't help anymore + // FIXME Shouldn't we fix that? + if (blockSize.HasPercent()) { + if (NS_AUTOHEIGHT == cbSize.BSize(wm)) { + // this if clause enables %-blockSize on replaced inline frames, + // such as images. See bug 54119. The else clause "blockSizeUnit = eStyleUnit_Auto;" + // used to be called exclusively. + if (NS_FRAME_REPLACED(NS_CSS_FRAME_TYPE_INLINE) == mFrameType || + NS_FRAME_REPLACED_CONTAINS_BLOCK( + NS_CSS_FRAME_TYPE_INLINE) == mFrameType) { + // Get the containing block reflow state + NS_ASSERTION(nullptr != cbrs, "no containing block"); + // in quirks mode, get the cb height using the special quirk method + if (!wm.IsVertical() && + eCompatibility_NavQuirks == aPresContext->CompatibilityMode()) { + if (!IS_TABLE_CELL(fType)) { + cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(cbrs); + if (cbSize.BSize(wm) == NS_AUTOHEIGHT) { + blockSizeUnit = eStyleUnit_Auto; + } + } + else { + blockSizeUnit = eStyleUnit_Auto; + } + } + // in standard mode, use the cb block size. if it's "auto", + // as will be the case by default in BODY, use auto block size + // as per CSS2 spec. + else + { + nscoord computedBSize = cbrs->ComputedSize(wm).BSize(wm); + if (NS_AUTOHEIGHT != computedBSize) { + cbSize.BSize(wm) = computedBSize; + } + else { + blockSizeUnit = eStyleUnit_Auto; + } + } + } + else { + // default to interpreting the blockSize like 'auto' + blockSizeUnit = eStyleUnit_Auto; + } + } + } + + // Compute our offsets if the element is relatively positioned. We + // need the correct containing block inline-size and block-size + // here, which is why we need to do it after all the quirks-n-such + // above. (If the element is sticky positioned, we need to wait + // until the scroll container knows its size, so we compute offsets + // from StickyScrollContainer::UpdatePositions.) + if (mStyleDisplay->IsRelativelyPositioned(mFrame) && + NS_STYLE_POSITION_RELATIVE == mStyleDisplay->mPosition) { + ComputeRelativeOffsets(cbwm, mFrame, cbSize.ConvertTo(cbwm, wm), + ComputedPhysicalOffsets()); + } else { + // Initialize offsets to 0 + ComputedPhysicalOffsets().SizeTo(0, 0, 0, 0); + } + + // Calculate the computed values for min and max properties. Note that + // this MUST come after we've computed our border and padding. + ComputeMinMaxValues(cbSize); + + // Calculate the computed inlineSize and blockSize. + // This varies by frame type. + + if (NS_CSS_FRAME_TYPE_INTERNAL_TABLE == mFrameType) { + // Internal table elements. The rules vary depending on the type. + // Calculate the computed isize + bool rowOrRowGroup = false; + const nsStyleCoord &inlineSize = mStylePosition->ISize(wm); + nsStyleUnit inlineSizeUnit = inlineSize.GetUnit(); + if ((StyleDisplay::TableRow == mStyleDisplay->mDisplay) || + (StyleDisplay::TableRowGroup == mStyleDisplay->mDisplay)) { + // 'inlineSize' property doesn't apply to table rows and row groups + inlineSizeUnit = eStyleUnit_Auto; + rowOrRowGroup = true; + } + + // calc() with percentages acts like auto on internal table elements + if (eStyleUnit_Auto == inlineSizeUnit || + (inlineSize.IsCalcUnit() && inlineSize.CalcHasPercent())) { + ComputedISize() = AvailableISize(); + + if ((ComputedISize() != NS_UNCONSTRAINEDSIZE) && !rowOrRowGroup){ + // Internal table elements don't have margins. Only tables and + // cells have border and padding + ComputedISize() -= ComputedLogicalBorderPadding().IStartEnd(wm); + if (ComputedISize() < 0) + ComputedISize() = 0; + } + NS_ASSERTION(ComputedISize() >= 0, "Bogus computed isize"); + + } else { + NS_ASSERTION(inlineSizeUnit == inlineSize.GetUnit(), + "unexpected inline size unit change"); + ComputedISize() = ComputeISizeValue(cbSize.ISize(wm), + mStylePosition->mBoxSizing, + inlineSize); + } + + // Calculate the computed block size + if ((StyleDisplay::TableColumn == mStyleDisplay->mDisplay) || + (StyleDisplay::TableColumnGroup == mStyleDisplay->mDisplay)) { + // 'blockSize' property doesn't apply to table columns and column groups + blockSizeUnit = eStyleUnit_Auto; + } + // calc() with percentages acts like 'auto' on internal table elements + if (eStyleUnit_Auto == blockSizeUnit || + (blockSize.IsCalcUnit() && blockSize.CalcHasPercent())) { + ComputedBSize() = NS_AUTOHEIGHT; + } else { + NS_ASSERTION(blockSizeUnit == blockSize.GetUnit(), + "unexpected block size unit change"); + ComputedBSize() = ComputeBSizeValue(cbSize.BSize(wm), + mStylePosition->mBoxSizing, + blockSize); + } + + // Doesn't apply to table elements + ComputedMinWidth() = ComputedMinHeight() = 0; + ComputedMaxWidth() = ComputedMaxHeight() = NS_UNCONSTRAINEDSIZE; + + } else if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE) { + // XXX not sure if this belongs here or somewhere else - cwk + InitAbsoluteConstraints(aPresContext, cbrs, cbSize.ConvertTo(cbrs->GetWritingMode(), wm), aFrameType); + } else { + AutoMaybeDisableFontInflation an(mFrame); + + bool isBlock = NS_CSS_FRAME_TYPE_BLOCK == NS_FRAME_GET_TYPE(mFrameType); + typedef nsIFrame::ComputeSizeFlags ComputeSizeFlags; + ComputeSizeFlags computeSizeFlags = + isBlock ? ComputeSizeFlags::eDefault : ComputeSizeFlags::eShrinkWrap; + if (mFlags.mIClampMarginBoxMinSize) { + computeSizeFlags = ComputeSizeFlags(computeSizeFlags | + ComputeSizeFlags::eIClampMarginBoxMinSize); + } + if (mFlags.mBClampMarginBoxMinSize) { + computeSizeFlags = ComputeSizeFlags(computeSizeFlags | + ComputeSizeFlags::eBClampMarginBoxMinSize); + } + if (mFlags.mShrinkWrap) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + } + if (mFlags.mUseAutoBSize) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eUseAutoBSize); + } + + nsIFrame* alignCB = mFrame->GetParent(); + nsIAtom* alignCBType = alignCB ? alignCB->GetType() : nullptr; + if (alignCBType == nsGkAtoms::tableWrapperFrame && + alignCB->GetParent()) { + auto parentCBType = alignCB->GetParent()->GetType(); + // XXX grid-specific for now; maybe remove this check after we address bug 799725 + if (parentCBType == nsGkAtoms::gridContainerFrame) { + alignCB = alignCB->GetParent(); + alignCBType = parentCBType; + } + } + if (alignCBType == nsGkAtoms::gridContainerFrame) { + // Shrink-wrap grid items that will be aligned (rather than stretched) + // in its inline axis. + auto inlineAxisAlignment = wm.IsOrthogonalTo(cbwm) ? + mStylePosition->UsedAlignSelf(mFrame->StyleContext()->GetParent()) : + mStylePosition->UsedJustifySelf(mFrame->StyleContext()->GetParent()); + if ((inlineAxisAlignment != NS_STYLE_ALIGN_STRETCH && + inlineAxisAlignment != NS_STYLE_ALIGN_NORMAL) || + mStyleMargin->mMargin.GetIStartUnit(wm) == eStyleUnit_Auto || + mStyleMargin->mMargin.GetIEndUnit(wm) == eStyleUnit_Auto) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + } + } else { + // Make sure legend frames with display:block and width:auto still + // shrink-wrap. + // Also shrink-wrap blocks that are orthogonal to their container. + if (isBlock && + ((aFrameType == nsGkAtoms::legendFrame && + mFrame->StyleContext()->GetPseudo() != nsCSSAnonBoxes::scrolledContent) || + (aFrameType == nsGkAtoms::scrollFrame && + mFrame->GetContentInsertionFrame()->GetType() == nsGkAtoms::legendFrame) || + (mCBReflowInput && + mCBReflowInput->GetWritingMode().IsOrthogonalTo(mWritingMode)))) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + } + + if (alignCBType == nsGkAtoms::flexContainerFrame) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eShrinkWrap); + + // If we're inside of a flex container that needs to measure our + // auto height, pass that information along to ComputeSize(). + if (mFlags.mIsFlexContainerMeasuringHeight) { + computeSizeFlags = + ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eUseAutoBSize); + } + } else { + MOZ_ASSERT(!mFlags.mIsFlexContainerMeasuringHeight, + "We're not in a flex container, so the flag " + "'mIsFlexContainerMeasuringHeight' shouldn't be set"); + } + } + + if (cbSize.ISize(wm) == NS_UNCONSTRAINEDSIZE) { + // For orthogonal flows, where we found a parent orthogonal-limit + // for AvailableISize() in Init(), we'll use the same here as well. + cbSize.ISize(wm) = AvailableISize(); + } + + LogicalSize size = + mFrame->ComputeSize(mRenderingContext, wm, cbSize, AvailableISize(), + ComputedLogicalMargin().Size(wm), + ComputedLogicalBorderPadding().Size(wm) - + ComputedLogicalPadding().Size(wm), + ComputedLogicalPadding().Size(wm), + computeSizeFlags); + + ComputedISize() = size.ISize(wm); + ComputedBSize() = size.BSize(wm); + NS_ASSERTION(ComputedISize() >= 0, "Bogus inline-size"); + NS_ASSERTION(ComputedBSize() == NS_UNCONSTRAINEDSIZE || + ComputedBSize() >= 0, "Bogus block-size"); + + // Exclude inline tables, side captions, flex and grid items from block + // margin calculations. + if (isBlock && + !IsSideCaption(mFrame, mStyleDisplay, cbwm) && + mStyleDisplay->mDisplay != StyleDisplay::InlineTable && + alignCBType != nsGkAtoms::flexContainerFrame && + alignCBType != nsGkAtoms::gridContainerFrame) { + CalculateBlockSideMargins(aFrameType); + } + } + } +} + +static void +UpdateProp(FrameProperties& aProps, + const FramePropertyDescriptor<nsMargin>* aProperty, + bool aNeeded, + nsMargin& aNewValue) +{ + if (aNeeded) { + nsMargin* propValue = aProps.Get(aProperty); + if (propValue) { + *propValue = aNewValue; + } else { + aProps.Set(aProperty, new nsMargin(aNewValue)); + } + } else { + aProps.Delete(aProperty); + } +} + +void +SizeComputationInput::InitOffsets(WritingMode aWM, + const LogicalSize& aPercentBasis, + nsIAtom* aFrameType, + ReflowInputFlags aFlags, + const nsMargin* aBorder, + const nsMargin* aPadding) +{ + DISPLAY_INIT_OFFSETS(mFrame, this, aPercentBasis, aBorder, aPadding); + + // Since we are in reflow, we don't need to store these properties anymore + // unless they are dependent on width, in which case we store the new value. + nsPresContext *presContext = mFrame->PresContext(); + FrameProperties props(presContext->PropertyTable(), mFrame); + props.Delete(nsIFrame::UsedBorderProperty()); + + // Compute margins from the specified margin style information. These + // become the default computed values, and may be adjusted below + // XXX fix to provide 0,0 for the top&bottom margins for + // inline-non-replaced elements + bool needMarginProp = ComputeMargin(aWM, aPercentBasis); + // XXX We need to include 'auto' horizontal margins in this too! + // ... but if we did that, we'd need to fix nsFrame::GetUsedMargin + // to use it even when the margins are all zero (since sometimes + // they get treated as auto) + ::UpdateProp(props, nsIFrame::UsedMarginProperty(), needMarginProp, + ComputedPhysicalMargin()); + + + const nsStyleDisplay *disp = mFrame->StyleDisplay(); + bool isThemed = mFrame->IsThemed(disp); + bool needPaddingProp; + nsIntMargin widget; + if (isThemed && + presContext->GetTheme()->GetWidgetPadding(presContext->DeviceContext(), + mFrame, disp->mAppearance, + &widget)) { + ComputedPhysicalPadding().top = presContext->DevPixelsToAppUnits(widget.top); + ComputedPhysicalPadding().right = presContext->DevPixelsToAppUnits(widget.right); + ComputedPhysicalPadding().bottom = presContext->DevPixelsToAppUnits(widget.bottom); + ComputedPhysicalPadding().left = presContext->DevPixelsToAppUnits(widget.left); + needPaddingProp = false; + } + else if (mFrame->IsSVGText()) { + ComputedPhysicalPadding().SizeTo(0, 0, 0, 0); + needPaddingProp = false; + } + else if (aPadding) { // padding is an input arg + ComputedPhysicalPadding() = *aPadding; + needPaddingProp = mFrame->StylePadding()->IsWidthDependent() || + (mFrame->GetStateBits() & NS_FRAME_REFLOW_ROOT); + } + else { + needPaddingProp = ComputePadding(aWM, aPercentBasis, aFrameType); + } + + // Add [align|justify]-content:baseline padding contribution. + typedef const FramePropertyDescriptor<SmallValueHolder<nscoord>>* Prop; + auto ApplyBaselinePadding = [this, &needPaddingProp] + (LogicalAxis aAxis, Prop aProp) { + bool found; + nscoord val = mFrame->Properties().Get(aProp, &found); + if (found) { + NS_ASSERTION(val != nscoord(0), "zero in this property is useless"); + WritingMode wm = GetWritingMode(); + LogicalSide side; + if (val > 0) { + side = MakeLogicalSide(aAxis, eLogicalEdgeStart); + } else { + side = MakeLogicalSide(aAxis, eLogicalEdgeEnd); + val = -val; + } + mComputedPadding.Side(wm.PhysicalSide(side)) += val; + needPaddingProp = true; + } + }; + if (!aFlags.mUseAutoBSize) { + ApplyBaselinePadding(eLogicalAxisBlock, nsIFrame::BBaselinePadProperty()); + } + if (!aFlags.mShrinkWrap) { + ApplyBaselinePadding(eLogicalAxisInline, nsIFrame::IBaselinePadProperty()); + } + + if (isThemed) { + nsIntMargin widget; + presContext->GetTheme()->GetWidgetBorder(presContext->DeviceContext(), + mFrame, disp->mAppearance, + &widget); + ComputedPhysicalBorderPadding().top = + presContext->DevPixelsToAppUnits(widget.top); + ComputedPhysicalBorderPadding().right = + presContext->DevPixelsToAppUnits(widget.right); + ComputedPhysicalBorderPadding().bottom = + presContext->DevPixelsToAppUnits(widget.bottom); + ComputedPhysicalBorderPadding().left = + presContext->DevPixelsToAppUnits(widget.left); + } + else if (mFrame->IsSVGText()) { + ComputedPhysicalBorderPadding().SizeTo(0, 0, 0, 0); + } + else if (aBorder) { // border is an input arg + ComputedPhysicalBorderPadding() = *aBorder; + } + else { + ComputedPhysicalBorderPadding() = mFrame->StyleBorder()->GetComputedBorder(); + } + ComputedPhysicalBorderPadding() += ComputedPhysicalPadding(); + + if (aFrameType == nsGkAtoms::tableFrame) { + nsTableFrame *tableFrame = static_cast<nsTableFrame*>(mFrame); + + if (tableFrame->IsBorderCollapse()) { + // border-collapsed tables don't use any of their padding, and + // only part of their border. We need to do this here before we + // try to do anything like handling 'auto' widths, + // 'box-sizing', or 'auto' margins. + ComputedPhysicalPadding().SizeTo(0,0,0,0); + SetComputedLogicalBorderPadding( + tableFrame->GetIncludedOuterBCBorder(mWritingMode)); + } + + // The margin is inherited to the table wrapper frame via + // the ::-moz-table-wrapper rule in ua.css. + ComputedPhysicalMargin().SizeTo(0, 0, 0, 0); + } else if (aFrameType == nsGkAtoms::scrollbarFrame) { + // scrollbars may have had their width or height smashed to zero + // by the associated scrollframe, in which case we must not report + // any padding or border. + nsSize size(mFrame->GetSize()); + if (size.width == 0 || size.height == 0) { + ComputedPhysicalPadding().SizeTo(0,0,0,0); + ComputedPhysicalBorderPadding().SizeTo(0,0,0,0); + } + } + ::UpdateProp(props, nsIFrame::UsedPaddingProperty(), needPaddingProp, + ComputedPhysicalPadding()); +} + +// This code enforces section 10.3.3 of the CSS2 spec for this formula: +// +// 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + +// 'padding-right' + 'border-right-width' + 'margin-right' +// = width of containing block +// +// Note: the width unit is not auto when this is called +void +ReflowInput::CalculateBlockSideMargins(nsIAtom* aFrameType) +{ + // Calculations here are done in the containing block's writing mode, + // which is where margins will eventually be applied: we're calculating + // margins that will be used by the container in its inline direction, + // which in the case of an orthogonal contained block will correspond to + // the block direction of this reflow state. So in the orthogonal-flow + // case, "CalculateBlock*Side*Margins" will actually end up adjusting + // the BStart/BEnd margins; those are the "sides" of the block from its + // container's point of view. + WritingMode cbWM = + mCBReflowInput ? mCBReflowInput->GetWritingMode(): GetWritingMode(); + + nscoord availISizeCBWM = AvailableSize(cbWM).ISize(cbWM); + nscoord computedISizeCBWM = ComputedSize(cbWM).ISize(cbWM); + if (computedISizeCBWM == NS_UNCONSTRAINEDSIZE) { + // For orthogonal flows, where we found a parent orthogonal-limit + // for AvailableISize() in Init(), we'll use the same here as well. + computedISizeCBWM = availISizeCBWM; + } + + LAYOUT_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != computedISizeCBWM && + NS_UNCONSTRAINEDSIZE != availISizeCBWM, + "have unconstrained inline-size; this should only " + "result from very large sizes, not attempts at " + "intrinsic inline-size calculation"); + + LogicalMargin margin = + ComputedLogicalMargin().ConvertTo(cbWM, mWritingMode); + LogicalMargin borderPadding = + ComputedLogicalBorderPadding().ConvertTo(cbWM, mWritingMode); + nscoord sum = margin.IStartEnd(cbWM) + + borderPadding.IStartEnd(cbWM) + computedISizeCBWM; + if (sum == availISizeCBWM) { + // The sum is already correct + return; + } + + // Determine the start and end margin values. The isize value + // remains constant while we do this. + + // Calculate how much space is available for margins + nscoord availMarginSpace = availISizeCBWM - sum; + + // If the available margin space is negative, then don't follow the + // usual overconstraint rules. + if (availMarginSpace < 0) { + margin.IEnd(cbWM) += availMarginSpace; + SetComputedLogicalMargin(margin.ConvertTo(mWritingMode, cbWM)); + return; + } + + // The css2 spec clearly defines how block elements should behave + // in section 10.3.3. + const nsStyleSides& styleSides = mStyleMargin->mMargin; + bool isAutoStartMargin = eStyleUnit_Auto == styleSides.GetIStartUnit(cbWM); + bool isAutoEndMargin = eStyleUnit_Auto == styleSides.GetIEndUnit(cbWM); + if (!isAutoStartMargin && !isAutoEndMargin) { + // Neither margin is 'auto' so we're over constrained. Use the + // 'direction' property of the parent to tell which margin to + // ignore + // First check if there is an HTML alignment that we should honor + const ReflowInput* prs = mParentReflowInput; + if (aFrameType == nsGkAtoms::tableFrame) { + NS_ASSERTION(prs->mFrame->GetType() == nsGkAtoms::tableWrapperFrame, + "table not inside table wrapper"); + // Center the table within the table wrapper based on the alignment + // of the table wrapper's parent. + prs = prs->mParentReflowInput; + } + if (prs && + (prs->mStyleText->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_LEFT || + prs->mStyleText->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_CENTER || + prs->mStyleText->mTextAlign == NS_STYLE_TEXT_ALIGN_MOZ_RIGHT)) { + if (prs->mWritingMode.IsBidiLTR()) { + isAutoStartMargin = + prs->mStyleText->mTextAlign != NS_STYLE_TEXT_ALIGN_MOZ_LEFT; + isAutoEndMargin = + prs->mStyleText->mTextAlign != NS_STYLE_TEXT_ALIGN_MOZ_RIGHT; + } else { + isAutoStartMargin = + prs->mStyleText->mTextAlign != NS_STYLE_TEXT_ALIGN_MOZ_RIGHT; + isAutoEndMargin = + prs->mStyleText->mTextAlign != NS_STYLE_TEXT_ALIGN_MOZ_LEFT; + } + } + // Otherwise apply the CSS rules, and ignore one margin by forcing + // it to 'auto', depending on 'direction'. + else { + isAutoEndMargin = true; + } + } + + // Logic which is common to blocks and tables + // The computed margins need not be zero because the 'auto' could come from + // overconstraint or from HTML alignment so values need to be accumulated + + if (isAutoStartMargin) { + if (isAutoEndMargin) { + // Both margins are 'auto' so the computed addition should be equal + nscoord forStart = availMarginSpace / 2; + margin.IStart(cbWM) += forStart; + margin.IEnd(cbWM) += availMarginSpace - forStart; + } else { + margin.IStart(cbWM) += availMarginSpace; + } + } else if (isAutoEndMargin) { + margin.IEnd(cbWM) += availMarginSpace; + } + SetComputedLogicalMargin(margin.ConvertTo(mWritingMode, cbWM)); +} + +#define NORMAL_LINE_HEIGHT_FACTOR 1.2f // in term of emHeight +// For "normal" we use the font's normal line height (em height + leading). +// If both internal leading and external leading specified by font itself +// are zeros, we should compensate this by creating extra (external) leading +// in eCompensateLeading mode. This is necessary because without this +// compensation, normal line height might looks too tight. + +// For risk management, we use preference to control the behavior, and +// eNoExternalLeading is the old behavior. +static nscoord +GetNormalLineHeight(nsFontMetrics* aFontMetrics) +{ + NS_PRECONDITION(nullptr != aFontMetrics, "no font metrics"); + + nscoord normalLineHeight; + + nscoord externalLeading = aFontMetrics->ExternalLeading(); + nscoord internalLeading = aFontMetrics->InternalLeading(); + nscoord emHeight = aFontMetrics->EmHeight(); + switch (GetNormalLineHeightCalcControl()) { + case eIncludeExternalLeading: + normalLineHeight = emHeight+ internalLeading + externalLeading; + break; + case eCompensateLeading: + if (!internalLeading && !externalLeading) + normalLineHeight = NSToCoordRound(emHeight * NORMAL_LINE_HEIGHT_FACTOR); + else + normalLineHeight = emHeight+ internalLeading + externalLeading; + break; + default: + //case eNoExternalLeading: + normalLineHeight = emHeight + internalLeading; + } + return normalLineHeight; +} + +static inline nscoord +ComputeLineHeight(nsStyleContext* aStyleContext, + nscoord aBlockBSize, + float aFontSizeInflation) +{ + const nsStyleCoord& lhCoord = aStyleContext->StyleText()->mLineHeight; + + if (lhCoord.GetUnit() == eStyleUnit_Coord) { + nscoord result = lhCoord.GetCoordValue(); + if (aFontSizeInflation != 1.0f) { + result = NSToCoordRound(result * aFontSizeInflation); + } + return result; + } + + if (lhCoord.GetUnit() == eStyleUnit_Factor) + // For factor units the computed value of the line-height property + // is found by multiplying the factor by the font's computed size + // (adjusted for min-size prefs and text zoom). + return NSToCoordRound(lhCoord.GetFactorValue() * aFontSizeInflation * + aStyleContext->StyleFont()->mFont.size); + + NS_ASSERTION(lhCoord.GetUnit() == eStyleUnit_Normal || + lhCoord.GetUnit() == eStyleUnit_Enumerated, + "bad line-height unit"); + + if (lhCoord.GetUnit() == eStyleUnit_Enumerated) { + NS_ASSERTION(lhCoord.GetIntValue() == NS_STYLE_LINE_HEIGHT_BLOCK_HEIGHT, + "bad line-height value"); + if (aBlockBSize != NS_AUTOHEIGHT) { + return aBlockBSize; + } + } + + RefPtr<nsFontMetrics> fm = nsLayoutUtils:: + GetFontMetricsForStyleContext(aStyleContext, aFontSizeInflation); + return GetNormalLineHeight(fm); +} + +nscoord +ReflowInput::CalcLineHeight() const +{ + nscoord blockBSize = + nsLayoutUtils::IsNonWrapperBlock(mFrame) ? ComputedBSize() : + (mCBReflowInput ? mCBReflowInput->ComputedBSize() : NS_AUTOHEIGHT); + + return CalcLineHeight(mFrame->GetContent(), mFrame->StyleContext(), blockBSize, + nsLayoutUtils::FontSizeInflationFor(mFrame)); +} + +/* static */ nscoord +ReflowInput::CalcLineHeight(nsIContent* aContent, + nsStyleContext* aStyleContext, + nscoord aBlockBSize, + float aFontSizeInflation) +{ + NS_PRECONDITION(aStyleContext, "Must have a style context"); + + nscoord lineHeight = + ComputeLineHeight(aStyleContext, aBlockBSize, aFontSizeInflation); + + NS_ASSERTION(lineHeight >= 0, "ComputeLineHeight screwed up"); + + HTMLInputElement* input = HTMLInputElement::FromContentOrNull(aContent); + if (input && input->IsSingleLineTextControl()) { + // For Web-compatibility, single-line text input elements cannot + // have a line-height smaller than one. + nscoord lineHeightOne = + aFontSizeInflation * aStyleContext->StyleFont()->mFont.size; + if (lineHeight < lineHeightOne) { + lineHeight = lineHeightOne; + } + } + + return lineHeight; +} + +bool +SizeComputationInput::ComputeMargin(WritingMode aWM, + const LogicalSize& aPercentBasis) +{ + // SVG text frames have no margin. + if (mFrame->IsSVGText()) { + return false; + } + + // If style style can provide us the margin directly, then use it. + const nsStyleMargin *styleMargin = mFrame->StyleMargin(); + + bool isCBDependent = !styleMargin->GetMargin(ComputedPhysicalMargin()); + if (isCBDependent) { + // We have to compute the value. Note that this calculation is + // performed according to the writing mode of the containing block + // (http://dev.w3.org/csswg/css-writing-modes-3/#orthogonal-flows) + LogicalMargin m(aWM); + m.IStart(aWM) = nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.ISize(aWM), + styleMargin->mMargin.GetIStart(aWM)); + m.IEnd(aWM) = nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.ISize(aWM), + styleMargin->mMargin.GetIEnd(aWM)); + + m.BStart(aWM) = nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.BSize(aWM), + styleMargin->mMargin.GetBStart(aWM)); + m.BEnd(aWM) = nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.BSize(aWM), + styleMargin->mMargin.GetBEnd(aWM)); + + SetComputedLogicalMargin(aWM, m); + } + + // ... but font-size-inflation-based margin adjustment uses the + // frame's writing mode + nscoord marginAdjustment = FontSizeInflationListMarginAdjustment(mFrame); + + if (marginAdjustment > 0) { + LogicalMargin m = ComputedLogicalMargin(); + m.IStart(mWritingMode) += marginAdjustment; + SetComputedLogicalMargin(m); + } + + return isCBDependent; +} + +bool +SizeComputationInput::ComputePadding(WritingMode aWM, + const LogicalSize& aPercentBasis, + nsIAtom* aFrameType) +{ + // If style can provide us the padding directly, then use it. + const nsStylePadding *stylePadding = mFrame->StylePadding(); + bool isCBDependent = !stylePadding->GetPadding(ComputedPhysicalPadding()); + // a table row/col group, row/col doesn't have padding + // XXXldb Neither do border-collapse tables. + if (nsGkAtoms::tableRowGroupFrame == aFrameType || + nsGkAtoms::tableColGroupFrame == aFrameType || + nsGkAtoms::tableRowFrame == aFrameType || + nsGkAtoms::tableColFrame == aFrameType) { + ComputedPhysicalPadding().SizeTo(0,0,0,0); + } + else if (isCBDependent) { + // We have to compute the value. This calculation is performed + // according to the writing mode of the containing block + // (http://dev.w3.org/csswg/css-writing-modes-3/#orthogonal-flows) + // clamp negative calc() results to 0 + LogicalMargin p(aWM); + p.IStart(aWM) = std::max(0, nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.ISize(aWM), + stylePadding->mPadding.GetIStart(aWM))); + p.IEnd(aWM) = std::max(0, nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.ISize(aWM), + stylePadding->mPadding.GetIEnd(aWM))); + + p.BStart(aWM) = std::max(0, nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.BSize(aWM), + stylePadding->mPadding.GetBStart(aWM))); + p.BEnd(aWM) = std::max(0, nsLayoutUtils:: + ComputeCBDependentValue(aPercentBasis.BSize(aWM), + stylePadding->mPadding.GetBEnd(aWM))); + + SetComputedLogicalPadding(aWM, p); + } + return isCBDependent; +} + +void +ReflowInput::ComputeMinMaxValues(const LogicalSize&aCBSize) +{ + WritingMode wm = GetWritingMode(); + + const nsStyleCoord& minISize = mStylePosition->MinISize(wm); + const nsStyleCoord& maxISize = mStylePosition->MaxISize(wm); + const nsStyleCoord& minBSize = mStylePosition->MinBSize(wm); + const nsStyleCoord& maxBSize = mStylePosition->MaxBSize(wm); + + // NOTE: min-width:auto resolves to 0, except on a flex item. (But + // even there, it's supposed to be ignored (i.e. treated as 0) until + // the flex container explicitly resolves & considers it.) + if (eStyleUnit_Auto == minISize.GetUnit()) { + ComputedMinISize() = 0; + } else { + ComputedMinISize() = ComputeISizeValue(aCBSize.ISize(wm), + mStylePosition->mBoxSizing, + minISize); + } + + if (eStyleUnit_None == maxISize.GetUnit()) { + // Specified value of 'none' + ComputedMaxISize() = NS_UNCONSTRAINEDSIZE; // no limit + } else { + ComputedMaxISize() = ComputeISizeValue(aCBSize.ISize(wm), + mStylePosition->mBoxSizing, + maxISize); + } + + // If the computed value of 'min-width' is greater than the value of + // 'max-width', 'max-width' is set to the value of 'min-width' + if (ComputedMinISize() > ComputedMaxISize()) { + ComputedMaxISize() = ComputedMinISize(); + } + + // Check for percentage based values and a containing block height that + // depends on the content height. Treat them like 'auto' + // Likewise, check for calc() with percentages on internal table elements; + // that's treated as 'auto' too. + // Likewise, if we're a child of a flex container who's measuring our + // intrinsic height, then we want to disregard our min-height. + + // NOTE: min-height:auto resolves to 0, except on a flex item. (But + // even there, it's supposed to be ignored (i.e. treated as 0) until + // the flex container explicitly resolves & considers it.) + if (eStyleUnit_Auto == minBSize.GetUnit() || + (NS_AUTOHEIGHT == aCBSize.BSize(wm) && + minBSize.HasPercent()) || + (mFrameType == NS_CSS_FRAME_TYPE_INTERNAL_TABLE && + minBSize.IsCalcUnit() && minBSize.CalcHasPercent()) || + mFlags.mIsFlexContainerMeasuringHeight) { + ComputedMinBSize() = 0; + } else { + ComputedMinBSize() = ComputeBSizeValue(aCBSize.BSize(wm), + mStylePosition->mBoxSizing, + minBSize); + } + nsStyleUnit maxBSizeUnit = maxBSize.GetUnit(); + if (eStyleUnit_None == maxBSizeUnit) { + // Specified value of 'none' + ComputedMaxBSize() = NS_UNCONSTRAINEDSIZE; // no limit + } else { + // Check for percentage based values and a containing block height that + // depends on the content height. Treat them like 'none' + // Likewise, check for calc() with percentages on internal table elements; + // that's treated as 'auto' too. + // Likewise, if we're a child of a flex container who's measuring our + // intrinsic height, then we want to disregard our max-height. + if ((NS_AUTOHEIGHT == aCBSize.BSize(wm) && + maxBSize.HasPercent()) || + (mFrameType == NS_CSS_FRAME_TYPE_INTERNAL_TABLE && + maxBSize.IsCalcUnit() && maxBSize.CalcHasPercent()) || + mFlags.mIsFlexContainerMeasuringHeight) { + ComputedMaxBSize() = NS_UNCONSTRAINEDSIZE; + } else { + ComputedMaxBSize() = ComputeBSizeValue(aCBSize.BSize(wm), + mStylePosition->mBoxSizing, + maxBSize); + } + } + + // If the computed value of 'min-height' is greater than the value of + // 'max-height', 'max-height' is set to the value of 'min-height' + if (ComputedMinBSize() > ComputedMaxBSize()) { + ComputedMaxBSize() = ComputedMinBSize(); + } +} + +void +ReflowInput::SetTruncated(const ReflowOutput& aMetrics, + nsReflowStatus* aStatus) const +{ + const WritingMode containerWM = aMetrics.GetWritingMode(); + if (GetWritingMode().IsOrthogonalTo(containerWM)) { + // Orthogonal flows are always reflowed with an unconstrained dimension, + // so should never end up truncated (see ReflowInput::Init()). + *aStatus &= ~NS_FRAME_TRUNCATED; + } else if (AvailableBSize() != NS_UNCONSTRAINEDSIZE && + AvailableBSize() < aMetrics.BSize(containerWM) && + !mFlags.mIsTopOfPage) { + *aStatus |= NS_FRAME_TRUNCATED; + } else { + *aStatus &= ~NS_FRAME_TRUNCATED; + } +} + +bool +ReflowInput::IsFloating() const +{ + return mStyleDisplay->IsFloating(mFrame); +} + +mozilla::StyleDisplay +ReflowInput::GetDisplay() const +{ + return mStyleDisplay->GetDisplay(mFrame); +} |