gecko-dev/layout/generic/nsBlockFrame.cpp
Daniel Holbert 7f66b8109f Bug 1438020: Add some includes for "Inlines" headers to address clang build warnings in non-unified build. r=mats
MozReview-Commit-ID: HRHnjznPjx1

--HG--
extra : rebase_source : f0500a1c68a823a4583093a25f1a9fcf97aa1515
2018-02-13 14:38:20 -08:00

7765 lines
280 KiB
C++

/* -*- 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/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "nsCOMPtr.h"
#include "nsAbsoluteContainingBlock.h"
#include "nsBlockReflowContext.h"
#include "BlockReflowInput.h"
#include "nsBulletFrame.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 "nsIPresShell.h"
#include "nsStyleContext.h"
#include "nsHTMLParts.h"
#include "nsGkAtoms.h"
#include "nsGenericHTMLElement.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 "nsISelection.h"
#include "mozilla/dom/HTMLDetailsElement.h"
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/RestyleManagerInlines.h"
#include "mozilla/ServoRestyleManager.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/StyleSetHandle.h"
#include "mozilla/StyleSetHandleInlines.h"
#include "mozilla/Telemetry.h"
#include "nsBidiPresUtils.h"
#include <inttypes.h>
static const int MIN_LINES_NEEDING_CURSOR = 20;
static const char16_t kDiscCharacter = 0x2022;
using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::dom;
using namespace mozilla::layout;
using ShapeType = nsFloatManager::ShapeType;
typedef nsAbsoluteContainingBlock::AbsPosReflowFlags AbsPosReflowFlags;
static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock)
{
nsLineList::iterator line = aBlock->LinesBegin();
nsLineList::iterator endLine = aBlock->LinesEnd();
while (line != endLine) {
if (line->IsBlock()) {
nsIFrame* f = line->mFirstChild;
nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(f);
if (bf) {
MarkAllDescendantLinesDirty(bf);
}
}
line->MarkDirty();
++line;
}
}
static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock)
{
nsBlockFrame* blockWithFloatMgr = aBlock;
while (!(blockWithFloatMgr->GetStateBits() & NS_BLOCK_FLOAT_MGR)) {
nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(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 = nsLayoutUtils::GetAsBlock(aFrame);
if (!block)
return false;
if (block->GetChildList(nsIFrame::kFloatList).FirstChild())
return true;
nsLineList::iterator line = block->LinesBegin();
nsLineList::iterator endLine = block->LinesEnd();
while (line != endLine) {
if (line->IsBlock() && BlockHasAnyFloats(line->mFirstChild))
return true;
++line;
}
return false;
}
#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(OutsideBulletProperty)
NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(InsideBulletProperty, nsBulletFrame)
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(BlockEndEdgeOfChildrenProperty, nscoord)
//----------------------------------------------------------------------
nsBlockFrame*
NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{
return new (aPresShell) nsBlockFrame(aContext);
}
nsBlockFrame*
NS_NewBlockFormattingContext(nsIPresShell* aPresShell,
nsStyleContext* aStyleContext)
{
nsBlockFrame* blockFrame = NS_NewBlockFrame(aPresShell, aStyleContext);
blockFrame->AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
return blockFrame;
}
NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame)
nsBlockFrame::~nsBlockFrame()
{
}
void
nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
{
ClearLineCursor();
DestroyAbsoluteFrames(aDestructRoot, aPostDestroyData);
mFloats.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
nsPresContext* presContext = PresContext();
nsIPresShell* shell = presContext->PresShell();
nsLineBox::DeleteLineList(presContext, mLines, aDestructRoot,
&mFrames, aPostDestroyData);
if (HasPushedFloats()) {
SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, shell,
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 (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) {
SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, shell,
OverflowOutOfFlowsProperty());
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
if (HasOutsideBullet()) {
SafelyDestroyFrameListProp(aDestructRoot, aPostDestroyData, shell,
OutsideBulletProperty());
RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET);
}
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 == NS_STYLE_DIRECTION_RTL);
if (NS_FAILED(rv)) {
delete it;
return nullptr;
}
return it;
}
NS_QUERYFRAME_HEAD(nsBlockFrame)
NS_QUERYFRAME_ENTRY(nsBlockFrame)
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
nsSplittableType
nsBlockFrame::GetSplittableType() const
{
return NS_FRAME_SPLITTABLE_NON_RECTANGULAR;
}
#ifdef DEBUG_FRAME_DUMP
void
nsBlockFrame::List(FILE* out, const char* aPrefix, uint32_t 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
ChildListIterator lists(this);
ChildListIDs skip(kPrincipalList | kOverflowList);
for (; !lists.IsDone(); lists.Next()) {
if (skip.Contains(lists.CurrentID())) {
continue;
}
fprintf_stderr(out, "%s%s %p <\n", pfx.get(),
mozilla::layout::ChildListName(lists.CurrentID()),
&GetChildList(lists.CurrentID()));
nsCString nestedPfx(pfx);
nestedPfx += " ";
nsFrameList::Enumerator childFrames(lists.CurrentList());
for (; !childFrames.AtEnd(); childFrames.Next()) {
nsIFrame* kid = childFrames.get();
kid->List(out, nestedPfx.get(), aFlags);
}
fprintf_stderr(out, "%s>\n", pfx.get());
}
fprintf_stderr(out, "%s>\n", aPrefix);
}
nsresult
nsBlockFrame::GetFrameName(nsAString& aResult) const
{
return MakeFrameName(NS_LITERAL_STRING("Block"), aResult);
}
#endif
void
nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey)
{
if (nsSVGUtils::IsInSVGTextSubtree(this)) {
NS_ASSERTION(GetParent()->IsSVGTextFrame(),
"unexpected block frame in SVG text");
GetParent()->InvalidateFrame();
return;
}
nsContainerFrame::InvalidateFrame(aDisplayItemKey);
}
void
nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
{
if (nsSVGUtils::IsInSVGTextSubtree(this)) {
NS_ASSERTION(GetParent()->IsSVGTextFrame(),
"unexpected block frame in SVG text");
GetParent()->InvalidateFrame();
return;
}
nsContainerFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey);
}
nscoord
nsBlockFrame::GetLogicalBaseline(WritingMode aWM) const
{
auto lastBaseline =
BaselineBOffset(aWM, BaselineSharingGroup::eLast, AlignmentContext::eInline);
return BSize(aWM) - lastBaseline;
}
bool
nsBlockFrame::GetNaturalBaselineBOffset(mozilla::WritingMode aWM,
BaselineSharingGroup aBaselineGroup,
nscoord* aBaseline) const
{
if (aBaselineGroup == BaselineSharingGroup::eFirst) {
return nsLayoutUtils::GetFirstLineBaseline(aWM, this, aBaseline);
}
for (ConstReverseLineIterator line = LinesRBegin(), line_end = LinesREnd();
line != line_end; ++line) {
if (line->IsBlock()) {
nscoord offset;
nsIFrame* kid = line->mFirstChild;
if (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();
const nsLineBox* firstLine = line;
if (firstLine->GetChildCount()) {
return bp.top + firstLine->mFirstChild->GetCaretBaseline();
}
}
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
nscoord lineHeight =
ReflowInput::CalcLineHeight(GetContent(), StyleContext(),
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 = GetOutsideBulletList();
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 = GetOutsideBulletList();
if (list) {
list->AppendIfNonempty(aLists, kBulletList);
}
list = GetPushedFloats();
if (list) {
list->AppendIfNonempty(aLists, kPushedFloatsList);
}
}
/* virtual */ bool
nsBlockFrame::IsFloatContainingBlock() const
{
return true;
}
static void
ReparentFrame(nsIFrame* aFrame, nsContainerFrame* aOldParent,
nsContainerFrame* aNewParent)
{
NS_ASSERTION(aOldParent == aFrame->GetParent(),
"Parent not consistent with expectations");
aFrame->SetParent(aNewParent);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented
nsContainerFrame::ReparentFrameView(aFrame, aOldParent, aNewParent);
}
static void
ReparentFrames(nsFrameList& aFrameList, nsContainerFrame* aOldParent,
nsContainerFrame* aNewParent)
{
for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
ReparentFrame(e.get(), aOldParent, aNewParent);
}
}
/**
* 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->mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
dirtyBlock->mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
if (!(GetStateBits() & 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 !=
!!(GetStateBits() & NS_BLOCK_FRAME_INTRINSICS_INFLATED)) {
mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN;
mPrefWidth = NS_INTRINSIC_WIDTH_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_WIDTH(this, mMinWidth);
CheckIntrinsicCacheAgainstShrinkWrapState();
if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
return mMinWidth;
#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 (RenumberList()) {
AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
ResolveBidi();
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, nsLayoutUtils::MIN_ISIZE);
data.ForceBreak();
} else {
if (!curFrame->GetPrevContinuation() &&
line == curFrame->LinesBegin()) {
// Only add text-indent if it has no percentages; using a
// percentage basis of 0 unconditionally would give strange
// behavior for calc(10%-3px).
const nsStyleCoord &indent = StyleText()->mTextIndent;
if (indent.ConvertsToLength())
data.mCurrentLine += indent.ComputeCoordPercentCalc(0);
}
// XXX Bug NNNNNN Should probably handle percentage text-indent.
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);
}
}
#ifdef DEBUG
if (gNoisyIntrinsic) {
IndentBy(stdout, gNoiseIndent);
printf("min: [prevLines=%d currentLine=%d]\n",
data.mPrevLines, data.mCurrentLine);
}
#endif
}
}
data.ForceBreak();
mMinWidth = data.mPrevLines;
return mMinWidth;
}
/* virtual */ nscoord
nsBlockFrame::GetPrefISize(gfxContext *aRenderingContext)
{
nsIFrame* firstInFlow = FirstContinuation();
if (firstInFlow != this)
return firstInFlow->GetPrefISize(aRenderingContext);
DISPLAY_PREF_WIDTH(this, mPrefWidth);
CheckIntrinsicCacheAgainstShrinkWrapState();
if (mPrefWidth != NS_INTRINSIC_WIDTH_UNKNOWN)
return mPrefWidth;
#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 (RenumberList()) {
AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
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()->PhysicalBreakType(data.mLineContainerWM);
}
data.ForceBreak(breakType);
data.mCurrentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
line->mFirstChild, nsLayoutUtils::PREF_ISIZE);
data.ForceBreak();
} else {
if (!curFrame->GetPrevContinuation() &&
line == curFrame->LinesBegin()) {
// Only add text-indent if it has no percentages; using a
// percentage basis of 0 unconditionally would give strange
// behavior for calc(10%-3px).
const nsStyleCoord &indent = StyleText()->mTextIndent;
if (indent.ConvertsToLength()) {
nscoord length = indent.ToLength();
if (length != 0) {
data.mCurrentLine += length;
data.mLineIsEmpty = false;
}
}
}
// XXX Bug NNNNNN Should probably handle percentage text-indent.
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();
mPrefWidth = data.mPrevLines;
return mPrefWidth;
}
nsRect
nsBlockFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const
{
// be conservative
if (StyleContext()->HasTextDecorationLines()) {
return GetVisualOverflowRect();
}
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()) {
// Only add text-indent if it has no percentages; using a
// percentage basis of 0 unconditionally would give strange
// behavior for calc(10%-3px).
const nsStyleCoord &indent = StyleText()->mTextIndent;
if (indent.ConvertsToLength()) {
data.mCurrentLine += indent.ComputeCoordPercentCalc(0);
}
}
// XXX Bug NNNNNN Should probably handle percentage text-indent.
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 state
// 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 state 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 state for the outermost wrapping frame, so use
// its computed metrics if available, converted to our writing mode
WritingMode lastWM = aLastRI->GetWritingMode();
LogicalSize lastRISize =
LogicalSize(lastWM,
aLastRI->ComputedISize(),
aLastRI->ComputedBSize()).ConvertTo(aWM, lastWM);
LogicalMargin lastRIPadding =
aLastRI->ComputedLogicalPadding().ConvertTo(aWM, lastWM);
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;
}
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
const ReflowInput *reflowInput = &aReflowInput;
WritingMode wm = aReflowInput.GetWritingMode();
nscoord consumedBSize = ConsumedBSize(wm);
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.
if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
aReflowInput.ComputedBSize() != NS_AUTOHEIGHT &&
ShouldApplyOverflowClipping(this, aReflowInput.mStyleDisplay)) {
LogicalMargin blockDirExtras = aReflowInput.ComputedLogicalBorderPadding();
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().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.VisualOverflow().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 state so that we don't have
// to continually recompute it.
BlockReflowInput state(*reflowInput, aPresContext, this,
blockStartMarginRoot, blockEndMarginRoot,
needFloatManager, consumedBSize);
if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)
static_cast<nsBlockFrame*>(FirstContinuation())->ResolveBidi();
if (RenumberList()) {
AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
// Handle paginated overflow (see nsContainerFrame.h)
nsOverflowAreas ocBounds;
nsReflowStatus ocStatus;
if (GetPrevInFlow()) {
ReflowOverflowContainerChildren(aPresContext, *reflowInput, ocBounds, 0,
ocStatus);
}
// 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();
nsOverflowAreas fcBounds;
nsReflowStatus fcStatus;
ReflowPushedFloats(state, fcBounds, fcStatus);
// 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 (!(GetStateBits() & NS_FRAME_IS_DIRTY) && reflowInput->IsIResize()) {
PrepareResizeReflow(state);
}
// The same for percentage text-indent, except conditioned on the
// parent resizing.
if (!(GetStateBits() & NS_FRAME_IS_DIRTY) &&
reflowInput->mCBReflowInput &&
reflowInput->mCBReflowInput->IsIResize() &&
reflowInput->mStyleText->mTextIndent.HasPercent() &&
!mLines.empty()) {
mLines.front()->MarkDirty();
}
LazyMarkLinesDirty();
RemoveStateBits(NS_FRAME_FIRST_REFLOW);
// 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->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
state.mReflowStatus.SetOverflowIncomplete();
} else {
state.mReflowStatus.SetIncomplete();
}
break;
}
nif = static_cast<nsBlockFrame*>(nif->GetNextInFlow());
}
}
state.mReflowStatus.MergeCompletionStatusFrom(ocStatus);
state.mReflowStatus.MergeCompletionStatusFrom(fcStatus);
// 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" (bullet) frame if it is placed next to a block
// child.
//
// According to the CSS2 spec, section 12.6.1, the "marker" box
// participates in the height calculation of the list-item box's
// first line box.
//
// There are exactly two places a bullet 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 (HasOutsideBullet() && !mLines.empty() &&
(mLines.front()->IsBlock() ||
(0 == mLines.front()->BSize() &&
mLines.front() != mLines.back() &&
mLines.begin().next()->IsBlock()))) {
// Reflow the bullet
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().BStart(wm);
nsIFrame* bullet = GetOutsideBullet();
ReflowBullet(bullet, state, reflowOutput, lineBStart);
NS_ASSERTION(!BulletIsEmpty() || reflowOutput.BSize(wm) == 0,
"empty bullet took up space");
if (havePosition && !BulletIsEmpty()) {
// We have some lines to align the bullet with.
// Doing the alignment using the baseline will also cater for
// bullets that are placed next to a child block (bug 92896)
// Tall bullets won't look particularly nice here...
LogicalRect bbox = bullet->GetLogicalRect(wm, reflowOutput.PhysicalSize());
bbox.BStart(wm) = position.mBaseline - reflowOutput.BlockStartAscent();
bullet->SetRect(wm, bbox, reflowOutput.PhysicalSize());
}
// Otherwise just leave the bullet where it is, up against our
// block-start padding.
}
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) {
for (LineIterator line = LinesBegin(), end = LinesEnd();
line != end; line++) {
UpdateLineContainerSize(line, containerSize);
}
for (nsIFrame* f : mFloats) {
nsPoint physicalDelta(deltaX, 0);
f->MovePositionBy(physicalDelta);
}
nsFrameList* bulletList = GetOutsideBulletList();
if (bulletList) {
nsPoint physicalDelta(deltaX, 0);
for (nsIFrame* f : *bulletList) {
f->MovePositionBy(physicalDelta);
}
}
}
}
nsRect areaBounds = nsRect(0, 0, aMetrics.Width(), aMetrics.Height());
ComputeOverflowAreas(areaBounds, reflowInput->mStyleDisplay,
blockEndEdgeOfChildren, aMetrics.mOverflowAreas);
// Factor overflow container child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(ocBounds);
// Factor pushed float child bounds into the overflow area
aMetrics.mOverflowAreas.UnionWith(fcBounds);
// Let the absolutely positioned container reflow any absolutely positioned
// child frames that need to be reflowed, e.g., elements with a percentage
// based width/height
// We want to do this under either of two conditions:
// 1. If we didn't do the incremental reflow above.
// 2. If our size changed.
// Even though it's the padding edge that's the containing block, we
// can use our rect (the border edge) since if the border style
// changed, the reflow would have been targeted at us so we'd satisfy
// condition 1.
// XXX checking oldSize is bogus, there are various reasons we might have
// reflowed but our size might not have been changed to what we
// asked for (e.g., we ended up being pushed to a new page)
// When WillReflowAgainForClearance is true, we will reflow again without
// resetting the size. Because of this, we must not reflow our abs-pos children
// in that situation --- what we think is our "new size"
// will not be our real new size. This also happens to be more efficient.
WritingMode parentWM = aMetrics.GetWritingMode();
if (HasAbsolutelyPositionedChildren()) {
nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
bool haveInterrupt = aPresContext->HasPendingInterrupt();
if (reflowInput->WillReflowAgainForClearance() ||
haveInterrupt) {
// Make sure that when we reflow again we'll actually reflow all the abs
// pos frames that might conceivably depend on our size (or all of them,
// if we're dirty right now and interrupted; in that case we also need
// to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much
// better than that, because we don't really know what our size will be,
// and it might in fact not change on the followup reflow!
if (haveInterrupt && (GetStateBits() & NS_FRAME_IS_DIRTY)) {
absoluteContainer->MarkAllFramesDirty();
} else {
absoluteContainer->MarkSizeDependentFramesDirty();
}
} else {
LogicalSize containingBlockSize =
CalculateContainingBlockSizeForAbsolutes(parentWM, *reflowInput,
aMetrics.Size(parentWM));
// Mark frames that depend on changes we just made to this frame as dirty:
// Now we can assume that the padding edge hasn't moved.
// We need to reflow the absolutes if one of them depends on
// its placeholder position, or the containing block size in a
// direction in which the containing block size might have
// changed.
// XXX "width" and "height" in this block will become ISize and BSize
// when nsAbsoluteContainingBlock is logicalized
bool cbWidthChanged = aMetrics.Width() != oldSize.width;
bool isRoot = !GetContent()->GetParent();
// If isRoot and we have auto height, then we are the initial
// containing block and the containing block height is the
// viewport height, which can't change during incremental
// reflow.
bool cbHeightChanged =
!(isRoot && NS_UNCONSTRAINEDSIZE == reflowInput->ComputedHeight()) &&
aMetrics.Height() != oldSize.height;
nsRect containingBlock(nsPoint(0, 0),
containingBlockSize.GetPhysicalSize(parentWM));
AbsPosReflowFlags flags = AbsPosReflowFlags::eConstrainHeight;
if (cbWidthChanged) {
flags |= AbsPosReflowFlags::eCBWidthChanged;
}
if (cbHeightChanged) {
flags |= AbsPosReflowFlags::eCBHeightChanged;
}
// Setup the line cursor here to optimize line searching for
// calculating hypothetical position of absolutely-positioned
// frames. The line cursor is immediately cleared afterward to
// avoid affecting the display list generation.
AutoLineCursorSetup autoLineCursor(this);
absoluteContainer->Reflow(this, aPresContext, *reflowInput,
state.mReflowStatus,
containingBlock, flags,
&aMetrics.mOverflowAreas);
}
}
FinishAndStoreOverflow(&aMetrics, reflowInput->mStyleDisplay);
aStatus = state.mReflowStatus;
#ifdef DEBUG
// Between when we drain pushed floats and when we complete reflow,
// we're allowed to have multiple continuations of the same float on
// our floats list, since a first-in-flow might get pushed to a later
// continuation of its containing block. But it's not permitted
// outside that time.
nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats);
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": status=%s metrics=%d,%d carriedMargin=%d",
ToString(aStatus).c_str(),
aMetrics.ISize(parentWM), aMetrics.BSize(parentWM),
aMetrics.mCarriedOutBEndMargin.get());
if (HasOverflowAreas()) {
printf(" overflow-vis={%d,%d,%d,%d}",
aMetrics.VisualOverflow().x,
aMetrics.VisualOverflow().y,
aMetrics.VisualOverflow().width,
aMetrics.VisualOverflow().height);
printf(" overflow-scr={%d,%d,%d,%d}",
aMetrics.ScrollableOverflow().x,
aMetrics.ScrollableOverflow().y,
aMetrics.ScrollableOverflow().width,
aMetrics.ScrollableOverflow().height);
}
printf("\n");
}
if (gLameReflowMetrics) {
PRTime end = PR_Now();
int32_t ectc = nsLineBox::GetCtorCount();
int32_t numLines = mLines.size();
if (!numLines) numLines = 1;
PRTime delta, perLineDelta, lines;
lines = int64_t(numLines);
delta = end - start;
perLineDelta = delta / lines;
ListTag(stdout);
char buf[400];
SprintfLiteral(buf,
": %" PRId64 " elapsed (%" PRId64 " per line) (%d lines; %d new lines)",
delta, perLineDelta, numLines, ectc - ctc);
printf("%s\n", buf);
}
#endif
NS_FRAME_SET_TRUNCATION(aStatus, (*reflowInput), aMetrics);
}
bool
nsBlockFrame::CheckForCollapsedBEndMarginFromClearanceLine()
{
LineIterator begin = LinesBegin();
LineIterator line = LinesEnd();
while (true) {
if (begin == line) {
return false;
}
--line;
if (line->BSize() != 0 || !line->CachedIsEmpty()) {
return false;
}
if (line->HasClearance()) {
return true;
}
}
// not reached
}
void
nsBlockFrame::ComputeFinalSize(const ReflowInput& aReflowInput,
BlockReflowInput& aState,
ReflowOutput& aMetrics,
nscoord* aBEndEdgeOfChildren)
{
WritingMode wm = aState.mReflowInput.GetWritingMode();
const LogicalMargin& borderPadding = aState.BorderPadding();
#ifdef NOISY_FINAL_SIZE
ListTag(stdout);
printf(": mBCoord=%d mIsBEndMarginRoot=%s mPrevBEndMargin=%d bp=%d,%d\n",
aState.mBCoord, aState.mFlags.mIsBEndMarginRoot ? "yes" : "no",
aState.mPrevBEndMargin.get(),
borderPadding.BStart(wm), borderPadding.BEnd(wm));
#endif
// Compute final inline size
LogicalSize finalSize(wm);
finalSize.ISize(wm) =
NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.IStart(wm),
aReflowInput.ComputedISize()),
borderPadding.IEnd(wm));
// Return block-end margin information
// rbs says he hit this assertion occasionally (see bug 86947), so
// just set the margin to zero and we'll figure out why later
//NS_ASSERTION(aMetrics.mCarriedOutBEndMargin.IsZero(),
// "someone else set the margin");
nscoord nonCarriedOutBDirMargin = 0;
if (!aState.mFlags.mIsBEndMarginRoot) {
// Apply rule from CSS 2.1 section 8.3.1. If we have some empty
// line with clearance and a non-zero block-start margin and all
// subsequent lines are empty, then we do not allow our children's
// carried out block-end margin to be carried out of us and collapse
// with our own block-end margin.
if (CheckForCollapsedBEndMarginFromClearanceLine()) {
// Convert the children's carried out margin to something that
// we will include in our height
nonCarriedOutBDirMargin = aState.mPrevBEndMargin.get();
aState.mPrevBEndMargin.Zero();
}
aMetrics.mCarriedOutBEndMargin = aState.mPrevBEndMargin;
} else {
aMetrics.mCarriedOutBEndMargin.Zero();
}
nscoord blockEndEdgeOfChildren = aState.mBCoord + nonCarriedOutBDirMargin;
// Shrink wrap our height around our contents.
if (aState.mFlags.mIsBEndMarginRoot ||
NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()) {
// When we are a block-end-margin root make sure that our last
// childs block-end margin is fully applied. We also do this when
// we have a computed height, since in that case the carried out
// margin is not going to be applied anywhere, so we should note it
// here to be included in the overflow area.
// Apply the margin only if there's space for it.
if (blockEndEdgeOfChildren < aState.mReflowInput.AvailableBSize())
{
// Truncate block-end margin if it doesn't fit to our available BSize.
blockEndEdgeOfChildren =
std::min(blockEndEdgeOfChildren + aState.mPrevBEndMargin.get(),
aState.mReflowInput.AvailableBSize());
}
}
if (aState.mFlags.mBlockNeedsFloatManager) {
// Include the float manager's state to properly account for the
// block-end margin of any floated elements; e.g., inside a table cell.
nscoord floatHeight =
aState.ClearFloats(blockEndEdgeOfChildren, StyleClear::Both,
nullptr, nsFloatManager::DONT_CLEAR_PUSHED_FLOATS);
blockEndEdgeOfChildren = std::max(blockEndEdgeOfChildren, floatHeight);
}
if (NS_UNCONSTRAINEDSIZE != aReflowInput.ComputedBSize()
&& (!GetParent()->IsColumnSetFrame() ||
aReflowInput.mParentReflowInput->AvailableBSize() == NS_UNCONSTRAINEDSIZE)) {
ComputeFinalBSize(aReflowInput, &aState.mReflowStatus,
aState.mBCoord + nonCarriedOutBDirMargin,
borderPadding, finalSize, aState.mConsumedBSize);
if (!aState.mReflowStatus.IsComplete()) {
// Use the current height; continuations will take up the rest.
// Do extend the height to at least consume the available
// height, otherwise our left/right borders (for example) won't
// extend all the way to the break.
finalSize.BSize(wm) = std::max(aReflowInput.AvailableBSize(),
aState.mBCoord + nonCarriedOutBDirMargin);
// ... but don't take up more block size than is available
nscoord effectiveComputedBSize =
GetEffectiveComputedBSize(aReflowInput, aState.ConsumedBSize());
finalSize.BSize(wm) =
std::min(finalSize.BSize(wm),
borderPadding.BStart(wm) + effectiveComputedBSize);
// XXX It's pretty wrong that our bottom border still gets drawn on
// on its own on the last-in-flow, even if we ran out of height
// here. We need GetSkipSides to check whether we ran out of content
// height in the current frame, not whether it's last-in-flow.
}
// Don't carry out a block-end margin when our BSize is fixed.
aMetrics.mCarriedOutBEndMargin.Zero();
}
else if (aState.mReflowStatus.IsComplete()) {
nscoord contentBSize = blockEndEdgeOfChildren - borderPadding.BStart(wm);
nscoord autoBSize = aReflowInput.ApplyMinMaxBSize(contentBSize);
if (autoBSize != contentBSize) {
// Our min- or max-bsize value made our bsize change. Don't carry out
// our kids' block-end margins.
aMetrics.mCarriedOutBEndMargin.Zero();
}
autoBSize += borderPadding.BStart(wm) + borderPadding.BEnd(wm);
finalSize.BSize(wm) = autoBSize;
}
else {
NS_ASSERTION(aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE,
"Shouldn't be incomplete if availableBSize is UNCONSTRAINED.");
finalSize.BSize(wm) = std::max(aState.mBCoord,
aReflowInput.AvailableBSize());
if (aReflowInput.AvailableBSize() == NS_UNCONSTRAINEDSIZE) {
// This should never happen, but it does. See bug 414255
finalSize.BSize(wm) = aState.mBCoord;
}
}
if (IS_TRUE_OVERFLOW_CONTAINER(this)) {
if (aState.mReflowStatus.IsIncomplete()) {
// Overflow containers can only be overflow complete.
// Note that auto height overflow containers have no normal children
NS_ASSERTION(finalSize.BSize(wm) == 0,
"overflow containers must be zero-block-size");
aState.mReflowStatus.SetOverflowIncomplete();
}
} else if (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
!aState.mReflowStatus.IsInlineBreakBefore() &&
aState.mReflowStatus.IsComplete()) {
// Currently only used for grid items, but could be used in other contexts.
// The FragStretchBSizeProperty is our expected non-fragmented block-size
// we should stretch to (for align-self:stretch etc). In some fragmentation
// cases though, the last fragment (this frame since we're complete), needs
// to have extra size applied because earlier fragments consumed too much of
// our computed size due to overflowing their containing block. (E.g. this
// ensures we fill the last row when a multi-row grid item is fragmented).
bool found;
nscoord bSize = GetProperty(FragStretchBSizeProperty(), &found);
if (found) {
finalSize.BSize(wm) = std::max(bSize, finalSize.BSize(wm));
}
}
// Clamp the content size to fit within the margin-box clamp size, if any.
if (MOZ_UNLIKELY(aReflowInput.mFlags.mBClampMarginBoxMinSize) &&
aState.mReflowStatus.IsComplete()) {
bool found;
nscoord cbSize = GetProperty(BClampMarginBoxMinSizeProperty(), &found);
if (found) {
auto marginBoxBSize = finalSize.BSize(wm) +
aReflowInput.ComputedLogicalMargin().BStartEnd(wm);
auto overflow = marginBoxBSize - cbSize;
if (overflow > 0) {
auto contentBSize = finalSize.BSize(wm) - borderPadding.BStartEnd(wm);
auto newContentBSize = std::max(nscoord(0), contentBSize - overflow);
// XXXmats deal with percentages better somehow?
finalSize.BSize(wm) -= contentBSize - newContentBSize;
}
}
}
// Screen out negative block sizes --- can happen due to integer overflows :-(
finalSize.BSize(wm) = std::max(0, finalSize.BSize(wm));
*aBEndEdgeOfChildren = blockEndEdgeOfChildren;
if (blockEndEdgeOfChildren != finalSize.BSize(wm) - borderPadding.BEnd(wm)) {
SetProperty(BlockEndEdgeOfChildrenProperty(), blockEndEdgeOfChildren);
} else {
DeleteProperty(BlockEndEdgeOfChildrenProperty());
}
aMetrics.SetSize(wm, finalSize);
#ifdef DEBUG_blocks
if ((CRAZY_SIZE(aMetrics.Width()) || CRAZY_SIZE(aMetrics.Height())) &&
!GetParent()->IsCrazySizeAssertSuppressed()) {
ListTag(stdout);
printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height());
}
#endif
}
static void
ConsiderBlockEndEdgeOfChildren(const WritingMode aWritingMode,
nscoord aBEndEdgeOfChildren,
nsOverflowAreas& aOverflowAreas)
{
// Factor in the block-end edge of the children. Child frames will be added
// to the overflow area as we iterate through the lines, but their margins
// won't, so we need to account for block-end margins here.
// REVIEW: For now, we do this for both visual and scrollable area,
// although when we make scrollable overflow area not be a subset of
// visual, we can change this.
// XXX Currently, overflow areas are stored as physical rects, so we have
// to handle writing modes explicitly here. If we change overflow rects
// to be stored logically, this can be simplified again.
if (aWritingMode.IsVertical()) {
if (aWritingMode.IsVerticalLR()) {
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
nsRect& o = aOverflowAreas.Overflow(otype);
o.width = std::max(o.XMost(), aBEndEdgeOfChildren) - o.x;
}
} else {
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
nsRect& o = aOverflowAreas.Overflow(otype);
nscoord xmost = o.XMost();
o.x = std::min(o.x, xmost - aBEndEdgeOfChildren);
o.width = xmost - o.x;
}
}
} else {
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
nsRect& o = aOverflowAreas.Overflow(otype);
o.height = std::max(o.YMost(), aBEndEdgeOfChildren) - o.y;
}
}
}
void
nsBlockFrame::ComputeOverflowAreas(const nsRect& aBounds,
const nsStyleDisplay* aDisplay,
nscoord aBEndEdgeOfChildren,
nsOverflowAreas& aOverflowAreas)
{
// Compute the overflow areas of our children
// XXX_perf: This can be done incrementally. It is currently one of
// the things that makes incremental reflow O(N^2).
nsOverflowAreas areas(aBounds, aBounds);
if (!ShouldApplyOverflowClipping(this, aDisplay)) {
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line) {
areas.UnionWith(line->GetOverflowAreas());
}
// Factor an outside bullet in; normally the bullet will be factored into
// the line-box's overflow areas. However, if the line is a block
// line then it won't; if there are no lines, it won't. So just
// factor it in anyway (it can't hurt if it was already done).
// XXXldb Can we just fix GetOverflowArea instead?
nsIFrame* outsideBullet = GetOutsideBullet();
if (outsideBullet) {
areas.UnionAllWith(outsideBullet->GetRect());
}
ConsiderBlockEndEdgeOfChildren(GetWritingMode(),
aBEndEdgeOfChildren, areas);
}
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
const nsRect& vis = areas.VisualOverflow();
printf(": VisualOverflowArea CA=%d,%d,%d,%d\n", vis.x, vis.y, vis.width, vis.height);
const nsRect& scr = areas.ScrollableOverflow();
printf(": ScrollableOverflowArea CA=%d,%d,%d,%d\n", scr.x, scr.y, scr.width, scr.height);
#endif
aOverflowAreas = areas;
}
void
nsBlockFrame::UnionChildOverflow(nsOverflowAreas& aOverflowAreas)
{
// We need to update the overflow areas of lines manually, as they
// get cached and re-used otherwise. Lines aren't exposed as normal
// frame children, so calling UnionChildOverflow alone will end up
// using the old cached values.
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line) {
nsRect bounds = line->GetPhysicalBounds();
nsOverflowAreas lineAreas(bounds, bounds);
int32_t n = line->GetChildCount();
for (nsIFrame* lineFrame = line->mFirstChild;
n > 0; lineFrame = lineFrame->GetNextSibling(), --n) {
ConsiderChildOverflow(lineAreas, lineFrame);
}
// Consider the overflow areas of the floats attached to the line as well
if (line->HasFloats()) {
for (nsFloatCache* fc = line->GetFirstFloat(); fc; fc = fc->Next()) {
ConsiderChildOverflow(lineAreas, fc->mFloat);
}
}
line->SetOverflowAreas(lineAreas);
aOverflowAreas.UnionWith(lineAreas);
}
// Union with child frames, skipping the principal and float lists
// since we already handled those using the line boxes.
nsLayoutUtils::UnionChildOverflow(this, aOverflowAreas,
kPrincipalList | kFloatList);
}
bool
nsBlockFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
{
bool found;
nscoord blockEndEdgeOfChildren =
GetProperty(BlockEndEdgeOfChildrenProperty(), &found);
if (found) {
ConsiderBlockEndEdgeOfChildren(GetWritingMode(),
blockEndEdgeOfChildren, aOverflowAreas);
}
// Line cursor invariants depend on the overflow areas of the lines, so
// we must clear the line cursor since those areas may have changed.
ClearLineCursor();
return nsContainerFrame::ComputeCustomOverflow(aOverflowAreas);
}
void
nsBlockFrame::LazyMarkLinesDirty()
{
if (GetStateBits() & NS_BLOCK_LOOK_FOR_DIRTY_FRAMES) {
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end; ++line) {
int32_t n = line->GetChildCount();
for (nsIFrame* lineFrame = line->mFirstChild;
n > 0; lineFrame = lineFrame->GetNextSibling(), --n) {
if (NS_SUBTREE_DIRTY(lineFrame)) {
// NOTE: MarkLineDirty does more than just marking the line dirty.
MarkLineDirty(line, &mLines);
break;
}
}
}
RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
}
}
void
nsBlockFrame::MarkLineDirty(LineIterator aLine, const nsLineList* aLineList)
{
// Mark aLine dirty
aLine->MarkDirty();
aLine->SetInvalidateTextRuns(true);
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": mark line %p dirty\n", static_cast<void*>(aLine.get()));
}
#endif
// Mark previous line dirty if it's an inline line so that it can
// maybe pullup something from the line just affected.
// XXX We don't need to do this if aPrevLine ends in a break-after...
if (aLine != aLineList->front() && aLine->IsInline() &&
aLine.prev()->IsInline()) {
aLine.prev()->MarkDirty();
aLine.prev()->SetInvalidateTextRuns(true);
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": mark prev-line %p dirty\n",
static_cast<void*>(aLine.prev().get()));
}
#endif
}
}
/**
* Test whether lines are certain to be aligned left so that we can make
* resizing optimizations
*/
static inline bool
IsAlignedLeft(uint8_t aAlignment,
uint8_t aDirection,
uint8_t aUnicodeBidi,
nsIFrame* aFrame)
{
return nsSVGUtils::IsInSVGTextSubtree(aFrame) ||
NS_STYLE_TEXT_ALIGN_LEFT == aAlignment ||
(((NS_STYLE_TEXT_ALIGN_START == aAlignment &&
NS_STYLE_DIRECTION_LTR == aDirection) ||
(NS_STYLE_TEXT_ALIGN_END == aAlignment &&
NS_STYLE_DIRECTION_RTL == aDirection)) &&
!(NS_STYLE_UNICODE_BIDI_PLAINTEXT & aUnicodeBidi));
}
void
nsBlockFrame::PrepareResizeReflow(BlockReflowInput& aState)
{
// See if we can try and avoid marking all the lines as dirty
bool tryAndSkipLines =
// The left content-edge must be a constant distance from the left
// border-edge.
!StylePadding()->mPadding.GetLeft().HasPercent();
#ifdef DEBUG
if (gDisableResizeOpt) {
tryAndSkipLines = false;
}
if (gNoisyReflow) {
if (!tryAndSkipLines) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": marking all lines dirty: availISize=%d\n",
aState.mReflowInput.AvailableISize());
}
}
#endif
if (tryAndSkipLines) {
WritingMode wm = aState.mReflowInput.GetWritingMode();
nscoord newAvailISize =
aState.mReflowInput.ComputedLogicalBorderPadding().IStart(wm) +
aState.mReflowInput.ComputedISize();
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": trying to avoid marking all lines dirty\n");
}
#endif
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line)
{
// We let child blocks make their own decisions the same
// way we are here.
bool isLastLine = line == mLines.back() && !GetNextInFlow();
if (line->IsBlock() ||
line->HasFloats() ||
(!isLastLine && !line->HasBreakAfter()) ||
((isLastLine || !line->IsLineWrapped())) ||
line->ResizeReflowOptimizationDisabled() ||
line->IsImpactedByFloat() ||
(line->IEnd() > newAvailISize)) {
line->MarkDirty();
}
#ifdef REALLY_NOISY_REFLOW
if (!line->IsBlock()) {
printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n",
line.get(), line->IsImpactedByFloat() ? "" : "not ");
}
#endif
#ifdef DEBUG
if (gNoisyReflow && !line->IsDirty()) {
IndentBy(stdout, gNoiseIndent + 1);
printf("skipped: line=%p next=%p %s %s%s%s breakTypeBefore/After=%s/%s xmost=%d\n",
static_cast<void*>(line.get()),
static_cast<void*>((line.next() != LinesEnd() ? line.next().get() : nullptr)),
line->IsBlock() ? "block" : "inline",
line->HasBreakAfter() ? "has-break-after " : "",
line->HasFloats() ? "has-floats " : "",
line->IsImpactedByFloat() ? "impacted " : "",
line->BreakTypeToString(line->GetBreakTypeBefore()),
line->BreakTypeToString(line->GetBreakTypeAfter()),
line->IEnd());
}
#endif
}
}
else {
// Mark everything dirty
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line)
{
line->MarkDirty();
}
}
}
//----------------------------------------
/**
* Propagate reflow "damage" from from earlier lines to the current
* line. The reflow damage comes from the following sources:
* 1. The regions of float damage remembered during reflow.
* 2. The combination of nonzero |aDeltaBCoord| and any impact by a
* float, either the previous reflow or now.
*
* When entering this function, |aLine| is still at its old position and
* |aDeltaBCoord| indicates how much it will later be slid (assuming it
* doesn't get marked dirty and reflowed entirely).
*/
void
nsBlockFrame::PropagateFloatDamage(BlockReflowInput& aState,
nsLineBox* aLine,
nscoord aDeltaBCoord)
{
nsFloatManager* floatManager = aState.FloatManager();
NS_ASSERTION((aState.mReflowInput.mParentReflowInput &&
aState.mReflowInput.mParentReflowInput->mFloatManager == floatManager) ||
aState.mReflowInput.mBlockDelta == 0, "Bad block delta passed in");
// Check to see if there are any floats; if there aren't, there can't
// be any float damage
if (!floatManager->HasAnyFloats())
return;
// Check the damage region recorded in the float damage.
if (floatManager->HasFloatDamage()) {
// Need to check mBounds *and* mCombinedArea to find intersections
// with aLine's floats
nscoord lineBCoordBefore = aLine->BStart() + aDeltaBCoord;
nscoord lineBCoordAfter = lineBCoordBefore + aLine->BSize();
// Scrollable overflow should be sufficient for things that affect
// layout.
WritingMode wm = aState.mReflowInput.GetWritingMode();
nsSize containerSize = aState.ContainerSize();
LogicalRect overflow = aLine->GetOverflowArea(eScrollableOverflow, wm,
containerSize);
nscoord lineBCoordCombinedBefore = overflow.BStart(wm) + aDeltaBCoord;
nscoord lineBCoordCombinedAfter = lineBCoordCombinedBefore +
overflow.BSize(wm);
bool isDirty = floatManager->IntersectsDamage(lineBCoordBefore,
lineBCoordAfter) ||
floatManager->IntersectsDamage(lineBCoordCombinedBefore,
lineBCoordCombinedAfter);
if (isDirty) {
aLine->MarkDirty();
return;
}
}
// Check if the line is moving relative to the float manager
if (aDeltaBCoord + aState.mReflowInput.mBlockDelta != 0) {
if (aLine->IsBlock()) {
// Unconditionally reflow sliding blocks; we only really need to reflow
// if there's a float impacting this block, but the current float manager
// makes it difficult to check that. Therefore, we let the child block
// decide what it needs to reflow.
aLine->MarkDirty();
} else {
bool wasImpactedByFloat = aLine->IsImpactedByFloat();
nsFlowAreaRect floatAvailableSpace =
aState.GetFloatAvailableSpaceForBSize(aLine->BStart() + aDeltaBCoord,
aLine->BSize(),
nullptr);
#ifdef REALLY_NOISY_REFLOW
printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n",
this, wasImpactedByFloat, floatAvailableSpace.mHasFloats);
#endif
// Mark the line dirty if it was or is affected by a float
// We actually only really need to reflow if the amount of impact
// changes, but that's not straightforward to check
if (wasImpactedByFloat || floatAvailableSpace.mHasFloats) {
aLine->MarkDirty();
}
}
}
}
static bool LineHasClear(nsLineBox* aLine) {
return aLine->IsBlock()
? (aLine->GetBreakTypeBefore() != StyleClear::None ||
(aLine->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN) ||
!nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild))
: aLine->HasFloatBreakAfter();
}
/**
* Reparent a whole list of floats from aOldParent to this block. The
* floats might be taken from aOldParent's overflow list. They will be
* removed from the list. They end up appended to our mFloats list.
*/
void
nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame, nsBlockFrame* aOldParent,
bool aReparentSiblings) {
nsFrameList list;
aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings);
if (list.NotEmpty()) {
for (nsIFrame* f : list) {
MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
"CollectFloats should've removed that bit");
ReparentFrame(f, aOldParent, this);
}
mFloats.AppendFrames(nullptr, list);
}
}
static void DumpLine(const BlockReflowInput& aState, nsLineBox* aLine,
nscoord aDeltaBCoord, int32_t aDeltaIndent) {
#ifdef DEBUG
if (nsBlockFrame::gNoisyReflow) {
nsRect ovis(aLine->GetVisualOverflowArea());
nsRect oscr(aLine->GetScrollableOverflowArea());
nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent);
printf("line=%p mBCoord=%d dirty=%s oldBounds={%d,%d,%d,%d} oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} deltaBCoord=%d mPrevBEndMargin=%d childCount=%d\n",
static_cast<void*>(aLine), aState.mBCoord,
aLine->IsDirty() ? "yes" : "no",
aLine->IStart(), aLine->BStart(),
aLine->ISize(), aLine->BSize(),
ovis.x, ovis.y, ovis.width, ovis.height,
oscr.x, oscr.y, oscr.width, oscr.height,
aDeltaBCoord, aState.mPrevBEndMargin.get(), aLine->GetChildCount());
}
#endif
}
void
nsBlockFrame::ReflowDirtyLines(BlockReflowInput& aState)
{
bool keepGoing = true;
bool repositionViews = false; // should we really need this?
bool foundAnyClears = aState.mFloatBreakType != StyleClear::None;
bool willReflowAgain = false;
#ifdef DEBUG
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent);
ListTag(stdout);
printf(": reflowing dirty lines");
printf(" computedISize=%d\n", aState.mReflowInput.ComputedISize());
}
AutoNoisyIndenter indent(gNoisyReflow);
#endif
bool selfDirty = (GetStateBits() & NS_FRAME_IS_DIRTY) ||
(aState.mReflowInput.IsBResize() &&
(GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_BSIZE));
// Reflow our last line if our availableBSize has increased
// so that we (and our last child) pull up content as necessary
if (aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE
&& GetNextInFlow() && aState.mReflowInput.AvailableBSize() >
GetLogicalSize().BSize(aState.mReflowInput.GetWritingMode())) {
LineIterator lastLine = LinesEnd();
if (lastLine != LinesBegin()) {
--lastLine;
lastLine->MarkDirty();
}
}
// the amount by which we will slide the current line if it is not
// dirty
nscoord deltaBCoord = 0;
// whether we did NOT reflow the previous line and thus we need to
// recompute the carried out margin before the line if we want to
// reflow it or if its previous margin is dirty
bool needToRecoverState = false;
// Float continuations were reflowed in ReflowPushedFloats
bool reflowedFloat = mFloats.NotEmpty() &&
(mFloats.FirstChild()->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT);
bool lastLineMovedUp = false;
// We save up information about BR-clearance here
StyleClear inlineFloatBreakType = aState.mFloatBreakType;
LineIterator line = LinesBegin(), line_end = LinesEnd();
// Reflow the lines that are already ours
for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) {
DumpLine(aState, line, deltaBCoord, 0);
#ifdef DEBUG
AutoNoisyIndenter indent2(gNoisyReflow);
#endif
if (selfDirty)
line->MarkDirty();
// This really sucks, but we have to look inside any blocks that have clear
// elements inside them.
// XXX what can we do smarter here?
if (!line->IsDirty() && line->IsBlock() &&
(line->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)) {
line->MarkDirty();
}
nsIFrame *replacedBlock = nullptr;
if (line->IsBlock() &&
!nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) {
replacedBlock = line->mFirstChild;
}
// We have to reflow the line if it's a block whose clearance
// might have changed, so detect that.
if (!line->IsDirty() &&
(line->GetBreakTypeBefore() != StyleClear::None ||
replacedBlock)) {
nscoord curBCoord = aState.mBCoord;
// See where we would be after applying any clearance due to
// BRs.
if (inlineFloatBreakType != StyleClear::None) {
curBCoord = aState.ClearFloats(curBCoord, inlineFloatBreakType);
}
nscoord newBCoord =
aState.ClearFloats(curBCoord, line->GetBreakTypeBefore(), replacedBlock);
if (line->HasClearance()) {
// Reflow the line if it might not have clearance anymore.
if (newBCoord == curBCoord
// aState.mBCoord is the clearance point which should be the
// block-start border-edge of the block frame. If sliding the
// block by deltaBCoord isn't going to put it in the predicted
// position, then we'd better reflow the line.
|| newBCoord != line->BStart() + deltaBCoord) {
line->MarkDirty();
}
} else {
// Reflow the line if the line might have clearance now.
if (curBCoord != newBCoord) {
line->MarkDirty();
}
}
}
// We might have to reflow a line that is after a clearing BR.
if (inlineFloatBreakType != StyleClear::None) {
aState.mBCoord = aState.ClearFloats(aState.mBCoord, inlineFloatBreakType);
if (aState.mBCoord != line->BStart() + deltaBCoord) {
// SlideLine is not going to put the line where the clearance
// put it. Reflow the line to be sure.
line->MarkDirty();
}
inlineFloatBreakType = StyleClear::None;
}
bool previousMarginWasDirty = line->IsPreviousMarginDirty();
if (previousMarginWasDirty) {
// If the previous margin is dirty, reflow the current line
line->MarkDirty();
line->ClearPreviousMarginDirty();
} else if (line->BEnd() + deltaBCoord > aState.mBEndEdge) {
// Lines that aren't dirty but get slid past our height constraint must
// be reflowed.
line->MarkDirty();
}
// If we have a constrained height (i.e., breaking columns/pages),
// and the distance to the bottom might have changed, then we need
// to reflow any line that might have floats in it, both because the
// breakpoints within those floats may have changed and because we
// might have to push/pull the floats in their entirety.
// FIXME: What about a deltaBCoord or block-size change that forces us to
// push lines? Why does that work?
if (!line->IsDirty() &&
aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
(deltaBCoord != 0 || aState.mReflowInput.IsBResize() ||
aState.mReflowInput.mFlags.mMustReflowPlaceholders) &&
(line->IsBlock() || line->HasFloats() || line->HadFloatPushed())) {
line->MarkDirty();
}
if (!line->IsDirty()) {
// See if there's any reflow damage that requires that we mark the
// line dirty.
PropagateFloatDamage(aState, line, deltaBCoord);
}
// If the container size has changed, reset mContainerSize. If the
// line's writing mode is not ltr, or if the line is not left-aligned, also
// mark the line dirty.
if (aState.ContainerSize() != line->mContainerSize) {
line->mContainerSize = aState.ContainerSize();
bool isLastLine = line == mLines.back() &&
!GetNextInFlow() &&
NS_STYLE_TEXT_ALIGN_AUTO == StyleText()->mTextAlignLast;
uint8_t align = isLastLine ?
StyleText()->mTextAlign : StyleText()->mTextAlignLast;
if (line->mWritingMode.IsVertical() ||
!line->mWritingMode.IsBidiLTR() ||
!IsAlignedLeft(align,
aState.mReflowInput.mStyleVisibility->mDirection,
StyleTextReset()->mUnicodeBidi, this)) {
line->MarkDirty();
}
}
if (needToRecoverState && line->IsDirty()) {
// We need to reconstruct the block-end margin only if we didn't
// reflow the previous line and we do need to reflow (or repair
// the block-start position of) the next line.
aState.ReconstructMarginBefore(line);
}
bool reflowedPrevLine = !needToRecoverState;
if (needToRecoverState) {
needToRecoverState = false;
// Update aState.mPrevChild as if we had reflowed all of the frames in
// this line.
if (line->IsDirty()) {
NS_ASSERTION(line->mFirstChild->GetPrevSibling() ==
line.prev()->LastChild(), "unexpected line frames");
aState.mPrevChild = line->mFirstChild->GetPrevSibling();
}
}
// Now repair the line and update |aState.mBCoord| by calling
// |ReflowLine| or |SlideLine|.
// If we're going to reflow everything again, then no need to reflow
// the dirty line ... unless the line has floats, in which case we'd
// better reflow it now to refresh its float cache, which may contain
// dangling frame pointers! Ugh! This reflow of the line may be
// incorrect because we skipped reflowing previous lines (e.g., floats
// may be placed incorrectly), but that's OK because we'll mark the
// line dirty below under "if (aState.mReflowInput.mDiscoveredClearance..."
if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) {
lastLineMovedUp = true;
bool maybeReflowingForFirstTime =
line->IStart() == 0 && line->BStart() == 0 &&
line->ISize() == 0 && line->BSize() == 0;
// Compute the dirty lines "before" BEnd, after factoring in
// the running deltaBCoord value - the running value is implicit in
// aState.mBCoord.
nscoord oldB = line->BStart();
nscoord oldBMost = line->BEnd();
NS_ASSERTION(!willReflowAgain || !line->IsBlock(),
"Don't reflow blocks while willReflowAgain is true, reflow of block abs-pos children depends on this");
// Reflow the dirty line. If it's an incremental reflow, then force
// it to invalidate the dirty area if necessary
ReflowLine(aState, line, &keepGoing);
if (aState.mReflowInput.WillReflowAgainForClearance()) {
line->MarkDirty();
willReflowAgain = true;
// Note that once we've entered this state, every line that gets here
// (e.g. because it has floats) gets marked dirty and reflowed again.
// in the next pass. This is important, see above.
}
if (line->HasFloats()) {
reflowedFloat = true;
}
if (!keepGoing) {
DumpLine(aState, line, deltaBCoord, -1);
if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
}
break;
}
// Test to see whether the margin that should be carried out
// to the next line (NL) might have changed. In ReflowBlockFrame
// we call nextLine->MarkPreviousMarginDirty if the block's
// actual carried-out block-end margin changed. So here we only
// need to worry about the following effects:
// 1) the line was just created, and it might now be blocking
// a carried-out block-end margin from previous lines that
// used to reach NL from reaching NL
// 2) the line used to be empty, and is now not empty,
// thus blocking a carried-out block-end margin from previous lines
// that used to reach NL from reaching NL
// 3) the line wasn't empty, but now is, so a carried-out
// block-end margin from previous lines that didn't used to reach NL
// now does
// 4) the line might have changed in a way that affects NL's
// ShouldApplyBStartMargin decision. The three things that matter
// are the line's emptiness, its adjacency to the block-start edge of the
// block, and whether it has clearance (the latter only matters if the
// block was and is adjacent to the block-start and empty).
//
// If the line is empty now, we can't reliably tell if the line was empty
// before, so we just assume it was and do nextLine->MarkPreviousMarginDirty.
// This means the checks in 4) are redundant; if the line is empty now
// we don't need to check 4), but if the line is not empty now and we're sure
// it wasn't empty before, any adjacency and clearance changes are irrelevant
// to the result of nextLine->ShouldApplyBStartMargin.
if (line.next() != LinesEnd()) {
bool maybeWasEmpty = oldB == line.next()->BStart();
bool isEmpty = line->CachedIsEmpty();
if (maybeReflowingForFirstTime /*1*/ ||
(isEmpty || maybeWasEmpty) /*2/3/4*/) {
line.next()->MarkPreviousMarginDirty();
// since it's marked dirty, nobody will care about |deltaBCoord|
}
}
// If the line was just reflowed for the first time, then its
// old mBounds cannot be trusted so this deltaBCoord computation is
// bogus. But that's OK because we just did
// MarkPreviousMarginDirty on the next line which will force it
// to be reflowed, so this computation of deltaBCoord will not be
// used.
deltaBCoord = line->BEnd() - oldBMost;
// Now do an interrupt check. We want to do this only in the case when we
// actually reflow the line, so that if we get back in here we'll get
// further on the reflow before interrupting.
aState.mPresContext->CheckForInterrupt(this);
} else {
aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus);
// Nop except for blocks (we don't create overflow container
// continuations for any inlines atm), so only checking mFirstChild
// is enough
lastLineMovedUp = deltaBCoord < 0;
if (deltaBCoord != 0)
SlideLine(aState, line, deltaBCoord);
else
repositionViews = true;
NS_ASSERTION(!line->IsDirty() || !line->HasFloats(),
"Possibly stale float cache here!");
if (willReflowAgain && line->IsBlock()) {
// If we're going to reflow everything again, and this line is a block,
// then there is no need to recover float state. The line may contain
// other lines with floats, but in that case RecoverStateFrom would only
// add floats to the float manager. We don't need to do that because
// everything's going to get reflowed again "for real". Calling
// RecoverStateFrom in this situation could be lethal because the
// block's descendant lines may have float caches containing dangling
// frame pointers. Ugh!
// If this line is inline, then we need to recover its state now
// to make sure that we don't forget to move its floats by deltaBCoord.
} else {
// XXX EVIL O(N^2) EVIL
aState.RecoverStateFrom(line, deltaBCoord);
}
// Keep mBCoord up to date in case we're propagating reflow damage
// and also because our final height may depend on it. If the
// line is inlines, then only update mBCoord if the line is not
// empty, because that's what PlaceLine does. (Empty blocks may
// want to update mBCoord, e.g. if they have clearance.)
if (line->IsBlock() || !line->CachedIsEmpty()) {
aState.mBCoord = line->BEnd();
}
needToRecoverState = true;
if (reflowedPrevLine && !line->IsBlock() &&
aState.mPresContext->HasPendingInterrupt()) {
// Need to make sure to pull overflows from any prev-in-flows
for (nsIFrame* inlineKid = line->mFirstChild; inlineKid;
inlineKid = inlineKid->PrincipalChildList().FirstChild()) {
inlineKid->PullOverflowsFromPrevInFlow();
}
}
}
// Record if we need to clear floats before reflowing the next
// line. Note that inlineFloatBreakType will be handled and
// cleared before the next line is processed, so there is no
// need to combine break types here.
if (line->HasFloatBreakAfter()) {
inlineFloatBreakType = line->GetBreakTypeAfter();
}
if (LineHasClear(line.get())) {
foundAnyClears = true;
}
DumpLine(aState, line, deltaBCoord, -1);
if (aState.mPresContext->HasPendingInterrupt()) {
willReflowAgain = true;
// Another option here might be to leave |line| clean if
// !HasPendingInterrupt() before the CheckForInterrupt() call, since in
// that case the line really did reflow as it should have. Not sure
// whether that would be safe, so doing this for now instead. Also not
// sure whether we really want to mark all lines dirty after an
// interrupt, but until we get better at propagating float damage we
// really do need to do it this way; see comments inside MarkLineDirty.
MarkLineDirtyForInterrupt(line);
}
}
// Handle BR-clearance from the last line of the block
if (inlineFloatBreakType != StyleClear::None) {
aState.mBCoord = aState.ClearFloats(aState.mBCoord, inlineFloatBreakType);
}
if (needToRecoverState) {
// Is this expensive?
aState.ReconstructMarginBefore(line);
// Update aState.mPrevChild as if we had reflowed all of the frames in
// the last line.
NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() ==
line.prev()->LastChild(), "unexpected line frames");
aState.mPrevChild =
line == line_end ? mFrames.LastChild() : line->mFirstChild->GetPrevSibling();
}
// Should we really have to do this?
if (repositionViews)
nsContainerFrame::PlaceFrameView(this);
// We can skip trying to pull up the next line if our height is constrained
// (so we can report being incomplete) and there is no next in flow or we
// were told not to or we know it will be futile, i.e.,
// -- the next in flow is not changing
// -- and we cannot have added more space for its first line to be
// pulled up into,
// -- it's an incremental reflow of a descendant
// -- and we didn't reflow any floats (so the available space
// didn't change)
// -- my chain of next-in-flows either has no first line, or its first
// line isn't dirty.
bool heightConstrained =
aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE;
bool skipPull = willReflowAgain && heightConstrained;
if (!skipPull && heightConstrained && aState.mNextInFlow &&
(aState.mReflowInput.mFlags.mNextInFlowUntouched &&
!lastLineMovedUp &&
!(GetStateBits() & NS_FRAME_IS_DIRTY) &&
!reflowedFloat)) {
// We'll place lineIter at the last line of this block, so that
// nsBlockInFlowLineIterator::Next() will take us to the first
// line of my next-in-flow-chain. (But first, check that I
// have any lines -- if I don't, just bail out of this
// optimization.)
LineIterator lineIter = this->LinesEnd();
if (lineIter != this->LinesBegin()) {
lineIter--; // I have lines; step back from dummy iterator to last line.
nsBlockInFlowLineIterator bifLineIter(this, lineIter);
// Check for next-in-flow-chain's first line.
// (First, see if there is such a line, and second, see if it's clean)
if (!bifLineIter.Next() ||
!bifLineIter.GetLine()->IsDirty()) {
skipPull=true;
}
}
}
if (skipPull && aState.mNextInFlow) {
NS_ASSERTION(heightConstrained, "Height should be constrained here\n");
if (IS_TRUE_OVERFLOW_CONTAINER(aState.mNextInFlow))
aState.mReflowStatus.SetOverflowIncomplete();
else
aState.mReflowStatus.SetIncomplete();
}
if (!skipPull && aState.mNextInFlow) {
// Pull data from a next-in-flow if there's still room for more
// content here.
while (keepGoing && aState.mNextInFlow) {
// Grab first line from our next-in-flow
nsBlockFrame* nextInFlow = aState.mNextInFlow;
nsLineBox* pulledLine;
nsFrameList pulledFrames;
if (!nextInFlow->mLines.empty()) {
RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames,
&pulledLine, &pulledFrames);
} else {
// Grab an overflow line if there are any
FrameLines* overflowLines = nextInFlow->GetOverflowLines();
if (!overflowLines) {
aState.mNextInFlow =
static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
continue;
}
bool last =
RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames,
&pulledLine, &pulledFrames);
if (last) {
nextInFlow->DestroyOverflowLines();
}
}
if (pulledFrames.IsEmpty()) {
// The line is empty. Try the next one.
NS_ASSERTION(pulledLine->GetChildCount() == 0 &&
!pulledLine->mFirstChild, "bad empty line");
nextInFlow->FreeLineBox(pulledLine);
continue;
}
if (pulledLine == nextInFlow->GetLineCursor()) {
nextInFlow->ClearLineCursor();
}
ReparentFrames(pulledFrames, nextInFlow, this);
NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(),
"Unexpected last frame");
NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here");
NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(),
"Incorrect aState.mPrevChild before inserting line at end");
// Shift pulledLine's frames into our mFrames list.
mFrames.AppendFrames(nullptr, pulledFrames);
// Add line to our line list, and set its last child as our new prev-child
line = mLines.before_insert(LinesEnd(), pulledLine);
aState.mPrevChild = mFrames.LastChild();
// Reparent floats whose placeholders are in the line.
ReparentFloats(pulledLine->mFirstChild, nextInFlow, true);
DumpLine(aState, pulledLine, deltaBCoord, 0);
#ifdef DEBUG
AutoNoisyIndenter indent2(gNoisyReflow);
#endif
if (aState.mPresContext->HasPendingInterrupt()) {
MarkLineDirtyForInterrupt(line);
} else {
// Now reflow it and any lines that it makes during it's reflow
// (we have to loop here because reflowing the line may cause a new
// line to be created; see SplitLine's callers for examples of
// when this happens).
while (line != LinesEnd()) {
ReflowLine(aState, line, &keepGoing);
if (aState.mReflowInput.WillReflowAgainForClearance()) {
line->MarkDirty();
keepGoing = false;
aState.mReflowStatus.SetIncomplete();
break;
}
DumpLine(aState, line, deltaBCoord, -1);
if (!keepGoing) {
if (0 == line->GetChildCount()) {
DeleteLine(aState, line, line_end);
}
break;
}
if (LineHasClear(line.get())) {
foundAnyClears = true;
}
if (aState.mPresContext->CheckForInterrupt(this)) {
MarkLineDirtyForInterrupt(line);
break;
}
// If this is an inline frame then its time to stop
++line;
aState.AdvanceToNextLine();
}
}
}
if (aState.mReflowStatus.IsIncomplete()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
} //XXXfr shouldn't set this flag when nextinflow has no lines
}
// Handle an odd-ball case: a list-item with no lines
if (HasOutsideBullet() && mLines.empty()) {
ReflowOutput metrics(aState.mReflowInput);
nsIFrame* bullet = GetOutsideBullet();
WritingMode wm = aState.mReflowInput.GetWritingMode();
ReflowBullet(bullet, aState, metrics,
aState.mReflowInput.ComputedPhysicalBorderPadding().top);
NS_ASSERTION(!BulletIsEmpty() || metrics.BSize(wm) == 0,
"empty bullet took up space");
if (!BulletIsEmpty()) {
// There are no lines so we have to fake up some y motion so that
// we end up with *some* height.
if (metrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
nscoord ascent;
WritingMode wm = aState.mReflowInput.GetWritingMode();
if (nsLayoutUtils::GetFirstLineBaseline(wm, bullet, &ascent)) {
metrics.SetBlockStartAscent(ascent);
} else {
metrics.SetBlockStartAscent(metrics.BSize(wm));
}
}
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetInflatedFontMetricsForFrame(this);
nscoord minAscent =
nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight,
wm.IsLineInverted());
nscoord minDescent = aState.mMinLineHeight - minAscent;
aState.mBCoord += std::max(minAscent, metrics.BlockStartAscent()) +
std::max(minDescent, metrics.BSize(wm) -
metrics.BlockStartAscent());
nscoord offset = minAscent - metrics.BlockStartAscent();
if (offset > 0) {
bullet->SetRect(bullet->GetRect() + nsPoint(0, offset));
}
}
}
if (foundAnyClears) {
AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
} else {
RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN);
}
#ifdef DEBUG
VerifyLines(true);
VerifyOverflowSituation();
if (gNoisyReflow) {
IndentBy(stdout, gNoiseIndent - 1);
ListTag(stdout);
printf(": done reflowing dirty lines (status=%s)\n",
ToString(aState.mReflowStatus).c_str());
}
#endif
}
void
nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine)
{
aLine->MarkDirty();
// Just checking NS_FRAME_IS_DIRTY is ok, because we've already
// marked the lines that need to be marked dirty based on our
// vertical resize stuff. So we'll definitely reflow all those kids;
// the only question is how they should behave.
if (GetStateBits() & NS_FRAME_IS_DIRTY) {
// Mark all our child frames dirty so we make sure to reflow them
// later.
int32_t n = aLine->GetChildCount();
for (nsIFrame* f = aLine->mFirstChild; n > 0;
f = f->GetNextSibling(), --n) {
f->AddStateBits(NS_FRAME_IS_DIRTY);
}
// And mark all the floats whose reflows we might be skipping dirty too.
if (aLine->HasFloats()) {
for (nsFloatCache* fc = aLine->GetFirstFloat(); fc; fc = fc->Next()) {
fc->mFloat->AddStateBits(NS_FRAME_IS_DIRTY);
}
}
} else {
// Dirty all the descendant lines of block kids to handle float damage,
// since our nsFloatManager will go away by the next time we're reflowing.
// XXXbz Can we do something more like what PropagateFloatDamage does?
// Would need to sort out the exact business with mBlockDelta for that....
// This marks way too much dirty. If we ever make this better, revisit
// which lines we mark dirty in the interrupt case in ReflowDirtyLines.
nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(aLine->mFirstChild);
if (bf) {
MarkAllDescendantLinesDirty(bf);
}
}
}
void
nsBlockFrame::DeleteLine(BlockReflowInput& aState,
nsLineList::iterator aLine,
nsLineList::iterator aLineEnd)
{
NS_PRECONDITION(0 == aLine->GetChildCount(), "can't delete !empty line");
if (0 == aLine->GetChildCount()) {
NS_ASSERTION(aState.mCurrentLine == aLine,
"using function more generally than designed, "
"but perhaps OK now");
nsLineBox* line = aLine;
aLine = mLines.erase(aLine);
FreeLineBox(line);
// Mark the previous margin of the next line dirty since we need to
// recompute its top position.
if (aLine != aLineEnd)
aLine->MarkPreviousMarginDirty();
}
}
/**
* Reflow a line. The line will either contain a single block frame
* or contain 1 or more inline frames. aKeepReflowGoing indicates
* whether or not the caller should continue to reflow more lines.
*/
void
nsBlockFrame::ReflowLine(BlockReflowInput& aState,
LineIterator aLine,
bool* aKeepReflowGoing)
{
MOZ_ASSERT(aLine->GetChildCount(), "reflowing empty line");
// Setup the line-layout for the new line
aState.mCurrentLine = aLine;
aLine->ClearDirty();
aLine->InvalidateCachedIsEmpty();
aLine->ClearHadFloatPushed();
// Now that we know what kind of line we have, reflow it
if (aLine->IsBlock()) {
ReflowBlockFrame(aState, aLine, aKeepReflowGoing);
} else {
aLine->SetLineWrapped(false);
ReflowInlineFrames(aState, aLine, aKeepReflowGoing);
// Store the line's float edges for text-overflow analysis if needed.
aLine->ClearFloatEdges();
if (aState.mFlags.mCanHaveTextOverflow) {
WritingMode wm = aLine->mWritingMode;
nsFlowAreaRect r = aState.GetFloatAvailableSpaceForBSize(aLine->BStart(),
aLine->BSize(),
nullptr);
if (r.mHasFloats) {
LogicalRect so =
aLine->GetOverflowArea(eScrollableOverflow, wm, aLine->mContainerSize);
nscoord s = r.mRect.IStart(wm);
nscoord e = r.mRect.IEnd(wm);
if (so.IEnd(wm) > e || so.IStart(wm) < s) {
// This line is overlapping a float - store the edges marking the area
// between the floats for text-overflow analysis.
aLine->SetFloatEdges(s, e);
}
}
}
}
}
nsIFrame*
nsBlockFrame::PullFrame(BlockReflowInput& aState,
LineIterator aLine)
{
// First check our remaining lines.
if (LinesEnd() != aLine.next()) {
return PullFrameFrom(aLine, this, aLine.next());
}
NS_ASSERTION(!GetOverflowLines(),
"Our overflow lines should have been removed at the start of reflow");
// Try each next-in-flow.
nsBlockFrame* nextInFlow = aState.mNextInFlow;
while (nextInFlow) {
if (nextInFlow->mLines.empty()) {
nextInFlow->DrainSelfOverflowList();
}
if (!nextInFlow->mLines.empty()) {
return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin());
}
nextInFlow = static_cast<nsBlockFrame*>(nextInFlow->GetNextInFlow());
aState.mNextInFlow = nextInFlow;
}
return nullptr;
}
nsIFrame*
nsBlockFrame::PullFrameFrom(nsLineBox* aLine,
nsBlockFrame* aFromContainer,
nsLineList::iterator aFromLine)
{
nsLineBox* fromLine = aFromLine;
MOZ_ASSERT(fromLine, "bad line to pull from");
MOZ_ASSERT(fromLine->GetChildCount(), "empty line");
MOZ_ASSERT(aLine->GetChildCount(), "empty line");
NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(),
"Disagreement about whether it's a block or not");
if (fromLine->IsBlock()) {
// If our line is not empty and the child in aFromLine is a block
// then we cannot pull up the frame into this line. In this case
// we stop pulling.
return nullptr;
}
// Take frame from fromLine
nsIFrame* frame = fromLine->mFirstChild;
nsIFrame* newFirstChild = frame->GetNextSibling();
if (aFromContainer != this) {
// The frame is being pulled from a next-in-flow; therefore we
// need to add it to our sibling list.
MOZ_ASSERT(aLine == mLines.back());
MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(),
"should only pull from first line");
aFromContainer->mFrames.RemoveFrame(frame);
// When pushing and pulling frames we need to check for whether any
// views need to be reparented.
ReparentFrame(frame, aFromContainer, this);
mFrames.AppendFrame(nullptr, frame);
// The frame might have (or contain) floats that need to be brought
// over too. (pass 'false' since there are no siblings to check)
ReparentFloats(frame, aFromContainer, false);
} else {
MOZ_ASSERT(aLine == aFromLine.prev());
}
aLine->NoteFrameAdded(frame);
fromLine->NoteFrameRemoved(frame);
if (fromLine->GetChildCount() > 0) {
// Mark line dirty now that we pulled a child
fromLine->MarkDirty();
fromLine->mFirstChild = newFirstChild;
} else {
// Free up the fromLine now that it's empty.
// Its bounds might need to be redrawn, though.
if (aFromLine.next() != aFromContainer->mLines.end()) {
aFromLine.next()->MarkPreviousMarginDirty();
}
aFromContainer->mLines.erase(aFromLine);
// aFromLine is now invalid
aFromContainer->FreeLineBox(fromLine);
}
#ifdef DEBUG
VerifyLines(true);
VerifyOverflowSituation();
#endif
return frame;
}
void
nsBlockFrame::SlideLine(BlockReflowInput& aState,
nsLineBox* aLine, nscoord aDeltaBCoord)
{
NS_PRECONDITION(aDeltaBCoord != 0, "why slide a line nowhere?");
// Adjust line state
aLine->SlideBy(aDeltaBCoord, aState.ContainerSize());
// Adjust the frames in the line
MoveChildFramesOfLine(aLine, aDeltaBCoord);
}
void
nsBlockFrame::UpdateLineContainerSize(nsLineBox* aLine,
const nsSize& aNewContainerSize)
{
if (aNewContainerSize == aLine->mContainerSize) {
return;
}
// Adjust line state
nsSize sizeDelta = aLine->UpdateContainerSize(aNewContainerSize);
// Changing container width only matters if writing mode is vertical-rl
if (GetWritingMode().IsVerticalRL()) {
MoveChildFramesOfLine(aLine, sizeDelta.width);
}
}
void
nsBlockFrame::MoveChildFramesOfLine(nsLineBox* aLine, nscoord aDeltaBCoord)
{
// Adjust the frames in the line
nsIFrame* kid = aLine->mFirstChild;
if (!kid) {
return;
}
WritingMode wm = GetWritingMode();
LogicalPoint translation(wm, 0, aDeltaBCoord);
if (aLine->IsBlock()) {
if (aDeltaBCoord) {
kid->MovePositionBy(wm, translation);
}
// Make sure the frame's view and any child views are updated
nsContainerFrame::PlaceFrameView(kid);
}
else {
// Adjust the block-dir coordinate of the frames in the line.
// Note: we need to re-position views even if aDeltaBCoord is 0, because
// one of our parent frames may have moved and so the view's position
// relative to its parent may have changed.
int32_t n = aLine->GetChildCount();
while (--n >= 0) {
if (aDeltaBCoord) {
kid->MovePositionBy(wm, translation);
}
// Make sure the frame's view and any child views are updated
nsContainerFrame::PlaceFrameView(kid);
kid = kid->GetNextSibling();
}
}
}
nsresult
nsBlockFrame::AttributeChanged(int32_t aNameSpaceID,
nsAtom* aAttribute,
int32_t aModType)
{
nsresult rv = nsContainerFrame::AttributeChanged(aNameSpaceID,
aAttribute, aModType);
if (NS_FAILED(rv)) {
return rv;
}
if (nsGkAtoms::value == aAttribute) {
const nsStyleDisplay* styleDisplay = StyleDisplay();
if (mozilla::StyleDisplay::ListItem == styleDisplay->mDisplay) {
// Search for the closest ancestor that's a block frame. We
// make the assumption that all related list items share a
// common block/grid/flex ancestor.
// XXXldb I think that's a bad assumption.
nsContainerFrame* ancestor = GetParent();
for (; ancestor; ancestor = ancestor->GetParent()) {
auto frameType = ancestor->Type();
if (frameType == LayoutFrameType::Block ||
frameType == LayoutFrameType::FlexContainer ||
frameType == LayoutFrameType::GridContainer) {
break;
}
}
// Tell the ancestor to renumber list items within itself.
if (ancestor) {
// XXX Not sure if this is necessary anymore
if (ancestor->RenumberList()) {
PresShell()->FrameNeedsReflow(ancestor, nsIPresShell::eStyleChange,
NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
}
return rv;
}
static inline bool
IsNonAutoNonZeroBSize(const nsStyleCoord& aCoord)
{
nsStyleUnit unit = aCoord.GetUnit();
if (unit == eStyleUnit_Auto ||
// The enumerated values were originally aimed at inline-size
// (or width, as it was before logicalization). For now, let them
// return false here, so we treat them like 'auto' pending a
// real implementation. (See bug 1126420.)
//
// FIXME (bug 567039, bug 527285)
// This isn't correct for the 'fill' value, which should more
// likely (but not necessarily, depending on the available space)
// be returning true.
unit == eStyleUnit_Enumerated) {
return false;
}
if (aCoord.IsCoordPercentCalcUnit()) {
// If we evaluate the length/percent/calc at a percentage basis of
// both nscoord_MAX and 0, and it's zero both ways, then it's a zero
// length, percent, or combination thereof. Test > 0 so we clamp
// negative calc() results to 0.
return aCoord.ComputeCoordPercentCalc(nscoord_MAX) > 0 ||
aCoord.ComputeCoordPercentCalc(0) > 0;
}
MOZ_ASSERT(false, "unexpected unit for height or min-height");
return true;
}
/* virtual */ bool
nsBlockFrame::IsSelfEmpty()
{
// Blocks which are margin-roots (including inline-blocks) cannot be treated
// as empty for margin-collapsing and other purposes. They're more like
// replaced elements.
if (GetStateBits() & NS_BLOCK_MARGIN_ROOT) {
return false;
}
WritingMode wm = GetWritingMode();
const nsStylePosition* position = StylePosition();
if (IsNonAutoNonZeroBSize(position->MinBSize(wm)) ||
IsNonAutoNonZeroBSize(position->BSize(wm))) {
return false;
}
const nsStyleBorder* border = StyleBorder();
const nsStylePadding* padding = StylePadding();
if (border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBStart)) != 0 ||
border->GetComputedBorderWidth(wm.PhysicalSide(eLogicalSideBEnd)) != 0 ||
!nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBStart(wm)) ||
!nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBEnd(wm))) {
return false;
}
if (HasOutsideBullet() && !BulletIsEmpty()) {
return false;
}
return true;
}
bool
nsBlockFrame::CachedIsEmpty()
{
if (!IsSelfEmpty()) {
return false;
}
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line)
{
if (!line->CachedIsEmpty())
return false;
}
return true;
}
bool
nsBlockFrame::IsEmpty()
{
if (!IsSelfEmpty()) {
return false;
}
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line)
{
if (!line->IsEmpty())
return false;
}
return true;
}
bool
nsBlockFrame::ShouldApplyBStartMargin(BlockReflowInput& aState,
nsLineBox* aLine,
nsIFrame* aChildFrame)
{
if (aState.mFlags.mShouldApplyBStartMargin) {
// Apply short-circuit check to avoid searching the line list
return true;
}
if (!aState.IsAdjacentWithTop() ||
aChildFrame->StyleBorder()->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Clone) {
// If we aren't at the start block-coordinate then something of non-zero
// height must have been placed. Therefore the childs block-start margin
// applies.
aState.mFlags.mShouldApplyBStartMargin = true;
return true;
}
// Determine if this line is "essentially" the first line
LineIterator line = LinesBegin();
if (aState.mFlags.mHasLineAdjacentToTop) {
line = aState.mLineAdjacentToTop;
}
while (line != aLine) {
if (!line->CachedIsEmpty() || line->HasClearance()) {
// A line which precedes aLine is non-empty, or has clearance,
// so therefore the block-start margin applies.
aState.mFlags.mShouldApplyBStartMargin = true;
return true;
}
// No need to apply the block-start margin if the line has floats. We
// should collapse anyway (bug 44419)
++line;
aState.mFlags.mHasLineAdjacentToTop = true;
aState.mLineAdjacentToTop = line;
}
// The line being reflowed is "essentially" the first line in the
// block. Therefore its block-start margin will be collapsed by the
// generational collapsing logic with its parent (us).
return false;
}
void
nsBlockFrame::ReflowBlockFrame(BlockReflowInput& aState,
LineIterator aLine,
bool* aKeepReflowGoing)
{
NS_PRECONDITION(*aKeepReflowGoing, "bad caller");
nsIFrame* frame = aLine->mFirstChild;
if (!frame) {
NS_ASSERTION(false, "program error - unexpected empty line");
return;
}
// Prepare the block reflow engine
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
StyleClear breakType = frame->StyleDisplay()->
PhysicalBreakType(aState.mReflowInput.GetWritingMode());
if (StyleClear::None != aState.mFloatBreakType) {
breakType = nsLayoutUtils::CombineBreakType(breakType,
aState.mFloatBreakType);
aState.mFloatBreakType = StyleClear::None;
}
// Clear past floats before the block if the clear style is not none
aLine->SetBreakTypeBefore(breakType);
// See if we should apply the block-start margin. If the block frame being
// reflowed is a continuation (non-null prev-in-flow) then we don't
// apply its block-start margin because it's not significant unless it has
// 'box-decoration-break:clone'. Otherwise, dig deeper.
bool applyBStartMargin = (frame->StyleBorder()->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Clone ||
!frame->GetPrevInFlow()) &&
ShouldApplyBStartMargin(aState, aLine, frame);
if (applyBStartMargin) {
// The HasClearance setting is only valid if ShouldApplyBStartMargin
// returned false (in which case the block-start margin-root set our
// clearance flag). Otherwise clear it now. We'll set it later on
// ourselves if necessary.
aLine->ClearHasClearance();
}
bool treatWithClearance = aLine->HasClearance();
bool mightClearFloats = breakType != StyleClear::None;
nsIFrame *replacedBlock = nullptr;
if (!nsBlockFrame::BlockCanIntersectFloats(frame)) {
mightClearFloats = true;
replacedBlock = frame;
}
// If our block-start margin was counted as part of some parent's block-start
// margin collapse, and we are being speculatively reflowed assuming this
// frame DID NOT need clearance, then we need to check that
// assumption.
if (!treatWithClearance && !applyBStartMargin && mightClearFloats &&
aState.mReflowInput.mDiscoveredClearance) {
nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
nscoord clearBCoord = aState.ClearFloats(curBCoord, breakType, replacedBlock);
if (clearBCoord != curBCoord) {
// Only record the first frame that requires clearance
if (!*aState.mReflowInput.mDiscoveredClearance) {
*aState.mReflowInput.mDiscoveredClearance = frame;
}
aState.mPrevChild = frame;
// Exactly what we do now is flexible since we'll definitely be
// reflowed.
return;
}
}
if (treatWithClearance) {
applyBStartMargin = true;
}
nsIFrame* clearanceFrame = nullptr;
nscoord startingBCoord = aState.mBCoord;
nsCollapsingMargin incomingMargin = aState.mPrevBEndMargin;
nscoord clearance;
// Save the original position of the frame so that we can reposition
// its view as needed.
nsPoint originalPosition = frame->GetPosition();
while (true) {
clearance = 0;
nscoord bStartMargin = 0;
bool mayNeedRetry = false;
bool clearedFloats = false;
if (applyBStartMargin) {
// Precompute the blocks block-start margin value so that we can get the
// correct available space (there might be a float that's
// already been placed below the aState.mPrevBEndMargin
// Setup a reflowInput to get the style computed block-start margin
// value. We'll use a reason of `resize' so that we don't fudge
// any incremental reflow state.
// The availSpace here is irrelevant to our needs - all we want
// out if this setup is the block-start margin value which doesn't depend
// on the childs available space.
// XXX building a complete ReflowInput just to get the block-start
// margin seems like a waste. And we do this for almost every block!
WritingMode wm = frame->GetWritingMode();
LogicalSize availSpace = aState.ContentSize(wm);
ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
frame, availSpace);
if (treatWithClearance) {
aState.mBCoord += aState.mPrevBEndMargin.get();
aState.mPrevBEndMargin.Zero();
}
// Now compute the collapsed margin-block-start value into
// aState.mPrevBEndMargin, assuming that all child margins
// collapse down to clearanceFrame.
brc.ComputeCollapsedBStartMargin(reflowInput,
&aState.mPrevBEndMargin,
clearanceFrame,
&mayNeedRetry);
// XXX optimization; we could check the collapsing children to see if they are sure
// to require clearance, and so avoid retrying them
if (clearanceFrame) {
// Don't allow retries on the second pass. The clearance decisions for the
// blocks whose block-start margins collapse with ours are now fixed.
mayNeedRetry = false;
}
if (!treatWithClearance && !clearanceFrame && mightClearFloats) {
// We don't know if we need clearance and this is the first,
// optimistic pass. So determine whether *this block* needs
// clearance. Note that we do not allow the decision for whether
// this block has clearance to change on the second pass; that
// decision is only allowed to be made under the optimistic
// first pass.
nscoord curBCoord = aState.mBCoord + aState.mPrevBEndMargin.get();
nscoord clearBCoord = aState.ClearFloats(curBCoord, breakType, replacedBlock);
if (clearBCoord != curBCoord) {
// Looks like we need clearance and we didn't know about it already. So
// recompute collapsed margin
treatWithClearance = true;
// Remember this decision, needed for incremental reflow
aLine->SetHasClearance();
// Apply incoming margins
aState.mBCoord += aState.mPrevBEndMargin.get();
aState.mPrevBEndMargin.Zero();
// Compute the collapsed margin again, ignoring the incoming margin this time
mayNeedRetry = false;
brc.ComputeCollapsedBStartMargin(reflowInput,
&aState.mPrevBEndMargin,
clearanceFrame,
&mayNeedRetry);
}
}
// Temporarily advance the running Y value so that the
// GetAvailableSpace method will return the right available
// space. This undone as soon as the horizontal margins are
// computed.
bStartMargin = aState.mPrevBEndMargin.get();
if (treatWithClearance) {
nscoord currentBCoord = aState.mBCoord;
// advance mBCoord to the clear position.
aState.mBCoord = aState.ClearFloats(aState.mBCoord, breakType,
replacedBlock);
clearedFloats = aState.mBCoord != currentBCoord;
// Compute clearance. It's the amount we need to add to the block-start
// border-edge of the frame, after applying collapsed margins
// from the frame and its children, to get it to line up with
// the block-end of the floats. The former is
// currentBCoord + bStartMargin, the latter is the current
// aState.mBCoord.
// Note that negative clearance is possible
clearance = aState.mBCoord - (currentBCoord + bStartMargin);
// Add clearance to our block-start margin while we compute available
// space for the frame
bStartMargin += clearance;
// Note that aState.mBCoord should stay where it is: at the block-start
// border-edge of the frame
} else {
// Advance aState.mBCoord to the block-start border-edge of the frame.
aState.mBCoord += bStartMargin;
}
}
aLine->SetLineIsImpactedByFloat(false);
// Here aState.mBCoord is the block-start border-edge of the block.
// Compute the available space for the block
nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
WritingMode wm = aState.mReflowInput.GetWritingMode();
LogicalRect availSpace(wm);
aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
replacedBlock != nullptr, availSpace);
// The check for
// (!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats)
// is to some degree out of paranoia: if we reliably eat up block-start
// margins at the top of the page as we ought to, it wouldn't be
// needed.
if ((!aState.mReflowInput.mFlags.mIsTopOfPage || clearedFloats) &&
availSpace.BSize(wm) < 0) {
// We know already that this child block won't fit on this
// page/column due to the block-start margin or the clearance. So we
// need to get out of here now. (If we don't, most blocks will handle
// things fine, and report break-before, but zero-height blocks
// won't, and will thus make their parent overly-large and force
// *it* to be pushed in its entirety.)
// Doing this means that we also don't need to worry about the
// |availSpace.BSize(wm) += bStartMargin| below interacting with
// pushed floats (which force nscoord_MAX clearance) to cause a
// constrained block size to turn into an unconstrained one.
aState.mBCoord = startingBCoord;
aState.mPrevBEndMargin = incomingMargin;
*aKeepReflowGoing = false;
if (ShouldAvoidBreakInside(aState.mReflowInput)) {
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
} else {
PushLines(aState, aLine.prev());
aState.mReflowStatus.SetIncomplete();
}
return;
}
// Now put the block-dir coordinate back to the start of the
// block-start-margin + clearance.
aState.mBCoord -= bStartMargin;
availSpace.BStart(wm) -= bStartMargin;
if (NS_UNCONSTRAINEDSIZE != availSpace.BSize(wm)) {
availSpace.BSize(wm) += bStartMargin;
}
// construct the html reflow state for the block. ReflowBlock
// will initialize it.
Maybe<ReflowInput> blockHtmlRI;
blockHtmlRI.emplace(
aState.mPresContext, aState.mReflowInput, frame,
availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
nsFloatManager::SavedState floatManagerState;
nsReflowStatus frameReflowStatus;
do {
if (floatAvailableSpace.mHasFloats) {
// Set if floatAvailableSpace.mHasFloats is true for any
// iteration of the loop.
aLine->SetLineIsImpactedByFloat(true);
}
// We might need to store into mDiscoveredClearance later if it's
// currently null; we want to overwrite any writes that
// brc.ReflowBlock() below does, so we need to remember now
// whether it's empty.
const bool shouldStoreClearance =
aState.mReflowInput.mDiscoveredClearance &&
!*aState.mReflowInput.mDiscoveredClearance;
// Reflow the block into the available space
if (mayNeedRetry || replacedBlock) {
aState.FloatManager()->PushState(&floatManagerState);
}
if (mayNeedRetry) {
blockHtmlRI->mDiscoveredClearance = &clearanceFrame;
} else if (!applyBStartMargin) {
blockHtmlRI->mDiscoveredClearance =
aState.mReflowInput.mDiscoveredClearance;
}
frameReflowStatus.Reset();
brc.ReflowBlock(availSpace, applyBStartMargin, aState.mPrevBEndMargin,
clearance, aState.IsAdjacentWithTop(),
aLine.get(), *blockHtmlRI, frameReflowStatus, aState);
// Now the block has a height. Using that height, get the
// available space again and call ComputeBlockAvailSpace again.
// If ComputeBlockAvailSpace gives a different result, we need to
// reflow again.
if (!replacedBlock) {
break;
}
LogicalRect oldFloatAvailableSpaceRect(floatAvailableSpace.mRect);
floatAvailableSpace = aState.GetFloatAvailableSpaceForBSize(
aState.mBCoord + bStartMargin,
brc.GetMetrics().BSize(wm),
&floatManagerState);
NS_ASSERTION(floatAvailableSpace.mRect.BStart(wm) ==
oldFloatAvailableSpaceRect.BStart(wm),
"yikes");
// Restore the height to the position of the next band.
floatAvailableSpace.mRect.BSize(wm) =
oldFloatAvailableSpaceRect.BSize(wm);
// Determine whether the available space shrunk on either side,
// because (the first time round) we now know the block's height,
// and it may intersect additional floats, or (on later
// iterations) because narrowing the width relative to the
// previous time may cause the block to become taller. Note that
// since we're reflowing the block, narrowing the width might also
// make it shorter, so we must pass aCanGrow as true.
if (!AvailableSpaceShrunk(wm, oldFloatAvailableSpaceRect,
floatAvailableSpace.mRect, true)) {
// The size and position we chose before are fine (i.e., they
// don't cause intersecting with floats that requires a change
// in size or position), so we're done.
break;
}
bool advanced = false;
if (!aState.ReplacedBlockFitsInAvailSpace(replacedBlock,
floatAvailableSpace)) {
// Advance to the next band.
nscoord newBCoord = aState.mBCoord;
if (aState.AdvanceToNextBand(floatAvailableSpace.mRect, &newBCoord)) {
advanced = true;
}
// ClearFloats might be able to advance us further once we're there.
aState.mBCoord =
aState.ClearFloats(newBCoord, StyleClear::None, replacedBlock);
// Start over with a new available space rect at the new height.
floatAvailableSpace =
aState.GetFloatAvailableSpaceWithState(aState.mBCoord,
ShapeType::ShapeOutside,
&floatManagerState);
}
LogicalRect oldAvailSpace(availSpace);
aState.ComputeBlockAvailSpace(frame, floatAvailableSpace,
replacedBlock != nullptr, availSpace);
if (!advanced && availSpace.IsEqualEdges(oldAvailSpace)) {
break;
}
// We need another reflow.
aState.FloatManager()->PopState(&floatManagerState);
if (!treatWithClearance && !applyBStartMargin &&
aState.mReflowInput.mDiscoveredClearance) {
// We set shouldStoreClearance above to record only the first
// frame that requires clearance.
if (shouldStoreClearance) {
*aState.mReflowInput.mDiscoveredClearance = frame;
}
aState.mPrevChild = frame;
// Exactly what we do now is flexible since we'll definitely be
// reflowed.
return;
}
if (advanced) {
// We're pushing down the border-box, so we don't apply margin anymore.
// This should never cause us to move up since the call to
// GetFloatAvailableSpaceForBSize above included the margin.
applyBStartMargin = false;
bStartMargin = 0;
treatWithClearance = true; // avoid hitting test above
clearance = 0;
}
blockHtmlRI.reset();
blockHtmlRI.emplace(
aState.mPresContext, aState.mReflowInput, frame,
availSpace.Size(wm).ConvertTo(frame->GetWritingMode(), wm));
} while (true);
if (mayNeedRetry && clearanceFrame) {
aState.FloatManager()->PopState(&floatManagerState);
aState.mBCoord = startingBCoord;
aState.mPrevBEndMargin = incomingMargin;
continue;
}
aState.mPrevChild = frame;
if (blockHtmlRI->WillReflowAgainForClearance()) {
// If an ancestor of ours is going to reflow for clearance, we
// need to avoid calling PlaceBlock, because it unsets dirty bits
// on the child block (both itself, and through its call to
// nsFrame::DidReflow), and those dirty bits imply dirtiness for
// all of the child block, including the lines it didn't reflow.
NS_ASSERTION(originalPosition == frame->GetPosition(),
"we need to call PositionChildViews");
return;
}
#if defined(REFLOW_STATUS_COVERAGE)
RecordReflowStatus(true, frameReflowStatus);
#endif
if (frameReflowStatus.IsInlineBreakBefore()) {
// None of the child block fits.
*aKeepReflowGoing = false;
if (ShouldAvoidBreakInside(aState.mReflowInput)) {
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
} else {
PushLines(aState, aLine.prev());
aState.mReflowStatus.SetIncomplete();
}
}
else {
// Note: line-break-after a block is a nop
// Try to place the child block.
// Don't force the block to fit if we have positive clearance, because
// pushing it to the next page would give it more room.
// Don't force the block to fit if it's impacted by a float. If it is,
// then pushing it to the next page would give it more room. Note that
// isImpacted doesn't include impact from the block's own floats.
bool forceFit = aState.IsAdjacentWithTop() && clearance <= 0 &&
!floatAvailableSpace.mHasFloats;
nsCollapsingMargin collapsedBEndMargin;
nsOverflowAreas overflowAreas;
*aKeepReflowGoing = brc.PlaceBlock(*blockHtmlRI, forceFit, aLine.get(),
collapsedBEndMargin,
overflowAreas,
frameReflowStatus);
if (!frameReflowStatus.IsFullyComplete() &&
ShouldAvoidBreakInside(aState.mReflowInput)) {
*aKeepReflowGoing = false;
}
if (aLine->SetCarriedOutBEndMargin(collapsedBEndMargin)) {
LineIterator nextLine = aLine;
++nextLine;
if (nextLine != LinesEnd()) {
nextLine->MarkPreviousMarginDirty();
}
}
aLine->SetOverflowAreas(overflowAreas);
if (*aKeepReflowGoing) {
// Some of the child block fit
// Advance to new Y position
nscoord newBCoord = aLine->BEnd();
aState.mBCoord = newBCoord;
// Continue the block frame now if it didn't completely fit in
// the available space.
if (!frameReflowStatus.IsFullyComplete()) {
bool madeContinuation =
CreateContinuationFor(aState, nullptr, frame);
nsIFrame* nextFrame = frame->GetNextInFlow();
NS_ASSERTION(nextFrame, "We're supposed to have a next-in-flow by now");
if (frameReflowStatus.IsIncomplete()) {
// If nextFrame used to be an overflow container, make it a normal block
if (!madeContinuation &&
(NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) {
nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, frame);
nsContainerFrame* parent = nextFrame->GetParent();
nsresult rv = parent->StealFrame(nextFrame);
if (NS_FAILED(rv)) {
return;
}
if (parent != this)
ReparentFrame(nextFrame, parent, this);
mFrames.InsertFrame(nullptr, frame, nextFrame);
madeContinuation = true; // needs to be added to mLines
nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
frameReflowStatus.SetNextInFlowNeedsReflow();
}
// Push continuation to a new line, but only if we actually made one.
if (madeContinuation) {
nsLineBox* line = NewLineBox(nextFrame, true);
mLines.after_insert(aLine, line);
}
PushLines(aState, aLine);
aState.mReflowStatus.SetIncomplete();
// If we need to reflow the continuation of the block child,
// then we'd better reflow our continuation
if (frameReflowStatus.NextInFlowNeedsReflow()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
// We also need to make that continuation's line dirty so
// it gets reflowed when we reflow our next in flow. The
// nif's line must always be either a line of the nif's
// parent block (only if we didn't make a continuation) or
// else one of our own overflow lines. In the latter case
// the line is already marked dirty, so just handle the
// first case.
if (!madeContinuation) {
nsBlockFrame* nifBlock =
nsLayoutUtils::GetAsBlock(nextFrame->GetParent());
NS_ASSERTION(nifBlock,
"A block's child's next in flow's parent must be a block!");
for (LineIterator line = nifBlock->LinesBegin(),
line_end = nifBlock->LinesEnd(); line != line_end; ++line) {
if (line->Contains(nextFrame)) {
line->MarkDirty();
break;
}
}
}
}
*aKeepReflowGoing = false;
// The block-end margin for a block is only applied on the last
// flow block. Since we just continued the child block frame,
// we know that line->mFirstChild is not the last flow block
// therefore zero out the running margin value.
#ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": reflow incomplete, frame=");
nsFrame::ListTag(stdout, mFrame);
printf(" prevBEndMargin=%d, setting to zero\n",
aState.mPrevBEndMargin.get());
#endif
aState.mPrevBEndMargin.Zero();
}
else { // frame is complete but its overflow is not complete
// Disconnect the next-in-flow and put it in our overflow tracker
if (!madeContinuation &&
!(NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) {
// It already exists, but as a normal next-in-flow, so we need
// to dig it out of the child lists.
nsresult rv = nextFrame->GetParent()->StealFrame(nextFrame);
if (NS_FAILED(rv)) {
return;
}
}
else if (madeContinuation) {
mFrames.RemoveFrame(nextFrame);
}
// Put it in our overflow list
aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus);
aState.mReflowStatus.MergeCompletionStatusFrom(frameReflowStatus);
#ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": reflow complete but overflow incomplete for ");
nsFrame::ListTag(stdout, mFrame);
printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
#endif
aState.mPrevBEndMargin = collapsedBEndMargin;
}
}
else { // frame is fully complete
#ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": reflow complete for ");
nsFrame::ListTag(stdout, mFrame);
printf(" prevBEndMargin=%d collapsedBEndMargin=%d\n",
aState.mPrevBEndMargin.get(), collapsedBEndMargin.get());
#endif
aState.mPrevBEndMargin = collapsedBEndMargin;
}
#ifdef NOISY_BLOCK_DIR_MARGINS
ListTag(stdout);
printf(": frame=");
nsFrame::ListTag(stdout, mFrame);
printf(" carriedOutBEndMargin=%d collapsedBEndMargin=%d => %d\n",
brc.GetCarriedOutBEndMargin().get(), collapsedBEndMargin.get(),
aState.mPrevBEndMargin.get());
#endif
} else {
if ((aLine == mLines.front() && !GetPrevInFlow()) ||
ShouldAvoidBreakInside(aState.mReflowInput)) {
// If it's our very first line *or* we're not at the top of the page
// and we have page-break-inside:avoid, then we need to be pushed to
// our parent's next-in-flow.
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
// When we reflow in the new position, we need to reflow this
// line again.
aLine->MarkDirty();
} else {
// Push the line that didn't fit and any lines that follow it
// to our next-in-flow.
PushLines(aState, aLine.prev());
aState.mReflowStatus.SetIncomplete();
}
}
}
break; // out of the reflow retry loop
}
// Now that we've got its final position all figured out, position any child
// views it may have. Note that the case when frame has a view got handled
// by FinishReflowChild, but that function didn't have the coordinates needed
// to correctly decide whether to reposition child views.
if (originalPosition != frame->GetPosition() && !frame->HasView()) {
nsContainerFrame::PositionChildViews(frame);
}
#ifdef DEBUG
VerifyLines(true);
#endif
}
void
nsBlockFrame::ReflowInlineFrames(BlockReflowInput& aState,
LineIterator aLine,
bool* aKeepReflowGoing)
{
*aKeepReflowGoing = true;
aLine->SetLineIsImpactedByFloat(false);
// Setup initial coordinate system for reflowing the inline frames
// into. Apply a previous block frame's block-end margin first.
if (ShouldApplyBStartMargin(aState, aLine, aLine->mFirstChild)) {
aState.mBCoord += aState.mPrevBEndMargin.get();
}
nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace();
LineReflowStatus lineReflowStatus;
do {
nscoord availableSpaceBSize = 0;
aState.mLineBSize.reset();
do {
bool allowPullUp = true;
nsIFrame* forceBreakInFrame = nullptr;
int32_t forceBreakOffset = -1;
gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak;
do {
nsFloatManager::SavedState floatManagerState;
aState.FloatManager()->PushState(&floatManagerState);
// Once upon a time we allocated the first 30 nsLineLayout objects
// on the stack, and then we switched to the heap. At that time
// these objects were large (1100 bytes on a 32 bit system).
// Then the nsLineLayout object was shrunk to 156 bytes by
// removing some internal buffers. Given that it is so much
// smaller, the complexity of 2 different ways of allocating
// no longer makes sense. Now we always allocate on the stack.
nsLineLayout lineLayout(aState.mPresContext,
aState.FloatManager(),
&aState.mReflowInput, &aLine, nullptr);
lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber);
if (forceBreakInFrame) {
lineLayout.ForceBreakAtPosition(forceBreakInFrame, forceBreakOffset);
}
DoReflowInlineFrames(aState, lineLayout, aLine,
floatAvailableSpace, availableSpaceBSize,
&floatManagerState, aKeepReflowGoing,
&lineReflowStatus, allowPullUp);
lineLayout.EndLineReflow();
if (LineReflowStatus::RedoNoPull == lineReflowStatus ||
LineReflowStatus::RedoMoreFloats == lineReflowStatus ||
LineReflowStatus::RedoNextBand == lineReflowStatus) {
if (lineLayout.NeedsBackup()) {
NS_ASSERTION(!forceBreakInFrame, "Backing up twice; this should never be necessary");
// If there is no saved break position, then this will set
// set forceBreakInFrame to null and we won't back up, which is
// correct.
forceBreakInFrame =
lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset, &forceBreakPriority);
} else {
forceBreakInFrame = nullptr;
}
// restore the float manager state
aState.FloatManager()->PopState(&floatManagerState);
// Clear out float lists
aState.mCurrentLineFloats.DeleteAll();
aState.mBelowCurrentLineFloats.DeleteAll();
}
// Don't allow pullup on a subsequent LineReflowStatus::RedoNoPull pass
allowPullUp = false;
} while (LineReflowStatus::RedoNoPull == lineReflowStatus);
} while (LineReflowStatus::RedoMoreFloats == lineReflowStatus);
} while (LineReflowStatus::RedoNextBand == lineReflowStatus);
}
void
nsBlockFrame::PushTruncatedLine(BlockReflowInput& aState,
LineIterator aLine,
bool* aKeepReflowGoing)
{
PushLines(aState, aLine.prev());
*aKeepReflowGoing = false;
aState.mReflowStatus.SetIncomplete();
}
void
nsBlockFrame::DoReflowInlineFrames(BlockReflowInput& aState,
nsLineLayout& aLineLayout,
LineIterator aLine,
nsFlowAreaRect& aFloatAvailableSpace,
nscoord& aAvailableSpaceBSize,
nsFloatManager::SavedState*
aFloatStateBeforeLine,
bool* aKeepReflowGoing,
LineReflowStatus* aLineReflowStatus,
bool aAllowPullUp)
{
// Forget all of the floats on the line
aLine->FreeFloats(aState.mFloatCacheFreeList);
aState.mFloatOverflowAreas.Clear();
// We need to set this flag on the line if any of our reflow passes
// are impacted by floats.
if (aFloatAvailableSpace.mHasFloats)
aLine->SetLineIsImpactedByFloat(true);
#ifdef REALLY_NOISY_REFLOW
printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n",
this, aFloatAvailableSpace.mHasFloats);
#endif
WritingMode outerWM = aState.mReflowInput.GetWritingMode();
WritingMode lineWM = WritingModeForLine(outerWM, aLine->mFirstChild);
LogicalRect lineRect =
aFloatAvailableSpace.mRect.ConvertTo(lineWM, outerWM,
aState.ContainerSize());
nscoord iStart = lineRect.IStart(lineWM);
nscoord availISize = lineRect.ISize(lineWM);
nscoord availBSize;
if (aState.mFlags.mHasUnconstrainedBSize) {
availBSize = NS_UNCONSTRAINEDSIZE;
}
else {
/* XXX get the height right! */
availBSize = lineRect.BSize(lineWM);
}
// Make sure to enable resize optimization before we call BeginLineReflow
// because it might get disabled there
aLine->EnableResizeReflowOptimization();
aLineLayout.BeginLineReflow(iStart, aState.mBCoord,
availISize, availBSize,
aFloatAvailableSpace.mHasFloats,
false, /*XXX isTopOfPage*/
lineWM, aState.mContainerSize);
aState.mFlags.mIsLineLayoutEmpty = false;
// XXX Unfortunately we need to know this before reflowing the first
// inline frame in the line. FIX ME.
if ((0 == aLineLayout.GetLineNumber()) &&
(NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) &&
(NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) {
aLineLayout.SetFirstLetterStyleOK(true);
}
NS_ASSERTION(!((NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) &&
GetPrevContinuation()),
"first letter child bit should only be on first continuation");
// Reflow the frames that are already on the line first
LineReflowStatus lineReflowStatus = LineReflowStatus::OK;
int32_t i;
nsIFrame* frame = aLine->mFirstChild;
if (aFloatAvailableSpace.mHasFloats) {
// There is a soft break opportunity at the start of the line, because
// we can always move this line down below float(s).
if (aLineLayout.NotifyOptionalBreakPosition(
frame, 0, true, gfxBreakPriority::eNormalBreak)) {
lineReflowStatus = LineReflowStatus::RedoNextBand;
}
}
// need to repeatedly call GetChildCount here, because the child
// count can change during the loop!
for (i = 0; LineReflowStatus::OK == lineReflowStatus && i < aLine->GetChildCount();
i++, frame = frame->GetNextSibling()) {
ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
if (LineReflowStatus::OK != lineReflowStatus) {
// It is possible that one or more of next lines are empty
// (because of DeleteNextInFlowChild). If so, delete them now
// in case we are finished.
++aLine;
while ((aLine != LinesEnd()) && (0 == aLine->GetChildCount())) {
// XXX Is this still necessary now that DeleteNextInFlowChild
// uses DoRemoveFrame?
nsLineBox *toremove = aLine;
aLine = mLines.erase(aLine);
NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line");
FreeLineBox(toremove);
}
--aLine;
NS_ASSERTION(lineReflowStatus != LineReflowStatus::Truncated,
"ReflowInlineFrame should never determine that a line "
"needs to go to the next page/column");
}
}
// Don't pull up new frames into lines with continuation placeholders
if (aAllowPullUp) {
// Pull frames and reflow them until we can't
while (LineReflowStatus::OK == lineReflowStatus) {
frame = PullFrame(aState, aLine);
if (!frame) {
break;
}
while (LineReflowStatus::OK == lineReflowStatus) {
int32_t oldCount = aLine->GetChildCount();
ReflowInlineFrame(aState, aLineLayout, aLine, frame, &lineReflowStatus);
if (aLine->GetChildCount() != oldCount) {
// We just created a continuation for aFrame AND its going
// to end up on this line (e.g. :first-letter
// situation). Therefore we have to loop here before trying
// to pull another frame.
frame = frame->GetNextSibling();
}
else {
break;
}
}
}
}
aState.mFlags.mIsLineLayoutEmpty = aLineLayout.LineIsEmpty();
// We only need to backup if the line isn't going to be reflowed again anyway
bool needsBackup = aLineLayout.NeedsBackup() &&
(lineReflowStatus == LineReflowStatus::Stop ||
lineReflowStatus == LineReflowStatus::OK);
if (needsBackup && aLineLayout.HaveForcedBreakPosition()) {
NS_WARNING("We shouldn't be backing up more than once! "
"Someone must have set a break opportunity beyond the available width, "
"even though there were better break opportunities before it");
needsBackup = false;
}
if (needsBackup) {
// We need to try backing up to before a text run
// XXX It's possible, in fact not unusual, for the break opportunity to already
// be the end of the line. We should detect that and optimize to not
// re-do the line.
if (aLineLayout.HasOptionalBreakPosition()) {
// We can back up!
lineReflowStatus = LineReflowStatus::RedoNoPull;
}
} else {
// In case we reflow this line again, remember that we don't
// need to force any breaking
aLineLayout.ClearOptionalBreakPosition();
}
if (LineReflowStatus::RedoNextBand == lineReflowStatus) {
// This happens only when we have a line that is impacted by
// floats and the first element in the line doesn't fit with
// the floats.
//
// What we do is to advance past the first float we find and
// then reflow the line all over again.
NS_ASSERTION(NS_UNCONSTRAINEDSIZE !=
aFloatAvailableSpace.mRect.BSize(outerWM),
"unconstrained block size on totally empty line");
// See the analogous code for blocks in BlockReflowInput::ClearFloats.
if (aFloatAvailableSpace.mRect.BSize(outerWM) > 0) {
NS_ASSERTION(aFloatAvailableSpace.mHasFloats,
"redo line on totally empty line with non-empty band...");
// We should never hit this case if we've placed floats on the
// line; if we have, then the GetFloatAvailableSpace call is wrong
// and needs to happen after the caller pops the space manager
// state.
aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
aState.mBCoord += aFloatAvailableSpace.mRect.BSize(outerWM);
aFloatAvailableSpace = aState.GetFloatAvailableSpace();
} else {
NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mReflowInput.AvailableBSize(),
"We shouldn't be running out of height here");
if (NS_UNCONSTRAINEDSIZE == aState.mReflowInput.AvailableBSize()) {
// just move it down a bit to try to get out of this mess
aState.mBCoord += 1;
// We should never hit this case if we've placed floats on the
// line; if we have, then the GetFloatAvailableSpace call is wrong
// and needs to happen after the caller pops the space manager
// state.
aState.FloatManager()->AssertStateMatches(aFloatStateBeforeLine);
aFloatAvailableSpace = aState.GetFloatAvailableSpace();
} else {
// There's nowhere to retry placing the line, so we want to push
// it to the next page/column where its contents can fit not
// next to a float.
lineReflowStatus = LineReflowStatus::Truncated;
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
}
}
// XXX: a small optimization can be done here when paginating:
// if the new Y coordinate is past the end of the block then
// push the line and return now instead of later on after we are
// past the float.
}
else if (LineReflowStatus::Truncated != lineReflowStatus &&
LineReflowStatus::RedoNoPull != lineReflowStatus) {
// If we are propagating out a break-before status then there is
// no point in placing the line.
if (!aState.mReflowStatus.IsInlineBreakBefore()) {
if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine,
aFloatAvailableSpace.mRect, aAvailableSpaceBSize,
aKeepReflowGoing)) {
lineReflowStatus = LineReflowStatus::RedoMoreFloats;
// PlaceLine already called GetAvailableSpaceForBSize for us.
}
}
}
#ifdef DEBUG
if (gNoisyReflow) {
printf("Line reflow status = %s\n", LineReflowStatusToString(lineReflowStatus));
}
#endif
if (aLineLayout.GetDirtyNextLine()) {
// aLine may have been pushed to the overflow lines.
FrameLines* overflowLines = GetOverflowLines();
// We can't just compare iterators front() to aLine here, since they may be in
// different lists.
bool pushedToOverflowLines = overflowLines &&
overflowLines->mLines.front() == aLine.get();
if (pushedToOverflowLines) {
// aLine is stale, it's associated with the main line list but it should
// be associated with the overflow line list now
aLine = overflowLines->mLines.begin();
}
nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines);
if (iter.Next() && iter.GetLine()->IsInline()) {
iter.GetLine()->MarkDirty();
if (iter.GetContainer() != this) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
}
}
}
*aLineReflowStatus = lineReflowStatus;
}
/**
* Reflow an inline frame. The reflow status is mapped from the frames
* reflow status to the lines reflow status (not to our reflow status).
* The line reflow status is simple: true means keep placing frames
* on the line; false means don't (the line is done). If the line
* has some sort of breaking affect then aLine's break-type will be set
* to something other than StyleClear::None.
*/
void
nsBlockFrame::ReflowInlineFrame(BlockReflowInput& aState,
nsLineLayout& aLineLayout,
LineIterator aLine,
nsIFrame* aFrame,
LineReflowStatus* aLineReflowStatus)
{
MOZ_ASSERT(aFrame);
*aLineReflowStatus = LineReflowStatus::OK;
#ifdef NOISY_FIRST_LETTER
ListTag(stdout);
printf(": reflowing ");
nsFrame::ListTag(stdout, aFrame);
printf(" reflowingFirstLetter=%s\n",
aLineLayout.GetFirstLetterStyleOK() ? "on" : "off");
#endif
if (aFrame->IsPlaceholderFrame()) {
auto ph = static_cast<nsPlaceholderFrame*>(aFrame);
ph->ForgetLineIsEmptySoFar();
}
// Reflow the inline frame
nsReflowStatus frameReflowStatus;
bool pushedFrame;
aLineLayout.ReflowFrame(aFrame, frameReflowStatus, nullptr, pushedFrame);
if (frameReflowStatus.NextInFlowNeedsReflow()) {
aLineLayout.SetDirtyNextLine();
}
#ifdef REALLY_NOISY_REFLOW
nsFrame::ListTag(stdout, aFrame);
printf(": status=%x\n", frameReflowStatus);
#endif
#if defined(REFLOW_STATUS_COVERAGE)
RecordReflowStatus(false, frameReflowStatus);
#endif
// Send post-reflow notification
aState.mPrevChild = aFrame;
/* XXX
This is where we need to add logic to handle some odd behavior.
For one thing, we should usually place at least one thing next
to a left float, even when that float takes up all the width on a line.
see bug 22496
*/
// Process the child frames reflow status. There are 5 cases:
// complete, not-complete, break-before, break-after-complete,
// break-after-not-complete. There are two situations: we are a
// block or we are an inline. This makes a total of 10 cases
// (fortunately, there is some overlap).
aLine->SetBreakTypeAfter(StyleClear::None);
if (frameReflowStatus.IsInlineBreak() ||
StyleClear::None != aState.mFloatBreakType) {
// Always abort the line reflow (because a line break is the
// minimal amount of break we do).
*aLineReflowStatus = LineReflowStatus::Stop;
// XXX what should aLine's break-type be set to in all these cases?
StyleClear breakType = frameReflowStatus.BreakType();
MOZ_ASSERT(StyleClear::None != breakType ||
StyleClear::None != aState.mFloatBreakType, "bad break type");
if (frameReflowStatus.IsInlineBreakBefore()) {
// Break-before cases.
if (aFrame == aLine->mFirstChild) {
// If we break before the first frame on the line then we must
// be trying to place content where there's no room (e.g. on a
// line with wide floats). Inform the caller to reflow the
// line after skipping past a float.
*aLineReflowStatus = LineReflowStatus::RedoNextBand;
}
else {
// It's not the first child on this line so go ahead and split
// the line. We will see the frame again on the next-line.
SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus);
// If we're splitting the line because the frame didn't fit and it
// was pushed, then mark the line as having word wrapped. We need to
// know that if we're shrink wrapping our width
if (pushedFrame) {
aLine->SetLineWrapped(true);
}
}
}
else {
// If a float split and its prev-in-flow was followed by a <BR>, then combine
// the <BR>'s break type with the inline's break type (the inline will be the very
// next frame after the split float).
if (StyleClear::None != aState.mFloatBreakType) {
breakType = nsLayoutUtils::CombineBreakType(breakType,
aState.mFloatBreakType);
aState.mFloatBreakType = StyleClear::None;
}
// Break-after cases
if (breakType == StyleClear::Line) {
if (!aLineLayout.GetLineEndsInBR()) {
breakType = StyleClear::None;
}
}
aLine->SetBreakTypeAfter(breakType);
if (frameReflowStatus.IsComplete()) {
// Split line, but after the frame just reflowed
SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus);
if (frameReflowStatus.IsInlineBreakAfter() &&
!aLineLayout.GetLineEndsInBR()) {
aLineLayout.SetDirtyNextLine();
}
}
}
}
if (!frameReflowStatus.IsFullyComplete()) {
// Create a continuation for the incomplete frame. Note that the
// frame may already have a continuation.
CreateContinuationFor(aState, aLine, aFrame);
// Remember that the line has wrapped
if (!aLineLayout.GetLineEndsInBR()) {
aLine->SetLineWrapped(true);
}
// If we just ended a first-letter frame or reflowed a placeholder then
// don't split the line and don't stop the line reflow...
// But if we are going to stop anyways we'd better split the line.
if ((!frameReflowStatus.FirstLetterComplete() &&
!aFrame->IsPlaceholderFrame()) ||
*aLineReflowStatus == LineReflowStatus::Stop) {
// Split line after the current frame
*aLineReflowStatus = LineReflowStatus::Stop;
SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus);
}
}
}
bool
nsBlockFrame::CreateContinuationFor(BlockReflowInput& aState,
nsLineBox* aLine,
nsIFrame* aFrame)
{
nsIFrame* newFrame = nullptr;
if (!aFrame->GetNextInFlow()) {
newFrame = aState.mPresContext->PresShell()->FrameConstructor()->
CreateContinuingFrame(aState.mPresContext, aFrame, this);
mFrames.InsertFrame(nullptr, aFrame, newFrame);
if (aLine) {
aLine->NoteFrameAdded(newFrame);
}
}
#ifdef DEBUG
VerifyLines(false);
#endif
return !!newFrame;
}
void
nsBlockFrame::SplitFloat(BlockReflowInput& aState,
nsIFrame* aFloat,
const nsReflowStatus& aFloatStatus)
{
MOZ_ASSERT(!aFloatStatus.IsFullyComplete(),
"why split the frame if it's fully complete?");
MOZ_ASSERT(aState.mBlock == this);
nsIFrame* nextInFlow = aFloat->GetNextInFlow();
if (nextInFlow) {
nsContainerFrame *oldParent = nextInFlow->GetParent();
DebugOnly<nsresult> rv = oldParent->StealFrame(nextInFlow);
NS_ASSERTION(NS_SUCCEEDED(rv), "StealFrame failed");
if (oldParent != this) {
ReparentFrame(nextInFlow, oldParent, this);
}
if (!aFloatStatus.IsOverflowIncomplete()) {
nextInFlow->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
}
} else {
nextInFlow = aState.mPresContext->PresShell()->FrameConstructor()->
CreateContinuingFrame(aState.mPresContext, aFloat, this);
}
if (aFloatStatus.IsOverflowIncomplete()) {
nextInFlow->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
}
StyleFloat floatStyle =
aFloat->StyleDisplay()->PhysicalFloats(aState.mReflowInput.GetWritingMode());
if (floatStyle == StyleFloat::Left) {
aState.FloatManager()->SetSplitLeftFloatAcrossBreak();
} else {
MOZ_ASSERT(floatStyle == StyleFloat::Right, "Unexpected float side!");
aState.FloatManager()->SetSplitRightFloatAcrossBreak();
}
aState.AppendPushedFloatChain(nextInFlow);
aState.mReflowStatus.SetOverflowIncomplete();
}
static nsFloatCache*
GetLastFloat(nsLineBox* aLine)
{
nsFloatCache* fc = aLine->GetFirstFloat();
while (fc && fc->Next()) {
fc = fc->Next();
}
return fc;
}
static bool
CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, nsFloatCache* aFC)
{
if (!aFC)
return true;
NS_ASSERTION(!aFC->mFloat->GetPrevContinuation(),
"float in a line should never be a continuation");
NS_ASSERTION(!(aFC->mFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
"float in a line should never be a pushed float");
nsIFrame* ph = aFC->mFloat->FirstInFlow()->GetPlaceholderFrame();
for (nsIFrame* f = ph; f; f = f->GetParent()) {
if (f->GetParent() == aBlock)
return aLine->Contains(f);
}
NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!");
return true;
}
void
nsBlockFrame::SplitLine(BlockReflowInput& aState,
nsLineLayout& aLineLayout,
LineIterator aLine,
nsIFrame* aFrame,
LineReflowStatus* aLineReflowStatus)
{
MOZ_ASSERT(aLine->IsInline(), "illegal SplitLine on block line");
int32_t pushCount = aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount();
MOZ_ASSERT(pushCount >= 0, "bad push count");
#ifdef DEBUG
if (gNoisyReflow) {
nsFrame::IndentBy(stdout, gNoiseIndent);
printf("split line: from line=%p pushCount=%d aFrame=",
static_cast<void*>(aLine.get()), pushCount);
if (aFrame) {
nsFrame::ListTag(stdout, aFrame);
}
else {
printf("(null)");
}
printf("\n");
if (gReallyNoisyReflow) {
aLine->List(stdout, gNoiseIndent+1);
}
}
#endif
if (0 != pushCount) {
MOZ_ASSERT(aLine->GetChildCount() > pushCount, "bad push");
MOZ_ASSERT(nullptr != aFrame, "whoops");
#ifdef DEBUG
{
nsIFrame *f = aFrame;
int32_t count = pushCount;
while (f && count > 0) {
f = f->GetNextSibling();
--count;
}
NS_ASSERTION(count == 0, "Not enough frames to push");
}
#endif
// Put frames being split out into their own line
nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount);
mLines.after_insert(aLine, newLine);
#ifdef DEBUG
if (gReallyNoisyReflow) {
newLine->List(stdout, gNoiseIndent+1);
}
#endif
// Let line layout know that some frames are no longer part of its
// state.
aLineLayout.SplitLineTo(aLine->GetChildCount());
// If floats have been placed whose placeholders have been pushed to the new
// line, we need to reflow the old line again. We don't want to look at the
// frames in the new line, because as a large paragraph is laid out the
// we'd get O(N^2) performance. So instead we just check that the last
// float and the last below-current-line float are still in aLine.
if (!CheckPlaceholderInLine(this, aLine, GetLastFloat(aLine)) ||
!CheckPlaceholderInLine(this, aLine, aState.mBelowCurrentLineFloats.Tail())) {
*aLineReflowStatus = LineReflowStatus::RedoNoPull;
}
#ifdef DEBUG
VerifyLines(true);
#endif
}
}
bool
nsBlockFrame::IsLastLine(BlockReflowInput& aState,
LineIterator aLine)
{
while (++aLine != LinesEnd()) {
// There is another line
if (0 != aLine->GetChildCount()) {
// If the next line is a block line then this line is the last in a
// group of inline lines.
return aLine->IsBlock();
}
// The next line is empty, try the next one
}
// XXX Not sure about this part
// Try our next-in-flows lines to answer the question
nsBlockFrame* nextInFlow = (nsBlockFrame*) GetNextInFlow();
while (nullptr != nextInFlow) {
for (LineIterator line = nextInFlow->LinesBegin(),
line_end = nextInFlow->LinesEnd();
line != line_end;
++line)
{
if (0 != line->GetChildCount())
return line->IsBlock();
}
nextInFlow = (nsBlockFrame*) nextInFlow->GetNextInFlow();
}
// This is the last line - so don't allow justification
return true;
}
bool
nsBlockFrame::PlaceLine(BlockReflowInput& aState,
nsLineLayout& aLineLayout,
LineIterator aLine,
nsFloatManager::SavedState *aFloatStateBeforeLine,
LogicalRect& aFloatAvailableSpace,
nscoord& aAvailableSpaceBSize,
bool* aKeepReflowGoing)
{
// Trim extra white-space from the line before placing the frames
aLineLayout.TrimTrailingWhiteSpace();
// Vertically align the frames on this line.
//
// According to the CSS2 spec, section 12.6.1, the "marker" box
// participates in the height calculation of the list-item box's
// first line box.
//
// There are exactly two places a bullet can be placed: near the
// first or second line. It's only placed on the second line in a
// rare case: when the first line is empty.
WritingMode wm = aState.mReflowInput.GetWritingMode();
bool addedBullet = false;
if (HasOutsideBullet() &&
((aLine == mLines.front() &&
(!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) ||
(mLines.front() != mLines.back() &&
0 == mLines.front()->BSize() &&
aLine == mLines.begin().next()))) {
ReflowOutput metrics(aState.mReflowInput);
nsIFrame* bullet = GetOutsideBullet();
ReflowBullet(bullet, aState, metrics, aState.mBCoord);
NS_ASSERTION(!BulletIsEmpty() || metrics.BSize(wm) == 0,
"empty bullet took up space");
aLineLayout.AddBulletFrame(bullet, metrics);
addedBullet = true;
}
aLineLayout.VerticalAlignLine();
// We want to consider the floats in the current line when determining
// whether the float available space is shrunk. If mLineBSize doesn't
// exist, we are in the first pass trying to place the line. Calling
// GetFloatAvailableSpace() like we did in BlockReflowInput::AddFloat()
// for UpdateBand().
// floatAvailableSpaceWithOldLineBSize is the float available space with
// the old BSize, but including the floats that were added in this line.
LogicalRect floatAvailableSpaceWithOldLineBSize =
aState.mLineBSize.isNothing()
? aState.GetFloatAvailableSpace(aLine->BStart()).mRect
: aState.GetFloatAvailableSpaceForBSize(aLine->BStart(),
aState.mLineBSize.value(),
nullptr).mRect;
// As we redo for floats, we can't reduce the amount of BSize we're
// checking.
aAvailableSpaceBSize = std::max(aAvailableSpaceBSize, aLine->BSize());
LogicalRect floatAvailableSpaceWithLineBSize =
aState.GetFloatAvailableSpaceForBSize(aLine->BStart(),
aAvailableSpaceBSize,
nullptr).mRect;
// If the available space between the floats is smaller now that we
// know the BSize, return false (and cause another pass with
// LineReflowStatus::RedoMoreFloats). We ensure aAvailableSpaceBSize
// never decreases, which means that we can't reduce the set of floats
// we intersect, which means that the available space cannot grow.
if (AvailableSpaceShrunk(wm, floatAvailableSpaceWithOldLineBSize,
floatAvailableSpaceWithLineBSize, false)) {
// Prepare data for redoing the line.
aState.mLineBSize = Some(aLine->BSize());
// Since we want to redo the line, we update aFloatAvailableSpace by
// using the aFloatStateBeforeLine, which is the float manager's state
// before the line is placed.
LogicalRect oldFloatAvailableSpace(aFloatAvailableSpace);
aFloatAvailableSpace =
aState.GetFloatAvailableSpaceForBSize(aLine->BStart(),
aAvailableSpaceBSize,
aFloatStateBeforeLine).mRect;
NS_ASSERTION(aFloatAvailableSpace.BStart(wm) ==
oldFloatAvailableSpace.BStart(wm), "yikes");
// Restore the BSize to the position of the next band.
aFloatAvailableSpace.BSize(wm) = oldFloatAvailableSpace.BSize(wm);
// Enforce both IStart() and IEnd() never move outwards to prevent
// infinite grow-shrink loops.
const nscoord iStartDiff =
aFloatAvailableSpace.IStart(wm) - oldFloatAvailableSpace.IStart(wm);
const nscoord iEndDiff =
aFloatAvailableSpace.IEnd(wm) - oldFloatAvailableSpace.IEnd(wm);
if (iStartDiff < 0) {
aFloatAvailableSpace.IStart(wm) -= iStartDiff;
aFloatAvailableSpace.ISize(wm) += iStartDiff;
}
if (iEndDiff > 0) {
aFloatAvailableSpace.ISize(wm) -= iEndDiff;
}
return false;
}
#ifdef DEBUG
if (!GetParent()->IsCrazySizeAssertSuppressed()) {
static nscoord lastHeight = 0;
if (CRAZY_SIZE(aLine->BStart())) {
lastHeight = aLine->BStart();
if (abs(aLine->BStart() - lastHeight) > CRAZY_COORD/10) {
nsFrame::ListTag(stdout);
printf(": line=%p y=%d line.bounds.height=%d\n",
static_cast<void*>(aLine.get()),
aLine->BStart(), aLine->BSize());
}
}
else {
lastHeight = 0;
}
}
#endif
// Only block frames horizontally align their children because
// inline frames "shrink-wrap" around their children (therefore
// there is no extra horizontal space).
const nsStyleText* styleText = StyleText();
/**
* text-align-last defaults to the same value as text-align when
* text-align-last is set to auto (except when text-align is set to justify),
* so in that case we don't need to set isLastLine.
*
* In other words, isLastLine really means isLastLineAndWeCare.
*/
bool isLastLine =
!nsSVGUtils::IsInSVGTextSubtree(this) &&
((NS_STYLE_TEXT_ALIGN_AUTO != styleText->mTextAlignLast ||
NS_STYLE_TEXT_ALIGN_JUSTIFY == styleText->mTextAlign) &&
(aLineLayout.GetLineEndsInBR() ||
IsLastLine(aState, aLine)));
aLineLayout.TextAlignLine(aLine, isLastLine);
// From here on, pfd->mBounds rectangles are incorrect because bidi
// might have moved frames around!
nsOverflowAreas overflowAreas;
aLineLayout.RelativePositionFrames(overflowAreas);
aLine->SetOverflowAreas(overflowAreas);
if (addedBullet) {
aLineLayout.RemoveBulletFrame(GetOutsideBullet());
}
// Inline lines do not have margins themselves; however they are
// impacted by prior block margins. If this line ends up having some
// height then we zero out the previous block-end margin value that was
// already applied to the line's starting Y coordinate. Otherwise we
// leave it be so that the previous blocks block-end margin can be
// collapsed with a block that follows.
nscoord newBCoord;
if (!aLine->CachedIsEmpty()) {
// This line has some height. Therefore the application of the
// previous-bottom-margin should stick.
aState.mPrevBEndMargin.Zero();
newBCoord = aLine->BEnd();
}
else {
// Don't let the previous-bottom-margin value affect the newBCoord
// coordinate (it was applied in ReflowInlineFrames speculatively)
// since the line is empty.
// We already called |ShouldApplyBStartMargin|, and if we applied it
// then mShouldApplyBStartMargin is set.
nscoord dy = aState.mFlags.mShouldApplyBStartMargin
? -aState.mPrevBEndMargin.get() : 0;
newBCoord = aState.mBCoord + dy;
}
if (!aState.mReflowStatus.IsFullyComplete() &&
ShouldAvoidBreakInside(aState.mReflowInput)) {
aLine->AppendFloats(aState.mCurrentLineFloats);
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
// Reflow the line again when we reflow at our new position.
aLine->MarkDirty();
*aKeepReflowGoing = false;
return true;
}
// See if the line fit (our first line always does).
if (mLines.front() != aLine &&
newBCoord > aState.mBEndEdge &&
aState.mBEndEdge != NS_UNCONSTRAINEDSIZE) {
NS_ASSERTION(aState.mCurrentLine == aLine, "oops");
if (ShouldAvoidBreakInside(aState.mReflowInput)) {
// All our content doesn't fit, start on the next page.
aState.mReflowStatus.SetInlineLineBreakBeforeAndReset();
*aKeepReflowGoing = false;
} else {
// Push aLine and all of its children and anything else that
// follows to our next-in-flow.
PushTruncatedLine(aState, aLine, aKeepReflowGoing);
}
return true;
}
// Note that any early return before this update of aState.mBCoord
// must either (a) return false or (b) set aKeepReflowGoing to false.
// Otherwise we'll keep reflowing later lines at an incorrect
// position, and we might not come back and clean up the damage later.
aState.mBCoord = newBCoord;
// Add the already placed current-line floats to the line
aLine->AppendFloats(aState.mCurrentLineFloats);
// Any below current line floats to place?
if (aState.mBelowCurrentLineFloats.NotEmpty()) {
// Reflow the below-current-line floats, which places on the line's
// float list.
aState.PlaceBelowCurrentLineFloats(aState.mBelowCurrentLineFloats, aLine);
aLine->AppendFloats(aState.mBelowCurrentLineFloats);
}
// When a line has floats, factor them into the combined-area
// computations.
if (aLine->HasFloats()) {
// Combine the float combined area (stored in aState) and the
// value computed by the line layout code.
nsOverflowAreas lineOverflowAreas;
NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
nsRect &o = lineOverflowAreas.Overflow(otype);
o = aLine->GetOverflowArea(otype);
#ifdef NOISY_COMBINED_AREA
ListTag(stdout);
printf(": overflow %d lineCA=%d,%d,%d,%d floatCA=%d,%d,%d,%d\n",
otype,
o.x, o.y, o.width, o.height,
aState.mFloatOverflowAreas.Overflow(otype).x,
aState.mFloatOverflowAreas.Overflow(otype).y,
aState.mFloatOverflowAreas.Overflow(otype).width,
aState.mFloatOverflowAreas.Overflow(otype).height);
#endif
o.UnionRect(aState.mFloatOverflowAreas.Overflow(otype), o);
#ifdef NOISY_COMBINED_AREA
printf(" ==> final lineCA=%d,%d,%d,%d\n",
o.x, o.y, o.width, o.height);
#endif
}
aLine->SetOverflowAreas(lineOverflowAreas);
}
// Apply break-after clearing if necessary
// This must stay in sync with |ReflowDirtyLines|.
if (aLine->HasFloatBreakAfter()) {
aState.mBCoord = aState.ClearFloats(aState.mBCoord, aLine->GetBreakTypeAfter());
}
return true;
}
void
nsBlockFrame::PushLines(BlockReflowInput& aState,
nsLineList::iterator aLineBefore)
{
// NOTE: aLineBefore is always a normal line, not an overflow line.
// The following expression will assert otherwise.
DebugOnly<bool> check = aLineBefore == mLines.begin();
nsLineList::iterator overBegin(aLineBefore.next());
// PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh.
bool firstLine = overBegin == LinesBegin();
if (overBegin != LinesEnd()) {
// Remove floats in the lines from mFloats
nsFrameList floats;
CollectFloats(overBegin->mFirstChild, floats, true);
if (floats.NotEmpty()) {
#ifdef DEBUG
for (nsIFrame* f : floats) {
MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
"CollectFloats should've removed that bit");
}
#endif
// Push the floats onto the front of the overflow out-of-flows list
nsAutoOOFFrameList oofs(this);
oofs.mList.InsertFrames(nullptr, nullptr, floats);
}
// overflow lines can already exist in some cases, in particular,
// when shrinkwrapping and we discover that the shrinkwap causes
// the height of some child block to grow which creates additional
// overflowing content. In such cases we must prepend the new
// overflow to the existing overflow.
FrameLines* overflowLines = RemoveOverflowLines();
if (!overflowLines) {
// XXXldb use presshell arena!
overflowLines = new FrameLines();
}
if (overflowLines) {
nsIFrame* lineBeforeLastFrame;
if (firstLine) {
lineBeforeLastFrame = nullptr; // removes all frames
} else {
nsIFrame* f = overBegin->mFirstChild;
lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild();
NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(),
"unexpected line frames");
}
nsFrameList pushedFrames = mFrames.RemoveFramesAfter(lineBeforeLastFrame);
overflowLines->mFrames.InsertFrames(nullptr, nullptr, pushedFrames);
overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines,
overBegin, LinesEnd());
NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty");
// this takes ownership but it won't delete it immediately so we
// can keep using it.
SetOverflowLines(overflowLines);
// Mark all the overflow lines dirty so that they get reflowed when
// they are pulled up by our next-in-flow.
// XXXldb Can this get called O(N) times making the whole thing O(N^2)?
for (LineIterator line = overflowLines->mLines.begin(),
line_end = overflowLines->mLines.end();
line != line_end;
++line)
{
line->MarkDirty();
line->MarkPreviousMarginDirty();
line->SetBoundsEmpty();
if (line->HasFloats()) {
line->FreeFloats(aState.mFloatCacheFreeList);
}
}
}
}
#ifdef DEBUG
VerifyOverflowSituation();
#endif
}
// The overflowLines property is stored as a pointer to a line list,
// which must be deleted. However, the following functions all maintain
// the invariant that the property is never set if the list is empty.
bool
nsBlockFrame::DrainOverflowLines()
{
#ifdef DEBUG
VerifyOverflowSituation();
#endif
// Steal the prev-in-flow's overflow lines and prepend them.
bool didFindOverflow = false;
nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
if (prevBlock) {
prevBlock->ClearLineCursor();
FrameLines* overflowLines = prevBlock->RemoveOverflowLines();
if (overflowLines) {
// Make all the frames on the overflow line list mine.
ReparentFrames(overflowLines->mFrames, prevBlock, this);
// Make the overflow out-of-flow frames mine too.
nsAutoOOFFrameList oofs(prevBlock);
if (oofs.mList.NotEmpty()) {
// In case we own a next-in-flow of any of the drained frames, then
// those are now not PUSHED_FLOATs anymore.
for (nsFrameList::Enumerator e(oofs.mList); !e.AtEnd(); e.Next()) {
nsIFrame* nif = e.get()->GetNextInFlow();
for (; nif && nif->GetParent() == this; nif = nif->GetNextInFlow()) {
MOZ_ASSERT(mFloats.ContainsFrame(nif));
nif->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
}
}
ReparentFrames(oofs.mList, prevBlock, this);
mFloats.InsertFrames(nullptr, nullptr, oofs.mList);
}
if (!mLines.empty()) {
// Remember to recompute the margins on the first line. This will
// also recompute the correct deltaBCoord if necessary.
mLines.front()->MarkPreviousMarginDirty();
}
// The overflow lines have already been marked dirty and their previous
// margins marked dirty also.
// Prepend the overflow frames/lines to our principal list.
mFrames.InsertFrames(nullptr, nullptr, overflowLines->mFrames);
mLines.splice(mLines.begin(), overflowLines->mLines);
NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list");
delete overflowLines;
didFindOverflow = true;
}
}
// Now append our own overflow lines.
return DrainSelfOverflowList() || didFindOverflow;
}
bool
nsBlockFrame::DrainSelfOverflowList()
{
UniquePtr<FrameLines> ourOverflowLines(RemoveOverflowLines());
if (!ourOverflowLines) {
return false;
}
// No need to reparent frames in our own overflow lines/oofs, because they're
// already ours. But we should put overflow floats back in mFloats.
// (explicit scope to remove the OOF list before VerifyOverflowSituation)
{
nsAutoOOFFrameList oofs(this);
if (oofs.mList.NotEmpty()) {
#ifdef DEBUG
for (nsIFrame* f : oofs.mList) {
MOZ_ASSERT(!(f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT),
"CollectFloats should've removed that bit");
}
#endif
// The overflow floats go after our regular floats.
mFloats.AppendFrames(nullptr, oofs.mList);
}
}
if (!ourOverflowLines->mLines.empty()) {
mFrames.AppendFrames(nullptr, ourOverflowLines->mFrames);
mLines.splice(mLines.end(), ourOverflowLines->mLines);
}
#ifdef DEBUG
VerifyOverflowSituation();
#endif
return true;
}
/**
* Pushed floats are floats whose placeholders are in a previous
* continuation. They might themselves be next-continuations of a float
* that partially fit in an earlier continuation, or they might be the
* first continuation of a float that couldn't be placed at all.
*
* Pushed floats live permanently at the beginning of a block's float
* list, where they must live *before* any floats whose placeholders are
* in that block.
*
* Temporarily, during reflow, they also live on the pushed floats list,
* which only holds them between (a) when one continuation pushes them to
* its pushed floats list because they don't fit and (b) when the next
* continuation pulls them onto the beginning of its float list.
*
* DrainPushedFloats sets up pushed floats the way we need them at the
* start of reflow; they are then reflowed by ReflowPushedFloats (which
* might push some of them on). Floats with placeholders in this block
* are reflowed by (BlockReflowInput/nsLineLayout)::AddFloat, which
* also maintains these invariants.
*
* DrainSelfPushedFloats moves any pushed floats from this block's own
* PushedFloats list back into mFloats. DrainPushedFloats additionally
* moves frames from its prev-in-flow's PushedFloats list into mFloats.
*/
void
nsBlockFrame::DrainSelfPushedFloats()
{
// If we're getting reflowed multiple times without our
// next-continuation being reflowed, we might need to pull back floats
// that we just put in the list to be pushed to our next-in-flow.
// We don't want to pull back any next-in-flows of floats on our own
// float list, and we only need to pull back first-in-flows whose
// placeholders were in earlier blocks (since first-in-flows whose
// placeholders are in this block will get pulled appropriately by
// AddFloat, and will then be more likely to be in the correct order).
// FIXME: What if there's a continuation in our pushed floats list
// whose prev-in-flow is in a previous continuation of this block
// rather than this block? Might we need to pull it back so we don't
// report ourselves complete?
// FIXME: Maybe we should just pull all of them back?
nsPresContext* presContext = PresContext();
nsFrameList* ourPushedFloats = GetPushedFloats();
if (ourPushedFloats) {
// When we pull back floats, we want to put them with the pushed
// floats, which must live at the start of our float list, but we
// want them at the end of those pushed floats.
// FIXME: This isn't quite right! What if they're all pushed floats?
nsIFrame *insertionPrevSibling = nullptr; /* beginning of list */
for (nsIFrame* f = mFloats.FirstChild();
f && (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT);
f = f->GetNextSibling()) {
insertionPrevSibling = f;
}
for (nsIFrame *f = ourPushedFloats->LastChild(), *next; f; f = next) {
next = f->GetPrevSibling();
if (f->GetPrevContinuation()) {
// FIXME
} else {
nsPlaceholderFrame* placeholder = f->GetPlaceholderFrame();
nsIFrame* floatOriginalParent = presContext->PresShell()->
FrameConstructor()->GetFloatContainingBlock(placeholder);
if (floatOriginalParent != this) {
// This is a first continuation that was pushed from one of our
// previous continuations. Take it out of the pushed floats
// list and put it in our floats list, before any of our
// floats, but after other pushed floats.
ourPushedFloats->RemoveFrame(f);
mFloats.InsertFrame(nullptr, insertionPrevSibling, f);
}
}
}
if (ourPushedFloats->IsEmpty()) {
RemovePushedFloats()->Delete(presContext->PresShell());
}
}
}
void
nsBlockFrame::DrainPushedFloats()
{
DrainSelfPushedFloats();
// After our prev-in-flow has completed reflow, it may have a pushed
// floats list, containing floats that we need to own. Take these.
nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
if (prevBlock) {
AutoFrameListPtr list(PresContext(), prevBlock->RemovePushedFloats());
if (list && list->NotEmpty()) {
mFloats.InsertFrames(this, nullptr, *list);
}
}
}
nsBlockFrame::FrameLines*
nsBlockFrame::GetOverflowLines() const
{
if (!HasOverflowLines()) {
return nullptr;
}
FrameLines* prop = GetProperty(OverflowLinesProperty());
NS_ASSERTION(prop && !prop->mLines.empty() &&
prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() :
prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
"value should always be stored and non-empty when state set");
return prop;
}
nsBlockFrame::FrameLines*
nsBlockFrame::RemoveOverflowLines()
{
if (!HasOverflowLines()) {
return nullptr;
}
FrameLines* prop = RemoveProperty(OverflowLinesProperty());
NS_ASSERTION(prop && !prop->mLines.empty() &&
prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() :
prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(),
"value should always be stored and non-empty when state set");
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
return prop;
}
void
nsBlockFrame::DestroyOverflowLines()
{
NS_ASSERTION(HasOverflowLines(), "huh?");
FrameLines* prop = RemoveProperty(OverflowLinesProperty());
NS_ASSERTION(prop && prop->mLines.empty(),
"value should always be stored but empty when destroying");
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
delete prop;
}
// This takes ownership of aOverflowLines.
// XXX We should allocate overflowLines from presShell arena!
void
nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines)
{
NS_ASSERTION(aOverflowLines, "null lines");
NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines");
NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild ==
aOverflowLines->mFrames.FirstChild(),
"invalid overflow lines / frames");
NS_ASSERTION(!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES),
"Overwriting existing overflow lines");
// Verify that we won't overwrite an existing overflow list
NS_ASSERTION(!GetProperty(OverflowLinesProperty()), "existing overflow list");
SetProperty(OverflowLinesProperty(), aOverflowLines);
AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES);
}
nsFrameList*
nsBlockFrame::GetOverflowOutOfFlows() const
{
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
return nullptr;
}
nsFrameList* result =
GetPropTableFrames(OverflowOutOfFlowsProperty());
NS_ASSERTION(result, "value should always be non-empty when state set");
return result;
}
// This takes ownership of the frames
void
nsBlockFrame::SetOverflowOutOfFlows(const nsFrameList& aList,
nsFrameList* aPropValue)
{
NS_PRECONDITION(!!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) ==
!!aPropValue, "state does not match value");
if (aList.IsEmpty()) {
if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) {
return;
}
nsFrameList* list = RemovePropTableFrames(OverflowOutOfFlowsProperty());
NS_ASSERTION(aPropValue == list, "prop value mismatch");
list->Clear();
list->Delete(PresShell());
RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
else if (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) {
NS_ASSERTION(aPropValue == GetPropTableFrames(OverflowOutOfFlowsProperty()),
"prop value mismatch");
*aPropValue = aList;
}
else {
SetPropTableFrames(new (PresShell()) nsFrameList(aList),
OverflowOutOfFlowsProperty());
AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS);
}
}
nsBulletFrame*
nsBlockFrame::GetInsideBullet() const
{
if (!HasInsideBullet()) {
return nullptr;
}
NS_ASSERTION(!HasOutsideBullet(), "invalid bullet state");
nsBulletFrame* frame = GetProperty(InsideBulletProperty());
NS_ASSERTION(frame && frame->IsBulletFrame(), "bogus inside bullet frame");
return frame;
}
nsBulletFrame*
nsBlockFrame::GetOutsideBullet() const
{
nsFrameList* list = GetOutsideBulletList();
return list ? static_cast<nsBulletFrame*>(list->FirstChild())
: nullptr;
}
nsFrameList*
nsBlockFrame::GetOutsideBulletList() const
{
if (!HasOutsideBullet()) {
return nullptr;
}
NS_ASSERTION(!HasInsideBullet(), "invalid bullet state");
nsFrameList* list = GetProperty(OutsideBulletProperty());
NS_ASSERTION(list && list->GetLength() == 1 &&
list->FirstChild()->IsBulletFrame(),
"bogus outside bullet list");
return list;
}
nsFrameList*
nsBlockFrame::GetPushedFloats() const
{
if (!HasPushedFloats()) {
return nullptr;
}
nsFrameList* result = GetProperty(PushedFloatProperty());
NS_ASSERTION(result, "value should always be non-empty when state set");
return result;
}
nsFrameList*
nsBlockFrame::EnsurePushedFloats()
{
nsFrameList *result = GetPushedFloats();
if (result)
return result;
result = new (PresShell()) nsFrameList;
SetProperty(PushedFloatProperty(), result);
AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
return result;
}
nsFrameList*
nsBlockFrame::RemovePushedFloats()
{
if (!HasPushedFloats()) {
return nullptr;
}
nsFrameList *result = RemoveProperty(PushedFloatProperty());
RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS);
NS_ASSERTION(result, "value should always be non-empty when state set");
return result;
}
//////////////////////////////////////////////////////////////////////
// Frame list manipulation routines
void
nsBlockFrame::AppendFrames(ChildListID aListID,
nsFrameList& aFrameList)
{
if (aFrameList.IsEmpty()) {
return;
}
if (aListID != kPrincipalList) {
if (kFloatList == aListID) {
DrainSelfPushedFloats(); // ensure the last frame is in mFloats
mFloats.AppendFrames(nullptr, aFrameList);
return;
}
MOZ_ASSERT(kNoReflowPrincipalList == aListID, "unexpected child list");
}
// Find the proper last-child for where the append should go
nsIFrame* lastKid = mFrames.LastChild();
NS_ASSERTION((mLines.empty() ? nullptr : mLines.back()->LastChild()) ==
lastKid, "out-of-sync mLines / mFrames");
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": append ");
nsFrame::ListTag(stdout, aFrameList);
if (lastKid) {
printf(" after ");
nsFrame::ListTag(stdout, lastKid);
}
printf("\n");
#endif
if (nsSVGUtils::IsInSVGTextSubtree(this)) {
MOZ_ASSERT(GetParent()->IsSVGTextFrame(),
"unexpected block frame in SVG text");
// Workaround for bug 1399425 in case this bit has been removed from the
// SVGTextFrame just before the parser adds more descendant nodes.
GetParent()->AddStateBits(NS_STATE_SVG_TEXT_CORRESPONDENCE_DIRTY);
}
AddFrames(aFrameList, lastKid);
if (aListID != kNoReflowPrincipalList) {
PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
}
}
void
nsBlockFrame::InsertFrames(ChildListID aListID,
nsIFrame* aPrevFrame,
nsFrameList& aFrameList)
{
NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this,
"inserting after sibling frame with different parent");
if (aListID != kPrincipalList) {
if (kFloatList == aListID) {
DrainSelfPushedFloats(); // ensure aPrevFrame is in mFloats
mFloats.InsertFrames(this, aPrevFrame, aFrameList);
return;
}
MOZ_ASSERT(kNoReflowPrincipalList == aListID, "unexpected child list");
}
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": insert ");
nsFrame::ListTag(stdout, aFrameList);
if (aPrevFrame) {
printf(" after ");
nsFrame::ListTag(stdout, aPrevFrame);
}
printf("\n");
#endif
AddFrames(aFrameList, aPrevFrame);
if (aListID != kNoReflowPrincipalList) {
PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
}
}
void
nsBlockFrame::RemoveFrame(ChildListID aListID,
nsIFrame* aOldFrame)
{
#ifdef NOISY_REFLOW_REASON
ListTag(stdout);
printf(": remove ");
nsFrame::ListTag(stdout, aOldFrame);
printf("\n");
#endif
if (aListID == kPrincipalList) {
bool hasFloats = BlockHasAnyFloats(aOldFrame);
DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS);
if (hasFloats) {
MarkSameFloatManagerLinesDirty(this);
}
}
else if (kFloatList == aListID) {
// Make sure to mark affected lines dirty for the float frame
// we are removing; this way is a bit messy, but so is the rest of the code.
// See bug 390762.
NS_ASSERTION(!aOldFrame->GetPrevContinuation(),
"RemoveFrame should not be called on pushed floats.");
for (nsIFrame* f = aOldFrame;
f && !(f->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER);
f = f->GetNextContinuation()) {
MarkSameFloatManagerLinesDirty(static_cast<nsBlockFrame*>(f->GetParent()));
}
DoRemoveOutOfFlowFrame(aOldFrame);
}
else if (kNoReflowPrincipalList == aListID) {
// Skip the call to |FrameNeedsReflow| below by returning now.
DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS);
return;
}
else {
MOZ_CRASH("unexpected child list");
}
PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange,
NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient?
}
static bool
ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame)
{
LayoutFrameType type = aLastFrame->Type();
if (type == LayoutFrameType::Br) {
return true;
}
// XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910.
if (type == LayoutFrameType::Text &&
!(aLastFrame->GetStateBits() & TEXT_OFFSETS_NEED_FIXING)) {
return aLastFrame->HasSignificantTerminalNewline();
}
return false;
}
void
nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling)
{
// Clear our line cursor, since our lines may change.
ClearLineCursor();
if (aFrameList.IsEmpty()) {
return;
}
// If we're inserting at the beginning of our list and we have an
// inside bullet, insert after that bullet.
if (!aPrevSibling && HasInsideBullet()) {
aPrevSibling = GetInsideBullet();
}
// Attempt to find the line that contains the previous sibling
nsLineList* lineList = &mLines;
nsFrameList* frames = &mFrames;
nsLineList::iterator prevSibLine = lineList->end();
int32_t prevSiblingIndex = -1;
if (aPrevSibling) {
// XXX_perf This is technically O(N^2) in some cases, but by using
// RFind instead of Find, we make it O(N) in the most common case,
// which is appending content.
// Find the line that contains the previous sibling
if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(),
prevSibLine, mFrames.LastChild(),
&prevSiblingIndex)) {
// Not in mLines - try overflow lines.
FrameLines* overflowLines = GetOverflowLines();
bool found = false;
if (overflowLines) {
prevSibLine = overflowLines->mLines.end();
prevSiblingIndex = -1;
found = nsLineBox::RFindLineContaining(aPrevSibling,
overflowLines->mLines.begin(),
prevSibLine,
overflowLines->mFrames.LastChild(),
&prevSiblingIndex);
}
if (MOZ_LIKELY(found)) {
lineList = &overflowLines->mLines;
frames = &overflowLines->mFrames;
} else {
// Note: defensive code! RFindLineContaining must not return
// false in this case, so if it does...
MOZ_ASSERT_UNREACHABLE("prev sibling not in line list");
aPrevSibling = nullptr;
prevSibLine = lineList->end();
}
}
}
// Find the frame following aPrevSibling so that we can join up the
// two lists of frames.
if (aPrevSibling) {
// Split line containing aPrevSibling in two if the insertion
// point is somewhere in the middle of the line.
int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1;
if (rem) {
// Split the line in two where the frame(s) are being inserted.
nsLineBox* line = NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem);
lineList->after_insert(prevSibLine, line);
// Mark prevSibLine dirty and as needing textrun invalidation, since
// we may be breaking up text in the line. Its previous line may also
// need to be invalidated because it may be able to pull some text up.
MarkLineDirty(prevSibLine, lineList);
// The new line will also need its textruns recomputed because of the
// frame changes.
line->MarkDirty();
line->SetInvalidateTextRuns(true);
}
}
else if (! lineList->empty()) {
lineList->front()->MarkDirty();
lineList->front()->SetInvalidateTextRuns(true);
}
const nsFrameList::Slice& newFrames =
frames->InsertFrames(nullptr, aPrevSibling, aFrameList);
// Walk through the new frames being added and update the line data
// structures to fit.
for (nsFrameList::Enumerator e(newFrames); !e.AtEnd(); e.Next()) {
nsIFrame* newFrame = e.get();
NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame,
"Unexpected aPrevSibling");
NS_ASSERTION(!newFrame->IsPlaceholderFrame() ||
(!newFrame->IsAbsolutelyPositioned() &&
!newFrame->IsFloating()),
"Placeholders should not float or be positioned");
bool isBlock = newFrame->IsBlockOutside();
// If the frame is a block frame, or if there is no previous line or if the
// previous line is a block line we need to make a new line. We also make
// a new line, as an optimization, in the two cases we know we'll need it:
// if the previous line ended with a <br>, or if it has significant whitespace
// and ended in a newline.
if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() ||
(aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) {
// Create a new line for the frame and add its line to the line
// list.
nsLineBox* line = NewLineBox(newFrame, isBlock);
if (prevSibLine != lineList->end()) {
// Append new line after prevSibLine
lineList->after_insert(prevSibLine, line);
++prevSibLine;
}
else {
// New line is going before the other lines
lineList->push_front(line);
prevSibLine = lineList->begin();
}
}
else {
prevSibLine->NoteFrameAdded(newFrame);
// We're adding inline content to prevSibLine, so we need to mark it
// dirty, ensure its textruns are recomputed, and possibly do the same
// to its previous line since that line may be able to pull content up.
MarkLineDirty(prevSibLine, lineList);
}
aPrevSibling = newFrame;
}
#ifdef DEBUG
MOZ_ASSERT(aFrameList.IsEmpty());
VerifyLines(true);
#endif
}
void
nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat)
{
// Find which line contains the float, so we can update
// the float cache.
LineIterator line = LinesBegin(), line_end = LinesEnd();
for ( ; line != line_end; ++line) {
if (line->IsInline() && line->RemoveFloat(aFloat)) {
break;
}
}
}
void
nsBlockFrame::RemoveFloat(nsIFrame* aFloat)
{
#ifdef DEBUG
// Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows
// frame list properties.
if (!mFloats.ContainsFrame(aFloat)) {
MOZ_ASSERT((GetOverflowOutOfFlows() &&
GetOverflowOutOfFlows()->ContainsFrame(aFloat)) ||
(GetPushedFloats() &&
GetPushedFloats()->ContainsFrame(aFloat)),
"aFloat is not our child or on an unexpected frame list");
}
#endif
if (mFloats.StartRemoveFrame(aFloat)) {
return;
}
nsFrameList* list = GetPushedFloats();
if (list && list->ContinueRemoveFrame(aFloat)) {
#if 0
// XXXmats not yet - need to investigate BlockReflowInput::mPushedFloats
// first so we don't leave it pointing to a deleted list.
if (list->IsEmpty()) {
delete RemovePushedFloats();
}
#endif
return;
}
{
nsAutoOOFFrameList oofs(this);
if (oofs.mList.ContinueRemoveFrame(aFloat)) {
return;
}
}
}
void
nsBlockFrame::DoRemoveOutOfFlowFrame(nsIFrame* aFrame)
{
// The containing block is always the parent of aFrame.
nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent();
// Remove aFrame from the appropriate list.
if (aFrame->IsAbsolutelyPositioned()) {
// This also deletes the next-in-flows
block->GetAbsoluteContainingBlock()->RemoveFrame(block,
kAbsoluteList,
aFrame);
}
else {
// First remove aFrame's next-in-flows.
nsIFrame* nif = aFrame->GetNextInFlow();
if (nif) {
nif->GetParent()->DeleteNextInFlowChild(nif, false);
}
// Now remove aFrame from its child list and Destroy it.
block->RemoveFloatFromFloatCache(aFrame);
block->RemoveFloat(aFrame);
aFrame->Destroy();
}
}
/**
* This helps us iterate over the list of all normal + overflow lines
*/
void
nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator,
nsLineList::iterator* aStartIterator,
nsLineList::iterator* aEndIterator,
bool* aInOverflowLines,
FrameLines** aOverflowLines)
{
if (*aIterator == *aEndIterator) {
if (!*aInOverflowLines) {
// Try the overflow lines
*aInOverflowLines = true;
FrameLines* lines = GetOverflowLines();
if (lines) {
*aStartIterator = lines->mLines.begin();
*aIterator = *aStartIterator;
*aEndIterator = lines->mLines.end();
*aOverflowLines = lines;
}
}
}
}
nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
LineIterator aLine)
: mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines)
{
// This will assert if aLine isn't in mLines of aFrame:
DebugOnly<bool> check = aLine == mFrame->LinesBegin();
}
nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
LineIterator aLine, bool aInOverflow)
: mFrame(aFrame), mLine(aLine),
mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines
: &aFrame->mLines)
{
}
nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
bool* aFoundValidLine)
: mFrame(aFrame), mLineList(&aFrame->mLines)
{
mLine = aFrame->LinesBegin();
*aFoundValidLine = FindValidLine();
}
void
nsBlockFrame::UpdateFirstLetterStyle(ServoRestyleState& aRestyleState)
{
nsIFrame* letterFrame = GetFirstLetter();
if (!letterFrame) {
return;
}
// Figure out what the right style parent is. This needs to match
// nsCSSFrameConstructor::CreateLetterFrame.
nsIFrame* inFlowFrame = letterFrame;
if (inFlowFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
inFlowFrame = inFlowFrame->GetPlaceholderFrame();
}
nsIFrame* styleParent =
CorrectStyleParentFrame(inFlowFrame->GetParent(),
nsCSSPseudoElements::firstLetter);
ServoStyleContext* parentStyle = styleParent->StyleContext()->AsServo();
RefPtr<nsStyleContext> firstLetterStyle =
aRestyleState.StyleSet()
.ResolvePseudoElementStyle(mContent->AsElement(),
CSSPseudoElementType::firstLetter,
parentStyle,
nullptr);
// Note that we don't need to worry about changehints for the continuation
// styles: those will be handled by the styleParent already.
RefPtr<nsStyleContext> continuationStyle =
aRestyleState.StyleSet().ResolveStyleForFirstLetterContinuation(parentStyle);
UpdateStyleOfOwnedChildFrame(letterFrame, firstLetterStyle, aRestyleState,
Some(continuationStyle.get()));
// We also want to update the style on the textframe inside the first-letter.
// We don't need to compute a changehint for this, though, since any changes
// to it are handled by the first-letter anyway.
nsIFrame* textFrame = letterFrame->PrincipalChildList().FirstChild();
RefPtr<nsStyleContext> firstTextStyle =
aRestyleState.StyleSet().ResolveStyleForText(textFrame->GetContent(),
firstLetterStyle->AsServo());
textFrame->SetStyleContext(firstTextStyle);
// We don't need to update style for textFrame's continuations: it's already
// set up to inherit from parentStyle, which is what we want.
}
static nsIFrame*
FindChildContaining(nsBlockFrame* aFrame, nsIFrame* aFindFrame)
{
NS_ASSERTION(aFrame, "must have frame");
nsIFrame* child;
while (true) {
nsIFrame* block = aFrame;
do {
child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame);
if (child)
break;
block = block->GetNextContinuation();
} while (block);
if (!child)
return nullptr;
if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW))
break;
aFindFrame = child->GetPlaceholderFrame();
}
return child;
}
nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame,
nsIFrame* aFindFrame, bool* aFoundValidLine)
: mFrame(aFrame), mLineList(&aFrame->mLines)
{
*aFoundValidLine = false;
nsIFrame* child = FindChildContaining(aFrame, aFindFrame);
if (!child)
return;
LineIterator line_end = aFrame->LinesEnd();
mLine = aFrame->LinesBegin();
if (mLine != line_end && mLine.next() == line_end &&
!aFrame->HasOverflowLines()) {
// The block has a single line - that must be it!
*aFoundValidLine = true;
return;
}
// Try to use the cursor if it exists, otherwise fall back to the first line
if (nsLineBox* const cursor = aFrame->GetLineCursor()) {
mLine = line_end;
// Perform a simultaneous forward and reverse search starting from the
// line cursor.
nsBlockFrame::LineIterator line = aFrame->LinesBeginFrom(cursor);
nsBlockFrame::ReverseLineIterator rline = aFrame->LinesRBeginFrom(cursor);
nsBlockFrame::ReverseLineIterator rline_end = aFrame->LinesREnd();
// rline is positioned on the line containing 'cursor', so it's not
// rline_end. So we can safely increment it (i.e. move it to one line
// earlier) to start searching there.
++rline;
while (line != line_end || rline != rline_end) {
if (line != line_end) {
if (line->Contains(child)) {
mLine = line;
break;
}
++line;
}
if (rline != rline_end) {
if (rline->Contains(child)) {
mLine = rline;
break;
}
++rline;
}
}
if (mLine != line_end) {
*aFoundValidLine = true;
if (mLine != cursor) {
aFrame->SetProperty(nsBlockFrame::LineCursorProperty(), mLine);
}
return;
}
} else {
for (mLine = aFrame->LinesBegin(); mLine != line_end; ++mLine) {
if (mLine->Contains(child)) {
*aFoundValidLine = true;
return;
}
}
}
// Didn't find the line
MOZ_ASSERT(mLine == line_end, "mLine should be line_end at this point");
// If we reach here, it means that we have not been able to find the
// desired frame in our in-flow lines. So we should start looking at
// our overflow lines. In order to do that, we set mLine to the end
// iterator so that FindValidLine starts to look at overflow lines,
// if any.
if (!FindValidLine())
return;
do {
if (mLine->Contains(child)) {
*aFoundValidLine = true;
return;
}
} while (Next());
}
nsBlockFrame::LineIterator
nsBlockInFlowLineIterator::End()
{
return mLineList->end();
}
bool
nsBlockInFlowLineIterator::IsLastLineInList()
{
LineIterator end = End();
return mLine != end && mLine.next() == end;
}
bool
nsBlockInFlowLineIterator::Next()
{
++mLine;
return FindValidLine();
}
bool
nsBlockInFlowLineIterator::Prev()
{
LineIterator begin = mLineList->begin();
if (mLine != begin) {
--mLine;
return true;
}
bool currentlyInOverflowLines = GetInOverflow();
while (true) {
if (currentlyInOverflowLines) {
mLineList = &mFrame->mLines;
mLine = mLineList->end();
if (mLine != mLineList->begin()) {
--mLine;
return true;
}
} else {
mFrame = static_cast<nsBlockFrame*>(mFrame->GetPrevInFlow());
if (!mFrame)
return false;
nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
if (overflowLines) {
mLineList = &overflowLines->mLines;
mLine = mLineList->end();
NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?");
--mLine;
return true;
}
}
currentlyInOverflowLines = !currentlyInOverflowLines;
}
}
bool
nsBlockInFlowLineIterator::FindValidLine()
{
LineIterator end = mLineList->end();
if (mLine != end)
return true;
bool currentlyInOverflowLines = GetInOverflow();
while (true) {
if (currentlyInOverflowLines) {
mFrame = static_cast<nsBlockFrame*>(mFrame->GetNextInFlow());
if (!mFrame)
return false;
mLineList = &mFrame->mLines;
mLine = mLineList->begin();
if (mLine != mLineList->end())
return true;
} else {
nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines();
if (overflowLines) {
mLineList = &overflowLines->mLines;
mLine = mLineList->begin();
NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?");
return true;
}
}
currentlyInOverflowLines = !currentlyInOverflowLines;
}
}
// This function removes aDeletedFrame and all its continuations. It
// is optimized for deleting a whole series of frames. The easy
// implementation would invoke itself recursively on
// aDeletedFrame->GetNextContinuation, then locate the line containing
// aDeletedFrame and remove aDeletedFrame from that line. But here we
// start by locating aDeletedFrame and then scanning from that point
// on looking for continuations.
void
nsBlockFrame::DoRemoveFrameInternal(nsIFrame* aDeletedFrame, uint32_t aFlags,
PostDestroyData& aPostDestroyData)
{
// Clear our line cursor, since our lines may change.
ClearLineCursor();
if (aDeletedFrame->GetStateBits() &
(NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) {
if (!aDeletedFrame->GetPrevInFlow()) {
NS_ASSERTION(aDeletedFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW,
"Expected out-of-flow frame");
DoRemoveOutOfFlowFrame(aDeletedFrame);
}
else {
nsContainerFrame::DeleteNextInFlowChild(aDeletedFrame,
(aFlags & FRAMES_ARE_EMPTY) != 0);
}
return;
}
// Find the line that contains deletedFrame
nsLineList::iterator line_start = mLines.begin(),
line_end = mLines.end();
nsLineList::iterator line = line_start;
FrameLines* overflowLines = nullptr;
bool searchingOverflowList = false;
// Make sure we look in the overflow lines even if the normal line
// list is empty
TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
&overflowLines);
while (line != line_end) {
if (line->Contains(aDeletedFrame)) {
break;
}
++line;
TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
&overflowLines);
}
if (line == line_end) {
NS_ERROR("can't find deleted frame in lines");
return;
}
if (!(aFlags & FRAMES_ARE_EMPTY)) {
if (line != line_start) {
line.prev()->MarkDirty();
line.prev()->SetInvalidateTextRuns(true);
}
else if (searchingOverflowList && !mLines.empty()) {
mLines.back()->MarkDirty();
mLines.back()->SetInvalidateTextRuns(true);
}
}
while (line != line_end && aDeletedFrame) {
NS_ASSERTION(this == aDeletedFrame->GetParent(), "messed up delete code");
NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line");
if (!(aFlags & FRAMES_ARE_EMPTY)) {
line->MarkDirty();
line->SetInvalidateTextRuns(true);
}
// If the frame being deleted is the last one on the line then
// optimize away the line->Contains(next-in-flow) call below.
bool isLastFrameOnLine = 1 == line->GetChildCount();
if (!isLastFrameOnLine) {
LineIterator next = line.next();
nsIFrame* lastFrame = next != line_end ?
next->mFirstChild->GetPrevSibling() :
(searchingOverflowList ? overflowLines->mFrames.LastChild() :
mFrames.LastChild());
NS_ASSERTION(next == line_end || lastFrame == line->LastChild(),
"unexpected line frames");
isLastFrameOnLine = lastFrame == aDeletedFrame;
}
// Remove aDeletedFrame from the line
if (line->mFirstChild == aDeletedFrame) {
// We should be setting this to null if aDeletedFrame
// is the only frame on the line. HOWEVER in that case
// we will be removing the line anyway, see below.
line->mFirstChild = aDeletedFrame->GetNextSibling();
}
// Hmm, this won't do anything if we're removing a frame in the first
// overflow line... Hopefully doesn't matter
--line;
if (line != line_end && !line->IsBlock()) {
// Since we just removed a frame that follows some inline
// frames, we need to reflow the previous line.
line->MarkDirty();
}
++line;
// Take aDeletedFrame out of the sibling list. Note that
// prevSibling will only be nullptr when we are deleting the very
// first frame in the main or overflow list.
if (searchingOverflowList) {
overflowLines->mFrames.RemoveFrame(aDeletedFrame);
} else {
mFrames.RemoveFrame(aDeletedFrame);
}
// Update the child count of the line to be accurate
line->NoteFrameRemoved(aDeletedFrame);
// Destroy frame; capture its next continuation first in case we need
// to destroy that too.
nsIFrame* deletedNextContinuation = (aFlags & REMOVE_FIXED_CONTINUATIONS) ?
aDeletedFrame->GetNextContinuation() : aDeletedFrame->GetNextInFlow();
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p frame=",
searchingOverflowList?"overflow":"normal", line.get());
nsFrame::ListTag(stdout, aDeletedFrame);
printf(" prevSibling=%p deletedNextContinuation=%p\n",
aDeletedFrame->GetPrevSibling(), deletedNextContinuation);
#endif
// If next-in-flow is an overflow container, must remove it first.
if (deletedNextContinuation &&
deletedNextContinuation->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) {
deletedNextContinuation->GetParent()->
DeleteNextInFlowChild(deletedNextContinuation, false);
deletedNextContinuation = nullptr;
}
aDeletedFrame->DestroyFrom(aDeletedFrame, aPostDestroyData);
aDeletedFrame = deletedNextContinuation;
bool haveAdvancedToNextLine = false;
// If line is empty, remove it now.
if (0 == line->GetChildCount()) {
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n",
searchingOverflowList?"overflow":"normal", line.get());
#endif
nsLineBox *cur = line;
if (!searchingOverflowList) {
line = mLines.erase(line);
// Invalidate the space taken up by the line.
// XXX We need to do this if we're removing a frame as a result of
// a call to RemoveFrame(), but we may not need to do this in all
// cases...
#ifdef NOISY_BLOCK_INVALIDATE
nsRect visOverflow(cur->GetVisualOverflowArea());
printf("%p invalidate 10 (%d, %d, %d, %d)\n",
this, visOverflow.x, visOverflow.y,
visOverflow.width, visOverflow.height);
#endif
} else {
line = overflowLines->mLines.erase(line);
if (overflowLines->mLines.empty()) {
DestroyOverflowLines();
overflowLines = nullptr;
// We just invalidated our iterators. Since we were in
// the overflow lines list, which is now empty, set them
// so we're at the end of the regular line list.
line_start = mLines.begin();
line_end = mLines.end();
line = line_end;
}
}
FreeLineBox(cur);
// If we're removing a line, ReflowDirtyLines isn't going to
// know that it needs to slide lines unless something is marked
// dirty. So mark the previous margin of the next line dirty if
// there is one.
if (line != line_end) {
line->MarkPreviousMarginDirty();
}
haveAdvancedToNextLine = true;
} else {
// Make the line that just lost a frame dirty, and advance to
// the next line.
if (!deletedNextContinuation || isLastFrameOnLine ||
!line->Contains(deletedNextContinuation)) {
line->MarkDirty();
++line;
haveAdvancedToNextLine = true;
}
}
if (deletedNextContinuation) {
// See if we should keep looking in the current flow's line list.
if (deletedNextContinuation->GetParent() != this) {
// The deceased frames continuation is not a child of the
// current block. So break out of the loop so that we advance
// to the next parent.
//
// If we have a continuation in a different block then all bets are
// off regarding whether we are deleting frames without actual content,
// so don't propagate FRAMES_ARE_EMPTY any further.
aFlags &= ~FRAMES_ARE_EMPTY;
break;
}
// If we advanced to the next line then check if we should switch to the
// overflow line list.
if (haveAdvancedToNextLine) {
if (line != line_end && !searchingOverflowList &&
!line->Contains(deletedNextContinuation)) {
// We have advanced to the next *normal* line but the next-in-flow
// is not there - force a switch to the overflow line list.
line = line_end;
}
TryAllLines(&line, &line_start, &line_end, &searchingOverflowList,
&overflowLines);
#ifdef NOISY_REMOVE_FRAME
printf("DoRemoveFrame: now on %s line=%p\n",
searchingOverflowList?"overflow":"normal", line.get());
#endif
}
}
}
if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) {
line.next()->MarkDirty();
line.next()->SetInvalidateTextRuns(true);
}
#ifdef DEBUG
VerifyLines(true);
VerifyOverflowSituation();
#endif
// Advance to next flow block if the frame has more continuations.
if (!aDeletedFrame) {
return;
}
nsBlockFrame* nextBlock = nsLayoutUtils::GetAsBlock(aDeletedFrame->GetParent());
NS_ASSERTION(nextBlock,
"Our child's continuation's parent is not a block?");
uint32_t flags = (aFlags & REMOVE_FIXED_CONTINUATIONS);
nextBlock->DoRemoveFrameInternal(aDeletedFrame, flags, aPostDestroyData);
}
static bool
FindBlockLineFor(nsIFrame* aChild,
nsLineList::iterator aBegin,
nsLineList::iterator aEnd,
nsLineList::iterator* aResult)
{
MOZ_ASSERT(aChild->IsBlockOutside());
for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
MOZ_ASSERT(line->GetChildCount() > 0);
if (line->IsBlock() && line->mFirstChild == aChild) {
MOZ_ASSERT(line->GetChildCount() == 1);
*aResult = line;
return true;
}
}
return false;
}
static bool
FindInlineLineFor(nsIFrame* aChild,
const nsFrameList& aFrameList,
nsLineList::iterator aBegin,
nsLineList::iterator aEnd,
nsLineList::iterator* aResult)
{
MOZ_ASSERT(!aChild->IsBlockOutside());
for (nsLineList::iterator line = aBegin; line != aEnd; ++line) {
MOZ_ASSERT(line->GetChildCount() > 0);
if (!line->IsBlock()) {
// Optimize by comparing the line's last child first.
nsLineList::iterator next = line.next();
if (aChild == (next == aEnd ? aFrameList.LastChild()
: next->mFirstChild->GetPrevSibling()) ||
line->Contains(aChild)) {
*aResult = line;
return true;
}
}
}
return false;
}
static bool
FindLineFor(nsIFrame* aChild,
const nsFrameList& aFrameList,
nsLineList::iterator aBegin,
nsLineList::iterator aEnd,
nsLineList::iterator* aResult)
{
return aChild->IsBlockOutside() ?
FindBlockLineFor(aChild, aBegin, aEnd, aResult) :
FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult);
}
nsresult
nsBlockFrame::StealFrame(nsIFrame* aChild)
{
MOZ_ASSERT(aChild->GetParent() == this);
if ((aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
aChild->IsFloating()) {
RemoveFloat(aChild);
return NS_OK;
}
if (MaybeStealOverflowContainerFrame(aChild)) {
return NS_OK;
}
MOZ_ASSERT(!(aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW));
nsLineList::iterator line;
if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) {
RemoveFrameFromLine(aChild, line, mFrames, mLines);
} else {
FrameLines* overflowLines = GetOverflowLines();
DebugOnly<bool> found;
found = FindLineFor(aChild, overflowLines->mFrames,
overflowLines->mLines.begin(),
overflowLines->mLines.end(), &line);
MOZ_ASSERT(found);
RemoveFrameFromLine(aChild, line, overflowLines->mFrames,
overflowLines->mLines);
if (overflowLines->mLines.empty()) {
DestroyOverflowLines();
}
}
return NS_OK;
}
void
nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild, nsLineList::iterator aLine,
nsFrameList& aFrameList, nsLineList& aLineList)
{
aFrameList.RemoveFrame(aChild);
if (aChild == aLine->mFirstChild) {
aLine->mFirstChild = aChild->GetNextSibling();
}
aLine->NoteFrameRemoved(aChild);
if (aLine->GetChildCount() > 0) {
aLine->MarkDirty();
} else {
// The line became empty - destroy it.
nsLineBox* lineBox = aLine;
aLine = aLineList.erase(aLine);
if (aLine != aLineList.end()) {
aLine->MarkPreviousMarginDirty();
}
FreeLineBox(lineBox);
}
}
void
nsBlockFrame::DeleteNextInFlowChild(nsIFrame* aNextInFlow,
bool aDeletingEmptyFrames)
{
NS_PRECONDITION(aNextInFlow->GetPrevInFlow(), "bad next-in-flow");
if (aNextInFlow->GetStateBits() &
(NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) {
nsContainerFrame::DeleteNextInFlowChild(aNextInFlow, aDeletingEmptyFrames);
}
else {
#ifdef DEBUG
if (aDeletingEmptyFrames) {
nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow);
}
#endif
DoRemoveFrame(aNextInFlow,
aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0);
}
}
const nsStyleText*
nsBlockFrame::StyleTextForLineLayout()
{
// Return the pointer to an unmodified style text
return StyleText();
}
////////////////////////////////////////////////////////////////////////
// Float support
LogicalRect
nsBlockFrame::AdjustFloatAvailableSpace(BlockReflowInput& aState,
const LogicalRect& aFloatAvailableSpace,
nsIFrame* aFloatFrame)
{
// Compute the available inline size. By default, assume the inline
// size of the containing block.
nscoord availISize;
const nsStyleDisplay* floatDisplay = aFloatFrame->StyleDisplay();
WritingMode wm = aState.mReflowInput.GetWritingMode();
if (mozilla::StyleDisplay::Table != floatDisplay->mDisplay ||
eCompatibility_NavQuirks != aState.mPresContext->CompatibilityMode() ) {
availISize = aState.ContentISize();
}
else {
// This quirk matches the one in BlockReflowInput::FlowAndPlaceFloat
// give tables only the available space
// if they can shrink we may not be constrained to place
// them in the next line
availISize = aFloatAvailableSpace.ISize(wm);
}
nscoord availBSize = NS_UNCONSTRAINEDSIZE == aState.ContentBSize()
? NS_UNCONSTRAINEDSIZE
: std::max(0, aState.ContentBEnd() - aState.mBCoord);
if (availBSize != NS_UNCONSTRAINEDSIZE &&
!aState.mFlags.mFloatFragmentsInsideColumnEnabled &&
nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::ColumnSet)) {
// Tell the float it has unrestricted block-size, so it won't break.
// If the float doesn't actually fit in the column it will fail to be
// placed, and either move to the block-start of the next column or just
// overflow.
availBSize = NS_UNCONSTRAINEDSIZE;
}
return LogicalRect(wm, aState.ContentIStart(), aState.ContentBStart(),
availISize, availBSize);
}
nscoord
nsBlockFrame::ComputeFloatISize(BlockReflowInput& aState,
const LogicalRect& aFloatAvailableSpace,
nsIFrame* aFloat)
{
NS_PRECONDITION(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW,
"aFloat must be an out-of-flow frame");
// Reflow the float.
LogicalRect availSpace = AdjustFloatAvailableSpace(aState,
aFloatAvailableSpace,
aFloat);
WritingMode blockWM = aState.mReflowInput.GetWritingMode();
WritingMode floatWM = aFloat->GetWritingMode();
ReflowInput
floatRS(aState.mPresContext, aState.mReflowInput, aFloat,
availSpace.Size(blockWM).ConvertTo(floatWM, blockWM));
return floatRS.ComputedSizeWithMarginBorderPadding(blockWM).ISize(blockWM);
}
void
nsBlockFrame::ReflowFloat(BlockReflowInput& aState,
const LogicalRect& aAdjustedAvailableSpace,
nsIFrame* aFloat,
LogicalMargin& aFloatMargin,
LogicalMargin& aFloatOffsets,
bool aFloatPushedDown,
nsReflowStatus& aReflowStatus)
{
NS_PRECONDITION(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW,
"aFloat must be an out-of-flow frame");
// Reflow the float.
aReflowStatus.Reset();
WritingMode wm = aState.mReflowInput.GetWritingMode();
#ifdef NOISY_FLOAT
printf("Reflow Float %p in parent %p, availSpace(%d,%d,%d,%d)\n",
aFloat, this,
aAdjustedAvailableSpace.IStart(wm), aAdjustedAvailableSpace.BStart(wm),
aAdjustedAvailableSpace.ISize(wm), aAdjustedAvailableSpace.BSize(wm)
);
#endif
ReflowInput
floatRS(aState.mPresContext, aState.mReflowInput, aFloat,
aAdjustedAvailableSpace.Size(wm).ConvertTo(aFloat->GetWritingMode(),
wm));
// Normally the mIsTopOfPage state is copied from the parent reflow
// state. However, when reflowing a float, if we've placed other
// floats that force this float *down* or *narrower*, we should unset
// the mIsTopOfPage state.
// FIXME: This is somewhat redundant with the |isAdjacentWithTop|
// variable below, which has the exact same effect. Perhaps it should
// be merged into that, except that the test for narrowing here is not
// about adjacency with the top, so it seems misleading.
if (floatRS.mFlags.mIsTopOfPage &&
(aFloatPushedDown ||
aAdjustedAvailableSpace.ISize(wm) != aState.ContentISize())) {
floatRS.mFlags.mIsTopOfPage = false;
}
// Setup a block reflow context to reflow the float.
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowInput);
// Reflow the float
bool isAdjacentWithTop = aState.IsAdjacentWithTop();
nsIFrame* clearanceFrame = nullptr;
do {
nsCollapsingMargin margin;
bool mayNeedRetry = false;
floatRS.mDiscoveredClearance = nullptr;
// Only first in flow gets a block-start margin.
if (!aFloat->GetPrevInFlow()) {
brc.ComputeCollapsedBStartMargin(floatRS, &margin,
clearanceFrame,
&mayNeedRetry);
if (mayNeedRetry && !clearanceFrame) {
floatRS.mDiscoveredClearance = &clearanceFrame;
// We don't need to push the float manager state because the the block has its own
// float manager that will be destroyed and recreated
}
}
brc.ReflowBlock(aAdjustedAvailableSpace, true, margin,
0, isAdjacentWithTop,
nullptr, floatRS,
aReflowStatus, aState);
} while (clearanceFrame);
if (!aReflowStatus.IsFullyComplete() &&
ShouldAvoidBreakInside(floatRS)) {
aReflowStatus.SetInlineLineBreakBeforeAndReset();
} else if (aReflowStatus.IsIncomplete() &&
(NS_UNCONSTRAINEDSIZE == aAdjustedAvailableSpace.BSize(wm))) {
// An incomplete reflow status means we should split the float
// if the height is constrained (bug 145305).
aReflowStatus.Reset();
}
if (aReflowStatus.NextInFlowNeedsReflow()) {
aState.mReflowStatus.SetNextInFlowNeedsReflow();
}
if (aFloat->IsLetterFrame()) {
// We never split floating first letters; an incomplete state for
// such frames simply means that there is more content to be
// reflowed on the line.
if (aReflowStatus.IsIncomplete())
aReflowStatus.Reset();
}
// Capture the margin and offsets information for the caller
aFloatMargin =
// float margins don't collapse
floatRS.ComputedLogicalMargin().ConvertTo(wm, floatRS.GetWritingMode());
aFloatOffsets =
floatRS.ComputedLogicalOffsets().ConvertTo(wm, floatRS.GetWritingMode());
const ReflowOutput& metrics = brc.GetMetrics();
// Set the rect, make sure the view is properly sized and positioned,
// and tell the frame we're done reflowing it
// XXXldb This seems like the wrong place to be doing this -- shouldn't
// we be doing this in BlockReflowInput::FlowAndPlaceFloat after
// we've positioned the float, and shouldn't we be doing the equivalent
// of |PlaceFrameView| here?
WritingMode metricsWM = metrics.GetWritingMode();
aFloat->SetSize(metricsWM, metrics.Size(metricsWM));
if (aFloat->HasView()) {
nsContainerFrame::SyncFrameViewAfterReflow(aState.mPresContext, aFloat,
aFloat->GetView(),
metrics.VisualOverflow(),
NS_FRAME_NO_MOVE_VIEW);
}
// Pass floatRS so the frame hierarchy can be used (redoFloatRS has the same hierarchy)
aFloat->DidReflow(aState.mPresContext, &floatRS);
#ifdef NOISY_FLOAT
printf("end ReflowFloat %p, sized to %d,%d\n",
aFloat, metrics.Width(), metrics.Height());
#endif
}
StyleClear
nsBlockFrame::FindTrailingClear()
{
// find the break type of the last line
for (nsIFrame* b = this; b; b = b->GetPrevInFlow()) {
nsBlockFrame* block = static_cast<nsBlockFrame*>(b);
LineIterator endLine = block->LinesEnd();
if (endLine != block->LinesBegin()) {
--endLine;
return endLine->GetBreakTypeAfter();
}
}
return StyleClear::None;
}
void
nsBlockFrame::ReflowPushedFloats(BlockReflowInput& aState,
nsOverflowAreas& aOverflowAreas,
nsReflowStatus& aStatus)
{
// Pushed floats live at the start of our float list; see comment
// above nsBlockFrame::DrainPushedFloats.
nsIFrame* f = mFloats.FirstChild();
nsIFrame* prev = nullptr;
while (f && (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT)) {
MOZ_ASSERT(prev == f->GetPrevSibling());
// When we push a first-continuation float in a non-initial reflow,
// it's possible that we end up with two continuations with the same
// parent. This happens if, on the previous reflow of the block or
// a previous reflow of the line containing the block, the float was
// split between continuations A and B of the parent, but on the
// current reflow, none of the float can fit in A.
//
// When this happens, we might even have the two continuations
// out-of-order due to the management of the pushed floats. In
// particular, if the float's placeholder was in a pushed line that
// we reflowed before it was pushed, and we split the float during
// that reflow, we might have the continuation of the float before
// the float itself. (In the general case, however, it's correct
// for floats in the pushed floats list to come before floats
// anchored in pushed lines; however, in this case it's wrong. We
// should probably find a way to fix it somehow, since it leads to
// incorrect layout in some cases.)
//
// When we have these out-of-order continuations, we might hit the
// next-continuation before the previous-continuation. When that
// happens, just push it. When we reflow the next continuation,
// we'll either pull all of its content back and destroy it (by
// calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will
// pull it out of its current position and push it again (and
// potentially repeat this cycle for the next continuation, although
// hopefully then they'll be in the right order).
//
// We should also need this code for the in-order case if the first
// continuation of a float gets moved across more than one
// continuation of the containing block. In this case we'd manage
// to push the second continuation without this check, but not the
// third and later.
nsIFrame *prevContinuation = f->GetPrevContinuation();
if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) {
mFloats.RemoveFrame(f);
aState.AppendPushedFloatChain(f);
f = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
continue;
}
// Always call FlowAndPlaceFloat; we might need to place this float
// if didn't belong to this block the last time it was reflowed.
aState.FlowAndPlaceFloat(f);
ConsiderChildOverflow(aOverflowAreas, f);
nsIFrame* next = !prev ? mFloats.FirstChild() : prev->GetNextSibling();
if (next == f) {
// We didn't push |f| so its next-sibling is next.
next = f->GetNextSibling();
prev = f;
} // else: we did push |f| so |prev|'s new next-sibling is next.
f = next;
}
// If there are continued floats, then we may need to continue BR clearance
if (0 != aState.ClearFloats(0, StyleClear::Both)) {
nsBlockFrame* prevBlock = static_cast<nsBlockFrame*>(GetPrevInFlow());
if (prevBlock) {
aState.mFloatBreakType = prevBlock->FindTrailingClear();
}
}
}
void
nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager, WritingMode aWM,
const nsSize& aContainerSize)
{
// Recover our own floats
nsIFrame* stop = nullptr; // Stop before we reach pushed floats that
// belong to our next-in-flow
for (nsIFrame* f = mFloats.FirstChild(); f && f != stop; f = f->GetNextSibling()) {
LogicalRect region = nsFloatManager::GetRegionFor(aWM, f, aContainerSize);
aFloatManager.AddFloat(f, region, aWM, aContainerSize);
if (!stop && f->GetNextInFlow())
stop = f->GetNextInFlow();
}
// Recurse into our overflow container children
for (nsIFrame* oc = GetChildList(kOverflowContainersList).FirstChild();
oc; oc = oc->GetNextSibling()) {
RecoverFloatsFor(oc, aFloatManager, aWM, aContainerSize);
}
// Recurse into our normal children
for (nsBlockFrame::LineIterator line = LinesBegin(); line != LinesEnd(); ++line) {
if (line->IsBlock()) {
RecoverFloatsFor(line->mFirstChild, aFloatManager, aWM, aContainerSize);
}
}
}
void
nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame,
nsFloatManager& aFloatManager,
WritingMode aWM,
const nsSize& aContainerSize)
{
NS_PRECONDITION(aFrame, "null frame");
// Only blocks have floats
nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aFrame);
// Don't recover any state inside a block that has its own space manager
// (we don't currently have any blocks like this, though, thanks to our
// use of extra frames for 'overflow')
if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) {
// If the element is relatively positioned, then adjust x and y
// accordingly so that we consider relatively positioned frames
// at their original position.
LogicalRect rect(aWM, block->GetNormalRect(), aContainerSize);
nscoord lineLeft = rect.LineLeft(aWM, aContainerSize);
nscoord blockStart = rect.BStart(aWM);
aFloatManager.Translate(lineLeft, blockStart);
block->RecoverFloats(aFloatManager, aWM, aContainerSize);
aFloatManager.Translate(-lineLeft, -blockStart);
}
}
//////////////////////////////////////////////////////////////////////
// Painting, event handling
#ifdef DEBUG
static void ComputeVisualOverflowArea(nsLineList& aLines,
nscoord aWidth, nscoord aHeight,
nsRect& aResult)
{
nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight;
for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end();
line != line_end;
++line) {
// Compute min and max x/y values for the reflowed frame's
// combined areas
nsRect visOverflow(line->GetVisualOverflowArea());
nscoord x = visOverflow.x;
nscoord y = visOverflow.y;
nscoord xmost = x + visOverflow.width;
nscoord ymost = y + visOverflow.height;
if (x < xa) {
xa = x;
}
if (xmost > xb) {
xb = xmost;
}
if (y < ya) {
ya = y;
}
if (ymost > yb) {
yb = ymost;
}
}
aResult.x = xa;
aResult.y = ya;
aResult.width = xb - xa;
aResult.height = yb - ya;
}
#endif
bool
nsBlockFrame::IsVisibleInSelection(nsISelection* aSelection)
{
if (mContent->IsAnyOfHTMLElements(nsGkAtoms::html, nsGkAtoms::body))
return true;
nsCOMPtr<nsIDOMNode> node(do_QueryInterface(mContent));
bool visible;
nsresult rv = aSelection->ContainsNode(node, true, &visible);
return NS_SUCCEEDED(rv) && visible;
}
#ifdef DEBUG
static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) {
if (nsBlockFrame::gNoisyDamageRepair) {
nsFrame::IndentBy(stdout, aDepth+1);
nsRect lineArea = aLine->GetVisualOverflowArea();
printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
aDrawn ? "draw" : "skip",
static_cast<void*>(aLine),
aLine->IStart(), aLine->BStart(),
aLine->ISize(), aLine->BSize(),
lineArea.x, lineArea.y,
lineArea.width, lineArea.height);
}
}
#endif
static void
DisplayLine(nsDisplayListBuilder* aBuilder, const nsRect& aLineArea,
nsBlockFrame::LineIterator& aLine,
int32_t aDepth, int32_t& aDrawnLines, const nsDisplayListSet& aLists,
nsBlockFrame* aFrame, TextOverflow* aTextOverflow,
uint32_t aLineNumberForTextOverflow) {
// If the line's combined area (which includes child frames that
// stick outside of the line's bounding box or our bounding box)
// intersects the dirty rect then paint the line.
bool intersect = aLineArea.Intersects(aBuilder->GetDirtyRect());
bool visible = aLineArea.Intersects(aBuilder->GetVisibleRect());
#ifdef DEBUG
if (nsBlockFrame::gLamePaintMetrics) {
aDrawnLines++;
}
DebugOutputDrawLine(aDepth, aLine.get(), intersect);
#endif
// The line might contain a placeholder for a visible out-of-flow, in which
// case we need to descend into it. If there is such a placeholder, we will
// have NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO set.
// In particular, we really want to check ShouldDescendIntoFrame()
// on all the frames on the line, but that might be expensive. So
// we approximate it by checking it on aFrame; if it's true for any
// frame in the line, it's also true for aFrame.
bool lineInline = aLine->IsInline();
bool lineMayHaveTextOverflow = aTextOverflow && lineInline;
if (!intersect && !aBuilder->ShouldDescendIntoFrame(aFrame, visible) &&
!lineMayHaveTextOverflow)
return;
// Collect our line's display items in a temporary nsDisplayListCollection,
// so that we can apply any "text-overflow" clipping to the entire collection
// without affecting previous lines.
nsDisplayListCollection collection(aBuilder);
// Block-level child backgrounds go on the blockBorderBackgrounds list ...
// Inline-level child backgrounds go on the regular child content list.
nsDisplayListSet childLists(collection,
lineInline ? collection.Content() : collection.BlockBorderBackgrounds());
uint32_t flags = lineInline ? nsIFrame::DISPLAY_CHILD_INLINE : 0;
nsIFrame* kid = aLine->mFirstChild;
int32_t n = aLine->GetChildCount();
while (--n >= 0) {
aFrame->BuildDisplayListForChild(aBuilder, kid, childLists, flags);
kid = kid->GetNextSibling();
}
if (lineMayHaveTextOverflow) {
aTextOverflow->ProcessLine(collection, aLine.get(), aLineNumberForTextOverflow);
}
collection.MoveTo(aLists);
}
void
nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsDisplayListSet& aLists)
{
int32_t drawnLines; // Will only be used if set (gLamePaintMetrics).
int32_t depth = 0;
#ifdef DEBUG
if (gNoisyDamageRepair) {
nsRect dirty = aBuilder->GetDirtyRect();
depth = GetDepth();
nsRect ca;
::ComputeVisualOverflowArea(mLines, mRect.width, mRect.height, ca);
nsFrame::IndentBy(stdout, depth);
ListTag(stdout);
printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n",
mRect.x, mRect.y, mRect.width, mRect.height,
dirty.x, dirty.y, dirty.width, dirty.height,
ca.x, ca.y, ca.width, ca.height);
}
PRTime start = 0; // Initialize these variables to silence the compiler.
if (gLamePaintMetrics) {
start = PR_Now();
drawnLines = 0;
}
#endif
DisplayBorderBackgroundOutline(aBuilder, aLists);
if (GetPrevInFlow()) {
DisplayOverflowContainers(aBuilder, aLists);
for (nsIFrame* f : mFloats) {
if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT)
BuildDisplayListForChild(aBuilder, f, aLists);
}
}
aBuilder->MarkFramesForDisplayList(this, mFloats);
// Prepare for text-overflow processing.
UniquePtr<TextOverflow> textOverflow =
TextOverflow::WillProcessLines(aBuilder, this);
// We'll collect our lines' display items here, & then append this to aLists.
nsDisplayListCollection linesDisplayListCollection(aBuilder);
// Don't use the line cursor if we might have a descendant placeholder ...
// it might skip lines that contain placeholders but don't themselves
// intersect with the dirty area.
// In particular, we really want to check ShouldDescendIntoFrame()
// on all our child frames, but that might be expensive. So we
// approximate it by checking it on |this|; if it's true for any
// frame in our child list, it's also true for |this|.
// Also skip the cursor if we're creating text overflow markers,
// since we need to know what line number we're up to in order
// to generate unique display item keys.
nsLineBox* cursor = (aBuilder->ShouldDescendIntoFrame(this, true) || textOverflow) ?
nullptr : GetFirstLineContaining(aBuilder->GetDirtyRect().y);
LineIterator line_end = LinesEnd();
if (cursor) {
for (LineIterator line = mLines.begin(cursor);
line != line_end;
++line) {
nsRect lineArea = line->GetVisualOverflowArea();
if (!lineArea.IsEmpty()) {
// Because we have a cursor, the combinedArea.ys are non-decreasing.
// Once we've passed aDirtyRect.YMost(), we can never see it again.
if (lineArea.y >= aBuilder->GetDirtyRect().YMost()) {
break;
}
MOZ_ASSERT(!textOverflow);
DisplayLine(aBuilder, lineArea, line, depth, drawnLines,
linesDisplayListCollection, this, nullptr, 0);
}
}
} else {
bool nonDecreasingYs = true;
uint32_t lineCount = 0;
nscoord lastY = INT32_MIN;
nscoord lastYMost = INT32_MIN;
for (LineIterator line = LinesBegin();
line != line_end;
++line) {
nsRect lineArea = line->GetVisualOverflowArea();
DisplayLine(aBuilder, lineArea, line, depth, drawnLines,
linesDisplayListCollection, this, textOverflow.get(), lineCount);
if (!lineArea.IsEmpty()) {
if (lineArea.y < lastY
|| lineArea.YMost() < lastYMost) {
nonDecreasingYs = false;
}
lastY = lineArea.y;
lastYMost = lineArea.YMost();
}
lineCount++;
}
if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) {
SetupLineCursor();
}
}
// Pick up the resulting text-overflow markers. We append them to
// PositionedDescendants just before we append the lines' display items,
// so that our text-overflow markers will appear on top of this block's
// normal content but below any of its its' positioned children.
if (textOverflow) {
aLists.PositionedDescendants()->AppendToTop(&textOverflow->GetMarkers());
}
linesDisplayListCollection.MoveTo(aLists);
if (HasOutsideBullet()) {
// Display outside bullets manually
nsIFrame* bullet = GetOutsideBullet();
BuildDisplayListForChild(aBuilder, bullet, aLists);
}
#ifdef DEBUG
if (gLamePaintMetrics) {
PRTime end = PR_Now();
int32_t numLines = mLines.size();
if (!numLines) numLines = 1;
PRTime lines, deltaPerLine, delta;
lines = int64_t(numLines);
delta = end - start;
deltaPerLine = delta / lines;
ListTag(stdout);
char buf[400];
SprintfLiteral(buf,
": %" PRId64 " elapsed (%" PRId64 " per line) lines=%d drawn=%d skip=%d",
delta, deltaPerLine,
numLines, drawnLines, numLines - drawnLines);
printf("%s\n", buf);
}
#endif
}
#ifdef ACCESSIBILITY
a11y::AccType
nsBlockFrame::AccessibleType()
{
if (IsTableCaption()) {
return GetRect().IsEmpty() ? a11y::eNoType : a11y::eHTMLCaptionType;
}
// block frame may be for <hr>
if (mContent->IsHTMLElement(nsGkAtoms::hr)) {
return a11y::eHTMLHRType;
}
if (!HasBullet() || !PresContext()) {
//XXXsmaug What if we're in the shadow dom?
if (!mContent->GetParent()) {
// Don't create accessible objects for the root content node, they are redundant with
// the nsDocAccessible object created with the document node
return a11y::eNoType;
}
if (mContent == mContent->OwnerDoc()->GetBody()) {
// Don't create accessible objects for the body, they are redundant with
// the nsDocAccessible object created with the document node
return a11y::eNoType;
}
// Not a bullet, treat as normal HTML container
return a11y::eHyperTextType;
}
// Create special list bullet accessible
return a11y::eHTMLLiType;
}
#endif
void nsBlockFrame::ClearLineCursor()
{
if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) {
return;
}
DeleteProperty(LineCursorProperty());
RemoveStateBits(NS_BLOCK_HAS_LINE_CURSOR);
}
void nsBlockFrame::SetupLineCursor()
{
if (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR
|| mLines.empty()) {
return;
}
SetProperty(LineCursorProperty(), mLines.front());
AddStateBits(NS_BLOCK_HAS_LINE_CURSOR);
}
nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y)
{
if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) {
return nullptr;
}
nsLineBox* property = GetProperty(LineCursorProperty());
LineIterator cursor = mLines.begin(property);
nsRect cursorArea = cursor->GetVisualOverflowArea();
while ((cursorArea.IsEmpty() || cursorArea.YMost() > y)
&& cursor != mLines.front()) {
cursor = cursor.prev();
cursorArea = cursor->GetVisualOverflowArea();
}
while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y)
&& cursor != mLines.back()) {
cursor = cursor.next();
cursorArea = cursor->GetVisualOverflowArea();
}
if (cursor.get() != property) {
SetProperty(LineCursorProperty(), cursor.get());
}
return cursor.get();
}
/* virtual */ void
nsBlockFrame::ChildIsDirty(nsIFrame* aChild)
{
// See if the child is absolutely positioned
if (aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW &&
aChild->IsAbsolutelyPositioned()) {
// do nothing
} else if (aChild == GetOutsideBullet()) {
// The bullet lives in the first line, unless the first line has
// height 0 and there is a second line, in which case it lives
// in the second line.
LineIterator bulletLine = LinesBegin();
if (bulletLine != LinesEnd() && bulletLine->BSize() == 0 &&
bulletLine != mLines.back()) {
bulletLine = bulletLine.next();
}
if (bulletLine != LinesEnd()) {
MarkLineDirty(bulletLine, &mLines);
}
// otherwise we have an empty line list, and ReflowDirtyLines
// will handle reflowing the bullet.
} else {
// Note that we should go through our children to mark lines dirty
// before the next reflow. Doing it now could make things O(N^2)
// since finding the right line is O(N).
// We don't need to worry about marking lines on the overflow list
// as dirty; we're guaranteed to reflow them if we take them off the
// overflow list.
// However, we might have gotten a float, in which case we need to
// reflow the line containing its placeholder. So find the
// ancestor-or-self of the placeholder that's a child of the block,
// and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark
// its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
// We need to take some care to handle the case where a float is in
// a different continuation than its placeholder, including marking
// an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES.
if (!(aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) {
AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
} else {
NS_ASSERTION(aChild->IsFloating(), "should be a float");
nsIFrame* thisFC = FirstContinuation();
nsIFrame* placeholderPath = aChild->GetPlaceholderFrame();
// SVG code sometimes sends FrameNeedsReflow notifications during
// frame destruction, leading to null placeholders, but we're safe
// ignoring those.
if (placeholderPath) {
for (;;) {
nsIFrame *parent = placeholderPath->GetParent();
if (parent->GetContent() == mContent &&
parent->FirstContinuation() == thisFC) {
parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES);
break;
}
placeholderPath = parent;
}
placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
}
}
nsContainerFrame::ChildIsDirty(aChild);
}
void
nsBlockFrame::Init(nsIContent* aContent,
nsContainerFrame* aParent,
nsIFrame* aPrevInFlow)
{
if (aPrevInFlow) {
// Copy over the inherited block frame bits from the prev-in-flow.
RemoveStateBits(NS_BLOCK_FLAGS_MASK);
AddStateBits(aPrevInFlow->GetStateBits() &
(NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK));
}
nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
if (!aPrevInFlow ||
aPrevInFlow->GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) {
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
}
// A display:flow-root box establishes a block formatting context.
// If a box has a different block flow direction than its containing block:
// ...
// If the box is a block container, then it establishes a new block
// formatting context.
// (http://dev.w3.org/csswg/css-writing-modes/#block-flow)
// If the box has contain: paint (or contain: strict), then it should also
// establish a formatting context.
if (StyleDisplay()->mDisplay == mozilla::StyleDisplay::FlowRoot ||
(GetParent() && StyleVisibility()->mWritingMode !=
GetParent()->StyleVisibility()->mWritingMode) ||
StyleDisplay()->IsContainPaint()) {
AddStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS);
}
if ((GetStateBits() &
(NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) ==
(NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) {
AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT);
}
}
void
nsBlockFrame::SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList)
{
if (kFloatList == aListID) {
mFloats.SetFrames(aChildList);
} else if (kPrincipalList == aListID) {
NS_ASSERTION((GetStateBits() & (NS_BLOCK_FRAME_HAS_INSIDE_BULLET |
NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET)) == 0,
"how can we have a bullet already?");
#ifdef DEBUG
// The only times a block that is an anonymous box is allowed to have a
// first-letter frame are when it's the block inside a non-anonymous cell,
// the block inside a fieldset, button or column set, or a scrolled content
// block, except for <select>. Note that this means that blocks which are
// the anonymous block in {ib} splits do NOT get first-letter frames.
// Note that NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations
// of the block.
nsAtom *pseudo = StyleContext()->GetPseudo();
bool haveFirstLetterStyle =
(!pseudo ||
(pseudo == nsCSSAnonBoxes::cellContent &&
GetParent()->StyleContext()->GetPseudo() == nullptr) ||
pseudo == nsCSSAnonBoxes::fieldsetContent ||
pseudo == nsCSSAnonBoxes::buttonContent ||
pseudo == nsCSSAnonBoxes::columnContent ||
(pseudo == nsCSSAnonBoxes::scrolledContent &&
!GetParent()->IsListControlFrame()) ||
pseudo == nsCSSAnonBoxes::mozSVGText) &&
!IsComboboxControlFrame() &&
!IsFrameOfType(eMathML) &&
RefPtr<nsStyleContext>(GetFirstLetterStyle(PresContext())) != nullptr;
NS_ASSERTION(haveFirstLetterStyle ==
((mState & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0),
"NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync");
#endif
AddFrames(aChildList, nullptr);
// Create a list bullet if this is a list-item. Note that this is
// done here so that RenumberLists will work (it needs the bullets
// to store the bullet numbers). Also note that due to various
// wrapper frames (scrollframes, columns) we want to use the
// outermost (primary, ideally, but it's not set yet when we get
// here) frame of our content for the display check. On the other
// hand, we look at ourselves for the GetPrevInFlow() check, since
// for a columnset we don't want a bullet per column. Note that
// the outermost frame for the content is the primary frame in
// most cases; the ones when it's not (like tables) can't be
// StyleDisplay::ListItem).
nsIFrame* possibleListItem = this;
while (1) {
nsIFrame* parent = possibleListItem->GetParent();
if (parent->GetContent() != GetContent()) {
break;
}
possibleListItem = parent;
}
if (mozilla::StyleDisplay::ListItem ==
possibleListItem->StyleDisplay()->mDisplay &&
!GetPrevInFlow()) {
// Resolve style for the bullet frame
const nsStyleList* styleList = StyleList();
CounterStyle* style = styleList->mCounterStyle;
CreateBulletFrameForListItem(
style->IsBullet(),
styleList->mListStylePosition == NS_STYLE_LIST_STYLE_POSITION_INSIDE);
}
} else {
nsContainerFrame::SetInitialChildList(aListID, aChildList);
}
}
void
nsBlockFrame::CreateBulletFrameForListItem(bool aCreateBulletList,
bool aListStylePositionInside)
{
nsIPresShell* shell = PresShell();
CSSPseudoElementType pseudoType = aCreateBulletList ?
CSSPseudoElementType::mozListBullet :
CSSPseudoElementType::mozListNumber;
RefPtr<nsStyleContext> kidSC = ResolveBulletStyle(pseudoType,
shell->StyleSet());
// Create bullet frame
nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC);
bullet->Init(mContent, this, nullptr);
// If the list bullet frame should be positioned inside then add
// it to the flow now.
if (aListStylePositionInside) {
nsFrameList bulletList(bullet, bullet);
AddFrames(bulletList, nullptr);
SetProperty(InsideBulletProperty(), bullet);
AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET);
} else {
nsFrameList* bulletList = new (shell) nsFrameList(bullet, bullet);
SetProperty(OutsideBulletProperty(), bulletList);
AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET);
}
}
bool
nsBlockFrame::BulletIsEmpty() const
{
NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->mDisplay ==
mozilla::StyleDisplay::ListItem && HasOutsideBullet(),
"should only care when we have an outside bullet");
const nsStyleList* list = StyleList();
return list->mCounterStyle->IsNone() &&
!list->GetListStyleImage();
}
void
nsBlockFrame::GetSpokenBulletText(nsAString& aText) const
{
const nsStyleList* myList = StyleList();
if (myList->GetListStyleImage()) {
aText.Assign(kDiscCharacter);
aText.Append(' ');
} else {
nsBulletFrame* bullet = GetBullet();
if (bullet) {
bullet->GetSpokenText(aText);
} else {
aText.Truncate();
}
}
}
bool
nsBlockFrame::RenumberChildFrames(int32_t* aOrdinal,
int32_t aDepth,
int32_t aIncrement,
bool aForCounting)
{
// Examine each line in the block
bool foundValidLine;
nsBlockInFlowLineIterator bifLineIter(this, &foundValidLine);
if (!foundValidLine) {
return false;
}
bool renumberedABullet = false;
do {
nsLineList::iterator line = bifLineIter.GetLine();
nsIFrame* kid = line->mFirstChild;
int32_t n = line->GetChildCount();
while (--n >= 0) {
bool kidRenumberedABullet =
kid->RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
if (!aForCounting && kidRenumberedABullet) {
line->MarkDirty();
renumberedABullet = true;
}
kid = kid->GetNextSibling();
}
} while (bifLineIter.Next());
// We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between
// the bullet and the caller of RenumberLists. But the caller itself
// has to be responsible for setting the bit itself, since that caller
// might be making a FrameNeedsReflow call, which requires that the
// bit not be set yet.
if (renumberedABullet && aDepth != 0) {
AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
}
return renumberedABullet;
}
void
nsBlockFrame::ReflowBullet(nsIFrame* aBulletFrame,
BlockReflowInput& aState,
ReflowOutput& aMetrics,
nscoord aLineTop)
{
const ReflowInput &ri = aState.mReflowInput;
// Reflow the bullet now
WritingMode bulletWM = aBulletFrame->GetWritingMode();
LogicalSize availSize(bulletWM);
// Make up an inline-size since it doesn't really matter (XXX).
availSize.ISize(bulletWM) = aState.ContentISize();
availSize.BSize(bulletWM) = NS_UNCONSTRAINEDSIZE;
// Get the reason right.
// XXXwaterson Should this look just like the logic in
// nsBlockReflowContext::ReflowBlock and nsLineLayout::ReflowFrame?
ReflowInput reflowInput(aState.mPresContext, ri,
aBulletFrame, availSize);
nsReflowStatus status;
aBulletFrame->Reflow(aState.mPresContext, aMetrics, reflowInput, status);
// Get the float available space using our saved state from before we
// started reflowing the block, so that we ignore any floats inside
// the block.
// FIXME: aLineTop isn't actually set correctly by some callers, since
// they reposition the line.
LogicalRect floatAvailSpace =
aState.GetFloatAvailableSpaceWithState(aLineTop, ShapeType::ShapeOutside,
&aState.mFloatManagerStateBefore)
.mRect;
// FIXME (bug 25888): need to check the entire region that the first
// line overlaps, not just the top pixel.
// Place the bullet now. We want to place the bullet relative to the
// border-box of the associated block (using the right/left margin of
// the bullet frame as separation). However, if a line box would be
// displaced by floats that are *outside* the associated block, we
// want to displace it by the same amount. That is, we act as though
// the edge of the floats is the content-edge of the block, and place
// the bullet at a position offset from there by the block's padding,
// the block's border, and the bullet frame's margin.
// IStart from floatAvailSpace gives us the content/float start edge
// in the current writing mode. Then we subtract out the start
// border/padding and the bullet's width and margin to offset the position.
WritingMode wm = ri.GetWritingMode();
// Get the bullet's margin, converted to our writing mode so that we can
// combine it with other logical values here.
LogicalMargin bulletMargin =
reflowInput.ComputedLogicalMargin().ConvertTo(wm, bulletWM);
nscoord iStart = floatAvailSpace.IStart(wm) -
ri.ComputedLogicalBorderPadding().IStart(wm) -
bulletMargin.IEnd(wm) -
aMetrics.ISize(wm);
// Approximate the bullets position; vertical alignment will provide
// the final vertical location. We pass our writing-mode here, because
// it may be different from the bullet frame's mode.
nscoord bStart = floatAvailSpace.BStart(wm);
aBulletFrame->SetRect(wm, LogicalRect(wm, iStart, bStart,
aMetrics.ISize(wm),
aMetrics.BSize(wm)),
aState.ContainerSize());
aBulletFrame->DidReflow(aState.mPresContext, &aState.mReflowInput);
}
// This is used to scan frames for any float placeholders, add their
// floats to the list represented by aList, and remove the
// floats from whatever list they might be in. We don't search descendants
// that are float containing blocks. Floats that or not children of 'this'
// are ignored (they are not added to aList).
void
nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList,
bool aCollectSiblings)
{
while (aFrame) {
// Don't descend into float containing blocks.
if (!aFrame->IsFloatContainingBlock()) {
nsIFrame* outOfFlowFrame =
aFrame->IsPlaceholderFrame()
? nsLayoutUtils::GetFloatFromPlaceholder(aFrame)
: nullptr;
while (outOfFlowFrame && outOfFlowFrame->GetParent() == this) {
RemoveFloat(outOfFlowFrame);
// Remove the IS_PUSHED_FLOAT bit, in case |outOfFlowFrame| came from
// the PushedFloats list.
outOfFlowFrame->RemoveStateBits(NS_FRAME_IS_PUSHED_FLOAT);
aList.AppendFrame(nullptr, outOfFlowFrame);
outOfFlowFrame = outOfFlowFrame->GetNextInFlow();
// FIXME: By not pulling floats whose parent is one of our
// later siblings, are we risking the pushed floats getting
// out-of-order?
// XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that.
}
DoCollectFloats(aFrame->PrincipalChildList().FirstChild(), aList, true);
DoCollectFloats(aFrame->GetChildList(kOverflowList).FirstChild(), aList, true);
}
if (!aCollectSiblings)
break;
aFrame = aFrame->GetNextSibling();
}
}
void
nsBlockFrame::CheckFloats(BlockReflowInput& aState)
{
#ifdef DEBUG
// If any line is still dirty, that must mean we're going to reflow this
// block again soon (e.g. because we bailed out after noticing that
// clearance was imposed), so don't worry if the floats are out of sync.
bool anyLineDirty = false;
// Check that the float list is what we would have built
AutoTArray<nsIFrame*, 8> lineFloats;
for (LineIterator line = LinesBegin(), line_end = LinesEnd();
line != line_end; ++line) {
if (line->HasFloats()) {
nsFloatCache* fc = line->GetFirstFloat();
while (fc) {
lineFloats.AppendElement(fc->mFloat);
fc = fc->Next();
}
}
if (line->IsDirty()) {
anyLineDirty = true;
}
}
AutoTArray<nsIFrame*, 8> storedFloats;
bool equal = true;
uint32_t i = 0;
for (nsIFrame* f : mFloats) {
if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT)
continue;
storedFloats.AppendElement(f);
if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) {
equal = false;
}
++i;
}
if ((!equal || lineFloats.Length() != storedFloats.Length()) && !anyLineDirty) {
NS_WARNING("nsBlockFrame::CheckFloats: Explicit float list is out of sync with float cache");
#if defined(DEBUG_roc)
nsFrame::RootFrameList(PresContext(), stdout, 0);
for (i = 0; i < lineFloats.Length(); ++i) {
printf("Line float: %p\n", lineFloats.ElementAt(i));
}
for (i = 0; i < storedFloats.Length(); ++i) {
printf("Stored float: %p\n", storedFloats.ElementAt(i));
}
#endif
}
#endif
const nsFrameList* oofs = GetOverflowOutOfFlows();
if (oofs && oofs->NotEmpty()) {
// Floats that were pushed should be removed from our float
// manager. Otherwise the float manager's YMost or XMost might
// be larger than necessary, causing this block to get an
// incorrect desired height (or width). Some of these floats
// may not actually have been added to the float manager because
// they weren't reflowed before being pushed; that's OK,
// RemoveRegions will ignore them. It is safe to do this here
// because we know from here on the float manager will only be
// used for its XMost and YMost, not to place new floats and
// lines.
aState.FloatManager()->RemoveTrailingRegions(oofs->FirstChild());
}
}
void
nsBlockFrame::IsMarginRoot(bool* aBStartMarginRoot, bool* aBEndMarginRoot)
{
if (!(GetStateBits() & NS_BLOCK_MARGIN_ROOT)) {
nsIFrame* parent = GetParent();
if (!parent || parent->IsFloatContainingBlock()) {
*aBStartMarginRoot = false;
*aBEndMarginRoot = false;
return;
}
if (parent->IsColumnSetFrame()) {
*aBStartMarginRoot = GetPrevInFlow() == nullptr;
*aBEndMarginRoot = GetNextInFlow() == nullptr;
return;
}
}
*aBStartMarginRoot = true;
*aBEndMarginRoot = true;
}
/* static */
bool
nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock)
{
NS_PRECONDITION(aBlock, "Must have a frame");
NS_ASSERTION(nsLayoutUtils::GetAsBlock(aBlock), "aBlock must be a block");
nsIFrame* parent = aBlock->GetParent();
return (aBlock->GetStateBits() & NS_BLOCK_FLOAT_MGR) ||
(parent && !parent->IsFloatContainingBlock());
}
/* static */
bool
nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame)
{
return aFrame->IsFrameOfType(nsIFrame::eBlockFrame) &&
!aFrame->IsFrameOfType(nsIFrame::eReplaced) &&
!(aFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR);
}
// Note that this width can vary based on the vertical position.
// However, the cases where it varies are the cases where the width fits
// in the available space given, which means that variation shouldn't
// matter.
/* static */
nsBlockFrame::ReplacedElementISizeToClear
nsBlockFrame::ISizeToClearPastFloats(const BlockReflowInput& aState,
const LogicalRect& aFloatAvailableSpace,
nsIFrame* aFrame)
{
nscoord inlineStartOffset, inlineEndOffset;
WritingMode wm = aState.mReflowInput.GetWritingMode();
SizeComputationInput offsetState(aFrame, aState.mReflowInput.mRenderingContext,
wm, aState.mContentArea.ISize(wm));
ReplacedElementISizeToClear result;
aState.ComputeReplacedBlockOffsetsForFloats(aFrame, aFloatAvailableSpace,
inlineStartOffset,
inlineEndOffset);
nscoord availISize = aState.mContentArea.ISize(wm) -
inlineStartOffset - inlineEndOffset;
// We actually don't want the min width here; see bug 427782; we only
// want to displace if the width won't compute to a value small enough
// to fit.
// All we really need here is the result of ComputeSize, and we
// could *almost* get that from an SizeComputationInput, except for the
// last argument.
WritingMode frWM = aFrame->GetWritingMode();
LogicalSize availSpace = LogicalSize(wm, availISize, NS_UNCONSTRAINEDSIZE).
ConvertTo(frWM, wm);
ReflowInput reflowInput(aState.mPresContext, aState.mReflowInput,
aFrame, availSpace);
result.borderBoxISize =
reflowInput.ComputedSizeWithBorderPadding().ConvertTo(wm, frWM).ISize(wm);
// Use the margins from offsetState rather than reflowInput so that
// they aren't reduced by ignoring margins in overconstrained cases.
LogicalMargin computedMargin =
offsetState.ComputedLogicalMargin().ConvertTo(wm, frWM);
result.marginIStart = computedMargin.IStart(wm);
return result;
}
/* static */
nsBlockFrame*
nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate)
{
nsBlockFrame* block = nullptr;
while(aCandidate) {
block = nsLayoutUtils::GetAsBlock(aCandidate);
if (block) {
// yay, candidate is a block!
return block;
}
// Not a block. Check its parent next.
aCandidate = aCandidate->GetParent();
}
NS_NOTREACHED("Fell off frame tree looking for ancestor block!");
return nullptr;
}
void
nsBlockFrame::ComputeFinalBSize(const ReflowInput& aReflowInput,
nsReflowStatus* aStatus,
nscoord aContentBSize,
const LogicalMargin& aBorderPadding,
LogicalSize& aFinalSize,
nscoord aConsumed)
{
WritingMode wm = aReflowInput.GetWritingMode();
// Figure out how much of the computed height should be
// applied to this frame.
nscoord computedBSizeLeftOver = GetEffectiveComputedBSize(aReflowInput,
aConsumed);
NS_ASSERTION(!( IS_TRUE_OVERFLOW_CONTAINER(this)
&& computedBSizeLeftOver ),
"overflow container must not have computedBSizeLeftOver");
aFinalSize.BSize(wm) =
NSCoordSaturatingAdd(NSCoordSaturatingAdd(aBorderPadding.BStart(wm),
computedBSizeLeftOver),
aBorderPadding.BEnd(wm));
if (aStatus->IsIncomplete() &&
aFinalSize.BSize(wm) <= aReflowInput.AvailableBSize()) {
// We ran out of height on this page but we're incomplete.
// Set status to complete except for overflow.
aStatus->SetOverflowIncomplete();
}
if (aStatus->IsComplete()) {
if (computedBSizeLeftOver > 0 &&
NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableBSize() &&
aFinalSize.BSize(wm) > aReflowInput.AvailableBSize()) {
if (ShouldAvoidBreakInside(aReflowInput)) {
aStatus->SetInlineLineBreakBeforeAndReset();
return;
}
// We don't fit and we consumed some of the computed height,
// so we should consume all the available height and then
// break. If our bottom border/padding straddles the break
// point, then this will increase our height and push the
// border/padding to the next page/column.
aFinalSize.BSize(wm) = std::max(aReflowInput.AvailableBSize(),
aContentBSize);
aStatus->SetIncomplete();
if (!GetNextInFlow())
aStatus->SetNextInFlowNeedsReflow();
}
}
}
nsresult
nsBlockFrame::ResolveBidi()
{
NS_ASSERTION(!GetPrevInFlow(),
"ResolveBidi called on non-first continuation");
nsPresContext* presContext = PresContext();
if (!presContext->BidiEnabled()) {
return NS_OK;
}
return nsBidiPresUtils::Resolve(this);
}
void
nsBlockFrame::UpdatePseudoElementStyles(ServoRestyleState& aRestyleState)
{
// first-letter needs to be updated before first-line, because first-line can
// change the style of the first-letter.
if (HasFirstLetterChild()) {
UpdateFirstLetterStyle(aRestyleState);
}
if (nsBulletFrame* bullet = GetBullet()) {
CSSPseudoElementType type = bullet->StyleContext()->GetPseudoType();
RefPtr<nsStyleContext> newBulletStyle =
ResolveBulletStyle(type, &aRestyleState.StyleSet());
UpdateStyleOfOwnedChildFrame(bullet, newBulletStyle, aRestyleState);
}
if (nsIFrame* firstLineFrame = GetFirstLineFrame()) {
nsIFrame* styleParent =
CorrectStyleParentFrame(firstLineFrame->GetParent(),
nsCSSPseudoElements::firstLine);
ServoStyleContext* parentStyle = styleParent->StyleContext()->AsServo();
RefPtr<ServoStyleContext> firstLineStyle =
aRestyleState.StyleSet()
.ResolvePseudoElementStyle(mContent->AsElement(),
CSSPseudoElementType::firstLine,
parentStyle,
nullptr);
// FIXME(bz): Can we make first-line continuations be non-inheriting anon
// boxes?
RefPtr<ServoStyleContext> continuationStyle = aRestyleState.StyleSet().
ResolveInheritingAnonymousBoxStyle(nsCSSAnonBoxes::mozLineFrame,
parentStyle);
UpdateStyleOfOwnedChildFrame(firstLineFrame, firstLineStyle, aRestyleState,
Some(continuationStyle.get()));
// We also want to update the styles of the first-line's descendants. We
// don't need to compute a changehint for this, though, since any changes to
// them are handled by the first-line anyway.
ServoRestyleManager* manager = PresContext()->RestyleManager()->AsServo();
for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
manager->ReparentStyleContext(kid);
}
}
}
already_AddRefed<nsStyleContext>
nsBlockFrame::ResolveBulletStyle(CSSPseudoElementType aType,
StyleSetHandle aStyleSet)
{
nsStyleContext* parentStyle =
CorrectStyleParentFrame(this,
nsCSSPseudoElements::GetPseudoAtom(aType))->
StyleContext();
return aStyleSet->ResolvePseudoElementStyle(mContent->AsElement(), aType,
parentStyle, nullptr);
}
nsIFrame*
nsBlockFrame::GetFirstLetter() const
{
if (!(GetStateBits() & NS_BLOCK_HAS_FIRST_LETTER_STYLE)) {
// Certainly no first-letter frame.
return nullptr;
}
return GetProperty(FirstLetterProperty());
}
nsIFrame*
nsBlockFrame::GetFirstLineFrame() const
{
// Our ::first-line frame is either the first thing on our principal child
// list, or the second one if we have an inside bullet.
nsIFrame* bullet = GetInsideBullet();
nsIFrame* maybeFirstLine;
if (bullet) {
maybeFirstLine = bullet->GetNextSibling();
} else {
maybeFirstLine = PrincipalChildList().FirstChild();
}
if (maybeFirstLine && maybeFirstLine->IsLineFrame()) {
return maybeFirstLine;
}
return nullptr;
}
#ifdef DEBUG
void
nsBlockFrame::VerifyLines(bool aFinalCheckOK)
{
if (!gVerifyLines) {
return;
}
if (mLines.empty()) {
return;
}
nsLineBox* cursor = GetLineCursor();
// Add up the counts on each line. Also validate that IsFirstLine is
// set properly.
int32_t count = 0;
LineIterator line, line_end;
for (line = LinesBegin(), line_end = LinesEnd();
line != line_end;
++line) {
if (line == cursor) {
cursor = nullptr;
}
if (aFinalCheckOK) {
MOZ_ASSERT(line->GetChildCount(), "empty line");
if (line->IsBlock()) {
NS_ASSERTION(1 == line->GetChildCount(), "bad first line");
}
}
count += line->GetChildCount();
}
// Then count the frames
int32_t frameCount = 0;
nsIFrame* frame = mLines.front()->mFirstChild;
while (frame) {
frameCount++;
frame = frame->GetNextSibling();
}
NS_ASSERTION(count == frameCount, "bad line list");
// Next: test that each line has right number of frames on it
for (line = LinesBegin(), line_end = LinesEnd();
line != line_end;
) {
count = line->GetChildCount();
frame = line->mFirstChild;
while (--count >= 0) {
frame = frame->GetNextSibling();
}
++line;
if ((line != line_end) && (0 != line->GetChildCount())) {
NS_ASSERTION(frame == line->mFirstChild, "bad line list");
}
}
if (cursor) {
FrameLines* overflowLines = GetOverflowLines();
if (overflowLines) {
LineIterator line = overflowLines->mLines.begin();
LineIterator line_end = overflowLines->mLines.end();
for (; line != line_end; ++line) {
if (line == cursor) {
cursor = nullptr;
break;
}
}
}
}
NS_ASSERTION(!cursor, "stale LineCursorProperty");
}
void
nsBlockFrame::VerifyOverflowSituation()
{
// Overflow out-of-flows must not have a next-in-flow in mFloats or mFrames.
nsFrameList* oofs = GetOverflowOutOfFlows() ;
if (oofs) {
for (nsFrameList::Enumerator e(*oofs); !e.AtEnd(); e.Next()) {
nsIFrame* nif = e.get()->GetNextInFlow();
MOZ_ASSERT(!nif || (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
}
}
// Pushed floats must not have a next-in-flow in mFloats or mFrames.
oofs = GetPushedFloats();
if (oofs) {
for (nsFrameList::Enumerator e(*oofs); !e.AtEnd(); e.Next()) {
nsIFrame* nif = e.get()->GetNextInFlow();
MOZ_ASSERT(!nif || (!mFloats.ContainsFrame(nif) && !mFrames.ContainsFrame(nif)));
}
}
// A child float next-in-flow's parent must be |this| or a next-in-flow of |this|.
// Later next-in-flows must have the same or later parents.
nsIFrame::ChildListID childLists[] = { nsIFrame::kFloatList,
nsIFrame::kPushedFloatsList };
for (size_t i = 0; i < ArrayLength(childLists); ++i) {
nsFrameList children(GetChildList(childLists[i]));
for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
nsIFrame* parent = this;
nsIFrame* nif = e.get()->GetNextInFlow();
for (; nif; nif = nif->GetNextInFlow()) {
bool found = false;
for (nsIFrame* p = parent; p; p = p->GetNextInFlow()) {
if (nif->GetParent() == p) {
parent = p;
found = true;
break;
}
}
MOZ_ASSERT(found, "next-in-flow is a child of parent earlier in the frame tree?");
}
}
}
nsBlockFrame* flow = static_cast<nsBlockFrame*>(FirstInFlow());
while (flow) {
FrameLines* overflowLines = flow->GetOverflowLines();
if (overflowLines) {
NS_ASSERTION(!overflowLines->mLines.empty(),
"should not be empty if present");
NS_ASSERTION(overflowLines->mLines.front()->mFirstChild,
"bad overflow lines");
NS_ASSERTION(overflowLines->mLines.front()->mFirstChild ==
overflowLines->mFrames.FirstChild(),
"bad overflow frames / lines");
}
nsLineBox* cursor = flow->GetLineCursor();
if (cursor) {
LineIterator line = flow->LinesBegin();
LineIterator line_end = flow->LinesEnd();
for (; line != line_end && line != cursor; ++line)
;
if (line == line_end && overflowLines) {
line = overflowLines->mLines.begin();
line_end = overflowLines->mLines.end();
for (; line != line_end && line != cursor; ++line)
;
}
MOZ_ASSERT(line != line_end, "stale LineCursorProperty");
}
flow = static_cast<nsBlockFrame*>(flow->GetNextInFlow());
}
}
int32_t
nsBlockFrame::GetDepth() const
{
int32_t depth = 0;
nsIFrame* parent = GetParent();
while (parent) {
parent = parent->GetParent();
depth++;
}
return depth;
}
already_AddRefed<nsStyleContext>
nsBlockFrame::GetFirstLetterStyle(nsPresContext* aPresContext)
{
return aPresContext->StyleSet()->
ProbePseudoElementStyle(mContent->AsElement(),
CSSPseudoElementType::firstLetter,
mStyleContext);
}
#endif