/* -*- 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 & I_APPLY_AUTO_MIN_SIZE) { mFlags.mApplyAutoMinSize = 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); mFlags.mApplyAutoMinSize = !!(aFlags & I_APPLY_AUTO_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.mApplyAutoMinSize) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eIApplyAutoMinSize); } 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.mApplyAutoMinSize) { computeSizeFlags = ComputeSizeFlags(computeSizeFlags | ComputeSizeFlags::eIApplyAutoMinSize); } 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); }