Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
/* state used in reflow of block frames */
#ifndef BlockReflowState_h
#define BlockReflowState_h
#include <tuple>
#include "mozilla/ReflowInput.h"
#include "nsFloatManager.h"
#include "nsLineBox.h"
class nsBlockFrame;
class nsFrameList;
class nsOverflowContinuationTracker;
namespace mozilla {
// BlockReflowState contains additional reflow input information that the
// block frame uses along with ReflowInput. Like ReflowInput, this
// is read-only data that is passed down from a parent frame to its children.
class BlockReflowState {
using BandInfoType = nsFloatManager::BandInfoType;
using ShapeType = nsFloatManager::ShapeType;
// Block reflow input flags.
struct Flags {
Flags()
: mIsBStartMarginRoot(false),
mIsBEndMarginRoot(false),
mShouldApplyBStartMargin(false),
mHasLineAdjacentToTop(false),
mBlockNeedsFloatManager(false),
mIsLineLayoutEmpty(false),
mIsFloatListInBlockPropertyTable(false),
mCanHaveOverflowMarkers(false) {}
// Set in the BlockReflowState constructor when reflowing a "block margin
// root" frame (i.e. a frame with any of the NS_BLOCK_BFC flag set, for
// which margins apply by default).
//
// The flag is also set when reflowing a frame whose computed BStart border
// padding is non-zero.
bool mIsBStartMarginRoot : 1;
// Set in the BlockReflowState constructor when reflowing a "block margin
// root" frame (i.e. a frame with any of the NS_BLOCK_BFC flag set, for
// which margins apply by default).
//
// The flag is also set when reflowing a frame whose computed BEnd border
// padding is non-zero.
bool mIsBEndMarginRoot : 1;
// Set if the BStart margin should be considered when placing a linebox that
// contains a block frame. It may be set as a side-effect of calling
// nsBlockFrame::ShouldApplyBStartMargin(); once set,
// ShouldApplyBStartMargin() uses it as a fast-path way to return whether
// the BStart margin should apply.
//
// If the flag hasn't been set in the block reflow state, then
// ShouldApplyBStartMargin() will crawl the line list to see if a block
// frame precedes the specified frame. If so, the BStart margin should be
// applied, and the flag is set to cache the result. (If not, the BStart
// margin will be applied as a result of the generational margin collapsing
// logic in nsBlockReflowContext::ComputeCollapsedBStartMargin(). In this
// case, the flag won't be set, so subsequent calls to
// ShouldApplyBStartMargin() will continue crawl the line list.)
//
// This flag is also set in the BlockReflowState constructor if
// mIsBStartMarginRoot is set; that is, the frame being reflowed is a margin
// root by default.
bool mShouldApplyBStartMargin : 1;
// Set when mLineAdjacentToTop is valid.
bool mHasLineAdjacentToTop : 1;
// Set when the block has the equivalent of NS_BLOCK_*_BFC.
bool mBlockNeedsFloatManager : 1;
// Set when nsLineLayout::LineIsEmpty was true at the end of reflowing
// the current line.
bool mIsLineLayoutEmpty : 1;
// Set when our mPushedFloats list is stored on the block's property table.
bool mIsFloatListInBlockPropertyTable : 1;
// Set when we need text-overflow or -webkit-line-clamp processing.
bool mCanHaveOverflowMarkers : 1;
};
public:
BlockReflowState(const ReflowInput& aReflowInput, nsPresContext* aPresContext,
nsBlockFrame* aFrame, bool aBStartMarginRoot,
bool aBEndMarginRoot, bool aBlockNeedsFloatManager,
const nscoord aConsumedBSize,
const nscoord aEffectiveContentBoxBSize,
const nscoord aInset = 0);
/**
* Unshifts coords, restores availableBSize to reality.
* (Constructor applies any cached shift before reflow
* so that frames are reflowed with cached shift)
*/
void UndoAlignContentShift();
/**
* Get the available reflow space (the area not occupied by floats)
* for the current y coordinate. The available space is relative to
* our coordinate system, which is the content box, with (0, 0) in the
* upper left.
*
* Returns whether there are floats present at the given block-direction
* coordinate and within the inline size of the content rect.
*
* Note: some codepaths clamp this structure's inline-size to be >=0 "for
* compatibility with nsSpaceManager". So if you encounter a nsFlowAreaRect
* which appears to have an ISize of 0, you can't necessarily assume that a
* 0-ISize float-avoiding block would actually fit; you need to check the
* InitialISizeIsNegative flag to see whether that 0 is actually a clamped
* negative value (in which case a 0-ISize float-avoiding block *should not*
* be considered as fitting, because it would intersect some float).
*/
nsFlowAreaRect GetFloatAvailableSpace() const {
return GetFloatAvailableSpace(mBCoord);
}
nsFlowAreaRect GetFloatAvailableSpaceForPlacingFloat(nscoord aBCoord) const {
return GetFloatAvailableSpaceWithState(aBCoord, ShapeType::Margin, nullptr);
}
nsFlowAreaRect GetFloatAvailableSpace(nscoord aBCoord) const {
return GetFloatAvailableSpaceWithState(aBCoord, ShapeType::ShapeOutside,
nullptr);
}
nsFlowAreaRect GetFloatAvailableSpaceWithState(
nscoord aBCoord, ShapeType aShapeType,
nsFloatManager::SavedState* aState) const;
nsFlowAreaRect GetFloatAvailableSpaceForBSize(
nscoord aBCoord, nscoord aBSize,
nsFloatManager::SavedState* aState) const;
// @return true if AddFloat was able to place the float; false if the float
// did not fit in available space.
//
// Note: if it returns false, then the float's position and size should be
// considered stale/invalid (until the float is successfully placed).
bool AddFloat(nsLineLayout* aLineLayout, nsIFrame* aFloat,
nscoord aAvailableISize);
enum class PlaceFloatResult : uint8_t {
Placed,
ShouldPlaceBelowCurrentLine,
ShouldPlaceInNextContinuation,
};
// @param aAvailableISizeInCurrentLine the available inline-size of the
// current line if current line is not empty.
PlaceFloatResult FlowAndPlaceFloat(
nsIFrame* aFloat, mozilla::Maybe<nscoord> aAvailableISizeInCurrentLine =
mozilla::Nothing());
void PlaceBelowCurrentLineFloats(nsLineBox* aLine);
// Returns the first coordinate >= aBCoord that clears the
// floats indicated by aClearType and has enough inline size between floats
// (or no floats remaining) to accomodate aFloatAvoidingBlock.
enum class ClearFloatsResult : uint8_t {
BCoordNoChange,
BCoordAdvanced,
FloatsPushedOrSplit,
};
std::tuple<nscoord, ClearFloatsResult> ClearFloats(
nscoord aBCoord, StyleClear aClearType,
nsIFrame* aFloatAvoidingBlock = nullptr);
nsFloatManager* FloatManager() const {
MOZ_ASSERT(mReflowInput.mFloatManager,
"Float manager should be valid during the lifetime of "
"BlockReflowState!");
return mReflowInput.mFloatManager;
}
// Advances to the next band, i.e., the next horizontal stripe in
// which there is a different set of floats.
// Return false if it did not advance, which only happens for
// constrained heights (and means that we should get pushed to the
// next column/page).
bool AdvanceToNextBand(const LogicalRect& aFloatAvailableSpace,
nscoord* aBCoord) const {
WritingMode wm = mReflowInput.GetWritingMode();
if (aFloatAvailableSpace.BSize(wm) > 0) {
// See if there's room in the next band.
*aBCoord += aFloatAvailableSpace.BSize(wm);
} else {
if (mReflowInput.AvailableHeight() != NS_UNCONSTRAINEDSIZE) {
// Stop trying to clear here; we'll just get pushed to the
// next column or page and try again there.
return false;
}
MOZ_ASSERT_UNREACHABLE("avail space rect with zero height!");
*aBCoord += 1;
}
return true;
}
bool FloatAvoidingBlockFitsInAvailSpace(
nsIFrame* aFloatAvoidingBlock,
const nsFlowAreaRect& aFloatAvailableSpace) const;
// True if the current block-direction coordinate, for placing the children
// within the content area, is still adjacent with the block-start of the
// content area.
bool IsAdjacentWithBStart() const { return mBCoord == ContentBStart(); }
const LogicalMargin& BorderPadding() const { return mBorderPadding; }
// Reconstruct the previous block-end margin that goes before |aLine|.
void ReconstructMarginBefore(nsLineList::iterator aLine);
// Caller must have called GetFloatAvailableSpace for the correct position
// (which need not be the current mBCoord).
void ComputeFloatAvoidingOffsets(nsIFrame* aFloatAvoidingBlock,
const LogicalRect& aFloatAvailableSpace,
nscoord& aIStartResult,
nscoord& aIEndResult) const;
// Compute the amount of available space for reflowing a block frame at the
// current block-direction coordinate mBCoord. Caller must have called
// GetFloatAvailableSpace for the current mBCoord.
LogicalRect ComputeBlockAvailSpace(nsIFrame* aFrame,
const nsFlowAreaRect& aFloatAvailableSpace,
bool aBlockAvoidsFloats);
LogicalSize ComputeAvailableSizeForFloat() const;
void RecoverStateFrom(nsLineList::iterator aLine, nscoord aDeltaBCoord);
void AdvanceToNextLine() {
if (mFlags.mIsLineLayoutEmpty) {
mFlags.mIsLineLayoutEmpty = false;
} else {
mLineNumber++;
}
}
//----------------------------------------
// This state is the "global" state computed once for the reflow of
// the block.
// The block frame that is using this object
nsBlockFrame* const mBlock;
nsPresContext* const mPresContext;
const ReflowInput& mReflowInput;
// The coordinates within the float manager where the block is being
// placed <b>after</b> taking into account the blocks border and
// padding. This, therefore, represents the inner "content area" (in
// float manager coordinates) where child frames will be placed,
// including child blocks and floats.
nscoord mFloatManagerI, mFloatManagerB;
// XXX get rid of this
nsReflowStatus mReflowStatus;
// The float manager state as it was before the contents of this
// block. This is needed for positioning bullets, since we only want
// to move the bullet to flow around floats that were before this
// block, not floats inside of it.
nsFloatManager::SavedState mFloatManagerStateBefore;
// The content area to reflow child frames within. This is within
// this frame's coordinate system and writing mode, which means
// mContentArea.IStart == BorderPadding().IStart and
// mContentArea.BStart == BorderPadding().BStart.
// The block size may be NS_UNCONSTRAINEDSIZE, which indicates that there
// is no page/column boundary below (the common case).
// mContentArea.BEnd() should only be called after checking that
// mContentArea.BSize is not NS_UNCONSTRAINEDSIZE; otherwise
// coordinate overflow may occur.
LogicalRect mContentArea;
nscoord ContentIStart() const {
return mContentArea.IStart(mReflowInput.GetWritingMode());
}
nscoord ContentISize() const {
return mContentArea.ISize(mReflowInput.GetWritingMode());
}
nscoord ContentIEnd() const {
return mContentArea.IEnd(mReflowInput.GetWritingMode());
}
nscoord ContentBStart() const {
return mContentArea.BStart(mReflowInput.GetWritingMode());
}
nscoord ContentBSize() const {
return mContentArea.BSize(mReflowInput.GetWritingMode());
}
nscoord ContentBEnd() const {
NS_ASSERTION(
ContentBSize() != NS_UNCONSTRAINEDSIZE,
"ContentBSize() is unconstrained, so ContentBEnd() may overflow.");
return mContentArea.BEnd(mReflowInput.GetWritingMode());
}
LogicalSize ContentSize(WritingMode aWM) const {
WritingMode wm = mReflowInput.GetWritingMode();
return mContentArea.Size(wm).ConvertTo(aWM, wm);
}
// Amount of inset to apply during line-breaking, used by text-wrap:balance
// to adjust line-breaks for more consistent lengths throughout the block.
nscoord mInsetForBalance;
// Physical size. Use only for physical <-> logical coordinate conversion.
//
// Note: for vertical-rl writing-mode, if mContainerSize's width is
// initialized to zero due to unconstrained block-size, lines will be
// positioned (physically) incorrectly. We will fix them up at the end of
// nsBlockFrame::Reflow() after we know the total block-size of the frame.
nsSize mContainerSize;
const nsSize& ContainerSize() const { return mContainerSize; }
// Continuation out-of-flow float frames that need to move to our
// next in flow are placed here during reflow. It's a pointer to
// a frame list stored in the block's property table.
nsFrameList* mPushedFloats;
// This method makes sure pushed floats are accessible to
// StealFrame. Call it before adding any frames to mPushedFloats.
void SetupPushedFloatList();
/**
* Append aFloatCont and its next-in-flows within the same block to
* mPushedFloats. aFloatCont should not be on any child list when
* making this call. Its next-in-flows will be removed from
* mBlock using StealFrame() before being added to mPushedFloats.
* All appended frames will be marked NS_FRAME_IS_PUSHED_FLOAT.
*/
void AppendPushedFloatChain(nsIFrame* aFloatCont);
// Track child overflow continuations.
nsOverflowContinuationTracker* mOverflowTracker;
//----------------------------------------
// This state is "running" state updated by the reflow of each line
// in the block. This same state is "recovered" when a line is not
// dirty and is passed over during incremental reflow.
// The current line being reflowed
// If it is mBlock->end_lines(), then it is invalid.
nsLineList::iterator mCurrentLine;
// When mHasLineAdjacentToTop is set, this refers to a line
// which we know is adjacent to the top of the block (in other words,
// all lines before it are empty and do not have clearance. This line is
// always before the current line.
nsLineList::iterator mLineAdjacentToTop;
// The current block-direction coordinate in the block
nscoord mBCoord;
// mBlock's computed logical border+padding with pre-reflow skip sides applied
// (See the constructor and nsIFrame::PreReflowBlockLevelLogicalSkipSides).
const LogicalMargin mBorderPadding;
// The overflow areas of all floats placed so far
OverflowAreas mFloatOverflowAreas;
// Previous child. This is used when pulling up a frame to update
// the sibling list.
nsIFrame* mPrevChild;
// The previous child frames collapsed bottom margin value.
nsCollapsingMargin mPrevBEndMargin;
// The current next-in-flow for the block. When lines are pulled
// from a next-in-flow, this is used to know which next-in-flow to
// pull from. When a next-in-flow is emptied of lines, we advance
// this to the next next-in-flow.
nsBlockFrame* mNextInFlow;
//----------------------------------------
// Temporary state, for line-reflow. This state is used during the reflow
// of a given line, but doesn't have meaning before or after.
// The list of floats that are "current-line" floats. These are
// added to the line after the line has been reflowed, to keep the
// list fiddling from being N^2.
nsTArray<nsIFrame*> mCurrentLineFloats;
// The list of floats which are "below current-line"
// floats. These are reflowed/placed after the line is reflowed
// and placed. Again, this is done to keep the list fiddling from
// being N^2.
nsTArray<nsIFrame*> mBelowCurrentLineFloats;
// The list of floats that are waiting on a break opportunity in order to be
// placed, since we're on a nowrap context.
nsTArray<nsIFrame*> mNoWrapFloats;
const nscoord mMinLineHeight;
int32_t mLineNumber;
Flags mFlags;
// Cache the result of nsBlockFrame::FindTrailingClear() from mBlock's
// prev-in-flows. See nsBlockFrame::ReflowPushedFloats().
StyleClear mTrailingClearFromPIF;
// The amount of computed content block-size "consumed" by our previous
// continuations.
const nscoord mConsumedBSize;
// The amount of block-axis alignment shift to assume during reflow.
// Cached between reflows in the AlignContentShift property.
// (This system optimizes reflow for not changing the shift.)
nscoord mAlignContentShift;
// Cache the current line's BSize if nsBlockFrame::PlaceLine() fails to
// place the line. When redoing the line, it will be used to query the
// accurate float available space in AddFloat() and
// nsBlockFrame::PlaceLine().
Maybe<nscoord> mLineBSize;
private:
bool CanPlaceFloat(nscoord aFloatISize,
const nsFlowAreaRect& aFloatAvailableSpace);
void PushFloatPastBreak(nsIFrame* aFloat);
void RecoverFloats(nsLineList::iterator aLine, nscoord aDeltaBCoord);
};
}; // namespace mozilla
#endif // BlockReflowState_h