Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* 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/AppUnits.h"
#include "mozilla/Baseline.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/ToString.h"
#include "mozilla/UniquePtr.h"
#include "nsCRT.h"
#include "nsCOMPtr.h"
#include "nsCSSRendering.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsBlockReflowContext.h"
#include "BlockReflowState.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 "mozilla/Sprintf.h"
#include "nsFloatManager.h"
#include "prenv.h"
#include "nsError.h"
#include "nsIScrollableFrame.h"
#include <algorithm>
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsCSSFrameConstructor.h"
#include "TextOverflow.h"
#include "nsIFrameInlines.h"
#include "CounterStyleManager.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/PresShell.h"
#include "mozilla/RestyleManager.h"
#include "mozilla/ServoStyleSet.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 = BlockReflowState::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_BFC)) {
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(FrameChildListID::Float).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 text or has visible text that
// participate in the same line. Frames that are not line participants do not
// have their children checked.
static bool FrameHasVisibleInlineText(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Frame argument cannot be null");
if (!aFrame->IsLineParticipant()) {
return false;
}
if (aFrame->IsTextFrame()) {
return aFrame->StyleVisibility()->IsVisible() &&
NS_GET_A(aFrame->StyleText()->mWebkitTextFillColor.CalcColor(
aFrame)) != 0;
}
for (nsIFrame* kid : aFrame->PrincipalChildList()) {
if (FrameHasVisibleInlineText(kid)) {
return true;
}
}
return false;
}
// Determines whether any of the frames from the given line have visible text.
static bool LineHasVisibleInlineText(nsLineBox* aLine) {
nsIFrame* kid = aLine->mFirstChild;
int32_t n = aLine->GetChildCount();
while (n-- > 0) {
if (FrameHasVisibleInlineText(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 (const nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
if (!textFrame->IsEntirelyWhitespace()) {
textArea = aFrame->InkOverflowRect();
}
} else if (aFrame->IsLineParticipant()) {
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 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.
const auto* 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);
}
#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 = 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();
}
}
}
}
#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());
}
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::Destroy(DestroyContext& aContext) {
ClearLineCursors();
DestroyAbsoluteFrames(aContext);
mFloats.DestroyFrames(aContext);
nsPresContext* presContext = PresContext();
mozilla::PresShell* presShell = presContext->PresShell();
nsLineBox::DeleteLineList(presContext, mLines, &mFrames, aContext);
if (HasPushedFloats()) {
SafelyDestroyFrameListProp(aContext, presShell, PushedFloatProperty());
RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
}
// destroy overflow lines now
FrameLines* overflowLines = RemoveOverflowLines();
if (overflowLines) {
nsLineBox::DeleteLineList(presContext, overflowLines->mLines,
&overflowLines->mFrames, aContext);
delete overflowLines;
}
if (HasAnyStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
SafelyDestroyFrameListProp(aContext, presShell,
OverflowOutOfFlowsProperty());
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
if (HasOutsideMarker()) {
SafelyDestroyFrameListProp(aContext, presShell, OutsideMarkerProperty());
RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER);
}
nsContainerFrame::Destroy(aContext);
}
/* virtual */
nsILineIterator* nsBlockFrame::GetLineIterator() {
nsLineIterator* iter = GetProperty(LineIteratorProperty());
if (!iter) {
const nsStyleVisibility* visibility = StyleVisibility();
iter = new nsLineIterator(mLines,
visibility->mDirection == StyleDirection::Rtl);
SetProperty(LineIteratorProperty(), iter);
}
return iter;
}
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 = {FrameChildListID::Principal, FrameChildListID::Overflow};
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 (IsInSVGTextSubtree()) {
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 (IsInSVGTextSubtree()) {
NS_ASSERTION(GetParent()->IsSVGTextFrame(),
"unexpected block frame in SVG text");
GetParent()->InvalidateFrame();
return;
}
nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
aRebuildDisplayItems);
}
nscoord nsBlockFrame::SynthesizeFallbackBaseline(
WritingMode aWM, BaselineSharingGroup aBaselineGroup) const {
return Baseline::SynthesizeBOffsetFromMarginBox(this, aWM, aBaselineGroup);
}
template <typename LineIteratorType>
Maybe<nscoord> nsBlockFrame::GetBaselineBOffset(
LineIteratorType aStart, LineIteratorType aEnd, WritingMode aWM,
BaselineSharingGroup aBaselineGroup,
BaselineExportContext aExportContext) const {
MOZ_ASSERT((std::is_same_v<LineIteratorType, ConstLineIterator> &&
aBaselineGroup == BaselineSharingGroup::First) ||
(std::is_same_v<LineIteratorType, ConstReverseLineIterator> &&
aBaselineGroup == BaselineSharingGroup::Last),
"Iterator direction must match baseline sharing group.");
for (auto line = aStart; line != aEnd; ++line) {
if (!line->IsBlock()) {
// 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()) {
const auto ascent = line->BStart() + line->GetLogicalAscent();
if (aBaselineGroup == BaselineSharingGroup::Last) {
return Some(BSize(aWM) - ascent);
}
return Some(ascent);
}
continue;
}
nsIFrame* kid = line->mFirstChild;
if (aWM.IsOrthogonalTo(kid->GetWritingMode())) {
continue;
}
if (aExportContext == BaselineExportContext::LineLayout &&
kid->IsTableWrapperFrame()) {
// `<table>` in inline-block context does not export any baseline.
continue;
}
const auto kidBaselineGroup =
aExportContext == BaselineExportContext::LineLayout
? kid->GetDefaultBaselineSharingGroup()
: aBaselineGroup;
const auto kidBaseline =
kid->GetNaturalBaselineBOffset(aWM, kidBaselineGroup, aExportContext);
if (!kidBaseline) {
continue;
}
auto result = *kidBaseline;
if (kidBaselineGroup == BaselineSharingGroup::Last) {
result = kid->BSize(aWM) - result;
}
// Ignore relative positioning for baseline calculations.
const nsSize& sz = line->mContainerSize;
result += kid->GetLogicalNormalPosition(aWM, sz).B(aWM);
if (aBaselineGroup == BaselineSharingGroup::Last) {
return Some(BSize(aWM) - result);
}
return Some(result);
}
return Nothing{};
}
Maybe<nscoord> nsBlockFrame::GetNaturalBaselineBOffset(
WritingMode aWM, BaselineSharingGroup aBaselineGroup,
BaselineExportContext aExportContext) const {
if (StyleDisplay()->IsContainLayout()) {
return Nothing{};
}
if (aBaselineGroup == BaselineSharingGroup::First) {
return GetBaselineBOffset(LinesBegin(), LinesEnd(), aWM, aBaselineGroup,
aExportContext);
}
return GetBaselineBOffset(LinesRBegin(), LinesREnd(), aWM, aBaselineGroup,
aExportContext);
}
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(
*Style(), PresContext(), GetContent(), 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 FrameChildListID::Principal:
return mFrames;
case FrameChildListID::Overflow: {
FrameLines* overflowLines = GetOverflowLines();
return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList();
}
case FrameChildListID::Float:
return mFloats;
case FrameChildListID::OverflowOutOfFlow: {
const nsFrameList* list = GetOverflowOutOfFlows();
return list ? *list : nsFrameList::EmptyList();
}
case FrameChildListID::PushedFloats: {
const nsFrameList* list = GetPushedFloats();
return list ? *list : nsFrameList::EmptyList();
}
case FrameChildListID::Bullet: {
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, FrameChildListID::Overflow);
}
const nsFrameList* list = GetOverflowOutOfFlows();
if (list) {
list->AppendIfNonempty(aLists, FrameChildListID::OverflowOutOfFlow);
}
mFloats.AppendIfNonempty(aLists, FrameChildListID::Float);
list = GetOutsideMarkerList();
if (list) {
list->AppendIfNonempty(aLists, FrameChildListID::Bullet);
}
list = GetPushedFloats();
if (list) {
list->AppendIfNonempty(aLists, FrameChildListID::PushedFloats);
}
}
/* 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* firstFrameInNextLine = isLastLine ? nullptr : next->mFirstChild;
*aOutFrames = aFromFrames.TakeFramesBefore(firstFrameInNextLine);
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);
}
}
}
// Whether this line is indented by the text-indent amount.
bool nsBlockFrame::TextIndentAppliesTo(const LineIterator& aLine) const {
const auto& 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());
}
return true;
}();
// The 'hanging' option inverts which lines are/aren't indented.
return isFirstLineOrAfterHardBreak != textIndent.hanging;
}
/* virtual */
nscoord nsBlockFrame::GetMinISize(gfxContext* aRenderingContext) {
nsIFrame* firstInFlow = FirstContinuation();
if (firstInFlow != this) {
return firstInFlow->GetMinISize(aRenderingContext);
}
CheckIntrinsicCacheAgainstShrinkWrapState();
if (mCachedMinISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
return mCachedMinISize;
}
if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
mCachedMinISize = *containISize;
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() && TextIndentAppliesTo(line)) {
data.mCurrentLine += StyleText()->mTextIndent.length.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);
}
CheckIntrinsicCacheAgainstShrinkWrapState();
if (mCachedPrefISize != NS_INTRINSIC_ISIZE_UNKNOWN) {
return mCachedPrefISize;
}
if (Maybe<nscoord> containISize = ContainIntrinsicISize()) {
mCachedPrefISize = *containISize;
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 clearType;
if (!data.mLineIsEmpty || BlockCanIntersectFloats(line->mFirstChild)) {
clearType = StyleClear::Both;
} else {
clearType = line->mFirstChild->StyleDisplay()->mClear;
}
data.ForceBreak(clearType);
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(
aRenderingContext, line->mFirstChild,
IntrinsicISizeType::PrefISize);
data.ForceBreak();
} else {
if (!curFrame->GetPrevContinuation() && TextIndentAppliesTo(line)) {
nscoord indent = StyleText()->mTextIndent.length.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() && TextIndentAppliesTo(line)) {
data.mCurrentLine += StyleText()->mTextIndent.length.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 = aReflowInput.ComputedLogicalBorder(aWM);
cbSize.ISize(aWM) -= border.IStartEnd(aWM);
cbSize.BSize(aWM) -= border.BStartEnd(aWM);
if (frame->GetParent()->GetContent() != frame->GetContent() ||
frame->GetParent()->IsCanvasFrame()) {
return cbSize;
}
// 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.
const ReflowInput* lastRI = &aReflowInput;
DebugOnly<const ReflowInput*> lastButOneRI = &aReflowInput;
while (lastRI->mParentReflowInput &&
lastRI->mParentReflowInput->mFrame->GetContent() ==
frame->GetContent()) {
lastButOneRI = lastRI;
lastRI = lastRI->mParentReflowInput;
}
if (lastRI == &aReflowInput) {
return cbSize;
}
// For scroll containers, we can just use cbSize (which is the padding-box
// size of the scrolled-content frame).
if (nsIScrollableFrame* scrollFrame = do_QueryFrame(lastRI->mFrame)) {
// Assert that we're not missing any frames between the abspos containing
// block and the scroll container.
// the parent.
Unused << scrollFrame;
MOZ_ASSERT(lastButOneRI == &aReflowInput);
return cbSize;
}
// Same for fieldsets, where the inner anonymous frame has the correct padding
// area with the legend taken into account.
if (lastRI->mFrame->IsFieldSetFrame()) {
return cbSize;
}
// We found a reflow input for the outermost wrapping frame, so use
// its computed metrics if available, converted to our writing mode
const LogicalSize lastRISize = lastRI->ComputedSize(aWM);
const LogicalMargin lastRIPadding = lastRI->ComputedLogicalPadding(aWM);
if (lastRISize.ISize(aWM) != NS_UNCONSTRAINEDSIZE) {
cbSize.ISize(aWM) =
std::max(0, lastRISize.ISize(aWM) + lastRIPadding.IStartEnd(aWM));
}
if (lastRISize.BSize(aWM) != NS_UNCONSTRAINEDSIZE) {
cbSize.BSize(aWM) =
std::max(0, lastRISize.BSize(aWM) + lastRIPadding.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 const nsBlockFrame* GetAsLineClampDescendant(const nsIFrame* aFrame) {
if (const nsBlockFrame* block = do_QueryFrame(aFrame)) {
if (!block->HasAnyStateBits(NS_BLOCK_BFC)) {
return block;
}
}
return nullptr;
}
static nsBlockFrame* GetAsLineClampDescendant(nsIFrame* aFrame) {
return const_cast<nsBlockFrame*>(
GetAsLineClampDescendant(const_cast<const nsIFrame*>(aFrame)));
}
static bool IsLineClampRoot(const nsBlockFrame* aFrame) {
if (!aFrame->StyleDisplay()->mWebkitLineClamp) {
return false;
}
if (!aFrame->HasAnyStateBits(NS_BLOCK_BFC)) {
return false;
}
if (StaticPrefs::layout_css_webkit_line_clamp_block_enabled() ||
aFrame->PresContext()->Document()->ChromeRulesEnabled()) {
return true;
}
// 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::scrolledContent) {
// 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());
const auto& 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;
}
bool nsBlockFrame::IsInLineClampContext() const {
if (IsLineClampRoot(this)) {
return true;
}
const nsBlockFrame* cur = this;
while (GetAsLineClampDescendant(cur)) {
cur = do_QueryFrame(cur->GetParent());
if (!cur) {
return false;
}
if (IsLineClampRoot(cur)) {
return true;
}
}
return false;
}
/**
* 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); }
void nsBlockFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
const ReflowInput& aReflowInput,
nsReflowStatus& aStatus) {
if (IsHiddenByContentVisibilityOfInFlowParentForLayout()) {
FinishAndStoreOverflow(&aMetrics, aReflowInput.mStyleDisplay);
return;
}
MarkInReflow();
DO_GLOBAL_REFLOW_COUNT("nsBlockFrame");
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);
}
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.
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 (effectiveContentBoxBSize + blockDirExtras.BStartEnd(wm) <=
aReflowInput.AvailableBSize()) {
restoreReflowInputAvailBSize.emplace(aReflowInput.AvailableBSize());
const_cast<ReflowInput&>(aReflowInput)
.SetAvailableBSize(NS_UNCONSTRAINEDSIZE);
}
}
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.
bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this);
if (needFloatManager) {
autoFloatManager.CreateFloatManager(aPresContext);
}
if (HasAnyStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION) &&
PresContext()->BidiEnabled()) {
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
}
// Whether to apply text-wrap: balance behavior.
bool tryBalance =
StyleText()->mTextWrapStyle == StyleTextWrapStyle::Balance &&
!GetPrevContinuation();
// Struct used to hold the "target" number of lines or clamp position to
// maintain when doing text-wrap: balance.
struct BalanceTarget {
// If line-clamp is in effect, mContent and mOffset indicate the starting
// position of the first line after the clamp limit, 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;
bool operator==(const BalanceTarget& aOther) const {
return mContent == aOther.mContent && mOffset == aOther.mOffset &&
mBlockCoord == aOther.mBlockCoord;
}
bool operator!=(const BalanceTarget& aOther) const {
return !(*this == aOther);
}
};
BalanceTarget balanceTarget;
// Helpers for text-wrap: balance implementation:
// Count the number of lines in the mLines list, but return -1 (to suppress
// balancing) instead if the count is going to exceed aLimit, or if we
// encounter a block.
auto countLinesUpTo = [&](int32_t aLimit) -> int32_t {
int32_t n = 0;
for (auto iter = mLines.begin(); iter != mLines.end(); ++iter) {
if (++n > aLimit || iter->IsBlock()) {
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 {
MOZ_ASSERT(aClampCount < mLines.size());
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);
aMetrics = ReflowOutput(aMetrics.GetWritingMode());
reflowStatus =
TrialReflow(aPresContext, aMetrics, aReflowInput, trialState);
// Do we need to start a `text-wrap: balance` iteration?
if (tryBalance) {
tryBalance = false;
// Don't try to balance an incomplete block, 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) {
return false;
}
if (balanceTarget.mContent) {
auto t = getClampPosition(aReflowInput.mStyleDisplay->mWebkitLineClamp);