Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* rendering object for CSS display:block, inline-block, and list-item
* boxes, also used for various anonymous boxes
*/
#include "nsBlockFrame.h"
#include "gfxContext.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/ToString.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsBlockReflowContext.h"
#include "BlockReflowInput.h"
#include "nsFontMetrics.h"
#include "nsGenericHTMLElement.h"
#include "nsLineBox.h"
#include "nsLineLayout.h"
#include "nsPlaceholderFrame.h"
#include "nsStyleConsts.h"
#include "nsFrameManager.h"
#include "nsPresContext.h"
#include "nsPresContextInlines.h"
#include "nsHTMLParts.h"
#include "nsGkAtoms.h"
#include "nsAttrValueInlines.h"
#include "mozilla/Sprintf.h"
#include "nsFloatManager.h"
#include "prenv.h"
#include "plstr.h"
#include "nsError.h"
#include "nsIScrollableFrame.h"
#include <algorithm>
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsCSSAnonBoxes.h"
#include "nsCSSFrameConstructor.h"
#include "TextOverflow.h"
#include "nsIFrameInlines.h"
#include "CounterStyleManager.h"
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/PresShell.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/Telemetry.h"
#include "nsFlexContainerFrame.h"
#include "nsBidiPresUtils.h"
#include <inttypes.h>
static const int MIN_LINES_NEEDING_CURSOR = 20;
using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::layout;
using AbsPosReflowFlags = nsAbsoluteContainingBlock::AbsPosReflowFlags;
using ClearFloatsResult = BlockReflowInput::ClearFloatsResult;
using ShapeType = nsFloatManager::ShapeType;
static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) {
for (auto& line : aBlock->Lines()) {
if (line.IsBlock()) {
nsBlockFrame* bf = do_QueryFrame(line.mFirstChild);
if (bf) {
MarkAllDescendantLinesDirty(bf);
}
}
line.MarkDirty();
}
}
static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) {
nsBlockFrame* blockWithFloatMgr = aBlock;
while (!blockWithFloatMgr->HasAnyStateBits(NS_BLOCK_FLOAT_MGR)) {
nsBlockFrame* bf = do_QueryFrame(blockWithFloatMgr->GetParent());
if (!bf) {
break;
}
blockWithFloatMgr = bf;
}
// 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);
}
/**
* Returns true if aFrame is a block that has one or more float children.
*/
static bool BlockHasAnyFloats(nsIFrame* aFrame) {
nsBlockFrame* block = do_QueryFrame(aFrame);
if (!block) return false;
if (block->GetChildList(nsIFrame::kFloatList).FirstChild()) return true;
for (const auto& line : block->Lines()) {
if (line.IsBlock() && BlockHasAnyFloats(line.mFirstChild)) {
return true;
}
}
return false;
}
/**
* Determines whether the given frame is visible or has
* visible children that participate in the same line. Frames
* that are not line participants do not have their
* children checked.
*/
static bool FrameHasVisibleInlineContent(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Frame argument cannot be null");
if (aFrame->StyleVisibility()->IsVisible()) {
return true;
}
if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
for (nsIFrame* kid : aFrame->PrincipalChildList()) {
if (kid->StyleVisibility()->IsVisible() ||
FrameHasVisibleInlineContent(kid)) {
return true;
}
}
}
return false;
}
/**
* Determines whether any of the frames descended from the
* given line have inline content with 'visibility: visible'.
* This function calls FrameHasVisibleInlineContent to process
* each frame in the line's child list.
*/
static bool LineHasVisibleInlineContent(nsLineBox* aLine) {
nsIFrame* kid = aLine->mFirstChild;
int32_t n = aLine->GetChildCount();
while (n-- > 0) {
if (FrameHasVisibleInlineContent(kid)) {
return true;
}
kid = kid->GetNextSibling();
}
return false;
}
/**
* Iterates through the frame's in-flow children and
* unions the ink overflow of all text frames which
* participate in the line aFrame belongs to.
* If a child of aFrame is not a text frame,
* we recurse with the child as the aFrame argument.
* If aFrame isn't a line participant, we skip it entirely
* and return an empty rect.
* The resulting nsRect is offset relative to the parent of aFrame.
*/
static nsRect GetFrameTextArea(nsIFrame* aFrame,
nsDisplayListBuilder* aBuilder) {
nsRect textArea;
if (aFrame->IsTextFrame()) {
textArea = aFrame->InkOverflowRect();
} else if (aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) {
for (nsIFrame* kid : aFrame->PrincipalChildList()) {
nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
textArea.OrWith(kidTextArea);
}
}
// add aFrame's position to keep textArea relative to aFrame's parent
return textArea + aFrame->GetPosition();
}
/**
* Iterates through the line's children and
* unions the ink overflow of all text frames.
* GetFrameTextArea unions and returns the ink overflow
* from all line-participating text frames within the given child.
* The nsRect returned from GetLineTextArea is offset
* relative to the given line.
*/
static nsRect GetLineTextArea(nsLineBox* aLine,
nsDisplayListBuilder* aBuilder) {
nsRect textArea;
nsIFrame* kid = aLine->mFirstChild;
int32_t n = aLine->GetChildCount();
while (n-- > 0) {
nsRect kidTextArea = GetFrameTextArea(kid, aBuilder);
textArea.OrWith(kidTextArea);
kid = kid->GetNextSibling();
}
return textArea;
}
/**
* Starting with aFrame, iterates upward through parent frames and checks for
* non-transparent background colors. If one is found, we use that as our
* backplate color. Otheriwse, we use the default background color from
* our high contrast theme.
*/
static Maybe<nscolor> GetBackplateColor(nsIFrame* aFrame) {
for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
if (frame->IsThemed()) {
return Nothing();
}
auto* bg = frame->StyleBackground();
if (bg->IsTransparent(frame)) {
continue;
}
nscolor backgroundColor = bg->BackgroundColor(frame);
if (NS_GET_A(backgroundColor) != 0) {
// NOTE: We intentionally disregard the alpha channel here for the purpose
// of the backplate, in order to guarantee contrast.
return Some(NS_RGB(NS_GET_R(backgroundColor), NS_GET_G(backgroundColor),
NS_GET_B(backgroundColor)));
}
break;
}
return Some(aFrame->PresContext()->DefaultBackgroundColor());
}
#ifdef DEBUG
# include "nsBlockDebugFlags.h"
bool nsBlockFrame::gLamePaintMetrics;
bool nsBlockFrame::gLameReflowMetrics;
bool nsBlockFrame::gNoisy;
bool nsBlockFrame::gNoisyDamageRepair;
bool nsBlockFrame::gNoisyIntrinsic;
bool nsBlockFrame::gNoisyReflow;
bool nsBlockFrame::gReallyNoisyReflow;
bool nsBlockFrame::gNoisyFloatManager;
bool nsBlockFrame::gVerifyLines;
bool nsBlockFrame::gDisableResizeOpt;
int32_t nsBlockFrame::gNoiseIndent;
struct BlockDebugFlags {
const char* name;
bool* on;
};
static const BlockDebugFlags gFlags[] = {
{"reflow", &nsBlockFrame::gNoisyReflow},
{"really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow},
{"intrinsic", &nsBlockFrame::gNoisyIntrinsic},
{"float-manager", &nsBlockFrame::gNoisyFloatManager},
{"verify-lines", &nsBlockFrame::gVerifyLines},
{"damage-repair", &nsBlockFrame::gNoisyDamageRepair},
{"lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics},
{"lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics},
{"disable-resize-opt", &nsBlockFrame::gDisableResizeOpt},
};
# define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
static void 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() {
static bool firstTime = true;
if (firstTime) {
firstTime = false;
char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS");
if (flags) {
bool error = false;
for (;;) {
char* cm = PL_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 (PL_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();
}
}
}
}
#endif
//----------------------------------------------------------------------
// Debugging support code
#ifdef DEBUG
const char* nsBlockFrame::kReflowCommandType[] = {
"ContentChanged", "StyleChanged", "ReflowDirty", "Timeout", "UserDefined",
};
const char* nsBlockFrame::LineReflowStatusToString(
LineReflowStatus aLineReflowStatus) const {
switch (aLineReflowStatus) {
case LineReflowStatus::OK:
return "LINE_REFLOW_OK";
case LineReflowStatus::Stop:
return "LINE_REFLOW_STOP";
case LineReflowStatus::RedoNoPull:
return "LINE_REFLOW_REDO_NO_PULL";
case LineReflowStatus::RedoMoreFloats:
return "LINE_REFLOW_REDO_MORE_FLOATS";
case LineReflowStatus::RedoNextBand:
return "LINE_REFLOW_REDO_NEXT_BAND";
case LineReflowStatus::Truncated:
return "LINE_REFLOW_TRUNCATED";
}
return "unknown";
}
#endif
#ifdef REFLOW_STATUS_COVERAGE
static void RecordReflowStatus(bool aChildIsBlock,
const nsReflowStatus& aFrameReflowStatus) {
static uint32_t record[2];
// 0: child-is-block
// 1: child-is-inline
int index = 0;
if (!aChildIsBlock) index |= 1;
// Compute new status
uint32_t newS = record[index];
if (aFrameReflowStatus.IsInlineBreak()) {
if (aFrameReflowStatus.IsInlineBreakBefore()) {
newS |= 1;
} else if (aFrameReflowStatus.IsIncomplete()) {
newS |= 2;
} else {
newS |= 4;
}
} else if (aFrameReflowStatus.IsIncomplete()) {
newS |= 8;
} else {
newS |= 16;
}
// 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
NS_DECLARE_FRAME_PROPERTY_WITH_DTOR_NEVER_CALLED(OverflowLinesProperty,
nsBlockFrame::FrameLines)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty)
NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideMarkerProperty)
NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideMarkerProperty, nsIFrame)
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord)
//----------------------------------------------------------------------
nsBlockFrame* NS_NewBlockFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
return new (aPresShell) nsBlockFrame(aStyle, aPresShell->GetPresContext());
}
nsBlockFrame* NS_NewBlockFormattingContext(PresShell* aPresShell,
ComputedStyle* aComputedStyle) {
nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aComputedStyle);
blockFrame->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
return blockFrame;
}
NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
nsBlockFrame::~nsBlockFrame() = default;
void nsBlockFrame::AddSizeOfExcludingThisForTree(
nsWindowSizes& aWindowSizes) const {
nsContainerFrame::AddSizeOfExcludingThisForTree(aWindowSizes);
// Add the size of any nsLineBox::mFrames hashtables we might have:
for (const auto& 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);
}
}
}
void nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
ClearLineCursor();
DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData);
mFloats.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
nsPresContext* presContext = PresContext();
mozilla::PresShell* presShell = presContext->PresShell();
nsLineBox::DeleteLineList(presContext, mLines, aDestructRoot, &mFrames,
aPostDestroyData);
if (HasPushedFloats()) {
SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, presShell,
PushedFloatProperty());
RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
}
// destroy overflow lines now
FrameLines* overflowLines = RemoveOverflowLines();
if (overflowLines) {
nsLineBox::DeleteLineList(presContext, overflowLines->mLines, aDestructRoot,
&overflowLines->mFrames, aPostDestroyData);
delete overflowLines;
}
if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, presShell,
OverflowOutOfFlowsProperty());
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
if (HasOutsideMarker()) {
SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, presShell,
OutsideMarkerProperty());
RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
}
nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
}
/* virtual */
nsILineIterator* nsBlockFrame::GetLineIterator() {
nsLineIterator* it = new nsLineIterator;
if (!it) return nullptr;
const nsStyleVisibility* visibility = StyleVisibility();
nsresult rv = it->Init(mLines, visibility->mDirection == StyleDirection::Rtl);
if (NS_FAILED(rv)) {
delete it;
return nullptr;
}
return it;
}
NS_QUERYFRAME_HEAD(nsBlockFrame)
NS_QUERYFRAME_ENTRY(nsBlockFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
#ifdef DEBUG_FRAME_DUMP
void nsBlockFrame::List(FILE* out, const char* aPrefix,
ListFlags aFlags) const {
nsCString str;
ListGeneric(str, aPrefix, aFlags);
fprintf_stderr(out, "%s <\n", str.get());
nsCString pfx(aPrefix);
pfx += " ";
// 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 = {kPrincipalList, kOverflowList};
ListChildLists(out, pfx.get(), aFlags, skip);
fprintf_stderr(out, "%s>\n", aPrefix);
}
nsresult nsBlockFrame::GetFrameName(nsAString& aResult) const {
return MakeFrameName(u"Block"_ns, aResult);
}
#endif
void nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey,
bool aRebuildDisplayItems) {
if (SVGUtils::IsInSVGTextSubtree(this)) {
NS_ASSERTION(GetParent()->IsSVGTextFrame(),
"unexpected block frame in SVG text");
GetParent()->InvalidateFrame();
return;
}
nsContainerFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
}
void nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect,
uint32_t aDisplayItemKey,
bool aRebuildDisplayItems) {
if (SVGUtils::IsInSVGTextSubtree(this)) {
NS_ASSERTION(GetParent()->IsSVGTextFrame(),
"unexpected block frame in SVG text");
GetParent()->InvalidateFrame();
return;
}
nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
aRebuildDisplayItems);
}
nscoord nsBlockFrame::GetLogicalBaseline(WritingMode aWM) const {
auto lastBaseline = BaselineBOffset(aWM, BaselineSharingGroup::Last,
AlignmentContext::Inline);
return BSize(aWM) - lastBaseline;
}
bool nsBlockFrame::GetNaturalBaselineBOffset(
mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup,
nscoord* aBaseline) const {
if (aBaselineGroup == BaselineSharingGroup::First) {
return nsLayoutUtils::GetFirstLineBaseline(aWM, this, aBaseline);
}
if (StyleDisplay()->IsContainLayout()) {
return false;
}
for (ConstReverseLineIterator line = LinesRBegin(), line_end = LinesREnd();
line != line_end; ++line) {
if (line->IsBlock()) {
nscoord offset;
nsIFrame* kid = line->mFirstChild;
if (!aWM.IsOrthogonalTo(kid->GetWritingMode()) &&
kid->GetVerticalAlignBaseline(aWM, &offset)) {
// Ignore relative positioning for baseline calculations.
const nsSize& sz = line->mContainerSize;
offset += kid->GetLogicalNormalPosition(aWM, sz).B(aWM);
*aBaseline = BSize(aWM) - offset;
return true;
}
} else {
// XXX Is this the right test? We have some bogus empty lines
// floating around, but IsEmpty is perhaps too weak.
if (line->BSize() != 0 || !line->IsEmpty()) {
*aBaseline = BSize(aWM) - (line->BStart() + line->GetLogicalAscent());
return true;
}
}
}
return false;
}
nscoord nsBlockFrame::GetCaretBaseline() const {
nsRect contentRect = GetContentRect();
nsMargin bp = GetUsedBorderAndPadding();
if (!mLines.empty()) {
ConstLineIterator line = LinesBegin();
if (!line->IsEmpty()) {
if (line->IsBlock()) {
return bp.top + line->mFirstChild->GetCaretBaseline();
}
return line->BStart() + line->GetLogicalAscent();
}
}
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
nscoord lineHeight = ReflowInput::CalcLineHeight(
GetContent(), Style(), PresContext(), contentRect.height, inflation);
const WritingMode wm = GetWritingMode();
return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
wm.IsLineInverted()) +
bp.top;
}
/////////////////////////////////////////////////////////////////////////////
// Child frame enumeration
const nsFrameList& nsBlockFrame::GetChildList(ChildListID aListID) const {
switch (aListID) {
case kPrincipalList:
return mFrames;
case kOverflowList: {
FrameLines* overflowLines = GetOverflowLines();
return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
}
case kFloatList:
return mFloats;
case kOverflowOutOfFlowList: {
const nsFrameList* list = GetOverflowOutOfFlows();
return list ? *list : nsFrameList::EmptyList();
}
case kPushedFloatsList: {
const nsFrameList* list = GetPushedFloats();
return list ? *list : nsFrameList::EmptyList();
}
case kBulletList: {
const nsFrameList* list = GetOutsideMarkerList();
return list ? *list : nsFrameList::EmptyList();
}
default:
return nsContainerFrame::GetChildList(aListID);
}
}
void nsBlockFrame::GetChildLists(nsTArray<ChildList>* aLists) const {
nsContainerFrame::GetChildLists(aLists);
FrameLines* overflowLines = GetOverflowLines();
if (overflowLines) {
overflowLines->mFrames.AppendIfNonempty(aLists, kOverflowList);
}
const nsFrameList* list = GetOverflowOutOfFlows();
if (list) {
list->AppendIfNonempty(aLists, kOverflowOutOfFlowList);
}
mFloats.AppendIfNonempty(aLists, kFloatList);
list = GetOutsideMarkerList();
if (list) {
list->AppendIfNonempty(aLists, kBulletList);
}
list = GetPushedFloats();
if (list) {
list->AppendIfNonempty(aLists, kPushedFloatsList);
}
}
/* virtual */
bool nsBlockFrame::IsFloatContainingBlock() const { return true; }
/**
* Remove the first line from aFromLines and adjust the associated frame list
* aFromFrames accordingly. The removed line is assigned to *aOutLine and
* a frame list with its frames is assigned to *aOutFrames, i.e. the frames
* that were extracted from the head of aFromFrames.
* aFromLines must contain at least one line, the line may be empty.
* @return true if aFromLines becomes empty
*/
static bool RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames,
nsLineBox** aOutLine, nsFrameList* aOutFrames) {
nsLineList_iterator removedLine = aFromLines.begin();
*aOutLine = removedLine;
nsLineList_iterator next = aFromLines.erase(removedLine);
bool isLastLine = next == aFromLines.end();
nsIFrame* lastFrame = isLastLine ? aFromFrames.LastChild()
: next->mFirstChild->GetPrevSibling();
nsFrameList::FrameLinkEnumerator linkToBreak(aFromFrames, lastFrame);
*aOutFrames = aFromFrames.ExtractHead(linkToBreak);
return isLastLine;
}
//////////////////////////////////////////////////////////////////////
// Reflow methods
/* virtual */
void nsBlockFrame::MarkIntrinsicISizesDirty() {
nsBlockFrame* dirtyBlock = static_cast<nsBlockFrame*>(FirstContinuation());
dirtyBlock->mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
dirtyBlock->mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
if (!HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION)) {
for (nsIFrame* frame = dirtyBlock; frame;
frame = frame->GetNextContinuation()) {
frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
}
}
nsContainerFrame::MarkIntrinsicISizesDirty();
}
void nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() {
nsPresContext* presContext = PresContext();
if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) {
return;
}
bool inflationEnabled = !presContext->mInflationDisabledForShrinkWrap;
if (inflationEnabled != HasAnyStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED)) {
mCachedMinISize = NS_INTRINSIC_ISIZE_UNKNOWN;
mCachedPrefISize = NS_INTRINSIC_ISIZE_UNKNOWN;
if (inflationEnabled) {
AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
} else {
RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED);
}
}
}
/* virtual */
nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
nsIFrame* firstInFlow = FirstContinuation();
if (firstInFlow != this) return firstInFlow->GetMinISize(aRenderingContext);
DISPLAY_MIN_INLINE_SIZE(this, mCachedMinISize);
CheckIntrinsicCacheAgainstShrinkWrapState();
if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
return mCachedMinISize;
}
if (StyleDisplay()->IsContainSize()) {
mCachedMinISize = 0;
return mCachedMinISize;
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": GetMinISize\n");
}
AutoNoisyIndenter indenter(gNoisyIntrinsic);
#endif
for (nsBlockFrame* curFrame = this; curFrame;
curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
curFrame->LazyMarkLinesDirty();
}
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) {
ResolveBidi();
}
const bool whiteSpaceCanWrap = StyleText()->WhiteSpaceCanWrapStyle();
InlineMinISizeData 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) {
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
line->IsEmpty() ? ", empty" : "");
}
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
#endif
if (line->IsBlock()) {
data.ForceBreak();
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
aRenderingContext, line->mFirstChild, IntrinsicISizeType::MinISize);
data.ForceBreak();
} else {
if (!curFrame->GetPrevContinuation() &&
line == curFrame->LinesBegin()) {
data.mCurrentLine += StyleText()->mTextIndent.Resolve(0);
}
data.mLine = &line;
data.SetLineContainer(curFrame);
nsIFrame* kid = line->mFirstChild;
for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
++i, kid = kid->GetNextSibling()) {
kid->AddInlineMinISize(aRenderingContext, &data);
if (whiteSpaceCanWrap && data.mTrailingWhitespace) {
data.OptionallyBreak();
}
}
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("min: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
data.mCurrentLine);
}
#endif
}
}
data.ForceBreak();
mCachedMinISize = data.mPrevLines;
return mCachedMinISize;
}
/* virtual */
nscoord nsBlockFrame::GetPrefISize(gfxContext* aRenderingContext) {
nsIFrame* firstInFlow = FirstContinuation();
if (firstInFlow != this) return firstInFlow->GetPrefISize(aRenderingContext);
DISPLAY_PREF_INLINE_SIZE(this, mCachedPrefISize);
CheckIntrinsicCacheAgainstShrinkWrapState();
if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
return mCachedPrefISize;
}
if (StyleDisplay()->IsContainSize()) {
mCachedPrefISize = 0;
return mCachedPrefISize;
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": GetPrefISize\n");
}
AutoNoisyIndenter indenter(gNoisyIntrinsic);
#endif
for (nsBlockFrame* curFrame = this; curFrame;
curFrame = static_cast<nsBlockFrame*>(curFrame->GetNextContinuation())) {
curFrame->LazyMarkLinesDirty();
}
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) {
ResolveBidi();
}
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) {
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("line (%s%s)\n", line->IsBlock() ? "block" : "inline",
line->IsEmpty() ? ", empty" : "");
}
AutoNoisyIndenter lineindent(gNoisyIntrinsic);
#endif
if (line->IsBlock()) {
StyleClear breakType;
if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
breakType = StyleClear::Both;
} else {
breakType = line->mFirstChild->StyleDisplay()->mBreakType;
}
data.ForceBreak(breakType);
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
aRenderingContext, line->mFirstChild,
IntrinsicISizeType::PrefISize);
data.ForceBreak();
} else {
if (!curFrame->GetPrevContinuation() &&
line == curFrame->LinesBegin()) {
nscoord indent = StyleText()->mTextIndent.Resolve(0);
data.mCurrentLine += indent;
// XXXmats should the test below be indent > 0?
if (indent != nscoord(0)) {
data.mLineIsEmpty = false;
}
}
data.mLine = &line;
data.SetLineContainer(curFrame);
nsIFrame* kid = line->mFirstChild;
for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
++i, kid = kid->GetNextSibling()) {
kid->AddInlinePrefISize(aRenderingContext, &data);
}
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("pref: [prevLines=%d currentLine=%d]\n", data.mPrevLines,
data.mCurrentLine);
}
#endif
}
}
data.ForceBreak();
mCachedPrefISize = data.mPrevLines;
return mCachedPrefISize;
}
nsRect nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
// be conservative
if (Style()->HasTextDecorationLines()) {
return InkOverflowRect();
}
return ComputeSimpleTightBounds(aDrawTarget);
}
/* virtual */
nsresult nsBlockFrame::GetPrefWidthTightBounds(gfxContext* aRenderingContext,
nscoord* aX, nscoord* aXMost) {
nsIFrame* firstInFlow = FirstContinuation();
if (firstInFlow != this) {
return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost);
}
*aX = 0;
*aXMost = 0;
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() &&
line == curFrame->LinesBegin()) {
data.mCurrentLine += StyleText()->mTextIndent.Resolve(0);
}
data.mLine = &line;
data.SetLineContainer(curFrame);
nsIFrame* kid = line->mFirstChild;
for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end;
++i, kid = kid->GetNextSibling()) {
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(aRenderingContext, &data);
}
}
}
}
data.ForceBreak();
return NS_OK;
}
/**
* Return whether aNewAvailableSpace is smaller *on either side*
* (inline-start or inline-end) than aOldAvailableSpace, so that we know
* if we need to redo layout on an line, replaced block, or block
* formatting context, because its height (which we used to compute
* aNewAvailableSpace) caused it to intersect additional floats.
*/
static bool 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);
}
static LogicalSize CalculateContainingBlockSizeForAbsolutes(
WritingMode aWM, const ReflowInput& aReflowInput, LogicalSize aFrameSize) {
// The issue here is that for a 'height' of 'auto' the reflow input
// code won't know how to calculate the containing block height
// because it's calculated bottom up. So we use our own computed
// size as the dimensions.
nsIFrame* frame = aReflowInput.mFrame;
LogicalSize cbSize(aFrameSize);
// Containing block is relative to the padding edge
const LogicalMargin& border =
LogicalMargin(aWM, aReflowInput.ComputedPhysicalBorderPadding() -
aReflowInput.ComputedPhysicalPadding());
cbSize.ISize(aWM) -= border.IStartEnd(aWM);
cbSize.BSize(aWM) -= border.BStartEnd(aWM);
if (frame->GetParent()->GetContent() == frame->GetContent() &&
!frame->GetParent()->IsCanvasFrame()) {
// We are a wrapped frame for the content (and the wrapper is not the
// canvas frame, whose size is not meaningful here).
// Use the container's dimensions, if they have been precomputed.
// XXX This is a hack! We really should be waiting until the outermost
// frame is fully reflowed and using the resulting dimensions, even
// if they're intrinsic.
// In fact we should be attaching absolute children to the outermost
// frame and not always sticking them in block frames.
// First, find the reflow input for the outermost frame for this
// content, except for fieldsets where the inner anonymous frame has
// the correct padding area with the legend taken into account.
const ReflowInput* aLastRI = &aReflowInput;
const ReflowInput* lastButOneRI = &aReflowInput;
while (aLastRI->mParentReflowInput &&
aLastRI->mParentReflowInput->mFrame->GetContent() ==
frame->GetContent() &&
!aLastRI->mParentReflowInput->mFrame->IsFieldSetFrame()) {
lastButOneRI = aLastRI;
aLastRI = aLastRI->mParentReflowInput;
}
if (aLastRI != &aReflowInput) {
// Scrollbars need to be specifically excluded, if present, because they
// are outside the padding-edge. We need better APIs for getting the
// various boxes from a frame.
nsIScrollableFrame* scrollFrame = do_QueryFrame(aLastRI->mFrame);
nsMargin scrollbars(0, 0, 0, 0);
if (scrollFrame) {
scrollbars = scrollFrame->GetDesiredScrollbarSizes(
aLastRI->mFrame->PresContext(), aLastRI->mRenderingContext);
if (!lastButOneRI->mFlags.mAssumingHScrollbar) {
scrollbars.top = scrollbars.bottom = 0;
}
if (!lastButOneRI->mFlags.mAssumingVScrollbar) {
scrollbars.left = scrollbars.right = 0;
}
}
// We found a reflow input for the outermost wrapping frame, so use
// its computed metrics if available, converted to our writing mode
WritingMode lastWM = aLastRI->GetWritingMode();
LogicalSize lastRISize = aLastRI->ComputedSize().ConvertTo(aWM, lastWM);
LogicalMargin lastRIPadding = aLastRI->ComputedLogicalPadding(aWM);
LogicalMargin logicalScrollbars(aWM, scrollbars);
if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
cbSize.ISize(aWM) =
std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM) -
logicalScrollbars.IStartEnd(aWM));
}
if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
cbSize.BSize(aWM) =
std::max(0, lastRISize.BSize(aWM) + lastRIPadding.BStartEnd(aWM) -
logicalScrollbars.BStartEnd(aWM));
}
}
}
return cbSize;
}
/**
* Returns aFrame if it is a non-BFC block frame, and null otherwise.
*
* This is used to determine whether to recurse into aFrame when applying
* -webkit-line-clamp.
*/
static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
if (nsBlockFrame* block = do_QueryFrame(aFrame)) {
if (!block->HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)) {
return block;
}
}
return nullptr;
}
/**
* Iterator over all descendant inline line boxes, except for those that are
* under an independent formatting context.
*/
class MOZ_RAII LineClampLineIterator {
public:
explicit LineClampLineIterator(nsBlockFrame* aFrame)
: mCur(aFrame->LinesBegin()),
mEnd(aFrame->LinesEnd()),
mCurrentFrame(mCur == mEnd ? nullptr : aFrame) {
if (mCur != mEnd && !mCur->IsInline()) {
Advance();
}
}
nsLineBox* GetCurrentLine() { return mCurrentFrame ? mCur.get() : nullptr; }
nsBlockFrame* GetCurrentFrame() { return mCurrentFrame; }
// 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;
}
auto entry = mStack.PopLastElement();
mCurrentFrame = entry.first;
mCur = entry.second;
mEnd = mCurrentFrame->LinesEnd();
} else if (mCur->IsBlock()) {
if (nsBlockFrame* child = GetAsLineClampDescendant(mCur->mFirstChild)) {
nsBlockFrame::LineIterator next = mCur;
++next;
mStack.AppendElement(std::make_pair(mCurrentFrame, next));
mCur = child->LinesBegin();
mEnd = child->LinesEnd();
mCurrentFrame = child;
} else {
// Some kind of frame we shouldn't descend into.
++mCur;
}
} else {
MOZ_ASSERT(mCur->IsInline());
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;
// 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;
};
static bool ClearLineClampEllipsis(nsBlockFrame* aFrame) {
if (!aFrame->HasAnyStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS)) {
for (nsIFrame* f : aFrame->PrincipalChildList()) {
if (nsBlockFrame* child = GetAsLineClampDescendant(f)) {
if (ClearLineClampEllipsis(child)) {
return true;
}
}
}
return false;
}
aFrame->RemoveStateBits(NS_BLOCK_HAS_LINE_CLAMP_ELLIPSIS);
for (auto& line : aFrame->Lines()) {
if (line.HasLineClampEllipsis()) {
line.ClearHasLineClampEllipsis();
return true;
}
}
// We didn't find a line with the ellipsis; it must have been deleted already.
return true;
}
void nsBlockFrame::ClearLineClampEllipsis() { ::ClearLineClampEllipsis(this); }
static bool IsLineClampItem(const ReflowInput& aReflowInput) {
return aReflowInput.mFlags.mApplyLineClamp ||
(aReflowInput.mParentReflowInput &&
aReflowInput.mParentReflowInput->mFrame->IsScrollFrame() &&
aReflowInput.mParentReflowInput->mFlags.mApplyLineClamp);
}
void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n",
aReflowInput.AvailableISize(), aReflowInput.AvailableBSize(),
aReflowInput.ComputedISize(), aReflowInput.ComputedBSize());
}
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);
}
const ReflowInput* reflowInput = &aReflowInput;
WritingMode wm = aReflowInput.GetWritingMode();
nscoord consumedBSize = CalcAndCacheConsumedBSize();
nscoord effectiveComputedBSize =
GetEffectiveComputedBSize(aReflowInput, consumedBSize);
Maybe<ReflowInput> mutableReflowInput;
// If we have non-auto block size, we're clipping our kids and we fit,
// make sure our kids fit too.
const PhysicalAxes physicalBlockAxis =
wm.IsVertical() ? PhysicalAxes::Horizontal : PhysicalAxes::Vertical;
if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
aReflowInput.ComputedBSize() != NS_UNCONSTRAINEDSIZE &&
(ShouldApplyOverflowClipping(aReflowInput.mStyleDisplay) &
physicalBlockAxis)) {
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 (effectiveComputedBSize + blockDirExtras.BStartEnd(wm) <=
aReflowInput.AvailableBSize()) {
mutableReflowInput.emplace(aReflowInput);
mutableReflowInput->AvailableBSize() = NS_UNCONSTRAINEDSIZE;
reflowInput = mutableReflowInput.ptr();
}
}
// 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&>(*reflowInput));
// 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.
bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
if (needFloatManager) autoFloatManager.CreateFloatManager(aPresContext);
// OK, some lines may be reflowed. Blow away any saved line cursor
// because we may invalidate the nondecreasing
// overflowArea.InkOverflow().y/yMost invariant, and we may even
// delete the line with the line cursor.
ClearLineCursor();
if (IsFrameTreeTooDeep(*reflowInput, aMetrics, aStatus)) {
return;
}
#ifdef DEBUG
// Between when we drain pushed floats and when we complete reflow,
// we're allowed to have multiple continuations of the same float on
// our floats list, since a first-in-flow might get pushed to a later
// continuation of its containing block. But it's not permitted
// outside that time.
nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
#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();
bool blockStartMarginRoot, blockEndMarginRoot;
IsMarginRoot(&blockStartMarginRoot, &blockEndMarginRoot);
// Cache the consumed height in the block reflow input so that we don't have
// to continually recompute it.
BlockReflowInput state(*reflowInput, aPresContext, this, blockStartMarginRoot,
blockEndMarginRoot, needFloatManager, consumedBSize);
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) {
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
}
// Handle paginated overflow (see nsContainerFrame.h)
OverflowAreas ocBounds;
nsReflowStatus ocStatus;
if (GetPrevInFlow()) {
ReflowOverflowContainerChildren(
aPresContext, *reflowInput, ocBounds, 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;
// Drain & handle pushed floats
DrainPushedFloats();
OverflowAreas fcBounds;
ReflowPushedFloats(state, fcBounds);
// If we're not dirty (which means we'll mark everything dirty later)
// and our inline-size has changed, mark the lines dirty that we need to
// mark dirty for a resize reflow.
if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && reflowInput->IsIResize()) {
PrepareResizeReflow(state);
}
// The same for percentage text-indent, except conditioned on the
// parent resizing.
if (!HasAnyStateBits(NS_FRAME_IS_DIRTY) && reflowInput->mCBReflowInput &&
reflowInput->mCBReflowInput->IsIResize() &&
reflowInput->mStyleText->mTextIndent.HasPercent() && !mLines.empty()) {
mLines.front()->MarkDirty();
}
LazyMarkLinesDirty();
// Now reflow...
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;
}
nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
}
}
state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
// If we end in a BR with clear and affected floats continue,
// we need to continue, too.
if (NS_UNCONSTRAINEDSIZE != reflowInput->AvailableBSize() &&
state.mReflowStatus.IsComplete() &&
state.FloatManager()->ClearContinues(FindTrailingClear())) {
state.mReflowStatus.SetIncomplete();
}
if (!state.mReflowStatus.IsFullyComplete()) {
if (HasOverflowLines() || HasPushedFloats()) {
state.mReflowStatus.SetNextInFlowNeedsReflow();
}
#ifdef DEBUG_kipp
ListTag(stdout);
printf(": block is not fully complete\n");
#endif
}
// 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.
if (HasOutsideMarker() && !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
: reflowInput->ComputedLogicalBorderPadding(wm).BStart(wm);
nsIFrame* marker = GetOutsideMarker();
ReflowOutsideMarker(marker, state, reflowOutput, lineBStart);
NS_ASSERTION(!MarkerIsEmpty() || reflowOutput.BSize(wm) == 0,
"empty ::marker frame took up space");
if (havePosition && !MarkerIsEmpty()) {
// 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 =
marker->GetLogicalRect(wm, reflowOutput.PhysicalSize());
const auto baselineGroup = BaselineSharingGroup::First;
nscoord markerBaseline;
if (MOZ_UNLIKELY(wm.IsOrthogonalTo(marker->GetWritingMode()) ||
!marker->GetNaturalBaselineBOffset(wm, baselineGroup,
&markerBaseline))) {
// ::marker has no baseline in this axis: align with its margin-box end.
markerBaseline =
bbox.BSize(wm) + marker->GetLogicalUsedMargin(wm).BEnd(wm);
}
bbox.BStart(wm) = position.mBaseline - markerBaseline;
marker->SetRect(wm, bbox, reflowOutput.PhysicalSize());
}
// Otherwise just leave the ::marker where it is, up against our
// block-start padding.
}
// Clear any existing -webkit-line-clamp ellipsis.
if (IsLineClampItem(aReflowInput)) {
ClearLineClampEllipsis();
}
CheckFloats(state);
// Compute our final size
nscoord blockEndEdgeOfChildren;
ComputeFinalSize(*reflowInput, state, aMetrics, &blockEndEdgeOfChildren);
// 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
// BlockReflowInput's constructor) and then fix up the positions of the
// lines here, once the final block size is known.
//
// Note that writing-mode:vertical-rl is the only case where the block
// logical direction progresses in a negative physical direction, and
// therefore block-dir coordinate conversion depends on knowing the width
// of the coordinate space in order to translate between the logical and
// physical origins.
if (wm.IsVerticalRL()) {
nsSize containerSize = aMetrics.PhysicalSize();
nscoord deltaX = containerSize.width - state.ContainerSize().width;
if (deltaX != 0) {
// We compute our lines and markers' overflow areas later in
// ComputeOverflowAreas(), so we don't need to adjust their overflow areas
// here.
const nsPoint physicalDelta(deltaX, 0);
for (auto& line : Lines()) {
UpdateLineContainerSize(&line, containerSize);
}
fcBounds.Clear();
for (nsIFrame* f : mFloats) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(fcBounds, f);
}
nsFrameList* markerList = GetOutsideMarkerList();
if (markerList) {
for (nsIFrame* f : *markerList) {
f->MovePositionBy(physicalDelta);
}
}
if (nsFrameList* overflowContainers = GetOverflowContainers()) {
ocBounds.Clear();
for (nsIFrame* f : *overflowContainers) {
f->MovePositionBy(physicalDelta);
ConsiderChildOverflow(ocBounds, f);
}
}
}
}
aMetrics.SetOverflowAreasToDesiredBounds();
ComputeOverflowAreas(aMetrics.mOverflowAreas, blockEndEdgeOfChildren,
reflowInput->mStyleDisplay);
// Factor overflow container child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(ocBounds);