/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Mark every line at and below the line where the float was // dirty, and mark their lines dirty too. We could probably do // something more efficient --- e.g., just dirty the lines that intersect // the float vertically.
MarkAllDescendantLinesDirty(blockWithFloatMgr);
}
for (constauto& line : block->Lines()) { if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) { returntrue;
}
} returnfalse;
}
// Determines whether the given frame is visible text or has visible text that // participate in the same line. Frames that are not line participants do not // have their children checked. staticbool FrameHasVisibleInlineText(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Frame argument cannot be null"); if (!aFrame->IsLineParticipant()) { returnfalse;
} if (aFrame->IsTextFrame()) { return aFrame->StyleVisibility()->IsVisible() &&
NS_GET_A(aFrame->StyleText()->mWebkitTextFillColor.CalcColor(
aFrame)) != 0;
} for (nsIFrame* kid : aFrame->PrincipalChildList()) { if (FrameHasVisibleInlineText(kid)) { returntrue;
}
} returnfalse;
}
// Determines whether any of the frames from the given line have visible text. staticbool LineHasVisibleInlineText(nsLineBox* aLine) { for (nsIFrame* kid : aLine->ChildFrames()) { if (FrameHasVisibleInlineText(kid)) { returntrue;
}
} returnfalse;
}
/** *StartingwithaFrame,iteratesupwardthroughparentframesandchecksfor *non-transparentbackgroundcolors.Ifoneisfound,weusethatasour *backplatecolor.Otheriwse,weusethedefaultbackgroundcolorfrom *ourhighcontrasttheme.
*/ static nscolor GetBackplateColor(nsIFrame* aFrame) {
nsPresContext* pc = aFrame->PresContext();
nscolor currentBackgroundColor = NS_TRANSPARENT; for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) { // NOTE(emilio): We assume themed frames (frame->IsThemed()) have correct // background-color information so as to compute the right backplate color. // // This holds because HTML widgets with author-specified backgrounds or // borders disable theming. So as long as the UA-specified background colors // match the actual theme (which they should because we always use system // colors with the non-native theme, and native system colors should also // match the native theme), then we're alright and we should compute an // appropriate backplate color. constauto* style = frame->Style(); if (style->StyleBackground()->IsTransparent(style)) { continue;
} bool drawImage = false, drawColor = false;
nscolor backgroundColor = nsCSSRendering::DetermineBackgroundColor(
pc, style, frame, drawImage, drawColor); if (!drawColor && !drawImage) { continue;
} if (NS_GET_A(backgroundColor) == 0) { // Even if there's a background image, if there's no background color we // keep going up the frame tree, see bug 1723938. continue;
} if (NS_GET_A(currentBackgroundColor) == 0) { // Try to avoid somewhat expensive math in the common case.
currentBackgroundColor = backgroundColor;
} else {
currentBackgroundColor =
NS_ComposeColors(backgroundColor, currentBackgroundColor);
} if (NS_GET_A(currentBackgroundColor) == 0xff) { // If fully opaque, we're done, otherwise keep going up blending with our // background. return currentBackgroundColor;
}
}
nscolor backgroundColor = aFrame->PresContext()->DefaultBackgroundColor(); if (NS_GET_A(currentBackgroundColor) == 0) { return backgroundColor;
} return NS_ComposeColors(backgroundColor, currentBackgroundColor);
}
static nsRect GetNormalMarginRect(const nsIFrame& aFrame, bool aIncludePositiveMargins = true) {
nsMargin m = aFrame.GetUsedMargin().ApplySkipSides(aFrame.GetSkipSides()); if (!aIncludePositiveMargins) {
m.EnsureAtMost(nsMargin());
} auto rect = aFrame.GetRectRelativeToSelf();
rect.Inflate(m); return rect + aFrame.GetNormalPosition();
}
staticvoid ShowDebugFlags() {
printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n"); const BlockDebugFlags* bdf = gFlags; const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; for (; bdf < end; bdf++) {
printf(" %s\n", bdf->name);
}
printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n");
printf("names (no whitespace)\n");
}
void nsBlockFrame::InitDebugFlags() { staticbool firstTime = true; if (firstTime) {
firstTime = false; char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS"); if (flags) { bool error = false; for (;;) { char* cm = strchr(flags, ','); if (cm) {
*cm = '\0';
}
bool found = false; const BlockDebugFlags* bdf = gFlags; const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; for (; bdf < end; bdf++) { if (nsCRT::strcasecmp(bdf->name, flags) == 0) {
*(bdf->on) = true;
printf("nsBlockFrame: setting %s debug flag on\n", bdf->name);
gNoisy = true;
found = true; break;
}
} if (!found) {
error = true;
}
if (!cm) { break;
}
*cm = ',';
flags = cm + 1;
} if (error) {
ShowDebugFlags();
}
}
}
}
// Log updates to the status that yield different values if (record[index] != newS) {
record[index] = newS;
printf("record(%d): %02x %02x\n", index, record[0], record[1]);
}
} #endif
// Add the size of any nsLineBox::mFrames hashtables we might have: for (constauto& line : Lines()) {
line.AddSizeOfExcludingThis(aWindowSizes);
} const FrameLines* overflowLines = GetOverflowLines(); if (overflowLines) {
ConstLineIterator line = overflowLines->mLines.begin(),
line_end = overflowLines->mLines.end(); for (; line != line_end; ++line) {
line->AddSizeOfExcludingThis(aWindowSizes);
}
}
}
// Output the lines if (!mLines.empty()) {
ConstLineIterator line = LinesBegin(), line_end = LinesEnd(); for (; line != line_end; ++line) {
line->List(out, pfx.get(), aFlags);
}
}
// Output the overflow lines. const FrameLines* overflowLines = GetOverflowLines(); if (overflowLines && !overflowLines->mLines.empty()) {
fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines,
&overflowLines->mFrames);
nsCString nestedPfx(pfx);
nestedPfx += " ";
ConstLineIterator line = overflowLines->mLines.begin(),
line_end = overflowLines->mLines.end(); for (; line != line_end; ++line) {
line->List(out, nestedPfx.get(), aFlags);
}
fprintf_stderr(out, "%s>\n", pfx.get());
}
// skip the principal list - we printed the lines above // skip the overflow list - we printed the overflow lines above
ChildListIDs skip = {FrameChildListID::Principal, FrameChildListID::Overflow};
ListChildLists(out, pfx.get(), aFlags, skip);
Maybe<nscoord> offset =
aBaselineGroup == BaselineSharingGroup::First
? GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup,
aExportContext)
: GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup,
aExportContext); if (!offset && IsButtonOrTextInput()) { for (constauto& line : Reversed(Lines())) { if (line.IsEmpty()) { continue;
} // Buttons use the end of the content as a baseline if we haven't found // one yet.
nscoord bEnd = line.BEnd();
offset.emplace(aBaselineGroup == BaselineSharingGroup::Last
? BSize(aWM) - bEnd
: bEnd); break;
}
} return offset;
}
nscoord nsBlockFrame::GetCaretBaseline() const { constauto wm = GetWritingMode(); if (!mLines.empty()) {
ConstLineIterator line = LinesBegin(); if (!line->IsEmpty()) { if (line->IsBlock()) { return GetLogicalUsedBorderAndPadding(wm).BStart(wm) +
line->mFirstChild->GetCaretBaseline();
} return line->BStart() + line->GetLogicalAscent();
}
} return GetFontMetricsDerivedCaretBaseline(ContentBSize(wm));
}
// Whether this line is indented by the text-indent amount. bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const { constauto& textIndent = StyleText()->mTextIndent;
bool isFirstLineOrAfterHardBreak = [&] { if (aLine != LinesBegin()) { // If not the first line of the block, but 'each-line' is in effect, // check if the previous line was not wrapped. return textIndent.each_line && !aLine.prev()->IsLineWrapped();
} if (nsBlockFrame* prevBlock = do_QueryFrame(GetPrevInFlow())) { // There's a prev-in-flow, so this only counts as a first-line if // 'each-line' and the prev-in-flow's last line was not wrapped. return textIndent.each_line &&
(prevBlock->Lines().empty() ||
!prevBlock->LinesEnd().prev()->IsLineWrapped());
} returntrue;
}();
// The 'hanging' option inverts which lines are/aren't indented. return isFirstLineOrAfterHardBreak != textIndent.hanging;
}
nsresult rv;
InlinePrefISizeData data; for (nsBlockFrame* curFrame = this; curFrame;
curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) { for (LineIterator line = curFrame->LinesBegin(),
line_end = curFrame->LinesEnd();
line != line_end; ++line) {
nscoord childX, childXMost; if (line->IsBlock()) {
data.ForceBreak();
rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext,
&childX, &childXMost);
NS_ENSURE_SUCCESS(rv, rv);
*aX = std::min(*aX, childX);
*aXMost = std::max(*aXMost, childXMost);
} else { if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
data.mCurrentLine += StyleText()->mTextIndent.length.Resolve(0);
}
data.mLine = &line;
data.SetLineContainer(curFrame); // Per comment in nsIFrame::GetPrefWidthTightBounds(), the function is // only implemented for nsBlockFrame and nsTextFrame and is used to // determine the intrinsic inline sizes of MathML token elements. These // elements shouldn't have percentage block sizes that require a // percentage basis for resolution. const IntrinsicSizeInput kidInput(aRenderingContext, Nothing(),
Nothing()); for (nsIFrame* kid : line->ChildFrames()) {
rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX,
&childXMost);
NS_ENSURE_SUCCESS(rv, rv);
*aX = std::min(*aX, data.mCurrentLine + childX);
*aXMost = std::max(*aXMost, data.mCurrentLine + childXMost);
kid->AddInlinePrefISize(kidInput, &data);
}
}
}
}
data.ForceBreak();
return NS_OK;
}
/** *ReturnwhetheraNewAvailableSpaceissmaller*oneitherside* *(inline-startorinline-end)thanaOldAvailableSpace,sothatweknow *ifweneedtoredolayoutonanline,replacedblock,orblock *formattingcontext,becauseitsheight(whichweusedtocompute *aNewAvailableSpace)causedittointersectadditionalfloats.
*/ staticbool AvailableSpaceShrunk(WritingMode aWM, const LogicalRect& aOldAvailableSpace, const LogicalRect& aNewAvailableSpace, bool aCanGrow /* debug-only */) { if (aNewAvailableSpace.ISize(aWM) == 0) { // Positions are not significant if the inline size is zero. return aOldAvailableSpace.ISize(aWM) != 0;
} if (aCanGrow) {
NS_ASSERTION(
aNewAvailableSpace.IStart(aWM) <= aOldAvailableSpace.IStart(aWM) ||
aNewAvailableSpace.IEnd(aWM) <= aOldAvailableSpace.IEnd(aWM), "available space should not shrink on the start side and " "grow on the end side");
NS_ASSERTION(
aNewAvailableSpace.IStart(aWM) >= aOldAvailableSpace.IStart(aWM) ||
aNewAvailableSpace.IEnd(aWM) >= aOldAvailableSpace.IEnd(aWM), "available space should not grow on the start side and " "shrink on the end side");
} else {
NS_ASSERTION(
aOldAvailableSpace.IStart(aWM) <= aNewAvailableSpace.IStart(aWM) &&
aOldAvailableSpace.IEnd(aWM) >= aNewAvailableSpace.IEnd(aWM), "available space should never grow");
} // Have we shrunk on either side? return aNewAvailableSpace.IStart(aWM) > aOldAvailableSpace.IStart(aWM) ||
aNewAvailableSpace.IEnd(aWM) < aOldAvailableSpace.IEnd(aWM);
}
staticbool IsLineClampRoot(const nsBlockFrame* aFrame) { if (!aFrame->StyleDisplay()->mWebkitLineClamp) { returnfalse;
}
if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC)) { returnfalse;
}
if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled() ||
aFrame->PresContext()->Document()->ChromeRulesEnabled()) { returntrue;
}
// For now, -webkit-box is the only thing allowed to be a line-clamp root. // Ideally we'd just make this work everywhere, but for now we're carrying // this forward as a limitation on the legacy -webkit-line-clamp feature, // since relaxing this limitation might create webcompat trouble. auto origDisplay = [&] { if (aFrame->Style()->GetPseudoType() ==
PseudoStyleType::MozScrolledContent) { // If we're the anonymous block inside the scroll frame, we need to look // at the original display of our parent frame.
MOZ_ASSERT(aFrame->GetParent()); constauto& parentDisp = *aFrame->GetParent()->StyleDisplay();
MOZ_ASSERT(parentDisp.mWebkitLineClamp ==
aFrame->StyleDisplay()->mWebkitLineClamp, ":-moz-scrolled-content should inherit -webkit-line-clamp, " "via rule in UA stylesheet"); return parentDisp.mOriginalDisplay;
} return aFrame->StyleDisplay()->mOriginalDisplay;
}(); return origDisplay.Inside() == StyleDisplayInside::WebkitBox;
}
nsBlockFrame* nsBlockFrame::GetLineClampRoot() const { if (IsLineClampRoot(this)) { returnconst_cast<nsBlockFrame*>(this);
} const nsBlockFrame* cur = this; while (GetAsLineClampDescendant(cur)) {
cur = do_QueryFrame(cur->GetParent()); if (!cur) { break;
} if (IsLineClampRoot(cur)) { returnconst_cast<nsBlockFrame*>(cur);
}
} return nullptr;
}
bool nsBlockFrame::MaybeHasFloats() const { if (HasFloats()) { returntrue;
} if (HasPushedFloats()) { returntrue;
} // For the OverflowOutOfFlowsProperty I think we do enforce that, but it's // a mix of out-of-flow frames, so that's why the method name has "Maybe". return HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
// Advances the iterator to the next line line. // // Next() shouldn't be called once the iterator is at the end, which can be // checked for by GetCurrentLine() or GetCurrentFrame() returning null. void Next() {
MOZ_ASSERT(mCur != mEnd && mCurrentFrame, "Don't call Next() when the iterator is at the end");
++mCur;
Advance();
}
private: void Advance() { for (;;) { if (mCur == mEnd) { // Reached the end of the current block. Pop the parent off the // stack; if there isn't one, then we've reached the end. if (mStack.IsEmpty()) {
mCurrentFrame = nullptr; break;
} if (mCurrentFrame == mStopAtFrame) {
mStack.Clear();
mCurrentFrame = nullptr; break;
}
// The current line within the current block. // // When this is equal to mEnd, the iterator is at its end, and mCurrentFrame // is set to null.
nsBlockFrame::LineIterator mCur;
// The iterator end for the current block.
nsBlockFrame::LineIterator mEnd;
// The current block.
nsBlockFrame* mCurrentFrame;
// The block past which we can't look at line-clamp. const nsBlockFrame* mStopAtFrame;
// Stack of mCurrentFrame and mEnd values that we push and pop as we enter and // exist blocks.
AutoTArray<std::pair<nsBlockFrame*, nsBlockFrame::LineIterator>, 8> mStack;
};
staticbool ClearLineClampEllipsis(nsBlockFrame* aFrame) { if (aFrame->HasLineClampEllipsis()) {
MOZ_ASSERT(!aFrame->HasLineClampEllipsisDescendant());
aFrame->SetHasLineClampEllipsis(false); for (auto& line : aFrame->Lines()) { if (line.HasLineClampEllipsis()) {
line.ClearHasLineClampEllipsis(); break;
}
} returntrue;
}
if (aFrame->HasLineClampEllipsisDescendant()) {
aFrame->SetHasLineClampEllipsisDescendant(false); for (nsIFrame* f : aFrame->PrincipalChildList()) { if (nsBlockFrame* child = GetAsLineClampDescendant(f)) { if (ClearLineClampEllipsis(child)) { returntrue;
}
}
}
}
// Compute an inline absolute containing block's rect in aInlineFrame's // coordinate space (relative to its border-box origin) per // https://drafts.csswg.org/css-position-3/#absolute-cb static nsRect ComputeInlineAbsoluteCBRect(const nsInlineFrame* aInlineFrame) {
MOZ_ASSERT(aInlineFrame->IsAbsoluteContainer(), "Why computing the rect if it is not an absolute container?");
constauto cbwm = aInlineFrame->GetWritingMode();
// The LogicalRect API requires a container size to compute logical // coordinates whose "start" side is on the far physical side of the container // (RTL or vertical-rl writing modes). For our usage here, we don't need a // real container size, because that only influences the position of the // logical rect (which is an intermediate variable in this function). The // container size ultimately gets canceled out when we convert the logical // rect back to a physical rect at the end of this function, so we use a // zero-initialized dummy size. const nsSize dummyContainerSize;
// A helper to get aFrame's border-box rect relative to aInlineFrame, in // aInlineFrame's writing mode. auto BorderBoxRectRelativeToInlineFrame = [&](const nsIFrame* aFrame) { const nsRect physicalRect =
aFrame->GetRectRelativeToSelf() + aFrame->GetOffsetTo(aInlineFrame); return LogicalRect(cbwm, physicalRect, dummyContainerSize);
};
// Start edges from the first continuation, end edges from the last // continuation. const nscoord iStart = firstContRect.IStart(cbwm); const nscoord bStart = firstContRect.BStart(cbwm); const nscoord iEnd = lastContRect.IEnd(cbwm); const nscoord bEnd = lastContRect.BEnd(cbwm);
LogicalRect cbRect(cbwm, iStart, bStart, iEnd - iStart, bEnd - bStart);
// The CSS Position 3 spec says to form the cb rect from content edges of // continuations [1], but other browsers and the unfragmented scenario defined // in CSS 2.2 section 10.1.4 [2] use the padding edge. Therefore, we deflate // only the border for interop. // https://github.com/w3c/csswg-drafts/issues/13952 // // [1] https://drafts.csswg.org/css-position-3/#absolute-cb // [2] https://www.w3.org/TR/CSS22/visudet.html#containing-block-details const LogicalMargin firstBorder = firstCont->GetLogicalUsedBorder(cbwm); const LogicalMargin lastBorder = lastCont->GetLogicalUsedBorder(cbwm); const LogicalMargin cbBorder(cbwm, firstBorder.BStart(cbwm),
lastBorder.IEnd(cbwm), lastBorder.BEnd(cbwm),
firstBorder.IStart(cbwm));
cbRect.Deflate(cbwm, cbBorder);
void nsBlockFrame::ReflowAbsoluteDescendantsInInlineFrame(
nsPresContext* aPresContext, const ReflowInput& aReflowInput,
ReflowOutput& aReflowOutput, nsReflowStatus& aStatus) { bool foundAbspos = false; for (auto& line : Lines()) { if (line.IsBlock()) { // The block frame in this line is responsible for reflowing its abspos // descendants. continue;
}
// Traverse the kids on this inline line, and translate each kid's overflow // areas into this block's coordinate space and accumulate.
OverflowAreas lineAbsposOverflow; for (nsIFrame* kid : line.ChildFrames()) { if (auto kidOverflow = WalkInlineDescendantsToReflowAbsoluteFrames(
kid, aPresContext, aReflowInput, aStatus)) {
foundAbspos = true;
lineAbsposOverflow.UnionWithAbsoluteOverflowAreas(*kidOverflow +
kid->GetPosition());
}
}
if (lineAbsposOverflow != OverflowAreas()) { // Update the line's overflow areas to include abspos descendants' // overflow areas. This is required for painting code to generate their // display items.
OverflowAreas lineOverflow = line.GetOverflowAreas();
lineOverflow.UnionWithAbsoluteOverflowAreas(lineAbsposOverflow);
line.SetOverflowAreas(lineOverflow);
if (!foundAbspos) { // Remove NS_BLOCK_HAS_INLINE_ABSPOS_DESCENDANT bit if we didn't find any // abspos descendants after a full walk over all our inline lines.
RemoveStateBits(NS_BLOCK_HAS_INLINE_ABSPOS_DESCENDANT);
}
}
Maybe<OverflowAreas> nsBlockFrame::WalkInlineDescendantsToReflowAbsoluteFrames(
nsIFrame* aFrame, nsPresContext* aPresContext, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) { if (aFrame->IsBlockFrameOrSubclass()) { // Block frames (e.g. 'inline-block') will walk their own inline // descendants. return Nothing();
}
// absposOverflow accumulates overflow areas from the visited abspos // descendants, in aFrame's coordinate space.
OverflowAreas absposOverflow; bool foundAbspos = false;
// Traverse aFrame's kids first. Each kid will give us back the overflow areas // from its own abspos descendants. We translate those to aFrame's coordinate // space, and accumulate. for (nsIFrame* kid : aFrame->PrincipalChildList()) { if (auto absposOverflowFromKid =
WalkInlineDescendantsToReflowAbsoluteFrames(
kid, aPresContext, aReflowInput, aStatus)) {
foundAbspos = true;
absposOverflow.UnionWithAbsoluteOverflowAreas(*absposOverflowFromKid +
kid->GetPosition());
}
}
if (nsInlineFrame* inlineFrame = do_QueryFrame(aFrame)) { // If aFrame is an inline frame (or a subclass of inline frame), reflow its // abspos kids, and accumulate their overflow areas. if (auto absposOverflowFromInlineFrame = ReflowAbsoluteFramesInInlineFrame(
inlineFrame, aPresContext, aReflowInput, aStatus)) {
foundAbspos = true;
absposOverflow.UnionWithAbsoluteOverflowAreas(
*absposOverflowFromInlineFrame);
}
}
if (!foundAbspos) { return Nothing();
}
if (absposOverflow != OverflowAreas()) { // Update aFrame's overflow areas via FinishAndStoreOverflow() so that // painting related properties are set correctly, e.g. setting // PreEffectsBBoxProperty() in ComputeEffectsRect().
OverflowAreas frameOverflow = aFrame->GetOverflowAreas();
frameOverflow.UnionWithAbsoluteOverflowAreas(absposOverflow);
aFrame->FinishAndStoreOverflow(frameOverflow, aFrame->GetSize());
}
// TODO(Bug 2038072): After we support splitting abspos frames in multicol or // printing, a later inline continuation might have abspos kids.
MOZ_ASSERT(!aInlineFrame->GetPrevInFlow(), "Only the first inline continuation can have absolute kids!");
// TODO(Bug 2038072): Pass an actual available block-size to split the abspos // frames correctly in multicol or printing. const LogicalSize availSize(cbwm, aInlineFrame->ISize(cbwm),
NS_UNCONSTRAINEDSIZE);
ReflowInput inlineRI(aPresContext, aReflowInput, aInlineFrame, availSize);
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
#ifdef DEBUG if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
fmt::println("{}: begin reflow: availSize={} computedSize={}",
ListTag().get(), ToString(aReflowInput.AvailableSize()),
ToString(aReflowInput.ComputedSize()));
}
AutoNoisyIndenter indent(gNoisy);
PRTime start = 0; // Initialize these variablies to silence the compiler.
int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics). if (gLameReflowMetrics) {
start = PR_Now();
ctc = nsLineBox::GetCtorCount();
} #endif
// ColumnSetWrapper's children depend on ColumnSetWrapper's block-size or // max-block-size because both affect the children's available block-size. if (IsColumnSetWrapperFrame()) {
AddStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE);
}
Maybe<nscoord> restoreReflowInputAvailBSize; auto MaybeRestore = MakeScopeExit([&] { if (MOZ_UNLIKELY(restoreReflowInputAvailBSize)) { const_cast<ReflowInput&>(aReflowInput)
.SetAvailableBSize(*restoreReflowInputAvailBSize);
}
});
WritingMode wm = aReflowInput.GetWritingMode(); const nscoord consumedBSize = CalcAndCacheConsumedBSize(); const nscoord effectiveContentBoxBSize =
GetEffectiveComputedBSize(aReflowInput, consumedBSize); // If we have non-auto block size, we're clipping our kids and we fit, // make sure our kids fit too. if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay)
.contains(wm.PhysicalAxis(LogicalAxis::Block))) {
LogicalMargin blockDirExtras =
aReflowInput.ComputedLogicalBorderPadding(wm); if (GetLogicalSkipSides().BStart()) {
blockDirExtras.BStart(wm) = 0;
} else { // Block-end margin never causes us to create continuations, so we // don't need to worry about whether it fits in its entirety.
blockDirExtras.BStart(wm) +=
aReflowInput.ComputedLogicalMargin(wm).BStart(wm);
}
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();
// Should we create a float manager?
nsAutoFloatManager autoFloatManager(const_cast<ReflowInput&>(aReflowInput));
// XXXldb If we start storing the float manager in the frame rather // than keeping it around only during reflow then we should create it // only when there are actually floats to manage. Otherwise things // like tables will gain significant bloat. // // See https://bugzilla.mozilla.org/show_bug.cgi?id=1931286: // if we're a reflow root and no float manager is provided by the caller // in aReflowInput, we'd normally expect the block to be a BFC and so // BlockNeedsFloatManager will return true. But sometimes the block may // have lost its BFC-ness since it was recorded as a dirty reflow root // but before the reflow actually happens. Creating a float manager here // avoids crashing, but may not be entirely correct in such a case. bool needFloatManager =
!aReflowInput.mFloatManager || nsBlockFrame::BlockNeedsFloatManager(this); if (needFloatManager) {
autoFloatManager.CreateFloatManager(aPresContext);
}
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) { static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
}
// 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, and mBlockCoord is the // block-axis offset of its position. // If line-clamp is not in use, mContent is null, mOffset is the total // number of lines that the block must contain, and mBlockCoord is its end // edge in the block direction.
nsIContent* mContent = nullptr;
int32_t mOffset = -1;
nscoord mBlockCoord = 0;
// Count the number of inline lines in the mLines list, but return -1 (to // suppress balancing) 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) { // Block lines are ignored as they do not participate in balancing. if (iter->IsInline() && ++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 = [&](uint32_t aClampCount) -> BalanceTarget { if (NS_WARN_IF(aClampCount >= mLines.size())) { return BalanceTarget{};
} auto iter = mLines.begin(); for (uint32_t i = 0; i < aClampCount; i++) {
++iter;
}
nsIFrame* firstChild = iter->mFirstChild; if (!firstChild) { return BalanceTarget{};
}
nsIContent* content = firstChild->GetContent(); if (!content) { return BalanceTarget{};
}
int32_t offset = 0; if (firstChild->IsTextFrame()) { auto* textFrame = static_cast<nsTextFrame*>(firstChild);
offset = textFrame->GetContentOffset();
} return BalanceTarget{content, offset, iter.get()->BStart()};
};
// "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);
// Do we need to start a `text-wrap: balance` iteration? if (tryBalance) {
tryBalance = false; // Don't try to balance an incomplete block, or if we had to use an // overflow-wrap break position in the initial reflow. if (!reflowStatus.IsFullyComplete() || trialState.mUsedOverflowWrap) { 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;
}
balanceTarget.mBlockCoord = mLines.back()->BEnd(); // 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)) {
uint32_t lineClampCount = aReflowInput.mStyleDisplay->mWebkitLineClamp; if (uint32_t(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() || trialState.mUsedOverflowWrap) { returnfalse;
} 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 &&
mLines.back()->BEnd() == balanceTarget.mBlockCoord;
};
// 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;
// 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 (!trialState.mInset || trialSucceeded()) { break;
}
trialState.ResetForBalance(-1);
// 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(); if (nsFrameList* floats = GetFloats()) { for (nsIFrame* f : *floats) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(trialState.mFcBounds, f);
}
} if (nsFrameList* markerList = GetOutsideMarkerList()) { 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, 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);
// Reflow absolute descendants of inline absolute containing blocks after all // the lines are reflowed and placed. // // NS_BLOCK_HAS_INLINE_ABSPOS_DESCENDANT would have been set on us, if // appropriate, when we reflowed our inline lines, by // nsInlineFrame::MarkBlockAncestorHavingAbsoluteDescendants() (called from // the Reflow() of an inline/ruby descendant that has abspos children), so it // is up to date here. At the end of ReflowAbsoluteDescendantsInInlineFrame(), // the bit will be cleared when no abspos descendant is found (we do not // proactively traverse this block's subtree to clear the bit when an abspos // frame is removed). if (StaticPrefs::layout_abspos_fragment_aware_inline_cb_enabled() &&
HasAnyStateBits(NS_BLOCK_HAS_INLINE_ABSPOS_DESCENDANT) &&
!aReflowInput.WillReflowAgainForClearance() &&
!aPresContext->HasPendingInterrupt()) {
ReflowAbsoluteDescendantsInInlineFrame(aPresContext, aReflowInput, aMetrics,
reflowStatus);
}
// 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. auto* absoluteContainer = GetAbsoluteContainingBlock(); if (absoluteContainer && absoluteContainer->PrepareAbsoluteFrames(this)) { 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): AbsoluteContainingBlock::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()) {
ConsiderChildOverflow(aMetrics.mOverflowAreas, kid);
}
}
} else { // 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 AbsoluteContainingBlock 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;
AbsPosReflowFlags flags{AbsPosReflowFlag::AllowFragmentation}; if (cbWidthChanged) {
flags += AbsPosReflowFlag::CBWidthChanged;
} if (cbHeightChanged) {
flags += AbsPosReflowFlag::CBHeightChanged;
} // Setup the line cursor here to optimize line searching for // calculating hypothetical position of absolutely-positioned // frames.
SetupLineCursorForQuery();
#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, GetChildList(FrameChildListID::Float));
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 // 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, GetChildList(FrameChildListID::Float)); #endif
// ALWAYS drain overflow. We never want to leave the previnflow's // overflow lines hanging around; block reflow depends on the // overflow line lists being cleared out between reflow passes.
DrainOverflowLines();
// Clear any existing -webkit-line-clamp ellipsis if we're reflowing the // line-clamp root. if (IsLineClampRoot(this)) {
ClearLineClampEllipsis();
}
// Handle paginated overflow (see nsContainerFrame.h)
nsReflowStatus ocStatus; if (GetPrevInFlow()) {
ReflowOverflowContainerChildren(
aPresContext, aReflowInput, aTrialState.mOcBounds,
ReflowChildFlags::Default, ocStatus, DefaultChildFrameMerge,
Some(state.ContainerSize()));
}
// Now that we're done cleaning up our overflow container lists, we can // give |state| its nsOverflowContinuationTracker.
nsOverflowContinuationTracker tracker(this, false);
state.mOverflowTracker = &tracker;
// 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 // mark dirty for a resize reflow. if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.IsIResize()) {
PrepareResizeReflow(state);
}
// The same for percentage text-indent, except conditioned on the // parent resizing. if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && aReflowInput.mCBReflowInput &&
aReflowInput.mCBReflowInput->IsIResize() &&
StyleText()->mTextIndent.length.HasPercent() && !mLines.empty()) {
mLines.front()->MarkDirty();
}
// For text-wrap:balance trials, we need to reflow all the inline lines even // if they're not all "dirty", but we don't need to reflow any block lines. if (aTrialState.mBalancing) {
MarkAllInlineLinesDirty(this);
} else {
LazyMarkLinesDirty();
}
// Now reflow...
aTrialState.mUsedOverflowWrap = ReflowDirtyLines(state);
// If we have a next-in-flow, and that next-in-flow has pushed floats from // this frame from a previous iteration of reflow, then we should not return // a status with IsFullyComplete() equals to true, since we actually have // overflow, it's just already been handled.
// NOTE: This really shouldn't happen, since we _should_ pull back our floats // and reflow them, but just in case it does, this is a safety precaution so // we don't end up with a placeholder pointing to frames that have already // been deleted as part of removing our next-in-flow. if (state.mReflowStatus.IsFullyComplete()) {
nsBlockFrame* nif = static_cast<nsBlockFrame*>(GetNextInFlow()); while (nif) { if (nif->HasPushedFloatsFromPrevContinuation()) { if (nif->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
state.mReflowStatus.SetOverflowIncomplete();
} else {
state.mReflowStatus.SetIncomplete();
} break;
}
// If we end in a BR with clear and affected floats continue, // we need to continue, too. if (NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
state.mReflowStatus.IsComplete() &&
state.FloatManager()->ClearContinues(FindTrailingClear())) {
state.mReflowStatus.SetIncomplete();
}
if (!state.mReflowStatus.IsFullyComplete()) { if (HasOverflowLines() || HasPushedFloats()) {
state.mReflowStatus.SetNextInFlowNeedsReflow();
}
}
// Place the ::marker's frame if it is placed next to a block child. // // According to the CSS2 spec, section 12.6.1, the ::marker's box // participates in the height calculation of the list-item box's // first line box. // // There are exactly two places a ::marker can be placed: near the // first or second line. It's only placed on the second line in a // rare case: an empty first line followed by a second line that // contains a block (example: <LI>\n<P>... ). This is where // the second case can happen.
nsIFrame* outsideMarker = GetOutsideMarker(); if (outsideMarker && !mLines.empty() &&
(mLines.front()->IsBlock() ||
(0 == mLines.front()->BSize() && mLines.front() != mLines.back() &&
mLines.begin().next()->IsBlock()))) { // Reflow the ::marker's frame.
ReflowOutput reflowOutput(aReflowInput); // XXX Use the entire line when we fix bug 25888.
nsLayoutUtils::LinePosition position;
WritingMode wm = aReflowInput.GetWritingMode(); bool havePosition =
nsLayoutUtils::GetFirstLinePosition(wm, this, &position);
nscoord lineBStart =
havePosition ? position.mBStart
: aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm);
ReflowOutsideMarker(outsideMarker, state, reflowOutput, lineBStart);
NS_ASSERTION(!MarkerIsEmpty(outsideMarker) || reflowOutput.BSize(wm) == 0, "empty ::marker frame took up space");
if (havePosition && !MarkerIsEmpty(outsideMarker)) { // We have some lines to align the ::marker with.
// Doing the alignment using the baseline will also cater for // ::markers that are placed next to a child block (bug 92896)
// Tall ::markers won't look particularly nice here...
LogicalRect bbox =
outsideMarker->GetLogicalRect(wm, reflowOutput.PhysicalSize()); constauto baselineGroup = BaselineSharingGroup::First;
Maybe<nscoord> result; if (MOZ_LIKELY(!wm.IsOrthogonalTo(outsideMarker->GetWritingMode()))) {
result = outsideMarker->GetNaturalBaselineBOffset(
wm, baselineGroup, BaselineExportContext::LineLayout);
} constauto markerBaseline =
result.valueOrFrom([bbox, wm, outsideMarker]() { return bbox.BSize(wm) +
outsideMarker->GetLogicalUsedMargin(wm).BEnd(wm);
});
bbox.BStart(wm) = position.mBaseline - markerBaseline;
outsideMarker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
} // Otherwise just leave the ::marker where it is, up against our // block-start padding.
}
CheckFloats(state);
// Compute our final size (for this trial layout)
aTrialState.mBlockEndEdgeOfChildren =
ComputeFinalSize(aReflowInput, state, aMetrics);
aTrialState.mContainerWidth = state.ContainerSize().width;
while (nsLineBox* line = iter.GetCurrentLine()) { // Don't count a line that only has collapsible white space (as might exist // after calling e.g. getBoxQuads). if (line->IsEmpty()) {
iter.Next(); continue;
}
if (aLineNumber == 0) { // We already previously found our target line, and now we have // confirmed that there is another line after it.
foundFollowingLine = true; break;
}
if (--aLineNumber == 0) { // This is our target line. Continue looping to confirm that we // have another line after us.
targetLine = line;
targetFrame = iter.GetCurrentFrame();
}
iter.Next();
}
if (!foundFollowingLine) {
MOZ_ASSERT(!aRootFrame->HasLineClampEllipsis(), "should have been removed earlier"); return std::pair(nullptr, nullptr);
}
MOZ_ASSERT(targetLine);
MOZ_ASSERT(targetFrame);
// If targetFrame is not the same as the line-clamp root, any ellipsis on the // root should have been previously cleared.
MOZ_ASSERT(targetFrame == aRootFrame || !aRootFrame->HasLineClampEllipsis(), "line-clamp target mismatch");
auto lineClamp = root->StyleDisplay()->mWebkitLineClamp; auto [target, line] = FindLineClampTarget(root, this, lineClamp); if (!line) { // The number of lines did not exceed the -webkit-line-clamp value. return aContentBlockEndEdge;
}
// Mark the line as having an ellipsis so that TextOverflow will render it.
line->SetHasLineClampEllipsis();
target->SetHasLineClampEllipsis(true);
// Translate the b-end edge of the line up to aFrame's space.
nscoord edge = line->BEnd(); for (nsIFrame* f = target; f; f = f->GetParent()) {
MOZ_ASSERT(f->IsBlockFrameOrSubclass(), "GetAsLineClampDescendant guarantees this"); if (f != target) { static_cast<nsBlockFrame*>(f)->SetHasLineClampEllipsisDescendant(true);
} if (f == this) { break;
} if (f == root) { // The clamped line is not in our subtree. return aContentBlockEndEdge;
} constauto wm = f->GetWritingMode(); const nsSize parentSize = f->GetParent()->GetSize();
edge = f->GetLogicalRect(parentSize).BEnd(wm);
}
// Return block-end margin information // rbs says he hit this assertion occasionally (see bug 86947), so // just set the margin to zero and we'll figure out why later // NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(), // "someone else set the margin");
nscoord nonCarriedOutBDirMargin = 0; if (!aState.mFlags.mIsBEndMarginRoot) { // Apply rule from CSS 2.1 section 8.3.1. If we have some empty // line with clearance and a non-zero block-start margin and all // subsequent lines are empty, then we do not allow our children's // carried out block-end margin to be carried out of us and collapse // with our own block-end margin. if (CheckForCollapsedBEndMarginFromClearanceLine()) { // Convert the children's carried out margin to something that // we will include in our height
nonCarriedOutBDirMargin = aState.mPrevBEndMargin.Get();
aState.mPrevBEndMargin.Zero();
}
aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
} else {
aMetrics.mCarriedOutBEndMargin.Zero();
}
nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin; // Shrink wrap our height around our contents. if (aState.mFlags.mIsBEndMarginRoot ||
NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { // When we are a block-end-margin root make sure that our last // child's block-end margin is fully applied. We also do this when // we have a computed height, since in that case the carried out // margin is not going to be applied anywhere, so we should note it // here to be included in the overflow area. // Apply the margin only if there's space for it. if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize()) { // Truncate block-end margin if it doesn't fit to our available BSize.
blockEndEdgeOfChildren =
std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.Get(),
aState.mReflowInput.AvailableBSize());
}
} if (aState.mFlags.mBlockNeedsFloatManager) { // Include the float manager's state to properly account for the // block-end margin of any floated elements; e.g., inside a table cell. // // Note: The block coordinate returned by ClearFloats is always greater than // or equal to blockEndEdgeOfChildren.
std::tie(blockEndEdgeOfChildren, std::ignore) =
aState.ClearFloats(blockEndEdgeOfChildren, UsedClear::Both);
}
// undo cached alignment shift for sizing purposes // (we used shifted positions because the float manager uses them)
blockEndEdgeOfChildren -= aState.mAlignContentShift;
aState.UndoAlignContentShift();
if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) { // Note: We don't use blockEndEdgeOfChildren because it includes the // previous margin. const nscoord contentBSizeWithBStartBP =
aState.mBCoord + nonCarriedOutBDirMargin;
// We don't care about ApplyLineClamp's return value (the line-clamped // content BSize) in this explicit-BSize codepath, but we do still need to // call ApplyLineClamp for ellipsis markers to be placed as-needed.
ApplyLineClamp(contentBSizeWithBStartBP);
// If the content block-size is larger than the effective computed // block-size, we extend the block-size to contain all the content. // https://drafts.csswg.org/css-sizing-4/#aspect-ratio-minimum if (aReflowInput.ShouldApplyAutomaticMinimumOnBlockAxis()) { // Note: finalSize.BSize(wm) is the border-box size, so we compare it with // the content's block-size plus our border and padding..
finalSize.BSize(wm) =
std::max(finalSize.BSize(wm),
contentBSizeWithBStartBP + borderPadding.BEnd(wm));
// The size should be capped by its maximum block size. if (aReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
finalSize.BSize(wm) =
std::min(finalSize.BSize(wm), aReflowInput.ComputedMaxBSize() +
borderPadding.BStartEnd(wm));
}
}
// Don't carry out a block-end margin when our BSize is fixed. // // Note: this also includes the case that aReflowInput.ComputedBSize() is // calculated from aspect-ratio. i.e. Don't carry out block margin-end if it // is replaced by the block size from aspect-ratio and inline size.
aMetrics.mCarriedOutBEndMargin.Zero();
} elseif (Maybe<nscoord> containBSize = ContainIntrinsicBSize(
IsComboboxControlFrame() ? aReflowInput.GetLineHeight() : 0)) { // If we're size-containing in block axis and we don't have a specified // block size, then our final size should actually be computed from only // our border, padding and contain-intrinsic-block-size, ignoring the // actual contents. Hence this case is a simplified version of the case // below.
nscoord contentBSize = *containBSize;
nscoord autoBSize =
aReflowInput.ApplyMinMaxBSize(contentBSize, aState.mConsumedBSize);
aMetrics.mCarriedOutBEndMargin.Zero();
autoBSize += borderPadding.BStartEnd(wm);
finalSize.BSize(wm) = autoBSize;
} elseif (aState.mReflowStatus.IsInlineBreakBefore()) { // Our parent is expected to push this frame to the next page/column so // what size we set here doesn't really matter.
finalSize.BSize(wm) = aReflowInput.AvailableBSize();
} elseif (aState.mReflowStatus.IsComplete()) { const nscoord lineClampedContentBlockEndEdge =
ApplyLineClamp(blockEndEdgeOfChildren);
const nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(
lineClampedContentBSize, aState.mConsumedBSize); if (autoBSize != contentBSize) { // Our min-block-size, max-block-size, or -webkit-line-clamp value made // our bsize change. Don't carry out our kids' block-end margins.
aMetrics.mCarriedOutBEndMargin.Zero();
}
nscoord bSize = autoBSize + borderPadding.BStartEnd(wm); if (MOZ_UNLIKELY(autoBSize > contentBSize &&
bSize > aReflowInput.AvailableBSize() &&
aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE)) { // Applying `min-size` made us overflow our available size. // Clamp it and report that we're Incomplete, or BreakBefore if we have // 'break-inside: avoid' that is applicable.
bSize = aReflowInput.AvailableBSize(); if (ShouldAvoidBreakInside(aReflowInput)) {
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
} else {
aState.mReflowStatus.SetIncomplete();
}
}
finalSize.BSize(wm) = bSize;
} else {
NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE, "Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
nscoord bSize = std::max(aState.mBCoord, aReflowInput.AvailableBSize()); if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) { // This should never happen, but it does. See bug 414255
bSize = aState.mBCoord;
} const nscoord maxBSize = aReflowInput.ComputedMaxBSize(); if (maxBSize != NS_UNCONSTRAINEDSIZE &&
aState.mConsumedBSize + bSize - borderPadding.BStart(wm) > maxBSize) { // Compute this fragment's block-size, with the max-block-size // constraint taken into consideration. const nscoord clampedBSizeWithoutEndBP =
std::max(0, maxBSize - aState.mConsumedBSize) +
borderPadding.BStart(wm); const nscoord clampedBSize =
clampedBSizeWithoutEndBP + borderPadding.BEnd(wm); if (clampedBSize <= aReflowInput.AvailableBSize()) { // We actually fit after applying `max-size` so we should be // Overflow-Incomplete instead.
bSize = clampedBSize;
aState.mReflowStatus.SetOverflowIncomplete();
} else { // We cannot fit after applying `max-size` with our block-end BP, so // we should draw it in our next continuation.
bSize = clampedBSizeWithoutEndBP;
}
}
finalSize.BSize(wm) = bSize;
}
if (IsTrueOverflowContainer()) { if (aState.mReflowStatus.IsIncomplete()) { // Overflow containers can only be overflow complete. // Note that auto height overflow containers have no normal children
NS_ASSERTION(finalSize.BSize(wm) == 0, "overflow containers must be zero-block-size");
aState.mReflowStatus.SetOverflowIncomplete();
}
} elseif (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
!aState.mReflowStatus.IsInlineBreakBefore() &&
aState.mReflowStatus.IsComplete()) { // Currently only used for grid items, but could be used in other contexts. // The FragStretchBSizeProperty is our expected non-fragmented block-size // we should stretch to (for align-self:stretch etc). In some fragmentation // cases though, the last fragment (this frame since we're complete), needs // to have extra size applied because earlier fragments consumed too much of // our computed size due to overflowing their containing block. (E.g. this // ensures we fill the last row when a multi-row grid item is fragmented). bool found;
nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found); if (found) {
finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
}
}
// Clamp the content size to fit within the margin-box clamp size, if any. if (MOZ_UNLIKELY(aReflowInput.mComputeSizeFlags.contains(
ComputeSizeFlag::BClampMarginBoxMinSize)) &&
aState.mReflowStatus.IsComplete()) { bool found;
nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found); if (found) { auto marginBoxBSize =
finalSize.BSize(wm) +
aReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm); auto overflow = marginBoxBSize - cbSize; if (overflow > 0) { auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm); auto newContentBSize = std::max(nscoord(0), contentBSize - overflow); // XXXmats deal with percentages better somehow?
finalSize.BSize(wm) -= contentBSize - newContentBSize;
}
}
}
// Screen out negative block sizes --- can happen due to integer overflows :-(
finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
aMetrics.SetSize(wm, finalSize);
// NOTE: ComputeFinalSize already called aState.UndoAlignContentShift(), // so metrics no longer include cached shift. // NOTE: Content is currently positioned at cached shift // NOTE: Content has been fragmented against 0-shift assumption.
// note: these measures all include start BP, so it subtracts out if (!(originalAlignment & StyleAlignFlags::UNSAFE)) {
shift = std::max(0, shift);
} if (isCentered) {
shift = shift / 2;
}
} // else: zero shift if start-aligned or if fragmented
nscoord delta = shift - aState.mAlignContentShift; if (delta) { // Shift children
LogicalPoint translation(wm, 0, delta); for (nsLineBox& line : Lines()) {
SlideLine(aState, &line, delta);
} for (nsIFrame* kid : GetChildList(FrameChildListID::Float)) {
kid->MovePositionBy(wm, translation);
}
nsIFrame* outsideMarker = GetOutsideMarker(); if (outsideMarker && !mLines.empty()) {
outsideMarker->MovePositionBy(wm, translation);
}
}
void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas, const nsStyleDisplay* aDisplay) const { // XXX_perf: This can be done incrementally. It is currently one of // the things that makes incremental reflow O(N^2). auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay); auto overflowClipMargin =
OverflowClipMargin(overflowClipAxes, /* aAllowNegative = */ false); if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin.IsAllZero()) { return;
}
// We rely here on our caller having called SetOverflowAreasToDesiredBounds(). const nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
constauto wm = GetWritingMode(); constauto borderPadding =
GetLogicalUsedBorderAndPadding(wm).GetPhysicalMargin(wm); // Compute content-box by subtracting borderPadding off of frame rect. // This gives us a reasonable starting-rect for the child-rect-unioning // below, which we can then inflate by our padding (without needing to // worry about having double-counted our padding or anything). auto frameContentBounds = frameBounds;
frameContentBounds.Deflate(borderPadding); // Margin rects (Zero-area rects included) of in-flow children (And floats, // as discussed later) are unioned (starting with the scroller's own // content-box), then inflated by the scroll container's padding... auto inFlowChildBounds = frameContentBounds; // ... While scrollable overflow rects contributed from further descendants // (Regardless of if they're in-flow or out-of-flow) are unioned separately // and their union does not get inflated by the scroll container's padding. auto inFlowScrollableOverflow = frameContentBounds;
for (constauto& line : Lines()) {
aOverflowAreas.InkOverflow() =
aOverflowAreas.InkOverflow().Union(line.InkOverflowRect()); if (aDisplay->IsContainLayout()) { // If we have layout containment, we should only consider our child's // ink overflow, leaving the scrollable regions of the parent // unaffected. // Note: Any overflow must be treated as ink overflow (As per // https://drafts.csswg.org/css-contain/#containment-layout part 3). // However, by unioning the children's ink overflow, we've already // incorporated its scrollable overflow, since scrollable overflow // is a subset of ink overflow. continue;
}
if (line.IsInline()) { // This is the maximum contribution for inline line-participating frames - // See `GetLineFrameInFlowBounds`.
inFlowChildBounds =
inFlowChildBounds.UnionEdges(line.GetPhysicalBounds());
} auto lineInFlowChildBounds = line.GetInFlowChildBounds(); if (lineInFlowChildBounds) {
inFlowChildBounds = inFlowChildBounds.UnionEdges(*lineInFlowChildBounds);
}
inFlowScrollableOverflow =
inFlowScrollableOverflow.Union(line.ScrollableOverflowRect());
}
if (Style()->GetPseudoType() == PseudoStyleType::MozScrolledContent) { // Padding inflation only applies to scrolled containers. constauto paddingInflatedOverflow =
ComputePaddingInflatedScrollableOverflow(inFlowChildBounds);
aOverflowAreas.UnionAllWith(paddingInflatedOverflow);
// For backwards compatibility reasons, we don't allow scrollable overflow // on the block axis on inline inputs. if (IsSingleLineTextInput()) {
overflowClipAxes +=
wm.IsVertical() ? PhysicalAxis::Horizontal : PhysicalAxis::Vertical;
}
} // Note: we're using UnionAllWith so as to maintain the invariant of // ink overflow being a superset of scrollable overflow.
aOverflowAreas.UnionAllWith(inFlowScrollableOverflow);
// Factor an outside ::marker in; normally the ::marker will be factored // into the line-box's overflow areas. However, if the line is a block // line then it won't; if there are no lines, it won't. So just // factor it in anyway (it can't hurt if it was already done). // XXXldb Can we just fix GetOverflowArea instead? if (nsIFrame* outsideMarker = GetOutsideMarker()) {
aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
}
if (!overflowClipAxes.isEmpty()) {
aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
overflowClipMargin);
}
// Text control padding handling is kinda disgusting. staticbool RestrictPaddingInflationInInline(const nsIFrame* aFrame) {
MOZ_ASSERT(aFrame); if (aFrame->Style()->GetPseudoType() != PseudoStyleType::MozScrolledContent) { // This can only happen when computing scrollable overflow for overflow: // visible frames (for scroll{Width,Height}). returnfalse;
} // If we're `input` or `textarea`, our grandparent element must be the text // control element that we can query.
nsTextControlFrame* textControl = do_QueryFrame(aFrame->GetParent()); if (MOZ_LIKELY(!textControl)) { returnfalse;
}
// We implement `textarea` as a special case of a div, but based on // web-platform-tests, different rules apply for it - namely, no inline // padding inflation. See // `textarea-padding-iend-overlaps-content-001.tentative.html`. if (!textControl->IsTextArea()) { returnfalse;
} returntrue;
}
nsRect nsBlockFrame::ComputePaddingInflatedScrollableOverflow( const nsRect& aInFlowChildBounds) const { auto result = aInFlowChildBounds; constauto wm = GetWritingMode(); auto padding = GetLogicalUsedPadding(wm); if (RestrictPaddingInflationInInline(this)) {
padding.IStart(wm) = padding.IEnd(wm) = 0;
}
result.Inflate(padding.GetPhysicalMargin(wm)); return result;
}
Maybe<nsRect> nsBlockFrame::GetLineFrameInFlowBounds( const nsLineBox& aLine, const nsIFrame& aLineChildFrame, bool aConsiderPositiveMargins) const {
MOZ_ASSERT(aLineChildFrame.GetParent() == this, "Line's frame doesn't belong to this block frame?"); // Line participants are considered in-flow for content within the line // bounds, which should be accounted for from the line bounds. This is // consistent with e.g. inline element's `margin-bottom` not affecting the // placement of the next line. if (aLineChildFrame.IsPlaceholderFrame() ||
aLineChildFrame.IsLineParticipant()) { return Nothing{};
} if (aLine.IsInline()) { return Some(GetNormalMarginRect(aLineChildFrame, aConsiderPositiveMargins));
} constauto wm = GetWritingMode(); auto rect = aLineChildFrame.GetRectRelativeToSelf(); // Special handling is required for boxes of zero block size, which carry // out margin collapsing with themselves. We end up "rewinding" the line // position after carrying out the block start margin. This is not reflected // in the zero-sized frame's own frame-position. constauto linePoint = aLine.GetPhysicalBounds().TopLeft(); constauto normalPosition = aLineChildFrame.GetLogicalSize(wm).BSize(wm) == 0
? linePoint
: aLineChildFrame.GetNormalPosition(); // Ensure we use the margin we actually carried out.
nsMargin margin; if (aConsiderPositiveMargins) { auto logicalMargin = aLineChildFrame.GetLogicalUsedMargin(wm);
logicalMargin.BEnd(wm) = aLine.GetCarriedOutBEndMargin().Get();
margin = logicalMargin.GetPhysicalMargin(wm).ApplySkipSides(
aLineChildFrame.GetSkipSides());
} else {
margin = aLineChildFrame.GetUsedMargin().ApplySkipSides(
aLineChildFrame.GetSkipSides());
margin.EnsureAtMost(nsMargin());
}
rect.Inflate(margin); return Some(rect + normalPosition);
}
void nsBlockFrame::UnionChildOverflow(OverflowAreas& aOverflowAreas, bool aAsIfScrolled) { // We need to update the overflow areas of lines manually, as they // get cached and re-used otherwise. Lines aren't exposed as normal // frame children, so calling UnionChildOverflow alone will end up // using the old cached values. constauto wm = GetWritingMode();
// ButtonControlFrame elements don't support scrolling, and some like // comboboxes intentionally ignore padding to place their inner elements like // the button, so to preserve behavior of stuff like the scroll{Width,Height} // APIs, forcefully ignore aAsIfScrolled there.
aAsIfScrolled = aAsIfScrolled && !IsButtonControlFrame();
// Overflow area computed here should agree with one computed in // `ComputeOverflowAreas` (see bug 1800939 and bug 1800719). So the // documentation in that function applies here as well. constbool isScrolled =
aAsIfScrolled ||
Style()->GetPseudoType() == PseudoStyleType::MozScrolledContent; // Note that we don't add line in-flow margins if we're not a BFC (which can // happen only for overflow: visible), so that we don't incorrectly account // for margins that otherwise collapse through, see bug 1936156. Note that // ::-moz-scrolled-content is always a BFC (see `AnonymousBoxIsBFC`). constbool considerPositiveMarginsForInFlowChildBounds =
isScrolled && HasAnyStateBits(NS_BLOCK_BFC);
// Relying on aOverflowAreas having been set to frame border rect (if // aAsIfScrolled is false), or padding rect (if true). auto frameContentBounds = aOverflowAreas.ScrollableOverflow();
frameContentBounds.Deflate((aAsIfScrolled
? GetLogicalUsedPadding(wm)
: GetLogicalUsedBorderAndPadding(wm))
.GetPhysicalMargin(wm)); // We need to take in-flow children's margin rect into account, and inflate // it by the padding. auto inFlowChildBounds = frameContentBounds; auto inFlowScrollableOverflow = frameContentBounds;
for (auto& line : Lines()) {
nsRect bounds = line.GetPhysicalBounds();
OverflowAreas lineAreas(bounds, bounds);
const OverflowAreaUnionFlags flags =
aAsIfScrolled ? OverflowAreaUnionFlags::AsIfScrolled
: OverflowAreaUnionFlags::None; for (nsIFrame* lineFrame : line.ChildFrames()) { // Ensure this is called for each frame in the line
ConsiderChildOverflow(lineAreas, lineFrame, flags);
// Consider the overflow areas of the floats attached to the line as well if (line.HasFloats()) { for (nsIFrame* f : line.Floats()) {
ConsiderChildOverflow(lineAreas, f, flags); if (inkOverflowOnly || !isScrolled) { continue;
} auto rect = GetNormalMarginRect(
*f, considerPositiveMarginsForInFlowChildBounds);
inFlowChildBounds = inFlowChildBounds.UnionEdges(rect);
}
}
if (!aAsIfScrolled) {
line.SetOverflowAreas(lineAreas);
}
aOverflowAreas.InkOverflow() =
aOverflowAreas.InkOverflow().Union(lineAreas.InkOverflow()); if (!inkOverflowOnly) {
inFlowScrollableOverflow =
inFlowScrollableOverflow.Union(lineAreas.ScrollableOverflow());
}
}
if (isScrolled) { constauto paddingInflatedOverflow =
ComputePaddingInflatedScrollableOverflow(inFlowChildBounds);
aOverflowAreas.UnionAllWith(paddingInflatedOverflow);
}
aOverflowAreas.UnionAllWith(inFlowScrollableOverflow);
// Union with child frames, skipping the principal and float lists // since we already handled those using the line boxes.
nsLayoutUtils::UnionChildOverflow( this, aOverflowAreas,
{FrameChildListID::Principal, FrameChildListID::Float});
}
bool nsBlockFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) { // Line cursor invariants depend on the overflow areas of the lines, so // we must clear the line cursor since those areas may have changed.
ClearLineCursors(); return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
}
void nsBlockFrame::LazyMarkLinesDirty() { if (HasAnyStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES)) { for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end; ++line) { for (nsIFrame* lineFrame : line->ChildFrames()) { if (lineFrame->IsSubtreeDirty()) { // NOTE: MarkLineDirty does more than just marking the line dirty.
MarkLineDirty(line, &mLines); break;
}
}
}
RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
}
}
void nsBlockFrame::MarkLineDirty(LineIterator aLine, const nsLineList* aLineList) { // Mark aLine dirty
aLine->MarkDirty();
aLine->SetInvalidateTextRuns(true); #ifdef DEBUG if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
} #endif
// Mark previous line dirty if it's an inline line so that it can // maybe pullup something from the line just affected. // XXX We don't need to do this if aPrevLine ends in a break-after... if (aLine != aLineList->front() && aLine->IsInline() &&
aLine.prev()->IsInline()) {
aLine.prev()->MarkDirty();
aLine.prev()->SetInvalidateTextRuns(true); #ifdef DEBUG if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": mark prev-line %p dirty\n", static_cast<void*>(aLine.prev().get()));
} #endif
}
}
void nsBlockFrame::PrepareResizeReflow(BlockReflowState& aState) { // See if we can try and avoid marking all the lines as dirty // FIXME(emilio): This should be writing-mode aware, I guess. bool tryAndSkipLines = // The left content-edge must be a constant distance from the left // border-edge.
!StylePadding()->mPadding.Get(eSideLeft).HasPercent();
#ifdef DEBUG if (gDisableResizeOpt) {
tryAndSkipLines = false;
} if (gNoisyReflow) { if (!tryAndSkipLines) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": marking all lines dirty: availISize=%d\n",
aState.mReflowInput.AvailableISize());
}
} #endif
#ifdef DEBUG if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": trying to avoid marking all lines dirty\n");
} #endif
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end; ++line) { // We let child blocks make their own decisions the same // way we are here. bool isLastLine = line == mLines.back() && !GetNextInFlow(); if (line->IsBlock() || line->HasFloats() ||
(!isLastLine && !line->HasForcedLineBreakAfter()) ||
((isLastLine || !line->IsLineWrapped())) ||
line->ResizeReflowOptimizationDisabled() ||
line->IsImpactedByFloat() || (line->IEnd() > newAvailISize)) {
line->MarkDirty();
}
// Check if the line is moving relative to the float manager if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) { if (aLine->IsBlock()) { // Unconditionally reflow sliding blocks; we only really need to reflow // if there's a float impacting this block, but the current float manager // makes it difficult to check that. Therefore, we let the child block // decide what it needs to reflow.
aLine->MarkDirty();
} else { bool wasImpactedByFloat = aLine->IsImpactedByFloat();
nsFlowAreaRect floatAvailableSpace =
aState.GetFloatAvailableSpaceForBSize(
aState.mReflowInput.GetWritingMode(),
aLine->BStart() + aDeltaBCoord, aLine->BSize(), nullptr);
// Mark the line dirty if it was or is affected by a float // We actually only really need to reflow if the amount of impact // changes, but that's not straightforward to check if (wasImpactedByFloat || floatAvailableSpace.HasFloats()) {
aLine->MarkDirty();
}
}
}
}
// Reflow our last line if our availableBSize has increased // so that we (and our last child) pull up content as necessary if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
GetNextInFlow() &&
aState.mReflowInput.AvailableBSize() >
GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
LineIterator lastLine = LinesEnd(); if (lastLine != LinesBegin()) {
--lastLine;
lastLine->MarkDirty();
}
} // the amount by which we will slide the current line if it is not // dirty
nscoord deltaBCoord = 0;
// whether we did NOT reflow the previous line and thus we need to // recompute the carried out margin before the line if we want to // reflow it or if its previous margin is dirty bool needToRecoverState = false; // Float continuations were reflowed in ReflowPushedFloats bool reflowedFloat =
HasFloats() && GetFloats()->FirstChild()->HasAnyStateBits(
NS_FRAME_IS_PUSHED_OUT_OF_FLOW); bool lastLineMovedUp = false; // We save up information about BR-clearance here
UsedClear inlineFloatClearType = aState.mTrailingClearFromPIF;
LineIterator line = LinesBegin(), line_end = LinesEnd();
// Determine if children of this frame could have breaks between them for // page names. // // We need to check for paginated layout, the named-page pref, and if the // available block-size is constrained. // // Note that we need to check for paginated layout as named-pages are only // used during paginated reflow. We need to additionally check for // unconstrained block-size to avoid introducing fragmentation breaks during // "measuring" reflows within an overall paginated reflow, and to avoid // fragmentation in monolithic containers like 'inline-block'. // // Because we can only break for named pages using Class A breakpoints, we // also need to check that the block flow direction of the containing frame // of these items (which is this block) is parallel to that of this page. // See: https://www.w3.org/TR/css-break-3/#btw-blocks const nsPresContext* const presCtx = aState.mPresContext; constbool canBreakForPageNames =
aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
GetWritingMode().IsVertical();
// ReflowInput.mFlags.mCanHaveClassABreakpoints should respect the named // pages pref and presCtx->IsPaginated, so we did not explicitly check these // above when setting canBreakForPageNames. if (canBreakForPageNames) {
MOZ_ASSERT(presCtx->IsPaginated(), "canBreakForPageNames should not be set during non-paginated " "reflow");
}
// Reflow the lines that are already ours for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
DumpLine(aState, line, deltaBCoord, 0); #ifdef DEBUG
AutoNoisyIndenter indent2(gNoisyReflow); #endif
if (selfDirty) {
line->MarkDirty();
}
// This really sucks, but we have to look inside any blocks that have clear // elements inside them. // XXX what can we do smarter here? if (!line->IsDirty() && line->IsBlock() &&
line->mFirstChild->HasAnyStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN) &&
aState.FloatManager()->HasAnyFloats()) {
line->MarkDirty();
}
// We have to reflow the line if it's a block whose clearance // might have changed, so detect that. if (!line->IsDirty() &&
(line->HasFloatClearTypeBefore() || floatAvoidingBlock)) {
nscoord curBCoord = aState.mBCoord; // See where we would be after applying any clearance due to // BRs. if (inlineFloatClearType != UsedClear::None) {
std::tie(curBCoord, std::ignore) =
aState.ClearFloats(curBCoord, inlineFloatClearType);
}
auto [newBCoord, result] = aState.ClearFloats(
curBCoord, line->FloatClearTypeBefore(), floatAvoidingBlock);
if (line->HasClearance()) { // Reflow the line if it might not have clearance anymore. if (result == ClearFloatsResult::BCoordNoChange // aState.mBCoord is the clearance point which should be the // block-start border-edge of the block frame. If sliding the // block by deltaBCoord isn't going to put it in the predicted // position, then we'd better reflow the line.
|| newBCoord != line->BStart() + deltaBCoord) {
line->MarkDirty();
}
} else { // Reflow the line if the line might have clearance now. if (result != ClearFloatsResult::BCoordNoChange) {
line->MarkDirty();
}
}
}
// We might have to reflow a line that is after a clearing BR. if (inlineFloatClearType != UsedClear::None) {
std::tie(aState.mBCoord, std::ignore) =
aState.ClearFloats(aState.mBCoord, inlineFloatClearType); if (aState.mBCoord != line->BStart() + deltaBCoord) { // SlideLine is not going to put the line where the clearance // put it. Reflow the line to be sure.
line->MarkDirty();
}
inlineFloatClearType = UsedClear::None;
}
bool previousMarginWasDirty = line->IsPreviousMarginDirty(); if (previousMarginWasDirty) { // If the previous margin is dirty, reflow the current line
line->MarkDirty();
line->ClearPreviousMarginDirty();
} elseif (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) { const nscoord scrollableOverflowBEnd =
LogicalRect(line->mWritingMode, line->ScrollableOverflowRect(),
line->mContainerSize)
.BEnd(line->mWritingMode); if (scrollableOverflowBEnd + deltaBCoord > aState.ContentBEnd()) { // Lines that aren't dirty but get slid past our available block-size // constraint must be reflowed.
line->MarkDirty();
}
}
if (!line->IsDirty()) { constbool isPaginated = // Last column can be reflowed unconstrained during column balancing. // Hence the additional NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR bit check // as a fail-safe fallback.
aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE ||
HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) || // Table can also be reflowed unconstrained during printing.
aState.mPresContext->IsPaginated(); if (isPaginated) { // We are in a paginated context, i.e. in columns or pages. constbool mayContainFloats =
line->IsBlock() || line->HasFloats() || line->HadFloatPushed(); if (mayContainFloats) { // The following if-else conditions check whether this line -- which // might have floats in its subtree, or has floats as direct children, // or had floats pushed -- needs to be reflowed. if (deltaBCoord != 0 || aState.mReflowInput.IsBResize()) { // The distance to the block-end edge might have changed. Reflow the // line both because the breakpoints within its floats may have // changed and because we might have to push/pull the floats in // their entirety.
line->MarkDirty();
} elseif (HasPushedFloats()) { // We had pushed floats which haven't been drained by our // next-in-flow, which means our parent is currently reflowing us // again due to clearance without creating a next-in-flow for us. // Reflow the line to redo the floats split logic to correctly set // our reflow status.
line->MarkDirty();
} elseif (aState.mReflowInput.mFlags.mMustReflowPlaceholders) { // Reflow the line (that may containing a float's placeholder frame) // if our parent tells us to do so.
line->MarkDirty();
} elseif (aState.mReflowInput.mFlags.mMovedBlockFragments) { // Our parent's line containing us moved to a different fragment. // Reflow the line because the decision about whether the float fits // may be different in a different fragment.
line->MarkDirty();
}
}
}
}
if (!line->IsDirty()) { // See if there's any reflow damage that requires that we mark the // line dirty.
PropagateFloatDamage(aState, line, deltaBCoord);
}
// If the container size has changed, reset mContainerSize. If the // line's writing mode is not ltr, or if the line is not left-aligned, also // mark the line dirty. if (aState.ContainerSize() != line->mContainerSize) {
line->mContainerSize = aState.ContainerSize();
// Check for a page break caused by CSS named pages. // // We should break for named pages when two frames meet at a class A // breakpoint, where the first frame has a different end page value to the // second frame's start page value. canBreakForPageNames is true iff // children of this frame can form class A breakpoints, and that we are not // in a measurement reflow or in a monolithic container such as // 'inline-block'. // // We specifically do not want to cause a page-break for named pages when // we are at the top of a page. This would otherwise happen when the // previous sibling is an nsPageBreakFrame, or all previous siblings on the // current page are zero-height. The latter may not be per-spec, but is // compatible with Chrome's implementation of named pages. const nsAtom* nextPageName = nullptr; bool shouldBreakForPageName = false; if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
!aState.IsAdjacentWithBStart())) { const nsIFrame* const frame = line->mFirstChild; if (!frame->IsPlaceholderFrame() && !frame->IsPageBreakFrame()) {
nextPageName = frame->GetStartPageValue(); // Walk back to the last frame that isn't a placeholder. const nsIFrame* prevFrame = frame->GetPrevSibling(); while (prevFrame && prevFrame->IsPlaceholderFrame()) {
prevFrame = prevFrame->GetPrevSibling();
} if (prevFrame && prevFrame->GetEndPageValue() != nextPageName) {
shouldBreakForPageName = true;
line->MarkDirty();
}
}
}
if (needToRecoverState && line->IsDirty()) { // We need to reconstruct the block-end margin only if we didn't // reflow the previous line and we do need to reflow (or repair // the block-start position of) the next line.
aState.ReconstructMarginBefore(line);
}
bool reflowedPrevLine = !needToRecoverState; if (needToRecoverState) {
needToRecoverState = false;
// Update aState.mPrevChild as if we had reflowed all of the frames in // this line. if (line->IsDirty()) {
NS_ASSERTION(
line->mFirstChild->GetPrevSibling() == line.prev()->LastChild(), "unexpected line frames");
aState.mPrevChild = line->mFirstChild->GetPrevSibling();
}
}
// Now repair the line and update |aState.mBCoord| by calling // |ReflowLine| or |SlideLine|. // If we're going to reflow everything again, then no need to reflow // the dirty line ... unless the line has floats, in which case we'd // better reflow it now to refresh its float cache, which may contain // dangling frame pointers! Ugh! This reflow of the line may be // incorrect because we skipped reflowing previous lines (e.g., floats // may be placed incorrectly), but that's OK because we'll mark the // line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..." if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
lastLineMovedUp = true;
// Compute the dirty lines "before" BEnd, after factoring in // the running deltaBCoord value - the running value is implicit in // aState.mBCoord.
nscoord oldB = line->BStart();
nscoord oldBMost = line->BEnd();
NS_ASSERTION(!willReflowAgain || !line->IsBlock(), "Don't reflow blocks while willReflowAgain is true, reflow " "of block abs-pos children depends on this");
if (shouldBreakForPageName) { // Immediately fragment for page-name. It is possible we could break // out of the loop right here, but this should make it more similar to // what happens when reflow causes fragmentation. // Set the page name, so that PushTruncatedLine does not need to // recalculate the new page name.
PresShell()->FrameConstructor()->SetNextPageContentFramePageName(
nextPageName ? nextPageName : GetAutoPageValue());
PushTruncatedLine(aState, line, &keepGoing,
ComputeNewPageNameIfNeeded::No);
} else { // Reflow the dirty line. If it's an incremental reflow, then force // it to invalidate the dirty area if necessary
usedOverflowWrap |= ReflowLine(aState, line, &keepGoing);
}
if (aState.mReflowInput.WillReflowAgainForClearance()) {
line->MarkDirty();
willReflowAgain = true; // Note that once we've entered this state, every line that gets here // (e.g. because it has floats) gets marked dirty and reflowed again. // in the next pass. This is important, see above.
}
if (line->HasFloats()) {
reflowedFloat = true;
}
if (!keepGoing) {
DumpLine(aState, line, deltaBCoord, -1); if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
} break;
}
// Test to see whether the margin that should be carried out // to the next line (NL) might have changed. In ReflowBlockFrame // we call nextLine->MarkPreviousMarginDirty if the block's // actual carried-out block-end margin changed. So here we only // need to worry about the following effects: // 1) the line was just created, and it might now be blocking // a carried-out block-end margin from previous lines that // used to reach NL from reaching NL // 2) the line used to be empty, and is now not empty, // thus blocking a carried-out block-end margin from previous lines // that used to reach NL from reaching NL // 3) the line wasn't empty, but now is, so a carried-out // block-end margin from previous lines that didn't used to reach NL // now does // 4) the line might have changed in a way that affects NL's // ShouldApplyBStartMargin decision. The three things that matter // are the line's emptiness, its adjacency to the block-start edge of the // block, and whether it has clearance (the latter only matters if the // block was and is adjacent to the block-start and empty). // // If the line is empty now, we can't reliably tell if the line was empty // before, so we just assume it was and do // nextLine->MarkPreviousMarginDirty. This means the checks in 4) are // redundant; if the line is empty now we don't need to check 4), but if // the line is not empty now and we're sure it wasn't empty before, any // adjacency and clearance changes are irrelevant to the result of // nextLine->ShouldApplyBStartMargin. if (line.next() != LinesEnd()) { bool maybeWasEmpty = oldB == line.next()->BStart(); bool isEmpty = line->CachedIsEmpty(); if (maybeReflowingForFirstTime /*1*/ ||
(isEmpty || maybeWasEmpty) /*2/3/4*/) {
line.next()->MarkPreviousMarginDirty(); // since it's marked dirty, nobody will care about |deltaBCoord|
}
}
// If the line was just reflowed for the first time, then its // old mBounds cannot be trusted so this deltaBCoord computation is // bogus. But that's OK because we just did // MarkPreviousMarginDirty on the next line which will force it // to be reflowed, so this computation of deltaBCoord will not be // used.
deltaBCoord = line->BEnd() - oldBMost;
// Now do an interrupt check. We want to do this only in the case when we // actually reflow the line, so that if we get back in here we'll get // further on the reflow before interrupting.
aState.mPresContext->CheckForInterrupt(this);
} else {
aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus); // Nop except for blocks (we don't create overflow container // continuations for any inlines atm), so only checking mFirstChild // is enough
lastLineMovedUp = deltaBCoord < 0;
if (deltaBCoord != 0) {
SlideLine(aState, line, deltaBCoord);
}
NS_ASSERTION(!line->IsDirty() || !line->HasFloats(), "Possibly stale float cache here!"); if (willReflowAgain && line->IsBlock()) { // If we're going to reflow everything again, and this line is a block, // then there is no need to recover float state. The line may contain // other lines with floats, but in that case RecoverStateFrom would only // add floats to the float manager. We don't need to do that because // everything's going to get reflowed again "for real". Calling // RecoverStateFrom in this situation could be lethal because the // block's descendant lines may have float caches containing dangling // frame pointers. Ugh! // If this line is inline, then we need to recover its state now // to make sure that we don't forget to move its floats by deltaBCoord.
} else { // XXX EVIL O(N^2) EVIL
aState.RecoverStateFrom(line, deltaBCoord);
}
// Keep mBCoord up to date in case we're propagating reflow damage // and also because our final height may depend on it. If the // line is inlines, then only update mBCoord if the line is not // empty, because that's what PlaceLine does. (Empty blocks may // want to update mBCoord, e.g. if they have clearance.) if (line->IsBlock() || !line->CachedIsEmpty()) {
aState.mBCoord = line->BEnd();
}
needToRecoverState = true;
if (reflowedPrevLine && !line->IsBlock() &&
aState.mPresContext->HasPendingInterrupt()) { // Need to make sure to pull overflows from any prev-in-flows for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
inlineKid->PullOverflowsFromPrevInFlow();
}
}
}
// Record if we need to clear floats before reflowing the next // line. Note that inlineFloatClearType will be handled and // cleared before the next line is processed, so there is no // need to combine break types here. if (line->HasFloatClearTypeAfter()) {
inlineFloatClearType = line->FloatClearTypeAfter();
}
if (LineHasClear(line.get())) {
foundAnyClears = true;
}
DumpLine(aState, line, deltaBCoord, -1);
if (aState.mPresContext->HasPendingInterrupt()) {
willReflowAgain = true; // Another option here might be to leave |line| clean if // !HasPendingInterrupt() before the CheckForInterrupt() call, since in // that case the line really did reflow as it should have. Not sure // whether that would be safe, so doing this for now instead. Also not // sure whether we really want to mark all lines dirty after an // interrupt, but until we get better at propagating float damage we // really do need to do it this way; see comments inside MarkLineDirty.
MarkLineDirtyForInterrupt(line);
}
}
// Handle BR-clearance from the last line of the block if (inlineFloatClearType != UsedClear::None) {
std::tie(aState.mBCoord, std::ignore) =
aState.ClearFloats(aState.mBCoord, inlineFloatClearType);
}
if (needToRecoverState) { // Is this expensive?
aState.ReconstructMarginBefore(line);
// Update aState.mPrevChild as if we had reflowed all of the frames in // the last line.
NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
line.prev()->LastChild(), "unexpected line frames");
aState.mPrevChild = line == line_end ? mFrames.LastChild()
: line->mFirstChild->GetPrevSibling();
}
// We can skip trying to pull up the next line if our height is constrained // (so we can report being incomplete) and there is no next in flow or we // were told not to or we know it will be futile, i.e., // -- the next in flow is not changing // -- and we cannot have added more space for its first line to be // pulled up into, // -- it's an incremental reflow of a descendant // -- and we didn't reflow any floats (so the available space // didn't change) // -- my chain of next-in-flows either has no first line, or its first // line isn't dirty. bool heightConstrained =
aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE; bool skipPull = willReflowAgain && heightConstrained; if (!skipPull && heightConstrained && aState.mNextInFlow &&
(aState.mReflowInput.mFlags.mNextInFlowUntouched && !lastLineMovedUp &&
!HasAnyStateBits(NS_FRAME_IS_DIRTY) && !reflowedFloat)) { // We'll place lineIter at the last line of this block, so that // nsBlockInFlowLineIterator::Next() will take us to the first // line of my next-in-flow-chain. (But first, check that I // have any lines -- if I don't, just bail out of this // optimization.)
LineIterator lineIter = this->LinesEnd(); if (lineIter != this->LinesBegin()) {
lineIter--; // I have lines; step back from dummy iterator to last line.
nsBlockInFlowLineIterator bifLineIter(this, lineIter);
// Check for next-in-flow-chain's first line. // (First, see if there is such a line, and second, see if it's clean) if (!bifLineIter.Next() || !bifLineIter.GetLine()->IsDirty()) {
skipPull = true;
}
}
}
if (skipPull && aState.mNextInFlow) {
NS_ASSERTION(heightConstrained, "Height should be constrained here\n"); if (aState.mNextInFlow->IsTrueOverflowContainer()) {
aState.mReflowStatus.SetOverflowIncomplete();
} else {
aState.mReflowStatus.SetIncomplete();
}
}
if (!skipPull && aState.mNextInFlow) { // Pull data from a next-in-flow if there's still room for more // content here. while (keepGoing && aState.mNextInFlow) { // Grab first line from our next-in-flow
nsBlockFrame* nextInFlow = aState.mNextInFlow;
nsLineBox* pulledLine;
nsFrameList pulledFrames; if (!nextInFlow->mLines.empty()) {
RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, &pulledLine,
&pulledFrames);
ClearLineCursors();
} else { // Grab an overflow line if there are any
FrameLines* overflowLines = nextInFlow->GetOverflowLines(); if (!overflowLines) {
aState.mNextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow()); continue;
} bool last =
RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
&pulledLine, &pulledFrames); if (last) {
nextInFlow->DestroyOverflowLines();
}
}
if (pulledFrames.IsEmpty()) { // The line is empty. Try the next one.
NS_ASSERTION(
pulledLine->GetChildCount() == 0 && !pulledLine->mFirstChild, "bad empty line");
nextInFlow->FreeLineBox(pulledLine); continue;
}
if (nextInFlow->MaybeHasLineCursor()) { if (pulledLine == nextInFlow->GetLineCursorForDisplay()) {
nextInFlow->ClearLineCursorForDisplay();
} if (pulledLine == nextInFlow->GetLineCursorForQuery()) {
nextInFlow->ClearLineCursorForQuery();
}
}
ReparentFrames(pulledFrames, nextInFlow, this);
pulledLine->SetMovedFragments();
NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(), "Unexpected last frame");
NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here");
NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), "Incorrect aState.mPrevChild before inserting line at end");
// Shift pulledLine's frames into our mFrames list.
mFrames.AppendFrames(nullptr, std::move(pulledFrames));
// Add line to our line list, and set its last child as our new prev-child
line = mLines.before_insert(LinesEnd(), pulledLine);
aState.mPrevChild = mFrames.LastChild();
// Reparent floats whose placeholders are in the line.
ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
if (aState.mPresContext->HasPendingInterrupt()) {
MarkLineDirtyForInterrupt(line);
} else { // Now reflow it and any lines that it makes during it's reflow // (we have to loop here because reflowing the line may cause a new // line to be created; see SplitLine's callers for examples of // when this happens). while (line != LinesEnd()) {
usedOverflowWrap |= ReflowLine(aState, line, &keepGoing);
if (aState.mReflowInput.WillReflowAgainForClearance()) {
line->MarkDirty();
keepGoing = false; // If we are going to be reflowed again by our ancestor due to a // clearance frame being discovered, reset the reflow completion // status. The current status does not matter to our parent frame // since we will reflow again anyway. This also prevents the false // assumption that we are incomplete when reflowing under an // unconstrained available block-size.
aState.mReflowStatus.Reset(); break;
}
DumpLine(aState, line, deltaBCoord, -1); if (!keepGoing) { if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
} break;
}
if (LineHasClear(line.get())) {
foundAnyClears = true;
}
if (aState.mPresContext->CheckForInterrupt(this)) {
MarkLineDirtyForInterrupt(line); break;
}
// If this is an inline frame then its time to stop
++line;
aState.AdvanceToNextLine();
}
}
}
if (aState.mReflowStatus.IsIncomplete()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
} // XXXfr shouldn't set this flag when nextinflow has no lines
}
// Handle an odd-ball case: a list-item with no lines
nsIFrame* outsideMarker = GetOutsideMarker(); if (outsideMarker && mLines.empty()) {
ReflowOutput metrics(aState.mReflowInput);
WritingMode wm = aState.mReflowInput.GetWritingMode();
ReflowOutsideMarker(
outsideMarker, aState, metrics,
aState.mReflowInput.ComputedPhysicalBorderPadding().top);
NS_ASSERTION(!MarkerIsEmpty(outsideMarker) || metrics.BSize(wm) == 0, "empty ::marker frame took up space");
if (!MarkerIsEmpty(outsideMarker)) { // There are no lines so we have to fake up some y motion so that // we end up with *some* height. // (Note: if we're layout-contained, we have to be sure to leave our // ReflowOutput's BlockStartAscent() (i.e. the baseline) untouched, // because layout-contained frames have no baseline.) if (!aState.mReflowInput.mStyleDisplay->IsContainLayout() &&
metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
nscoord ascent;
WritingMode wm = aState.mReflowInput.GetWritingMode(); if (nsLayoutUtils::GetFirstLineBaseline(wm, outsideMarker, &ascent)) {
metrics.SetBlockStartAscent(ascent);
} else {
metrics.SetBlockStartAscent(metrics.BSize(wm));
}
}
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
// Just checking NS_FRAME_IS_DIRTY is ok, because we've already // marked the lines that need to be marked dirty based on our // vertical resize stuff. So we'll definitely reflow all those kids; // the only question is how they should behave. if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) { // Mark all our child frames dirty so we make sure to reflow them // later. for (nsIFrame* f : aLine->ChildFrames()) {
f->MarkSubtreeDirty();
} // And mark all the floats whose reflows we might be skipping dirty too. if (aLine->HasFloats()) { for (nsIFrame* f : aLine->Floats()) {
f->MarkSubtreeDirty();
}
}
} else { // Dirty all the descendant lines of block kids to handle float damage, // since our nsFloatManager will go away by the next time we're reflowing. // XXXbz Can we do something more like what PropagateFloatDamage does? // Would need to sort out the exact business with mBlockDelta for that.... // This marks way too much dirty. If we ever make this better, revisit // which lines we mark dirty in the interrupt case in ReflowDirtyLines.
nsBlockFrame* bf = do_QueryFrame(aLine->mFirstChild); if (bf) {
MarkAllDescendantLinesDirty(bf);
}
}
}
void nsBlockFrame::DeleteLine(BlockReflowState& aState,
nsLineList::iterator aLine,
nsLineList::iterator aLineEnd) {
MOZ_ASSERT(0 == aLine->GetChildCount(), "can't delete !empty line"); if (0 == aLine->GetChildCount()) {
NS_ASSERTION(aState.mCurrentLine == aLine, "using function more generally than designed, " "but perhaps OK now");
nsLineBox* line = aLine;
aLine = mLines.erase(aLine);
FreeLineBox(line);
ClearLineCursors(); // Mark the previous margin of the next line dirty since we need to // recompute its top position. if (aLine != aLineEnd) {
aLine->MarkPreviousMarginDirty();
}
}
}
// Setup the line-layout for the new line
aState.mCurrentLine = aLine;
aLine->ClearDirty();
aLine->InvalidateCachedIsEmpty();
aLine->ClearHadFloatPushed();
// If this line contains a single block that is hidden by `content-visibility` // don't reflow the line. If this line contains inlines and the first one is // hidden by `content-visibility`, all of them are, so avoid reflow in that // case as well. // For frames that own anonymous children, even the first child is hidden by // `content-visibility`, there could be some anonymous children need reflow, // so we don't skip reflow this line.
nsIFrame* firstChild = aLine->mFirstChild; if (firstChild->IsHiddenByContentVisibilityOfInFlowParentForLayout() &&
!HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES)) { returnfalse;
}
// Now that we know what kind of line we have, reflow it bool usedOverflowWrap = false; if (aLine->IsBlock()) {
ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
} else {
aLine->SetLineWrapped(false);
usedOverflowWrap = ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
// Store the line's float edges for overflow marker analysis if needed.
aLine->ClearFloatEdges(); if (aState.mFlags.mCanHaveOverflowMarkers) {
WritingMode wm = aLine->mWritingMode;
nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(
wm, aLine->BStart(), aLine->BSize(), nullptr); if (r.HasFloats()) {
LogicalRect so = aLine->GetOverflowArea(OverflowType::Scrollable, wm,
aLine->mContainerSize);
nscoord s = r.mRect.IStart(wm);
nscoord e = r.mRect.IEnd(wm); if (so.IEnd(wm) > e || so.IStart(wm) < s) { // This line is overlapping a float - store the edges marking the area // between the floats for text-overflow analysis.
aLine->SetFloatEdges(s, e);
}
}
}
}
NS_ASSERTION(
!GetOverflowLines(), "Our overflow lines should have been removed at the start of reflow");
// Try each next-in-flow.
nsBlockFrame* nextInFlow = aState.mNextInFlow; while (nextInFlow) { if (nextInFlow->mLines.empty()) {
nextInFlow->DrainSelfOverflowList();
} if (!nextInFlow->mLines.empty()) { return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
}
nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
aState.mNextInFlow = nextInFlow;
}
return nullptr;
}
nsIFrame* nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
nsBlockFrame* aFromContainer,
nsLineList::iterator aFromLine) {
nsLineBox* fromLine = aFromLine;
MOZ_ASSERT(fromLine, "bad line to pull from");
MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
MOZ_ASSERT(aLine->GetChildCount(), "empty line");
MOZ_ASSERT(!HasProperty(LineIteratorProperty()), "Shouldn't have line iterators mid-reflow");
NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(), "Disagreement about whether it's a block or not");
if (fromLine->IsBlock()) { // If our line is not empty and the child in aFromLine is a block // then we cannot pull up the frame into this line. In this case // we stop pulling. return nullptr;
} // Take frame from fromLine
nsIFrame* frame = fromLine->mFirstChild;
nsIFrame* newFirstChild = frame->GetNextSibling();
if (aFromContainer != this) { // The frame is being pulled from a next-in-flow; therefore we need to add // it to our sibling list.
MOZ_ASSERT(aLine == mLines.back());
MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(), "should only pull from first line");
aFromContainer->mFrames.RemoveFrame(frame);
// When pushing and pulling frames we need to check for whether any // views need to be reparented.
ReparentFrame(frame, aFromContainer, this);
mFrames.AppendFrame(nullptr, frame);
// The frame might have (or contain) floats that need to be brought // over too. (pass 'false' since there are no siblings to check)
ReparentFloats(frame, aFromContainer, false);
} else {
MOZ_ASSERT(aLine == aFromLine.prev());
}
if (fromLine->GetChildCount() > 0) { // Mark line dirty now that we pulled a child
fromLine->MarkDirty();
fromLine->mFirstChild = newFirstChild;
} else { // Free up the fromLine now that it's empty. // Its bounds might need to be redrawn, though. if (aFromLine.next() != aFromContainer->mLines.end()) {
aFromLine.next()->MarkPreviousMarginDirty();
}
aFromContainer->mLines.erase(aFromLine); // aFromLine is now invalid
aFromContainer->FreeLineBox(fromLine);
}
// Adjust line state
nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
// Changing container width only matters if writing mode is vertical-rl if (GetWritingMode().IsVerticalRL()) {
MoveChildFramesOfLine(aLine, sizeDelta.width);
}
}
void nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine,
nscoord aDeltaBCoord) { // Adjust the frames in the line
nsIFrame* kid = aLine->mFirstChild; if (!kid) { return;
}
if (aLine->IsBlock()) { if (aDeltaBCoord) {
kid->MovePositionBy(wm, translation);
}
} else { // Adjust the block-dir coordinate of the frames in the line. // Note: we need to re-position views even if aDeltaBCoord is 0, because // one of our parent frames may have moved and so the view's position // relative to its parent may have changed. if (aDeltaBCoord) { for (nsIFrame* f : aLine->ChildFrames()) {
f->MovePositionBy(wm, translation);
}
}
}
}
staticinlinebool IsNonAutoNonZeroBSize(const StyleSize& aCoord) { // The "extremum length" values (see ExtremumLength) that return true from // 'BehavesLikeInitialValueOnBlockAxis()' were originally aimed at // inline-size (or width, as it was before logicalization). For now, let them // return false here, so we treat them like 'auto' pending a real // implementation. (See bug 1126420.) if (aCoord.BehavesLikeInitialValueOnBlockAxis()) { returnfalse;
} if (aCoord.BehavesLikeStretchOnBlockAxis()) { // We return true for "stretch" because it's essentially equivalent to // "100%" for the purposes of this function (and this function returns true // for nonzero percentage values, in the final return statement below). returntrue;
}
MOZ_ASSERT(aCoord.IsLengthPercentage()); // If we evaluate the length/percent/calc at a percentage basis of // both nscoord_MAX and 0, and it's zero both ways, then it's a zero // length, percent, or combination thereof. Test > 0 so we clamp // negative calc() results to 0. return aCoord.AsLengthPercentage().Resolve(nscoord_MAX) > 0 ||
aCoord.AsLengthPercentage().Resolve(0) > 0;
}
// Blocks which are margin-roots (including inline-blocks) cannot be treated // as empty for margin-collapsing and other purposes. They're more like // replaced elements. if (HasAnyStateBits(NS_BLOCK_BFC)) { returnfalse;
}
if (IsNonAutoNonZeroBSize(*position->MinBSize(wm, anchorResolutionParams)) ||
IsNonAutoNonZeroBSize(*bSize)) { returnfalse;
}
// FIXME: Bug 1646100 - Take intrinsic size into account. // FIXME: Handle the case that both inline and block sizes are auto. // https://github.com/w3c/csswg-drafts/issues/5060. // Note: block-size could be zero or auto/intrinsic keywords here. if (bSize->BehavesLikeInitialValueOnBlockAxis() &&
position->mAspectRatio.HasFiniteRatio()) { returnfalse;
}
bool nsBlockFrame::ShouldApplyBStartMargin(BlockReflowState& aState,
nsLineBox* aLine) { if (aLine->mFirstChild->IsPageBreakFrame()) { // A page break frame consumes margins adjacent to it. // https://drafts.csswg.org/css-break/#break-margins returnfalse;
}
if (aState.mFlags.mShouldApplyBStartMargin) { // Apply short-circuit check to avoid searching the line list returntrue;
}
if (!aState.IsAdjacentWithBStart()) { // If we aren't at the start block-coordinate then something of non-zero // height must have been placed. Therefore the childs block-start margin // applies.
aState.mFlags.mShouldApplyBStartMargin = true; returntrue;
}
// Determine if this line is "essentially" the first line
LineIterator line = LinesBegin(); if (aState.mFlags.mHasLineAdjacentToTop) {
line = aState.mLineAdjacentToTop;
} while (line != aLine) { if (!line->CachedIsEmpty() || line->HasClearance()) { // A line which precedes aLine is non-empty, or has clearance, // so therefore the block-start margin applies.
aState.mFlags.mShouldApplyBStartMargin = true; returntrue;
} // No need to apply the block-start margin if the line has floats. We // should collapse anyway (bug 44419)
++line;
aState.mFlags.mHasLineAdjacentToTop = true;
aState.mLineAdjacentToTop = line;
}
// The line being reflowed is "essentially" the first line in the // block. Therefore its block-start margin will be collapsed by the // generational collapsing logic with its parent (us). returnfalse;
}
// If the previous frame was a page-break-frame, then preemptively push this // frame to the next page. // This is primarily important for the placeholders for abspos frames, which // measure as zero height and then would be placed on this page. if (aState.ContentBSize() != NS_UNCONSTRAINEDSIZE) { const nsIFrame* const prev = frame->GetPrevSibling(); if (prev && prev->IsPageBreakFrame()) {
PushTruncatedLine(aState, aLine, aKeepReflowGoing); return;
}
}
// Prepare the block reflow engine
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
// Clear past floats before the block if the clear style is not none
aLine->ClearForcedLineBreak(); if (clearType != UsedClear::None) {
aLine->SetFloatClearTypeBefore(clearType);
}
// See if we should apply the block-start margin. If the block frame being // reflowed is a continuation, then we don't apply its block-start margin // because it's not significant. Otherwise, dig deeper. bool applyBStartMargin =
!frame->GetPrevContinuation() && ShouldApplyBStartMargin(aState, aLine); if (applyBStartMargin) { // The HasClearance setting is only valid if ShouldApplyBStartMargin // returned false (in which case the block-start margin-root set our // clearance flag). Otherwise clear it now. We'll set it later on // ourselves if necessary.
aLine->ClearHasClearance();
} bool treatWithClearance = aLine->HasClearance();
// If our block-start margin was counted as part of some parent's block-start // margin collapse, and we are being speculatively reflowed assuming this // frame DID NOT need clearance, then we need to check that // assumption. if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
aState.mReflowInput.mDiscoveredClearance) {
nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.Get(); if (auto [clearBCoord, result] =
aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
result != ClearFloatsResult::BCoordNoChange) {
(void)clearBCoord;
// Only record the first frame that requires clearance if (!*aState.mReflowInput.mDiscoveredClearance) {
*aState.mReflowInput.mDiscoveredClearance = frame;
}
aState.mPrevChild = frame; // Exactly what we do now is flexible since we'll definitely be // reflowed. return;
}
} if (treatWithClearance) {
applyBStartMargin = true;
}
nsIFrame* clearanceFrame = nullptr; const nscoord startingBCoord = aState.mBCoord; const CollapsingMargin incomingMargin = aState.mPrevBEndMargin;
nscoord clearance; while (true) {
clearance = 0;
nscoord bStartMargin = 0; bool mayNeedRetry = false; bool clearedFloats = false; bool clearedPushedOrSplitFloat = false; if (applyBStartMargin) { // Precompute the blocks block-start margin value so that we can get the // correct available space (there might be a float that's // already been placed below the aState.mPrevBEndMargin
// Setup a reflowInput to get the style computed block-start margin // value. We'll use a reason of `resize' so that we don't fudge // any incremental reflow input.
// The availSpace here is irrelevant to our needs - all we want // out if this setup is the block-start margin value which doesn't depend // on the childs available space. // XXX building a complete ReflowInput just to get the block-start // margin seems like a waste. And we do this for almost every block!
WritingMode wm = frame->GetWritingMode();
LogicalSize availSpace = aState.ContentSize(wm);
ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput, frame,
availSpace);
if (treatWithClearance) {
aState.mBCoord += aState.mPrevBEndMargin.Get();
aState.mPrevBEndMargin.Zero();
}
// Now compute the collapsed margin-block-start value into // aState.mPrevBEndMargin, assuming that all child margins // collapse down to clearanceFrame.
brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
clearanceFrame, &mayNeedRetry);
// XXX optimization; we could check the collapsing children to see if they // are sure to require clearance, and so avoid retrying them
if (clearanceFrame) { // Don't allow retries on the second pass. The clearance decisions for // the blocks whose block-start margins collapse with ours are now // fixed.
mayNeedRetry = false;
}
if (!treatWithClearance && !clearanceFrame && mightClearFloats) { // We don't know if we need clearance and this is the first, // optimistic pass. So determine whether *this block* needs // clearance. Note that we do not allow the decision for whether // this block has clearance to change on the second pass; that // decision is only allowed to be made under the optimistic // first pass.
nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.Get(); if (auto [clearBCoord, result] =
aState.ClearFloats(curBCoord, clearType, floatAvoidingBlock);
result != ClearFloatsResult::BCoordNoChange) {
(void)clearBCoord;
// Looks like we need clearance and we didn't know about it already. // So recompute collapsed margin
treatWithClearance = true; // Remember this decision, needed for incremental reflow
aLine->SetHasClearance();
// Compute the collapsed margin again, ignoring the incoming margin // this time
mayNeedRetry = false;
brc.ComputeCollapsedBStartMargin(reflowInput, &aState.mPrevBEndMargin,
clearanceFrame, &mayNeedRetry);
}
}
// Temporarily advance the running block-direction value so that the // GetFloatAvailableSpace method will return the right available space. // This undone as soon as the horizontal margins are computed.
bStartMargin = aState.mPrevBEndMargin.Get();
if (treatWithClearance) {
nscoord currentBCoord = aState.mBCoord; // advance mBCoord to the clear position. auto [clearBCoord, result] =
aState.ClearFloats(aState.mBCoord, clearType, floatAvoidingBlock);
aState.mBCoord = clearBCoord;
clearedFloats = result != ClearFloatsResult::BCoordNoChange;
clearedPushedOrSplitFloat =
result == ClearFloatsResult::FloatsPushedOrSplit;
// Compute clearance. It's the amount we need to add to the block-start // border-edge of the frame, after applying collapsed margins // from the frame and its children, to get it to line up with // the block-end of the floats. The former is // currentBCoord + bStartMargin, the latter is the current // aState.mBCoord. // Note that negative clearance is possible
clearance = aState.mBCoord - (currentBCoord + bStartMargin);
// Add clearance to our block-start margin while we compute available // space for the frame
bStartMargin += clearance;
// Note that aState.mBCoord should stay where it is: at the block-start // border-edge of the frame
} else { // Advance aState.mBCoord to the block-start border-edge of the frame.
aState.mBCoord += bStartMargin;
}
}
aLine->SetLineIsImpactedByFloat(false);
// Here aState.mBCoord is the block-start border-edge of the block. // Compute the available space for the block
nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(cbWM);
WritingMode wm = aState.mReflowInput.GetWritingMode();
LogicalRect availSpace = aState.ComputeBlockAvailSpace(
frame, floatAvailableSpace, (floatAvoidingBlock));
// The check for // (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) // is to some degree out of paranoia: if we reliably eat up block-start // margins at the top of the page as we ought to, it wouldn't be // needed. if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
(availSpace.BSize(wm) < 0 || clearedPushedOrSplitFloat)) { // We know already that this child block won't fit on this // page/column due to the block-start margin or the clearance. So we // need to get out of here now. (If we don't, most blocks will handle // things fine, and report break-before, but zero-height blocks // won't, and will thus make their parent overly-large and force // *it* to be pushed in its entirety.)
aState.mBCoord = startingBCoord;
aState.mPrevBEndMargin = incomingMargin; if (ShouldAvoidBreakInside(aState.mReflowInput)) {
SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
} else {
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
} return;
}
// Now put the block-dir coordinate back to the start of the // block-start-margin + clearance.
aState.mBCoord -= bStartMargin;
availSpace.BStart(wm) -= bStartMargin; if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
availSpace.BSize(wm) += bStartMargin;
}
// Construct the reflow input for the block.
Maybe<ReflowInput> childReflowInput;
Maybe<LogicalSize> cbSize;
LogicalSize availSize = availSpace.Size(wm); bool columnSetWrapperHasNoBSizeLeft = false; if (Style()->GetPseudoType() == PseudoStyleType::MozColumnContent) { // Calculate the multicol containing block's block size so that the // children with percentage block size get correct percentage basis. const ReflowInput* cbReflowInput =
aState.mReflowInput.mParentReflowInput->mCBReflowInput;
MOZ_ASSERT(cbReflowInput->mFrame->StyleColumn()->IsColumnContainerStyle(), "Get unexpected reflow input of multicol containing block!");
// Use column-width as the containing block's inline-size, i.e. the column // content's computed inline-size.
cbSize.emplace(LogicalSize(wm, aState.mReflowInput.ComputedISize(),
cbReflowInput->ComputedBSize())
.ConvertTo(frame->GetWritingMode(), wm));
// If a ColumnSetWrapper is in a balancing column content, it may be // pushed or pulled back and forth between column contents. Always add // NS_FRAME_HAS_DIRTY_CHILDREN bit to it so that its ColumnSet children // can have a chance to reflow under current block size constraint. if (aState.mReflowInput.mFlags.mIsColumnBalancing &&
frame->IsColumnSetWrapperFrame()) {
frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
} elseif (IsColumnSetWrapperFrame()) { // If we are reflowing our ColumnSet children, we want to apply our block // size constraint to the available block size when constructing reflow // input for ColumnSet so that ColumnSet can use it to compute its max // column block size. if (frame->IsColumnSetFrame()) {
nscoord contentBSize = aState.mReflowInput.ComputedBSize(); if (aState.mReflowInput.ComputedMaxBSize() != NS_UNCONSTRAINEDSIZE) {
contentBSize =
std::min(contentBSize, aState.mReflowInput.ComputedMaxBSize());
} if (contentBSize != NS_UNCONSTRAINEDSIZE) { // To get the remaining content block-size, subtract the content // block-size consumed by our previous continuations.
contentBSize -= aState.mConsumedBSize;
// ColumnSet is not the outermost frame in the column container, so it // cannot have any margin. We don't need to consider any margin that // can be generated by "box-decoration-break: clone" as we do in // BlockReflowState::ComputeBlockAvailSpace(). const nscoord availContentBSize = std::max( 0, contentBSize - (aState.mBCoord - aState.ContentBStart())); if (availSize.BSize(wm) >= availContentBSize) {
availSize.BSize(wm) = availContentBSize;
columnSetWrapperHasNoBSizeLeft = true;
}
}
}
}
if (aLine->MovedFragments()) { // We only need to set this the first reflow, since if we reflow // again (and replace childReflowInput) we'll be reflowing it // again in the same fragment as the previous time.
childReflowInput->mFlags.mMovedBlockFragments = true;
}
nsFloatManager::SavedState floatManagerState;
nsReflowStatus frameReflowStatus; do { if (floatAvailableSpace.HasFloats()) { // Set if floatAvailableSpace.HasFloats() is true for any // iteration of the loop.
aLine->SetLineIsImpactedByFloat(true);
}
// We might need to store into mDiscoveredClearance later if it's // currently null; we want to overwrite any writes that // brc.ReflowBlock() below does, so we need to remember now // whether it's empty. constbool shouldStoreClearance =
aState.mReflowInput.mDiscoveredClearance &&
!*aState.mReflowInput.mDiscoveredClearance;
// Reflow the block into the available space if (mayNeedRetry || floatAvoidingBlock) {
aState.FloatManager()->PushState(&floatManagerState);
}
if (frameReflowStatus.IsInlineBreakBefore()) { // No need to retry this loop if there is a break opportunity before the // child block. break;
}
// Now the block has a height. Using that height, get the // available space again and call ComputeBlockAvailSpace again. // If ComputeBlockAvailSpace gives a different result, we need to // reflow again. if (!floatAvoidingBlock) { break;
}
LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
cbWM, aState.mBCoord + bStartMargin, brc.GetMetrics().BSize(wm),
&floatManagerState);
NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
oldFloatAvailableSpaceRect.BStart(wm), "yikes"); // Restore the height to the position of the next band.
floatAvailableSpace.mRect.BSize(wm) =
oldFloatAvailableSpaceRect.BSize(wm); // Determine whether the available space shrunk on either side, // because (the first time round) we now know the block's height, // and it may intersect additional floats, or (on later // iterations) because narrowing the width relative to the // previous time may cause the block to become taller. Note that // since we're reflowing the block, narrowing the width might also // make it shorter, so we must pass aCanGrow as true. if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
floatAvailableSpace.mRect, true)) { // The size and position we chose before are fine (i.e., they // don't cause intersecting with floats that requires a change // in size or position), so we're done. break;
}
bool advanced = false; if (!aState.FloatAvoidingBlockFitsInAvailSpace(floatAvoidingBlock,
floatAvailableSpace)) { // Advance to the next band.
nscoord newBCoord = aState.mBCoord; if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
advanced = true;
} // ClearFloats might be able to advance us further once we're there.
std::tie(aState.mBCoord, std::ignore) =
aState.ClearFloats(newBCoord, UsedClear::None, floatAvoidingBlock);
// Start over with a new available space rect at the new height.
floatAvailableSpace = aState.GetFloatAvailableSpaceWithState(
cbWM, aState.mBCoord, ShapeType::ShapeOutside, &floatManagerState);
}
if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) { break;
}
// We need another reflow.
aState.FloatManager()->PopState(&floatManagerState);
if (!treatWithClearance && !applyBStartMargin &&
aState.mReflowInput.mDiscoveredClearance) { // We set shouldStoreClearance above to record only the first // frame that requires clearance. if (shouldStoreClearance) {
*aState.mReflowInput.mDiscoveredClearance = frame;
}
aState.mPrevChild = frame; // Exactly what we do now is flexible since we'll definitely be // reflowed. return;
}
if (advanced) { // We're pushing down the border-box, so we don't apply margin anymore. // This should never cause us to move up since the call to // GetFloatAvailableSpaceForBSize above included the margin.
applyBStartMargin = false;
bStartMargin = 0;
treatWithClearance = true; // avoid hitting test above
clearance = 0;
}
childReflowInput.reset();
childReflowInput.emplace(
aState.mPresContext, aState.mReflowInput, frame,
availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
} while (true);
if (mayNeedRetry && clearanceFrame) { // Found a clearance frame, so we need to reflow |frame| a second time. // Restore the states and start over again.
aState.FloatManager()->PopState(&floatManagerState);
aState.mBCoord = startingBCoord;
aState.mPrevBEndMargin = incomingMargin; continue;
}
aState.mPrevChild = frame;
if (childReflowInput->WillReflowAgainForClearance()) { return;
}
if (frameReflowStatus.IsInlineBreakBefore()) { // None of the child block fits. if (ShouldAvoidBreakInside(aState.mReflowInput)) {
SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
} else {
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
}
} else { // Note: line-break-after a block is a nop
// Try to place the child block. // Don't force the block to fit if we have positive clearance, because // pushing it to the next page would give it more room. // Don't force the block to fit if it's impacted by a float. If it is, // then pushing it to the next page would give it more room. Note that // isImpacted doesn't include impact from the block's own floats. bool forceFit = aState.IsAdjacentWithBStart() && clearance <= 0 &&
!floatAvailableSpace.HasFloats();
CollapsingMargin collapsedBEndMargin;
OverflowAreas overflowAreas;
*aKeepReflowGoing =
brc.PlaceBlock(*childReflowInput, forceFit, aLine.get(),
collapsedBEndMargin, overflowAreas, frameReflowStatus); if (!frameReflowStatus.IsFullyComplete() &&
ShouldAvoidBreakInside(aState.mReflowInput)) {
*aKeepReflowGoing = false;
aLine->MarkDirty();
}
if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
LineIterator nextLine = aLine;
++nextLine; if (nextLine != LinesEnd()) {
nextLine->MarkPreviousMarginDirty();
}
}
if (Style()->GetPseudoType() == PseudoStyleType::MozScrolledContent) { auto lineFrameBounds = GetLineFrameInFlowBounds(*aLine, *frame);
MOZ_ASSERT(aLine->GetChildCount() == 1, "More than one child in block line?"); // Inline-line (i.e. Multiple frames in one line) handled in one of // other callsites.
aLine->SetInFlowChildBounds(lineFrameBounds);
}
aLine->SetOverflowAreas(overflowAreas); if (*aKeepReflowGoing) { // Some of the child block fit
// Advance to new Y position
nscoord newBCoord = aLine->BEnd();
aState.mBCoord = newBCoord;
// Continue the block frame now if it didn't completely fit in // the available space. if (!frameReflowStatus.IsFullyComplete()) { bool madeContinuation = CreateContinuationFor(aState, nullptr, frame);
nsIFrame* nextFrame = frame->GetNextInFlow();
NS_ASSERTION(nextFrame, "We're supposed to have a next-in-flow by now");
if (frameReflowStatus.IsIncomplete()) { // If nextFrame used to be an overflow container, make it a normal // block if (!madeContinuation &&
nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
nsOverflowContinuationTracker::AutoFinish fini(
aState.mOverflowTracker, frame);
nsContainerFrame* parent = nextFrame->GetParent();
parent->StealFrame(nextFrame); if (parent != this) {
ReparentFrame(nextFrame, parent, this);
}
mFrames.InsertFrame(nullptr, frame, nextFrame);
madeContinuation = true; // needs to be added to mLines
nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
frameReflowStatus.SetNextInFlowNeedsReflow();
}
// Push continuation to a new line, but only if we actually made // one. if (madeContinuation) {
nsLineBox* line = NewLineBox(nextFrame, true);
mLines.after_insert(aLine, line);
}
// If we need to reflow the continuation of the block child, // then we'd better reflow our continuation if (frameReflowStatus.NextInFlowNeedsReflow()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow(); // We also need to make that continuation's line dirty so // it gets reflowed when we reflow our next in flow. The // nif's line must always be either a line of the nif's // parent block (only if we didn't make a continuation) or // else one of our own overflow lines. In the latter case // the line is already marked dirty, so just handle the // first case. if (!madeContinuation) {
nsBlockFrame* nifBlock = do_QueryFrame(nextFrame->GetParent());
NS_ASSERTION(
nifBlock, "A block's child's next in flow's parent must be a block!"); for (auto& line : nifBlock->Lines()) { if (line.Contains(nextFrame)) {
line.MarkDirty(); break;
}
}
}
}
// The block-end margin for a block is only applied on the last // flow block. Since we just continued the child block frame, // we know that line->mFirstChild is not the last flow block // therefore zero out the running margin value. #ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": reflow incomplete, frame=");
frame->ListTag(stdout);
printf(" prevBEndMargin=%d, setting to zero\n",
aState.mPrevBEndMargin.get()); #endif
aState.mPrevBEndMargin.Zero();
} else { // frame is complete but its overflow is not complete // Disconnect the next-in-flow and put it in our overflow tracker if (!madeContinuation &&
!nextFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) { // It already exists, but as a normal next-in-flow, so we need // to dig it out of the child lists.
nextFrame->GetParent()->StealFrame(nextFrame);
} elseif (madeContinuation) {
mFrames.RemoveFrame(nextFrame);
}
// Put it in our overflow list
aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
#ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": reflow complete but overflow incomplete for ");
frame->ListTag(stdout);
printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); #endif
aState.mPrevBEndMargin = collapsedBEndMargin;
}
} else { // frame is fully complete #ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": reflow complete for ");
frame->ListTag(stdout);
printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
aState.mPrevBEndMargin.get(), collapsedBEndMargin.get()); #endif
aState.mPrevBEndMargin = collapsedBEndMargin;
} #ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": frame=");
frame->ListTag(stdout);
printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
aState.mPrevBEndMargin.get()); #endif
} else { if (!frameReflowStatus.IsFullyComplete()) { // The frame reported an incomplete status, but then it also didn't // fit. This means we need to reflow it again so that it can // (again) report the incomplete status.
frame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
if ((aLine == mLines.front() && !GetPrevInFlow()) ||
ShouldAvoidBreakInside(aState.mReflowInput)) { // If it's our very first line *or* we're not at the top of the page // and we have page-break-inside:avoid, then we need to be pushed to // our parent's next-in-flow.
SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
} else { // Push the line that didn't fit and any lines that follow it // to our next-in-flow.
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
}
}
} break; // out of the reflow retry loop
}
#ifdef DEBUG
VerifyLines(true); #endif
}
// Returns true if an overflow-wrap break was used. bool nsBlockFrame::ReflowInlineFrames(BlockReflowState& aState,
LineIterator aLine, bool* aKeepReflowGoing) {
*aKeepReflowGoing = true; bool usedOverflowWrap = false;
aLine->SetLineIsImpactedByFloat(false);
// Setup initial coordinate system for reflowing the inline frames // into. Apply a previous block frame's block-end margin first. if (ShouldApplyBStartMargin(aState, aLine)) {
aState.mBCoord += aState.mPrevBEndMargin.Get();
}
nsFlowAreaRect floatAvailableSpace =
aState.GetFloatAvailableSpace(GetWritingMode());
// Once upon a time we allocated the first 30 nsLineLayout objects // on the stack, and then we switched to the heap. At that time // these objects were large (1100 bytes on a 32 bit system). // Then the nsLineLayout object was shrunk to 156 bytes by // removing some internal buffers. Given that it is so much // smaller, the complexity of 2 different ways of allocating // no longer makes sense. Now we always allocate on the stack.
nsLineLayout lineLayout(aState.mPresContext, aState.FloatManager(),
aState.mReflowInput, &aLine, nullptr);
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); if (forceBreakInFrame) {
lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
}
DoReflowInlineFrames(aState, lineLayout, aLine, floatAvailableSpace,
availableSpaceBSize, &floatManagerState,
aKeepReflowGoing, &lineReflowStatus, allowPullUp);
usedOverflowWrap = lineLayout.EndLineReflow();
if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
LineReflowStatus::RedoNextBand == lineReflowStatus) { if (lineLayout.NeedsBackup()) {
NS_ASSERTION(!forceBreakInFrame, "Backing up twice; this should never be necessary"); // If there is no saved break position, then this will set // set forceBreakInFrame to null and we won't back up, which is // correct.
forceBreakInFrame = lineLayout.GetLastOptionalBreakPosition(
&forceBreakOffset, &forceBreakPriority);
} else {
forceBreakInFrame = nullptr;
} // restore the float manager state
aState.FloatManager()->PopState(&floatManagerState); // Clear out float lists
aState.mCurrentLineFloats.Clear();
aState.mBelowCurrentLineFloats.Clear();
aState.mNoWrapFloats.Clear();
}
// Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
allowPullUp = false;
} while (LineReflowStatus::RedoNoPull == lineReflowStatus);
} while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
} while (LineReflowStatus::RedoNextBand == lineReflowStatus);
return usedOverflowWrap;
}
void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState,
LineIterator aLine, bool* aKeepReflowGoing) {
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset(); // Reflow the line again when we reflow at our new position.
aLine->MarkDirty();
*aKeepReflowGoing = false;
}
if (aComputeNewPageName == ComputeNewPageNameIfNeeded::Yes) { // mCanHaveClassABreakpoints can only be true during paginated reflow, and // we expect this function to only be called when the available bsize is // constrained. const WritingMode wm = GetWritingMode(); constbool canBreakForPageNames =
aState.mReflowInput.mFlags.mCanHaveClassABreakpoints &&
!PresShell()->GetRootFrame()->GetWritingMode().IsOrthogonalTo(wm); if (canBreakForPageNames) {
PresShell()->FrameConstructor()->MaybeSetNextPageContentFramePageName(
aLine->mFirstChild);
}
}
aState.mReflowStatus.SetIncomplete();
}
void nsBlockFrame::DoReflowInlineFrames(
BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine,
nsFlowAreaRect& aFloatAvailableSpace, nscoord& aAvailableSpaceBSize,
nsFloatManager::SavedState* aFloatStateBeforeLine, bool* aKeepReflowGoing,
LineReflowStatus* aLineReflowStatus, bool aAllowPullUp) { // Forget all of the floats on the line
aLine->ClearFloats();
aState.mFloatOverflowAreas.Clear();
// We need to set this flag on the line if any of our reflow passes // are impacted by floats. if (aFloatAvailableSpace.HasFloats()) {
aLine->SetLineIsImpactedByFloat(true);
} #ifdef REALLY_NOISY_REFLOW
printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", this,
aFloatAvailableSpace.HasFloats()); #endif
// Make sure to enable resize optimization before we call BeginLineReflow // because it might get disabled there
aLine->EnableResizeReflowOptimization();
// XXX Unfortunately we need to know this before reflowing the first // inline frame in the line. FIX ME. if (0 == aLineLayout.GetLineNumber() &&
HasAllStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD |
NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
aLineLayout.SetFirstLetterStyleOK(true);
}
NS_ASSERTION(!(HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD) &&
GetPrevContinuation()), "first letter child bit should only be on first continuation");
// Reflow the frames that are already on the line first
LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
int32_t i;
nsIFrame* frame = aLine->mFirstChild;
if (aFloatAvailableSpace.HasFloats()) { // There is a soft break opportunity at the start of the line, because // we can always move this line down below float(s). if (aLineLayout.NotifyOptionalBreakPosition(
frame, 0, true, gfxBreakPriority::eNormalBreak)) {
lineReflowStatus = LineReflowStatus::RedoNextBand;
}
}
// need to repeatedly call GetChildCount here, because the child // count can change during the loop! for (i = 0;
LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
i++, frame = frame->GetNextSibling()) {
SetLineCursorForDisplay(aLine);
ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); if (LineReflowStatus::OK != lineReflowStatus) { // It is possible that one or more of next lines are empty // (because of DeleteNextInFlowChild). If so, delete them now // in case we are finished.
++aLine; while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) { // XXX Is this still necessary now that DeleteNextInFlowChild // uses DoRemoveFrame?
nsLineBox* toremove = aLine;
aLine = mLines.erase(aLine);
NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
FreeLineBox(toremove);
ClearLineCursors();
}
--aLine;
NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated, "ReflowInlineFrame should never determine that a line " "needs to go to the next page/column");
}
}
// Don't pull up new frames into lines with continuation placeholders if (aAllowPullUp) { // Pull frames and reflow them until we can't while (LineReflowStatus::OK == lineReflowStatus) {
frame = PullFrame(aState, aLine); if (!frame) { break;
}
while (LineReflowStatus::OK == lineReflowStatus) {
int32_t oldCount = aLine->GetChildCount();
SetLineCursorForDisplay(aLine);
ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus); if (aLine->GetChildCount() != oldCount) { // We just created a continuation for aFrame AND its going // to end up on this line (e.g. :first-letter // situation). Therefore we have to loop here before trying // to pull another frame.
frame = frame->GetNextSibling();
} else { break;
}
}
}
}
ClearLineCursors();
// We only need to backup if the line isn't going to be reflowed again anyway bool needsBackup = aLineLayout.NeedsBackup() &&
(lineReflowStatus == LineReflowStatus::Stop ||
lineReflowStatus == LineReflowStatus::OK); if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
NS_WARNING( "We shouldn't be backing up more than once! " "Someone must have set a break opportunity beyond the available width, " "even though there were better break opportunities before it");
needsBackup = false;
} if (needsBackup) { // We need to try backing up to before a text run // XXX It's possible, in fact not unusual, for the break opportunity to // already be the end of the line. We should detect that and optimize to not // re-do the line. if (aLineLayout.HasOptionalBreakPosition()) { // We can back up!
lineReflowStatus = LineReflowStatus::RedoNoPull;
}
} else { // In case we reflow this line again, remember that we don't // need to force any breaking
aLineLayout.ClearOptionalBreakPosition();
}
if (LineReflowStatus::RedoNextBand == lineReflowStatus) { // This happens only when we have a line that is impacted by // floats and the first element in the line doesn't fit with // the floats. // // If there's block space available, we either try to reflow the line // past the current band (if it's non-zero and the band definitely won't // widen around a shape-outside), otherwise we try one pixel down. If // there's no block space available, we push the line to the next // page/column.
NS_ASSERTION(
NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.BSize(outerWM), "unconstrained block size on totally empty line");
// See the analogous code for blocks in BlockReflowState::ClearFloats.
nscoord bandBSize = aFloatAvailableSpace.mRect.BSize(outerWM); if (bandBSize > 0 ||
NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
NS_ASSERTION(bandBSize == 0 || aFloatAvailableSpace.HasFloats(), "redo line on totally empty line with non-empty band..."); // We should never hit this case if we've placed floats on the // line; if we have, then the GetFloatAvailableSpace call is wrong // and needs to happen after the caller pops the float manager // state.
aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
if (!aFloatAvailableSpace.MayWiden() && bandBSize > 0) { // Move it down far enough to clear the current band.
aState.mBCoord += bandBSize;
} else { // Move it down by one dev pixel.
aState.mBCoord += aState.mPresContext->DevPixelsToAppUnits(1);
}
aFloatAvailableSpace = aState.GetFloatAvailableSpace(GetWritingMode());
} else { // There's nowhere to retry placing the line, so we want to push // it to the next page/column where its contents can fit not // next to a float.
lineReflowStatus = LineReflowStatus::Truncated;
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
}
// XXX: a small optimization can be done here when paginating: // if the new Y coordinate is past the end of the block then // push the line and return now instead of later on after we are // past the float.
} elseif (LineReflowStatus::Truncated != lineReflowStatus &&
LineReflowStatus::RedoNoPull != lineReflowStatus) { // If we are propagating out a break-before status then there is // no point in placing the line. if (!aState.mReflowStatus.IsInlineBreakBefore()) { if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
aFloatAvailableSpace, aAvailableSpaceBSize,
aKeepReflowGoing)) {
lineReflowStatus = LineReflowStatus::RedoMoreFloats; // PlaceLine already called GetFloatAvailableSpaceForBSize or its // variant for us.
}
}
} #ifdef DEBUG if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
fmt::println("LineReflowStatus={}", ToString(lineReflowStatus));
} #endif
if (aLineLayout.GetDirtyNextLine()) { // aLine may have been pushed to the overflow lines.
FrameLines* overflowLines = GetOverflowLines(); // We can't just compare iterators front() to aLine here, since they may be // in different lists. bool pushedToOverflowLines =
overflowLines && overflowLines->mLines.front() == aLine.get(); if (pushedToOverflowLines) { // aLine is stale, it's associated with the main line list but it should // be associated with the overflow line list now
aLine = overflowLines->mLines.begin();
}
nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines); if (iter.Next() && iter.GetLine()->IsInline()) {
iter.GetLine()->MarkDirty(); if (iter.GetContainer() != this) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
}
}
}
// Process the child frames reflow status. There are 5 cases: // complete, not-complete, break-before, break-after-complete, // break-after-not-complete. There are two situations: we are a // block or we are an inline. This makes a total of 10 cases // (fortunately, there is some overlap).
aLine->ClearForcedLineBreak(); if (frameReflowStatus.IsInlineBreak() ||
aState.mTrailingClearFromPIF != UsedClear::None) { // Always abort the line reflow (because a line break is the // minimal amount of break we do).
*aLineReflowStatus = LineReflowStatus::Stop;
// XXX what should aLine's break-type be set to in all these cases? if (frameReflowStatus.IsInlineBreakBefore()) { // Break-before cases. if (aFrame == aLine->mFirstChild) { // If we break before the first frame on the line then we must // be trying to place content where there's no room (e.g. on a // line with wide floats). Inform the caller to reflow the // line after skipping past a float.
*aLineReflowStatus = LineReflowStatus::RedoNextBand;
} else { // It's not the first child on this line so go ahead and split // the line. We will see the frame again on the next-line.
SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
// If we're splitting the line because the frame didn't fit and it // was pushed, then mark the line as having word wrapped. We need to // know that if we're shrink wrapping our width if (pushedFrame) {
aLine->SetLineWrapped(true);
}
}
} else {
MOZ_ASSERT(frameReflowStatus.IsInlineBreakAfter() ||
aState.mTrailingClearFromPIF != UsedClear::None, "We should've handled inline break-before in the if-branch!");
// If a float split and its prev-in-flow was followed by a <BR>, then // combine the <BR>'s float clear type with the inline's float clear type // (the inline will be the very next frame after the split float).
UsedClear clearType = frameReflowStatus.FloatClearType(); if (aState.mTrailingClearFromPIF != UsedClear::None) {
clearType = nsLayoutUtils::CombineClearType(
clearType, aState.mTrailingClearFromPIF);
aState.mTrailingClearFromPIF = UsedClear::None;
} // Break-after cases if (clearType != UsedClear::None || aLineLayout.GetLineEndsInBR()) {
aLine->SetForcedLineBreakAfter(clearType);
} if (frameReflowStatus.IsComplete()) { // Split line, but after the frame just reflowed
SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
aLineReflowStatus);
if (frameReflowStatus.IsInlineBreakAfter() &&
!aLineLayout.GetLineEndsInBR()) {
aLineLayout.SetDirtyNextLine();
}
}
}
}
if (!frameReflowStatus.IsFullyComplete()) { // Create a continuation for the incomplete frame. Note that the // frame may already have a continuation.
CreateContinuationFor(aState, aLine, aFrame);
// Remember that the line has wrapped if (!aLineLayout.GetLineEndsInBR()) {
aLine->SetLineWrapped(true);
}
// If we just ended a first-letter frame or reflowed a placeholder then // don't split the line and don't stop the line reflow... // But if we are going to stop anyways we'd better split the line. if ((!frameReflowStatus.FirstLetterComplete() &&
!aFrame->IsPlaceholderFrame()) ||
*aLineReflowStatus == LineReflowStatus::Stop) { // Split line after the current frame
*aLineReflowStatus = LineReflowStatus::Stop;
SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(),
aLineReflowStatus);
}
}
}
staticbool CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine,
nsIFrame* aFloat) { if (!aFloat) { returntrue;
}
NS_ASSERTION(!aFloat->GetPrevContinuation(), "float in a line should never be a continuation");
NS_ASSERTION(!aFloat->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), "float in a line should never be a pushed float");
nsIFrame* ph = aFloat->FirstInFlow()->GetPlaceholderFrame(); for (nsIFrame* f = ph; f; f = f->GetParent()) { if (f->GetParent() == aBlock) { return aLine->Contains(f);
}
}
NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!"); returntrue;
}
#ifdef DEBUG if (gNoisyReflow) {
nsIFrame::IndentBy(stdout, gNoiseIndent);
printf("split line: from line=%p pushCount=%d aFrame=", static_cast<void*>(aLine.get()), pushCount); if (aFrame) {
aFrame->ListTag(stdout);
} else {
printf("(null)");
}
printf("\n"); if (gReallyNoisyReflow) {
aLine->List(stdout, gNoiseIndent + 1);
}
} #endif
if (0 != pushCount) {
MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
MOZ_ASSERT(nullptr != aFrame, "whoops"); #ifdef DEBUG
{
nsIFrame* f = aFrame;
int32_t count = pushCount; while (f && count > 0) {
f = f->GetNextSibling();
--count;
}
NS_ASSERTION(count == 0, "Not enough frames to push");
} #endif
// Put frames being split out into their own line
nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
mLines.after_insert(aLine, newLine); #ifdef DEBUG if (gReallyNoisyReflow) {
newLine->List(stdout, gNoiseIndent + 1);
} #endif
// Let line layout know that some frames are no longer part of its // state.
aLineLayout.SplitLineTo(aLine->GetChildCount());
// If floats have been placed whose placeholders have been pushed to the new // line, we need to reflow the old line again. We don't want to look at the // frames in the new line, because as a large paragraph is laid out the // we'd get O(N^2) performance. So instead we just check that the last // float and the last below-current-line float are still in aLine. if (!CheckPlaceholderInLine( this, aLine,
aLine->HasFloats() ? aLine->Floats().LastElement() : nullptr) ||
!CheckPlaceholderInLine( this, aLine,
aState.mBelowCurrentLineFloats.SafeLastElement(nullptr))) {
*aLineReflowStatus = LineReflowStatus::RedoNoPull;
}
#ifdef DEBUG
VerifyLines(true); #endif
}
}
bool nsBlockFrame::IsLastLine(BlockReflowState& aState, LineIterator aLine) { while (++aLine != LinesEnd()) { // There is another line if (0 != aLine->GetChildCount()) { // If the next line is a block line then this line is the last in a // group of inline lines. return aLine->IsBlock();
} // The next line is empty, try the next one
}
// Try our next-in-flows lines to answer the question
nsBlockFrame* nextInFlow = (nsBlockFrame*)GetNextInFlow(); while (nullptr != nextInFlow) { for (constauto& line : nextInFlow->Lines()) { if (0 != line.GetChildCount()) { return line.IsBlock();
}
}
nextInFlow = (nsBlockFrame*)nextInFlow->GetNextInFlow();
}
// This is the last line - so don't allow justification returntrue;
}
bool nsBlockFrame::PlaceLine(BlockReflowState& aState,
nsLineLayout& aLineLayout, LineIterator aLine,
nsFloatManager::SavedState* aFloatStateBeforeLine,
nsFlowAreaRect& aFlowArea,
nscoord& aAvailableSpaceBSize, bool* aKeepReflowGoing) { // Try to position the floats in a nowrap context.
aLineLayout.FlushNoWrapFloats();
// Trim extra white-space from the line before placing the frames
aLineLayout.TrimTrailingWhiteSpace();
// Vertically align the frames on this line. // // According to the CSS2 spec, section 12.6.1, the "marker" box // participates in the height calculation of the list-item box's // first line box. // // There are exactly two places a ::marker can be placed: near the // first or second line. It's only placed on the second line in a // rare case: when the first line is empty.
WritingMode wm = aState.mReflowInput.GetWritingMode(); bool addedMarker = false;
nsIFrame* outsideMarker = GetOutsideMarker(); if (outsideMarker &&
((aLine == mLines.front() &&
(!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
(mLines.front() != mLines.back() && 0 == mLines.front()->BSize() &&
aLine == mLines.begin().next()))) {
ReflowOutput metrics(aState.mReflowInput);
ReflowOutsideMarker(outsideMarker, aState, metrics, aState.mBCoord);
NS_ASSERTION(!MarkerIsEmpty(outsideMarker) || metrics.BSize(wm) == 0, "empty ::marker frame took up space");
aLineLayout.AddMarkerFrame(outsideMarker, metrics);
addedMarker = true;
}
aLineLayout.VerticalAlignLine();
// We want to consider the floats in the current line when determining // whether the float available space is shrunk. If mLineBSize doesn't // exist, we are in the first pass trying to place the line. Calling // GetFloatAvailableSpace() like we did in BlockReflowState::AddFloat() // for UpdateBand().
// floatAvailableSpaceWithOldLineBSize is the float available space with // the old BSize, but including the floats that were added in this line.
LogicalRect floatAvailableSpaceWithOldLineBSize =
aState.mLineBSize.isNothing()
? aState.GetFloatAvailableSpace(wm, aLine->BStart()).mRect
: aState
.GetFloatAvailableSpaceForBSize(
wm, aLine->BStart(), aState.mLineBSize.value(), nullptr)
.mRect;
// As we redo for floats, we can't reduce the amount of BSize we're // checking.
aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
LogicalRect floatAvailableSpaceWithLineBSize =
aState
.GetFloatAvailableSpaceForBSize(wm, aLine->BStart(),
aAvailableSpaceBSize, nullptr)
.mRect;
// If the available space between the floats is smaller now that we // know the BSize, return false (and cause another pass with // LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize // never decreases, which means that we can't reduce the set of floats // we intersect, which means that the available space cannot grow. if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
floatAvailableSpaceWithLineBSize, false)) { // Prepare data for redoing the line.
aState.mLineBSize = Some(aLine->BSize());
// Since we want to redo the line, we update aFlowArea by using the // aFloatStateBeforeLine, which is the float manager's state before the // line is placed.
LogicalRect oldFloatAvailableSpace(aFlowArea.mRect);
aFlowArea = aState.GetFloatAvailableSpaceForBSize(
wm, aLine->BStart(), aAvailableSpaceBSize, aFloatStateBeforeLine);
NS_ASSERTION(
aFlowArea.mRect.BStart(wm) == oldFloatAvailableSpace.BStart(wm), "yikes"); // Restore the BSize to the position of the next band.
aFlowArea.mRect.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
// Enforce both IStart() and IEnd() never move outwards to prevent // infinite grow-shrink loops. const nscoord iStartDiff =
aFlowArea.mRect.IStart(wm) - oldFloatAvailableSpace.IStart(wm); const nscoord iEndDiff =
aFlowArea.mRect.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm); if (iStartDiff < 0) {
aFlowArea.mRect.IStart(wm) -= iStartDiff;
aFlowArea.mRect.ISize(wm) += iStartDiff;
} if (iEndDiff > 0) {
aFlowArea.mRect.ISize(wm) -= iEndDiff;
}
// Only block frames horizontally align their children because // inline frames "shrink-wrap" around their children (therefore // there is no extra horizontal space). const nsStyleText* styleText = StyleText();
// From here on, pfd->mBounds rectangles are incorrect because bidi // might have moved frames around!
OverflowAreas overflowAreas;
aLineLayout.RelativePositionFrames(overflowAreas); if (Style()->GetPseudoType() == PseudoStyleType::MozScrolledContent) {
Maybe<nsRect> inFlowBounds; for (nsIFrame* lineFrame : aLine->ChildFrames()) { auto lineFrameBounds = GetLineFrameInFlowBounds(*aLine, *lineFrame); if (!lineFrameBounds) { continue;
} if (inFlowBounds) {
*inFlowBounds = inFlowBounds->UnionEdges(*lineFrameBounds);
} else {
inFlowBounds = Some(*lineFrameBounds);
}
}
aLine->SetInFlowChildBounds(inFlowBounds);
}
aLine->SetOverflowAreas(overflowAreas); if (addedMarker) {
aLineLayout.RemoveMarkerFrame(GetOutsideMarker());
}
// Inline lines do not have margins themselves; however they are // impacted by prior block margins. If this line ends up having some // height then we zero out the previous block-end margin value that was // already applied to the line's starting Y coordinate. Otherwise we // leave it be so that the previous blocks block-end margin can be // collapsed with a block that follows.
nscoord newBCoord;
if (!aLine->CachedIsEmpty()) { // This line has some height. Therefore the application of the // previous-bottom-margin should stick.
aState.mPrevBEndMargin.Zero();
newBCoord = aLine->BEnd();
} else { // Don't let the previous-bottom-margin value affect the newBCoord // coordinate (it was applied in ReflowInlineFrames speculatively) // since the line is empty. // We already called |ShouldApplyBStartMargin|, and if we applied it // then mShouldApplyBStartMargin is set.
nscoord dy = aState.mFlags.mShouldApplyBStartMargin
? -aState.mPrevBEndMargin.Get()
: 0;
newBCoord = aState.mBCoord + dy;
}
// See if the line fit (our first line always does). if (mLines.front() != aLine &&
aState.ContentBSize() != NS_UNCONSTRAINEDSIZE &&
newBCoord > aState.ContentBEnd()) {
NS_ASSERTION(aState.mCurrentLine == aLine, "oops"); if (ShouldAvoidBreakInside(aState.mReflowInput)) { // All our content doesn't fit, start on the next page.
SetBreakBeforeStatusBeforeLine(aState, aLine, aKeepReflowGoing);
} else { // Push aLine and all of its children and anything else that // follows to our next-in-flow.
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
} returntrue;
}
// Note that any early return before this update of aState.mBCoord // must either (a) return false or (b) set aKeepReflowGoing to false. // Otherwise we'll keep reflowing later lines at an incorrect // position, and we might not come back and clean up the damage later.
aState.mBCoord = newBCoord;
// Add the already placed current-line floats to the line
aLine->AppendFloats(std::move(aState.mCurrentLineFloats));
// Any below current line floats to place? if (!aState.mBelowCurrentLineFloats.IsEmpty()) { // Reflow the below-current-line floats, which places on the line's // float list.
aState.PlaceBelowCurrentLineFloats(aLine);
}
// When a line has floats, factor them into the overflow areas computations. if (aLine->HasFloats()) { // Union the float overflow areas (stored in aState) and the value computed // by the line layout code.
OverflowAreas lineOverflowAreas = aState.mFloatOverflowAreas;
lineOverflowAreas.UnionWith(aLine->GetOverflowAreas());
aLine->SetOverflowAreas(lineOverflowAreas); if (Style()->GetPseudoType() == PseudoStyleType::MozScrolledContent) {
Span<const nsIFrame* const> floats(aLine->Floats()); // Guaranteed to have at least 1 element since `HasFloats()` is true. auto floatRect = GetNormalMarginRect(*floats[0]); for (const nsIFrame* f : floats.From(1)) {
floatRect = floatRect.UnionEdges(GetNormalMarginRect(*f));
} auto inFlowBounds = aLine->GetInFlowChildBounds();
aLine->SetInFlowChildBounds(
Some(inFlowBounds ? inFlowBounds->UnionEdges(floatRect) : floatRect));
}
// Apply break-after clearing if necessary // This must stay in sync with |ReflowDirtyLines|. if (aLine->HasFloatClearTypeAfter()) {
std::tie(aState.mBCoord, std::ignore) =
aState.ClearFloats(aState.mBCoord, aLine->FloatClearTypeAfter());
} returntrue;
}
void nsBlockFrame::PushLines(BlockReflowState& aState,
nsLineList::iterator aLineBefore) { // NOTE: aLineBefore is always a normal line, not an overflow line. // The following expression will assert otherwise.
DebugOnly<bool> check = aLineBefore == mLines.begin();
// PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh. bool firstLine = overBegin == LinesBegin();
if (overBegin != LinesEnd()) { // Remove floats in the lines from floats list.
nsFrameList floats;
CollectFloats(overBegin->mFirstChild, floats, true);
if (floats.NotEmpty()) { #ifdef DEBUG for (nsIFrame* f : floats) {
MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), "CollectFloats should've removed that bit");
} #endif // Push the floats onto the front of the overflow out-of-flows list
nsAutoOOFFrameList oofs(this);
oofs.mList.InsertFrames(nullptr, nullptr, std::move(floats));
}
// overflow lines can already exist in some cases, in particular, // when shrinkwrapping and we discover that the shrinkwap causes // the height of some child block to grow which creates additional // overflowing content. In such cases we must prepend the new // overflow to the existing overflow.
FrameLines* overflowLines = RemoveOverflowLines(); if (!overflowLines) { // XXXldb use presshell arena!
overflowLines = new FrameLines();
} if (overflowLines) {
nsIFrame* lineBeforeLastFrame; if (firstLine) {
lineBeforeLastFrame = nullptr; // removes all frames
} else {
nsIFrame* f = overBegin->mFirstChild;
lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(), "unexpected line frames");
}
nsFrameList pushedFrames = mFrames.TakeFramesAfter(lineBeforeLastFrame);
overflowLines->mFrames.InsertFrames(nullptr, nullptr,
std::move(pushedFrames));
overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
overBegin, LinesEnd());
NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty"); // this takes ownership but it won't delete it immediately so we // can keep using it.
SetOverflowLines(overflowLines);
// Mark all the overflow lines dirty so that they get reflowed when // they are pulled up by our next-in-flow.
// XXXldb Can this get called O(N) times making the whole thing O(N^2)? for (LineIterator line = overflowLines->mLines.begin(),
line_end = overflowLines->mLines.end();
line != line_end; ++line) { if (line == cursorForDisplay || line == cursorForQuery) {
ClearLineCursors();
}
line->MarkDirty();
line->MarkPreviousMarginDirty();
line->SetMovedFragments();
line->SetBoundsEmpty(); if (line->HasFloats()) {
line->ClearFloats();
}
}
}
}
#ifdef DEBUG
VerifyOverflowSituation(); #endif
}
// The overflowLines property is stored as a pointer to a line list, // which must be deleted. However, the following functions all maintain // the invariant that the property is never set if the list is empty.
// Steal the prev-in-flow's overflow lines and prepend them. bool didFindOverflow = false;
nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); if (prevBlock) {
prevBlock->ClearLineCursors();
FrameLines* overflowLines = prevBlock->RemoveOverflowLines(); if (overflowLines) { // Make all the frames on the overflow line list mine.
ReparentFrames(overflowLines->mFrames, prevBlock, this);
// Collect overflow containers from our OverflowContainers list that are // continuations from the frames we picked up from our prev-in-flow, then // prepend those to ExcessOverflowContainers to ensure the continuations // are ordered. if (GetOverflowContainers()) {
nsFrameList ocContinuations; for (auto* f : overflowLines->mFrames) { auto* cont = f; bool done = false; while (!done && (cont = cont->GetNextContinuation()) &&
cont->GetParent() == this) { bool onlyChild = !cont->GetPrevSibling() && !cont->GetNextSibling(); if (cont->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER) &&
TryRemoveFrame(OverflowContainersProperty(), cont)) {
ocContinuations.AppendFrame(nullptr, cont);
done = onlyChild; continue;
} break;
} if (done) { break;
}
} if (!ocContinuations.IsEmpty()) { if (nsFrameList* eoc = GetExcessOverflowContainers()) {
eoc->InsertFrames(nullptr, nullptr, std::move(ocContinuations));
} else {
SetExcessOverflowContainers(std::move(ocContinuations));
}
}
}
// Make the overflow out-of-flow frames mine too.
nsAutoOOFFrameList oofs(prevBlock); if (oofs.mList.NotEmpty()) { // In case we own any next-in-flows of any of the drained frames, then // move those to the PushedFloat list.
nsFrameList pushedFloats; for (nsIFrame* f : oofs.mList) {
nsIFrame* nif = f->GetNextInFlow(); for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
MOZ_ASSERT(nif->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW));
RemoveFloat(nif);
pushedFloats.AppendFrame(nullptr, nif);
}
}
ReparentFrames(oofs.mList, prevBlock, this);
EnsureFloats()->InsertFrames(nullptr, nullptr, std::move(oofs.mList)); if (!pushedFloats.IsEmpty()) {
nsFrameList* pf = EnsurePushedFloats();
pf->InsertFrames(nullptr, nullptr, std::move(pushedFloats));
}
}
if (!mLines.empty()) { // Remember to recompute the margins on the first line. This will // also recompute the correct deltaBCoord if necessary.
mLines.front()->MarkPreviousMarginDirty();
} // The overflow lines have already been marked dirty and their previous // margins marked dirty also.
// Prepend the overflow frames/lines to our principal list.
mFrames.InsertFrames(nullptr, nullptr, std::move(overflowLines->mFrames));
mLines.splice(mLines.begin(), overflowLines->mLines);
NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list"); delete overflowLines;
didFindOverflow = true;
}
}
// Now append our own overflow lines. return DrainSelfOverflowList() || didFindOverflow;
}
bool nsBlockFrame::DrainSelfOverflowList() {
UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines()); if (!ourOverflowLines) { returnfalse;
}
// No need to reparent frames in our own overflow lines/oofs, because they're // already ours. But we should put overflow floats back in our floats list. // (explicit scope to remove the OOF list before VerifyOverflowSituation)
{
nsAutoOOFFrameList oofs(this); if (oofs.mList.NotEmpty()) { #ifdef DEBUG for (nsIFrame* f : oofs.mList) {
MOZ_ASSERT(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), "CollectFloats should've removed that bit");
} #endif // The overflow floats go after our regular floats.
EnsureFloats()->AppendFrames(nullptr, std::move(oofs).mList);
}
} if (!ourOverflowLines->mLines.empty()) {
mFrames.AppendFrames(nullptr, std::move(ourOverflowLines->mFrames));
mLines.splice(mLines.end(), ourOverflowLines->mLines);
}
/** *Pushedfloatsarefloatswhoseplaceholdersareinaprevious *continuation.Theymightthemselvesbenext-continuationsofafloat *thatpartiallyfitinanearliercontinuation,ortheymightbethe *firstcontinuationofafloatthatcouldn'tbeplacedatall. * *Pushedfloatslivepermanentlyatthebeginningofablock'sfloat *list,wheretheymustlive*before*anyfloatswhoseplaceholdersare *inthatblock. * *Temporarily,duringreflow,theyalsoliveonthepushedfloatslist, *whichonlyholdsthembetween(a)whenonecontinuationpushesthemto *itspushedfloatslistbecausetheydon'tfitand(b)whenthenext *continuationpullsthemontothebeginningofitsfloatlist. * *DrainPushedFloatssetsuppushedfloatsthewayweneedthematthe *startofreflow;theyarethenreflowedbyReflowPushedFloats(which *mightpushsomeofthemon).Floatswithplaceholdersinthisblock *arereflowedby(BlockReflowState/nsLineLayout)::AddFloat,which *alsomaintainstheseinvariants. * *DrainSelfPushedFloatsmovesanypushedfloatsfromthisblock'sown *pushedfloatslistbackintofloatslist.DrainPushedFloatsadditionally *movesframesfromitsprev-in-flow'spushedfloatslistintofloatslist.
*/ void nsBlockFrame::DrainSelfPushedFloats() { // If we're getting reflowed multiple times without our // next-continuation being reflowed, we might need to pull back floats // that we just put in the list to be pushed to our next-in-flow. // We don't want to pull back any next-in-flows of floats on our own // float list, and we only need to pull back first-in-flows whose // placeholders were in earlier blocks (since first-in-flows whose // placeholders are in this block will get pulled appropriately by // AddFloat, and will then be more likely to be in the correct order).
mozilla::PresShell* presShell = PresShell();
nsFrameList* ourPushedFloats = GetPushedFloats(); if (ourPushedFloats) {
nsFrameList* floats = GetFloats();
// When we pull back floats, we want to put them with the pushed // floats, which must live at the start of our float list, but we // want them at the end of those pushed floats. // FIXME: This isn't quite right! What if they're all pushed floats?
nsIFrame* insertionPrevSibling = nullptr; /* beginning of list */ for (nsIFrame* f = floats ? floats->FirstChild() : nullptr;
f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
f = f->GetNextSibling()) {
insertionPrevSibling = f;
}
nsIFrame* f = ourPushedFloats->LastChild(); while (f) {
nsIFrame* prevSibling = f->GetPrevSibling();
nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
nsIFrame* floatOriginalParent =
presShell->FrameConstructor()->GetFloatContainingBlock(placeholder); if (floatOriginalParent != this) { // This is a first continuation that was pushed from one of our // previous continuations. Take it out of the pushed floats // list and put it in our floats list, before any of our // floats, but after other pushed floats.
ourPushedFloats->RemoveFrame(f); if (!floats) {
floats = EnsureFloats();
}
floats->InsertFrame(nullptr, insertionPrevSibling, f);
}
f = prevSibling;
}
if (ourPushedFloats->IsEmpty()) {
StealPushedFloats()->Delete(presShell);
}
}
}
// After our prev-in-flow has completed reflow, it may have a pushed // floats list, containing floats that we need to own. Take these.
nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow()); if (prevBlock) {
AutoFrameListPtr list(PresContext(), prevBlock->StealPushedFloats()); if (list && list->NotEmpty()) {
EnsureFloats()->InsertFrames(this, nullptr, std::move(*list));
}
}
}
nsBlockFrame::FrameLines* nsBlockFrame::GetOverflowLines() const { if (!HasOverflowLines()) { return nullptr;
}
FrameLines* prop = GetProperty(OverflowLinesProperty());
NS_ASSERTION(
prop && !prop->mLines.empty() &&
prop->mLines.front()->GetChildCount() == 0
? prop->mFrames.IsEmpty()
: prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), "value should always be stored and non-empty when state set"); return prop;
}
nsBlockFrame::FrameLines* nsBlockFrame::RemoveOverflowLines() { if (!HasOverflowLines()) { return nullptr;
}
FrameLines* prop = TakeProperty(OverflowLinesProperty());
NS_ASSERTION(
prop && !prop->mLines.empty() &&
prop->mLines.front()->GetChildCount() == 0
? prop->mFrames.IsEmpty()
: prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), "value should always be stored and non-empty when state set");
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); return prop;
}
void nsBlockFrame::DestroyOverflowLines() {
NS_ASSERTION(HasOverflowLines(), "huh?");
FrameLines* prop = TakeProperty(OverflowLinesProperty());
NS_ASSERTION(prop && prop->mLines.empty(), "value should always be stored but empty when destroying");
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); delete prop;
}
// This takes ownership of aOverflowLines. // XXX We should allocate overflowLines from presShell arena! void nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) {
NS_ASSERTION(aOverflowLines, "null lines");
NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
aOverflowLines->mFrames.FirstChild(), "invalid overflow lines / frames");
NS_ASSERTION(!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_LINES), "Overwriting existing overflow lines");
// Verify that we won't overwrite an existing overflow list
NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
SetProperty(OverflowLinesProperty(), aOverflowLines);
AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
}
nsFrameList* nsBlockFrame::GetOverflowOutOfFlows() const { if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { return nullptr;
}
nsFrameList* result = GetProperty(OverflowOutOfFlowsProperty());
NS_ASSERTION(result, "value should always be non-empty when state set"); return result;
}
void nsBlockFrame::SetOverflowOutOfFlows(nsFrameList&& aList,
nsFrameList* aPropValue) {
MOZ_ASSERT(
HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == !!aPropValue, "state does not match value");
if (aList.IsEmpty()) { if (!HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { return;
}
nsFrameList* list = TakeProperty(OverflowOutOfFlowsProperty());
NS_ASSERTION(aPropValue == list, "prop value mismatch");
list->Clear();
list->Delete(PresShell());
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
} elseif (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
NS_ASSERTION(aPropValue == GetProperty(OverflowOutOfFlowsProperty()), "prop value mismatch");
*aPropValue = std::move(aList);
} else {
SetProperty(OverflowOutOfFlowsProperty(), new (PresShell()) nsFrameList(std::move(aList)));
AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
}
nsIFrame* nsBlockFrame::GetOutsideMarker() const {
nsFrameList* list = GetOutsideMarkerList(); return list ? list->FirstChild() : nullptr;
}
nsFrameList* nsBlockFrame::GetOutsideMarkerList() const { if (!HasMarker()) { return nullptr;
} if (nsFrameList* list = GetProperty(OutsideMarkerProperty())) {
MOZ_ASSERT(list->GetLength() == 1, "bogus outside ::marker list"); return list;
} return nullptr;
}
bool nsBlockFrame::HasFloats() const { constbool isStateBitSet = HasAnyStateBits(NS_BLOCK_HAS_FLOATS);
MOZ_ASSERT(
isStateBitSet == HasProperty(FloatsProperty()), "State bit should accurately reflect presence/absence of the property!"); return isStateBitSet;
}
nsFrameList* nsBlockFrame::GetFloats() const { if (!HasFloats()) { return nullptr;
}
nsFrameList* list = GetProperty(FloatsProperty());
MOZ_ASSERT(list, "List should always be valid when the property is set!");
MOZ_ASSERT(list->NotEmpty(), "Someone forgot to delete the list when it is empty!"); return list;
}
nsFrameList* nsBlockFrame::EnsureFloats() {
nsFrameList* list = GetFloats(); if (list) { return list;
}
list = new (PresShell()) nsFrameList;
SetProperty(FloatsProperty(), list);
AddStateBits(NS_BLOCK_HAS_FLOATS); return list;
}
nsFrameList* nsBlockFrame::StealFloats() { if (!HasFloats()) { return nullptr;
}
nsFrameList* list = TakeProperty(FloatsProperty());
RemoveStateBits(NS_BLOCK_HAS_FLOATS);
MOZ_ASSERT(list, "List should always be valid when the property is set!"); return list;
}
bool nsBlockFrame::HasPushedFloats() const { constbool isStateBitSet = HasAnyStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
MOZ_ASSERT(
isStateBitSet == HasProperty(PushedFloatsProperty()), "State bit should accurately reflect presence/absence of the property!"); return isStateBitSet;
}
nsFrameList* nsBlockFrame::GetPushedFloats() const { if (!HasPushedFloats()) { return nullptr;
}
nsFrameList* list = GetProperty(PushedFloatsProperty());
MOZ_ASSERT(list, "List should always be valid when the property is set!");
MOZ_ASSERT(list->NotEmpty(), "Someone forgot to delete the list when it is empty!"); return list;
}
nsFrameList* nsBlockFrame::EnsurePushedFloats() {
nsFrameList* result = GetPushedFloats(); if (result) { return result;
}
result = new (PresShell()) nsFrameList;
SetProperty(PushedFloatsProperty(), result);
AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
return result;
}
nsFrameList* nsBlockFrame::StealPushedFloats() { if (!HasPushedFloats()) { return nullptr;
}
nsFrameList* list = TakeProperty(PushedFloatsProperty());
RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
MOZ_ASSERT(list, "List should always be valid when the property is set!"); return list;
}
////////////////////////////////////////////////////////////////////// // Frame list manipulation routines
void nsBlockFrame::AppendFrames(ChildListID aListID, nsFrameList&& aFrameList) { if (aFrameList.IsEmpty()) { return;
} if (aListID != FrameChildListID::Principal) { if (FrameChildListID::Float == aListID) {
DrainSelfPushedFloats(); // ensure the last frame is in floats list.
EnsureFloats()->AppendFrames(nullptr, std::move(aFrameList)); return;
}
MOZ_ASSERT(FrameChildListID::NoReflowPrincipal == aListID, "unexpected child list");
}
// Find the proper last-child for where the append should go
nsIFrame* lastKid = mFrames.LastChild();
NS_ASSERTION(
(mLines.empty() ? nullptr : mLines.back()->LastChild()) == lastKid, "out-of-sync mLines / mFrames");
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": append "); for (nsIFrame* frame : aFrameList) {
frame->ListTag(stdout);
} if (lastKid) {
printf(" after ");
lastKid->ListTag(stdout);
}
printf("\n"); #endif
if (IsInSVGTextSubtree()) {
MOZ_ASSERT(GetParent()->IsSVGTextFrame(), "unexpected block frame in SVG text"); // Workaround for bug 1399425 in case this bit has been removed from the // SVGTextFrame just before the parser adds more descendant nodes.
GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
}
if (aListID == FrameChildListID::Principal) { bool hasFloats = BlockHasAnyFloats(aOldFrame);
DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS); if (hasFloats) {
MarkSameFloatManagerLinesDirty(this);
}
} elseif (FrameChildListID::Float == aListID) { // Make sure to mark affected lines dirty for the float frame // we are removing; this way is a bit messy, but so is the rest of the code. // See bug 390762.
NS_ASSERTION(!aOldFrame->GetPrevContinuation(), "RemoveFrame should not be called on pushed floats."); for (nsIFrame* f = aOldFrame;
f && !f->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
f = f->GetNextContinuation()) {
MarkSameFloatManagerLinesDirty( static_cast<nsBlockFrame*>(f->GetParent()));
}
DoRemoveFloats(aContext, aOldFrame);
} elseif (FrameChildListID::NoReflowPrincipal == aListID) { // Skip the call to |FrameNeedsReflow| below by returning now.
DoRemoveFrame(aContext, aOldFrame, REMOVE_FIXED_CONTINUATIONS); return;
} else {
MOZ_CRASH("unexpected child list");
}
staticbool ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) {
LayoutFrameType type = aLastFrame->Type(); if (type == LayoutFrameType::Br) { returntrue;
} // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910. if (type == LayoutFrameType::Text &&
!aLastFrame->HasAnyStateBits(TEXT_OFFSETS_NEED_FIXING)) { return aLastFrame->HasSignificantTerminalNewline();
} returnfalse;
}
void nsBlockFrame::AddFrames(nsFrameList&& aFrameList, nsIFrame* aPrevSibling, const nsLineList::iterator* aPrevSiblingLine) { // Clear our line cursor, since our lines may change.
ClearLineCursors();
if (aFrameList.IsEmpty()) { return;
}
// Attempt to find the line that contains the previous sibling
nsLineList* lineList = &mLines;
nsFrameList* frames = &mFrames;
nsLineList::iterator prevSibLine;
int32_t prevSiblingIndex; if (aPrevSiblingLine) {
MOZ_ASSERT(aPrevSibling);
prevSibLine = *aPrevSiblingLine;
FrameLines* overflowLines = GetOverflowLines();
MOZ_ASSERT(prevSibLine.IsInSameList(mLines.begin()) ||
(overflowLines &&
prevSibLine.IsInSameList(overflowLines->mLines.begin())), "must be one of our line lists"); if (overflowLines) { // We need to find out which list it's actually in. Assume that // *if* we have overflow lines, that our primary lines aren't // huge, but our overflow lines might be.
nsLineList::iterator line = mLines.begin(), lineEnd = mLines.end(); while (line != lineEnd) { if (line == prevSibLine) { break;
}
++line;
} if (line == lineEnd) { // By elimination, the line must be in our overflow lines.
lineList = &overflowLines->mLines;
frames = &overflowLines->mFrames;
}
}
nsLineList::iterator nextLine = prevSibLine.next();
nsIFrame* lastFrameInLine = nextLine == lineList->end()
? frames->LastChild()
: nextLine->mFirstChild->GetPrevSibling();
prevSiblingIndex = prevSibLine->RLIndexOf(aPrevSibling, lastFrameInLine);
MOZ_ASSERT(prevSiblingIndex >= 0, "aPrevSibling must be in aPrevSiblingLine");
} else {
prevSibLine = lineList->end();
prevSiblingIndex = -1; if (aPrevSibling) { // XXX_perf This is technically O(N^2) in some cases, but by using // RFind instead of Find, we make it O(N) in the most common case, // which is appending content.
// Find the line that contains the previous sibling if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
prevSibLine, mFrames.LastChild(),
&prevSiblingIndex)) { // Not in mLines - try overflow lines.
FrameLines* overflowLines = GetOverflowLines(); bool found = false; if (overflowLines) {
prevSibLine = overflowLines->mLines.end();
prevSiblingIndex = -1;
found = nsLineBox::RFindLineContaining(
aPrevSibling, overflowLines->mLines.begin(), prevSibLine,
overflowLines->mFrames.LastChild(), &prevSiblingIndex);
} if (MOZ_LIKELY(found)) {
lineList = &overflowLines->mLines;
frames = &overflowLines->mFrames;
} else { // Note: defensive code! RFindLineContaining must not return // false in this case, so if it does...
MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
aPrevSibling = nullptr;
prevSibLine = lineList->end();
}
}
}
}
// Find the frame following aPrevSibling so that we can join up the // two lists of frames. if (aPrevSibling) { // Split line containing aPrevSibling in two if the insertion // point is somewhere in the middle of the line.
int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; if (rem) { // Split the line in two where the frame(s) are being inserted.
nsLineBox* line =
NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
lineList->after_insert(prevSibLine, line); // Mark prevSibLine dirty and as needing textrun invalidation, since // we may be breaking up text in the line. Its previous line may also // need to be invalidated because it may be able to pull some text up.
MarkLineDirty(prevSibLine, lineList); // The new line will also need its textruns recomputed because of the // frame changes.
line->MarkDirty();
line->SetInvalidateTextRuns(true);
}
} elseif (!lineList->empty()) {
lineList->front()->MarkDirty();
lineList->front()->SetInvalidateTextRuns(true);
} const nsFrameList::Slice& newFrames =
frames->InsertFrames(nullptr, aPrevSibling, std::move(aFrameList));
// Walk through the new frames being added and update the line data // structures to fit. for (nsIFrame* newFrame : newFrames) {
NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame, "Unexpected aPrevSibling");
NS_ASSERTION(
!newFrame->IsPlaceholderFrame() ||
(!newFrame->IsAbsolutelyPositioned() && !newFrame->IsFloating()), "Placeholders should not float or be positioned");
bool isBlock = newFrame->IsBlockOutside();
// If the frame is a block frame, or if there is no previous line or if the // previous line is a block line we need to make a new line. We also make // a new line, as an optimization, in the two cases we know we'll need it: // if the previous line ended with a <br>, or if it has significant // whitespace and ended in a newline. if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
(aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) { // Create a new line for the frame and add its line to the line // list.
nsLineBox* line = NewLineBox(newFrame, isBlock); if (prevSibLine != lineList->end()) { // Append new line after prevSibLine
lineList->after_insert(prevSibLine, line);
++prevSibLine;
} else { // New line is going before the other lines
lineList->push_front(line);
prevSibLine = lineList->begin();
}
} else {
prevSibLine->NoteFrameAdded(newFrame); // We're adding inline content to prevSibLine, so we need to mark it // dirty, ensure its textruns are recomputed, and possibly do the same // to its previous line since that line may be able to pull content up.
MarkLineDirty(prevSibLine, lineList);
}
void nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) { // Find which line contains the float, so we can update // the float cache. for (auto& line : Lines()) { if (line.IsInline() && line.RemoveFloat(aFloat)) { break;
}
}
}
// Floats live in floats list, pushed floats list, or overflow out-of-flow // list.
MOZ_ASSERT(
GetChildList(FrameChildListID::Float).ContainsFrame(aFloat) ||
GetChildList(FrameChildListID::PushedFloats).ContainsFrame(aFloat) ||
GetChildList(FrameChildListID::OverflowOutOfFlow)
.ContainsFrame(aFloat), "aFloat is not our child or on an unexpected frame list");
bool didStartRemovingFloat = false; if (nsFrameList* floats = GetFloats()) {
didStartRemovingFloat = true; if (floats->StartRemoveFrame(aFloat)) { if (floats->IsEmpty()) {
StealFloats()->Delete(PresShell());
} return;
}
}
if (nsFrameList* pushedFloats = GetPushedFloats()) { bool found; if (didStartRemovingFloat) {
found = pushedFloats->ContinueRemoveFrame(aFloat);
} else {
didStartRemovingFloat = true;
found = pushedFloats->StartRemoveFrame(aFloat);
} if (found) { if (pushedFloats->IsEmpty()) {
StealPushedFloats()->Delete(PresShell());
} return;
}
}
void nsBlockFrame::DoRemoveFloats(DestroyContext& aContext, nsIFrame* aFrame) { // Note: nsFloatingFirstLetterFrame might lose its floating style after // nsBlockFrame::UpdateFirstLetterStyle().
MOZ_ASSERT(aFrame->IsFloating() || static_cast<nsFloatingFirstLetterFrame*>(
do_QueryFrame(aFrame)), "DoRemoveFloats() can only remove float elements!");
// The containing block is always the parent of aFrame. auto* block = static_cast<nsBlockFrame*>(aFrame->GetParent());
// First remove aFrame's next-in-flows. if (nsIFrame* nif = aFrame->GetNextInFlow()) {
nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false);
} // Now remove aFrame from its child list and Destroy it.
block->RemoveFloatFromFloatCache(aFrame);
block->RemoveFloat(aFrame);
aFrame->Destroy(aContext);
}
staticbool AnonymousBoxIsBFC(const ComputedStyle* aStyle) { switch (aStyle->GetPseudoType()) { case PseudoStyleType::MozFieldsetContent: case PseudoStyleType::MozColumnContent: case PseudoStyleType::MozCellContent: case PseudoStyleType::MozScrolledContent: case PseudoStyleType::MozAnonymousItem: returntrue; default: returnfalse;
}
}
staticbool EstablishesBFC(const nsBlockFrame* aFrame) { if (aFrame->HasAnyClassFlag(LayoutFrameClassFlags::BlockFormattingContext)) { returntrue;
}
if (nsIFrame* parent = aFrame->GetParent()) { if (parent->IsFieldSetFrame()) { // A rendered legend always establishes a new formatting context, and so // does the fieldset content frame, so we can just return true here. // https://html.spec.whatwg.org/#rendered-legend returntrue;
}
constauto wm = aFrame->GetWritingMode(); constauto parentWM = parent->GetWritingMode(); if (wm.GetBlockDir() != parentWM.GetBlockDir() ||
wm.IsVerticalSideways() != parentWM.IsVerticalSideways()) { // If a box has a different writing-mode value than its containing block // [...] if the box is a block container, then it establishes a new block // formatting context. // https://drafts.csswg.org/css-writing-modes/#block-flow returntrue;
}
}
if (aFrame->IsColumnSpan()) { returntrue;
}
if (aFrame->IsContentAligned()) { returntrue;
}
if (aFrame->IsSuppressedScrollableBlockForPrint()) { returntrue;
}
constauto* style = aFrame->Style(); if (style->GetPseudoType() == PseudoStyleType::Marker) { if (aFrame->GetParent() &&
aFrame->GetParent()->StyleList()->mListStylePosition ==
StyleListStylePosition::Outside) { // An outside ::marker needs to be an independent formatting context // to avoid being influenced by the float manager etc. returntrue;
}
} return StyleEstablishesBFC(style);
}
void nsBlockFrame::DidSetComputedStyle(ComputedStyle* aOldStyle) {
nsContainerFrame::DidSetComputedStyle(aOldStyle); if (IsInSVGTextSubtree() &&
(StyleSVGReset()->HasNonScalingStroke() &&
(!aOldStyle || !aOldStyle->StyleSVGReset()->HasNonScalingStroke()))) {
nsIFrame* textFrame =
nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText);
MOZ_ASSERT(textFrame, "Expecting to find an SVG text frame");
SVGUtils::UpdateNonScalingStrokeStateBit(textFrame);
} if (!aOldStyle) { return;
}
constbool isBFC = EstablishesBFC(this); if (HasAnyStateBits(NS_BLOCK_BFC) != isBFC) { if (MaybeHasFloats()) { // If the frame contains floats, this update may change their float // manager. Be safe by dirtying all descendant lines of the nearest // ancestor's float manager.
RemoveStateBits(NS_BLOCK_BFC);
MarkSameFloatManagerLinesDirty(this);
}
AddOrRemoveStateBits(NS_BLOCK_BFC, isBFC);
}
}
// Figure out what the right style parent is. This needs to match // nsCSSFrameConstructor::CreateLetterFrame.
nsIFrame* inFlowFrame = letterFrame; if (inFlowFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
inFlowFrame = inFlowFrame->GetPlaceholderFrame();
}
nsIFrame* styleParent = CorrectStyleParentFrame(inFlowFrame->GetParent(),
PseudoStyleType::FirstLetter);
ComputedStyle* parentStyle = styleParent->Style();
RefPtr<ComputedStyle> firstLetterStyle =
aRestyleState.StyleSet().ResolvePseudoElementStyle(
*mContent->AsElement(), PseudoStyleType::FirstLetter, nullptr,
parentStyle); // Note that we don't need to worry about changehints for the continuation // styles: those will be handled by the styleParent already.
RefPtr<ComputedStyle> continuationStyle =
aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(
parentStyle);
UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
Some(continuationStyle.get()));
// We also want to update the style on the textframe inside the first-letter. // We don't need to compute a changehint for this, though, since any changes // to it are handled by the first-letter anyway.
nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
RefPtr<ComputedStyle> firstTextStyle =
aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
firstLetterStyle);
textFrame->SetComputedStyle(firstTextStyle);
// We don't need to update style for textFrame's continuations: it's already // set up to inherit from parentStyle, which is what we want.
}
static nsIFrame* FindChildContaining(nsBlockFrame* aFrame,
nsIFrame* aFindFrame) {
NS_ASSERTION(aFrame, "must have frame");
nsIFrame* child; while (true) {
nsIFrame* block = aFrame; do {
child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame); if (child) { break;
}
block = block->GetNextContinuation();
} while (block); if (!child) { return nullptr;
} if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) { break;
}
aFindFrame = child->GetPlaceholderFrame();
}
nsIFrame* child = FindChildContaining(aFrame, aFindFrame); if (!child) { return;
}
LineIterator line_end = aFrame->LinesEnd();
mLine = aFrame->LinesBegin(); if (mLine != line_end && mLine.next() == line_end &&
!aFrame->HasOverflowLines()) { // The block has a single line - that must be it!
*aFoundValidLine = true; return;
}
// Try to use the cursor if it exists, otherwise fall back to the first line if (nsLineBox* const cursor = aFrame->GetLineCursorForQuery()) {
mLine = line_end; // Perform a simultaneous forward and reverse search starting from the // line cursor.
nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd(); // rline is positioned on the line containing 'cursor', so it's not // rline_end. So we can safely increment it (i.e. move it to one line // earlier) to start searching there.
++rline; while (line != line_end || rline != rline_end) { if (line != line_end) { if (line->Contains(child)) {
mLine = line; break;
}
++line;
} if (rline != rline_end) { if (rline->Contains(child)) {
mLine = rline; break;
}
++rline;
}
} if (mLine != line_end) {
*aFoundValidLine = true; if (mLine != cursor) {
aFrame->SetProperty(nsBlockFrame::LineCursorPropertyQuery(), mLine);
} return;
}
} else { for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) { if (mLine->Contains(child)) {
*aFoundValidLine = true; return;
}
}
} // Didn't find the line
MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
// If we reach here, it means that we have not been able to find the // desired frame in our in-flow lines. So we should start looking at // our overflow lines. In order to do that, we set mLine to the end // iterator so that FindValidLine starts to look at overflow lines, // if any.
if (!FindValidLine()) { return;
}
do { if (mLine->Contains(child)) {
*aFoundValidLine = true; return;
}
} while (Next());
}
bool nsBlockInFlowLineIterator::Prev() {
LineIterator begin = mLineList->begin(); if (mLine != begin) {
--mLine; returntrue;
} bool currentlyInOverflowLines = GetInOverflow(); while (true) { if (currentlyInOverflowLines) {
mLineList = &mFrame->mLines;
mLine = mLineList->end(); if (mLine != mLineList->begin()) {
--mLine; returntrue;
}
} else {
mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow()); if (!mFrame) { returnfalse;
}
nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); if (overflowLines) {
mLineList = &overflowLines->mLines;
mLine = mLineList->end();
NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
--mLine; returntrue;
}
}
currentlyInOverflowLines = !currentlyInOverflowLines;
}
}
bool nsBlockInFlowLineIterator::FindValidLine() {
LineIterator end = mLineList->end(); if (mLine != end) { returntrue;
} bool currentlyInOverflowLines = GetInOverflow(); while (true) { if (currentlyInOverflowLines) {
mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow()); if (!mFrame) { returnfalse;
}
mLineList = &mFrame->mLines;
mLine = mLineList->begin(); if (mLine != mLineList->end()) { returntrue;
}
} else {
nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); if (overflowLines) {
mLineList = &overflowLines->mLines;
mLine = mLineList->begin();
NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?"); returntrue;
}
}
currentlyInOverflowLines = !currentlyInOverflowLines;
}
}
// This function removes aDeletedFrame and all its continuations. It // is optimized for deleting a whole series of frames. The easy // implementation would invoke itself recursively on // aDeletedFrame->GetNextContinuation, then locate the line containing // aDeletedFrame and remove aDeletedFrame from that line. But here we // start by locating aDeletedFrame and then scanning from that point // on looking for continuations. void nsBlockFrame::DoRemoveFrame(DestroyContext& aContext,
nsIFrame* aDeletedFrame, uint32_t aFlags) {
MOZ_ASSERT(!aDeletedFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW |
NS_FRAME_IS_OVERFLOW_CONTAINER), "DoRemoveFrame() does not support removing out-of-flow frames or " "overflow containers!");
// Find the line that contains deletedFrame. Start from the line cursor // (if available) and search to the end of the normal line list, then // from the start to the line cursor, and last the overflow lines.
nsLineList::iterator line_start = mLines.begin(), line_end = mLines.end();
nsLineList::iterator line = line_start;
// We use the line cursor to attempt to optimize removal, but must ensure // it is cleared if lines change such that it may become invalid. bool found = false; if (nsLineBox* cursor = GetLineCursorForDisplay()) { for (line.SetPosition(cursor); line != line_end; ++line) { if (line->Contains(aDeletedFrame)) {
found = true; break;
}
} if (!found) { // Setup for a shorter TryAllLines normal line search to avoid searching // the [cursor .. line_end] range again.
line = line_start;
line_end.SetPosition(cursor);
}
}
FrameLines* overflowLines = nullptr; bool searchingOverflowList = false; if (!found) { // Make sure we look in the overflow lines even if the normal line // list is empty.
TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
&overflowLines); while (line != line_end) { if (line->Contains(aDeletedFrame)) { break;
}
++line;
TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
&overflowLines);
} if (!searchingOverflowList && (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) { // Restore line_end since we shortened the search to the cursor.
line_end = mLines.end(); // Clear our line cursors, since our normal line list may change.
ClearLineCursors();
}
}
if (line == line_end) {
NS_ERROR("can't find deleted frame in lines"); return;
}
while (line != line_end && aDeletedFrame) {
MOZ_ASSERT(this == aDeletedFrame->GetParent(), "messed up delete code");
MOZ_ASSERT(line->Contains(aDeletedFrame), "frame not in line");
if (!(aFlags & FRAMES_ARE_EMPTY)) {
line->MarkDirty();
line->SetInvalidateTextRuns(true);
}
// If the frame being deleted is the last one on the line then // optimize away the line->Contains(next-in-flow) call below. bool isLastFrameOnLine = 1 == line->GetChildCount(); if (!isLastFrameOnLine) {
LineIterator next = line.next();
nsIFrame* lastFrame =
next != line_end
? next->mFirstChild->GetPrevSibling()
: (searchingOverflowList ? overflowLines->mFrames.LastChild()
: mFrames.LastChild());
NS_ASSERTION(next == line_end || lastFrame == line->LastChild(), "unexpected line frames");
isLastFrameOnLine = lastFrame == aDeletedFrame;
}
// Remove aDeletedFrame from the line if (line->mFirstChild == aDeletedFrame) { // We should be setting this to null if aDeletedFrame // is the only frame on the line. HOWEVER in that case // we will be removing the line anyway, see below.
line->mFirstChild = aDeletedFrame->GetNextSibling();
}
// Hmm, this won't do anything if we're removing a frame in the first // overflow line... Hopefully doesn't matter
--line; if (line != line_end && !line->IsBlock()) { // Since we just removed a frame that follows some inline // frames, we need to reflow the previous line.
line->MarkDirty();
}
++line;
// Take aDeletedFrame out of the sibling list. Note that // prevSibling will only be nullptr when we are deleting the very // first frame in the main or overflow list. if (searchingOverflowList) {
overflowLines->mFrames.RemoveFrame(aDeletedFrame);
} else {
mFrames.RemoveFrame(aDeletedFrame);
}
// Update the child count of the line to be accurate
line->NoteFrameRemoved(aDeletedFrame);
// Destroy frame; capture its next continuation first in case we need // to destroy that too.
nsIFrame* deletedNextContinuation =
(aFlags & REMOVE_FIXED_CONTINUATIONS)
? aDeletedFrame->GetNextContinuation()
: aDeletedFrame->GetNextInFlow(); #ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p frame=",
searchingOverflowList ? "overflow" : "normal", line.get());
aDeletedFrame->ListTag(stdout);
printf(" prevSibling=%p deletedNextContinuation=%p\n",
aDeletedFrame->GetPrevSibling(), deletedNextContinuation); #endif
// If next-in-flow is an overflow container, must remove it first. // FIXME: Can we do this unconditionally? if (deletedNextContinuation && deletedNextContinuation->HasAnyStateBits(
NS_FRAME_IS_OVERFLOW_CONTAINER)) {
deletedNextContinuation->GetParent()->DeleteNextInFlowChild(
aContext, deletedNextContinuation, false);
deletedNextContinuation = nullptr;
}
bool haveAdvancedToNextLine = false; // If line is empty, remove it now. if (0 == line->GetChildCount()) { #ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
searchingOverflowList ? "overflow" : "normal", line.get()); #endif
nsLineBox* cur = line; if (!searchingOverflowList) {
line = mLines.erase(line);
ClearLineCursors(); // Invalidate the space taken up by the line. // XXX We need to do this if we're removing a frame as a result of // a call to RemoveFrame(), but we may not need to do this in all // cases... #ifdef NOISY_BLOCK_INVALIDATE
nsRect inkOverflow(cur->InkOverflowRect());
printf("%p invalidate 10 (%d, %d, %d, %d)\n", this, inkOverflow.x,
inkOverflow.y, inkOverflow.width, inkOverflow.height); #endif
} else {
line = overflowLines->mLines.erase(line); if (overflowLines->mLines.empty()) {
DestroyOverflowLines();
overflowLines = nullptr; // We just invalidated our iterators. Since we were in // the overflow lines list, which is now empty, set them // so we're at the end of the regular line list.
line_start = mLines.begin();
line_end = mLines.end();
line = line_end;
}
}
FreeLineBox(cur);
// If we're removing a line, ReflowDirtyLines isn't going to // know that it needs to slide lines unless something is marked // dirty. So mark the previous margin of the next line dirty if // there is one. if (line != line_end) {
line->MarkPreviousMarginDirty();
}
haveAdvancedToNextLine = true;
} else { // Make the line that just lost a frame dirty, and advance to // the next line. if (!deletedNextContinuation || isLastFrameOnLine ||
!line->Contains(deletedNextContinuation)) {
line->MarkDirty();
++line;
haveAdvancedToNextLine = true;
}
}
if (deletedNextContinuation) { // See if we should keep looking in the current flow's line list. if (deletedNextContinuation->GetParent() != this) { // The deceased frames continuation is not a child of the // current block. So break out of the loop so that we advance // to the next parent. // // If we have a continuation in a different block then all bets are // off regarding whether we are deleting frames without actual content, // so don't propagate FRAMES_ARE_EMPTY any further.
aFlags &= ~FRAMES_ARE_EMPTY; break;
}
// If we advanced to the next line then check if we should switch to the // overflow line list. if (haveAdvancedToNextLine) { if (line != line_end && !searchingOverflowList &&
!line->Contains(deletedNextContinuation)) { // We have advanced to the next *normal* line but the next-in-flow // is not there - force a switch to the overflow line list.
line = line_end;
}
// Advance to next flow block if the frame has more continuations. if (!aDeletedFrame) { return;
}
nsBlockFrame* nextBlock = do_QueryFrame(aDeletedFrame->GetParent());
NS_ASSERTION(nextBlock, "Our child's continuation's parent is not a block?");
uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
nextBlock->DoRemoveFrame(aContext, aDeletedFrame, flags);
}
const nsStyleText* nsBlockFrame::StyleTextForLineLayout() { // Return the pointer to an unmodified style text return StyleText();
}
void nsBlockFrame::ReflowFloat(BlockReflowState& aState, ReflowInput& aFloatRI,
nsIFrame* aFloat,
nsReflowStatus& aReflowStatus) {
MOZ_ASSERT(aReflowStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
MOZ_ASSERT(aFloat->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW), "aFloat must be an out-of-flow frame");
// Setup a block reflow context to reflow the float.
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
nsIFrame* clearanceFrame = nullptr; do {
CollapsingMargin margin; bool mayNeedRetry = false;
aFloatRI.mDiscoveredClearance = nullptr; // Only first in flow gets a block-start margin. if (!aFloat->GetPrevInFlow()) {
brc.ComputeCollapsedBStartMargin(aFloatRI, &margin, clearanceFrame,
&mayNeedRetry);
if (mayNeedRetry && !clearanceFrame) {
aFloatRI.mDiscoveredClearance = &clearanceFrame; // We don't need to push the float manager state because the the block // has its own float manager that will be destroyed and recreated
}
}
// When reflowing a float, aSpace argument doesn't matter because we pass // nullptr to aLine and we don't call nsBlockReflowContext::PlaceBlock() // later.
brc.ReflowBlock(LogicalRect(wm), true, margin, 0, nullptr, aFloatRI,
aReflowStatus, aState);
} while (clearanceFrame);
if (aFloat->IsLetterFrame()) { // We never split floating first letters; an incomplete status for such // frames simply means that there is more content to be reflowed on the // line. if (aReflowStatus.IsIncomplete()) {
aReflowStatus.Reset();
}
}
NS_ASSERTION(aReflowStatus.IsFullyComplete() ||
aFloatRI.AvailableBSize() != NS_UNCONSTRAINEDSIZE, "The status can only be incomplete or overflow-incomplete if " "the available block-size is constrained!");
if (aReflowStatus.NextInFlowNeedsReflow()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
}
const ReflowOutput& metrics = brc.GetMetrics();
// Set the rect, make sure the view is properly sized and positioned, // and tell the frame we're done reflowing it // XXXldb This seems like the wrong place to be doing this -- shouldn't // we be doing this in BlockReflowState::FlowAndPlaceFloat after // we've positioned the float, and shouldn't we be doing the equivalent // of |PlaceFrameView| here?
WritingMode metricsWM = metrics.GetWritingMode();
aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
aFloat->DidReflow(aState.mPresContext, &aFloatRI);
}
UsedClear nsBlockFrame::FindTrailingClear() { for (nsBlockFrame* b = this; b;
b = static_cast<nsBlockFrame*>(b->GetPrevInFlow())) { auto endLine = b->LinesRBegin(); if (endLine != b->LinesREnd()) { return endLine->FloatClearTypeAfter();
}
} return UsedClear::None;
}
void nsBlockFrame::ReflowPushedFloats(BlockReflowState& aState,
OverflowAreas& aOverflowAreas) { // Pushed floats live at the start of our float list; see comment // above nsBlockFrame::DrainPushedFloats.
nsFrameList* floats = GetFloats();
nsIFrame* f = floats ? floats->FirstChild() : nullptr;
nsIFrame* prev = nullptr; while (f && f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) {
MOZ_ASSERT(prev == f->GetPrevSibling()); // When we push a first-continuation float in a non-initial reflow, // it's possible that we end up with two continuations with the same // parent. This happens if, on the previous reflow of the block or // a previous reflow of the line containing the block, the float was // split between continuations A and B of the parent, but on the // current reflow, none of the float can fit in A. // // When this happens, we might even have the two continuations // out-of-order due to the management of the pushed floats. In // particular, if the float's placeholder was in a pushed line that // we reflowed before it was pushed, and we split the float during // that reflow, we might have the continuation of the float before // the float itself. (In the general case, however, it's correct // for floats in the pushed floats list to come before floats // anchored in pushed lines; however, in this case it's wrong. We // should probably find a way to fix it somehow, since it leads to // incorrect layout in some cases.) // // When we have these out-of-order continuations, we might hit the // next-continuation before the previous-continuation. When that // happens, just push it. When we reflow the next continuation, // we'll either pull all of its content back and destroy it (by // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will // pull it out of its current position and push it again (and // potentially repeat this cycle for the next continuation, although // hopefully then they'll be in the right order). // // We should also need this code for the in-order case if the first // continuation of a float gets moved across more than one // continuation of the containing block. In this case we'd manage // to push the second continuation without this check, but not the // third and later.
nsIFrame* prevContinuation = f->GetPrevContinuation(); if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
floats->RemoveFrame(f); if (floats->IsEmpty()) {
StealFloats()->Delete(PresShell());
floats = nullptr;
}
aState.AppendPushedFloatChain(f); if (!floats) { // The floats list becomes empty after removing |f|. Bail out.
f = prev = nullptr; break;
} // Even if we think |floats| is valid, AppendPushedFloatChain() can also // push |f|'s next-in-flows in our floats list to our pushed floats list. // If all the floats in the floats list are pushed, the floats list will // be deleted, and |floats| will be stale and poisoned. Therefore, we need // to get the floats list again to check its validity.
floats = GetFloats(); if (!floats) {
f = prev = nullptr; break;
}
f = !prev ? floats->FirstChild() : prev->GetNextSibling(); continue;
}
// Always call FlowAndPlaceFloat; we might need to place this float if it // didn't belong to this block the last time it was reflowed. Note that if // the float doesn't get placed, we don't consider its overflow areas. // (Not-getting-placed means it didn't fit and we pushed it instead of // placing it, and its position could be stale.) if (aState.FlowAndPlaceFloat(f) ==
BlockReflowState::PlaceFloatResult::Placed) {
ConsiderChildOverflow(aOverflowAreas, f);
}
// If f is the only child in the floats list, pushing it to the pushed // floats list in FlowAndPlaceFloat() can result in the floats list being // deleted. Get the floats list again.
floats = GetFloats(); if (!floats) {
f = prev = nullptr; break;
}
nsIFrame* next = !prev ? floats->FirstChild() : prev->GetNextSibling(); if (next == f) { // We didn't push |f| so its next-sibling is next.
next = f->GetNextSibling();
prev = f;
} // else: we did push |f| so |prev|'s new next-sibling is next.
f = next;
}
// If there are pushed or split floats, then we may need to continue BR // clearance if (auto [bCoord, result] = aState.ClearFloats(0, UsedClear::Both);
result != ClearFloatsResult::BCoordNoChange) {
(void)bCoord; if (auto* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow())) {
aState.mTrailingClearFromPIF = prevBlock->FindTrailingClear();
}
}
}
void nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM, const nsSize& aContainerSize) { // Recover our own floats
nsIFrame* stop = nullptr; // Stop before we reach pushed floats that // belong to our next-in-flow const nsFrameList* floats = GetFloats(); for (nsIFrame* f = floats ? floats->FirstChild() : nullptr; f && f != stop;
f = f->GetNextSibling()) {
LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
aFloatManager.AddFloat(f, region, aWM, aContainerSize); if (!stop && f->GetNextInFlow()) {
stop = f->GetNextInFlow();
}
}
// Recurse into our overflow container children for (nsIFrame* oc =
GetChildList(FrameChildListID::OverflowContainers).FirstChild();
oc; oc = oc->GetNextSibling()) {
RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
}
// Recurse into our normal children for (constauto& line : Lines()) { if (line.IsBlock()) {
RecoverFloatsFor(line.mFirstChild, aFloatManager, aWM, aContainerSize);
}
}
}
// Only blocks have floats
nsBlockFrame* block = do_QueryFrame(aFrame); // Don't recover any state inside a block that has its own float manager // (we don't currently have any blocks like this, though, thanks to our // use of extra frames for 'overflow') if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) { // If the element is relatively positioned, then adjust x and y // accordingly so that we consider relatively positioned frames // at their original position.
bool nsBlockFrame::HasPushedFloatsFromPrevContinuation() const { if (const nsFrameList* floats = GetFloats()) { // If we have pushed floats, then they should be at the beginning of our // float list. if (floats->FirstChild()->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) { returntrue;
} #ifdef DEBUG // Double-check the above assertion that pushed floats should be at the // beginning of our floats list. for (nsIFrame* f : *floats) {
NS_ASSERTION(!f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW), "pushed floats must be at the beginning of the float list");
} #endif
}
// We may have a pending push of pushed floats, too. return HasPushedFloats();
}
// Collect our line's display items in a temporary nsDisplayListCollection, // so that we can apply any "text-overflow" clipping to the entire collection // without affecting previous lines.
nsDisplayListCollection collection(aBuilder);
// Block-level child backgrounds go on the blockBorderBackgrounds list ... // Inline-level child backgrounds go on the regular child content list.
nsDisplayListSet childLists(
collection,
aLineInLine ? collection.Content() : collection.BlockBorderBackgrounds());
auto flags =
aLineInLine
? nsIFrame::DisplayChildFlags(nsIFrame::DisplayChildFlag::Inline)
: nsIFrame::DisplayChildFlags();
if (aFrame->HasLineClampEllipsisDescendant() && !aLineInLine) { if (nsBlockFrame* f = GetAsLineClampDescendant(aLine->mFirstChild)) { if (f->HasLineClampEllipsis() || f->HasLineClampEllipsisDescendant()) {
aFoundLineClamp = true;
}
}
}
if (aTextOverflow && aLineInLine) {
aTextOverflow->ProcessLine(collection, aLine.get(),
aLineNumberForTextOverflow);
}
collection.MoveTo(aLists);
}
void nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) {
int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
int32_t depth = 0; #ifdef DEBUG if (gNoisyDamageRepair) {
nsRect dirty = aBuilder->GetDirtyRect();
depth = GetDepth();
nsRect ca;
::ComputeInkOverflowArea(mLines, mRect.width, mRect.height, ca);
nsIFrame::IndentBy(stdout, depth);
ListTag(stdout);
printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
dirty.width, dirty.height, ca.x, ca.y, ca.width, ca.height);
}
PRTime start = 0; // Initialize these variables to silence the compiler. if (gLamePaintMetrics) {
start = PR_Now();
drawnLines = 0;
} #endif
// TODO(heycam): Should we boost the load priority of any shape-outside // images using CATEGORY_DISPLAY, now that this block is being displayed? // We don't have a float manager here.
DisplayBorderBackgroundOutline(aBuilder, aLists);
if (HidesContent()) { return;
}
if (GetPrevInFlow()) {
DisplayOverflowContainers(aBuilder, aLists); // TODO(dshin, bug 2013284): Location of this doesn't align with observed // behaviour. for (nsIFrame* f : GetChildList(FrameChildListID::Float)) { if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) {
BuildDisplayListForChild(aBuilder, f, aLists);
}
}
}
constauto ShouldDescendIntoLine = [&](const nsRect& aLineArea) -> bool { // TODO(miko): Unfortunately |descendAlways| cannot be cached, because with // some frame trees, building display list for child lines can change it. // See bug 1552789. constbool descendAlways =
HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) ||
aBuilder->GetIncludeAllOutOfFlows();
// We'll try to draw an accessibility backplate behind text (to ensure it's // readable over any possible background-images), if all of the following // hold: // (A) we are not honoring the document colors // (B) the backplate feature is preffed on // (C) the force color adjust property is set to auto if (PresContext()->ForcingColors() &&
StaticPrefs::browser_display_permit_backplate() &&
StyleText()->mForcedColorAdjust != StyleForcedColorAdjust::None) {
backplateColor.emplace(GetBackplateColor(this));
}
constbool canUseCursor = [&] { if (hasDescendantPlaceHolders) { // Don't use the line cursor if we might have a descendant placeholder. It // might skip lines that contain placeholders but don't themselves // intersect with the dirty area. // // In particular, we really want to check ShouldDescendIntoFrame() // on all our child frames, but that might be expensive. So we // approximate it by checking it on |this|; if it's true for any // frame in our child list, it's also true for |this|. returnfalse;
} if (textOverflow) { // Also skip the cursor if we're creating text overflow markers, since we // need to know what line number we're up to in order to generate unique // display item keys. returnfalse;
} if (backplateColor) { // Cursors should be skipped if we're drawing backplates behind text. When // backplating we consider consecutive runs of text as a whole, which // requires we iterate through all lines to find our backplate size. returnfalse;
} if ((HasLineClampEllipsis() || HasLineClampEllipsisDescendant()) &&
StaticPrefs::layout_css_webkit_line_clamp_skip_paint()) { // We can't use the cursor if we're in a line-clamping situation, and // we're configured to not paint its clamped content, as we need to know // whether we've hit the clamp point which requires iterating over all // lines. returnfalse;
} returntrue;
}();
if (cursor) { for (LineIterator line = mLines.begin(cursor); line != line_end; ++line) { const nsRect lineArea = line->InkOverflowRect(); if (!lineArea.IsEmpty()) { // Because we have a cursor, the combinedArea.ys are non-decreasing. // Once we've passed aDirtyRect.YMost(), we can never see it again. if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) { break;
}
MOZ_ASSERT(!textOverflow);
// A frame's display list cannot contain more than one copy of a // given display item unless the items are uniquely identifiable. // Because backplate occasionally requires multiple // SolidColor items, we use an index (backplateIndex) to maintain // uniqueness among them. Note this is a mapping of index to // item, and the mapping is stable even if the dirty rect changes.
uint16_t backplateIndex = 0;
nsRect curBackplateArea;
if (!lineInLine && !curBackplateArea.IsEmpty()) { // If we have encountered a non-inline line but were previously // forming a backplate, we should add the backplate to the display // list as-is and render future backplates disjointly.
MOZ_ASSERT(backplateColor, "if this master switch is off, curBackplateArea " "must be empty and we shouldn't get here");
AddBackplate();
backplateIndex++;
curBackplateArea = nsRect();
}
if (GetPrevInFlow() || GetNextInFlow()) {
DisplayAbsoluteFramesNotBuiltByPlaceholder(aBuilder, aLists);
}
if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
SetupLineCursorForDisplay();
}
if (!curBackplateArea.IsEmpty()) {
AddBackplate();
}
}
if (textOverflow) { // Put any text-overflow:ellipsis markers on top of the non-positioned // content of the block's lines. (If we ever start sorting the Content() // list this will end up in the wrong place.)
aLists.Content()->AppendToTop(&textOverflow->GetMarkers());
}
#ifdef DEBUG if (gLamePaintMetrics) {
PRTime end = PR_Now();
// block frame may be for <hr> if (mContent->IsHTMLElement(nsGkAtoms::hr)) { return a11y::eHTMLHRType;
}
if (IsButtonLike()) { return a11y::eHTMLButtonType;
}
if (HasMarker()) { // Create special list item accessible since we have a ::marker. return a11y::eHTMLLiType;
}
// XXXsmaug What if we're in the shadow dom? if (!mContent->GetParent()) { // Don't create accessible objects for the root content node, they are // redundant with the nsDocAccessible object created with the document // node return a11y::eNoType;
}
if (mContent == mContent->OwnerDoc()->GetBody()) { // Don't create accessible objects for the body, they are redundant with // the nsDocAccessible object created with the document node return a11y::eNoType;
}
// Not a list item with a ::marker, treat as normal HTML container. return a11y::eHyperTextType;
} #endif
void nsBlockFrame::SetupLineCursorForDisplay() { if (mLines.empty() || HasProperty(LineCursorPropertyDisplay())) { return;
}
nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) { // Although this looks like a "querying" method, it is used by the // display-list building code, so uses the Display cursor.
nsLineBox* property = GetLineCursorForDisplay(); if (!property) { return nullptr;
}
LineIterator cursor = mLines.begin(property);
nsRect cursorArea = cursor->InkOverflowRect();
if (cursor.get() != property) {
SetProperty(LineCursorPropertyDisplay(), cursor.get());
}
return cursor.get();
}
/* virtual */ void nsBlockFrame::ChildIsDirty(nsIFrame* aChild) { // See if the child is absolutely positioned if (aChild->IsAbsolutelyPositioned()) { // do nothing
} elseif (aChild == GetOutsideMarker()) { // The ::marker lives in the first line, unless the first line has // height 0 and there is a second line, in which case it lives // in the second line.
LineIterator markerLine = LinesBegin(); if (markerLine != LinesEnd() && markerLine->BSize() == 0 &&
markerLine != mLines.back()) {
markerLine = markerLine.next();
}
if (markerLine != LinesEnd()) {
MarkLineDirty(markerLine, &mLines);
} // otherwise we have an empty line list, and ReflowDirtyLines // will handle reflowing the ::marker.
} else { // Note that we should go through our children to mark lines dirty // before the next reflow. Doing it now could make things O(N^2) // since finding the right line is O(N). // We don't need to worry about marking lines on the overflow list // as dirty; we're guaranteed to reflow them if we take them off the // overflow list. // However, we might have gotten a float, in which case we need to // reflow the line containing its placeholder. So find the // ancestor-or-self of the placeholder that's a child of the block, // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. // We need to take some care to handle the case where a float is in // a different continuation than its placeholder, including marking // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. if (!aChild->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
} else {
NS_ASSERTION(aChild->IsFloating(), "should be a float");
nsIFrame* thisFC = FirstContinuation();
nsIFrame* placeholderPath = aChild->GetPlaceholderFrame(); // SVG code sometimes sends FrameNeedsReflow notifications during // frame destruction, leading to null placeholders, but we're safe // ignoring those. if (placeholderPath) { for (;;) {
nsIFrame* parent = placeholderPath->GetParent(); if (parent->GetContent() == mContent &&
parent->FirstContinuation() == thisFC) {
parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); break;
}
placeholderPath = parent;
}
placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
nsContainerFrame::ChildIsDirty(aChild);
}
void nsBlockFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
nsIFrame* aPrevInFlow) { // These are all the block specific frame bits, they are copied from // the prev-in-flow to a newly created next-in-flow, except for the // NS_BLOCK_FLAGS_NON_INHERITED_MASK bits below.
constexpr nsFrameState NS_BLOCK_FLAGS_MASK =
NS_BLOCK_BFC | NS_BLOCK_HAS_FIRST_LETTER_STYLE |
NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_HAS_MARKER |
NS_BLOCK_HAS_INLINE_ABSPOS_DESCENDANT;
// This is the subset of NS_BLOCK_FLAGS_MASK that is NOT inherited // by default. They should only be set on the first-in-flow.
constexpr nsFrameState NS_BLOCK_FLAGS_NON_INHERITED_MASK =
NS_BLOCK_HAS_FIRST_LETTER_CHILD | NS_BLOCK_HAS_MARKER;
if (aPrevInFlow) { // Copy over the inherited block frame bits from the prev-in-flow.
RemoveStateBits(NS_BLOCK_FLAGS_MASK);
AddStateBits(aPrevInFlow->GetStateBits() &
(NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
}
if (!aPrevInFlow ||
aPrevInFlow->HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
}
if (EstablishesBFC(this)) {
AddStateBits(NS_BLOCK_BFC);
}
if (HasAnyStateBits(NS_FRAME_FONT_INFLATION_CONTAINER) &&
HasAnyStateBits(NS_BLOCK_BFC)) {
AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
}
}
void nsBlockFrame::SetInitialChildList(ChildListID aListID,
nsFrameList&& aChildList) { if (FrameChildListID::Float == aListID) {
nsFrameList* floats = EnsureFloats();
*floats = std::move(aChildList);
} elseif (FrameChildListID::Principal == aListID) { #ifdef DEBUG // The only times a block that is an anonymous box is allowed to have a // first-letter frame are when it's the block inside a non-anonymous cell, // the block inside a fieldset, button or column set, or a scrolled content // block, except for <select>. Note that this means that blocks which are // the anonymous block in {ib} splits do NOT get first-letter frames. // Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations // of the block. auto pseudo = Style()->GetPseudoType(); bool haveFirstLetterStyle =
(pseudo == PseudoStyleType::NotPseudo ||
PseudoStyle::IsElementBackedPseudo(pseudo) ||
(pseudo == PseudoStyleType::MozCellContent &&
!GetParent()->Style()->IsPseudoOrAnonBox()) ||
pseudo == PseudoStyleType::MozFieldsetContent ||
pseudo == PseudoStyleType::MozColumnContent ||
pseudo == PseudoStyleType::MozScrolledContent ||
pseudo == PseudoStyleType::MozSvgText) &&
!IsMathMLFrame() && !IsColumnSetWrapperFrame() &&
!IsComboboxControlFrame() &&
RefPtr<ComputedStyle>(GetFirstLetterStyle(PresContext())) != nullptr;
NS_ASSERTION(haveFirstLetterStyle ==
HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE), "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync"); #endif
void nsBlockFrame::SetMarkerFrameForListItem(nsIFrame* aMarkerFrame) {
MOZ_ASSERT(aMarkerFrame);
MOZ_ASSERT(!HasMarker(), "How can we have a ::marker frame already?");
if (StyleList()->mListStylePosition == StyleListStylePosition::Inside) {
SetProperty(InsideMarkerProperty(), aMarkerFrame);
} else {
SetProperty(OutsideMarkerProperty(), new (PresShell()) nsFrameList(aMarkerFrame, aMarkerFrame));
}
AddStateBits(NS_BLOCK_HAS_MARKER);
}
bool nsBlockFrame::MarkerIsEmpty(const nsIFrame* aMarker) const {
MOZ_ASSERT(mContent->GetPrimaryFrame()->StyleDisplay()->IsListItem() &&
aMarker == GetOutsideMarker(), "should only care about an outside ::marker"); const nsStyleList* list = aMarker->StyleList(); return aMarker->StyleContent()->mContent.IsNone() ||
(list->mListStyleType.IsNone() && list->mListStyleImage.IsNone() &&
aMarker->StyleContent()->NonAltContentItems().IsEmpty());
}
WritingMode markerWM = aMarkerFrame->GetWritingMode();
LogicalSize availSize(markerWM); // Make up an inline-size since it doesn't really matter (XXX).
availSize.ISize(markerWM) = aState.ContentISize();
availSize.BSize(markerWM) = NS_UNCONSTRAINEDSIZE;
// Get the float available space using our saved state from before we // started reflowing the block, so that we ignore any floats inside // the block. // FIXME: aLineTop isn't actually set correctly by some callers, since // they reposition the line.
LogicalRect floatAvailSpace =
aState
.GetFloatAvailableSpaceWithState(ri.GetWritingMode(), aLineTop,
ShapeType::ShapeOutside,
&aState.mFloatManagerStateBefore)
.mRect; // FIXME (bug 25888): need to check the entire region that the first // line overlaps, not just the top pixel.
// Place the ::marker now. We want to place the ::marker relative to the // border-box of the associated block (using the right/left margin of // the ::marker frame as separation). However, if a line box would be // displaced by floats that are *outside* the associated block, we // want to displace it by the same amount. That is, we act as though // the edge of the floats is the content-edge of the block, and place // the ::marker at a position offset from there by the block's padding, // the block's border, and the ::marker frame's margin.
// IStart from floatAvailSpace gives us the content/float start edge // in the current writing mode. Then we subtract out the start // border/padding and the ::marker's width and margin to offset the position.
WritingMode wm = ri.GetWritingMode(); // Get the ::marker's margin, converted to our writing mode so that we can // combine it with other logical values here.
LogicalMargin markerMargin = reflowInput.ComputedLogicalMargin(wm);
nscoord iStart = floatAvailSpace.IStart(wm) -
ri.ComputedLogicalBorderPadding(wm).IStart(wm) -
markerMargin.IEnd(wm) - aMetrics.ISize(wm);
// Approximate the ::marker's position; vertical alignment will provide // the final vertical location. We pass our writing-mode here, because // it may be different from the ::marker frame's mode.
nscoord bStart = floatAvailSpace.BStart(wm);
aMarkerFrame->SetRect(
wm,
LogicalRect(wm, iStart, bStart, aMetrics.ISize(wm), aMetrics.BSize(wm)),
aState.ContainerSize());
aMarkerFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
}
// This is used to scan frames for any float placeholders, add their // floats to the list represented by aList, and remove the // floats from whatever list they might be in. We don't search descendants // that are float containing blocks. Floats that or not children of 'this' // are ignored (they are not added to aList). void nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList, bool aCollectSiblings) { while (aFrame) { // Don't descend into float containing blocks. if (!aFrame->IsFloatContainingBlock()) {
nsIFrame* outOfFlowFrame =
aFrame->IsPlaceholderFrame()
? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
: nullptr; while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
RemoveFloat(outOfFlowFrame); // Remove the NS_FRAME_IS_PUSHED_OUT_OF_FLOW bit, in case // |outOfFlowFrame| came from the PushedFloats list.
outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW);
aList.AppendFrame(nullptr, outOfFlowFrame);
outOfFlowFrame = outOfFlowFrame->GetNextInFlow(); // FIXME: By not pulling floats whose parent is one of our // later siblings, are we risking the pushed floats getting // out-of-order? // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
}
void nsBlockFrame::CheckFloats(BlockReflowState& aState) { #ifdef DEBUG // If any line is still dirty, that must mean we're going to reflow this // block again soon (e.g. because we bailed out after noticing that // clearance was imposed), so don't worry if the floats are out of sync. bool anyLineDirty = false;
// Check that the float list is what we would have built
AutoTArray<nsIFrame*, 8> lineFloats; for (auto& line : Lines()) { if (line.HasFloats()) {
lineFloats.AppendElements(line.Floats());
} if (line.IsDirty()) {
anyLineDirty = true;
}
}
AutoTArray<nsIFrame*, 8> storedFloats; bool equal = true; bool hasHiddenFloats = false;
uint32_t i = 0; for (nsIFrame* f : GetChildList(FrameChildListID::Float)) { if (f->HasAnyStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW)) { continue;
} // There are chances that the float children won't be added to lines, // because in nsBlockFrame::ReflowLine, it skips reflow line if the first // child of the line is IsHiddenByContentVisibilityOfInFlowParentForLayout. // There are also chances that the floats in line are out of date, for // instance, lines could reflow if // PresShell::IsForcingLayoutForHiddenContent, and after forcingLayout is // off, the reflow of lines could be skipped, but the floats are still in // there. Here we can't know whether the floats hidden by c-v are included // in the lines or not. So we use hasHiddenFloats to skip the float length // checking. if (!hasHiddenFloats &&
f->IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
hasHiddenFloats = true;
}
storedFloats.AppendElement(f); if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
equal = false;
}
++i;
}
if ((!equal || lineFloats.Length() != storedFloats.Length()) &&
!anyLineDirty && !hasHiddenFloats) {
NS_ERROR( "nsBlockFrame::CheckFloats: Explicit float list is out of sync with " "float cache");
} #endif
const nsFrameList* oofs = GetOverflowOutOfFlows(); if (oofs && oofs->NotEmpty()) { // Floats that were pushed should be removed from our float // manager. Otherwise the float manager's YMost or XMost might // be larger than necessary, causing this block to get an // incorrect desired height (or width). Some of these floats // may not actually have been added to the float manager because // they weren't reflowed before being pushed; that's OK, // RemoveRegions will ignore them. It is safe to do this here // because we know from here on the float manager will only be // used for its XMost and YMost, not to place new floats and // lines.
aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
}
}
if (parent && parent->IsColumnSetFrame()) { // The first column is a start margin root and the last column is an end // margin root. (If the column-set is split by a column-span:all box then // the first and last column in each column-set fragment are margin roots.)
*aBStartMarginRoot = GetPrevInFlow() == nullptr;
*aBEndMarginRoot = GetNextInFlow() == nullptr; return;
}
/* static */ bool nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) {
MOZ_ASSERT(aBlock, "Must have a frame");
NS_ASSERTION(aBlock->IsBlockFrameOrSubclass(), "aBlock must be a block");
/* static */ bool nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) { // NS_BLOCK_BFC is block specific bit, check first as an optimization, it's // okay because we also check that it is a block frame. return !aFrame->HasAnyStateBits(NS_BLOCK_BFC) && !aFrame->IsReplaced() &&
aFrame->IsBlockFrameOrSubclass();
}
// Note that this width can vary based on the vertical position. // However, the cases where it varies are the cases where the width fits // in the available space given, which means that variation shouldn't // matter. /* static */
nsBlockFrame::FloatAvoidingISizeToClear nsBlockFrame::ISizeToClearPastFloats( const BlockReflowState& aState, const LogicalRect& aFloatAvailableSpace,
nsIFrame* aFloatAvoidingBlock) {
nscoord inlineStartOffset, inlineEndOffset;
WritingMode wm = aState.mReflowInput.GetWritingMode();
// We actually don't want the min width here; see bug 427782; we only // want to displace if the width won't compute to a value small enough // to fit. // All we really need here is the result of ComputeSize, and we // could *almost* get that from an SizeComputationInput, except for the // last argument.
WritingMode frWM = aFloatAvoidingBlock->GetWritingMode();
LogicalSize availSpace =
LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).ConvertTo(frWM, wm);
ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
aFloatAvoidingBlock, availSpace);
result.borderBoxISize =
reflowInput.ComputedSizeWithBorderPadding(wm).ISize(wm);
// Use the margins from sizingInput rather than reflowInput so that // they aren't reduced by ignoring margins in overconstrained cases.
SizeComputationInput sizingInput(aFloatAvoidingBlock,
aState.mReflowInput.mRenderingContext, wm,
aState.mContentArea.ISize(wm)); const LogicalMargin computedMargin = sizingInput.ComputedLogicalMargin(wm);
nscoord marginISize = computedMargin.IStartEnd(wm); constauto iSize = reflowInput.mStylePosition->ISize(
wm, AnchorPosResolutionParams::From(&reflowInput)); if (marginISize < 0 &&
(iSize->IsAuto() || iSize->BehavesLikeStretchOnInlineAxis())) { // If we get here, floatAvoidingBlock has a negative amount of inline-axis // margin and an 'auto' (or ~equivalently, -moz-available) inline // size. Under these circumstances, we use the margin to establish a // (positive) minimum size for the border-box, in order to satisfy the // equation in CSS2 10.3.3. That equation essentially simplifies to the // following: // // iSize of margins + iSize of borderBox = iSize of containingBlock // // ...where "iSize of borderBox" is the sum of floatAvoidingBlock's // inline-axis components of border, padding, and {width,height}. // // Right now, in the above equation, "iSize of margins" is the only term // that we know for sure. (And we also know that it's negative, since we // got here.) The other terms are as-yet unresolved, since the frame has an // 'auto' iSize, and since we aren't yet sure if we'll clear this frame // beyond floats or place it alongside them. // // However: we *do* know that the equation's "iSize of containingBlock" // term *must* be non-negative, since boxes' widths and heights generally // can't be negative in CSS. To satisfy that requirement, we can then // infer that the equation's "iSize of borderBox" term *must* be large // enough to cancel out the (known-to-be-negative) "iSize of margins" // term. Therefore, marginISize value (negated to make it positive) // establishes a lower-bound for how much inline-axis space our border-box // will really require in order to fit alongside any floats. // // XXXdholbert This explanation is admittedly a bit hand-wavy and may not // precisely match what any particular spec requires. It's the best // reasoning I could come up with to explain engines' behavior. Also, our // behavior with -moz-available doesn't seem particularly correct here, per // bug 1767217, though that's probably due to a bug elsewhere in our float // handling code...
result.borderBoxISize = std::max(result.borderBoxISize, -marginISize);
}
if (HasColumnSpanSiblings()) {
MOZ_ASSERT(LastInFlow()->GetNextContinuation(), "Frame constructor should've created column-span siblings!");
// If a block is split by any column-spans, we calculate the final // block-size by shrinkwrapping our children's block-size for all the // fragments except for those after the final column-span, but we should // take no more than our effective border-box block-size. If there's any // leftover block-size, our next continuations will take up rest. // // We don't need to adjust aBri.mReflowStatus because our children's status // is the same as ours. return std::min(effectiveBorderBoxBSize, aBEndEdgeOfChildren);
}
// The maximum value our content-box block-size can take within the given // available block-size. const nscoord maxContentBoxBSize = aState.ContentBSize();
// The block-end edge of our content-box (relative to this frame's origin) if // we consumed the maximum block-size available to us (maxContentBoxBSize). const nscoord maxContentBoxBEnd = aState.ContentBEnd();
// These variables are uninitialized intentionally so that the compiler can // check they are assigned in every if-else branch below.
nscoord finalContentBoxBSizeWithBStartBP; bool isOurStatusComplete;
if (effectiveBorderBoxBSize <= availBSize) { // Our effective border-box block-size can fit in the available block-size, // so we are complete.
finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
isOurStatusComplete = true;
} elseif (effectiveContentBoxBSizeWithBStartBP <= maxContentBoxBEnd) { // Note: The following assertion should generally hold because, for // box-decoration-break:clone, this "else if" branch is mathematically // equivalent to the initial "if".
NS_ASSERTION(!isBDBClone, "This else-if branch is handling a situation that's specific " "to box-decoration-break:slice, i.e. a case when we can skip " "our block-end border and padding!");
// Our effective content-box block-size plus the block-start border and // padding can fit in the available block-size, but it cannot fit after // adding the block-end border and padding. Thus, we need a continuation // (unless we already weren't asking for any block-size, in which case we // stay complete to avoid looping forever).
finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
isOurStatusComplete = effectiveContentBoxBSize == 0;
} else { // We aren't going to be able to fit our content-box in the space available // to it, which means we'll probably call ourselves incomplete to request a // continuation. But before making that decision, we check for certain // conditions which would force us to overflow beyond the available space -- // these might result in us actually being complete if we're forced to // overflow far enough. if (MOZ_UNLIKELY(aState.mReflowInput.mFlags.mIsTopOfPage && isBDBClone &&
maxContentBoxBSize <= 0 &&
aBEndEdgeOfChildren == blockStartBP)) { // In this rare case, we are at the top of page/column, we have // box-decoration-break:clone and zero available block-size for our // content-box (e.g. our own block-start border and padding already exceed // the available block-size), and we didn't lay out any child to consume // our content-box block-size. To ensure we make progress (avoid looping // forever), use 1px as our content-box block-size regardless of our // effective content-box block-size, in the spirit of // https://drafts.csswg.org/css-break/#breaking-rules.
finalContentBoxBSizeWithBStartBP = blockStartBP + AppUnitsPerCSSPixel();
isOurStatusComplete = effectiveContentBoxBSize <= AppUnitsPerCSSPixel();
} elseif (aBEndEdgeOfChildren > maxContentBoxBEnd) { // We have a unbreakable child whose block-end edge exceeds the available // block-size for children. if (aBEndEdgeOfChildren >= effectiveContentBoxBSizeWithBStartBP) { // The unbreakable child's block-end edge forces us to consume all of // our effective content-box block-size.
finalContentBoxBSizeWithBStartBP = effectiveContentBoxBSizeWithBStartBP;
// Even though we've consumed all of our effective content-box // block-size, we may still need to report an incomplete status in order // to get another continuation, which will be responsible for laying out // & drawing our block-end border & padding. But if we have no such // border & padding, or if we're forced to apply that border & padding // on this frame due to box-decoration-break:clone, then we don't need // to bother with that additional continuation.
isOurStatusComplete = (isBDBClone || blockEndBP == 0);
} else { // The unbreakable child's block-end edge doesn't force us to consume // all of our effective content-box block-size.
finalContentBoxBSizeWithBStartBP = aBEndEdgeOfChildren;
isOurStatusComplete = false;
}
} else { // The children's block-end edge can fit in the content-box space that we // have available for it. Consume all the space that is available so that // our inline-start/inline-end borders extend all the way to the block-end // edge of column/page.
finalContentBoxBSizeWithBStartBP = maxContentBoxBEnd;
isOurStatusComplete = false;
}
}
nscoord finalBorderBoxBSize = finalContentBoxBSizeWithBStartBP; if (isOurStatusComplete) {
finalBorderBoxBSize = NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP); if (isChildStatusComplete) { // We want to use children's reflow status as ours, which can be overflow // incomplete. Suppress the urge to call aBri.mReflowStatus.Reset() here.
} else {
aState.mReflowStatus.SetOverflowIncomplete();
}
} else {
NS_ASSERTION(!IsTrueOverflowContainer(), "An overflow container should always be complete because of " "its zero border-box block-size!"); if (isBDBClone) {
finalBorderBoxBSize =
NSCoordSaturatingAdd(finalBorderBoxBSize, blockEndBP);
}
aState.mReflowStatus.SetIncomplete(); if (!GetNextInFlow()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
}
}
return finalBorderBoxBSize;
}
nsresult nsBlockFrame::ResolveBidi() {
NS_ASSERTION(!GetPrevInFlow(), "ResolveBidi called on non-first continuation");
MOZ_ASSERT(PresContext()->BidiEnabled()); return nsBidiPresUtils::Resolve(this);
}
void nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState) { // first-letter needs to be updated before first-line, because first-line can // change the style of the first-letter. if (HasFirstLetterChild()) {
UpdateFirstLetterStyle(aRestyleState);
}
// FIXME(bz): Can we make first-line continuations be non-inheriting anon // boxes?
RefPtr<ComputedStyle> continuationStyle =
aRestyleState.StyleSet().ResolveInheritingAnonymousBoxStyle(
PseudoStyleType::MozLineFrame, parentStyle);
// We also want to update the styles of the first-line's descendants. We // don't need to compute a changehint for this, though, since any changes to // them are handled by the first-line anyway.
RestyleManager* manager = PresContext()->RestyleManager(); for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
manager->ReparentComputedStyleForFirstLine(kid);
}
}
}
nsIFrame* nsBlockFrame::GetFirstLetter() const { if (!HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE)) { // Certainly no first-letter frame. return nullptr;
}
#ifdef DEBUG void nsBlockFrame::VerifyLines(bool aFinalCheckOK) { if (!gVerifyLines) { return;
} if (mLines.empty()) { return;
}
nsLineBox* cursor = GetLineCursorForQuery();
// Add up the counts on each line. Also validate that IsFirstLine is // set properly.
int32_t count = 0; for (constauto& line : Lines()) { if (&line == cursor) {
cursor = nullptr;
} if (aFinalCheckOK) {
MOZ_ASSERT(line.GetChildCount(), "empty line"); if (line.IsBlock()) {
NS_ASSERTION(1 == line.GetChildCount(), "bad first line");
}
}
count += line.GetChildCount();
}
// Then count the frames
int32_t frameCount = 0;
nsIFrame* frame = mLines.front()->mFirstChild; while (frame) {
frameCount++;
frame = frame->GetNextSibling();
}
NS_ASSERTION(count == frameCount, "bad line list");
// Next: test that each line has right number of frames on it for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;) {
count = line->GetChildCount();
frame = line->mFirstChild; while (--count >= 0) {
frame = frame->GetNextSibling();
}
++line; if ((line != line_end) && (0 != line->GetChildCount())) {
NS_ASSERTION(frame == line->mFirstChild, "bad line list");
}
}
if (cursor) {
FrameLines* overflowLines = GetOverflowLines(); if (overflowLines) {
LineIterator line = overflowLines->mLines.begin();
LineIterator line_end = overflowLines->mLines.end(); for (; line != line_end; ++line) { if (line == cursor) {
cursor = nullptr; break;
}
}
}
}
NS_ASSERTION(!cursor, "stale LineCursorProperty");
}
void nsBlockFrame::VerifyOverflowSituation() { // Overflow out-of-flows must not have a next-in-flow in floats list or // mFrames.
nsFrameList* oofs = GetOverflowOutOfFlows(); if (oofs) { for (nsIFrame* f : *oofs) {
nsIFrame* nif = f->GetNextInFlow();
MOZ_ASSERT(!nif ||
(!GetChildList(FrameChildListID::Float).ContainsFrame(nif) &&
!mFrames.ContainsFrame(nif)));
}
}
// Pushed floats must not have a next-in-flow in floats list or mFrames.
oofs = GetPushedFloats(); if (oofs) { for (nsIFrame* f : *oofs) {
nsIFrame* nif = f->GetNextInFlow();
MOZ_ASSERT(!nif ||
(!GetChildList(FrameChildListID::Float).ContainsFrame(nif) &&
!mFrames.ContainsFrame(nif)));
}
}
// A child float next-in-flow's parent must be |this| or a next-in-flow of // |this|. Later next-in-flows must have the same or later parents.
ChildListID childLists[] = {FrameChildListID::Float,
FrameChildListID::PushedFloats}; for (constauto childList : childLists) { const nsFrameList& children = GetChildList(childList); for (nsIFrame* f : children) {
nsIFrame* parent = this;
nsIFrame* nif = f->GetNextInFlow(); for (; nif; nif = nif->GetNextInFlow()) { bool found = false; for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) { if (nif->GetParent() == p) {
parent = p;
found = true; break;
}
}
MOZ_ASSERT(
found, "next-in-flow is a child of parent earlier in the frame tree?");
}
}
}
nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow()); while (flow) {
FrameLines* overflowLines = flow->GetOverflowLines(); if (overflowLines) {
NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty if present");
NS_ASSERTION(overflowLines->mLines.front()->mFirstChild, "bad overflow lines");
NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
overflowLines->mFrames.FirstChild(), "bad overflow frames / lines");
} auto checkCursor = [&](nsLineBox* cursor) -> bool { if (!cursor) { returntrue;
}
LineIterator line = flow->LinesBegin();
LineIterator line_end = flow->LinesEnd(); for (; line != line_end && line != cursor; ++line); if (line == line_end && overflowLines) {
line = overflowLines->mLines.begin();
line_end = overflowLines->mLines.end(); for (; line != line_end && line != cursor; ++line);
} return line != line_end;
};
MOZ_ASSERT(checkCursor(flow->GetLineCursorForDisplay()), "stale LineCursorPropertyDisplay");
MOZ_ASSERT(checkCursor(flow->GetLineCursorForQuery()), "stale LineCursorPropertyQuery");
flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.