Backed out 4 changesets (bug 1731541) for causing bustage on nsBlockFrame.cpp. CLOSED TREE

Backed out changeset 7ce2b41bb730 (bug 1731541)
Backed out changeset 71d889de8d97 (bug 1731541)
Backed out changeset 0fd4431f5279 (bug 1731541)
Backed out changeset 5926241957b4 (bug 1731541)
This commit is contained in:
Natalia Csoregi 2023-09-28 12:09:30 +03:00
Родитель e468f11905
Коммит 0489f93a3d
28 изменённых файлов: 284 добавлений и 726 удалений

Просмотреть файл

@ -174,7 +174,6 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
"-webkit-text-stroke-width",
"text-transform",
"text-underline-position",
"text-wrap",
"touch-action",
"transform-box",
"transform-style",

Просмотреть файл

@ -27,16 +27,17 @@
using namespace mozilla;
using namespace mozilla::layout;
BlockReflowState::BlockReflowState(
const ReflowInput& aReflowInput, nsPresContext* aPresContext,
nsBlockFrame* aFrame, bool aBStartMarginRoot, bool aBEndMarginRoot,
bool aBlockNeedsFloatManager, const nscoord aConsumedBSize,
const nscoord aEffectiveContentBoxBSize, const nscoord aInset)
BlockReflowState::BlockReflowState(const ReflowInput& aReflowInput,
nsPresContext* aPresContext,
nsBlockFrame* aFrame, bool aBStartMarginRoot,
bool aBEndMarginRoot,
bool aBlockNeedsFloatManager,
const nscoord aConsumedBSize,
const nscoord aEffectiveContentBoxBSize)
: mBlock(aFrame),
mPresContext(aPresContext),
mReflowInput(aReflowInput),
mContentArea(aReflowInput.GetWritingMode()),
mInsetForBalance(aInset),
mPushedFloats(nullptr),
mOverflowTracker(nullptr),
mBorderPadding(

Просмотреть файл

@ -98,8 +98,7 @@ class BlockReflowState {
nsBlockFrame* aFrame, bool aBStartMarginRoot,
bool aBEndMarginRoot, bool aBlockNeedsFloatManager,
const nscoord aConsumedBSize,
const nscoord aEffectiveContentBoxBSize,
const nscoord aInset = 0);
const nscoord aEffectiveContentBoxBSize);
/**
* Get the available reflow space (the area not occupied by floats)
@ -244,9 +243,9 @@ class BlockReflowState {
// the block.
// The block frame that is using this object
nsBlockFrame* const mBlock;
nsBlockFrame* mBlock;
nsPresContext* const mPresContext;
nsPresContext* mPresContext;
const ReflowInput& mReflowInput;
@ -302,10 +301,6 @@ class BlockReflowState {
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.
nsSize mContainerSize;
const nsSize& ContainerSize() const { return mContainerSize; }
@ -350,7 +345,7 @@ class BlockReflowState {
// mBlock's computed logical border+padding with pre-reflow skip sides applied
// (See the constructor and nsIFrame::PreReflowBlockLevelLogicalSkipSides).
const LogicalMargin mBorderPadding;
LogicalMargin mBorderPadding;
// The overflow areas of all floats placed so far
OverflowAreas mFloatOverflowAreas;
@ -388,7 +383,7 @@ class BlockReflowState {
// placed, since we're on a nowrap context.
nsTArray<nsIFrame*> mNoWrapFloats;
const nscoord mMinLineHeight;
nscoord mMinLineHeight;
int32_t mLineNumber;

Просмотреть файл

@ -1414,16 +1414,6 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
}
}
if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
return;
}
// OK, some lines may be reflowed. Blow away any saved line cursor
// because we may invalidate the nondecreasing
// overflowArea.InkOverflow().y/yMost invariant, and we may even
// delete the line with the line cursor.
ClearLineCursors();
// See comment below about oldSize. Use *only* for the
// abs-pos-containing-block-size-change optimization!
nsSize oldSize = GetSize();
@ -1440,375 +1430,16 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
autoFloatManager.CreateFloatManager(aPresContext);
}
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) {
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
// OK, some lines may be reflowed. Blow away any saved line cursor
// because we may invalidate the nondecreasing
// overflowArea.InkOverflow().y/yMost invariant, and we may even
// delete the line with the line cursor.
ClearLineCursors();
if (IsFrameTreeTooDeep(aReflowInput, aMetrics, aStatus)) {
return;
}
// Whether to apply text-wrap: balance behavior.
bool tryBalance = StyleText()->mTextWrap == StyleTextWrap::Balance &&
!GetPrevContinuation();
// Struct used to hold the "target" number of lines or clamp position to
// maintain when doing text-wrap: balance.
struct BalanceTarget {
// If line-clamp is in effect, mContent and mOffset indicate the starting
// position of the first line after the clamp limit. If line-clamp is not
// in use, mContent is null and mOffset is the total number of lines that
// the block must contain.
nsIContent* mContent = nullptr;
int32_t mOffset = -1;
bool operator==(const BalanceTarget& aOther) const {
return mContent == aOther.mContent && mOffset == aOther.mOffset;
}
bool operator!=(const BalanceTarget& aOther) const {
return !(*this == aOther);
}
};
BalanceTarget balanceTarget;
// Helpers for text-wrap: balance implementation:
// Count the number of lines in the mLines list, but return -1 instead if the
// count is going to exceed aLimit.
auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
int32_t n = 0;
for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
if (++n > aLimit) {
return -1;
}
}
return n;
};
// Return a BalanceTarget record representing the position at which line-clamp
// will take effect for the current line list. Only to be used when there are
// enough lines that the clamp will apply.
auto getClampPosition = [&](int32_t aClampCount) -> BalanceTarget {
MOZ_ASSERT(aClampCount < mLines.size());
auto iter = mLines.begin();
for (auto i = 0; i < aClampCount; i++) {
++iter;
}
nsIContent* content = iter->mFirstChild->GetContent();
int32_t offset = 0;
if (content && iter->mFirstChild->IsTextFrame()) {
auto* textFrame = static_cast<nsTextFrame*>(iter->mFirstChild);
offset = textFrame->GetContentOffset();
}
return BalanceTarget{content, offset};
};
// "balancing" is implemented by shortening the effective inline-size of the
// lines, so that content will tend to be pushed down to fill later lines of
// the block. `balanceInset` is the current amount of "inset" to apply, and
// `balanceStep` is the increment to adjust it by for the next iteration.
nscoord balanceStep = 0;
// text-wrap: balance loop, executed only once if balancing is not required.
nsReflowStatus reflowStatus;
TrialReflowState trialState(consumedBSize, effectiveContentBoxBSize,
needFloatManager);
while (true) {
// Save the initial floatManager state for repeated trial reflows.
// We'll restore (and re-save) the initial state each time we repeat the
// reflow.
nsFloatManager::SavedState floatManagerState;
aReflowInput.mFloatManager->PushState(&floatManagerState);
aMetrics = ReflowOutput(aMetrics.GetWritingMode());
reflowStatus =
TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
// Do we need to start a `text-wrap: balance` iteration?
if (tryBalance) {
tryBalance = false;
// Don't try to balance an incomplete block.
if (!reflowStatus.IsFullyComplete()) {
break;
}
balanceTarget.mOffset =
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
if (balanceTarget.mOffset < 2) {
// If there are less than 2 lines, or the number exceeds the limit,
// no balancing is needed; just break from the balance loop.
break;
}
// Initialize the amount of inset to try, and the iteration step size.
balanceStep = aReflowInput.ComputedISize() / balanceTarget.mOffset;
trialState.ResetForBalance(balanceStep);
balanceStep /= 2;
// If -webkit-line-clamp is in effect, then we need to maintain the
// content location at which clamping occurs, rather than the total
// number of lines in the block.
if (StaticPrefs::layout_css_text_wrap_balance_after_clamp_enabled() &&
IsLineClampRoot(this)) {
int32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp;
if (balanceTarget.mOffset > lineClampCount) {
auto t = getClampPosition(lineClampCount);
if (t.mContent) {
balanceTarget = t;
}
}
}
// Restore initial floatManager state for a new trial with updated inset.
aReflowInput.mFloatManager->PopState(&floatManagerState);
continue;
}
// Helper to determine whether the current trial succeeded (i.e. was able
// to fit the content into the expected number of lines).
auto trialSucceeded = [&]() -> bool {
if (!reflowStatus.IsFullyComplete()) {
return false;
}
if (balanceTarget.mContent) {
auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);
return t == balanceTarget;
}
int32_t numLines =
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
return numLines == balanceTarget.mOffset;
};
// If we're in the process of a balance operation, check whether we've
// inset by too much and either increase or reduce the inset for the next
// iteration.
if (balanceStep > 0) {
if (trialSucceeded()) {
trialState.ResetForBalance(balanceStep);
} else {
trialState.ResetForBalance(-balanceStep);
}
balanceStep /= 2;
aReflowInput.mFloatManager->PopState(&floatManagerState);
continue;
}
// If we were attempting to balance, check whether the final iteration was
// successful, and if not, back up by one step.
if (balanceTarget.mOffset >= 0) {
if (trialSucceeded()) {
break;
}
trialState.ResetForBalance(-1);
aReflowInput.mFloatManager->PopState(&floatManagerState);
continue;
}
// If we reach here, no balancing was required, so just exit; we don't
// reset (pop) the floatManager state because this is the reflow we're
// going to keep. So the saved state is just dropped.
break;
} // End of text-wrap: balance retry loop
// If the block direction is right-to-left, we need to update the bounds of
// lines that were placed relative to mContainerSize during reflow, as
// we typically do not know the true container size until we've reflowed all
// its children. So we use a dummy mContainerSize during reflow (see
// BlockReflowState's constructor) and then fix up the positions of the
// lines here, once the final block size is known.
//
// Note that writing-mode:vertical-rl is the only case where the block
// logical direction progresses in a negative physical direction, and
// therefore block-dir coordinate conversion depends on knowing the width
// of the coordinate space in order to translate between the logical and
// physical origins.
if (aReflowInput.GetWritingMode().IsVerticalRL()) {
nsSize containerSize = aMetrics.PhysicalSize();
nscoord deltaX = containerSize.width - trialState.mContainerWidth;
if (deltaX != 0) {
// We compute our lines and markers' overflow areas later in
// ComputeOverflowAreas(), so we don't need to adjust their overflow areas
// here.
const nsPoint physicalDelta(deltaX, 0);
for (auto& line : Lines()) {
UpdateLineContainerSize(&line, containerSize);
}
trialState.mFcBounds.Clear();
for (nsIFrame* f : mFloats) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(trialState.mFcBounds, f);
}
nsFrameList* markerList = GetOutsideMarkerList();
if (markerList) {
for (nsIFrame* f : *markerList) {
f->MovePositionBy(physicalDelta);
}
}
if (nsFrameList* overflowContainers = GetOverflowContainers()) {
trialState.mOcBounds.Clear();
for (nsIFrame* f : *overflowContainers) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(trialState.mOcBounds, f);
}
}
}
}
aMetrics.SetOverflowAreasToDesiredBounds();
ComputeOverflowAreas(aMetrics.mOverflowAreas,
trialState.mBlockEndEdgeOfChildren,
aReflowInput.mStyleDisplay);
// Factor overflow container child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(trialState.mOcBounds);
// Factor pushed float child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(trialState.mFcBounds);
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed, e.g., elements with a percentage
// based width/height
// We want to do this under either of two conditions:
// 1. If we didn't do the incremental reflow above.
// 2. If our size changed.
// Even though it's the padding edge that's the containing block, we
// can use our rect (the border edge) since if the border style
// changed, the reflow would have been targeted at us so we'd satisfy
// condition 1.
// XXX checking oldSize is bogus, there are various reasons we might have
// reflowed but our size might not have been changed to what we
// asked for (e.g., we ended up being pushed to a new page)
// When WillReflowAgainForClearance is true, we will reflow again without
// resetting the size. Because of this, we must not reflow our abs-pos
// children in that situation --- what we think is our "new size" will not be
// our real new size. This also happens to be more efficient.
WritingMode parentWM = aMetrics.GetWritingMode();
if (HasAbsolutelyPositionedChildren()) {
nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
bool haveInterrupt = aPresContext->HasPendingInterrupt();
if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
// Make sure that when we reflow again we'll actually reflow all the abs
// pos frames that might conceivably depend on our size (or all of them,
// if we're dirty right now and interrupted; in that case we also need
// to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
// better than that, because we don't really know what our size will be,
// and it might in fact not change on the followup reflow!
if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
absoluteContainer->MarkAllFramesDirty();
} else {
absoluteContainer->MarkSizeDependentFramesDirty();
}
if (haveInterrupt) {
// We're not going to reflow absolute frames; make sure to account for
// their existing overflow areas, which is usually a side effect of this
// reflow.
//
// TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
// interrupt, can we just rely on it and unconditionally take the else
// branch below? That's a bit more subtle / risky, since I don't see
// what would reflow them in that case if they depended on our size.
for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
kid; kid = kid->GetNextSibling()) {
ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
}
}
} else {
LogicalSize containingBlockSize =
CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput,
aMetrics.Size(parentWM));
// Mark frames that depend on changes we just made to this frame as dirty:
// Now we can assume that the padding edge hasn't moved.
// We need to reflow the absolutes if one of them depends on
// its placeholder position, or the containing block size in a
// direction in which the containing block size might have
// changed.
// XXX "width" and "height" in this block will become ISize and BSize
// when nsAbsoluteContainingBlock is logicalized
bool cbWidthChanged = aMetrics.Width() != oldSize.width;
bool isRoot = !GetContent()->GetParent();
// If isRoot and we have auto height, then we are the initial
// containing block and the containing block height is the
// viewport height, which can't change during incremental
// reflow.
bool cbHeightChanged =
!(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
aMetrics.Height() != oldSize.height;
nsRect containingBlock(nsPoint(0, 0),
containingBlockSize.GetPhysicalSize(parentWM));
AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
if (cbWidthChanged) {
flags |= AbsPosReflowFlags::CBWidthChanged;
}
if (cbHeightChanged) {
flags |= AbsPosReflowFlags::CBHeightChanged;
}
// Setup the line cursor here to optimize line searching for
// calculating hypothetical position of absolutely-positioned
// frames.
SetupLineCursorForQuery();
absoluteContainer->Reflow(this, aPresContext, aReflowInput, reflowStatus,
containingBlock, flags,
&aMetrics.mOverflowAreas);
}
}
FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
aStatus = reflowStatus;
#ifdef DEBUG
// Between when we drain pushed floats and when we complete reflow,
// we're allowed to have multiple continuations of the same float on
// our floats list, since a first-in-flow might get pushed to a later
// continuation of its containing block. But it's not permitted
// outside that time.
nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": status=%s metrics=%d,%d carriedMargin=%d",
ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
if (HasOverflowAreas()) {
printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
aMetrics.InkOverflow().height);
printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
aMetrics.ScrollableOverflow().y,
aMetrics.ScrollableOverflow().width,
aMetrics.ScrollableOverflow().height);
}
printf("\n");
}
if (gLameReflowMetrics) {
PRTime end = PR_Now();
int32_t ectc = nsLineBox::GetCtorCount();
int32_t numLines = mLines.size();
if (!numLines) {
numLines = 1;
}
PRTime delta, perLineDelta, lines;
lines = int64_t(numLines);
delta = end - start;
perLineDelta = delta / lines;
ListTag(stdout);
char buf[400];
SprintfLiteral(buf,
": %" PRId64 " elapsed (%" PRId64
" per line) (%d lines; %d new lines)",
delta, perLineDelta, numLines, ectc - ctc);
printf("%s\n", buf);
}
#endif
}
nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
TrialReflowState& aTrialState) {
#ifdef DEBUG
// Between when we drain pushed floats and when we complete reflow,
// we're allowed to have multiple continuations of the same float on
@ -1827,18 +1458,21 @@ nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot,
blockEndMarginRoot, aTrialState.mNeedFloatManager,
aTrialState.mConsumedBSize,
aTrialState.mEffectiveContentBoxBSize,
aTrialState.mInset);
blockEndMarginRoot, needFloatManager, consumedBSize,
effectiveContentBoxBSize);
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) {
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
}
// Handle paginated overflow (see nsContainerFrame.h)
OverflowAreas ocBounds;
nsReflowStatus ocStatus;
if (GetPrevInFlow()) {
ReflowOverflowContainerChildren(
aPresContext, aReflowInput, aTrialState.mOcBounds,
ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
Some(state.ContainerSize()));
aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default,
ocStatus, DefaultChildFrameMerge, Some(state.ContainerSize()));
}
// Now that we're done cleaning up our overflow container lists, we can
@ -1848,7 +1482,8 @@ nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
// Drain & handle pushed floats
DrainPushedFloats();
ReflowPushedFloats(state, aTrialState.mFcBounds);
OverflowAreas fcBounds;
ReflowPushedFloats(state, fcBounds);
// If we're not dirty (which means we'll mark everything dirty later)
// and our inline-size has changed, mark the lines dirty that we need to
@ -1865,13 +1500,7 @@ nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
mLines.front()->MarkDirty();
}
// For text-wrap:balance trials, we need to reflow all the lines even if
// they're not all "dirty".
if (aTrialState.mBalancing) {
MarkAllDescendantLinesDirty(this);
} else {
LazyMarkLinesDirty();
}
LazyMarkLinesDirty();
// Now reflow...
ReflowDirtyLines(state);
@ -1984,12 +1613,204 @@ nsReflowStatus nsBlockFrame::TrialReflow(nsPresContext* aPresContext,
CheckFloats(state);
// Compute our final size (for this trial layout)
aTrialState.mBlockEndEdgeOfChildren =
ComputeFinalSize(aReflowInput, state, aMetrics);
aTrialState.mContainerWidth = state.ContainerSize().width;
// Compute our final size
nscoord blockEndEdgeOfChildren;
ComputeFinalSize(aReflowInput, state, aMetrics, &blockEndEdgeOfChildren);
return state.mReflowStatus;
// If the block direction is right-to-left, we need to update the bounds of
// lines that were placed relative to mContainerSize during reflow, as
// we typically do not know the true container size until we've reflowed all
// its children. So we use a dummy mContainerSize during reflow (see
// BlockReflowState's constructor) and then fix up the positions of the
// lines here, once the final block size is known.
//
// Note that writing-mode:vertical-rl is the only case where the block
// logical direction progresses in a negative physical direction, and
// therefore block-dir coordinate conversion depends on knowing the width
// of the coordinate space in order to translate between the logical and
// physical origins.
if (wm.IsVerticalRL()) {
nsSize containerSize = aMetrics.PhysicalSize();
nscoord deltaX = containerSize.width - state.ContainerSize().width;
if (deltaX != 0) {
// We compute our lines and markers' overflow areas later in
// ComputeOverflowAreas(), so we don't need to adjust their overflow areas
// here.
const nsPoint physicalDelta(deltaX, 0);
for (auto& line : Lines()) {
UpdateLineContainerSize(&line, containerSize);
}
fcBounds.Clear();
for (nsIFrame* f : mFloats) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(fcBounds, f);
}
nsFrameList* markerList = GetOutsideMarkerList();
if (markerList) {
for (nsIFrame* f : *markerList) {
f->MovePositionBy(physicalDelta);
}
}
if (nsFrameList* overflowContainers = GetOverflowContainers()) {
ocBounds.Clear();
for (nsIFrame* f : *overflowContainers) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(ocBounds, f);
}
}
}
}
aMetrics.SetOverflowAreasToDesiredBounds();
ComputeOverflowAreas(aMetrics.mOverflowAreas, blockEndEdgeOfChildren,
aReflowInput.mStyleDisplay);
// Factor overflow container child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(ocBounds);
// Factor pushed float child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(fcBounds);
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed, e.g., elements with a percentage
// based width/height
// We want to do this under either of two conditions:
// 1. If we didn't do the incremental reflow above.
// 2. If our size changed.
// Even though it's the padding edge that's the containing block, we
// can use our rect (the border edge) since if the border style
// changed, the reflow would have been targeted at us so we'd satisfy
// condition 1.
// XXX checking oldSize is bogus, there are various reasons we might have
// reflowed but our size might not have been changed to what we
// asked for (e.g., we ended up being pushed to a new page)
// When WillReflowAgainForClearance is true, we will reflow again without
// resetting the size. Because of this, we must not reflow our abs-pos
// children in that situation --- what we think is our "new size" will not be
// our real new size. This also happens to be more efficient.
WritingMode parentWM = aMetrics.GetWritingMode();
if (HasAbsolutelyPositionedChildren()) {
nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
bool haveInterrupt = aPresContext->HasPendingInterrupt();
if (aReflowInput.WillReflowAgainForClearance() || haveInterrupt) {
// Make sure that when we reflow again we'll actually reflow all the abs
// pos frames that might conceivably depend on our size (or all of them,
// if we're dirty right now and interrupted; in that case we also need
// to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
// better than that, because we don't really know what our size will be,
// and it might in fact not change on the followup reflow!
if (haveInterrupt && HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
absoluteContainer->MarkAllFramesDirty();
} else {
absoluteContainer->MarkSizeDependentFramesDirty();
}
if (haveInterrupt) {
// We're not going to reflow absolute frames; make sure to account for
// their existing overflow areas, which is usually a side effect of this
// reflow.
//
// TODO(emilio): nsAbsoluteContainingBlock::Reflow already checks for
// interrupt, can we just rely on it and unconditionally take the else
// branch below? That's a bit more subtle / risky, since I don't see
// what would reflow them in that case if they depended on our size.
for (nsIFrame* kid = absoluteContainer->GetChildList().FirstChild();
kid; kid = kid->GetNextSibling()) {
ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
}
}
} else {
LogicalSize containingBlockSize =
CalculateContainingBlockSizeForAbsolutes(parentWM, aReflowInput,
aMetrics.Size(parentWM));
// Mark frames that depend on changes we just made to this frame as dirty:
// Now we can assume that the padding edge hasn't moved.
// We need to reflow the absolutes if one of them depends on
// its placeholder position, or the containing block size in a
// direction in which the containing block size might have
// changed.
// XXX "width" and "height" in this block will become ISize and BSize
// when nsAbsoluteContainingBlock is logicalized
bool cbWidthChanged = aMetrics.Width() != oldSize.width;
bool isRoot = !GetContent()->GetParent();
// If isRoot and we have auto height, then we are the initial
// containing block and the containing block height is the
// viewport height, which can't change during incremental
// reflow.
bool cbHeightChanged =
!(isRoot && NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedHeight()) &&
aMetrics.Height() != oldSize.height;
nsRect containingBlock(nsPoint(0, 0),
containingBlockSize.GetPhysicalSize(parentWM));
AbsPosReflowFlags flags = AbsPosReflowFlags::ConstrainHeight;
if (cbWidthChanged) {
flags |= AbsPosReflowFlags::CBWidthChanged;
}
if (cbHeightChanged) {
flags |= AbsPosReflowFlags::CBHeightChanged;
}
// Setup the line cursor here to optimize line searching for
// calculating hypothetical position of absolutely-positioned
// frames.
SetupLineCursorForQuery();
absoluteContainer->Reflow(this, aPresContext, aReflowInput,
state.mReflowStatus, containingBlock, flags,
&aMetrics.mOverflowAreas);
}
}
FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
aStatus = state.mReflowStatus;
#ifdef DEBUG
// Between when we drain pushed floats and when we complete reflow,
// we're allowed to have multiple continuations of the same float on
// our floats list, since a first-in-flow might get pushed to a later
// continuation of its containing block. But it's not permitted
// outside that time.
nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": status=%s metrics=%d,%d carriedMargin=%d",
ToString(aStatus).c_str(), aMetrics.ISize(parentWM),
aMetrics.BSize(parentWM), aMetrics.mCarriedOutBEndMargin.get());
if (HasOverflowAreas()) {
printf(" overflow-vis={%d,%d,%d,%d}", aMetrics.InkOverflow().x,
aMetrics.InkOverflow().y, aMetrics.InkOverflow().width,
aMetrics.InkOverflow().height);
printf(" overflow-scr={%d,%d,%d,%d}", aMetrics.ScrollableOverflow().x,
aMetrics.ScrollableOverflow().y,
aMetrics.ScrollableOverflow().width,
aMetrics.ScrollableOverflow().height);
}
printf("\n");
}
if (gLameReflowMetrics) {
PRTime end = PR_Now();
int32_t ectc = nsLineBox::GetCtorCount();
int32_t numLines = mLines.size();
if (!numLines) {
numLines = 1;
}
PRTime delta, perLineDelta, lines;
lines = int64_t(numLines);
delta = end - start;
perLineDelta = delta / lines;
ListTag(stdout);
char buf[400];
SprintfLiteral(buf,
": %" PRId64 " elapsed (%" PRId64
" per line) (%d lines; %d new lines)",
delta, perLineDelta, numLines, ectc - ctc);
printf("%s\n", buf);
}
#endif
}
bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
@ -2087,9 +1908,10 @@ static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
return edge;
}
nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
BlockReflowState& aState,
ReflowOutput& aMetrics) {
void nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
BlockReflowState& aState,
ReflowOutput& aMetrics,
nscoord* aBEndEdgeOfChildren) {
WritingMode wm = aState.mReflowInput.GetWritingMode();
const LogicalMargin& borderPadding = aState.BorderPadding();
#ifdef NOISY_FINAL_SIZE
@ -2327,6 +2149,7 @@ nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
// Screen out negative block sizes --- can happen due to integer overflows :-(
finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
*aBEndEdgeOfChildren = blockEndEdgeOfChildren;
if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
@ -2343,8 +2166,6 @@ nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
}
#endif
return blockEndEdgeOfChildren;
}
void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
@ -4731,10 +4552,10 @@ void nsBlockFrame::DoReflowInlineFrames(
// because it might get disabled there
aLine->EnableResizeReflowOptimization();
aLineLayout.BeginLineReflow(
iStart, aState.mBCoord, availISize, availBSize,
aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/
lineWM, aState.mContainerSize, aState.mInsetForBalance);
aLineLayout.BeginLineReflow(iStart, aState.mBCoord, availISize, availBSize,
aFloatAvailableSpace.HasFloats(),
false, /*XXX isTopOfPage*/
lineWM, aState.mContainerSize);
aState.mFlags.mIsLineLayoutEmpty = false;

Просмотреть файл

@ -481,9 +481,9 @@ class nsBlockFrame : public nsContainerFrame {
// helper for SlideLine and UpdateLineContainerSize
void MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord);
// Returns block-end edge of children.
nscoord ComputeFinalSize(const ReflowInput& aReflowInput,
BlockReflowState& aState, ReflowOutput& aMetrics);
void ComputeFinalSize(const ReflowInput& aReflowInput,
BlockReflowState& aState, ReflowOutput& aMetrics,
nscoord* aBEndEdgeOfChildren);
/**
* Helper method for Reflow(). Computes the overflow areas created by our
@ -534,61 +534,6 @@ class nsBlockFrame : public nsContainerFrame {
*/
bool IsVisualFormControl(nsPresContext* aPresContext);
/**
* For text-wrap:balance, we iteratively try reflowing with adjusted inline
* size to find the "best" result (the tightest size that can be applied
* without increasing the total line count of the block).
* This record is used to manage the state of these "trial reflows", and
* return results from the final trial.
*/
struct TrialReflowState {
// Values pre-computed at start of Reflow(), constant across trials.
const nscoord mConsumedBSize;
const nscoord mEffectiveContentBoxBSize;
bool mNeedFloatManager;
// Settings for the current trial.
bool mBalancing = false;
nscoord mInset = 0;
// Results computed during the trial reflow. Values from the final trial
// will be used by the remainder of Reflow().
mozilla::OverflowAreas mOcBounds;
mozilla::OverflowAreas mFcBounds;
nscoord mBlockEndEdgeOfChildren = 0;
nscoord mContainerWidth = 0;
// Initialize for the initial trial reflow, with zero inset.
TrialReflowState(nscoord aConsumedBSize, nscoord aEffectiveContentBoxBSize,
bool aNeedFloatManager)
: mConsumedBSize(aConsumedBSize),
mEffectiveContentBoxBSize(aEffectiveContentBoxBSize),
mNeedFloatManager(aNeedFloatManager) {}
// Adjust the inset amount, and reset state for a new trial.
void ResetForBalance(nscoord aInsetDelta) {
// Tells the reflow-lines loop we must consider all lines "dirty" (as we
// are modifying the effective inline-size to be used).
mBalancing = true;
// Adjust inset to apply.
mInset += aInsetDelta;
// Re-initialize state that the reflow loop will compute.
mOcBounds.Clear();
mFcBounds.Clear();
mBlockEndEdgeOfChildren = 0;
mContainerWidth = 0;
}
};
/**
* Internal helper for Reflow(); may be called repeatedly during a single
* Reflow() in order to implement text-wrap:balance.
* This method applies aTrialState.mInset during line-breaking to reduce
* the effective available inline-size (without affecting alignment).
*/
nsReflowStatus TrialReflow(nsPresContext* aPresContext,
ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
TrialReflowState& aTrialState);
public:
/**
* Helper function for the frame ctor to register a ::marker frame.

Просмотреть файл

@ -140,8 +140,7 @@ void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
nscoord aISize, nscoord aBSize,
bool aImpactedByFloats, bool aIsTopOfPage,
WritingMode aWritingMode,
const nsSize& aContainerSize,
nscoord aInset) {
const nsSize& aContainerSize) {
NS_ASSERTION(nullptr == mRootSpan, "bad linelayout user");
LAYOUT_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE,
"have unconstrained width; this should only result from "
@ -194,9 +193,6 @@ void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
psd->mIStart = aICoord;
psd->mICoord = aICoord;
psd->mIEnd = aICoord + aISize;
// Set up inset to be used for text-wrap:balance implementation, but only if
// the available size is at least 2*inset.
psd->mInset = aISize < aInset * 2 ? 0 : aInset;
mContainerSize = aContainerSize;
mBStartEdge = aBCoord;
@ -410,7 +406,6 @@ void nsLineLayout::BeginSpan(nsIFrame* aFrame,
psd->mIStart = aIStart;
psd->mICoord = aIStart;
psd->mIEnd = aIEnd;
psd->mInset = mCurrentSpan->mInset;
psd->mBaseline = aBaseline;
nsIFrame* frame = aSpanReflowInput->mFrame;
@ -806,7 +801,7 @@ void nsLineLayout::ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus,
"have unconstrained width; this should only result from "
"very large sizes, not attempts at intrinsic width "
"calculation");
nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord - psd->mInset;
nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord;
// Setup reflow input for reflowing the frame
Maybe<ReflowInput> reflowInputHolder;

Просмотреть файл

@ -48,10 +48,7 @@ class nsLineLayout {
void BeginLineReflow(nscoord aICoord, nscoord aBCoord, nscoord aISize,
nscoord aBSize, bool aImpactedByFloats,
bool aIsTopOfPage, mozilla::WritingMode aWritingMode,
const nsSize& aContainerSize,
// aInset is used during text-wrap:balance to reduce
// the effective available space on the line.
nscoord aInset = 0);
const nsSize& aContainerSize);
void EndLineReflow();
@ -497,7 +494,6 @@ class nsLineLayout {
nscoord mIStart;
nscoord mICoord;
nscoord mIEnd;
nscoord mInset;
nscoord mBStartLeading, mBEndLeading;
nscoord mLogicalBSize;

Просмотреть файл

@ -175,7 +175,6 @@ rusty-enums = [
"mozilla::StyleBlend",
"mozilla::StyleMaskComposite",
"mozilla::StyleWritingModeProperty",
"mozilla::StyleTextWrap",
"StyleFontVariantEmoji",
]
allowlist-vars = [

Просмотреть файл

@ -414,17 +414,6 @@ enum class StyleWhiteSpace : uint8_t {
BreakSpaces,
};
// See nsStyleText
// TODO: this will become StyleTextWrapStyle when we turn text-wrap
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=1758391) and
// white-space (https://bugzilla.mozilla.org/show_bug.cgi?id=1852478)
// into shorthands.
enum class StyleTextWrap : uint8_t {
Auto = 0,
Stable,
Balance,
};
// ruby-align, see nsStyleText
enum class StyleRubyAlign : uint8_t {
Start,

Просмотреть файл

@ -2894,8 +2894,7 @@ nsStyleText::nsStyleText(const nsStyleText& aSource)
mTextShadow(aSource.mTextShadow),
mTextEmphasisStyle(aSource.mTextEmphasisStyle),
mHyphenateCharacter(aSource.mHyphenateCharacter),
mWebkitTextSecurity(aSource.mWebkitTextSecurity),
mTextWrap(aSource.mTextWrap) {
mWebkitTextSecurity(aSource.mWebkitTextSecurity) {
MOZ_COUNT_CTOR(nsStyleText);
}
@ -2929,8 +2928,7 @@ nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aNewData) const {
(mWordSpacing != aNewData.mWordSpacing) ||
(mTabSize != aNewData.mTabSize) ||
(mHyphenateCharacter != aNewData.mHyphenateCharacter) ||
(mWebkitTextSecurity != aNewData.mWebkitTextSecurity) ||
(mTextWrap != aNewData.mTextWrap)) {
(mWebkitTextSecurity != aNewData.mWebkitTextSecurity)) {
return NS_STYLE_HINT_REFLOW;
}

Просмотреть файл

@ -889,8 +889,6 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleText {
mozilla::StyleTextSecurity mWebkitTextSecurity =
mozilla::StyleTextSecurity::None;
mozilla::StyleTextWrap mTextWrap = mozilla::StyleTextWrap::Auto;
char16_t TextSecurityMaskChar() const {
switch (mWebkitTextSecurity) {
case mozilla::StyleTextSecurity::None:

Просмотреть файл

@ -8561,17 +8561,6 @@ var gCSSProperties = {
],
invalid_values: [],
},
"text-wrap": {
domProp: "textWrap",
inherited: true,
type: CSS_TYPE_LONGHAND,
applies_to_placeholder: true,
applies_to_cue: true,
applies_to_marker: true,
initial_values: ["auto"],
other_values: ["stable", "balance"],
invalid_values: ["wrap", "nowrap", "normal"],
},
width: {
domProp: "width",
inherited: false,

Просмотреть файл

@ -8764,22 +8764,6 @@
value: false
mirror: always
- name: layout.css.text-wrap-balance.enabled
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
# Maximum number of lines to try balancing.
- name: layout.css.text-wrap-balance.limit
type: int32_t
value: 10
mirror: always
- name: layout.css.text-wrap-balance-after-clamp.enabled
type: bool
value: true
mirror: always
# Whether to block large cursors intersecting UI.
- name: layout.cursor.block.enabled
type: bool

Просмотреть файл

@ -805,7 +805,6 @@ def _remove_common_first_line_and_first_letter_properties(props, engine):
props.remove("text-align")
props.remove("text-justify")
props.remove("white-space")
props.remove("text-wrap")
props.remove("word-break")
props.remove("text-indent")
@ -908,7 +907,6 @@ class PropertyRestrictions:
props = PropertyRestrictions.first_line(data)
props.add("opacity")
props.add("white-space")
props.add("text-wrap")
props.add("text-overflow")
props.add("text-align")
props.add("text-justify")
@ -920,7 +918,6 @@ class PropertyRestrictions:
return set(
[
"white-space",
"text-wrap",
"color",
"text-combine-upright",
"text-transform",
@ -945,7 +942,6 @@ class PropertyRestrictions:
"visibility",
"text-shadow",
"white-space",
"text-wrap",
"text-combine-upright",
"ruby-position",
# XXX Should these really apply to cue?

Просмотреть файл

@ -438,15 +438,3 @@ ${helpers.single_keyword(
spec="https://drafts.csswg.org/css-text/#MISSING",
affects="layout",
)}
${helpers.single_keyword(
"text-wrap",
"auto stable balance",
engines="gecko",
gecko_pref="layout.css.text-wrap-balance.enabled",
has_effect_on_gecko_scrollbars=False,
gecko_enum_prefix="StyleTextWrap",
animation_value_type="discrete",
spec="https://drafts.csswg.org/css-text-4/#text-wrap",
affects="layout",
)}

Просмотреть файл

@ -1,3 +1,3 @@
[text-wrap-invalid.html]
expected:
PASS
if (os == "android") and fission: [OK, TIMEOUT]

Просмотреть файл

@ -1,13 +1,36 @@
[text-wrap-valid.html]
expected:
if (os == "android") and fission: [OK, TIMEOUT]
[e.style['text-wrap'\] = "wrap" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "nowrap" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "balance" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "stable" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "pretty" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "initial" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "inherit" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "unset" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "revert" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "revert-layer" should set the property value]
expected: FAIL
[e.style['text-wrap'\] = "auto" should set the property value]
expected: FAIL

Просмотреть файл

@ -1,6 +1,15 @@
[white-space-shorthand-text-wrap.html]
[`text-wrap: balance` should be set]
expected: FAIL
[`text-wrap` should not be affected by previous `white-space`]
expected: FAIL
[`white-space` should overwrite previous `text-wrap`]
expected: FAIL
[`text-wrap` should not be affected by `white-space` on the parent]
expected: FAIL
[`white-space` should overwrite `text-wrap` on the parent]
expected: FAIL

Просмотреть файл

@ -0,0 +1,2 @@
[text-wrap-balance-002.html]
expected: FAIL

Просмотреть файл

@ -0,0 +1,2 @@
[text-wrap-balance-text-indent-001.html]
expected: FAIL

Просмотреть файл

@ -1,16 +0,0 @@
<!DOCTYPE html>
<style>
.container {
font: 20px/1.5 monospace;
width: 23.5ch;
border: solid 1px;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: left;
}
</style>
<div class="container"><div class="float">FLOAT<br>FLOAT</div>abc de fg<br>hij klm<br>nop qrst uvw xyz!</div>

Просмотреть файл

@ -1,16 +0,0 @@
<!DOCTYPE html>
<style>
.container {
font: 20px/1.5 monospace;
width: 23.5ch;
border: solid 1px;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: right;
}
</style>
<div class="container"><div class="float">FLOAT<br>FLOAT</div>abc de fg<br>hij klm<br>nop qrst uvw xyz!</div>

Просмотреть файл

@ -1,22 +0,0 @@
<!DOCTYPE html>
<style>
.container {
font: 20px/1.5 monospace;
width: 26.5ch;
border: solid 1px;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: left;
}
.float2 {
background: cyan;
padding: 0.2em 1ch;
line-height: 1.2;
float: right;
}
</style>
<div class="container"><div class="float">FLOAT<br>FLOAT</div>abcde fghi<br>jklm nop<div class="float2">FLOAT<br>FLOAT</div><br>qrst uvw xyz!</div>

Просмотреть файл

@ -1,19 +0,0 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-text-4/#valdef-text-wrap-balance">
<link rel="match" href="reference/text-wrap-balance-float-001-ref.html">
<style>
.container {
font: 20px/1.5 monospace;
width: 23.5ch;
border: solid 1px;
text-wrap: balance;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: left;
}
</style>
<div class="container"><div class="float">FLOAT<br>FLOAT</div>abc de fg hij klm nop qrst uvw xyz!</div>

Просмотреть файл

@ -1,19 +0,0 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-text-4/#valdef-text-wrap-balance">
<link rel="match" href="reference/text-wrap-balance-float-002-ref.html">
<style>
.container {
font: 20px/1.5 monospace;
width: 23.5ch;
border: solid 1px;
text-wrap: balance;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: right;
}
</style>
<div class="container"><div class="float">FLOAT<br>FLOAT</div>abc de fg hij klm nop qrst uvw xyz!</div>

Просмотреть файл

@ -1,19 +0,0 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-text-4/#valdef-text-wrap-balance">
<link rel="match" href="reference/text-wrap-balance-float-001-ref.html">
<style>
.container {
font: 20px/1.5 monospace;
width: 23.5ch;
border: solid 1px;
text-wrap: balance;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: left;
}
</style>
<div class="container">abc de<div class="float">FLOAT<br>FLOAT</div> fg hij klm nop qrst uvw xyz!</div>

Просмотреть файл

@ -1,19 +0,0 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-text-4/#valdef-text-wrap-balance">
<link rel="match" href="reference/text-wrap-balance-float-002-ref.html">
<style>
.container {
font: 20px/1.5 monospace;
width: 23.5ch;
border: solid 1px;
text-wrap: balance;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: right;
}
</style>
<div class="container">abc de <div class="float">FLOAT<br>FLOAT</div>fg hij klm nop qrst uvw xyz!</div>

Просмотреть файл

@ -1,41 +0,0 @@
<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-text-4/#valdef-text-wrap-balance">
<link rel="match" href="reference/text-wrap-balance-float-005-ref.html">
<style>
.container {
font: 20px/1.5 monospace;
width: 26.5ch;
border: solid 1px;
text-wrap: balance;
}
.float {
background: yellow;
padding: 0.2em 1ch;
line-height: 1.2;
float: left;
}
.float2 {
background: cyan;
padding: 0.2em 1ch;
line-height: 1.2;
float: right;
}
</style>
<!--
The floats (inc. padding) are 7ch wide, and occupy ~2 lines,
so without balancing, layout would be
+--------------------------+
| FLOAT abcde fghi jklm nop|
| FLOAT qrst uvw FLOAT |
|xyx! FLOAT |
+--------------------------+
Applying text-wrap:balance results in
+--------------------------+
| FLOAT abcde fghi |
| FLOAT jklm nop FLOAT |
|qrst uvw xyx! FLOAT |
+--------------------------+
-->
<div class="container"><div class="float">FLOAT<br>FLOAT</div>abcde fghi jklm nop<div class="float2">FLOAT<br>FLOAT</div> qrst uvw xyz!</div>