mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
Bug 1731541 - Implement text-wrap: balance for nsBlockFrame reflow. r=emilio
A simple form of balance for short blocks, implemented by incrementally reducing the effective inline-size used during line-breaking, up to the point where an extra line would be created. This fails the test text-wrap-balance-line-clamp-001.html, but it's unclear to me if that test is correct (see https://github.com/w3c/csswg-drafts/issues/9310). If we do want the behavior expected by that test, an additional patch to handle the interaction with line-clamp will be required. Depends on D187543 Differential Revision: https://phabricator.services.mozilla.com/D187544
This commit is contained in:
parent
0428bf09b5
commit
bd93e1361b
@ -27,17 +27,16 @@
|
||||
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)
|
||||
BlockReflowState::BlockReflowState(
|
||||
const ReflowInput& aReflowInput, nsPresContext* aPresContext,
|
||||
nsBlockFrame* aFrame, bool aBStartMarginRoot, bool aBEndMarginRoot,
|
||||
bool aBlockNeedsFloatManager, const nscoord aConsumedBSize,
|
||||
const nscoord aEffectiveContentBoxBSize, const nscoord aInset)
|
||||
: mBlock(aFrame),
|
||||
mPresContext(aPresContext),
|
||||
mReflowInput(aReflowInput),
|
||||
mContentArea(aReflowInput.GetWritingMode()),
|
||||
mInsetForBalance(aInset),
|
||||
mPushedFloats(nullptr),
|
||||
mOverflowTracker(nullptr),
|
||||
mBorderPadding(
|
||||
|
@ -98,7 +98,8 @@ class BlockReflowState {
|
||||
nsBlockFrame* aFrame, bool aBStartMarginRoot,
|
||||
bool aBEndMarginRoot, bool aBlockNeedsFloatManager,
|
||||
const nscoord aConsumedBSize,
|
||||
const nscoord aEffectiveContentBoxBSize);
|
||||
const nscoord aEffectiveContentBoxBSize,
|
||||
const nscoord aInset = 0);
|
||||
|
||||
/**
|
||||
* Get the available reflow space (the area not occupied by floats)
|
||||
@ -243,9 +244,9 @@ class BlockReflowState {
|
||||
// the block.
|
||||
|
||||
// The block frame that is using this object
|
||||
nsBlockFrame* mBlock;
|
||||
nsBlockFrame* const mBlock;
|
||||
|
||||
nsPresContext* mPresContext;
|
||||
nsPresContext* const mPresContext;
|
||||
|
||||
const ReflowInput& mReflowInput;
|
||||
|
||||
@ -301,6 +302,10 @@ 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; }
|
||||
@ -345,7 +350,7 @@ class BlockReflowState {
|
||||
|
||||
// mBlock's computed logical border+padding with pre-reflow skip sides applied
|
||||
// (See the constructor and nsIFrame::PreReflowBlockLevelLogicalSkipSides).
|
||||
LogicalMargin mBorderPadding;
|
||||
const LogicalMargin mBorderPadding;
|
||||
|
||||
// The overflow areas of all floats placed so far
|
||||
OverflowAreas mFloatOverflowAreas;
|
||||
@ -383,7 +388,7 @@ class BlockReflowState {
|
||||
// placed, since we're on a nowrap context.
|
||||
nsTArray<nsIFrame*> mNoWrapFloats;
|
||||
|
||||
nscoord mMinLineHeight;
|
||||
const nscoord mMinLineHeight;
|
||||
|
||||
int32_t mLineNumber;
|
||||
|
||||
|
@ -1414,6 +1414,16 @@ 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();
|
||||
@ -1430,16 +1440,316 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
autoFloatManager.CreateFloatManager(aPresContext);
|
||||
}
|
||||
|
||||
// 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;
|
||||
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
|
||||
PresContext()->BidiEnabled()) {
|
||||
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
|
||||
}
|
||||
|
||||
// Whether to apply text-wrap: balance behavior.
|
||||
bool tryBalance = StyleText()->mTextWrap == StyleTextWrap::Balance &&
|
||||
!GetPrevContinuation();
|
||||
|
||||
// Target number of lines in the block while balancing; negative if no
|
||||
// balancing is being done.
|
||||
int32_t balanceTarget = -1;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
// "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 =
|
||||
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
|
||||
if (balanceTarget < 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;
|
||||
trialState.ResetForBalance(balanceStep);
|
||||
balanceStep /= 2;
|
||||
|
||||
// Restore initial floatManager state for a new trial with updated inset.
|
||||
aReflowInput.mFloatManager->PopState(&floatManagerState);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
int32_t numLines =
|
||||
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
|
||||
if (reflowStatus.IsFullyComplete() && numLines == balanceTarget) {
|
||||
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 >= 0) {
|
||||
int32_t numLines =
|
||||
countLinesUpTo(StaticPrefs::layout_css_text_wrap_balance_limit());
|
||||
if (reflowStatus.IsFullyComplete() && numLines == balanceTarget) {
|
||||
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
|
||||
@ -1458,21 +1768,18 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
|
||||
|
||||
BlockReflowState state(aReflowInput, aPresContext, this, blockStartMarginRoot,
|
||||
blockEndMarginRoot, needFloatManager, consumedBSize,
|
||||
effectiveContentBoxBSize);
|
||||
|
||||
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
|
||||
PresContext()->BidiEnabled()) {
|
||||
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
|
||||
}
|
||||
blockEndMarginRoot, aTrialState.mNeedFloatManager,
|
||||
aTrialState.mConsumedBSize,
|
||||
aTrialState.mEffectiveContentBoxBSize,
|
||||
aTrialState.mInset);
|
||||
|
||||
// Handle paginated overflow (see nsContainerFrame.h)
|
||||
OverflowAreas ocBounds;
|
||||
nsReflowStatus ocStatus;
|
||||
if (GetPrevInFlow()) {
|
||||
ReflowOverflowContainerChildren(
|
||||
aPresContext, aReflowInput, ocBounds, ReflowChildFlags::Default,
|
||||
ocStatus, DefaultChildFrameMerge, Some(state.ContainerSize()));
|
||||
aPresContext, aReflowInput, aTrialState.mOcBounds,
|
||||
ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
|
||||
Some(state.ContainerSize()));
|
||||
}
|
||||
|
||||
// Now that we're done cleaning up our overflow container lists, we can
|
||||
@ -1482,8 +1789,7 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
|
||||
// Drain & handle pushed floats
|
||||
DrainPushedFloats();
|
||||
OverflowAreas fcBounds;
|
||||
ReflowPushedFloats(state, fcBounds);
|
||||
ReflowPushedFloats(state, aTrialState.mFcBounds);
|
||||
|
||||
// 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
|
||||
@ -1500,7 +1806,13 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
mLines.front()->MarkDirty();
|
||||
}
|
||||
|
||||
LazyMarkLinesDirty();
|
||||
// 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();
|
||||
}
|
||||
|
||||
// Now reflow...
|
||||
ReflowDirtyLines(state);
|
||||
@ -1613,204 +1925,12 @@ void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
|
||||
|
||||
CheckFloats(state);
|
||||
|
||||
// Compute our final size
|
||||
nscoord blockEndEdgeOfChildren;
|
||||
ComputeFinalSize(aReflowInput, state, aMetrics, &blockEndEdgeOfChildren);
|
||||
// Compute our final size (for this trial layout)
|
||||
aTrialState.mBlockEndEdgeOfChildren =
|
||||
ComputeFinalSize(aReflowInput, state, aMetrics);
|
||||
aTrialState.mContainerWidth = state.ContainerSize().width;
|
||||
|
||||
// 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
|
||||
return state.mReflowStatus;
|
||||
}
|
||||
|
||||
bool nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine() {
|
||||
@ -1908,10 +2028,9 @@ static nscoord ApplyLineClamp(const ReflowInput& aReflowInput,
|
||||
return edge;
|
||||
}
|
||||
|
||||
void nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
|
||||
BlockReflowState& aState,
|
||||
ReflowOutput& aMetrics,
|
||||
nscoord* aBEndEdgeOfChildren) {
|
||||
nscoord nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
|
||||
BlockReflowState& aState,
|
||||
ReflowOutput& aMetrics) {
|
||||
WritingMode wm = aState.mReflowInput.GetWritingMode();
|
||||
const LogicalMargin& borderPadding = aState.BorderPadding();
|
||||
#ifdef NOISY_FINAL_SIZE
|
||||
@ -2149,7 +2268,6 @@ void 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);
|
||||
@ -2166,6 +2284,8 @@ void nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
|
||||
printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
|
||||
}
|
||||
#endif
|
||||
|
||||
return blockEndEdgeOfChildren;
|
||||
}
|
||||
|
||||
void nsBlockFrame::ConsiderBlockEndEdgeOfChildren(
|
||||
@ -4552,10 +4672,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);
|
||||
aLineLayout.BeginLineReflow(
|
||||
iStart, aState.mBCoord, availISize, availBSize,
|
||||
aFloatAvailableSpace.HasFloats(), false, /*XXX isTopOfPage*/
|
||||
lineWM, aState.mContainerSize, aState.mInsetForBalance);
|
||||
|
||||
aState.mFlags.mIsLineLayoutEmpty = false;
|
||||
|
||||
|
@ -481,9 +481,9 @@ class nsBlockFrame : public nsContainerFrame {
|
||||
// helper for SlideLine and UpdateLineContainerSize
|
||||
void MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord);
|
||||
|
||||
void ComputeFinalSize(const ReflowInput& aReflowInput,
|
||||
BlockReflowState& aState, ReflowOutput& aMetrics,
|
||||
nscoord* aBEndEdgeOfChildren);
|
||||
// Returns block-end edge of children.
|
||||
nscoord ComputeFinalSize(const ReflowInput& aReflowInput,
|
||||
BlockReflowState& aState, ReflowOutput& aMetrics);
|
||||
|
||||
/**
|
||||
* Helper method for Reflow(). Computes the overflow areas created by our
|
||||
@ -534,6 +534,61 @@ 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,7 +140,8 @@ void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
|
||||
nscoord aISize, nscoord aBSize,
|
||||
bool aImpactedByFloats, bool aIsTopOfPage,
|
||||
WritingMode aWritingMode,
|
||||
const nsSize& aContainerSize) {
|
||||
const nsSize& aContainerSize,
|
||||
nscoord aInset) {
|
||||
NS_ASSERTION(nullptr == mRootSpan, "bad linelayout user");
|
||||
LAYOUT_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE,
|
||||
"have unconstrained width; this should only result from "
|
||||
@ -193,6 +194,9 @@ 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;
|
||||
@ -406,6 +410,7 @@ 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;
|
||||
@ -801,7 +806,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;
|
||||
nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord - psd->mInset;
|
||||
|
||||
// Setup reflow input for reflowing the frame
|
||||
Maybe<ReflowInput> reflowInputHolder;
|
||||
|
@ -48,7 +48,10 @@ class nsLineLayout {
|
||||
void BeginLineReflow(nscoord aICoord, nscoord aBCoord, nscoord aISize,
|
||||
nscoord aBSize, bool aImpactedByFloats,
|
||||
bool aIsTopOfPage, mozilla::WritingMode aWritingMode,
|
||||
const nsSize& aContainerSize);
|
||||
const nsSize& aContainerSize,
|
||||
// aInset is used during text-wrap:balance to reduce
|
||||
// the effective available space on the line.
|
||||
nscoord aInset = 0);
|
||||
|
||||
void EndLineReflow();
|
||||
|
||||
@ -494,6 +497,7 @@ class nsLineLayout {
|
||||
nscoord mIStart;
|
||||
nscoord mICoord;
|
||||
nscoord mIEnd;
|
||||
nscoord mInset;
|
||||
|
||||
nscoord mBStartLeading, mBEndLeading;
|
||||
nscoord mLogicalBSize;
|
||||
|
@ -8777,6 +8777,12 @@
|
||||
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
|
||||
|
||||
# Support for the css Zoom property.
|
||||
- name: layout.css.zoom.enabled
|
||||
type: RelaxedAtomicBool
|
||||
|
@ -1,3 +0,0 @@
|
||||
[text-wrap-invalid.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
@ -1,39 +1,13 @@
|
||||
[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
|
||||
|
||||
[e.style['text-wrap'\] = "wrap auto" should set the property value]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -1,15 +1,6 @@
|
||||
[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
|
||||
|
@ -1,2 +0,0 @@
|
||||
[text-wrap-balance-002.html]
|
||||
expected: FAIL
|
@ -0,0 +1,2 @@
|
||||
[text-wrap-balance-line-clamp-001.html]
|
||||
expected: FAIL
|
@ -1,2 +0,0 @@
|
||||
[text-wrap-balance-text-indent-001.html]
|
||||
expected: FAIL
|
@ -0,0 +1,6 @@
|
||||
[accumulation-per-property-002.html]
|
||||
[text-wrap: "nowrap" onto "wrap"]
|
||||
expected: FAIL
|
||||
|
||||
[text-wrap: "wrap" onto "nowrap"]
|
||||
expected: FAIL
|
@ -1,3 +1,9 @@
|
||||
[addition-per-property-002.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [TIMEOUT, OK]
|
||||
|
||||
[text-wrap: "nowrap" onto "wrap"]
|
||||
expected: FAIL
|
||||
|
||||
[text-wrap: "wrap" onto "nowrap"]
|
||||
expected: FAIL
|
||||
|
@ -1,3 +1,12 @@
|
||||
[interpolation-per-property-002.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
|
||||
[text-wrap uses discrete animation when animating between "wrap" and "nowrap" with linear easing]
|
||||
expected: FAIL
|
||||
|
||||
[text-wrap uses discrete animation when animating between "wrap" and "nowrap" with effect easing]
|
||||
expected: FAIL
|
||||
|
||||
[text-wrap uses discrete animation when animating between "wrap" and "nowrap" with keyframe easing]
|
||||
expected: FAIL
|
||||
|
Loading…
Reference in New Issue
Block a user