michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: // vim:cindent:ts=2:et:sw=2: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * rendering object for CSS display:block, inline-block, and list-item michael@0: * boxes, also used for various anonymous boxes michael@0: */ michael@0: michael@0: #include "nsBlockFrame.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsAbsoluteContainingBlock.h" michael@0: #include "nsBlockReflowContext.h" michael@0: #include "nsBlockReflowState.h" michael@0: #include "nsBulletFrame.h" michael@0: #include "nsLineBox.h" michael@0: #include "nsLineLayout.h" michael@0: #include "nsPlaceholderFrame.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsFrameManager.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsHTMLParts.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsGenericHTMLElement.h" michael@0: #include "nsAttrValueInlines.h" michael@0: #include "prprf.h" michael@0: #include "nsFloatManager.h" michael@0: #include "prenv.h" michael@0: #include "plstr.h" michael@0: #include "nsError.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsIDOMHTMLDocument.h" michael@0: #endif michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "TextOverflow.h" michael@0: #include "nsIFrameInlines.h" michael@0: michael@0: #include "nsBidiPresUtils.h" michael@0: michael@0: static const int MIN_LINES_NEEDING_CURSOR = 20; michael@0: michael@0: static const char16_t kDiscCharacter = 0x2022; michael@0: static const char16_t kCircleCharacter = 0x25e6; michael@0: static const char16_t kSquareCharacter = 0x25aa; michael@0: michael@0: #define DISABLE_FLOAT_BREAKING_IN_COLUMNS michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::css; michael@0: using namespace mozilla::layout; michael@0: michael@0: #ifdef DEBUG michael@0: #include "nsBlockDebugFlags.h" michael@0: michael@0: bool nsBlockFrame::gLamePaintMetrics; michael@0: bool nsBlockFrame::gLameReflowMetrics; michael@0: bool nsBlockFrame::gNoisy; michael@0: bool nsBlockFrame::gNoisyDamageRepair; michael@0: bool nsBlockFrame::gNoisyIntrinsic; michael@0: bool nsBlockFrame::gNoisyReflow; michael@0: bool nsBlockFrame::gReallyNoisyReflow; michael@0: bool nsBlockFrame::gNoisyFloatManager; michael@0: bool nsBlockFrame::gVerifyLines; michael@0: bool nsBlockFrame::gDisableResizeOpt; michael@0: michael@0: int32_t nsBlockFrame::gNoiseIndent; michael@0: michael@0: struct BlockDebugFlags { michael@0: const char* name; michael@0: bool* on; michael@0: }; michael@0: michael@0: static const BlockDebugFlags gFlags[] = { michael@0: { "reflow", &nsBlockFrame::gNoisyReflow }, michael@0: { "really-noisy-reflow", &nsBlockFrame::gReallyNoisyReflow }, michael@0: { "intrinsic", &nsBlockFrame::gNoisyIntrinsic }, michael@0: { "float-manager", &nsBlockFrame::gNoisyFloatManager }, michael@0: { "verify-lines", &nsBlockFrame::gVerifyLines }, michael@0: { "damage-repair", &nsBlockFrame::gNoisyDamageRepair }, michael@0: { "lame-paint-metrics", &nsBlockFrame::gLamePaintMetrics }, michael@0: { "lame-reflow-metrics", &nsBlockFrame::gLameReflowMetrics }, michael@0: { "disable-resize-opt", &nsBlockFrame::gDisableResizeOpt }, michael@0: }; michael@0: #define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0])) michael@0: michael@0: static void michael@0: ShowDebugFlags() michael@0: { michael@0: printf("Here are the available GECKO_BLOCK_DEBUG_FLAGS:\n"); michael@0: const BlockDebugFlags* bdf = gFlags; michael@0: const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; michael@0: for (; bdf < end; bdf++) { michael@0: printf(" %s\n", bdf->name); michael@0: } michael@0: printf("Note: GECKO_BLOCK_DEBUG_FLAGS is a comma separated list of flag\n"); michael@0: printf("names (no whitespace)\n"); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::InitDebugFlags() michael@0: { michael@0: static bool firstTime = true; michael@0: if (firstTime) { michael@0: firstTime = false; michael@0: char* flags = PR_GetEnv("GECKO_BLOCK_DEBUG_FLAGS"); michael@0: if (flags) { michael@0: bool error = false; michael@0: for (;;) { michael@0: char* cm = PL_strchr(flags, ','); michael@0: if (cm) *cm = '\0'; michael@0: michael@0: bool found = false; michael@0: const BlockDebugFlags* bdf = gFlags; michael@0: const BlockDebugFlags* end = gFlags + NUM_DEBUG_FLAGS; michael@0: for (; bdf < end; bdf++) { michael@0: if (PL_strcasecmp(bdf->name, flags) == 0) { michael@0: *(bdf->on) = true; michael@0: printf("nsBlockFrame: setting %s debug flag on\n", bdf->name); michael@0: gNoisy = true; michael@0: found = true; michael@0: break; michael@0: } michael@0: } michael@0: if (!found) { michael@0: error = true; michael@0: } michael@0: michael@0: if (!cm) break; michael@0: *cm = ','; michael@0: flags = cm + 1; michael@0: } michael@0: if (error) { michael@0: ShowDebugFlags(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: #endif michael@0: michael@0: // add in a sanity check for absurdly deep frame trees. See bug 42138 michael@0: // can't just use IsFrameTreeTooDeep() because that method has side effects we don't want michael@0: #define MAX_DEPTH_FOR_LIST_RENUMBERING 200 // 200 open displayable tags is pretty unrealistic michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Debugging support code michael@0: michael@0: #ifdef DEBUG michael@0: const char* nsBlockFrame::kReflowCommandType[] = { michael@0: "ContentChanged", michael@0: "StyleChanged", michael@0: "ReflowDirty", michael@0: "Timeout", michael@0: "UserDefined", michael@0: }; michael@0: #endif michael@0: michael@0: #ifdef REALLY_NOISY_FIRST_LINE michael@0: static void michael@0: DumpStyleGeneaology(nsIFrame* aFrame, const char* gap) michael@0: { michael@0: fputs(gap, stdout); michael@0: nsFrame::ListTag(stdout, aFrame); michael@0: printf(": "); michael@0: nsStyleContext* sc = aFrame->StyleContext(); michael@0: while (nullptr != sc) { michael@0: nsStyleContext* psc; michael@0: printf("%p ", sc); michael@0: psc = sc->GetParent(); michael@0: sc = psc; michael@0: } michael@0: printf("\n"); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef REFLOW_STATUS_COVERAGE michael@0: static void michael@0: RecordReflowStatus(bool aChildIsBlock, nsReflowStatus aFrameReflowStatus) michael@0: { michael@0: static uint32_t record[2]; michael@0: michael@0: // 0: child-is-block michael@0: // 1: child-is-inline michael@0: int index = 0; michael@0: if (!aChildIsBlock) index |= 1; michael@0: michael@0: // Compute new status michael@0: uint32_t newS = record[index]; michael@0: if (NS_INLINE_IS_BREAK(aFrameReflowStatus)) { michael@0: if (NS_INLINE_IS_BREAK_BEFORE(aFrameReflowStatus)) { michael@0: newS |= 1; michael@0: } michael@0: else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) { michael@0: newS |= 2; michael@0: } michael@0: else { michael@0: newS |= 4; michael@0: } michael@0: } michael@0: else if (NS_FRAME_IS_NOT_COMPLETE(aFrameReflowStatus)) { michael@0: newS |= 8; michael@0: } michael@0: else { michael@0: newS |= 16; michael@0: } michael@0: michael@0: // Log updates to the status that yield different values michael@0: if (record[index] != newS) { michael@0: record[index] = newS; michael@0: printf("record(%d): %02x %02x\n", index, record[0], record[1]); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Destructor function for the overflowLines frame property michael@0: static void michael@0: DestroyOverflowLines(void* aPropertyValue) michael@0: { michael@0: NS_ERROR("Overflow lines should never be destroyed by the FramePropertyTable"); michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(OverflowLinesProperty, DestroyOverflowLines) michael@0: NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OverflowOutOfFlowsProperty) michael@0: NS_DECLARE_FRAME_PROPERTY_FRAMELIST(PushedFloatProperty) michael@0: NS_DECLARE_FRAME_PROPERTY_FRAMELIST(OutsideBulletProperty) michael@0: NS_DECLARE_FRAME_PROPERTY(InsideBulletProperty, nullptr) michael@0: NS_DECLARE_FRAME_PROPERTY(BottomEdgeOfChildrenProperty, nullptr) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: nsIFrame* michael@0: NS_NewBlockFrame(nsIPresShell* aPresShell, nsStyleContext* aContext, nsFrameState aFlags) michael@0: { michael@0: nsBlockFrame* it = new (aPresShell) nsBlockFrame(aContext); michael@0: it->SetFlags(aFlags); michael@0: return it; michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsBlockFrame) michael@0: michael@0: nsBlockFrame::~nsBlockFrame() michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: ClearLineCursor(); michael@0: DestroyAbsoluteFrames(aDestructRoot); michael@0: mFloats.DestroyFramesFrom(aDestructRoot); michael@0: nsPresContext* presContext = PresContext(); michael@0: nsIPresShell* shell = presContext->PresShell(); michael@0: nsLineBox::DeleteLineList(presContext, mLines, aDestructRoot, michael@0: &mFrames); michael@0: michael@0: FramePropertyTable* props = presContext->PropertyTable(); michael@0: michael@0: if (HasPushedFloats()) { michael@0: SafelyDestroyFrameListProp(aDestructRoot, shell, props, michael@0: PushedFloatProperty()); michael@0: RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); michael@0: } michael@0: michael@0: // destroy overflow lines now michael@0: FrameLines* overflowLines = RemoveOverflowLines(); michael@0: if (overflowLines) { michael@0: nsLineBox::DeleteLineList(presContext, overflowLines->mLines, michael@0: aDestructRoot, &overflowLines->mFrames); michael@0: delete overflowLines; michael@0: } michael@0: michael@0: if (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) { michael@0: SafelyDestroyFrameListProp(aDestructRoot, shell, props, michael@0: OverflowOutOfFlowsProperty()); michael@0: RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); michael@0: } michael@0: michael@0: if (HasOutsideBullet()) { michael@0: SafelyDestroyFrameListProp(aDestructRoot, shell, props, michael@0: OutsideBulletProperty()); michael@0: RemoveStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); michael@0: } michael@0: michael@0: nsBlockFrameSuper::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: /* virtual */ nsILineIterator* michael@0: nsBlockFrame::GetLineIterator() michael@0: { michael@0: nsLineIterator* it = new nsLineIterator; michael@0: if (!it) michael@0: return nullptr; michael@0: michael@0: const nsStyleVisibility* visibility = StyleVisibility(); michael@0: nsresult rv = it->Init(mLines, visibility->mDirection == NS_STYLE_DIRECTION_RTL); michael@0: if (NS_FAILED(rv)) { michael@0: delete it; michael@0: return nullptr; michael@0: } michael@0: return it; michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsBlockFrame) michael@0: NS_QUERYFRAME_ENTRY(nsBlockFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrameSuper) michael@0: michael@0: nsSplittableType michael@0: nsBlockFrame::GetSplittableType() const michael@0: { michael@0: return NS_FRAME_SPLITTABLE_NON_RECTANGULAR; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: void michael@0: nsBlockFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const michael@0: { michael@0: nsCString str; michael@0: ListGeneric(str, aPrefix, aFlags); michael@0: michael@0: fprintf_stderr(out, "%s<\n", str.get()); michael@0: michael@0: nsCString pfx(aPrefix); michael@0: pfx += " "; michael@0: michael@0: // Output the lines michael@0: if (!mLines.empty()) { michael@0: const_line_iterator line = begin_lines(), line_end = end_lines(); michael@0: for ( ; line != line_end; ++line) { michael@0: line->List(out, pfx.get(), aFlags); michael@0: } michael@0: } michael@0: michael@0: // Output the overflow lines. michael@0: const FrameLines* overflowLines = GetOverflowLines(); michael@0: if (overflowLines && !overflowLines->mLines.empty()) { michael@0: fprintf_stderr(out, "%sOverflow-lines %p/%p <\n", pfx.get(), overflowLines, &overflowLines->mFrames); michael@0: nsCString nestedPfx(pfx); michael@0: nestedPfx += " "; michael@0: const_line_iterator line = overflowLines->mLines.begin(), michael@0: line_end = overflowLines->mLines.end(); michael@0: for ( ; line != line_end; ++line) { michael@0: line->List(out, nestedPfx.get(), aFlags); michael@0: } michael@0: fprintf_stderr(out, "%s>\n", pfx.get()); michael@0: } michael@0: michael@0: // skip the principal list - we printed the lines above michael@0: // skip the overflow list - we printed the overflow lines above michael@0: ChildListIterator lists(this); michael@0: ChildListIDs skip(kPrincipalList | kOverflowList); michael@0: for (; !lists.IsDone(); lists.Next()) { michael@0: if (skip.Contains(lists.CurrentID())) { michael@0: continue; michael@0: } michael@0: fprintf_stderr(out, "%s%s %p <\n", pfx.get(), michael@0: mozilla::layout::ChildListName(lists.CurrentID()), michael@0: &GetChildList(lists.CurrentID())); michael@0: nsCString nestedPfx(pfx); michael@0: nestedPfx += " "; michael@0: nsFrameList::Enumerator childFrames(lists.CurrentList()); michael@0: for (; !childFrames.AtEnd(); childFrames.Next()) { michael@0: nsIFrame* kid = childFrames.get(); michael@0: kid->List(out, nestedPfx.get(), aFlags); michael@0: } michael@0: fprintf_stderr(out, "%s>\n", pfx.get()); michael@0: } michael@0: michael@0: fprintf_stderr(out, "%s>\n", aPrefix); michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: return MakeFrameName(NS_LITERAL_STRING("Block"), aResult); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: nsFrameState michael@0: nsBlockFrame::GetDebugStateBits() const michael@0: { michael@0: // We don't want to include our cursor flag in the bits the michael@0: // regression tester looks at michael@0: return nsBlockFrameSuper::GetDebugStateBits() & ~NS_BLOCK_HAS_LINE_CURSOR; michael@0: } michael@0: #endif michael@0: michael@0: nsIAtom* michael@0: nsBlockFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::blockFrame; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::InvalidateFrame(uint32_t aDisplayItemKey) michael@0: { michael@0: if (IsSVGText()) { michael@0: NS_ASSERTION(GetParent()->GetType() == nsGkAtoms::svgTextFrame, michael@0: "unexpected block frame in SVG text"); michael@0: GetParent()->InvalidateFrame(); michael@0: return; michael@0: } michael@0: nsBlockFrameSuper::InvalidateFrame(aDisplayItemKey); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) michael@0: { michael@0: if (IsSVGText()) { michael@0: NS_ASSERTION(GetParent()->GetType() == nsGkAtoms::svgTextFrame, michael@0: "unexpected block frame in SVG text"); michael@0: GetParent()->InvalidateFrame(); michael@0: return; michael@0: } michael@0: nsBlockFrameSuper::InvalidateFrameWithRect(aRect, aDisplayItemKey); michael@0: } michael@0: michael@0: nscoord michael@0: nsBlockFrame::GetBaseline() const michael@0: { michael@0: nscoord result; michael@0: if (nsLayoutUtils::GetLastLineBaseline(this, &result)) michael@0: return result; michael@0: return nsFrame::GetBaseline(); michael@0: } michael@0: michael@0: nscoord michael@0: nsBlockFrame::GetCaretBaseline() const michael@0: { michael@0: nsRect contentRect = GetContentRect(); michael@0: nsMargin bp = GetUsedBorderAndPadding(); michael@0: michael@0: if (!mLines.empty()) { michael@0: const_line_iterator line = begin_lines(); michael@0: const nsLineBox* firstLine = line; michael@0: if (firstLine->GetChildCount()) { michael@0: return bp.top + firstLine->mFirstChild->GetCaretBaseline(); michael@0: } michael@0: } michael@0: nsRefPtr fm; michael@0: float inflation = nsLayoutUtils::FontSizeInflationFor(this); michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), inflation); michael@0: nscoord lineHeight = michael@0: nsHTMLReflowState::CalcLineHeight(GetContent(), StyleContext(), michael@0: contentRect.height, inflation); michael@0: return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight) + bp.top; michael@0: } michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////// michael@0: // Child frame enumeration michael@0: michael@0: const nsFrameList& michael@0: nsBlockFrame::GetChildList(ChildListID aListID) const michael@0: { michael@0: switch (aListID) { michael@0: case kPrincipalList: michael@0: return mFrames; michael@0: case kOverflowList: { michael@0: FrameLines* overflowLines = GetOverflowLines(); michael@0: return overflowLines ? overflowLines->mFrames : nsFrameList::EmptyList(); michael@0: } michael@0: case kFloatList: michael@0: return mFloats; michael@0: case kOverflowOutOfFlowList: { michael@0: const nsFrameList* list = GetOverflowOutOfFlows(); michael@0: return list ? *list : nsFrameList::EmptyList(); michael@0: } michael@0: case kPushedFloatsList: { michael@0: const nsFrameList* list = GetPushedFloats(); michael@0: return list ? *list : nsFrameList::EmptyList(); michael@0: } michael@0: case kBulletList: { michael@0: const nsFrameList* list = GetOutsideBulletList(); michael@0: return list ? *list : nsFrameList::EmptyList(); michael@0: } michael@0: default: michael@0: return nsContainerFrame::GetChildList(aListID); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::GetChildLists(nsTArray* aLists) const michael@0: { michael@0: nsContainerFrame::GetChildLists(aLists); michael@0: FrameLines* overflowLines = GetOverflowLines(); michael@0: if (overflowLines) { michael@0: overflowLines->mFrames.AppendIfNonempty(aLists, kOverflowList); michael@0: } michael@0: const nsFrameList* list = GetOverflowOutOfFlows(); michael@0: if (list) { michael@0: list->AppendIfNonempty(aLists, kOverflowOutOfFlowList); michael@0: } michael@0: mFloats.AppendIfNonempty(aLists, kFloatList); michael@0: list = GetOutsideBulletList(); michael@0: if (list) { michael@0: list->AppendIfNonempty(aLists, kBulletList); michael@0: } michael@0: list = GetPushedFloats(); michael@0: if (list) { michael@0: list->AppendIfNonempty(aLists, kPushedFloatsList); michael@0: } michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsBlockFrame::IsFloatContainingBlock() const michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: ReparentFrame(nsIFrame* aFrame, nsIFrame* aOldParent, nsIFrame* aNewParent) michael@0: { michael@0: NS_ASSERTION(aOldParent == aFrame->GetParent(), michael@0: "Parent not consistent with expectations"); michael@0: michael@0: aFrame->SetParent(aNewParent); michael@0: michael@0: // When pushing and pulling frames we need to check for whether any michael@0: // views need to be reparented michael@0: nsContainerFrame::ReparentFrameView(aFrame, aOldParent, aNewParent); michael@0: } michael@0: michael@0: static void michael@0: ReparentFrames(nsFrameList& aFrameList, nsIFrame* aOldParent, michael@0: nsIFrame* aNewParent) michael@0: { michael@0: for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) { michael@0: ReparentFrame(e.get(), aOldParent, aNewParent); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Remove the first line from aFromLines and adjust the associated frame list michael@0: * aFromFrames accordingly. The removed line is assigned to *aOutLine and michael@0: * a frame list with its frames is assigned to *aOutFrames, i.e. the frames michael@0: * that were extracted from the head of aFromFrames. michael@0: * aFromLines must contain at least one line, the line may be empty. michael@0: * @return true if aFromLines becomes empty michael@0: */ michael@0: static bool michael@0: RemoveFirstLine(nsLineList& aFromLines, nsFrameList& aFromFrames, michael@0: nsLineBox** aOutLine, nsFrameList* aOutFrames) michael@0: { michael@0: nsLineList_iterator removedLine = aFromLines.begin(); michael@0: *aOutLine = removedLine; michael@0: nsLineList_iterator next = aFromLines.erase(removedLine); michael@0: bool isLastLine = next == aFromLines.end(); michael@0: nsIFrame* lastFrame = isLastLine ? aFromFrames.LastChild() michael@0: : next->mFirstChild->GetPrevSibling(); michael@0: nsFrameList::FrameLinkEnumerator linkToBreak(aFromFrames, lastFrame); michael@0: *aOutFrames = aFromFrames.ExtractHead(linkToBreak); michael@0: return isLastLine; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////// michael@0: // Reflow methods michael@0: michael@0: /* virtual */ void michael@0: nsBlockFrame::MarkIntrinsicWidthsDirty() michael@0: { michael@0: nsBlockFrame* dirtyBlock = static_cast(FirstContinuation()); michael@0: dirtyBlock->mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: dirtyBlock->mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: if (!(GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION)) { michael@0: for (nsIFrame* frame = dirtyBlock; frame; michael@0: frame = frame->GetNextContinuation()) { michael@0: frame->AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); michael@0: } michael@0: } michael@0: michael@0: nsBlockFrameSuper::MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::CheckIntrinsicCacheAgainstShrinkWrapState() michael@0: { michael@0: nsPresContext *presContext = PresContext(); michael@0: if (!nsLayoutUtils::FontSizeInflationEnabled(presContext)) { michael@0: return; michael@0: } michael@0: bool inflationEnabled = michael@0: !presContext->mInflationDisabledForShrinkWrap; michael@0: if (inflationEnabled != michael@0: !!(GetStateBits() & NS_BLOCK_FRAME_INTRINSICS_INFLATED)) { michael@0: mMinWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: mPrefWidth = NS_INTRINSIC_WIDTH_UNKNOWN; michael@0: if (inflationEnabled) { michael@0: AddStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED); michael@0: } else { michael@0: RemoveStateBits(NS_BLOCK_FRAME_INTRINSICS_INFLATED); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsBlockFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nsIFrame* firstInFlow = FirstContinuation(); michael@0: if (firstInFlow != this) michael@0: return firstInFlow->GetMinWidth(aRenderingContext); michael@0: michael@0: DISPLAY_MIN_WIDTH(this, mMinWidth); michael@0: michael@0: CheckIntrinsicCacheAgainstShrinkWrapState(); michael@0: michael@0: if (mMinWidth != NS_INTRINSIC_WIDTH_UNKNOWN) michael@0: return mMinWidth; michael@0: michael@0: #ifdef DEBUG michael@0: if (gNoisyIntrinsic) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": GetMinWidth\n"); michael@0: } michael@0: AutoNoisyIndenter indenter(gNoisyIntrinsic); michael@0: #endif michael@0: michael@0: for (nsBlockFrame* curFrame = this; curFrame; michael@0: curFrame = static_cast(curFrame->GetNextContinuation())) { michael@0: curFrame->LazyMarkLinesDirty(); michael@0: } michael@0: michael@0: if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) michael@0: ResolveBidi(); michael@0: InlineMinWidthData data; michael@0: for (nsBlockFrame* curFrame = this; curFrame; michael@0: curFrame = static_cast(curFrame->GetNextContinuation())) { michael@0: for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines(); michael@0: line != line_end; ++line) michael@0: { michael@0: #ifdef DEBUG michael@0: if (gNoisyIntrinsic) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: printf("line (%s%s)\n", michael@0: line->IsBlock() ? "block" : "inline", michael@0: line->IsEmpty() ? ", empty" : ""); michael@0: } michael@0: AutoNoisyIndenter lineindent(gNoisyIntrinsic); michael@0: #endif michael@0: if (line->IsBlock()) { michael@0: data.ForceBreak(aRenderingContext); michael@0: data.currentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: line->mFirstChild, nsLayoutUtils::MIN_WIDTH); michael@0: data.ForceBreak(aRenderingContext); michael@0: } else { michael@0: if (!curFrame->GetPrevContinuation() && michael@0: line == curFrame->begin_lines()) { michael@0: // Only add text-indent if it has no percentages; using a michael@0: // percentage basis of 0 unconditionally would give strange michael@0: // behavior for calc(10%-3px). michael@0: const nsStyleCoord &indent = StyleText()->mTextIndent; michael@0: if (indent.ConvertsToLength()) michael@0: data.currentLine += nsRuleNode::ComputeCoordPercentCalc(indent, 0); michael@0: } michael@0: // XXX Bug NNNNNN Should probably handle percentage text-indent. michael@0: michael@0: data.line = &line; michael@0: data.lineContainer = curFrame; michael@0: nsIFrame *kid = line->mFirstChild; michael@0: for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; michael@0: ++i, kid = kid->GetNextSibling()) { michael@0: kid->AddInlineMinWidth(aRenderingContext, &data); michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: if (gNoisyIntrinsic) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: printf("min: [prevLines=%d currentLine=%d]\n", michael@0: data.prevLines, data.currentLine); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: data.ForceBreak(aRenderingContext); michael@0: michael@0: mMinWidth = data.prevLines; michael@0: return mMinWidth; michael@0: } michael@0: michael@0: /* virtual */ nscoord michael@0: nsBlockFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: nsIFrame* firstInFlow = FirstContinuation(); michael@0: if (firstInFlow != this) michael@0: return firstInFlow->GetPrefWidth(aRenderingContext); michael@0: michael@0: DISPLAY_PREF_WIDTH(this, mPrefWidth); michael@0: michael@0: CheckIntrinsicCacheAgainstShrinkWrapState(); michael@0: michael@0: if (mPrefWidth != NS_INTRINSIC_WIDTH_UNKNOWN) michael@0: return mPrefWidth; michael@0: michael@0: #ifdef DEBUG michael@0: if (gNoisyIntrinsic) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": GetPrefWidth\n"); michael@0: } michael@0: AutoNoisyIndenter indenter(gNoisyIntrinsic); michael@0: #endif michael@0: michael@0: for (nsBlockFrame* curFrame = this; curFrame; michael@0: curFrame = static_cast(curFrame->GetNextContinuation())) { michael@0: curFrame->LazyMarkLinesDirty(); michael@0: } michael@0: michael@0: if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) michael@0: ResolveBidi(); michael@0: InlinePrefWidthData data; michael@0: for (nsBlockFrame* curFrame = this; curFrame; michael@0: curFrame = static_cast(curFrame->GetNextContinuation())) { michael@0: for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines(); michael@0: line != line_end; ++line) michael@0: { michael@0: #ifdef DEBUG michael@0: if (gNoisyIntrinsic) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: printf("line (%s%s)\n", michael@0: line->IsBlock() ? "block" : "inline", michael@0: line->IsEmpty() ? ", empty" : ""); michael@0: } michael@0: AutoNoisyIndenter lineindent(gNoisyIntrinsic); michael@0: #endif michael@0: if (line->IsBlock()) { michael@0: data.ForceBreak(aRenderingContext); michael@0: data.currentLine = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: line->mFirstChild, nsLayoutUtils::PREF_WIDTH); michael@0: data.ForceBreak(aRenderingContext); michael@0: } else { michael@0: if (!curFrame->GetPrevContinuation() && michael@0: line == curFrame->begin_lines()) { michael@0: // Only add text-indent if it has no percentages; using a michael@0: // percentage basis of 0 unconditionally would give strange michael@0: // behavior for calc(10%-3px). michael@0: const nsStyleCoord &indent = StyleText()->mTextIndent; michael@0: if (indent.ConvertsToLength()) michael@0: data.currentLine += nsRuleNode::ComputeCoordPercentCalc(indent, 0); michael@0: } michael@0: // XXX Bug NNNNNN Should probably handle percentage text-indent. michael@0: michael@0: data.line = &line; michael@0: data.lineContainer = curFrame; michael@0: nsIFrame *kid = line->mFirstChild; michael@0: for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; michael@0: ++i, kid = kid->GetNextSibling()) { michael@0: kid->AddInlinePrefWidth(aRenderingContext, &data); michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: if (gNoisyIntrinsic) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: printf("pref: [prevLines=%d currentLine=%d]\n", michael@0: data.prevLines, data.currentLine); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: data.ForceBreak(aRenderingContext); michael@0: michael@0: mPrefWidth = data.prevLines; michael@0: return mPrefWidth; michael@0: } michael@0: michael@0: nsRect michael@0: nsBlockFrame::ComputeTightBounds(gfxContext* aContext) const michael@0: { michael@0: // be conservative michael@0: if (StyleContext()->HasTextDecorationLines()) { michael@0: return GetVisualOverflowRect(); michael@0: } michael@0: return ComputeSimpleTightBounds(aContext); michael@0: } michael@0: michael@0: /* virtual */ nsresult michael@0: nsBlockFrame::GetPrefWidthTightBounds(nsRenderingContext* aRenderingContext, michael@0: nscoord* aX, michael@0: nscoord* aXMost) michael@0: { michael@0: nsIFrame* firstInFlow = FirstContinuation(); michael@0: if (firstInFlow != this) { michael@0: return firstInFlow->GetPrefWidthTightBounds(aRenderingContext, aX, aXMost); michael@0: } michael@0: michael@0: *aX = 0; michael@0: *aXMost = 0; michael@0: michael@0: nsresult rv; michael@0: InlinePrefWidthData data; michael@0: for (nsBlockFrame* curFrame = this; curFrame; michael@0: curFrame = static_cast(curFrame->GetNextContinuation())) { michael@0: for (line_iterator line = curFrame->begin_lines(), line_end = curFrame->end_lines(); michael@0: line != line_end; ++line) michael@0: { michael@0: nscoord childX, childXMost; michael@0: if (line->IsBlock()) { michael@0: data.ForceBreak(aRenderingContext); michael@0: rv = line->mFirstChild->GetPrefWidthTightBounds(aRenderingContext, michael@0: &childX, &childXMost); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *aX = std::min(*aX, childX); michael@0: *aXMost = std::max(*aXMost, childXMost); michael@0: } else { michael@0: if (!curFrame->GetPrevContinuation() && michael@0: line == curFrame->begin_lines()) { michael@0: // Only add text-indent if it has no percentages; using a michael@0: // percentage basis of 0 unconditionally would give strange michael@0: // behavior for calc(10%-3px). michael@0: const nsStyleCoord &indent = StyleText()->mTextIndent; michael@0: if (indent.ConvertsToLength()) { michael@0: data.currentLine += nsRuleNode::ComputeCoordPercentCalc(indent, 0); michael@0: } michael@0: } michael@0: // XXX Bug NNNNNN Should probably handle percentage text-indent. michael@0: michael@0: data.line = &line; michael@0: data.lineContainer = curFrame; michael@0: nsIFrame *kid = line->mFirstChild; michael@0: for (int32_t i = 0, i_end = line->GetChildCount(); i != i_end; michael@0: ++i, kid = kid->GetNextSibling()) { michael@0: rv = kid->GetPrefWidthTightBounds(aRenderingContext, &childX, michael@0: &childXMost); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: *aX = std::min(*aX, data.currentLine + childX); michael@0: *aXMost = std::max(*aXMost, data.currentLine + childXMost); michael@0: kid->AddInlinePrefWidth(aRenderingContext, &data); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: data.ForceBreak(aRenderingContext); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: AvailableSpaceShrunk(const nsRect& aOldAvailableSpace, michael@0: const nsRect& aNewAvailableSpace) michael@0: { michael@0: if (aNewAvailableSpace.width == 0) { michael@0: // Positions are not significant if the width is zero. michael@0: return aOldAvailableSpace.width != 0; michael@0: } michael@0: NS_ASSERTION(aOldAvailableSpace.x <= aNewAvailableSpace.x && michael@0: aOldAvailableSpace.XMost() >= aNewAvailableSpace.XMost(), michael@0: "available space should never grow"); michael@0: return aOldAvailableSpace.width != aNewAvailableSpace.width; michael@0: } michael@0: michael@0: static nsSize michael@0: CalculateContainingBlockSizeForAbsolutes(const nsHTMLReflowState& aReflowState, michael@0: nsSize aFrameSize) michael@0: { michael@0: // The issue here is that for a 'height' of 'auto' the reflow state michael@0: // code won't know how to calculate the containing block height michael@0: // because it's calculated bottom up. So we use our own computed michael@0: // size as the dimensions. michael@0: nsIFrame* frame = aReflowState.frame; michael@0: michael@0: nsSize cbSize(aFrameSize); michael@0: // Containing block is relative to the padding edge michael@0: const nsMargin& border = michael@0: aReflowState.ComputedPhysicalBorderPadding() - aReflowState.ComputedPhysicalPadding(); michael@0: cbSize.width -= border.LeftRight(); michael@0: cbSize.height -= border.TopBottom(); michael@0: michael@0: if (frame->GetParent()->GetContent() == frame->GetContent() && michael@0: frame->GetParent()->GetType() != nsGkAtoms::canvasFrame) { michael@0: // We are a wrapped frame for the content (and the wrapper is not the michael@0: // canvas frame, whose size is not meaningful here). michael@0: // Use the container's dimensions, if they have been precomputed. michael@0: // XXX This is a hack! We really should be waiting until the outermost michael@0: // frame is fully reflowed and using the resulting dimensions, even michael@0: // if they're intrinsic. michael@0: // In fact we should be attaching absolute children to the outermost michael@0: // frame and not always sticking them in block frames. michael@0: michael@0: // First, find the reflow state for the outermost frame for this michael@0: // content, except for fieldsets where the inner anonymous frame has michael@0: // the correct padding area with the legend taken into account. michael@0: const nsHTMLReflowState* aLastRS = &aReflowState; michael@0: const nsHTMLReflowState* lastButOneRS = &aReflowState; michael@0: while (aLastRS->parentReflowState && michael@0: aLastRS->parentReflowState->frame->GetContent() == frame->GetContent() && michael@0: aLastRS->parentReflowState->frame->GetType() != nsGkAtoms::fieldSetFrame) { michael@0: lastButOneRS = aLastRS; michael@0: aLastRS = aLastRS->parentReflowState; michael@0: } michael@0: if (aLastRS != &aReflowState) { michael@0: // Scrollbars need to be specifically excluded, if present, because they are outside the michael@0: // padding-edge. We need better APIs for getting the various boxes from a frame. michael@0: nsIScrollableFrame* scrollFrame = do_QueryFrame(aLastRS->frame); michael@0: nsMargin scrollbars(0,0,0,0); michael@0: if (scrollFrame) { michael@0: scrollbars = michael@0: scrollFrame->GetDesiredScrollbarSizes(aLastRS->frame->PresContext(), michael@0: aLastRS->rendContext); michael@0: if (!lastButOneRS->mFlags.mAssumingHScrollbar) { michael@0: scrollbars.top = scrollbars.bottom = 0; michael@0: } michael@0: if (!lastButOneRS->mFlags.mAssumingVScrollbar) { michael@0: scrollbars.left = scrollbars.right = 0; michael@0: } michael@0: } michael@0: // We found a reflow state for the outermost wrapping frame, so use michael@0: // its computed metrics if available michael@0: if (aLastRS->ComputedWidth() != NS_UNCONSTRAINEDSIZE) { michael@0: cbSize.width = std::max(0, michael@0: aLastRS->ComputedWidth() + aLastRS->ComputedPhysicalPadding().LeftRight() - scrollbars.LeftRight()); michael@0: } michael@0: if (aLastRS->ComputedHeight() != NS_UNCONSTRAINEDSIZE) { michael@0: cbSize.height = std::max(0, michael@0: aLastRS->ComputedHeight() + aLastRS->ComputedPhysicalPadding().TopBottom() - scrollbars.TopBottom()); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return cbSize; michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsBlockFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": begin reflow availSize=%d,%d computedSize=%d,%d\n", michael@0: aReflowState.AvailableWidth(), aReflowState.AvailableHeight(), michael@0: aReflowState.ComputedWidth(), aReflowState.ComputedHeight()); michael@0: } michael@0: AutoNoisyIndenter indent(gNoisy); michael@0: PRTime start = 0; // Initialize these variablies to silence the compiler. michael@0: int32_t ctc = 0; // We only use these if they are set (gLameReflowMetrics). michael@0: if (gLameReflowMetrics) { michael@0: start = PR_Now(); michael@0: ctc = nsLineBox::GetCtorCount(); michael@0: } michael@0: #endif michael@0: michael@0: const nsHTMLReflowState *reflowState = &aReflowState; michael@0: nscoord consumedHeight = GetConsumedHeight(); michael@0: nscoord effectiveComputedHeight = GetEffectiveComputedHeight(aReflowState, michael@0: consumedHeight); michael@0: Maybe mutableReflowState; michael@0: // If we have non-auto height, we're clipping our kids and we fit, michael@0: // make sure our kids fit too. michael@0: if (aReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE && michael@0: aReflowState.ComputedHeight() != NS_AUTOHEIGHT && michael@0: ShouldApplyOverflowClipping(this, aReflowState.mStyleDisplay)) { michael@0: LogicalMargin blockDirExtras = aReflowState.ComputedLogicalBorderPadding(); michael@0: WritingMode wm = aReflowState.GetWritingMode(); michael@0: if (GetLogicalSkipSides() & (LOGICAL_SIDE_B_START)) { michael@0: blockDirExtras.BStart(wm) = 0; michael@0: } else { michael@0: // Bottom margin never causes us to create continuations, so we michael@0: // don't need to worry about whether it fits in its entirety. michael@0: blockDirExtras.BStart(wm) += michael@0: aReflowState.ComputedLogicalMargin().BStart(wm); michael@0: } michael@0: michael@0: if (effectiveComputedHeight + blockDirExtras.BStartEnd(wm) <= michael@0: aReflowState.AvailableBSize()) { michael@0: mutableReflowState.construct(aReflowState); michael@0: mutableReflowState.ref().AvailableBSize() = NS_UNCONSTRAINEDSIZE; michael@0: reflowState = mutableReflowState.addr(); michael@0: } michael@0: } michael@0: michael@0: // See comment below about oldSize. Use *only* for the michael@0: // abs-pos-containing-block-size-change optimization! michael@0: nsSize oldSize = GetSize(); michael@0: michael@0: // Should we create a float manager? michael@0: nsAutoFloatManager autoFloatManager(const_cast(*reflowState)); michael@0: michael@0: // XXXldb If we start storing the float manager in the frame rather michael@0: // than keeping it around only during reflow then we should create it michael@0: // only when there are actually floats to manage. Otherwise things michael@0: // like tables will gain significant bloat. michael@0: bool needFloatManager = nsBlockFrame::BlockNeedsFloatManager(this); michael@0: if (needFloatManager) michael@0: autoFloatManager.CreateFloatManager(aPresContext); michael@0: michael@0: // OK, some lines may be reflowed. Blow away any saved line cursor michael@0: // because we may invalidate the nondecreasing michael@0: // overflowArea.VisualOverflow().y/yMost invariant, and we may even michael@0: // delete the line with the line cursor. michael@0: ClearLineCursor(); michael@0: michael@0: if (IsFrameTreeTooDeep(*reflowState, aMetrics, aStatus)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool topMarginRoot, bottomMarginRoot; michael@0: IsMarginRoot(&topMarginRoot, &bottomMarginRoot); michael@0: michael@0: // Cache the consumed height in the block reflow state so that we don't have michael@0: // to continually recompute it. michael@0: nsBlockReflowState state(*reflowState, aPresContext, this, michael@0: topMarginRoot, bottomMarginRoot, needFloatManager, michael@0: consumedHeight); michael@0: michael@0: if (GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) michael@0: static_cast(FirstContinuation())->ResolveBidi(); michael@0: michael@0: if (RenumberLists(aPresContext)) { michael@0: AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: // ALWAYS drain overflow. We never want to leave the previnflow's michael@0: // overflow lines hanging around; block reflow depends on the michael@0: // overflow line lists being cleared out between reflow passes. michael@0: DrainOverflowLines(); michael@0: michael@0: // Handle paginated overflow (see nsContainerFrame.h) michael@0: nsOverflowAreas ocBounds; michael@0: nsReflowStatus ocStatus = NS_FRAME_COMPLETE; michael@0: if (GetPrevInFlow()) { michael@0: ReflowOverflowContainerChildren(aPresContext, *reflowState, ocBounds, 0, michael@0: ocStatus); michael@0: } michael@0: michael@0: // Now that we're done cleaning up our overflow container lists, we can michael@0: // give |state| its nsOverflowContinuationTracker. michael@0: nsOverflowContinuationTracker tracker(this, false); michael@0: state.mOverflowTracker = &tracker; michael@0: michael@0: // Drain & handle pushed floats michael@0: DrainPushedFloats(state); michael@0: nsOverflowAreas fcBounds; michael@0: nsReflowStatus fcStatus = NS_FRAME_COMPLETE; michael@0: ReflowPushedFloats(state, fcBounds, fcStatus); michael@0: michael@0: // If we're not dirty (which means we'll mark everything dirty later) michael@0: // and our width has changed, mark the lines dirty that we need to michael@0: // mark dirty for a resize reflow. michael@0: if (!(GetStateBits() & NS_FRAME_IS_DIRTY) && reflowState->mFlags.mHResize) { michael@0: PrepareResizeReflow(state); michael@0: } michael@0: michael@0: LazyMarkLinesDirty(); michael@0: michael@0: mState &= ~NS_FRAME_FIRST_REFLOW; michael@0: michael@0: // Now reflow... michael@0: rv = ReflowDirtyLines(state); michael@0: michael@0: // If we have a next-in-flow, and that next-in-flow has pushed floats from michael@0: // this frame from a previous iteration of reflow, then we should not return michael@0: // a status of NS_FRAME_COMPLETE, since we actually have overflow, it's just michael@0: // already been handled. michael@0: michael@0: // NOTE: This really shouldn't happen, since we _should_ pull back our floats michael@0: // and reflow them, but just in case it does, this is a safety precaution so michael@0: // we don't end up with a placeholder pointing to frames that have already michael@0: // been deleted as part of removing our next-in-flow. michael@0: if (NS_FRAME_IS_COMPLETE(state.mReflowStatus)) { michael@0: nsBlockFrame* nif = static_cast(GetNextInFlow()); michael@0: while (nif) { michael@0: if (nif->HasPushedFloatsFromPrevContinuation()) { michael@0: NS_MergeReflowStatusInto(&state.mReflowStatus, NS_FRAME_NOT_COMPLETE); michael@0: } michael@0: michael@0: nif = static_cast(nif->GetNextInFlow()); michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "reflow dirty lines failed"); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_MergeReflowStatusInto(&state.mReflowStatus, ocStatus); michael@0: NS_MergeReflowStatusInto(&state.mReflowStatus, fcStatus); michael@0: michael@0: // If we end in a BR with clear and affected floats continue, michael@0: // we need to continue, too. michael@0: if (NS_UNCONSTRAINEDSIZE != reflowState->AvailableHeight() && michael@0: NS_FRAME_IS_COMPLETE(state.mReflowStatus) && michael@0: state.mFloatManager->ClearContinues(FindTrailingClear())) { michael@0: NS_FRAME_SET_INCOMPLETE(state.mReflowStatus); michael@0: } michael@0: michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(state.mReflowStatus)) { michael@0: if (HasOverflowLines() || HasPushedFloats()) { michael@0: state.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: } michael@0: michael@0: #ifdef DEBUG_kipp michael@0: ListTag(stdout); printf(": block is not fully complete\n"); michael@0: #endif michael@0: } michael@0: michael@0: // Place the "marker" (bullet) frame if it is placed next to a block michael@0: // child. michael@0: // michael@0: // According to the CSS2 spec, section 12.6.1, the "marker" box michael@0: // participates in the height calculation of the list-item box's michael@0: // first line box. michael@0: // michael@0: // There are exactly two places a bullet can be placed: near the michael@0: // first or second line. It's only placed on the second line in a michael@0: // rare case: an empty first line followed by a second line that michael@0: // contains a block (example:
  • \n

    ... ). This is where michael@0: // the second case can happen. michael@0: if (HasOutsideBullet() && !mLines.empty() && michael@0: (mLines.front()->IsBlock() || michael@0: (0 == mLines.front()->BSize() && michael@0: mLines.front() != mLines.back() && michael@0: mLines.begin().next()->IsBlock()))) { michael@0: // Reflow the bullet michael@0: nsHTMLReflowMetrics metrics(aReflowState); michael@0: // XXX Use the entire line when we fix bug 25888. michael@0: nsLayoutUtils::LinePosition position; michael@0: bool havePosition = nsLayoutUtils::GetFirstLinePosition(this, &position); michael@0: nscoord lineTop = havePosition ? position.mTop michael@0: : reflowState->ComputedPhysicalBorderPadding().top; michael@0: nsIFrame* bullet = GetOutsideBullet(); michael@0: ReflowBullet(bullet, state, metrics, lineTop); michael@0: NS_ASSERTION(!BulletIsEmpty() || metrics.Height() == 0, michael@0: "empty bullet took up space"); michael@0: michael@0: if (havePosition && !BulletIsEmpty()) { michael@0: // We have some lines to align the bullet with. michael@0: michael@0: // Doing the alignment using the baseline will also cater for michael@0: // bullets that are placed next to a child block (bug 92896) michael@0: michael@0: // Tall bullets won't look particularly nice here... michael@0: nsRect bbox = bullet->GetRect(); michael@0: bbox.y = position.mBaseline - metrics.TopAscent(); michael@0: bullet->SetRect(bbox); michael@0: } michael@0: // Otherwise just leave the bullet where it is, up against our top padding. michael@0: } michael@0: michael@0: CheckFloats(state); michael@0: michael@0: // Compute our final size michael@0: nscoord bottomEdgeOfChildren; michael@0: ComputeFinalSize(*reflowState, state, aMetrics, &bottomEdgeOfChildren); michael@0: nsRect areaBounds = nsRect(0, 0, aMetrics.Width(), aMetrics.Height()); michael@0: ComputeOverflowAreas(areaBounds, reflowState->mStyleDisplay, michael@0: bottomEdgeOfChildren, aMetrics.mOverflowAreas); michael@0: // Factor overflow container child bounds into the overflow area michael@0: aMetrics.mOverflowAreas.UnionWith(ocBounds); michael@0: // Factor pushed float child bounds into the overflow area michael@0: aMetrics.mOverflowAreas.UnionWith(fcBounds); michael@0: michael@0: // Let the absolutely positioned container reflow any absolutely positioned michael@0: // child frames that need to be reflowed, e.g., elements with a percentage michael@0: // based width/height michael@0: // We want to do this under either of two conditions: michael@0: // 1. If we didn't do the incremental reflow above. michael@0: // 2. If our size changed. michael@0: // Even though it's the padding edge that's the containing block, we michael@0: // can use our rect (the border edge) since if the border style michael@0: // changed, the reflow would have been targeted at us so we'd satisfy michael@0: // condition 1. michael@0: // XXX checking oldSize is bogus, there are various reasons we might have michael@0: // reflowed but our size might not have been changed to what we michael@0: // asked for (e.g., we ended up being pushed to a new page) michael@0: // When WillReflowAgainForClearance is true, we will reflow again without michael@0: // resetting the size. Because of this, we must not reflow our abs-pos children michael@0: // in that situation --- what we think is our "new size" michael@0: // will not be our real new size. This also happens to be more efficient. michael@0: if (HasAbsolutelyPositionedChildren()) { michael@0: nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock(); michael@0: bool haveInterrupt = aPresContext->HasPendingInterrupt(); michael@0: if (reflowState->WillReflowAgainForClearance() || michael@0: haveInterrupt) { michael@0: // Make sure that when we reflow again we'll actually reflow all the abs michael@0: // pos frames that might conceivably depend on our size (or all of them, michael@0: // if we're dirty right now and interrupted; in that case we also need michael@0: // to mark them all with NS_FRAME_IS_DIRTY). Sadly, we can't do much michael@0: // better than that, because we don't really know what our size will be, michael@0: // and it might in fact not change on the followup reflow! michael@0: if (haveInterrupt && (GetStateBits() & NS_FRAME_IS_DIRTY)) { michael@0: absoluteContainer->MarkAllFramesDirty(); michael@0: } else { michael@0: absoluteContainer->MarkSizeDependentFramesDirty(); michael@0: } michael@0: } else { michael@0: nsSize containingBlockSize = michael@0: CalculateContainingBlockSizeForAbsolutes(*reflowState, michael@0: nsSize(aMetrics.Width(), michael@0: aMetrics.Height())); michael@0: michael@0: // Mark frames that depend on changes we just made to this frame as dirty: michael@0: // Now we can assume that the padding edge hasn't moved. michael@0: // We need to reflow the absolutes if one of them depends on michael@0: // its placeholder position, or the containing block size in a michael@0: // direction in which the containing block size might have michael@0: // changed. michael@0: bool cbWidthChanged = aMetrics.Width() != oldSize.width; michael@0: bool isRoot = !GetContent()->GetParent(); michael@0: // If isRoot and we have auto height, then we are the initial michael@0: // containing block and the containing block height is the michael@0: // viewport height, which can't change during incremental michael@0: // reflow. michael@0: bool cbHeightChanged = michael@0: !(isRoot && NS_UNCONSTRAINEDSIZE == reflowState->ComputedHeight()) && michael@0: aMetrics.Height() != oldSize.height; michael@0: michael@0: nsRect containingBlock(nsPoint(0, 0), containingBlockSize); michael@0: absoluteContainer->Reflow(this, aPresContext, *reflowState, michael@0: state.mReflowStatus, michael@0: containingBlock, true, michael@0: cbWidthChanged, cbHeightChanged, michael@0: &aMetrics.mOverflowAreas); michael@0: michael@0: //XXXfr Why isn't this rv (and others in this file) checked/returned? michael@0: } michael@0: } michael@0: michael@0: FinishAndStoreOverflow(&aMetrics); michael@0: michael@0: // Clear the float manager pointer in the block reflow state so we michael@0: // don't waste time translating the coordinate system back on a dead michael@0: // float manager. michael@0: if (needFloatManager) michael@0: state.mFloatManager = nullptr; michael@0: michael@0: aStatus = state.mReflowStatus; michael@0: michael@0: #ifdef DEBUG michael@0: // Between when we drain pushed floats and when we complete reflow, michael@0: // we're allowed to have multiple continuations of the same float on michael@0: // our floats list, since a first-in-flow might get pushed to a later michael@0: // continuation of its containing block. But it's not permitted michael@0: // outside that time. michael@0: nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats); michael@0: michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": status=%x (%scomplete) metrics=%d,%d carriedMargin=%d", michael@0: aStatus, NS_FRAME_IS_COMPLETE(aStatus) ? "" : "not ", michael@0: aMetrics.Width(), aMetrics.Height(), michael@0: aMetrics.mCarriedOutBottomMargin.get()); michael@0: if (HasOverflowAreas()) { michael@0: printf(" overflow-vis={%d,%d,%d,%d}", michael@0: aMetrics.VisualOverflow().x, michael@0: aMetrics.VisualOverflow().y, michael@0: aMetrics.VisualOverflow().width, michael@0: aMetrics.VisualOverflow().height); michael@0: printf(" overflow-scr={%d,%d,%d,%d}", michael@0: aMetrics.ScrollableOverflow().x, michael@0: aMetrics.ScrollableOverflow().y, michael@0: aMetrics.ScrollableOverflow().width, michael@0: aMetrics.ScrollableOverflow().height); michael@0: } michael@0: printf("\n"); michael@0: } michael@0: michael@0: if (gLameReflowMetrics) { michael@0: PRTime end = PR_Now(); michael@0: michael@0: int32_t ectc = nsLineBox::GetCtorCount(); michael@0: int32_t numLines = mLines.size(); michael@0: if (!numLines) numLines = 1; michael@0: PRTime delta, perLineDelta, lines; michael@0: lines = int64_t(numLines); michael@0: delta = end - start; michael@0: perLineDelta = delta / lines; michael@0: michael@0: ListTag(stdout); michael@0: char buf[400]; michael@0: PR_snprintf(buf, sizeof(buf), michael@0: ": %lld elapsed (%lld per line) (%d lines; %d new lines)", michael@0: delta, perLineDelta, numLines, ectc - ctc); michael@0: printf("%s\n", buf); michael@0: } michael@0: #endif michael@0: michael@0: NS_FRAME_SET_TRUNCATION(aStatus, (*reflowState), aMetrics); michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::CheckForCollapsedBottomMarginFromClearanceLine() michael@0: { michael@0: line_iterator begin = begin_lines(); michael@0: line_iterator line = end_lines(); michael@0: michael@0: while (true) { michael@0: if (begin == line) { michael@0: return false; michael@0: } michael@0: --line; michael@0: if (line->BSize() != 0 || !line->CachedIsEmpty()) { michael@0: return false; michael@0: } michael@0: if (line->HasClearance()) { michael@0: return true; michael@0: } michael@0: } michael@0: // not reached michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::ComputeFinalSize(const nsHTMLReflowState& aReflowState, michael@0: nsBlockReflowState& aState, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: nscoord* aBottomEdgeOfChildren) michael@0: { michael@0: const nsMargin& borderPadding = aState.BorderPadding(); michael@0: #ifdef NOISY_FINAL_SIZE michael@0: ListTag(stdout); michael@0: printf(": mY=%d mIsBottomMarginRoot=%s mPrevBottomMargin=%d bp=%d,%d\n", michael@0: aState.mY, aState.GetFlag(BRS_ISBOTTOMMARGINROOT) ? "yes" : "no", michael@0: aState.mPrevBottomMargin, michael@0: borderPadding.top, borderPadding.bottom); michael@0: #endif michael@0: michael@0: // Compute final width michael@0: aMetrics.Width() = michael@0: NSCoordSaturatingAdd(NSCoordSaturatingAdd(borderPadding.left, michael@0: aReflowState.ComputedWidth()), michael@0: borderPadding.right); michael@0: michael@0: // Return bottom margin information michael@0: // rbs says he hit this assertion occasionally (see bug 86947), so michael@0: // just set the margin to zero and we'll figure out why later michael@0: //NS_ASSERTION(aMetrics.mCarriedOutBottomMargin.IsZero(), michael@0: // "someone else set the margin"); michael@0: nscoord nonCarriedOutVerticalMargin = 0; michael@0: if (!aState.GetFlag(BRS_ISBOTTOMMARGINROOT)) { michael@0: // Apply rule from CSS 2.1 section 8.3.1. If we have some empty michael@0: // line with clearance and a non-zero top margin and all michael@0: // subsequent lines are empty, then we do not allow our children's michael@0: // carried out bottom margin to be carried out of us and collapse michael@0: // with our own bottom margin. michael@0: if (CheckForCollapsedBottomMarginFromClearanceLine()) { michael@0: // Convert the children's carried out margin to something that michael@0: // we will include in our height michael@0: nonCarriedOutVerticalMargin = aState.mPrevBottomMargin.get(); michael@0: aState.mPrevBottomMargin.Zero(); michael@0: } michael@0: aMetrics.mCarriedOutBottomMargin = aState.mPrevBottomMargin; michael@0: } else { michael@0: aMetrics.mCarriedOutBottomMargin.Zero(); michael@0: } michael@0: michael@0: nscoord bottomEdgeOfChildren = aState.mY + nonCarriedOutVerticalMargin; michael@0: // Shrink wrap our height around our contents. michael@0: if (aState.GetFlag(BRS_ISBOTTOMMARGINROOT) || michael@0: NS_UNCONSTRAINEDSIZE != aReflowState.ComputedHeight()) { michael@0: // When we are a bottom-margin root make sure that our last michael@0: // childs bottom margin is fully applied. We also do this when michael@0: // we have a computed height, since in that case the carried out michael@0: // margin is not going to be applied anywhere, so we should note it michael@0: // here to be included in the overflow area. michael@0: // Apply the margin only if there's space for it. michael@0: if (bottomEdgeOfChildren < aState.mReflowState.AvailableHeight()) michael@0: { michael@0: // Truncate bottom margin if it doesn't fit to our available height. michael@0: bottomEdgeOfChildren = michael@0: std::min(bottomEdgeOfChildren + aState.mPrevBottomMargin.get(), michael@0: aState.mReflowState.AvailableHeight()); michael@0: } michael@0: } michael@0: if (aState.GetFlag(BRS_FLOAT_MGR)) { michael@0: // Include the float manager's state to properly account for the michael@0: // bottom margin of any floated elements; e.g., inside a table cell. michael@0: nscoord floatHeight = michael@0: aState.ClearFloats(bottomEdgeOfChildren, NS_STYLE_CLEAR_BOTH, michael@0: nullptr, nsFloatManager::DONT_CLEAR_PUSHED_FLOATS); michael@0: bottomEdgeOfChildren = std::max(bottomEdgeOfChildren, floatHeight); michael@0: } michael@0: michael@0: if (NS_UNCONSTRAINEDSIZE != aReflowState.ComputedHeight() michael@0: && (mParent->GetType() != nsGkAtoms::columnSetFrame || michael@0: aReflowState.parentReflowState->AvailableHeight() == NS_UNCONSTRAINEDSIZE)) { michael@0: ComputeFinalHeight(aReflowState, &aState.mReflowStatus, michael@0: aState.mY + nonCarriedOutVerticalMargin, michael@0: borderPadding, aMetrics, aState.mConsumedHeight); michael@0: if (!NS_FRAME_IS_COMPLETE(aState.mReflowStatus)) { michael@0: // Use the current height; continuations will take up the rest. michael@0: // Do extend the height to at least consume the available michael@0: // height, otherwise our left/right borders (for example) won't michael@0: // extend all the way to the break. michael@0: aMetrics.Height() = std::max(aReflowState.AvailableHeight(), michael@0: aState.mY + nonCarriedOutVerticalMargin); michael@0: // ... but don't take up more height than is available michael@0: nscoord effectiveComputedHeight = michael@0: GetEffectiveComputedHeight(aReflowState, aState.GetConsumedHeight()); michael@0: aMetrics.Height() = std::min(aMetrics.Height(), michael@0: borderPadding.top + effectiveComputedHeight); michael@0: // XXX It's pretty wrong that our bottom border still gets drawn on michael@0: // on its own on the last-in-flow, even if we ran out of height michael@0: // here. We need GetSkipSides to check whether we ran out of content michael@0: // height in the current frame, not whether it's last-in-flow. michael@0: } michael@0: michael@0: // Don't carry out a bottom margin when our height is fixed. michael@0: aMetrics.mCarriedOutBottomMargin.Zero(); michael@0: } michael@0: else if (NS_FRAME_IS_COMPLETE(aState.mReflowStatus)) { michael@0: nscoord contentHeight = bottomEdgeOfChildren - borderPadding.top; michael@0: nscoord autoHeight = aReflowState.ApplyMinMaxHeight(contentHeight); michael@0: if (autoHeight != contentHeight) { michael@0: // Our min-height or max-height made our height change. Don't carry out michael@0: // our kids' bottom margins. michael@0: aMetrics.mCarriedOutBottomMargin.Zero(); michael@0: } michael@0: autoHeight += borderPadding.top + borderPadding.bottom; michael@0: aMetrics.Height() = autoHeight; michael@0: } michael@0: else { michael@0: NS_ASSERTION(aReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE, michael@0: "Shouldn't be incomplete if availableHeight is UNCONSTRAINED."); michael@0: aMetrics.Height() = std::max(aState.mY, aReflowState.AvailableHeight()); michael@0: if (aReflowState.AvailableHeight() == NS_UNCONSTRAINEDSIZE) michael@0: // This should never happen, but it does. See bug 414255 michael@0: aMetrics.Height() = aState.mY; michael@0: } michael@0: michael@0: if (IS_TRUE_OVERFLOW_CONTAINER(this) && michael@0: NS_FRAME_IS_NOT_COMPLETE(aState.mReflowStatus)) { michael@0: // Overflow containers can only be overflow complete. michael@0: // Note that auto height overflow containers have no normal children michael@0: NS_ASSERTION(aMetrics.Height() == 0, "overflow containers must be zero-height"); michael@0: NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus); michael@0: } michael@0: michael@0: // Screen out negative heights --- can happen due to integer overflows :-( michael@0: aMetrics.Height() = std::max(0, aMetrics.Height()); michael@0: *aBottomEdgeOfChildren = bottomEdgeOfChildren; michael@0: michael@0: FrameProperties properties = Properties(); michael@0: if (bottomEdgeOfChildren != aMetrics.Height() - borderPadding.bottom) { michael@0: properties.Set(BottomEdgeOfChildrenProperty(), michael@0: NS_INT32_TO_PTR(bottomEdgeOfChildren)); michael@0: } else { michael@0: properties.Delete(BottomEdgeOfChildrenProperty()); michael@0: } michael@0: michael@0: #ifdef DEBUG_blocks michael@0: if (CRAZY_SIZE(aMetrics.Width()) || CRAZY_SIZE(aMetrics.Height())) { michael@0: ListTag(stdout); michael@0: printf(": WARNING: desired:%d,%d\n", aMetrics.Width(), aMetrics.Height()); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: static void michael@0: ConsiderBottomEdgeOfChildren(nscoord aBottomEdgeOfChildren, michael@0: nsOverflowAreas& aOverflowAreas) michael@0: { michael@0: // Factor in the bottom edge of the children. Child frames will be added michael@0: // to the overflow area as we iterate through the lines, but their margins michael@0: // won't, so we need to account for bottom margins here. michael@0: // REVIEW: For now, we do this for both visual and scrollable area, michael@0: // although when we make scrollable overflow area not be a subset of michael@0: // visual, we can change this. michael@0: NS_FOR_FRAME_OVERFLOW_TYPES(otype) { michael@0: nsRect& o = aOverflowAreas.Overflow(otype); michael@0: o.height = std::max(o.YMost(), aBottomEdgeOfChildren) - o.y; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::ComputeOverflowAreas(const nsRect& aBounds, michael@0: const nsStyleDisplay* aDisplay, michael@0: nscoord aBottomEdgeOfChildren, michael@0: nsOverflowAreas& aOverflowAreas) michael@0: { michael@0: // Compute the overflow areas of our children michael@0: // XXX_perf: This can be done incrementally. It is currently one of michael@0: // the things that makes incremental reflow O(N^2). michael@0: nsOverflowAreas areas(aBounds, aBounds); michael@0: if (!ShouldApplyOverflowClipping(this, aDisplay)) { michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) { michael@0: areas.UnionWith(line->GetOverflowAreas()); michael@0: } michael@0: michael@0: // Factor an outside bullet in; normally the bullet will be factored into michael@0: // the line-box's overflow areas. However, if the line is a block michael@0: // line then it won't; if there are no lines, it won't. So just michael@0: // factor it in anyway (it can't hurt if it was already done). michael@0: // XXXldb Can we just fix GetOverflowArea instead? michael@0: nsIFrame* outsideBullet = GetOutsideBullet(); michael@0: if (outsideBullet) { michael@0: areas.UnionAllWith(outsideBullet->GetRect()); michael@0: } michael@0: michael@0: ConsiderBottomEdgeOfChildren(aBottomEdgeOfChildren, areas); michael@0: } michael@0: michael@0: #ifdef NOISY_COMBINED_AREA michael@0: ListTag(stdout); michael@0: printf(": ca=%d,%d,%d,%d\n", area.x, area.y, area.width, area.height); michael@0: #endif michael@0: michael@0: aOverflowAreas = areas; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::UpdateOverflow() michael@0: { michael@0: nsRect rect(nsPoint(0, 0), GetSize()); michael@0: nsOverflowAreas overflowAreas(rect, rect); michael@0: michael@0: // We need to update the overflow areas of lines manually, as they michael@0: // get cached and re-used otherwise. Lines aren't exposed as normal michael@0: // frame children, so calling UnionChildOverflow alone will end up michael@0: // using the old cached values. michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) { michael@0: nsRect bounds = line->GetPhysicalBounds(); michael@0: nsOverflowAreas lineAreas(bounds, bounds); michael@0: michael@0: int32_t n = line->GetChildCount(); michael@0: for (nsIFrame* lineFrame = line->mFirstChild; michael@0: n > 0; lineFrame = lineFrame->GetNextSibling(), --n) { michael@0: ConsiderChildOverflow(lineAreas, lineFrame); michael@0: } michael@0: michael@0: // Consider the overflow areas of the floats attached to the line as well michael@0: if (line->HasFloats()) { michael@0: for (nsFloatCache* fc = line->GetFirstFloat(); fc; fc = fc->Next()) { michael@0: ConsiderChildOverflow(lineAreas, fc->mFloat); michael@0: } michael@0: } michael@0: michael@0: line->SetOverflowAreas(lineAreas); michael@0: overflowAreas.UnionWith(lineAreas); michael@0: } michael@0: michael@0: // Line cursor invariants depend on the overflow areas of the lines, so michael@0: // we must clear the line cursor since those areas may have changed. michael@0: ClearLineCursor(); michael@0: michael@0: // Union with child frames, skipping the principal and float lists michael@0: // since we already handled those using the line boxes. michael@0: nsLayoutUtils::UnionChildOverflow(this, overflowAreas, michael@0: kPrincipalList | kFloatList); michael@0: michael@0: bool found; michael@0: nscoord bottomEdgeOfChildren = NS_PTR_TO_INT32( michael@0: Properties().Get(BottomEdgeOfChildrenProperty(), &found)); michael@0: if (found) { michael@0: ConsiderBottomEdgeOfChildren(bottomEdgeOfChildren, overflowAreas); michael@0: } michael@0: michael@0: return FinishAndStoreOverflow(overflowAreas, GetSize()); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::LazyMarkLinesDirty() michael@0: { michael@0: if (GetStateBits() & NS_BLOCK_LOOK_FOR_DIRTY_FRAMES) { michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; ++line) { michael@0: int32_t n = line->GetChildCount(); michael@0: for (nsIFrame* lineFrame = line->mFirstChild; michael@0: n > 0; lineFrame = lineFrame->GetNextSibling(), --n) { michael@0: if (NS_SUBTREE_DIRTY(lineFrame)) { michael@0: // NOTE: MarkLineDirty does more than just marking the line dirty. michael@0: MarkLineDirty(line, &mLines); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: RemoveStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::MarkLineDirty(line_iterator aLine, const nsLineList* aLineList) michael@0: { michael@0: // Mark aLine dirty michael@0: aLine->MarkDirty(); michael@0: aLine->SetInvalidateTextRuns(true); michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": mark line %p dirty\n", static_cast(aLine.get())); michael@0: } michael@0: #endif michael@0: michael@0: // Mark previous line dirty if it's an inline line so that it can michael@0: // maybe pullup something from the line just affected. michael@0: // XXX We don't need to do this if aPrevLine ends in a break-after... michael@0: if (aLine != aLineList->front() && aLine->IsInline() && michael@0: aLine.prev()->IsInline()) { michael@0: aLine.prev()->MarkDirty(); michael@0: aLine.prev()->SetInvalidateTextRuns(true); michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": mark prev-line %p dirty\n", michael@0: static_cast(aLine.prev().get())); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Test whether lines are certain to be aligned left so that we can make michael@0: * resizing optimizations michael@0: */ michael@0: static inline bool michael@0: IsAlignedLeft(uint8_t aAlignment, michael@0: uint8_t aDirection, michael@0: uint8_t aUnicodeBidi, michael@0: nsIFrame* aFrame) michael@0: { michael@0: return aFrame->IsSVGText() || michael@0: NS_STYLE_TEXT_ALIGN_LEFT == aAlignment || michael@0: (((NS_STYLE_TEXT_ALIGN_DEFAULT == aAlignment && michael@0: NS_STYLE_DIRECTION_LTR == aDirection) || michael@0: (NS_STYLE_TEXT_ALIGN_END == aAlignment && michael@0: NS_STYLE_DIRECTION_RTL == aDirection)) && michael@0: !(NS_STYLE_UNICODE_BIDI_PLAINTEXT & aUnicodeBidi)); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::PrepareResizeReflow(nsBlockReflowState& aState) michael@0: { michael@0: // See if we can try and avoid marking all the lines as dirty michael@0: bool tryAndSkipLines = michael@0: // The left content-edge must be a constant distance from the left michael@0: // border-edge. michael@0: !StylePadding()->mPadding.GetLeft().HasPercent(); michael@0: michael@0: #ifdef DEBUG michael@0: if (gDisableResizeOpt) { michael@0: tryAndSkipLines = false; michael@0: } michael@0: if (gNoisyReflow) { michael@0: if (!tryAndSkipLines) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": marking all lines dirty: availWidth=%d\n", michael@0: aState.mReflowState.AvailableWidth()); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (tryAndSkipLines) { michael@0: nscoord newAvailWidth = aState.mReflowState.ComputedPhysicalBorderPadding().left + michael@0: aState.mReflowState.ComputedWidth(); michael@0: NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mReflowState.ComputedPhysicalBorderPadding().left && michael@0: NS_UNCONSTRAINEDSIZE != aState.mReflowState.ComputedWidth(), michael@0: "math on NS_UNCONSTRAINEDSIZE"); michael@0: michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": trying to avoid marking all lines dirty\n"); michael@0: } michael@0: #endif michael@0: michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) michael@0: { michael@0: // We let child blocks make their own decisions the same michael@0: // way we are here. michael@0: bool isLastLine = line == mLines.back() && !GetNextInFlow(); michael@0: if (line->IsBlock() || michael@0: line->HasFloats() || michael@0: (!isLastLine && !line->HasBreakAfter()) || michael@0: ((isLastLine || !line->IsLineWrapped())) || michael@0: line->ResizeReflowOptimizationDisabled() || michael@0: line->IsImpactedByFloat() || michael@0: (line->IEnd() > newAvailWidth)) { michael@0: line->MarkDirty(); michael@0: } michael@0: michael@0: #ifdef REALLY_NOISY_REFLOW michael@0: if (!line->IsBlock()) { michael@0: printf("PrepareResizeReflow thinks line %p is %simpacted by floats\n", michael@0: line.get(), line->IsImpactedByFloat() ? "" : "not "); michael@0: } michael@0: #endif michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow && !line->IsDirty()) { michael@0: IndentBy(stdout, gNoiseIndent + 1); michael@0: printf("skipped: line=%p next=%p %s %s%s%s breakTypeBefore/After=%d/%d xmost=%d\n", michael@0: static_cast(line.get()), michael@0: static_cast((line.next() != end_lines() ? line.next().get() : nullptr)), michael@0: line->IsBlock() ? "block" : "inline", michael@0: line->HasBreakAfter() ? "has-break-after " : "", michael@0: line->HasFloats() ? "has-floats " : "", michael@0: line->IsImpactedByFloat() ? "impacted " : "", michael@0: line->GetBreakTypeBefore(), line->GetBreakTypeAfter(), michael@0: line->IEnd()); michael@0: } michael@0: #endif michael@0: } michael@0: } michael@0: else { michael@0: // Mark everything dirty michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) michael@0: { michael@0: line->MarkDirty(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------- michael@0: michael@0: /** michael@0: * Propagate reflow "damage" from from earlier lines to the current michael@0: * line. The reflow damage comes from the following sources: michael@0: * 1. The regions of float damage remembered during reflow. michael@0: * 2. The combination of nonzero |aDeltaY| and any impact by a float, michael@0: * either the previous reflow or now. michael@0: * michael@0: * When entering this function, |aLine| is still at its old position and michael@0: * |aDeltaY| indicates how much it will later be slid (assuming it michael@0: * doesn't get marked dirty and reflowed entirely). michael@0: */ michael@0: void michael@0: nsBlockFrame::PropagateFloatDamage(nsBlockReflowState& aState, michael@0: nsLineBox* aLine, michael@0: nscoord aDeltaY) michael@0: { michael@0: nsFloatManager *floatManager = aState.mReflowState.mFloatManager; michael@0: NS_ASSERTION((aState.mReflowState.parentReflowState && michael@0: aState.mReflowState.parentReflowState->mFloatManager == floatManager) || michael@0: aState.mReflowState.mBlockDelta == 0, "Bad block delta passed in"); michael@0: michael@0: // Check to see if there are any floats; if there aren't, there can't michael@0: // be any float damage michael@0: if (!floatManager->HasAnyFloats()) michael@0: return; michael@0: michael@0: // Check the damage region recorded in the float damage. michael@0: if (floatManager->HasFloatDamage()) { michael@0: // Need to check mBounds *and* mCombinedArea to find intersections michael@0: // with aLine's floats michael@0: nscoord lineYA = aLine->BStart() + aDeltaY; michael@0: nscoord lineYB = lineYA + aLine->BSize(); michael@0: // Scrollable overflow should be sufficient for things that affect michael@0: // layout. michael@0: nsRect overflow = aLine->GetOverflowArea(eScrollableOverflow); michael@0: nscoord lineYCombinedA = overflow.y + aDeltaY; michael@0: nscoord lineYCombinedB = lineYCombinedA + overflow.height; michael@0: if (floatManager->IntersectsDamage(lineYA, lineYB) || michael@0: floatManager->IntersectsDamage(lineYCombinedA, lineYCombinedB)) { michael@0: aLine->MarkDirty(); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Check if the line is moving relative to the float manager michael@0: if (aDeltaY + aState.mReflowState.mBlockDelta != 0) { michael@0: if (aLine->IsBlock()) { michael@0: // Unconditionally reflow sliding blocks; we only really need to reflow michael@0: // if there's a float impacting this block, but the current float manager michael@0: // makes it difficult to check that. Therefore, we let the child block michael@0: // decide what it needs to reflow. michael@0: aLine->MarkDirty(); michael@0: } else { michael@0: bool wasImpactedByFloat = aLine->IsImpactedByFloat(); michael@0: nsFlowAreaRect floatAvailableSpace = michael@0: aState.GetFloatAvailableSpaceForHeight(aLine->BStart() + aDeltaY, michael@0: aLine->BSize(), michael@0: nullptr); michael@0: michael@0: #ifdef REALLY_NOISY_REFLOW michael@0: printf("nsBlockFrame::PropagateFloatDamage %p was = %d, is=%d\n", michael@0: this, wasImpactedByFloat, floatAvailableSpace.mHasFloats); michael@0: #endif michael@0: michael@0: // Mark the line dirty if it was or is affected by a float michael@0: // We actually only really need to reflow if the amount of impact michael@0: // changes, but that's not straightforward to check michael@0: if (wasImpactedByFloat || floatAvailableSpace.mHasFloats) { michael@0: aLine->MarkDirty(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static bool LineHasClear(nsLineBox* aLine) { michael@0: return aLine->IsBlock() michael@0: ? (aLine->GetBreakTypeBefore() || michael@0: (aLine->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN) || michael@0: !nsBlockFrame::BlockCanIntersectFloats(aLine->mFirstChild)) michael@0: : aLine->HasFloatBreakAfter(); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Reparent a whole list of floats from aOldParent to this block. The michael@0: * floats might be taken from aOldParent's overflow list. They will be michael@0: * removed from the list. They end up appended to our mFloats list. michael@0: */ michael@0: void michael@0: nsBlockFrame::ReparentFloats(nsIFrame* aFirstFrame, nsBlockFrame* aOldParent, michael@0: bool aReparentSiblings) { michael@0: nsFrameList list; michael@0: aOldParent->CollectFloats(aFirstFrame, list, aReparentSiblings); michael@0: if (list.NotEmpty()) { michael@0: for (nsIFrame* f = list.FirstChild(); f; f = f->GetNextSibling()) { michael@0: ReparentFrame(f, aOldParent, this); michael@0: } michael@0: mFloats.AppendFrames(nullptr, list); michael@0: } michael@0: } michael@0: michael@0: static void DumpLine(const nsBlockReflowState& aState, nsLineBox* aLine, michael@0: nscoord aDeltaY, int32_t aDeltaIndent) { michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gNoisyReflow) { michael@0: nsRect ovis(aLine->GetVisualOverflowArea()); michael@0: nsRect oscr(aLine->GetScrollableOverflowArea()); michael@0: nsBlockFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent + aDeltaIndent); michael@0: printf("line=%p mY=%d dirty=%s oldBounds={%d,%d,%d,%d} oldoverflow-vis={%d,%d,%d,%d} oldoverflow-scr={%d,%d,%d,%d} deltaY=%d mPrevBottomMargin=%d childCount=%d\n", michael@0: static_cast(aLine), aState.mY, michael@0: aLine->IsDirty() ? "yes" : "no", michael@0: aLine->IStart(), aLine->BStart(), michael@0: aLine->ISize(), aLine->BSize(), michael@0: ovis.x, ovis.y, ovis.width, ovis.height, michael@0: oscr.x, oscr.y, oscr.width, oscr.height, michael@0: aDeltaY, aState.mPrevBottomMargin.get(), aLine->GetChildCount()); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: /** michael@0: * Reflow the dirty lines michael@0: */ michael@0: nsresult michael@0: nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: bool keepGoing = true; michael@0: bool repositionViews = false; // should we really need this? michael@0: bool foundAnyClears = aState.mFloatBreakType != NS_STYLE_CLEAR_NONE; michael@0: bool willReflowAgain = false; michael@0: michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent); michael@0: ListTag(stdout); michael@0: printf(": reflowing dirty lines"); michael@0: printf(" computedWidth=%d\n", aState.mReflowState.ComputedWidth()); michael@0: } michael@0: AutoNoisyIndenter indent(gNoisyReflow); michael@0: #endif michael@0: michael@0: bool selfDirty = (GetStateBits() & NS_FRAME_IS_DIRTY) || michael@0: (aState.mReflowState.mFlags.mVResize && michael@0: (GetStateBits() & NS_FRAME_CONTAINS_RELATIVE_HEIGHT)); michael@0: michael@0: // Reflow our last line if our availableHeight has increased michael@0: // so that we (and our last child) pull up content as necessary michael@0: if (aState.mReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE michael@0: && GetNextInFlow() && aState.mReflowState.AvailableHeight() > mRect.height) { michael@0: line_iterator lastLine = end_lines(); michael@0: if (lastLine != begin_lines()) { michael@0: --lastLine; michael@0: lastLine->MarkDirty(); michael@0: } michael@0: } michael@0: // the amount by which we will slide the current line if it is not michael@0: // dirty michael@0: nscoord deltaY = 0; michael@0: michael@0: // whether we did NOT reflow the previous line and thus we need to michael@0: // recompute the carried out margin before the line if we want to michael@0: // reflow it or if its previous margin is dirty michael@0: bool needToRecoverState = false; michael@0: // Float continuations were reflowed in ReflowPushedFloats michael@0: bool reflowedFloat = mFloats.NotEmpty() && michael@0: (mFloats.FirstChild()->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT); michael@0: bool lastLineMovedUp = false; michael@0: // We save up information about BR-clearance here michael@0: uint8_t inlineFloatBreakType = aState.mFloatBreakType; michael@0: michael@0: line_iterator line = begin_lines(), line_end = end_lines(); michael@0: michael@0: // Reflow the lines that are already ours michael@0: for ( ; line != line_end; ++line, aState.AdvanceToNextLine()) { michael@0: DumpLine(aState, line, deltaY, 0); michael@0: #ifdef DEBUG michael@0: AutoNoisyIndenter indent2(gNoisyReflow); michael@0: #endif michael@0: michael@0: if (selfDirty) michael@0: line->MarkDirty(); michael@0: michael@0: // This really sucks, but we have to look inside any blocks that have clear michael@0: // elements inside them. michael@0: // XXX what can we do smarter here? michael@0: if (!line->IsDirty() && line->IsBlock() && michael@0: (line->mFirstChild->GetStateBits() & NS_BLOCK_HAS_CLEAR_CHILDREN)) { michael@0: line->MarkDirty(); michael@0: } michael@0: michael@0: nsIFrame *replacedBlock = nullptr; michael@0: if (line->IsBlock() && michael@0: !nsBlockFrame::BlockCanIntersectFloats(line->mFirstChild)) { michael@0: replacedBlock = line->mFirstChild; michael@0: } michael@0: michael@0: // We have to reflow the line if it's a block whose clearance michael@0: // might have changed, so detect that. michael@0: if (!line->IsDirty() && michael@0: (line->GetBreakTypeBefore() != NS_STYLE_CLEAR_NONE || michael@0: replacedBlock)) { michael@0: nscoord curY = aState.mY; michael@0: // See where we would be after applying any clearance due to michael@0: // BRs. michael@0: if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { michael@0: curY = aState.ClearFloats(curY, inlineFloatBreakType); michael@0: } michael@0: michael@0: nscoord newY = michael@0: aState.ClearFloats(curY, line->GetBreakTypeBefore(), replacedBlock); michael@0: michael@0: if (line->HasClearance()) { michael@0: // Reflow the line if it might not have clearance anymore. michael@0: if (newY == curY michael@0: // aState.mY is the clearance point which should be the michael@0: // top border-edge of the block frame. If sliding the michael@0: // block by deltaY isn't going to put it in the predicted michael@0: // position, then we'd better reflow the line. michael@0: || newY != line->BStart() + deltaY) { michael@0: line->MarkDirty(); michael@0: } michael@0: } else { michael@0: // Reflow the line if the line might have clearance now. michael@0: if (curY != newY) { michael@0: line->MarkDirty(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We might have to reflow a line that is after a clearing BR. michael@0: if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { michael@0: aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType); michael@0: if (aState.mY != line->BStart() + deltaY) { michael@0: // SlideLine is not going to put the line where the clearance michael@0: // put it. Reflow the line to be sure. michael@0: line->MarkDirty(); michael@0: } michael@0: inlineFloatBreakType = NS_STYLE_CLEAR_NONE; michael@0: } michael@0: michael@0: bool previousMarginWasDirty = line->IsPreviousMarginDirty(); michael@0: if (previousMarginWasDirty) { michael@0: // If the previous margin is dirty, reflow the current line michael@0: line->MarkDirty(); michael@0: line->ClearPreviousMarginDirty(); michael@0: } else if (line->BEnd() + deltaY > aState.mBottomEdge) { michael@0: // Lines that aren't dirty but get slid past our height constraint must michael@0: // be reflowed. michael@0: line->MarkDirty(); michael@0: } michael@0: michael@0: // If we have a constrained height (i.e., breaking columns/pages), michael@0: // and the distance to the bottom might have changed, then we need michael@0: // to reflow any line that might have floats in it, both because the michael@0: // breakpoints within those floats may have changed and because we michael@0: // might have to push/pull the floats in their entirety. michael@0: // FIXME: What about a deltaY or height change that forces us to michael@0: // push lines? Why does that work? michael@0: if (!line->IsDirty() && michael@0: aState.mReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE && michael@0: (deltaY != 0 || aState.mReflowState.mFlags.mVResize || michael@0: aState.mReflowState.mFlags.mMustReflowPlaceholders) && michael@0: (line->IsBlock() || line->HasFloats() || line->HadFloatPushed())) { michael@0: line->MarkDirty(); michael@0: } michael@0: michael@0: if (!line->IsDirty()) { michael@0: // See if there's any reflow damage that requires that we mark the michael@0: // line dirty. michael@0: PropagateFloatDamage(aState, line, deltaY); michael@0: } michael@0: michael@0: // If the container width has changed reset the container width. If the michael@0: // line's writing mode is not ltr, or if the line is not left-aligned, also michael@0: // mark the line dirty. michael@0: if (aState.mContainerWidth != line->mContainerWidth) { michael@0: line->mContainerWidth = aState.mContainerWidth; michael@0: michael@0: bool isLastLine = line == mLines.back() && michael@0: !GetNextInFlow() && michael@0: NS_STYLE_TEXT_ALIGN_AUTO == StyleText()->mTextAlignLast; michael@0: uint8_t align = isLastLine ? michael@0: StyleText()->mTextAlign : StyleText()->mTextAlignLast; michael@0: michael@0: if (line->mWritingMode.IsVertical() || michael@0: !line->mWritingMode.IsBidiLTR() || michael@0: !IsAlignedLeft(align, michael@0: aState.mReflowState.mStyleVisibility->mDirection, michael@0: StyleTextReset()->mUnicodeBidi, this)) { michael@0: line->MarkDirty(); michael@0: } michael@0: } michael@0: michael@0: if (needToRecoverState && line->IsDirty()) { michael@0: // We need to reconstruct the bottom margin only if we didn't michael@0: // reflow the previous line and we do need to reflow (or repair michael@0: // the top position of) the next line. michael@0: aState.ReconstructMarginAbove(line); michael@0: } michael@0: michael@0: bool reflowedPrevLine = !needToRecoverState; michael@0: if (needToRecoverState) { michael@0: needToRecoverState = false; michael@0: michael@0: // Update aState.mPrevChild as if we had reflowed all of the frames in michael@0: // this line. michael@0: if (line->IsDirty()) { michael@0: NS_ASSERTION(line->mFirstChild->GetPrevSibling() == michael@0: line.prev()->LastChild(), "unexpected line frames"); michael@0: aState.mPrevChild = line->mFirstChild->GetPrevSibling(); michael@0: } michael@0: } michael@0: michael@0: // Now repair the line and update |aState.mY| by calling michael@0: // |ReflowLine| or |SlideLine|. michael@0: // If we're going to reflow everything again, then no need to reflow michael@0: // the dirty line ... unless the line has floats, in which case we'd michael@0: // better reflow it now to refresh its float cache, which may contain michael@0: // dangling frame pointers! Ugh! This reflow of the line may be michael@0: // incorrect because we skipped reflowing previous lines (e.g., floats michael@0: // may be placed incorrectly), but that's OK because we'll mark the michael@0: // line dirty below under "if (aState.mReflowState.mDiscoveredClearance..." michael@0: if (line->IsDirty() && (line->HasFloats() || !willReflowAgain)) { michael@0: lastLineMovedUp = true; michael@0: michael@0: bool maybeReflowingForFirstTime = michael@0: line->IStart() == 0 && line->BStart() == 0 && michael@0: line->ISize() == 0 && line->BSize() == 0; michael@0: michael@0: // Compute the dirty lines "before" BEnd, after factoring in michael@0: // the running deltaY value - the running value is implicit in michael@0: // aState.mY. michael@0: nscoord oldY = line->BStart(); michael@0: nscoord oldYMost = line->BEnd(); michael@0: michael@0: NS_ASSERTION(!willReflowAgain || !line->IsBlock(), michael@0: "Don't reflow blocks while willReflowAgain is true, reflow of block abs-pos children depends on this"); michael@0: michael@0: // Reflow the dirty line. If it's an incremental reflow, then force michael@0: // it to invalidate the dirty area if necessary michael@0: rv = ReflowLine(aState, line, &keepGoing); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aState.mReflowState.WillReflowAgainForClearance()) { michael@0: line->MarkDirty(); michael@0: willReflowAgain = true; michael@0: // Note that once we've entered this state, every line that gets here michael@0: // (e.g. because it has floats) gets marked dirty and reflowed again. michael@0: // in the next pass. This is important, see above. michael@0: } michael@0: michael@0: if (line->HasFloats()) { michael@0: reflowedFloat = true; michael@0: } michael@0: michael@0: if (!keepGoing) { michael@0: DumpLine(aState, line, deltaY, -1); michael@0: if (0 == line->GetChildCount()) { michael@0: DeleteLine(aState, line, line_end); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: // Test to see whether the margin that should be carried out michael@0: // to the next line (NL) might have changed. In ReflowBlockFrame michael@0: // we call nextLine->MarkPreviousMarginDirty if the block's michael@0: // actual carried-out bottom margin changed. So here we only michael@0: // need to worry about the following effects: michael@0: // 1) the line was just created, and it might now be blocking michael@0: // a carried-out bottom margin from previous lines that michael@0: // used to reach NL from reaching NL michael@0: // 2) the line used to be empty, and is now not empty, michael@0: // thus blocking a carried-out bottom margin from previous lines michael@0: // that used to reach NL from reaching NL michael@0: // 3) the line wasn't empty, but now is, so a carried-out michael@0: // bottom margin from previous lines that didn't used to reach NL michael@0: // now does michael@0: // 4) the line might have changed in a way that affects NL's michael@0: // ShouldApplyTopMargin decision. The three things that matter michael@0: // are the line's emptiness, its adjacency to the top of the block, michael@0: // and whether it has clearance (the latter only matters if the block michael@0: // was and is adjacent to the top and empty). michael@0: // michael@0: // If the line is empty now, we can't reliably tell if the line was empty michael@0: // before, so we just assume it was and do nextLine->MarkPreviousMarginDirty. michael@0: // This means the checks in 4) are redundant; if the line is empty now michael@0: // we don't need to check 4), but if the line is not empty now and we're sure michael@0: // it wasn't empty before, any adjacency and clearance changes are irrelevant michael@0: // to the result of nextLine->ShouldApplyTopMargin. michael@0: if (line.next() != end_lines()) { michael@0: bool maybeWasEmpty = oldY == line.next()->BStart(); michael@0: bool isEmpty = line->CachedIsEmpty(); michael@0: if (maybeReflowingForFirstTime /*1*/ || michael@0: (isEmpty || maybeWasEmpty) /*2/3/4*/) { michael@0: line.next()->MarkPreviousMarginDirty(); michael@0: // since it's marked dirty, nobody will care about |deltaY| michael@0: } michael@0: } michael@0: michael@0: // If the line was just reflowed for the first time, then its michael@0: // old mBounds cannot be trusted so this deltaY computation is michael@0: // bogus. But that's OK because we just did michael@0: // MarkPreviousMarginDirty on the next line which will force it michael@0: // to be reflowed, so this computation of deltaY will not be michael@0: // used. michael@0: deltaY = line->BEnd() - oldYMost; michael@0: michael@0: // Now do an interrupt check. We want to do this only in the case when we michael@0: // actually reflow the line, so that if we get back in here we'll get michael@0: // further on the reflow before interrupting. michael@0: aState.mPresContext->CheckForInterrupt(this); michael@0: } else { michael@0: aState.mOverflowTracker->Skip(line->mFirstChild, aState.mReflowStatus); michael@0: // Nop except for blocks (we don't create overflow container michael@0: // continuations for any inlines atm), so only checking mFirstChild michael@0: // is enough michael@0: michael@0: lastLineMovedUp = deltaY < 0; michael@0: michael@0: if (deltaY != 0) michael@0: SlideLine(aState, line, deltaY); michael@0: else michael@0: repositionViews = true; michael@0: michael@0: NS_ASSERTION(!line->IsDirty() || !line->HasFloats(), michael@0: "Possibly stale float cache here!"); michael@0: if (willReflowAgain && line->IsBlock()) { michael@0: // If we're going to reflow everything again, and this line is a block, michael@0: // then there is no need to recover float state. The line may contain michael@0: // other lines with floats, but in that case RecoverStateFrom would only michael@0: // add floats to the float manager. We don't need to do that because michael@0: // everything's going to get reflowed again "for real". Calling michael@0: // RecoverStateFrom in this situation could be lethal because the michael@0: // block's descendant lines may have float caches containing dangling michael@0: // frame pointers. Ugh! michael@0: // If this line is inline, then we need to recover its state now michael@0: // to make sure that we don't forget to move its floats by deltaY. michael@0: } else { michael@0: // XXX EVIL O(N^2) EVIL michael@0: aState.RecoverStateFrom(line, deltaY); michael@0: } michael@0: michael@0: // Keep mY up to date in case we're propagating reflow damage michael@0: // and also because our final height may depend on it. If the michael@0: // line is inlines, then only update mY if the line is not michael@0: // empty, because that's what PlaceLine does. (Empty blocks may michael@0: // want to update mY, e.g. if they have clearance.) michael@0: if (line->IsBlock() || !line->CachedIsEmpty()) { michael@0: aState.mY = line->BEnd(); michael@0: } michael@0: michael@0: needToRecoverState = true; michael@0: michael@0: if (reflowedPrevLine && !line->IsBlock() && michael@0: aState.mPresContext->HasPendingInterrupt()) { michael@0: // Need to make sure to pull overflows from any prev-in-flows michael@0: for (nsIFrame* inlineKid = line->mFirstChild; inlineKid; michael@0: inlineKid = inlineKid->GetFirstPrincipalChild()) { michael@0: inlineKid->PullOverflowsFromPrevInFlow(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Record if we need to clear floats before reflowing the next michael@0: // line. Note that inlineFloatBreakType will be handled and michael@0: // cleared before the next line is processed, so there is no michael@0: // need to combine break types here. michael@0: if (line->HasFloatBreakAfter()) { michael@0: inlineFloatBreakType = line->GetBreakTypeAfter(); michael@0: } michael@0: michael@0: if (LineHasClear(line.get())) { michael@0: foundAnyClears = true; michael@0: } michael@0: michael@0: DumpLine(aState, line, deltaY, -1); michael@0: michael@0: if (aState.mPresContext->HasPendingInterrupt()) { michael@0: willReflowAgain = true; michael@0: // Another option here might be to leave |line| clean if michael@0: // !HasPendingInterrupt() before the CheckForInterrupt() call, since in michael@0: // that case the line really did reflow as it should have. Not sure michael@0: // whether that would be safe, so doing this for now instead. Also not michael@0: // sure whether we really want to mark all lines dirty after an michael@0: // interrupt, but until we get better at propagating float damage we michael@0: // really do need to do it this way; see comments inside MarkLineDirty. michael@0: MarkLineDirtyForInterrupt(line); michael@0: } michael@0: } michael@0: michael@0: // Handle BR-clearance from the last line of the block michael@0: if (inlineFloatBreakType != NS_STYLE_CLEAR_NONE) { michael@0: aState.mY = aState.ClearFloats(aState.mY, inlineFloatBreakType); michael@0: } michael@0: michael@0: if (needToRecoverState) { michael@0: // Is this expensive? michael@0: aState.ReconstructMarginAbove(line); michael@0: michael@0: // Update aState.mPrevChild as if we had reflowed all of the frames in michael@0: // the last line. michael@0: NS_ASSERTION(line == line_end || line->mFirstChild->GetPrevSibling() == michael@0: line.prev()->LastChild(), "unexpected line frames"); michael@0: aState.mPrevChild = michael@0: line == line_end ? mFrames.LastChild() : line->mFirstChild->GetPrevSibling(); michael@0: } michael@0: michael@0: // Should we really have to do this? michael@0: if (repositionViews) michael@0: nsContainerFrame::PlaceFrameView(this); michael@0: michael@0: // We can skip trying to pull up the next line if our height is constrained michael@0: // (so we can report being incomplete) and there is no next in flow or we michael@0: // were told not to or we know it will be futile, i.e., michael@0: // -- the next in flow is not changing michael@0: // -- and we cannot have added more space for its first line to be michael@0: // pulled up into, michael@0: // -- it's an incremental reflow of a descendant michael@0: // -- and we didn't reflow any floats (so the available space michael@0: // didn't change) michael@0: // -- my chain of next-in-flows either has no first line, or its first michael@0: // line isn't dirty. michael@0: bool heightConstrained = michael@0: aState.mReflowState.AvailableHeight() != NS_UNCONSTRAINEDSIZE; michael@0: bool skipPull = willReflowAgain && heightConstrained; michael@0: if (!skipPull && heightConstrained && aState.mNextInFlow && michael@0: (aState.mReflowState.mFlags.mNextInFlowUntouched && michael@0: !lastLineMovedUp && michael@0: !(GetStateBits() & NS_FRAME_IS_DIRTY) && michael@0: !reflowedFloat)) { michael@0: // We'll place lineIter at the last line of this block, so that michael@0: // nsBlockInFlowLineIterator::Next() will take us to the first michael@0: // line of my next-in-flow-chain. (But first, check that I michael@0: // have any lines -- if I don't, just bail out of this michael@0: // optimization.) michael@0: line_iterator lineIter = this->end_lines(); michael@0: if (lineIter != this->begin_lines()) { michael@0: lineIter--; // I have lines; step back from dummy iterator to last line. michael@0: nsBlockInFlowLineIterator bifLineIter(this, lineIter); michael@0: michael@0: // Check for next-in-flow-chain's first line. michael@0: // (First, see if there is such a line, and second, see if it's clean) michael@0: if (!bifLineIter.Next() || michael@0: !bifLineIter.GetLine()->IsDirty()) { michael@0: skipPull=true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (skipPull && aState.mNextInFlow) { michael@0: NS_ASSERTION(heightConstrained, "Height should be constrained here\n"); michael@0: if (IS_TRUE_OVERFLOW_CONTAINER(aState.mNextInFlow)) michael@0: NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus); michael@0: else michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: } michael@0: michael@0: if (!skipPull && aState.mNextInFlow) { michael@0: // Pull data from a next-in-flow if there's still room for more michael@0: // content here. michael@0: while (keepGoing && aState.mNextInFlow) { michael@0: // Grab first line from our next-in-flow michael@0: nsBlockFrame* nextInFlow = aState.mNextInFlow; michael@0: nsLineBox* pulledLine; michael@0: nsFrameList pulledFrames; michael@0: if (!nextInFlow->mLines.empty()) { michael@0: RemoveFirstLine(nextInFlow->mLines, nextInFlow->mFrames, michael@0: &pulledLine, &pulledFrames); michael@0: } else { michael@0: // Grab an overflow line if there are any michael@0: FrameLines* overflowLines = nextInFlow->GetOverflowLines(); michael@0: if (!overflowLines) { michael@0: aState.mNextInFlow = michael@0: static_cast(nextInFlow->GetNextInFlow()); michael@0: continue; michael@0: } michael@0: bool last = michael@0: RemoveFirstLine(overflowLines->mLines, overflowLines->mFrames, michael@0: &pulledLine, &pulledFrames); michael@0: if (last) { michael@0: nextInFlow->DestroyOverflowLines(); michael@0: } michael@0: } michael@0: michael@0: if (pulledFrames.IsEmpty()) { michael@0: // The line is empty. Try the next one. michael@0: NS_ASSERTION(pulledLine->GetChildCount() == 0 && michael@0: !pulledLine->mFirstChild, "bad empty line"); michael@0: nextInFlow->FreeLineBox(pulledLine); michael@0: continue; michael@0: } michael@0: michael@0: if (pulledLine == nextInFlow->GetLineCursor()) { michael@0: nextInFlow->ClearLineCursor(); michael@0: } michael@0: ReparentFrames(pulledFrames, nextInFlow, this); michael@0: michael@0: NS_ASSERTION(pulledFrames.LastChild() == pulledLine->LastChild(), michael@0: "Unexpected last frame"); michael@0: NS_ASSERTION(aState.mPrevChild || mLines.empty(), "should have a prevchild here"); michael@0: NS_ASSERTION(aState.mPrevChild == mFrames.LastChild(), michael@0: "Incorrect aState.mPrevChild before inserting line at end"); michael@0: michael@0: // Shift pulledLine's frames into our mFrames list. michael@0: mFrames.AppendFrames(nullptr, pulledFrames); michael@0: michael@0: // Add line to our line list, and set its last child as our new prev-child michael@0: line = mLines.before_insert(end_lines(), pulledLine); michael@0: aState.mPrevChild = mFrames.LastChild(); michael@0: michael@0: // Reparent floats whose placeholders are in the line. michael@0: ReparentFloats(pulledLine->mFirstChild, nextInFlow, true); michael@0: michael@0: DumpLine(aState, pulledLine, deltaY, 0); michael@0: #ifdef DEBUG michael@0: AutoNoisyIndenter indent2(gNoisyReflow); michael@0: #endif michael@0: michael@0: if (aState.mPresContext->HasPendingInterrupt()) { michael@0: MarkLineDirtyForInterrupt(line); michael@0: } else { michael@0: // Now reflow it and any lines that it makes during it's reflow michael@0: // (we have to loop here because reflowing the line may cause a new michael@0: // line to be created; see SplitLine's callers for examples of michael@0: // when this happens). michael@0: while (line != end_lines()) { michael@0: rv = ReflowLine(aState, line, &keepGoing); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (aState.mReflowState.WillReflowAgainForClearance()) { michael@0: line->MarkDirty(); michael@0: keepGoing = false; michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: break; michael@0: } michael@0: michael@0: DumpLine(aState, line, deltaY, -1); michael@0: if (!keepGoing) { michael@0: if (0 == line->GetChildCount()) { michael@0: DeleteLine(aState, line, line_end); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: if (LineHasClear(line.get())) { michael@0: foundAnyClears = true; michael@0: } michael@0: michael@0: if (aState.mPresContext->CheckForInterrupt(this)) { michael@0: MarkLineDirtyForInterrupt(line); michael@0: break; michael@0: } michael@0: michael@0: // If this is an inline frame then its time to stop michael@0: ++line; michael@0: aState.AdvanceToNextLine(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (NS_FRAME_IS_NOT_COMPLETE(aState.mReflowStatus)) { michael@0: aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: } //XXXfr shouldn't set this flag when nextinflow has no lines michael@0: } michael@0: michael@0: // Handle an odd-ball case: a list-item with no lines michael@0: if (HasOutsideBullet() && mLines.empty()) { michael@0: nsHTMLReflowMetrics metrics(aState.mReflowState); michael@0: nsIFrame* bullet = GetOutsideBullet(); michael@0: ReflowBullet(bullet, aState, metrics, michael@0: aState.mReflowState.ComputedPhysicalBorderPadding().top); michael@0: NS_ASSERTION(!BulletIsEmpty() || metrics.Height() == 0, michael@0: "empty bullet took up space"); michael@0: michael@0: if (!BulletIsEmpty()) { michael@0: // There are no lines so we have to fake up some y motion so that michael@0: // we end up with *some* height. michael@0: michael@0: if (metrics.TopAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) { michael@0: nscoord ascent; michael@0: if (nsLayoutUtils::GetFirstLineBaseline(bullet, &ascent)) { michael@0: metrics.SetTopAscent(ascent); michael@0: } else { michael@0: metrics.SetTopAscent(metrics.Height()); michael@0: } michael@0: } michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), michael@0: nsLayoutUtils::FontSizeInflationFor(this)); michael@0: aState.mReflowState.rendContext->SetFont(fm); // FIXME: needed? michael@0: michael@0: nscoord minAscent = michael@0: nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight); michael@0: nscoord minDescent = aState.mMinLineHeight - minAscent; michael@0: michael@0: aState.mY += std::max(minAscent, metrics.TopAscent()) + michael@0: std::max(minDescent, metrics.Height() - metrics.TopAscent()); michael@0: michael@0: nscoord offset = minAscent - metrics.TopAscent(); michael@0: if (offset > 0) { michael@0: bullet->SetRect(bullet->GetRect() + nsPoint(0, offset)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (foundAnyClears) { michael@0: AddStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); michael@0: } else { michael@0: RemoveStateBits(NS_BLOCK_HAS_CLEAR_CHILDREN); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: VerifyLines(true); michael@0: VerifyOverflowSituation(); michael@0: if (gNoisyReflow) { michael@0: IndentBy(stdout, gNoiseIndent - 1); michael@0: ListTag(stdout); michael@0: printf(": done reflowing dirty lines (status=%x)\n", michael@0: aState.mReflowStatus); michael@0: } michael@0: #endif michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static void MarkAllDescendantLinesDirty(nsBlockFrame* aBlock) michael@0: { michael@0: nsLineList::iterator line = aBlock->begin_lines(); michael@0: nsLineList::iterator endLine = aBlock->end_lines(); michael@0: while (line != endLine) { michael@0: if (line->IsBlock()) { michael@0: nsIFrame* f = line->mFirstChild; michael@0: nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(f); michael@0: if (bf) { michael@0: MarkAllDescendantLinesDirty(bf); michael@0: } michael@0: } michael@0: line->MarkDirty(); michael@0: ++line; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::MarkLineDirtyForInterrupt(nsLineBox* aLine) michael@0: { michael@0: aLine->MarkDirty(); michael@0: michael@0: // Just checking NS_FRAME_IS_DIRTY is ok, because we've already michael@0: // marked the lines that need to be marked dirty based on our michael@0: // vertical resize stuff. So we'll definitely reflow all those kids; michael@0: // the only question is how they should behave. michael@0: if (GetStateBits() & NS_FRAME_IS_DIRTY) { michael@0: // Mark all our child frames dirty so we make sure to reflow them michael@0: // later. michael@0: int32_t n = aLine->GetChildCount(); michael@0: for (nsIFrame* f = aLine->mFirstChild; n > 0; michael@0: f = f->GetNextSibling(), --n) { michael@0: f->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: } michael@0: // And mark all the floats whose reflows we might be skipping dirty too. michael@0: if (aLine->HasFloats()) { michael@0: for (nsFloatCache* fc = aLine->GetFirstFloat(); fc; fc = fc->Next()) { michael@0: fc->mFloat->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: } else { michael@0: // Dirty all the descendant lines of block kids to handle float damage, michael@0: // since our nsFloatManager will go away by the next time we're reflowing. michael@0: // XXXbz Can we do something more like what PropagateFloatDamage does? michael@0: // Would need to sort out the exact business with mBlockDelta for that.... michael@0: // This marks way too much dirty. If we ever make this better, revisit michael@0: // which lines we mark dirty in the interrupt case in ReflowDirtyLines. michael@0: nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(aLine->mFirstChild); michael@0: if (bf) { michael@0: MarkAllDescendantLinesDirty(bf); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::DeleteLine(nsBlockReflowState& aState, michael@0: nsLineList::iterator aLine, michael@0: nsLineList::iterator aLineEnd) michael@0: { michael@0: NS_PRECONDITION(0 == aLine->GetChildCount(), "can't delete !empty line"); michael@0: if (0 == aLine->GetChildCount()) { michael@0: NS_ASSERTION(aState.mCurrentLine == aLine, michael@0: "using function more generally than designed, " michael@0: "but perhaps OK now"); michael@0: nsLineBox* line = aLine; michael@0: aLine = mLines.erase(aLine); michael@0: FreeLineBox(line); michael@0: // Mark the previous margin of the next line dirty since we need to michael@0: // recompute its top position. michael@0: if (aLine != aLineEnd) michael@0: aLine->MarkPreviousMarginDirty(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Reflow a line. The line will either contain a single block frame michael@0: * or contain 1 or more inline frames. aKeepReflowGoing indicates michael@0: * whether or not the caller should continue to reflow more lines. michael@0: */ michael@0: nsresult michael@0: nsBlockFrame::ReflowLine(nsBlockReflowState& aState, michael@0: line_iterator aLine, michael@0: bool* aKeepReflowGoing) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: NS_ABORT_IF_FALSE(aLine->GetChildCount(), "reflowing empty line"); michael@0: michael@0: // Setup the line-layout for the new line michael@0: aState.mCurrentLine = aLine; michael@0: aLine->ClearDirty(); michael@0: aLine->InvalidateCachedIsEmpty(); michael@0: aLine->ClearHadFloatPushed(); michael@0: michael@0: // Now that we know what kind of line we have, reflow it michael@0: if (aLine->IsBlock()) { michael@0: rv = ReflowBlockFrame(aState, aLine, aKeepReflowGoing); michael@0: } else { michael@0: aLine->SetLineWrapped(false); michael@0: rv = ReflowInlineFrames(aState, aLine, aKeepReflowGoing); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsBlockFrame::PullFrame(nsBlockReflowState& aState, michael@0: line_iterator aLine) michael@0: { michael@0: // First check our remaining lines. michael@0: if (end_lines() != aLine.next()) { michael@0: return PullFrameFrom(aLine, this, aLine.next()); michael@0: } michael@0: michael@0: NS_ASSERTION(!GetOverflowLines(), michael@0: "Our overflow lines should have been removed at the start of reflow"); michael@0: michael@0: // Try each next-in-flow. michael@0: nsBlockFrame* nextInFlow = aState.mNextInFlow; michael@0: while (nextInFlow) { michael@0: if (nextInFlow->mLines.empty()) { michael@0: nextInFlow->DrainSelfOverflowList(); michael@0: } michael@0: if (!nextInFlow->mLines.empty()) { michael@0: return PullFrameFrom(aLine, nextInFlow, nextInFlow->mLines.begin()); michael@0: } michael@0: nextInFlow = static_cast(nextInFlow->GetNextInFlow()); michael@0: aState.mNextInFlow = nextInFlow; michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsBlockFrame::PullFrameFrom(nsLineBox* aLine, michael@0: nsBlockFrame* aFromContainer, michael@0: nsLineList::iterator aFromLine) michael@0: { michael@0: nsLineBox* fromLine = aFromLine; michael@0: NS_ABORT_IF_FALSE(fromLine, "bad line to pull from"); michael@0: NS_ABORT_IF_FALSE(fromLine->GetChildCount(), "empty line"); michael@0: NS_ABORT_IF_FALSE(aLine->GetChildCount(), "empty line"); michael@0: michael@0: NS_ASSERTION(fromLine->IsBlock() == fromLine->mFirstChild->IsBlockOutside(), michael@0: "Disagreement about whether it's a block or not"); michael@0: michael@0: if (fromLine->IsBlock()) { michael@0: // If our line is not empty and the child in aFromLine is a block michael@0: // then we cannot pull up the frame into this line. In this case michael@0: // we stop pulling. michael@0: return nullptr; michael@0: } michael@0: // Take frame from fromLine michael@0: nsIFrame* frame = fromLine->mFirstChild; michael@0: nsIFrame* newFirstChild = frame->GetNextSibling(); michael@0: michael@0: if (aFromContainer != this) { michael@0: // The frame is being pulled from a next-in-flow; therefore we michael@0: // need to add it to our sibling list. michael@0: MOZ_ASSERT(aLine == mLines.back()); michael@0: MOZ_ASSERT(aFromLine == aFromContainer->mLines.begin(), michael@0: "should only pull from first line"); michael@0: aFromContainer->mFrames.RemoveFrame(frame); michael@0: michael@0: // When pushing and pulling frames we need to check for whether any michael@0: // views need to be reparented. michael@0: ReparentFrame(frame, aFromContainer, this); michael@0: mFrames.AppendFrame(nullptr, frame); michael@0: michael@0: // The frame might have (or contain) floats that need to be brought michael@0: // over too. (pass 'false' since there are no siblings to check) michael@0: ReparentFloats(frame, aFromContainer, false); michael@0: } else { michael@0: MOZ_ASSERT(aLine == aFromLine.prev()); michael@0: } michael@0: michael@0: aLine->NoteFrameAdded(frame); michael@0: fromLine->NoteFrameRemoved(frame); michael@0: michael@0: if (fromLine->GetChildCount() > 0) { michael@0: // Mark line dirty now that we pulled a child michael@0: fromLine->MarkDirty(); michael@0: fromLine->mFirstChild = newFirstChild; michael@0: } else { michael@0: // Free up the fromLine now that it's empty. michael@0: // Its bounds might need to be redrawn, though. michael@0: if (aFromLine.next() != aFromContainer->mLines.end()) { michael@0: aFromLine.next()->MarkPreviousMarginDirty(); michael@0: } michael@0: aFromContainer->mLines.erase(aFromLine); michael@0: // aFromLine is now invalid michael@0: aFromContainer->FreeLineBox(fromLine); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: VerifyLines(true); michael@0: VerifyOverflowSituation(); michael@0: #endif michael@0: michael@0: return frame; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::SlideLine(nsBlockReflowState& aState, michael@0: nsLineBox* aLine, nscoord aDY) michael@0: { michael@0: NS_PRECONDITION(aDY != 0, "why slide a line nowhere?"); michael@0: michael@0: // Adjust line state michael@0: aLine->SlideBy(aDY, aState.mContainerWidth); michael@0: michael@0: // Adjust the frames in the line michael@0: nsIFrame* kid = aLine->mFirstChild; michael@0: if (!kid) { michael@0: return; michael@0: } michael@0: michael@0: if (aLine->IsBlock()) { michael@0: if (aDY) { michael@0: kid->MovePositionBy(nsPoint(0, aDY)); michael@0: } michael@0: michael@0: // Make sure the frame's view and any child views are updated michael@0: nsContainerFrame::PlaceFrameView(kid); michael@0: } michael@0: else { michael@0: // Adjust the Y coordinate of the frames in the line. michael@0: // Note: we need to re-position views even if aDY is 0, because michael@0: // one of our parent frames may have moved and so the view's position michael@0: // relative to its parent may have changed michael@0: int32_t n = aLine->GetChildCount(); michael@0: while (--n >= 0) { michael@0: if (aDY) { michael@0: kid->MovePositionBy(nsPoint(0, aDY)); michael@0: } michael@0: // Make sure the frame's view and any child views are updated michael@0: nsContainerFrame::PlaceFrameView(kid); michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: nsresult rv = nsBlockFrameSuper::AttributeChanged(aNameSpaceID, michael@0: aAttribute, aModType); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (nsGkAtoms::start == aAttribute || michael@0: (nsGkAtoms::reversed == aAttribute && mContent->IsHTML(nsGkAtoms::ol))) { michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // XXX Not sure if this is necessary anymore michael@0: if (RenumberLists(presContext)) { michael@0: presContext->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eStyleChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: } michael@0: else if (nsGkAtoms::value == aAttribute) { michael@0: const nsStyleDisplay* styleDisplay = StyleDisplay(); michael@0: if (NS_STYLE_DISPLAY_LIST_ITEM == styleDisplay->mDisplay) { michael@0: // Search for the closest ancestor that's a block frame. We michael@0: // make the assumption that all related list items share a michael@0: // common block parent. michael@0: // XXXldb I think that's a bad assumption. michael@0: nsBlockFrame* blockParent = nsLayoutUtils::FindNearestBlockAncestor(this); michael@0: michael@0: // Tell the enclosing block frame to renumber list items within michael@0: // itself michael@0: if (nullptr != blockParent) { michael@0: nsPresContext* presContext = PresContext(); michael@0: // XXX Not sure if this is necessary anymore michael@0: if (blockParent->RenumberLists(presContext)) { michael@0: presContext->PresShell()-> michael@0: FrameNeedsReflow(blockParent, nsIPresShell::eStyleChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: static inline bool michael@0: IsNonAutoNonZeroHeight(const nsStyleCoord& aCoord) michael@0: { michael@0: if (aCoord.GetUnit() == eStyleUnit_Auto) michael@0: return false; michael@0: if (aCoord.IsCoordPercentCalcUnit()) { michael@0: // If we evaluate the length/percent/calc at a percentage basis of michael@0: // both nscoord_MAX and 0, and it's zero both ways, then it's a zero michael@0: // length, percent, or combination thereof. Test > 0 so we clamp michael@0: // negative calc() results to 0. michael@0: return nsRuleNode::ComputeCoordPercentCalc(aCoord, nscoord_MAX) > 0 || michael@0: nsRuleNode::ComputeCoordPercentCalc(aCoord, 0) > 0; michael@0: } michael@0: NS_ABORT_IF_FALSE(false, "unexpected unit for height or min-height"); michael@0: return true; michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsBlockFrame::IsSelfEmpty() michael@0: { michael@0: // Blocks which are margin-roots (including inline-blocks) cannot be treated michael@0: // as empty for margin-collapsing and other purposes. They're more like michael@0: // replaced elements. michael@0: if (GetStateBits() & NS_BLOCK_MARGIN_ROOT) michael@0: return false; michael@0: michael@0: const nsStylePosition* position = StylePosition(); michael@0: michael@0: if (IsNonAutoNonZeroHeight(position->mMinHeight) || michael@0: IsNonAutoNonZeroHeight(position->mHeight)) michael@0: return false; michael@0: michael@0: const nsStyleBorder* border = StyleBorder(); michael@0: const nsStylePadding* padding = StylePadding(); michael@0: if (border->GetComputedBorderWidth(NS_SIDE_TOP) != 0 || michael@0: border->GetComputedBorderWidth(NS_SIDE_BOTTOM) != 0 || michael@0: !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetTop()) || michael@0: !nsLayoutUtils::IsPaddingZero(padding->mPadding.GetBottom())) { michael@0: return false; michael@0: } michael@0: michael@0: if (HasOutsideBullet() && !BulletIsEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::CachedIsEmpty() michael@0: { michael@0: if (!IsSelfEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) michael@0: { michael@0: if (!line->CachedIsEmpty()) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::IsEmpty() michael@0: { michael@0: if (!IsSelfEmpty()) { michael@0: return false; michael@0: } michael@0: michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) michael@0: { michael@0: if (!line->IsEmpty()) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::ShouldApplyTopMargin(nsBlockReflowState& aState, michael@0: nsLineBox* aLine) michael@0: { michael@0: if (aState.GetFlag(BRS_APPLYTOPMARGIN)) { michael@0: // Apply short-circuit check to avoid searching the line list michael@0: return true; michael@0: } michael@0: michael@0: if (!aState.IsAdjacentWithTop()) { michael@0: // If we aren't at the top Y coordinate then something of non-zero michael@0: // height must have been placed. Therefore the childs top-margin michael@0: // applies. michael@0: aState.SetFlag(BRS_APPLYTOPMARGIN, true); michael@0: return true; michael@0: } michael@0: michael@0: // Determine if this line is "essentially" the first line michael@0: line_iterator line = begin_lines(); michael@0: if (aState.GetFlag(BRS_HAVELINEADJACENTTOTOP)) { michael@0: line = aState.mLineAdjacentToTop; michael@0: } michael@0: while (line != aLine) { michael@0: if (!line->CachedIsEmpty() || line->HasClearance()) { michael@0: // A line which precedes aLine is non-empty, or has clearance, michael@0: // so therefore the top margin applies. michael@0: aState.SetFlag(BRS_APPLYTOPMARGIN, true); michael@0: return true; michael@0: } michael@0: // No need to apply the top margin if the line has floats. We michael@0: // should collapse anyway (bug 44419) michael@0: ++line; michael@0: aState.SetFlag(BRS_HAVELINEADJACENTTOTOP, true); michael@0: aState.mLineAdjacentToTop = line; michael@0: } michael@0: michael@0: // The line being reflowed is "essentially" the first line in the michael@0: // block. Therefore its top-margin will be collapsed by the michael@0: // generational collapsing logic with its parent (us). michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState, michael@0: line_iterator aLine, michael@0: bool* aKeepReflowGoing) michael@0: { michael@0: NS_PRECONDITION(*aKeepReflowGoing, "bad caller"); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsIFrame* frame = aLine->mFirstChild; michael@0: if (!frame) { michael@0: NS_ASSERTION(false, "program error - unexpected empty line"); michael@0: return NS_ERROR_NULL_POINTER; michael@0: } michael@0: michael@0: // Prepare the block reflow engine michael@0: const nsStyleDisplay* display = frame->StyleDisplay(); michael@0: nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState); michael@0: michael@0: uint8_t breakType = display->mBreakType; michael@0: if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) { michael@0: breakType = nsLayoutUtils::CombineBreakType(breakType, michael@0: aState.mFloatBreakType); michael@0: aState.mFloatBreakType = NS_STYLE_CLEAR_NONE; michael@0: } michael@0: michael@0: // Clear past floats before the block if the clear style is not none michael@0: aLine->SetBreakTypeBefore(breakType); michael@0: michael@0: // See if we should apply the top margin. If the block frame being michael@0: // reflowed is a continuation (non-null prev-in-flow) then we don't michael@0: // apply its top margin because it's not significant. Otherwise, dig michael@0: // deeper. michael@0: bool applyTopMargin = michael@0: !frame->GetPrevInFlow() && ShouldApplyTopMargin(aState, aLine); michael@0: michael@0: if (applyTopMargin) { michael@0: // The HasClearance setting is only valid if ShouldApplyTopMargin michael@0: // returned false (in which case the top-margin-root set our michael@0: // clearance flag). Otherwise clear it now. We'll set it later on michael@0: // ourselves if necessary. michael@0: aLine->ClearHasClearance(); michael@0: } michael@0: bool treatWithClearance = aLine->HasClearance(); michael@0: michael@0: bool mightClearFloats = breakType != NS_STYLE_CLEAR_NONE; michael@0: nsIFrame *replacedBlock = nullptr; michael@0: if (!nsBlockFrame::BlockCanIntersectFloats(frame)) { michael@0: mightClearFloats = true; michael@0: replacedBlock = frame; michael@0: } michael@0: michael@0: // If our top margin was counted as part of some parents top-margin michael@0: // collapse and we are being speculatively reflowed assuming this michael@0: // frame DID NOT need clearance, then we need to check that michael@0: // assumption. michael@0: if (!treatWithClearance && !applyTopMargin && mightClearFloats && michael@0: aState.mReflowState.mDiscoveredClearance) { michael@0: nscoord curY = aState.mY + aState.mPrevBottomMargin.get(); michael@0: nscoord clearY = aState.ClearFloats(curY, breakType, replacedBlock); michael@0: if (clearY != curY) { michael@0: // Looks like that assumption was invalid, we do need michael@0: // clearance. Tell our ancestor so it can reflow again. It is michael@0: // responsible for actually setting our clearance flag before michael@0: // the next reflow. michael@0: treatWithClearance = true; michael@0: // Only record the first frame that requires clearance michael@0: if (!*aState.mReflowState.mDiscoveredClearance) { michael@0: *aState.mReflowState.mDiscoveredClearance = frame; michael@0: } michael@0: aState.mPrevChild = frame; michael@0: // Exactly what we do now is flexible since we'll definitely be michael@0: // reflowed. michael@0: return NS_OK; michael@0: } michael@0: } michael@0: if (treatWithClearance) { michael@0: applyTopMargin = true; michael@0: } michael@0: michael@0: nsIFrame* clearanceFrame = nullptr; michael@0: nscoord startingY = aState.mY; michael@0: nsCollapsingMargin incomingMargin = aState.mPrevBottomMargin; michael@0: nscoord clearance; michael@0: // Save the original position of the frame so that we can reposition michael@0: // its view as needed. michael@0: nsPoint originalPosition = frame->GetPosition(); michael@0: while (true) { michael@0: clearance = 0; michael@0: nscoord topMargin = 0; michael@0: bool mayNeedRetry = false; michael@0: bool clearedFloats = false; michael@0: if (applyTopMargin) { michael@0: // Precompute the blocks top margin value so that we can get the michael@0: // correct available space (there might be a float that's michael@0: // already been placed below the aState.mPrevBottomMargin michael@0: michael@0: // Setup a reflowState to get the style computed margin-top michael@0: // value. We'll use a reason of `resize' so that we don't fudge michael@0: // any incremental reflow state. michael@0: michael@0: // The availSpace here is irrelevant to our needs - all we want michael@0: // out if this setup is the margin-top value which doesn't depend michael@0: // on the childs available space. michael@0: // XXX building a complete nsHTMLReflowState just to get the margin-top michael@0: // seems like a waste. And we do this for almost every block! michael@0: nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE); michael@0: nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, michael@0: frame, availSpace); michael@0: michael@0: if (treatWithClearance) { michael@0: aState.mY += aState.mPrevBottomMargin.get(); michael@0: aState.mPrevBottomMargin.Zero(); michael@0: } michael@0: michael@0: // Now compute the collapsed margin-top value into aState.mPrevBottomMargin, assuming michael@0: // that all child margins collapse down to clearanceFrame. michael@0: nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState, michael@0: &aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry); michael@0: michael@0: // XXX optimization; we could check the collapsing children to see if they are sure michael@0: // to require clearance, and so avoid retrying them michael@0: michael@0: if (clearanceFrame) { michael@0: // Don't allow retries on the second pass. The clearance decisions for the michael@0: // blocks whose top-margins collapse with ours are now fixed. michael@0: mayNeedRetry = false; michael@0: } michael@0: michael@0: if (!treatWithClearance && !clearanceFrame && mightClearFloats) { michael@0: // We don't know if we need clearance and this is the first, michael@0: // optimistic pass. So determine whether *this block* needs michael@0: // clearance. Note that we do not allow the decision for whether michael@0: // this block has clearance to change on the second pass; that michael@0: // decision is only allowed to be made under the optimistic michael@0: // first pass. michael@0: nscoord curY = aState.mY + aState.mPrevBottomMargin.get(); michael@0: nscoord clearY = aState.ClearFloats(curY, breakType, replacedBlock); michael@0: if (clearY != curY) { michael@0: // Looks like we need clearance and we didn't know about it already. So michael@0: // recompute collapsed margin michael@0: treatWithClearance = true; michael@0: // Remember this decision, needed for incremental reflow michael@0: aLine->SetHasClearance(); michael@0: michael@0: // Apply incoming margins michael@0: aState.mY += aState.mPrevBottomMargin.get(); michael@0: aState.mPrevBottomMargin.Zero(); michael@0: michael@0: // Compute the collapsed margin again, ignoring the incoming margin this time michael@0: mayNeedRetry = false; michael@0: nsBlockReflowContext::ComputeCollapsedTopMargin(reflowState, michael@0: &aState.mPrevBottomMargin, clearanceFrame, &mayNeedRetry); michael@0: } michael@0: } michael@0: michael@0: // Temporarily advance the running Y value so that the michael@0: // GetAvailableSpace method will return the right available michael@0: // space. This undone as soon as the horizontal margins are michael@0: // computed. michael@0: topMargin = aState.mPrevBottomMargin.get(); michael@0: michael@0: if (treatWithClearance) { michael@0: nscoord currentY = aState.mY; michael@0: // advance mY to the clear position. michael@0: aState.mY = aState.ClearFloats(aState.mY, breakType, replacedBlock); michael@0: michael@0: clearedFloats = aState.mY != currentY; michael@0: michael@0: // Compute clearance. It's the amount we need to add to the top michael@0: // border-edge of the frame, after applying collapsed margins michael@0: // from the frame and its children, to get it to line up with michael@0: // the bottom of the floats. The former is currentY + topMargin, michael@0: // the latter is the current aState.mY. michael@0: // Note that negative clearance is possible michael@0: clearance = aState.mY - (currentY + topMargin); michael@0: michael@0: // Add clearance to our top margin while we compute available michael@0: // space for the frame michael@0: topMargin += clearance; michael@0: michael@0: // Note that aState.mY should stay where it is: at the top michael@0: // border-edge of the frame michael@0: } else { michael@0: // Advance aState.mY to the top border-edge of the frame. michael@0: aState.mY += topMargin; michael@0: } michael@0: } michael@0: michael@0: // Here aState.mY is the top border-edge of the block. michael@0: // Compute the available space for the block michael@0: nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(); michael@0: #ifdef REALLY_NOISY_REFLOW michael@0: printf("setting line %p isImpacted to %s\n", michael@0: aLine.get(), floatAvailableSpace.mHasFloats?"true":"false"); michael@0: #endif michael@0: aLine->SetLineIsImpactedByFloat(floatAvailableSpace.mHasFloats); michael@0: nsRect availSpace; michael@0: aState.ComputeBlockAvailSpace(frame, display, floatAvailableSpace, michael@0: replacedBlock != nullptr, availSpace); michael@0: michael@0: // The check for michael@0: // (!aState.mReflowState.mFlags.mIsTopOfPage || clearedFloats) michael@0: // is to some degree out of paranoia: if we reliably eat up top michael@0: // margins at the top of the page as we ought to, it wouldn't be michael@0: // needed. michael@0: if ((!aState.mReflowState.mFlags.mIsTopOfPage || clearedFloats) && michael@0: availSpace.height < 0) { michael@0: // We know already that this child block won't fit on this michael@0: // page/column due to the top margin or the clearance. So we need michael@0: // to get out of here now. (If we don't, most blocks will handle michael@0: // things fine, and report break-before, but zero-height blocks michael@0: // won't, and will thus make their parent overly-large and force michael@0: // *it* to be pushed in its entirety.) michael@0: // Doing this means that we also don't need to worry about the michael@0: // |availSpace.height += topMargin| below interacting with pushed michael@0: // floats (which force nscoord_MAX clearance) to cause a michael@0: // constrained height to turn into an unconstrained one. michael@0: aState.mY = startingY; michael@0: aState.mPrevBottomMargin = incomingMargin; michael@0: *aKeepReflowGoing = false; michael@0: if (ShouldAvoidBreakInside(aState.mReflowState)) { michael@0: aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: } else { michael@0: PushLines(aState, aLine.prev()); michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Now put the Y coordinate back to the top of the top-margin + michael@0: // clearance, and flow the block. michael@0: aState.mY -= topMargin; michael@0: availSpace.y -= topMargin; michael@0: if (NS_UNCONSTRAINEDSIZE != availSpace.height) { michael@0: availSpace.height += topMargin; michael@0: } michael@0: michael@0: // Reflow the block into the available space michael@0: // construct the html reflow state for the block. ReflowBlock michael@0: // will initialize it michael@0: nsHTMLReflowState blockHtmlRS(aState.mPresContext, aState.mReflowState, frame, michael@0: availSpace.Size()); michael@0: blockHtmlRS.mFlags.mHasClearance = aLine->HasClearance(); michael@0: michael@0: nsFloatManager::SavedState floatManagerState; michael@0: if (mayNeedRetry) { michael@0: blockHtmlRS.mDiscoveredClearance = &clearanceFrame; michael@0: aState.mFloatManager->PushState(&floatManagerState); michael@0: } else if (!applyTopMargin) { michael@0: blockHtmlRS.mDiscoveredClearance = aState.mReflowState.mDiscoveredClearance; michael@0: } michael@0: michael@0: nsReflowStatus frameReflowStatus = NS_FRAME_COMPLETE; michael@0: rv = brc.ReflowBlock(availSpace, applyTopMargin, aState.mPrevBottomMargin, michael@0: clearance, aState.IsAdjacentWithTop(), michael@0: aLine.get(), blockHtmlRS, frameReflowStatus, aState); michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mayNeedRetry && clearanceFrame) { michael@0: aState.mFloatManager->PopState(&floatManagerState); michael@0: aState.mY = startingY; michael@0: aState.mPrevBottomMargin = incomingMargin; michael@0: continue; michael@0: } michael@0: michael@0: aState.mPrevChild = frame; michael@0: michael@0: if (blockHtmlRS.WillReflowAgainForClearance()) { michael@0: // If an ancestor of ours is going to reflow for clearance, we michael@0: // need to avoid calling PlaceBlock, because it unsets dirty bits michael@0: // on the child block (both itself, and through its call to michael@0: // nsFrame::DidReflow), and those dirty bits imply dirtiness for michael@0: // all of the child block, including the lines it didn't reflow. michael@0: NS_ASSERTION(originalPosition == frame->GetPosition(), michael@0: "we need to call PositionChildViews"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #if defined(REFLOW_STATUS_COVERAGE) michael@0: RecordReflowStatus(true, frameReflowStatus); michael@0: #endif michael@0: michael@0: if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { michael@0: // None of the child block fits. michael@0: *aKeepReflowGoing = false; michael@0: if (ShouldAvoidBreakInside(aState.mReflowState)) { michael@0: aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: } else { michael@0: PushLines(aState, aLine.prev()); michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: } michael@0: } michael@0: else { michael@0: // Note: line-break-after a block is a nop michael@0: michael@0: // Try to place the child block. michael@0: // Don't force the block to fit if we have positive clearance, because michael@0: // pushing it to the next page would give it more room. michael@0: // Don't force the block to fit if it's impacted by a float. If it is, michael@0: // then pushing it to the next page would give it more room. Note that michael@0: // isImpacted doesn't include impact from the block's own floats. michael@0: bool forceFit = aState.IsAdjacentWithTop() && clearance <= 0 && michael@0: !floatAvailableSpace.mHasFloats; michael@0: nsCollapsingMargin collapsedBottomMargin; michael@0: nsOverflowAreas overflowAreas; michael@0: *aKeepReflowGoing = brc.PlaceBlock(blockHtmlRS, forceFit, aLine.get(), michael@0: collapsedBottomMargin, michael@0: overflowAreas, michael@0: frameReflowStatus, michael@0: aState.mContainerWidth); michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(frameReflowStatus) && michael@0: ShouldAvoidBreakInside(aState.mReflowState)) { michael@0: *aKeepReflowGoing = false; michael@0: } michael@0: michael@0: if (aLine->SetCarriedOutBottomMargin(collapsedBottomMargin)) { michael@0: line_iterator nextLine = aLine; michael@0: ++nextLine; michael@0: if (nextLine != end_lines()) { michael@0: nextLine->MarkPreviousMarginDirty(); michael@0: } michael@0: } michael@0: michael@0: aLine->SetOverflowAreas(overflowAreas); michael@0: if (*aKeepReflowGoing) { michael@0: // Some of the child block fit michael@0: michael@0: // Advance to new Y position michael@0: nscoord newY = aLine->BEnd(); michael@0: aState.mY = newY; michael@0: michael@0: // Continue the block frame now if it didn't completely fit in michael@0: // the available space. michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(frameReflowStatus)) { michael@0: bool madeContinuation = michael@0: CreateContinuationFor(aState, nullptr, frame); michael@0: michael@0: nsIFrame* nextFrame = frame->GetNextInFlow(); michael@0: NS_ASSERTION(nextFrame, "We're supposed to have a next-in-flow by now"); michael@0: michael@0: if (NS_FRAME_IS_NOT_COMPLETE(frameReflowStatus)) { michael@0: // If nextFrame used to be an overflow container, make it a normal block michael@0: if (!madeContinuation && michael@0: (NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) { michael@0: nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker, frame); michael@0: nsContainerFrame* parent = michael@0: static_cast(nextFrame->GetParent()); michael@0: rv = parent->StealFrame(nextFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (parent != this) michael@0: ReparentFrame(nextFrame, parent, this); michael@0: mFrames.InsertFrame(nullptr, frame, nextFrame); michael@0: madeContinuation = true; // needs to be added to mLines michael@0: nextFrame->RemoveStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); michael@0: frameReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: } michael@0: michael@0: // Push continuation to a new line, but only if we actually made one. michael@0: if (madeContinuation) { michael@0: nsLineBox* line = NewLineBox(nextFrame, true); michael@0: mLines.after_insert(aLine, line); michael@0: } michael@0: michael@0: PushLines(aState, aLine); michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: michael@0: // If we need to reflow the continuation of the block child, michael@0: // then we'd better reflow our continuation michael@0: if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { michael@0: aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: // We also need to make that continuation's line dirty so michael@0: // it gets reflowed when we reflow our next in flow. The michael@0: // nif's line must always be either a line of the nif's michael@0: // parent block (only if we didn't make a continuation) or michael@0: // else one of our own overflow lines. In the latter case michael@0: // the line is already marked dirty, so just handle the michael@0: // first case. michael@0: if (!madeContinuation) { michael@0: nsBlockFrame* nifBlock = michael@0: nsLayoutUtils::GetAsBlock(nextFrame->GetParent()); michael@0: NS_ASSERTION(nifBlock, michael@0: "A block's child's next in flow's parent must be a block!"); michael@0: for (line_iterator line = nifBlock->begin_lines(), michael@0: line_end = nifBlock->end_lines(); line != line_end; ++line) { michael@0: if (line->Contains(nextFrame)) { michael@0: line->MarkDirty(); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: *aKeepReflowGoing = false; michael@0: michael@0: // The bottom margin for a block is only applied on the last michael@0: // flow block. Since we just continued the child block frame, michael@0: // we know that line->mFirstChild is not the last flow block michael@0: // therefore zero out the running margin value. michael@0: #ifdef NOISY_VERTICAL_MARGINS michael@0: ListTag(stdout); michael@0: printf(": reflow incomplete, frame="); michael@0: nsFrame::ListTag(stdout, frame); michael@0: printf(" prevBottomMargin=%d, setting to zero\n", michael@0: aState.mPrevBottomMargin); michael@0: #endif michael@0: aState.mPrevBottomMargin.Zero(); michael@0: } michael@0: else { // frame is complete but its overflow is not complete michael@0: // Disconnect the next-in-flow and put it in our overflow tracker michael@0: if (!madeContinuation && michael@0: !(NS_FRAME_IS_OVERFLOW_CONTAINER & nextFrame->GetStateBits())) { michael@0: // It already exists, but as a normal next-in-flow, so we need michael@0: // to dig it out of the child lists. michael@0: nsContainerFrame* parent = static_cast michael@0: (nextFrame->GetParent()); michael@0: rv = parent->StealFrame(nextFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else if (madeContinuation) { michael@0: mFrames.RemoveFrame(nextFrame); michael@0: } michael@0: michael@0: // Put it in our overflow list michael@0: aState.mOverflowTracker->Insert(nextFrame, frameReflowStatus); michael@0: NS_MergeReflowStatusInto(&aState.mReflowStatus, frameReflowStatus); michael@0: michael@0: #ifdef NOISY_VERTICAL_MARGINS michael@0: ListTag(stdout); michael@0: printf(": reflow complete but overflow incomplete for "); michael@0: nsFrame::ListTag(stdout, frame); michael@0: printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", michael@0: aState.mPrevBottomMargin, collapsedBottomMargin.get()); michael@0: #endif michael@0: aState.mPrevBottomMargin = collapsedBottomMargin; michael@0: } michael@0: } michael@0: else { // frame is fully complete michael@0: #ifdef NOISY_VERTICAL_MARGINS michael@0: ListTag(stdout); michael@0: printf(": reflow complete for "); michael@0: nsFrame::ListTag(stdout, frame); michael@0: printf(" prevBottomMargin=%d collapsedBottomMargin=%d\n", michael@0: aState.mPrevBottomMargin, collapsedBottomMargin.get()); michael@0: #endif michael@0: aState.mPrevBottomMargin = collapsedBottomMargin; michael@0: } michael@0: #ifdef NOISY_VERTICAL_MARGINS michael@0: ListTag(stdout); michael@0: printf(": frame="); michael@0: nsFrame::ListTag(stdout, frame); michael@0: printf(" carriedOutBottomMargin=%d collapsedBottomMargin=%d => %d\n", michael@0: brc.GetCarriedOutBottomMargin(), collapsedBottomMargin.get(), michael@0: aState.mPrevBottomMargin); michael@0: #endif michael@0: } else { michael@0: if ((aLine == mLines.front() && !GetPrevInFlow()) || michael@0: ShouldAvoidBreakInside(aState.mReflowState)) { michael@0: // If it's our very first line *or* we're not at the top of the page michael@0: // and we have page-break-inside:avoid, then we need to be pushed to michael@0: // our parent's next-in-flow. michael@0: aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: } else { michael@0: // Push the line that didn't fit and any lines that follow it michael@0: // to our next-in-flow. michael@0: PushLines(aState, aLine.prev()); michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: } michael@0: } michael@0: } michael@0: break; // out of the reflow retry loop michael@0: } michael@0: michael@0: // Now that we've got its final position all figured out, position any child michael@0: // views it may have. Note that the case when frame has a view got handled michael@0: // by FinishReflowChild, but that function didn't have the coordinates needed michael@0: // to correctly decide whether to reposition child views. michael@0: if (originalPosition != frame->GetPosition() && !frame->HasView()) { michael@0: nsContainerFrame::PositionChildViews(frame); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: VerifyLines(true); michael@0: #endif michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState, michael@0: line_iterator aLine, michael@0: bool* aKeepReflowGoing) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: *aKeepReflowGoing = true; michael@0: michael@0: aLine->SetLineIsImpactedByFloat(false); michael@0: michael@0: // Setup initial coordinate system for reflowing the inline frames michael@0: // into. Apply a previous block frame's bottom margin first. michael@0: if (ShouldApplyTopMargin(aState, aLine)) { michael@0: aState.mY += aState.mPrevBottomMargin.get(); michael@0: } michael@0: nsFlowAreaRect floatAvailableSpace = aState.GetFloatAvailableSpace(); michael@0: michael@0: LineReflowStatus lineReflowStatus; michael@0: do { michael@0: nscoord availableSpaceHeight = 0; michael@0: do { michael@0: bool allowPullUp = true; michael@0: nsIContent* forceBreakInContent = nullptr; michael@0: int32_t forceBreakOffset = -1; michael@0: gfxBreakPriority forceBreakPriority = gfxBreakPriority::eNoBreak; michael@0: do { michael@0: nsFloatManager::SavedState floatManagerState; michael@0: aState.mReflowState.mFloatManager->PushState(&floatManagerState); michael@0: michael@0: // Once upon a time we allocated the first 30 nsLineLayout objects michael@0: // on the stack, and then we switched to the heap. At that time michael@0: // these objects were large (1100 bytes on a 32 bit system). michael@0: // Then the nsLineLayout object was shrunk to 156 bytes by michael@0: // removing some internal buffers. Given that it is so much michael@0: // smaller, the complexity of 2 different ways of allocating michael@0: // no longer makes sense. Now we always allocate on the stack. michael@0: nsLineLayout lineLayout(aState.mPresContext, michael@0: aState.mReflowState.mFloatManager, michael@0: &aState.mReflowState, &aLine); michael@0: lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); michael@0: if (forceBreakInContent) { michael@0: lineLayout.ForceBreakAtPosition(forceBreakInContent, forceBreakOffset); michael@0: } michael@0: rv = DoReflowInlineFrames(aState, lineLayout, aLine, michael@0: floatAvailableSpace, availableSpaceHeight, michael@0: &floatManagerState, aKeepReflowGoing, michael@0: &lineReflowStatus, allowPullUp); michael@0: lineLayout.EndLineReflow(); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus || michael@0: LINE_REFLOW_REDO_MORE_FLOATS == lineReflowStatus || michael@0: LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) { michael@0: if (lineLayout.NeedsBackup()) { michael@0: NS_ASSERTION(!forceBreakInContent, "Backing up twice; this should never be necessary"); michael@0: // If there is no saved break position, then this will set michael@0: // set forceBreakInContent to null and we won't back up, which is michael@0: // correct. michael@0: forceBreakInContent = lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset, &forceBreakPriority); michael@0: } else { michael@0: forceBreakInContent = nullptr; michael@0: } michael@0: // restore the float manager state michael@0: aState.mReflowState.mFloatManager->PopState(&floatManagerState); michael@0: // Clear out float lists michael@0: aState.mCurrentLineFloats.DeleteAll(); michael@0: aState.mBelowCurrentLineFloats.DeleteAll(); michael@0: } michael@0: michael@0: // Don't allow pullup on a subsequent LINE_REFLOW_REDO_NO_PULL pass michael@0: allowPullUp = false; michael@0: } while (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus); michael@0: } while (LINE_REFLOW_REDO_MORE_FLOATS == lineReflowStatus); michael@0: } while (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::PushTruncatedLine(nsBlockReflowState& aState, michael@0: line_iterator aLine, michael@0: bool* aKeepReflowGoing) michael@0: { michael@0: PushLines(aState, aLine.prev()); michael@0: *aKeepReflowGoing = false; michael@0: NS_FRAME_SET_INCOMPLETE(aState.mReflowStatus); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static const char* LineReflowStatusNames[] = { michael@0: "LINE_REFLOW_OK", "LINE_REFLOW_STOP", "LINE_REFLOW_REDO_NO_PULL", michael@0: "LINE_REFLOW_REDO_MORE_FLOATS", michael@0: "LINE_REFLOW_REDO_NEXT_BAND", "LINE_REFLOW_TRUNCATED" michael@0: }; michael@0: #endif michael@0: michael@0: nsresult michael@0: nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, michael@0: nsLineLayout& aLineLayout, michael@0: line_iterator aLine, michael@0: nsFlowAreaRect& aFloatAvailableSpace, michael@0: nscoord& aAvailableSpaceHeight, michael@0: nsFloatManager::SavedState* michael@0: aFloatStateBeforeLine, michael@0: bool* aKeepReflowGoing, michael@0: LineReflowStatus* aLineReflowStatus, michael@0: bool aAllowPullUp) michael@0: { michael@0: // Forget all of the floats on the line michael@0: aLine->FreeFloats(aState.mFloatCacheFreeList); michael@0: aState.mFloatOverflowAreas.Clear(); michael@0: michael@0: // We need to set this flag on the line if any of our reflow passes michael@0: // are impacted by floats. michael@0: if (aFloatAvailableSpace.mHasFloats) michael@0: aLine->SetLineIsImpactedByFloat(true); michael@0: #ifdef REALLY_NOISY_REFLOW michael@0: printf("nsBlockFrame::DoReflowInlineFrames %p impacted = %d\n", michael@0: this, aFloatAvailableSpace.mHasFloats); michael@0: #endif michael@0: michael@0: WritingMode wm = GetWritingMode(aLine->mFirstChild); michael@0: LogicalRect lineRect(wm, aFloatAvailableSpace.mRect, aState.mContainerWidth); michael@0: michael@0: nscoord iStart = lineRect.IStart(wm); michael@0: michael@0: nscoord availISize = lineRect.ISize(wm); michael@0: nscoord availBSize; michael@0: if (aState.GetFlag(BRS_UNCONSTRAINEDHEIGHT)) { michael@0: availBSize = NS_UNCONSTRAINEDSIZE; michael@0: } michael@0: else { michael@0: /* XXX get the height right! */ michael@0: availBSize = lineRect.BSize(wm); michael@0: } michael@0: michael@0: // Make sure to enable resize optimization before we call BeginLineReflow michael@0: // because it might get disabled there michael@0: aLine->EnableResizeReflowOptimization(); michael@0: michael@0: aLineLayout.BeginLineReflow(iStart, aState.mY, michael@0: availISize, availBSize, michael@0: aFloatAvailableSpace.mHasFloats, michael@0: false, /*XXX isTopOfPage*/ michael@0: wm, aState.mContainerWidth); michael@0: michael@0: aState.SetFlag(BRS_LINE_LAYOUT_EMPTY, false); michael@0: michael@0: // XXX Unfortunately we need to know this before reflowing the first michael@0: // inline frame in the line. FIX ME. michael@0: if ((0 == aLineLayout.GetLineNumber()) && michael@0: (NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) && michael@0: (NS_BLOCK_HAS_FIRST_LETTER_STYLE & mState)) { michael@0: aLineLayout.SetFirstLetterStyleOK(true); michael@0: } michael@0: NS_ASSERTION(!((NS_BLOCK_HAS_FIRST_LETTER_CHILD & mState) && michael@0: GetPrevContinuation()), michael@0: "first letter child bit should only be on first continuation"); michael@0: michael@0: // Reflow the frames that are already on the line first michael@0: nsresult rv = NS_OK; michael@0: LineReflowStatus lineReflowStatus = LINE_REFLOW_OK; michael@0: int32_t i; michael@0: nsIFrame* frame = aLine->mFirstChild; michael@0: michael@0: if (aFloatAvailableSpace.mHasFloats) { michael@0: // There is a soft break opportunity at the start of the line, because michael@0: // we can always move this line down below float(s). michael@0: if (aLineLayout.NotifyOptionalBreakPosition(frame->GetContent(), 0, true, gfxBreakPriority::eNormalBreak)) { michael@0: lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND; michael@0: } michael@0: } michael@0: michael@0: // need to repeatedly call GetChildCount here, because the child michael@0: // count can change during the loop! michael@0: for (i = 0; LINE_REFLOW_OK == lineReflowStatus && i < aLine->GetChildCount(); michael@0: i++, frame = frame->GetNextSibling()) { michael@0: rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame, michael@0: &lineReflowStatus); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (LINE_REFLOW_OK != lineReflowStatus) { michael@0: // It is possible that one or more of next lines are empty michael@0: // (because of DeleteNextInFlowChild). If so, delete them now michael@0: // in case we are finished. michael@0: ++aLine; michael@0: while ((aLine != end_lines()) && (0 == aLine->GetChildCount())) { michael@0: // XXX Is this still necessary now that DeleteNextInFlowChild michael@0: // uses DoRemoveFrame? michael@0: nsLineBox *toremove = aLine; michael@0: aLine = mLines.erase(aLine); michael@0: NS_ASSERTION(nullptr == toremove->mFirstChild, "bad empty line"); michael@0: FreeLineBox(toremove); michael@0: } michael@0: --aLine; michael@0: michael@0: NS_ASSERTION(lineReflowStatus != LINE_REFLOW_TRUNCATED, michael@0: "ReflowInlineFrame should never determine that a line " michael@0: "needs to go to the next page/column"); michael@0: } michael@0: } michael@0: michael@0: // Don't pull up new frames into lines with continuation placeholders michael@0: if (aAllowPullUp) { michael@0: // Pull frames and reflow them until we can't michael@0: while (LINE_REFLOW_OK == lineReflowStatus) { michael@0: frame = PullFrame(aState, aLine); michael@0: if (!frame) { michael@0: break; michael@0: } michael@0: michael@0: while (LINE_REFLOW_OK == lineReflowStatus) { michael@0: int32_t oldCount = aLine->GetChildCount(); michael@0: rv = ReflowInlineFrame(aState, aLineLayout, aLine, frame, michael@0: &lineReflowStatus); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (aLine->GetChildCount() != oldCount) { michael@0: // We just created a continuation for aFrame AND its going michael@0: // to end up on this line (e.g. :first-letter michael@0: // situation). Therefore we have to loop here before trying michael@0: // to pull another frame. michael@0: frame = frame->GetNextSibling(); michael@0: } michael@0: else { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: aState.SetFlag(BRS_LINE_LAYOUT_EMPTY, aLineLayout.LineIsEmpty()); michael@0: michael@0: // We only need to backup if the line isn't going to be reflowed again anyway michael@0: bool needsBackup = aLineLayout.NeedsBackup() && michael@0: (lineReflowStatus == LINE_REFLOW_STOP || lineReflowStatus == LINE_REFLOW_OK); michael@0: if (needsBackup && aLineLayout.HaveForcedBreakPosition()) { michael@0: NS_WARNING("We shouldn't be backing up more than once! " michael@0: "Someone must have set a break opportunity beyond the available width, " michael@0: "even though there were better break opportunities before it"); michael@0: needsBackup = false; michael@0: } michael@0: if (needsBackup) { michael@0: // We need to try backing up to before a text run michael@0: int32_t offset; michael@0: gfxBreakPriority breakPriority; michael@0: nsIContent* breakContent = aLineLayout.GetLastOptionalBreakPosition(&offset, &breakPriority); michael@0: // XXX It's possible, in fact not unusual, for the break opportunity to already michael@0: // be the end of the line. We should detect that and optimize to not michael@0: // re-do the line. michael@0: if (breakContent) { michael@0: // We can back up! michael@0: lineReflowStatus = LINE_REFLOW_REDO_NO_PULL; michael@0: } michael@0: } else { michael@0: // In case we reflow this line again, remember that we don't michael@0: // need to force any breaking michael@0: aLineLayout.ClearOptionalBreakPosition(); michael@0: } michael@0: michael@0: if (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) { michael@0: // This happens only when we have a line that is impacted by michael@0: // floats and the first element in the line doesn't fit with michael@0: // the floats. michael@0: // michael@0: // What we do is to advance past the first float we find and michael@0: // then reflow the line all over again. michael@0: NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aFloatAvailableSpace.mRect.height, michael@0: "unconstrained height on totally empty line"); michael@0: michael@0: // See the analogous code for blocks in nsBlockReflowState::ClearFloats. michael@0: if (aFloatAvailableSpace.mRect.height > 0) { michael@0: NS_ASSERTION(aFloatAvailableSpace.mHasFloats, michael@0: "redo line on totally empty line with non-empty band..."); michael@0: // We should never hit this case if we've placed floats on the michael@0: // line; if we have, then the GetFloatAvailableSpace call is wrong michael@0: // and needs to happen after the caller pops the space manager michael@0: // state. michael@0: aState.mFloatManager->AssertStateMatches(aFloatStateBeforeLine); michael@0: aState.mY += aFloatAvailableSpace.mRect.height; michael@0: aFloatAvailableSpace = aState.GetFloatAvailableSpace(); michael@0: } else { michael@0: NS_ASSERTION(NS_UNCONSTRAINEDSIZE != aState.mReflowState.AvailableHeight(), michael@0: "We shouldn't be running out of height here"); michael@0: if (NS_UNCONSTRAINEDSIZE == aState.mReflowState.AvailableHeight()) { michael@0: // just move it down a bit to try to get out of this mess michael@0: aState.mY += 1; michael@0: // We should never hit this case if we've placed floats on the michael@0: // line; if we have, then the GetFloatAvailableSpace call is wrong michael@0: // and needs to happen after the caller pops the space manager michael@0: // state. michael@0: aState.mFloatManager->AssertStateMatches(aFloatStateBeforeLine); michael@0: aFloatAvailableSpace = aState.GetFloatAvailableSpace(); michael@0: } else { michael@0: // There's nowhere to retry placing the line, so we want to push michael@0: // it to the next page/column where its contents can fit not michael@0: // next to a float. michael@0: lineReflowStatus = LINE_REFLOW_TRUNCATED; michael@0: PushTruncatedLine(aState, aLine, aKeepReflowGoing); michael@0: } michael@0: } michael@0: michael@0: // XXX: a small optimization can be done here when paginating: michael@0: // if the new Y coordinate is past the end of the block then michael@0: // push the line and return now instead of later on after we are michael@0: // past the float. michael@0: } michael@0: else if (LINE_REFLOW_TRUNCATED != lineReflowStatus && michael@0: LINE_REFLOW_REDO_NO_PULL != lineReflowStatus) { michael@0: // If we are propagating out a break-before status then there is michael@0: // no point in placing the line. michael@0: if (!NS_INLINE_IS_BREAK_BEFORE(aState.mReflowStatus)) { michael@0: if (!PlaceLine(aState, aLineLayout, aLine, aFloatStateBeforeLine, michael@0: aFloatAvailableSpace.mRect, aAvailableSpaceHeight, michael@0: aKeepReflowGoing)) { michael@0: lineReflowStatus = LINE_REFLOW_REDO_MORE_FLOATS; michael@0: // PlaceLine already called GetAvailableSpaceForHeight for us. michael@0: } michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: printf("Line reflow status = %s\n", LineReflowStatusNames[lineReflowStatus]); michael@0: } michael@0: #endif michael@0: michael@0: if (aLineLayout.GetDirtyNextLine()) { michael@0: // aLine may have been pushed to the overflow lines. michael@0: FrameLines* overflowLines = GetOverflowLines(); michael@0: // We can't just compare iterators front() to aLine here, since they may be in michael@0: // different lists. michael@0: bool pushedToOverflowLines = overflowLines && michael@0: overflowLines->mLines.front() == aLine.get(); michael@0: if (pushedToOverflowLines) { michael@0: // aLine is stale, it's associated with the main line list but it should michael@0: // be associated with the overflow line list now michael@0: aLine = overflowLines->mLines.begin(); michael@0: } michael@0: nsBlockInFlowLineIterator iter(this, aLine, pushedToOverflowLines); michael@0: if (iter.Next() && iter.GetLine()->IsInline()) { michael@0: iter.GetLine()->MarkDirty(); michael@0: if (iter.GetContainer() != this) { michael@0: aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: } michael@0: } michael@0: } michael@0: michael@0: *aLineReflowStatus = lineReflowStatus; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Reflow an inline frame. The reflow status is mapped from the frames michael@0: * reflow status to the lines reflow status (not to our reflow status). michael@0: * The line reflow status is simple: true means keep placing frames michael@0: * on the line; false means don't (the line is done). If the line michael@0: * has some sort of breaking affect then aLine's break-type will be set michael@0: * to something other than NS_STYLE_CLEAR_NONE. michael@0: */ michael@0: nsresult michael@0: nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState, michael@0: nsLineLayout& aLineLayout, michael@0: line_iterator aLine, michael@0: nsIFrame* aFrame, michael@0: LineReflowStatus* aLineReflowStatus) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFrame); michael@0: michael@0: *aLineReflowStatus = LINE_REFLOW_OK; michael@0: michael@0: #ifdef NOISY_FIRST_LETTER michael@0: ListTag(stdout); michael@0: printf(": reflowing "); michael@0: nsFrame::ListTag(stdout, aFrame); michael@0: printf(" reflowingFirstLetter=%s\n", michael@0: aLineLayout.GetFirstLetterStyleOK() ? "on" : "off"); michael@0: #endif michael@0: michael@0: // Reflow the inline frame michael@0: nsReflowStatus frameReflowStatus; michael@0: bool pushedFrame; michael@0: nsresult rv = aLineLayout.ReflowFrame(aFrame, frameReflowStatus, michael@0: nullptr, pushedFrame); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (frameReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { michael@0: aLineLayout.SetDirtyNextLine(); michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: #ifdef REALLY_NOISY_REFLOW_CHILD michael@0: nsFrame::ListTag(stdout, aFrame); michael@0: printf(": status=%x\n", frameReflowStatus); michael@0: #endif michael@0: michael@0: #if defined(REFLOW_STATUS_COVERAGE) michael@0: RecordReflowStatus(false, frameReflowStatus); michael@0: #endif michael@0: michael@0: // Send post-reflow notification michael@0: aState.mPrevChild = aFrame; michael@0: michael@0: /* XXX michael@0: This is where we need to add logic to handle some odd behavior. michael@0: For one thing, we should usually place at least one thing next michael@0: to a left float, even when that float takes up all the width on a line. michael@0: see bug 22496 michael@0: */ michael@0: michael@0: // Process the child frames reflow status. There are 5 cases: michael@0: // complete, not-complete, break-before, break-after-complete, michael@0: // break-after-not-complete. There are two situations: we are a michael@0: // block or we are an inline. This makes a total of 10 cases michael@0: // (fortunately, there is some overlap). michael@0: aLine->SetBreakTypeAfter(NS_STYLE_CLEAR_NONE); michael@0: if (NS_INLINE_IS_BREAK(frameReflowStatus) || michael@0: (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType)) { michael@0: // Always abort the line reflow (because a line break is the michael@0: // minimal amount of break we do). michael@0: *aLineReflowStatus = LINE_REFLOW_STOP; michael@0: michael@0: // XXX what should aLine's break-type be set to in all these cases? michael@0: uint8_t breakType = NS_INLINE_GET_BREAK_TYPE(frameReflowStatus); michael@0: NS_ASSERTION((NS_STYLE_CLEAR_NONE != breakType) || michael@0: (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType), "bad break type"); michael@0: NS_ASSERTION(NS_STYLE_CLEAR_MAX >= breakType, "invalid break type"); michael@0: michael@0: if (NS_INLINE_IS_BREAK_BEFORE(frameReflowStatus)) { michael@0: // Break-before cases. michael@0: if (aFrame == aLine->mFirstChild) { michael@0: // If we break before the first frame on the line then we must michael@0: // be trying to place content where there's no room (e.g. on a michael@0: // line with wide floats). Inform the caller to reflow the michael@0: // line after skipping past a float. michael@0: *aLineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND; michael@0: } michael@0: else { michael@0: // It's not the first child on this line so go ahead and split michael@0: // the line. We will see the frame again on the next-line. michael@0: SplitLine(aState, aLineLayout, aLine, aFrame, aLineReflowStatus); michael@0: michael@0: // If we're splitting the line because the frame didn't fit and it michael@0: // was pushed, then mark the line as having word wrapped. We need to michael@0: // know that if we're shrink wrapping our width michael@0: if (pushedFrame) { michael@0: aLine->SetLineWrapped(true); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // If a float split and its prev-in-flow was followed by a
    , then combine michael@0: // the
    's break type with the inline's break type (the inline will be the very michael@0: // next frame after the split float). michael@0: if (NS_STYLE_CLEAR_NONE != aState.mFloatBreakType) { michael@0: breakType = nsLayoutUtils::CombineBreakType(breakType, michael@0: aState.mFloatBreakType); michael@0: aState.mFloatBreakType = NS_STYLE_CLEAR_NONE; michael@0: } michael@0: // Break-after cases michael@0: if (breakType == NS_STYLE_CLEAR_LINE) { michael@0: if (!aLineLayout.GetLineEndsInBR()) { michael@0: breakType = NS_STYLE_CLEAR_NONE; michael@0: } michael@0: } michael@0: aLine->SetBreakTypeAfter(breakType); michael@0: if (NS_FRAME_IS_COMPLETE(frameReflowStatus)) { michael@0: // Split line, but after the frame just reflowed michael@0: SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus); michael@0: michael@0: if (NS_INLINE_IS_BREAK_AFTER(frameReflowStatus) && michael@0: !aLineLayout.GetLineEndsInBR()) { michael@0: aLineLayout.SetDirtyNextLine(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(frameReflowStatus)) { michael@0: // Create a continuation for the incomplete frame. Note that the michael@0: // frame may already have a continuation. michael@0: CreateContinuationFor(aState, aLine, aFrame); michael@0: michael@0: // Remember that the line has wrapped michael@0: if (!aLineLayout.GetLineEndsInBR()) { michael@0: aLine->SetLineWrapped(true); michael@0: } michael@0: michael@0: // If we just ended a first-letter frame or reflowed a placeholder then michael@0: // don't split the line and don't stop the line reflow... michael@0: // But if we are going to stop anyways we'd better split the line. michael@0: if ((!(frameReflowStatus & NS_INLINE_BREAK_FIRST_LETTER_COMPLETE) && michael@0: nsGkAtoms::placeholderFrame != aFrame->GetType()) || michael@0: *aLineReflowStatus == LINE_REFLOW_STOP) { michael@0: // Split line after the current frame michael@0: *aLineReflowStatus = LINE_REFLOW_STOP; michael@0: SplitLine(aState, aLineLayout, aLine, aFrame->GetNextSibling(), aLineReflowStatus); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::CreateContinuationFor(nsBlockReflowState& aState, michael@0: nsLineBox* aLine, michael@0: nsIFrame* aFrame) michael@0: { michael@0: nsIFrame* newFrame = nullptr; michael@0: michael@0: if (!aFrame->GetNextInFlow()) { michael@0: newFrame = aState.mPresContext->PresShell()->FrameConstructor()-> michael@0: CreateContinuingFrame(aState.mPresContext, aFrame, this); michael@0: michael@0: mFrames.InsertFrame(nullptr, aFrame, newFrame); michael@0: michael@0: if (aLine) { michael@0: aLine->NoteFrameAdded(newFrame); michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: VerifyLines(false); michael@0: #endif michael@0: return !!newFrame; michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::SplitFloat(nsBlockReflowState& aState, michael@0: nsIFrame* aFloat, michael@0: nsReflowStatus aFloatStatus) michael@0: { michael@0: nsIFrame* nextInFlow = aFloat->GetNextInFlow(); michael@0: if (nextInFlow) { michael@0: nsContainerFrame *oldParent = michael@0: static_cast(nextInFlow->GetParent()); michael@0: DebugOnly rv = oldParent->StealFrame(nextInFlow); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "StealFrame failed"); michael@0: if (oldParent != this) { michael@0: ReparentFrame(nextInFlow, oldParent, this); michael@0: } michael@0: } else { michael@0: nextInFlow = aState.mPresContext->PresShell()->FrameConstructor()-> michael@0: CreateContinuingFrame(aState.mPresContext, aFloat, this); michael@0: } michael@0: if (NS_FRAME_OVERFLOW_IS_INCOMPLETE(aFloatStatus)) michael@0: aFloat->GetNextInFlow()->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER); michael@0: michael@0: // The containing block is now overflow-incomplete. michael@0: NS_FRAME_SET_OVERFLOW_INCOMPLETE(aState.mReflowStatus); michael@0: michael@0: if (aFloat->StyleDisplay()->mFloats == NS_STYLE_FLOAT_LEFT) { michael@0: aState.mFloatManager->SetSplitLeftFloatAcrossBreak(); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(aFloat->StyleDisplay()->mFloats == michael@0: NS_STYLE_FLOAT_RIGHT, "unexpected float side"); michael@0: aState.mFloatManager->SetSplitRightFloatAcrossBreak(); michael@0: } michael@0: michael@0: aState.AppendPushedFloat(nextInFlow); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsFloatCache* michael@0: GetLastFloat(nsLineBox* aLine) michael@0: { michael@0: nsFloatCache* fc = aLine->GetFirstFloat(); michael@0: while (fc && fc->Next()) { michael@0: fc = fc->Next(); michael@0: } michael@0: return fc; michael@0: } michael@0: michael@0: static bool michael@0: CheckPlaceholderInLine(nsIFrame* aBlock, nsLineBox* aLine, nsFloatCache* aFC) michael@0: { michael@0: if (!aFC) michael@0: return true; michael@0: NS_ASSERTION(!aFC->mFloat->GetPrevContinuation(), michael@0: "float in a line should never be a continuation"); michael@0: NS_ASSERTION(!(aFC->mFloat->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT), michael@0: "float in a line should never be a pushed float"); michael@0: nsIFrame* ph = aBlock->PresContext()->FrameManager()-> michael@0: GetPlaceholderFrameFor(aFC->mFloat->FirstInFlow()); michael@0: for (nsIFrame* f = ph; f; f = f->GetParent()) { michael@0: if (f->GetParent() == aBlock) michael@0: return aLine->Contains(f); michael@0: } michael@0: NS_ASSERTION(false, "aBlock is not an ancestor of aFrame!"); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::SplitLine(nsBlockReflowState& aState, michael@0: nsLineLayout& aLineLayout, michael@0: line_iterator aLine, michael@0: nsIFrame* aFrame, michael@0: LineReflowStatus* aLineReflowStatus) michael@0: { michael@0: NS_ABORT_IF_FALSE(aLine->IsInline(), "illegal SplitLine on block line"); michael@0: michael@0: int32_t pushCount = aLine->GetChildCount() - aLineLayout.GetCurrentSpanCount(); michael@0: NS_ABORT_IF_FALSE(pushCount >= 0, "bad push count"); michael@0: michael@0: #ifdef DEBUG michael@0: if (gNoisyReflow) { michael@0: nsFrame::IndentBy(stdout, gNoiseIndent); michael@0: printf("split line: from line=%p pushCount=%d aFrame=", michael@0: static_cast(aLine.get()), pushCount); michael@0: if (aFrame) { michael@0: nsFrame::ListTag(stdout, aFrame); michael@0: } michael@0: else { michael@0: printf("(null)"); michael@0: } michael@0: printf("\n"); michael@0: if (gReallyNoisyReflow) { michael@0: aLine->List(stdout, gNoiseIndent+1); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: if (0 != pushCount) { michael@0: NS_ABORT_IF_FALSE(aLine->GetChildCount() > pushCount, "bad push"); michael@0: NS_ABORT_IF_FALSE(nullptr != aFrame, "whoops"); michael@0: #ifdef DEBUG michael@0: { michael@0: nsIFrame *f = aFrame; michael@0: int32_t count = pushCount; michael@0: while (f && count > 0) { michael@0: f = f->GetNextSibling(); michael@0: --count; michael@0: } michael@0: NS_ASSERTION(count == 0, "Not enough frames to push"); michael@0: } michael@0: #endif michael@0: michael@0: // Put frames being split out into their own line michael@0: nsLineBox* newLine = NewLineBox(aLine, aFrame, pushCount); michael@0: mLines.after_insert(aLine, newLine); michael@0: #ifdef DEBUG michael@0: if (gReallyNoisyReflow) { michael@0: newLine->List(stdout, gNoiseIndent+1); michael@0: } michael@0: #endif michael@0: michael@0: // Let line layout know that some frames are no longer part of its michael@0: // state. michael@0: aLineLayout.SplitLineTo(aLine->GetChildCount()); michael@0: michael@0: // If floats have been placed whose placeholders have been pushed to the new michael@0: // line, we need to reflow the old line again. We don't want to look at the michael@0: // frames in the new line, because as a large paragraph is laid out the michael@0: // we'd get O(N^2) performance. So instead we just check that the last michael@0: // float and the last below-current-line float are still in aLine. michael@0: if (!CheckPlaceholderInLine(this, aLine, GetLastFloat(aLine)) || michael@0: !CheckPlaceholderInLine(this, aLine, aState.mBelowCurrentLineFloats.Tail())) { michael@0: *aLineReflowStatus = LINE_REFLOW_REDO_NO_PULL; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: VerifyLines(true); michael@0: #endif michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::IsLastLine(nsBlockReflowState& aState, michael@0: line_iterator aLine) michael@0: { michael@0: while (++aLine != end_lines()) { michael@0: // There is another line michael@0: if (0 != aLine->GetChildCount()) { michael@0: // If the next line is a block line then this line is the last in a michael@0: // group of inline lines. michael@0: return aLine->IsBlock(); michael@0: } michael@0: // The next line is empty, try the next one michael@0: } michael@0: michael@0: // XXX Not sure about this part michael@0: // Try our next-in-flows lines to answer the question michael@0: nsBlockFrame* nextInFlow = (nsBlockFrame*) GetNextInFlow(); michael@0: while (nullptr != nextInFlow) { michael@0: for (line_iterator line = nextInFlow->begin_lines(), michael@0: line_end = nextInFlow->end_lines(); michael@0: line != line_end; michael@0: ++line) michael@0: { michael@0: if (0 != line->GetChildCount()) michael@0: return line->IsBlock(); michael@0: } michael@0: nextInFlow = (nsBlockFrame*) nextInFlow->GetNextInFlow(); michael@0: } michael@0: michael@0: // This is the last line - so don't allow justification michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::PlaceLine(nsBlockReflowState& aState, michael@0: nsLineLayout& aLineLayout, michael@0: line_iterator aLine, michael@0: nsFloatManager::SavedState *aFloatStateBeforeLine, michael@0: nsRect& aFloatAvailableSpace, michael@0: nscoord& aAvailableSpaceHeight, michael@0: bool* aKeepReflowGoing) michael@0: { michael@0: // Trim extra white-space from the line before placing the frames michael@0: aLineLayout.TrimTrailingWhiteSpace(); michael@0: michael@0: // Vertically align the frames on this line. michael@0: // michael@0: // According to the CSS2 spec, section 12.6.1, the "marker" box michael@0: // participates in the height calculation of the list-item box's michael@0: // first line box. michael@0: // michael@0: // There are exactly two places a bullet can be placed: near the michael@0: // first or second line. It's only placed on the second line in a michael@0: // rare case: when the first line is empty. michael@0: bool addedBullet = false; michael@0: if (HasOutsideBullet() && michael@0: ((aLine == mLines.front() && michael@0: (!aLineLayout.IsZeroBSize() || (aLine == mLines.back()))) || michael@0: (mLines.front() != mLines.back() && michael@0: 0 == mLines.front()->BSize() && michael@0: aLine == mLines.begin().next()))) { michael@0: nsHTMLReflowMetrics metrics(aState.mReflowState); michael@0: nsIFrame* bullet = GetOutsideBullet(); michael@0: ReflowBullet(bullet, aState, metrics, aState.mY); michael@0: NS_ASSERTION(!BulletIsEmpty() || metrics.Height() == 0, michael@0: "empty bullet took up space"); michael@0: aLineLayout.AddBulletFrame(bullet, metrics); michael@0: addedBullet = true; michael@0: } michael@0: aLineLayout.BlockDirAlignLine(); michael@0: michael@0: // We want to compare to the available space that we would have had in michael@0: // the line's height *before* we placed any floats in the line itself. michael@0: // Floats that are in the line are handled during line reflow (and may michael@0: // result in floats being pushed to below the line or (I HOPE???) in a michael@0: // reflow with a forced break position). michael@0: nsRect oldFloatAvailableSpace(aFloatAvailableSpace); michael@0: // As we redo for floats, we can't reduce the amount of height we're michael@0: // checking. michael@0: aAvailableSpaceHeight = std::max(aAvailableSpaceHeight, aLine->BSize()); michael@0: aFloatAvailableSpace = michael@0: aState.GetFloatAvailableSpaceForHeight(aLine->BStart(), michael@0: aAvailableSpaceHeight, michael@0: aFloatStateBeforeLine).mRect; michael@0: NS_ASSERTION(aFloatAvailableSpace.y == oldFloatAvailableSpace.y, "yikes"); michael@0: // Restore the height to the position of the next band. michael@0: aFloatAvailableSpace.height = oldFloatAvailableSpace.height; michael@0: // If the available space between the floats is smaller now that we michael@0: // know the height, return false (and cause another pass with michael@0: // LINE_REFLOW_REDO_MORE_FLOATS). michael@0: if (AvailableSpaceShrunk(oldFloatAvailableSpace, aFloatAvailableSpace)) { michael@0: return false; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: static nscoord lastHeight = 0; michael@0: if (CRAZY_SIZE(aLine->BStart())) { michael@0: lastHeight = aLine->BStart(); michael@0: if (abs(aLine->BStart() - lastHeight) > CRAZY_COORD/10) { michael@0: nsFrame::ListTag(stdout); michael@0: printf(": line=%p y=%d line.bounds.height=%d\n", michael@0: static_cast(aLine.get()), michael@0: aLine->BStart(), aLine->BSize()); michael@0: } michael@0: } michael@0: else { michael@0: lastHeight = 0; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Only block frames horizontally align their children because michael@0: // inline frames "shrink-wrap" around their children (therefore michael@0: // there is no extra horizontal space). michael@0: const nsStyleText* styleText = StyleText(); michael@0: michael@0: /** michael@0: * text-align-last defaults to the same value as text-align when michael@0: * text-align-last is set to auto (except when text-align is set to justify), michael@0: * so in that case we don't need to set isLastLine. michael@0: * michael@0: * In other words, isLastLine really means isLastLineAndWeCare. michael@0: */ michael@0: bool isLastLine = michael@0: !IsSVGText() && michael@0: ((NS_STYLE_TEXT_ALIGN_AUTO != styleText->mTextAlignLast || michael@0: NS_STYLE_TEXT_ALIGN_JUSTIFY == styleText->mTextAlign) && michael@0: (aLineLayout.GetLineEndsInBR() || michael@0: IsLastLine(aState, aLine))); michael@0: michael@0: aLineLayout.InlineDirAlignFrames(aLine, isLastLine); michael@0: michael@0: // From here on, pfd->mBounds rectangles are incorrect because bidi michael@0: // might have moved frames around! michael@0: nsOverflowAreas overflowAreas; michael@0: aLineLayout.RelativePositionFrames(overflowAreas); michael@0: aLine->SetOverflowAreas(overflowAreas); michael@0: if (addedBullet) { michael@0: aLineLayout.RemoveBulletFrame(GetOutsideBullet()); michael@0: } michael@0: michael@0: // Inline lines do not have margins themselves; however they are michael@0: // impacted by prior block margins. If this line ends up having some michael@0: // height then we zero out the previous bottom margin value that was michael@0: // already applied to the line's starting Y coordinate. Otherwise we michael@0: // leave it be so that the previous blocks bottom margin can be michael@0: // collapsed with a block that follows. michael@0: nscoord newY; michael@0: michael@0: if (!aLine->CachedIsEmpty()) { michael@0: // This line has some height. Therefore the application of the michael@0: // previous-bottom-margin should stick. michael@0: aState.mPrevBottomMargin.Zero(); michael@0: newY = aLine->BEnd(); michael@0: } michael@0: else { michael@0: // Don't let the previous-bottom-margin value affect the newY michael@0: // coordinate (it was applied in ReflowInlineFrames speculatively) michael@0: // since the line is empty. michael@0: // We already called |ShouldApplyTopMargin|, and if we applied it michael@0: // then BRS_APPLYTOPMARGIN is set. michael@0: nscoord dy = aState.GetFlag(BRS_APPLYTOPMARGIN) michael@0: ? -aState.mPrevBottomMargin.get() : 0; michael@0: newY = aState.mY + dy; michael@0: } michael@0: michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(aState.mReflowStatus) && michael@0: ShouldAvoidBreakInside(aState.mReflowState)) { michael@0: aLine->AppendFloats(aState.mCurrentLineFloats); michael@0: aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: return true; michael@0: } michael@0: michael@0: // See if the line fit (our first line always does). michael@0: if (mLines.front() != aLine && michael@0: newY > aState.mBottomEdge && michael@0: aState.mBottomEdge != NS_UNCONSTRAINEDSIZE) { michael@0: NS_ASSERTION(aState.mCurrentLine == aLine, "oops"); michael@0: if (ShouldAvoidBreakInside(aState.mReflowState)) { michael@0: // All our content doesn't fit, start on the next page. michael@0: aState.mReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: } else { michael@0: // Push aLine and all of its children and anything else that michael@0: // follows to our next-in-flow. michael@0: PushTruncatedLine(aState, aLine, aKeepReflowGoing); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: aState.mY = newY; michael@0: michael@0: // Add the already placed current-line floats to the line michael@0: aLine->AppendFloats(aState.mCurrentLineFloats); michael@0: michael@0: // Any below current line floats to place? michael@0: if (aState.mBelowCurrentLineFloats.NotEmpty()) { michael@0: // Reflow the below-current-line floats, which places on the line's michael@0: // float list. michael@0: aState.PlaceBelowCurrentLineFloats(aState.mBelowCurrentLineFloats, aLine); michael@0: aLine->AppendFloats(aState.mBelowCurrentLineFloats); michael@0: } michael@0: michael@0: // When a line has floats, factor them into the combined-area michael@0: // computations. michael@0: if (aLine->HasFloats()) { michael@0: // Combine the float combined area (stored in aState) and the michael@0: // value computed by the line layout code. michael@0: nsOverflowAreas lineOverflowAreas; michael@0: NS_FOR_FRAME_OVERFLOW_TYPES(otype) { michael@0: nsRect &o = lineOverflowAreas.Overflow(otype); michael@0: o = aLine->GetOverflowArea(otype); michael@0: #ifdef NOISY_COMBINED_AREA michael@0: ListTag(stdout); michael@0: printf(": overflow %d lineCA=%d,%d,%d,%d floatCA=%d,%d,%d,%d\n", michael@0: otype, michael@0: o.x, o.y, o.width, o.height, michael@0: aState.mFloatOverflowAreas.Overflow(otype).x, michael@0: aState.mFloatOverflowAreas.Overflow(otype).y, michael@0: aState.mFloatOverflowAreas.Overflow(otype).width, michael@0: aState.mFloatOverflowAreas.Overflow(otype).height); michael@0: #endif michael@0: o.UnionRect(aState.mFloatOverflowAreas.Overflow(otype), o); michael@0: michael@0: #ifdef NOISY_COMBINED_AREA michael@0: printf(" ==> final lineCA=%d,%d,%d,%d\n", michael@0: o.x, o.y, o.width, o.height); michael@0: #endif michael@0: } michael@0: aLine->SetOverflowAreas(lineOverflowAreas); michael@0: } michael@0: michael@0: // Apply break-after clearing if necessary michael@0: // This must stay in sync with |ReflowDirtyLines|. michael@0: if (aLine->HasFloatBreakAfter()) { michael@0: aState.mY = aState.ClearFloats(aState.mY, aLine->GetBreakTypeAfter()); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::PushLines(nsBlockReflowState& aState, michael@0: nsLineList::iterator aLineBefore) michael@0: { michael@0: // NOTE: aLineBefore is always a normal line, not an overflow line. michael@0: // The following expression will assert otherwise. michael@0: DebugOnly check = aLineBefore == mLines.begin(); michael@0: michael@0: nsLineList::iterator overBegin(aLineBefore.next()); michael@0: michael@0: // PushTruncatedPlaceholderLine sometimes pushes the first line. Ugh. michael@0: bool firstLine = overBegin == begin_lines(); michael@0: michael@0: if (overBegin != end_lines()) { michael@0: // Remove floats in the lines from mFloats michael@0: nsFrameList floats; michael@0: CollectFloats(overBegin->mFirstChild, floats, true); michael@0: michael@0: if (floats.NotEmpty()) { michael@0: // Push the floats onto the front of the overflow out-of-flows list michael@0: nsAutoOOFFrameList oofs(this); michael@0: oofs.mList.InsertFrames(nullptr, nullptr, floats); michael@0: } michael@0: michael@0: // overflow lines can already exist in some cases, in particular, michael@0: // when shrinkwrapping and we discover that the shrinkwap causes michael@0: // the height of some child block to grow which creates additional michael@0: // overflowing content. In such cases we must prepend the new michael@0: // overflow to the existing overflow. michael@0: FrameLines* overflowLines = RemoveOverflowLines(); michael@0: if (!overflowLines) { michael@0: // XXXldb use presshell arena! michael@0: overflowLines = new FrameLines(); michael@0: } michael@0: if (overflowLines) { michael@0: nsIFrame* lineBeforeLastFrame; michael@0: if (firstLine) { michael@0: lineBeforeLastFrame = nullptr; // removes all frames michael@0: } else { michael@0: nsIFrame* f = overBegin->mFirstChild; michael@0: lineBeforeLastFrame = f ? f->GetPrevSibling() : mFrames.LastChild(); michael@0: NS_ASSERTION(!f || lineBeforeLastFrame == aLineBefore->LastChild(), michael@0: "unexpected line frames"); michael@0: } michael@0: nsFrameList pushedFrames = mFrames.RemoveFramesAfter(lineBeforeLastFrame); michael@0: overflowLines->mFrames.InsertFrames(nullptr, nullptr, pushedFrames); michael@0: michael@0: overflowLines->mLines.splice(overflowLines->mLines.begin(), mLines, michael@0: overBegin, end_lines()); michael@0: NS_ASSERTION(!overflowLines->mLines.empty(), "should not be empty"); michael@0: // this takes ownership but it won't delete it immediately so we michael@0: // can keep using it. michael@0: SetOverflowLines(overflowLines); michael@0: michael@0: // Mark all the overflow lines dirty so that they get reflowed when michael@0: // they are pulled up by our next-in-flow. michael@0: michael@0: // XXXldb Can this get called O(N) times making the whole thing O(N^2)? michael@0: for (line_iterator line = overflowLines->mLines.begin(), michael@0: line_end = overflowLines->mLines.end(); michael@0: line != line_end; michael@0: ++line) michael@0: { michael@0: line->MarkDirty(); michael@0: line->MarkPreviousMarginDirty(); michael@0: line->SetBoundsEmpty(); michael@0: if (line->HasFloats()) { michael@0: line->FreeFloats(aState.mFloatCacheFreeList); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: VerifyOverflowSituation(); michael@0: #endif michael@0: } michael@0: michael@0: // The overflowLines property is stored as a pointer to a line list, michael@0: // which must be deleted. However, the following functions all maintain michael@0: // the invariant that the property is never set if the list is empty. michael@0: michael@0: bool michael@0: nsBlockFrame::DrainOverflowLines() michael@0: { michael@0: #ifdef DEBUG michael@0: VerifyOverflowSituation(); michael@0: #endif michael@0: michael@0: // Steal the prev-in-flow's overflow lines and prepend them. michael@0: bool didFindOverflow = false; michael@0: nsBlockFrame* prevBlock = static_cast(GetPrevInFlow()); michael@0: if (prevBlock) { michael@0: prevBlock->ClearLineCursor(); michael@0: FrameLines* overflowLines = prevBlock->RemoveOverflowLines(); michael@0: if (overflowLines) { michael@0: // Make all the frames on the overflow line list mine. michael@0: ReparentFrames(overflowLines->mFrames, prevBlock, this); michael@0: michael@0: // Make the overflow out-of-flow frames mine too. michael@0: nsAutoOOFFrameList oofs(prevBlock); michael@0: if (oofs.mList.NotEmpty()) { michael@0: ReparentFrames(oofs.mList, prevBlock, this); michael@0: mFloats.InsertFrames(nullptr, nullptr, oofs.mList); michael@0: } michael@0: michael@0: if (!mLines.empty()) { michael@0: // Remember to recompute the margins on the first line. This will michael@0: // also recompute the correct deltaY if necessary. michael@0: mLines.front()->MarkPreviousMarginDirty(); michael@0: } michael@0: // The overflow lines have already been marked dirty and their previous michael@0: // margins marked dirty also. michael@0: michael@0: // Prepend the overflow frames/lines to our principal list. michael@0: mFrames.InsertFrames(nullptr, nullptr, overflowLines->mFrames); michael@0: mLines.splice(mLines.begin(), overflowLines->mLines); michael@0: NS_ASSERTION(overflowLines->mLines.empty(), "splice should empty list"); michael@0: delete overflowLines; michael@0: didFindOverflow = true; michael@0: } michael@0: } michael@0: michael@0: // Now append our own overflow lines. michael@0: return DrainSelfOverflowList() || didFindOverflow; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::DrainSelfOverflowList() michael@0: { michael@0: nsAutoPtr ourOverflowLines(RemoveOverflowLines()); michael@0: if (!ourOverflowLines) { michael@0: return false; michael@0: } michael@0: michael@0: // No need to reparent frames in our own overflow lines/oofs, because they're michael@0: // already ours. But we should put overflow floats back in mFloats. michael@0: nsAutoOOFFrameList oofs(this); michael@0: if (oofs.mList.NotEmpty()) { michael@0: // The overflow floats go after our regular floats. michael@0: mFloats.AppendFrames(nullptr, oofs.mList); michael@0: } michael@0: michael@0: if (!ourOverflowLines->mLines.empty()) { michael@0: mFrames.AppendFrames(nullptr, ourOverflowLines->mFrames); michael@0: mLines.splice(mLines.end(), ourOverflowLines->mLines); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Pushed floats are floats whose placeholders are in a previous michael@0: * continuation. They might themselves be next-continuations of a float michael@0: * that partially fit in an earlier continuation, or they might be the michael@0: * first continuation of a float that couldn't be placed at all. michael@0: * michael@0: * Pushed floats live permanently at the beginning of a block's float michael@0: * list, where they must live *before* any floats whose placeholders are michael@0: * in that block. michael@0: * michael@0: * Temporarily, during reflow, they also live on the pushed floats list, michael@0: * which only holds them between (a) when one continuation pushes them to michael@0: * its pushed floats list because they don't fit and (b) when the next michael@0: * continuation pulls them onto the beginning of its float list. michael@0: * michael@0: * DrainPushedFloats sets up pushed floats the way we need them at the michael@0: * start of reflow; they are then reflowed by ReflowPushedFloats (which michael@0: * might push some of them on). Floats with placeholders in this block michael@0: * are reflowed by (nsBlockReflowState/nsLineLayout)::AddFloat, which michael@0: * also maintains these invariants. michael@0: */ michael@0: void michael@0: nsBlockFrame::DrainPushedFloats(nsBlockReflowState& aState) michael@0: { michael@0: #ifdef DEBUG michael@0: // Between when we drain pushed floats and when we complete reflow, michael@0: // we're allowed to have multiple continuations of the same float on michael@0: // our floats list, since a first-in-flow might get pushed to a later michael@0: // continuation of its containing block. But it's not permitted michael@0: // outside that time. michael@0: nsLayoutUtils::AssertNoDuplicateContinuations(this, mFloats); michael@0: #endif michael@0: michael@0: // If we're getting reflowed multiple times without our michael@0: // next-continuation being reflowed, we might need to pull back floats michael@0: // that we just put in the list to be pushed to our next-in-flow. michael@0: // We don't want to pull back any next-in-flows of floats on our own michael@0: // float list, and we only need to pull back first-in-flows whose michael@0: // placeholders were in earlier blocks (since first-in-flows whose michael@0: // placeholders are in this block will get pulled appropriately by michael@0: // AddFloat, and will then be more likely to be in the correct order). michael@0: // FIXME: What if there's a continuation in our pushed floats list michael@0: // whose prev-in-flow is in a previous continuation of this block michael@0: // rather than this block? Might we need to pull it back so we don't michael@0: // report ourselves complete? michael@0: // FIXME: Maybe we should just pull all of them back? michael@0: nsPresContext* presContext = PresContext(); michael@0: nsFrameList* ourPushedFloats = GetPushedFloats(); michael@0: if (ourPushedFloats) { michael@0: // When we pull back floats, we want to put them with the pushed michael@0: // floats, which must live at the start of our float list, but we michael@0: // want them at the end of those pushed floats. michael@0: // FIXME: This isn't quite right! What if they're all pushed floats? michael@0: nsIFrame *insertionPrevSibling = nullptr; /* beginning of list */ michael@0: for (nsIFrame* f = mFloats.FirstChild(); michael@0: f && (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT); michael@0: f = f->GetNextSibling()) { michael@0: insertionPrevSibling = f; michael@0: } michael@0: michael@0: for (nsIFrame *f = ourPushedFloats->LastChild(), *next; f; f = next) { michael@0: next = f->GetPrevSibling(); michael@0: michael@0: if (f->GetPrevContinuation()) { michael@0: // FIXME michael@0: } else { michael@0: nsPlaceholderFrame *placeholder = michael@0: presContext->FrameManager()->GetPlaceholderFrameFor(f); michael@0: nsIFrame *floatOriginalParent = presContext->PresShell()-> michael@0: FrameConstructor()->GetFloatContainingBlock(placeholder); michael@0: if (floatOriginalParent != this) { michael@0: // This is a first continuation that was pushed from one of our michael@0: // previous continuations. Take it out of the pushed floats michael@0: // list and put it in our floats list, before any of our michael@0: // floats, but after other pushed floats. michael@0: ourPushedFloats->RemoveFrame(f); michael@0: mFloats.InsertFrame(nullptr, insertionPrevSibling, f); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (ourPushedFloats->IsEmpty()) { michael@0: RemovePushedFloats()->Delete(presContext->PresShell()); michael@0: } michael@0: } michael@0: michael@0: // After our prev-in-flow has completed reflow, it may have a pushed michael@0: // floats list, containing floats that we need to own. Take these. michael@0: nsBlockFrame* prevBlock = static_cast(GetPrevInFlow()); michael@0: if (prevBlock) { michael@0: AutoFrameListPtr list(presContext, prevBlock->RemovePushedFloats()); michael@0: if (list && list->NotEmpty()) { michael@0: mFloats.InsertFrames(this, nullptr, *list); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsBlockFrame::FrameLines* michael@0: nsBlockFrame::GetOverflowLines() const michael@0: { michael@0: if (!HasOverflowLines()) { michael@0: return nullptr; michael@0: } michael@0: FrameLines* prop = michael@0: static_cast(Properties().Get(OverflowLinesProperty())); michael@0: NS_ASSERTION(prop && !prop->mLines.empty() && michael@0: prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() : michael@0: prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), michael@0: "value should always be stored and non-empty when state set"); michael@0: return prop; michael@0: } michael@0: michael@0: nsBlockFrame::FrameLines* michael@0: nsBlockFrame::RemoveOverflowLines() michael@0: { michael@0: if (!HasOverflowLines()) { michael@0: return nullptr; michael@0: } michael@0: FrameLines* prop = michael@0: static_cast(Properties().Remove(OverflowLinesProperty())); michael@0: NS_ASSERTION(prop && !prop->mLines.empty() && michael@0: prop->mLines.front()->GetChildCount() == 0 ? prop->mFrames.IsEmpty() : michael@0: prop->mLines.front()->mFirstChild == prop->mFrames.FirstChild(), michael@0: "value should always be stored and non-empty when state set"); michael@0: RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); michael@0: return prop; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::DestroyOverflowLines() michael@0: { michael@0: NS_ASSERTION(HasOverflowLines(), "huh?"); michael@0: FrameLines* prop = michael@0: static_cast(Properties().Remove(OverflowLinesProperty())); michael@0: NS_ASSERTION(prop && prop->mLines.empty(), michael@0: "value should always be stored but empty when destroying"); michael@0: RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); michael@0: delete prop; michael@0: } michael@0: michael@0: // This takes ownership of aOverflowLines. michael@0: // XXX We should allocate overflowLines from presShell arena! michael@0: void michael@0: nsBlockFrame::SetOverflowLines(FrameLines* aOverflowLines) michael@0: { michael@0: NS_ASSERTION(aOverflowLines, "null lines"); michael@0: NS_ASSERTION(!aOverflowLines->mLines.empty(), "empty lines"); michael@0: NS_ASSERTION(aOverflowLines->mLines.front()->mFirstChild == michael@0: aOverflowLines->mFrames.FirstChild(), michael@0: "invalid overflow lines / frames"); michael@0: NS_ASSERTION(!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_LINES), michael@0: "Overwriting existing overflow lines"); michael@0: michael@0: FrameProperties props = Properties(); michael@0: // Verify that we won't overwrite an existing overflow list michael@0: NS_ASSERTION(!props.Get(OverflowLinesProperty()), "existing overflow list"); michael@0: props.Set(OverflowLinesProperty(), aOverflowLines); michael@0: AddStateBits(NS_BLOCK_HAS_OVERFLOW_LINES); michael@0: } michael@0: michael@0: nsFrameList* michael@0: nsBlockFrame::GetOverflowOutOfFlows() const michael@0: { michael@0: if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { michael@0: return nullptr; michael@0: } michael@0: nsFrameList* result = michael@0: GetPropTableFrames(OverflowOutOfFlowsProperty()); michael@0: NS_ASSERTION(result, "value should always be non-empty when state set"); michael@0: return result; michael@0: } michael@0: michael@0: // This takes ownership of the frames michael@0: void michael@0: nsBlockFrame::SetOverflowOutOfFlows(const nsFrameList& aList, michael@0: nsFrameList* aPropValue) michael@0: { michael@0: NS_PRECONDITION(!!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) == michael@0: !!aPropValue, "state does not match value"); michael@0: michael@0: if (aList.IsEmpty()) { michael@0: if (!(GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS)) { michael@0: return; michael@0: } michael@0: nsFrameList* list = RemovePropTableFrames(OverflowOutOfFlowsProperty()); michael@0: NS_ASSERTION(aPropValue == list, "prop value mismatch"); michael@0: list->Clear(); michael@0: list->Delete(PresContext()->PresShell()); michael@0: RemoveStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); michael@0: } michael@0: else if (GetStateBits() & NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS) { michael@0: NS_ASSERTION(aPropValue == GetPropTableFrames(OverflowOutOfFlowsProperty()), michael@0: "prop value mismatch"); michael@0: *aPropValue = aList; michael@0: } michael@0: else { michael@0: SetPropTableFrames(new (PresContext()->PresShell()) nsFrameList(aList), michael@0: OverflowOutOfFlowsProperty()); michael@0: AddStateBits(NS_BLOCK_HAS_OVERFLOW_OUT_OF_FLOWS); michael@0: } michael@0: } michael@0: michael@0: nsBulletFrame* michael@0: nsBlockFrame::GetInsideBullet() const michael@0: { michael@0: if (!HasInsideBullet()) { michael@0: return nullptr; michael@0: } michael@0: NS_ASSERTION(!HasOutsideBullet(), "invalid bullet state"); michael@0: nsBulletFrame* frame = michael@0: static_cast(Properties().Get(InsideBulletProperty())); michael@0: NS_ASSERTION(frame && frame->GetType() == nsGkAtoms::bulletFrame, michael@0: "bogus inside bullet frame"); michael@0: return frame; michael@0: } michael@0: michael@0: nsBulletFrame* michael@0: nsBlockFrame::GetOutsideBullet() const michael@0: { michael@0: nsFrameList* list = GetOutsideBulletList(); michael@0: return list ? static_cast(list->FirstChild()) michael@0: : nullptr; michael@0: } michael@0: michael@0: nsFrameList* michael@0: nsBlockFrame::GetOutsideBulletList() const michael@0: { michael@0: if (!HasOutsideBullet()) { michael@0: return nullptr; michael@0: } michael@0: NS_ASSERTION(!HasInsideBullet(), "invalid bullet state"); michael@0: nsFrameList* list = michael@0: static_cast(Properties().Get(OutsideBulletProperty())); michael@0: NS_ASSERTION(list && list->GetLength() == 1 && michael@0: list->FirstChild()->GetType() == nsGkAtoms::bulletFrame, michael@0: "bogus outside bullet list"); michael@0: return list; michael@0: } michael@0: michael@0: nsFrameList* michael@0: nsBlockFrame::GetPushedFloats() const michael@0: { michael@0: if (!HasPushedFloats()) { michael@0: return nullptr; michael@0: } michael@0: nsFrameList* result = michael@0: static_cast(Properties().Get(PushedFloatProperty())); michael@0: NS_ASSERTION(result, "value should always be non-empty when state set"); michael@0: return result; michael@0: } michael@0: michael@0: nsFrameList* michael@0: nsBlockFrame::EnsurePushedFloats() michael@0: { michael@0: nsFrameList *result = GetPushedFloats(); michael@0: if (result) michael@0: return result; michael@0: michael@0: result = new (PresContext()->PresShell()) nsFrameList; michael@0: Properties().Set(PushedFloatProperty(), result); michael@0: AddStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsFrameList* michael@0: nsBlockFrame::RemovePushedFloats() michael@0: { michael@0: if (!HasPushedFloats()) { michael@0: return nullptr; michael@0: } michael@0: nsFrameList *result = michael@0: static_cast(Properties().Remove(PushedFloatProperty())); michael@0: RemoveStateBits(NS_BLOCK_HAS_PUSHED_FLOATS); michael@0: NS_ASSERTION(result, "value should always be non-empty when state set"); michael@0: return result; michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////// michael@0: // Frame list manipulation routines michael@0: michael@0: nsresult michael@0: nsBlockFrame::AppendFrames(ChildListID aListID, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: if (aFrameList.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: if (aListID != kPrincipalList) { michael@0: if (kAbsoluteList == aListID) { michael@0: return nsContainerFrame::AppendFrames(aListID, aFrameList); michael@0: } michael@0: else if (kFloatList == aListID) { michael@0: mFloats.AppendFrames(nullptr, aFrameList); michael@0: return NS_OK; michael@0: } michael@0: else { michael@0: NS_ERROR("unexpected child list"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: } michael@0: michael@0: // Find the proper last-child for where the append should go michael@0: nsIFrame* lastKid = mFrames.LastChild(); michael@0: NS_ASSERTION((mLines.empty() ? nullptr : mLines.back()->LastChild()) == michael@0: lastKid, "out-of-sync mLines / mFrames"); michael@0: michael@0: // Add frames after the last child michael@0: #ifdef NOISY_REFLOW_REASON michael@0: ListTag(stdout); michael@0: printf(": append "); michael@0: nsFrame::ListTag(stdout, aFrameList); michael@0: if (lastKid) { michael@0: printf(" after "); michael@0: nsFrame::ListTag(stdout, lastKid); michael@0: } michael@0: printf("\n"); michael@0: #endif michael@0: michael@0: AddFrames(aFrameList, lastKid); michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::InsertFrames(ChildListID aListID, michael@0: nsIFrame* aPrevFrame, michael@0: nsFrameList& aFrameList) michael@0: { michael@0: NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == this, michael@0: "inserting after sibling frame with different parent"); michael@0: michael@0: if (aListID != kPrincipalList) { michael@0: if (kAbsoluteList == aListID) { michael@0: return nsContainerFrame::InsertFrames(aListID, aPrevFrame, aFrameList); michael@0: } michael@0: else if (kFloatList == aListID) { michael@0: mFloats.InsertFrames(this, aPrevFrame, aFrameList); michael@0: return NS_OK; michael@0: } michael@0: else if (kNoReflowPrincipalList != aListID) { michael@0: NS_ERROR("unexpected child list"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: } michael@0: michael@0: #ifdef NOISY_REFLOW_REASON michael@0: ListTag(stdout); michael@0: printf(": insert "); michael@0: nsFrame::ListTag(stdout, aFrameList); michael@0: if (aPrevFrame) { michael@0: printf(" after "); michael@0: nsFrame::ListTag(stdout, aPrevFrame); michael@0: } michael@0: printf("\n"); michael@0: #endif michael@0: michael@0: AddFrames(aFrameList, aPrevFrame); michael@0: michael@0: if (aListID != kNoReflowPrincipalList) michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame) michael@0: { michael@0: nsIAtom* type = aLastFrame->GetType(); michael@0: if (type == nsGkAtoms::brFrame) { michael@0: return true; michael@0: } michael@0: // XXX the TEXT_OFFSETS_NEED_FIXING check is a wallpaper for bug 822910. michael@0: if (type == nsGkAtoms::textFrame && michael@0: !(aLastFrame->GetStateBits() & TEXT_OFFSETS_NEED_FIXING)) { michael@0: return aLastFrame->HasSignificantTerminalNewline(); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::AddFrames(nsFrameList& aFrameList, nsIFrame* aPrevSibling) michael@0: { michael@0: // Clear our line cursor, since our lines may change. michael@0: ClearLineCursor(); michael@0: michael@0: if (aFrameList.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: // If we're inserting at the beginning of our list and we have an michael@0: // inside bullet, insert after that bullet. michael@0: if (!aPrevSibling && HasInsideBullet()) { michael@0: aPrevSibling = GetInsideBullet(); michael@0: } michael@0: michael@0: // Attempt to find the line that contains the previous sibling michael@0: FrameLines* overflowLines; michael@0: nsLineList* lineList = &mLines; michael@0: nsLineList::iterator prevSibLine = lineList->end(); michael@0: int32_t prevSiblingIndex = -1; michael@0: if (aPrevSibling) { michael@0: // XXX_perf This is technically O(N^2) in some cases, but by using michael@0: // RFind instead of Find, we make it O(N) in the most common case, michael@0: // which is appending content. michael@0: michael@0: // Find the line that contains the previous sibling michael@0: if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(), michael@0: prevSibLine, mFrames.LastChild(), michael@0: &prevSiblingIndex)) { michael@0: // Not in mLines - try overflow lines. michael@0: overflowLines = GetOverflowLines(); michael@0: lineList = overflowLines ? &overflowLines->mLines : nullptr; michael@0: if (overflowLines) { michael@0: prevSibLine = overflowLines->mLines.end(); michael@0: prevSiblingIndex = -1; michael@0: if (!nsLineBox::RFindLineContaining(aPrevSibling, lineList->begin(), michael@0: prevSibLine, michael@0: overflowLines->mFrames.LastChild(), michael@0: &prevSiblingIndex)) { michael@0: lineList = nullptr; michael@0: } michael@0: } michael@0: if (!lineList) { michael@0: // Note: defensive code! RFindLineContaining must not return michael@0: // false in this case, so if it does... michael@0: NS_NOTREACHED("prev sibling not in line list"); michael@0: lineList = &mLines; michael@0: aPrevSibling = nullptr; michael@0: prevSibLine = lineList->end(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Find the frame following aPrevSibling so that we can join up the michael@0: // two lists of frames. michael@0: if (aPrevSibling) { michael@0: // Split line containing aPrevSibling in two if the insertion michael@0: // point is somewhere in the middle of the line. michael@0: int32_t rem = prevSibLine->GetChildCount() - prevSiblingIndex - 1; michael@0: if (rem) { michael@0: // Split the line in two where the frame(s) are being inserted. michael@0: nsLineBox* line = NewLineBox(prevSibLine, aPrevSibling->GetNextSibling(), rem); michael@0: lineList->after_insert(prevSibLine, line); michael@0: // Mark prevSibLine dirty and as needing textrun invalidation, since michael@0: // we may be breaking up text in the line. Its previous line may also michael@0: // need to be invalidated because it may be able to pull some text up. michael@0: MarkLineDirty(prevSibLine, lineList); michael@0: // The new line will also need its textruns recomputed because of the michael@0: // frame changes. michael@0: line->MarkDirty(); michael@0: line->SetInvalidateTextRuns(true); michael@0: } michael@0: } michael@0: else if (! lineList->empty()) { michael@0: lineList->front()->MarkDirty(); michael@0: lineList->front()->SetInvalidateTextRuns(true); michael@0: } michael@0: nsFrameList& frames = lineList == &mLines ? mFrames : overflowLines->mFrames; michael@0: const nsFrameList::Slice& newFrames = michael@0: frames.InsertFrames(nullptr, aPrevSibling, aFrameList); michael@0: michael@0: // Walk through the new frames being added and update the line data michael@0: // structures to fit. michael@0: for (nsFrameList::Enumerator e(newFrames); !e.AtEnd(); e.Next()) { michael@0: nsIFrame* newFrame = e.get(); michael@0: NS_ASSERTION(!aPrevSibling || aPrevSibling->GetNextSibling() == newFrame, michael@0: "Unexpected aPrevSibling"); michael@0: NS_ASSERTION(newFrame->GetType() != nsGkAtoms::placeholderFrame || michael@0: (!newFrame->IsAbsolutelyPositioned() && michael@0: !newFrame->IsFloating()), michael@0: "Placeholders should not float or be positioned"); michael@0: michael@0: bool isBlock = newFrame->IsBlockOutside(); michael@0: michael@0: // If the frame is a block frame, or if there is no previous line or if the michael@0: // previous line is a block line we need to make a new line. We also make michael@0: // a new line, as an optimization, in the two cases we know we'll need it: michael@0: // if the previous line ended with a
    , or if it has significant whitespace michael@0: // and ended in a newline. michael@0: if (isBlock || prevSibLine == lineList->end() || prevSibLine->IsBlock() || michael@0: (aPrevSibling && ShouldPutNextSiblingOnNewLine(aPrevSibling))) { michael@0: // Create a new line for the frame and add its line to the line michael@0: // list. michael@0: nsLineBox* line = NewLineBox(newFrame, isBlock); michael@0: if (prevSibLine != lineList->end()) { michael@0: // Append new line after prevSibLine michael@0: lineList->after_insert(prevSibLine, line); michael@0: ++prevSibLine; michael@0: } michael@0: else { michael@0: // New line is going before the other lines michael@0: lineList->push_front(line); michael@0: prevSibLine = lineList->begin(); michael@0: } michael@0: } michael@0: else { michael@0: prevSibLine->NoteFrameAdded(newFrame); michael@0: // We're adding inline content to prevSibLine, so we need to mark it michael@0: // dirty, ensure its textruns are recomputed, and possibly do the same michael@0: // to its previous line since that line may be able to pull content up. michael@0: MarkLineDirty(prevSibLine, lineList); michael@0: } michael@0: michael@0: aPrevSibling = newFrame; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: MOZ_ASSERT(aFrameList.IsEmpty()); michael@0: VerifyLines(true); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::RemoveFloatFromFloatCache(nsIFrame* aFloat) michael@0: { michael@0: // Find which line contains the float, so we can update michael@0: // the float cache. michael@0: line_iterator line = begin_lines(), line_end = end_lines(); michael@0: for ( ; line != line_end; ++line) { michael@0: if (line->IsInline() && line->RemoveFloat(aFloat)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::RemoveFloat(nsIFrame* aFloat) michael@0: { michael@0: #ifdef DEBUG michael@0: // Floats live in mFloats, or in the PushedFloat or OverflowOutOfFlows michael@0: // frame list properties. michael@0: if (!mFloats.ContainsFrame(aFloat)) { michael@0: MOZ_ASSERT((GetOverflowOutOfFlows() && michael@0: GetOverflowOutOfFlows()->ContainsFrame(aFloat)) || michael@0: (GetPushedFloats() && michael@0: GetPushedFloats()->ContainsFrame(aFloat)), michael@0: "aFloat is not our child or on an unexpected frame list"); michael@0: } michael@0: #endif michael@0: michael@0: if (mFloats.StartRemoveFrame(aFloat)) { michael@0: return; michael@0: } michael@0: michael@0: nsFrameList* list = GetPushedFloats(); michael@0: if (list && list->ContinueRemoveFrame(aFloat)) { michael@0: #if 0 michael@0: // XXXmats not yet - need to investigate nsBlockReflowState::mPushedFloats michael@0: // first so we don't leave it pointing to a deleted list. michael@0: if (list->IsEmpty()) { michael@0: delete RemovePushedFloats(); michael@0: } michael@0: #endif michael@0: return; michael@0: } michael@0: michael@0: { michael@0: nsAutoOOFFrameList oofs(this); michael@0: if (oofs.mList.ContinueRemoveFrame(aFloat)) { michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void MarkSameFloatManagerLinesDirty(nsBlockFrame* aBlock) michael@0: { michael@0: nsBlockFrame* blockWithFloatMgr = aBlock; michael@0: while (!(blockWithFloatMgr->GetStateBits() & NS_BLOCK_FLOAT_MGR)) { michael@0: nsBlockFrame* bf = nsLayoutUtils::GetAsBlock(blockWithFloatMgr->GetParent()); michael@0: if (!bf) { michael@0: break; michael@0: } michael@0: blockWithFloatMgr = bf; michael@0: } michael@0: michael@0: // Mark every line at and below the line where the float was michael@0: // dirty, and mark their lines dirty too. We could probably do michael@0: // something more efficient --- e.g., just dirty the lines that intersect michael@0: // the float vertically. michael@0: MarkAllDescendantLinesDirty(blockWithFloatMgr); michael@0: } michael@0: michael@0: /** michael@0: * Returns true if aFrame is a block that has one or more float children. michael@0: */ michael@0: static bool BlockHasAnyFloats(nsIFrame* aFrame) michael@0: { michael@0: nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aFrame); michael@0: if (!block) michael@0: return false; michael@0: if (block->GetFirstChild(nsIFrame::kFloatList)) michael@0: return true; michael@0: michael@0: nsLineList::iterator line = block->begin_lines(); michael@0: nsLineList::iterator endLine = block->end_lines(); michael@0: while (line != endLine) { michael@0: if (line->IsBlock() && BlockHasAnyFloats(line->mFirstChild)) michael@0: return true; michael@0: ++line; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::RemoveFrame(ChildListID aListID, michael@0: nsIFrame* aOldFrame) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: #ifdef NOISY_REFLOW_REASON michael@0: ListTag(stdout); michael@0: printf(": remove "); michael@0: nsFrame::ListTag(stdout, aOldFrame); michael@0: printf("\n"); michael@0: #endif michael@0: michael@0: if (aListID == kPrincipalList) { michael@0: bool hasFloats = BlockHasAnyFloats(aOldFrame); michael@0: rv = DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS); michael@0: if (hasFloats) { michael@0: MarkSameFloatManagerLinesDirty(this); michael@0: } michael@0: } michael@0: else if (kAbsoluteList == aListID) { michael@0: nsContainerFrame::RemoveFrame(aListID, aOldFrame); michael@0: return NS_OK; michael@0: } michael@0: else if (kFloatList == aListID) { michael@0: // Make sure to mark affected lines dirty for the float frame michael@0: // we are removing; this way is a bit messy, but so is the rest of the code. michael@0: // See bug 390762. michael@0: NS_ASSERTION(!aOldFrame->GetPrevContinuation(), michael@0: "RemoveFrame should not be called on pushed floats."); michael@0: for (nsIFrame* f = aOldFrame; michael@0: f && !(f->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER); michael@0: f = f->GetNextContinuation()) { michael@0: MarkSameFloatManagerLinesDirty(static_cast(f->GetParent())); michael@0: } michael@0: DoRemoveOutOfFlowFrame(aOldFrame); michael@0: } michael@0: else if (kNoReflowPrincipalList == aListID) { michael@0: // Skip the call to |FrameNeedsReflow| below by returning now. michael@0: return DoRemoveFrame(aOldFrame, REMOVE_FIXED_CONTINUATIONS); michael@0: } michael@0: else { michael@0: NS_ERROR("unexpected child list"); michael@0: rv = NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eTreeChange, michael@0: NS_FRAME_HAS_DIRTY_CHILDREN); // XXX sufficient? michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::DoRemoveOutOfFlowFrame(nsIFrame* aFrame) michael@0: { michael@0: // The containing block is always the parent of aFrame. michael@0: nsBlockFrame* block = (nsBlockFrame*)aFrame->GetParent(); michael@0: michael@0: // Remove aFrame from the appropriate list. michael@0: if (aFrame->IsAbsolutelyPositioned()) { michael@0: // This also deletes the next-in-flows michael@0: block->GetAbsoluteContainingBlock()->RemoveFrame(block, michael@0: kAbsoluteList, michael@0: aFrame); michael@0: } michael@0: else { michael@0: // First remove aFrame's next-in-flows. michael@0: nsIFrame* nif = aFrame->GetNextInFlow(); michael@0: if (nif) { michael@0: static_cast(nif->GetParent()) michael@0: ->DeleteNextInFlowChild(nif, false); michael@0: } michael@0: // Now remove aFrame from its child list and Destroy it. michael@0: block->RemoveFloatFromFloatCache(aFrame); michael@0: block->RemoveFloat(aFrame); michael@0: aFrame->Destroy(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This helps us iterate over the list of all normal + overflow lines michael@0: */ michael@0: void michael@0: nsBlockFrame::TryAllLines(nsLineList::iterator* aIterator, michael@0: nsLineList::iterator* aStartIterator, michael@0: nsLineList::iterator* aEndIterator, michael@0: bool* aInOverflowLines, michael@0: FrameLines** aOverflowLines) michael@0: { michael@0: if (*aIterator == *aEndIterator) { michael@0: if (!*aInOverflowLines) { michael@0: // Try the overflow lines michael@0: *aInOverflowLines = true; michael@0: FrameLines* lines = GetOverflowLines(); michael@0: if (lines) { michael@0: *aStartIterator = lines->mLines.begin(); michael@0: *aIterator = *aStartIterator; michael@0: *aEndIterator = lines->mLines.end(); michael@0: *aOverflowLines = lines; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, michael@0: line_iterator aLine) michael@0: : mFrame(aFrame), mLine(aLine), mLineList(&aFrame->mLines) michael@0: { michael@0: // This will assert if aLine isn't in mLines of aFrame: michael@0: DebugOnly check = aLine == mFrame->begin_lines(); michael@0: } michael@0: michael@0: nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, michael@0: line_iterator aLine, bool aInOverflow) michael@0: : mFrame(aFrame), mLine(aLine), michael@0: mLineList(aInOverflow ? &aFrame->GetOverflowLines()->mLines michael@0: : &aFrame->mLines) michael@0: { michael@0: } michael@0: michael@0: nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, michael@0: bool* aFoundValidLine) michael@0: : mFrame(aFrame), mLineList(&aFrame->mLines) michael@0: { michael@0: mLine = aFrame->begin_lines(); michael@0: *aFoundValidLine = FindValidLine(); michael@0: } michael@0: michael@0: static nsIFrame* michael@0: FindChildContaining(nsBlockFrame* aFrame, nsIFrame* aFindFrame) michael@0: { michael@0: NS_ASSERTION(aFrame, "must have frame"); michael@0: nsIFrame* child; michael@0: while (true) { michael@0: nsIFrame* block = aFrame; michael@0: do { michael@0: child = nsLayoutUtils::FindChildContainingDescendant(block, aFindFrame); michael@0: if (child) michael@0: break; michael@0: block = block->GetNextContinuation(); michael@0: } while (block); michael@0: if (!child) michael@0: return nullptr; michael@0: if (!(child->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) michael@0: break; michael@0: aFindFrame = aFrame->PresContext()->FrameManager()->GetPlaceholderFrameFor(child); michael@0: } michael@0: michael@0: return child; michael@0: } michael@0: michael@0: nsBlockInFlowLineIterator::nsBlockInFlowLineIterator(nsBlockFrame* aFrame, michael@0: nsIFrame* aFindFrame, bool* aFoundValidLine) michael@0: : mFrame(aFrame), mLineList(&aFrame->mLines) michael@0: { michael@0: *aFoundValidLine = false; michael@0: michael@0: nsIFrame* child = FindChildContaining(aFrame, aFindFrame); michael@0: if (!child) michael@0: return; michael@0: michael@0: // Try to use the cursor if it exists, otherwise fall back to the first line michael@0: nsLineBox* cursor = aFrame->GetLineCursor(); michael@0: if (!cursor) { michael@0: line_iterator iter = aFrame->begin_lines(); michael@0: if (iter != aFrame->end_lines()) { michael@0: cursor = iter; michael@0: } michael@0: } michael@0: michael@0: if (cursor) { michael@0: // Perform a simultaneous forward and reverse search starting from the michael@0: // line cursor. michael@0: nsBlockFrame::line_iterator line = aFrame->line(cursor); michael@0: nsBlockFrame::reverse_line_iterator rline = aFrame->rline(cursor); michael@0: nsBlockFrame::line_iterator line_end = aFrame->end_lines(); michael@0: nsBlockFrame::reverse_line_iterator rline_end = aFrame->rend_lines(); michael@0: // rline is positioned on the line containing 'cursor', so it's not michael@0: // rline_end. So we can safely increment it (i.e. move it to one line michael@0: // earlier) to start searching there. michael@0: ++rline; michael@0: while (line != line_end || rline != rline_end) { michael@0: if (line != line_end) { michael@0: if (line->Contains(child)) { michael@0: *aFoundValidLine = true; michael@0: mLine = line; michael@0: return; michael@0: } michael@0: ++line; michael@0: } michael@0: if (rline != rline_end) { michael@0: if (rline->Contains(child)) { michael@0: *aFoundValidLine = true; michael@0: mLine = rline; michael@0: return; michael@0: } michael@0: ++rline; michael@0: } michael@0: } michael@0: // Didn't find the line michael@0: } michael@0: michael@0: // If we reach here, it means that we have not been able to find the michael@0: // desired frame in our in-flow lines. So we should start looking at michael@0: // our overflow lines. In order to do that, we set mLine to the end michael@0: // iterator so that FindValidLine starts to look at overflow lines, michael@0: // if any. michael@0: michael@0: mLine = aFrame->end_lines(); michael@0: michael@0: if (!FindValidLine()) michael@0: return; michael@0: michael@0: do { michael@0: if (mLine->Contains(child)) { michael@0: *aFoundValidLine = true; michael@0: return; michael@0: } michael@0: } while (Next()); michael@0: } michael@0: michael@0: nsBlockFrame::line_iterator michael@0: nsBlockInFlowLineIterator::End() michael@0: { michael@0: return mLineList->end(); michael@0: } michael@0: michael@0: bool michael@0: nsBlockInFlowLineIterator::IsLastLineInList() michael@0: { michael@0: line_iterator end = End(); michael@0: return mLine != end && mLine.next() == end; michael@0: } michael@0: michael@0: bool michael@0: nsBlockInFlowLineIterator::Next() michael@0: { michael@0: ++mLine; michael@0: return FindValidLine(); michael@0: } michael@0: michael@0: bool michael@0: nsBlockInFlowLineIterator::Prev() michael@0: { michael@0: line_iterator begin = mLineList->begin(); michael@0: if (mLine != begin) { michael@0: --mLine; michael@0: return true; michael@0: } michael@0: bool currentlyInOverflowLines = GetInOverflow(); michael@0: while (true) { michael@0: if (currentlyInOverflowLines) { michael@0: mLineList = &mFrame->mLines; michael@0: mLine = mLineList->end(); michael@0: if (mLine != mLineList->begin()) { michael@0: --mLine; michael@0: return true; michael@0: } michael@0: } else { michael@0: mFrame = static_cast(mFrame->GetPrevInFlow()); michael@0: if (!mFrame) michael@0: return false; michael@0: nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); michael@0: if (overflowLines) { michael@0: mLineList = &overflowLines->mLines; michael@0: mLine = mLineList->end(); michael@0: NS_ASSERTION(mLine != mLineList->begin(), "empty overflow line list?"); michael@0: --mLine; michael@0: return true; michael@0: } michael@0: } michael@0: currentlyInOverflowLines = !currentlyInOverflowLines; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsBlockInFlowLineIterator::FindValidLine() michael@0: { michael@0: line_iterator end = mLineList->end(); michael@0: if (mLine != end) michael@0: return true; michael@0: bool currentlyInOverflowLines = GetInOverflow(); michael@0: while (true) { michael@0: if (currentlyInOverflowLines) { michael@0: mFrame = static_cast(mFrame->GetNextInFlow()); michael@0: if (!mFrame) michael@0: return false; michael@0: mLineList = &mFrame->mLines; michael@0: mLine = mLineList->begin(); michael@0: if (mLine != mLineList->end()) michael@0: return true; michael@0: } else { michael@0: nsBlockFrame::FrameLines* overflowLines = mFrame->GetOverflowLines(); michael@0: if (overflowLines) { michael@0: mLineList = &overflowLines->mLines; michael@0: mLine = mLineList->begin(); michael@0: NS_ASSERTION(mLine != mLineList->end(), "empty overflow line list?"); michael@0: return true; michael@0: } michael@0: } michael@0: currentlyInOverflowLines = !currentlyInOverflowLines; michael@0: } michael@0: } michael@0: michael@0: static nsresult RemoveBlockChild(nsIFrame* aFrame, michael@0: bool aRemoveOnlyFluidContinuations) michael@0: { michael@0: if (!aFrame) michael@0: return NS_OK; michael@0: michael@0: nsBlockFrame* nextBlock = nsLayoutUtils::GetAsBlock(aFrame->GetParent()); michael@0: NS_ASSERTION(nextBlock, michael@0: "Our child's continuation's parent is not a block?"); michael@0: return nextBlock->DoRemoveFrame(aFrame, michael@0: (aRemoveOnlyFluidContinuations ? 0 : nsBlockFrame::REMOVE_FIXED_CONTINUATIONS)); michael@0: } michael@0: michael@0: // This function removes aDeletedFrame and all its continuations. It michael@0: // is optimized for deleting a whole series of frames. The easy michael@0: // implementation would invoke itself recursively on michael@0: // aDeletedFrame->GetNextContinuation, then locate the line containing michael@0: // aDeletedFrame and remove aDeletedFrame from that line. But here we michael@0: // start by locating aDeletedFrame and then scanning from that point michael@0: // on looking for continuations. michael@0: nsresult michael@0: nsBlockFrame::DoRemoveFrame(nsIFrame* aDeletedFrame, uint32_t aFlags) michael@0: { michael@0: // Clear our line cursor, since our lines may change. michael@0: ClearLineCursor(); michael@0: michael@0: if (aDeletedFrame->GetStateBits() & michael@0: (NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) { michael@0: if (!aDeletedFrame->GetPrevInFlow()) { michael@0: NS_ASSERTION(aDeletedFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW, michael@0: "Expected out-of-flow frame"); michael@0: DoRemoveOutOfFlowFrame(aDeletedFrame); michael@0: } michael@0: else { michael@0: nsContainerFrame::DeleteNextInFlowChild(aDeletedFrame, michael@0: (aFlags & FRAMES_ARE_EMPTY) != 0); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Find the line that contains deletedFrame michael@0: nsLineList::iterator line_start = mLines.begin(), michael@0: line_end = mLines.end(); michael@0: nsLineList::iterator line = line_start; michael@0: FrameLines* overflowLines = nullptr; michael@0: bool searchingOverflowList = false; michael@0: // Make sure we look in the overflow lines even if the normal line michael@0: // list is empty michael@0: TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, michael@0: &overflowLines); michael@0: while (line != line_end) { michael@0: if (line->Contains(aDeletedFrame)) { michael@0: break; michael@0: } michael@0: ++line; michael@0: TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, michael@0: &overflowLines); michael@0: } michael@0: michael@0: if (line == line_end) { michael@0: NS_ERROR("can't find deleted frame in lines"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!(aFlags & FRAMES_ARE_EMPTY)) { michael@0: if (line != line_start) { michael@0: line.prev()->MarkDirty(); michael@0: line.prev()->SetInvalidateTextRuns(true); michael@0: } michael@0: else if (searchingOverflowList && !mLines.empty()) { michael@0: mLines.back()->MarkDirty(); michael@0: mLines.back()->SetInvalidateTextRuns(true); michael@0: } michael@0: } michael@0: michael@0: while (line != line_end && aDeletedFrame) { michael@0: NS_ASSERTION(this == aDeletedFrame->GetParent(), "messed up delete code"); michael@0: NS_ASSERTION(line->Contains(aDeletedFrame), "frame not in line"); michael@0: michael@0: if (!(aFlags & FRAMES_ARE_EMPTY)) { michael@0: line->MarkDirty(); michael@0: line->SetInvalidateTextRuns(true); michael@0: } michael@0: michael@0: // If the frame being deleted is the last one on the line then michael@0: // optimize away the line->Contains(next-in-flow) call below. michael@0: bool isLastFrameOnLine = 1 == line->GetChildCount(); michael@0: if (!isLastFrameOnLine) { michael@0: line_iterator next = line.next(); michael@0: nsIFrame* lastFrame = next != line_end ? michael@0: next->mFirstChild->GetPrevSibling() : michael@0: (searchingOverflowList ? overflowLines->mFrames.LastChild() : michael@0: mFrames.LastChild()); michael@0: NS_ASSERTION(next == line_end || lastFrame == line->LastChild(), michael@0: "unexpected line frames"); michael@0: isLastFrameOnLine = lastFrame == aDeletedFrame; michael@0: } michael@0: michael@0: // Remove aDeletedFrame from the line michael@0: if (line->mFirstChild == aDeletedFrame) { michael@0: // We should be setting this to null if aDeletedFrame michael@0: // is the only frame on the line. HOWEVER in that case michael@0: // we will be removing the line anyway, see below. michael@0: line->mFirstChild = aDeletedFrame->GetNextSibling(); michael@0: } michael@0: michael@0: // Hmm, this won't do anything if we're removing a frame in the first michael@0: // overflow line... Hopefully doesn't matter michael@0: --line; michael@0: if (line != line_end && !line->IsBlock()) { michael@0: // Since we just removed a frame that follows some inline michael@0: // frames, we need to reflow the previous line. michael@0: line->MarkDirty(); michael@0: } michael@0: ++line; michael@0: michael@0: // Take aDeletedFrame out of the sibling list. Note that michael@0: // prevSibling will only be nullptr when we are deleting the very michael@0: // first frame in the main or overflow list. michael@0: if (searchingOverflowList) { michael@0: overflowLines->mFrames.RemoveFrame(aDeletedFrame); michael@0: } else { michael@0: mFrames.RemoveFrame(aDeletedFrame); michael@0: } michael@0: michael@0: // Update the child count of the line to be accurate michael@0: line->NoteFrameRemoved(aDeletedFrame); michael@0: michael@0: // Destroy frame; capture its next continuation first in case we need michael@0: // to destroy that too. michael@0: nsIFrame* deletedNextContinuation = (aFlags & REMOVE_FIXED_CONTINUATIONS) ? michael@0: aDeletedFrame->GetNextContinuation() : aDeletedFrame->GetNextInFlow(); michael@0: #ifdef NOISY_REMOVE_FRAME michael@0: printf("DoRemoveFrame: %s line=%p frame=", michael@0: searchingOverflowList?"overflow":"normal", line.get()); michael@0: nsFrame::ListTag(stdout, aDeletedFrame); michael@0: printf(" prevSibling=%p deletedNextContinuation=%p\n", michael@0: aDeletedFrame->GetPrevSibling(), deletedNextContinuation); michael@0: #endif michael@0: michael@0: // If next-in-flow is an overflow container, must remove it first. michael@0: if (deletedNextContinuation && michael@0: deletedNextContinuation->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) { michael@0: static_cast(deletedNextContinuation->GetParent()) michael@0: ->DeleteNextInFlowChild(deletedNextContinuation, false); michael@0: deletedNextContinuation = nullptr; michael@0: } michael@0: michael@0: aDeletedFrame->Destroy(); michael@0: aDeletedFrame = deletedNextContinuation; michael@0: michael@0: bool haveAdvancedToNextLine = false; michael@0: // If line is empty, remove it now. michael@0: if (0 == line->GetChildCount()) { michael@0: #ifdef NOISY_REMOVE_FRAME michael@0: printf("DoRemoveFrame: %s line=%p became empty so it will be removed\n", michael@0: searchingOverflowList?"overflow":"normal", line.get()); michael@0: #endif michael@0: nsLineBox *cur = line; michael@0: if (!searchingOverflowList) { michael@0: line = mLines.erase(line); michael@0: // Invalidate the space taken up by the line. michael@0: // XXX We need to do this if we're removing a frame as a result of michael@0: // a call to RemoveFrame(), but we may not need to do this in all michael@0: // cases... michael@0: #ifdef NOISY_BLOCK_INVALIDATE michael@0: nsRect visOverflow(cur->GetVisualOverflowArea()); michael@0: printf("%p invalidate 10 (%d, %d, %d, %d)\n", michael@0: this, visOverflow.x, visOverflow.y, michael@0: visOverflow.width, visOverflow.height); michael@0: #endif michael@0: } else { michael@0: line = overflowLines->mLines.erase(line); michael@0: if (overflowLines->mLines.empty()) { michael@0: DestroyOverflowLines(); michael@0: overflowLines = nullptr; michael@0: // We just invalidated our iterators. Since we were in michael@0: // the overflow lines list, which is now empty, set them michael@0: // so we're at the end of the regular line list. michael@0: line_start = mLines.begin(); michael@0: line_end = mLines.end(); michael@0: line = line_end; michael@0: } michael@0: } michael@0: FreeLineBox(cur); michael@0: michael@0: // If we're removing a line, ReflowDirtyLines isn't going to michael@0: // know that it needs to slide lines unless something is marked michael@0: // dirty. So mark the previous margin of the next line dirty if michael@0: // there is one. michael@0: if (line != line_end) { michael@0: line->MarkPreviousMarginDirty(); michael@0: } michael@0: haveAdvancedToNextLine = true; michael@0: } else { michael@0: // Make the line that just lost a frame dirty, and advance to michael@0: // the next line. michael@0: if (!deletedNextContinuation || isLastFrameOnLine || michael@0: !line->Contains(deletedNextContinuation)) { michael@0: line->MarkDirty(); michael@0: ++line; michael@0: haveAdvancedToNextLine = true; michael@0: } michael@0: } michael@0: michael@0: if (deletedNextContinuation) { michael@0: // See if we should keep looking in the current flow's line list. michael@0: if (deletedNextContinuation->GetParent() != this) { michael@0: // The deceased frames continuation is not a child of the michael@0: // current block. So break out of the loop so that we advance michael@0: // to the next parent. michael@0: // michael@0: // If we have a continuation in a different block then all bets are michael@0: // off regarding whether we are deleting frames without actual content, michael@0: // so don't propagate FRAMES_ARE_EMPTY any further. michael@0: aFlags &= ~FRAMES_ARE_EMPTY; michael@0: break; michael@0: } michael@0: michael@0: // If we advanced to the next line then check if we should switch to the michael@0: // overflow line list. michael@0: if (haveAdvancedToNextLine) { michael@0: if (line != line_end && !searchingOverflowList && michael@0: !line->Contains(deletedNextContinuation)) { michael@0: // We have advanced to the next *normal* line but the next-in-flow michael@0: // is not there - force a switch to the overflow line list. michael@0: line = line_end; michael@0: } michael@0: michael@0: TryAllLines(&line, &line_start, &line_end, &searchingOverflowList, michael@0: &overflowLines); michael@0: #ifdef NOISY_REMOVE_FRAME michael@0: printf("DoRemoveFrame: now on %s line=%p\n", michael@0: searchingOverflowList?"overflow":"normal", line.get()); michael@0: #endif michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!(aFlags & FRAMES_ARE_EMPTY) && line.next() != line_end) { michael@0: line.next()->MarkDirty(); michael@0: line.next()->SetInvalidateTextRuns(true); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: VerifyLines(true); michael@0: VerifyOverflowSituation(); michael@0: #endif michael@0: michael@0: // Advance to next flow block if the frame has more continuations michael@0: return RemoveBlockChild(aDeletedFrame, !(aFlags & REMOVE_FIXED_CONTINUATIONS)); michael@0: } michael@0: michael@0: static bool michael@0: FindBlockLineFor(nsIFrame* aChild, michael@0: nsLineList::iterator aBegin, michael@0: nsLineList::iterator aEnd, michael@0: nsLineList::iterator* aResult) michael@0: { michael@0: MOZ_ASSERT(aChild->IsBlockOutside()); michael@0: for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { michael@0: MOZ_ASSERT(line->GetChildCount() > 0); michael@0: if (line->IsBlock() && line->mFirstChild == aChild) { michael@0: MOZ_ASSERT(line->GetChildCount() == 1); michael@0: *aResult = line; michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: FindInlineLineFor(nsIFrame* aChild, michael@0: const nsFrameList& aFrameList, michael@0: nsLineList::iterator aBegin, michael@0: nsLineList::iterator aEnd, michael@0: nsLineList::iterator* aResult) michael@0: { michael@0: MOZ_ASSERT(!aChild->IsBlockOutside()); michael@0: for (nsLineList::iterator line = aBegin; line != aEnd; ++line) { michael@0: MOZ_ASSERT(line->GetChildCount() > 0); michael@0: if (!line->IsBlock()) { michael@0: // Optimize by comparing the line's last child first. michael@0: nsLineList::iterator next = line.next(); michael@0: if (aChild == (next == aEnd ? aFrameList.LastChild() michael@0: : next->mFirstChild->GetPrevSibling()) || michael@0: line->Contains(aChild)) { michael@0: *aResult = line; michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: static bool michael@0: FindLineFor(nsIFrame* aChild, michael@0: const nsFrameList& aFrameList, michael@0: nsLineList::iterator aBegin, michael@0: nsLineList::iterator aEnd, michael@0: nsLineList::iterator* aResult) michael@0: { michael@0: return aChild->IsBlockOutside() ? michael@0: FindBlockLineFor(aChild, aBegin, aEnd, aResult) : michael@0: FindInlineLineFor(aChild, aFrameList, aBegin, aEnd, aResult); michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::StealFrame(nsIFrame* aChild, michael@0: bool aForceNormal) michael@0: { michael@0: MOZ_ASSERT(aChild->GetParent() == this); michael@0: michael@0: if ((aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && michael@0: aChild->IsFloating()) { michael@0: RemoveFloat(aChild); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if ((aChild->GetStateBits() & NS_FRAME_IS_OVERFLOW_CONTAINER) michael@0: && !aForceNormal) { michael@0: return nsContainerFrame::StealFrame(aChild); michael@0: } michael@0: michael@0: MOZ_ASSERT(!(aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW)); michael@0: michael@0: nsLineList::iterator line; michael@0: if (FindLineFor(aChild, mFrames, mLines.begin(), mLines.end(), &line)) { michael@0: RemoveFrameFromLine(aChild, line, mFrames, mLines); michael@0: } else { michael@0: FrameLines* overflowLines = GetOverflowLines(); michael@0: DebugOnly found; michael@0: found = FindLineFor(aChild, overflowLines->mFrames, michael@0: overflowLines->mLines.begin(), michael@0: overflowLines->mLines.end(), &line); michael@0: MOZ_ASSERT(found); michael@0: RemoveFrameFromLine(aChild, line, overflowLines->mFrames, michael@0: overflowLines->mLines); michael@0: if (overflowLines->mLines.empty()) { michael@0: DestroyOverflowLines(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::RemoveFrameFromLine(nsIFrame* aChild, nsLineList::iterator aLine, michael@0: nsFrameList& aFrameList, nsLineList& aLineList) michael@0: { michael@0: aFrameList.RemoveFrame(aChild); michael@0: if (aChild == aLine->mFirstChild) { michael@0: aLine->mFirstChild = aChild->GetNextSibling(); michael@0: } michael@0: aLine->NoteFrameRemoved(aChild); michael@0: if (aLine->GetChildCount() > 0) { michael@0: aLine->MarkDirty(); michael@0: } else { michael@0: // The line became empty - destroy it. michael@0: nsLineBox* lineBox = aLine; michael@0: aLine = aLineList.erase(aLine); michael@0: if (aLine != aLineList.end()) { michael@0: aLine->MarkPreviousMarginDirty(); michael@0: } michael@0: FreeLineBox(lineBox); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::DeleteNextInFlowChild(nsIFrame* aNextInFlow, michael@0: bool aDeletingEmptyFrames) michael@0: { michael@0: NS_PRECONDITION(aNextInFlow->GetPrevInFlow(), "bad next-in-flow"); michael@0: michael@0: if (aNextInFlow->GetStateBits() & michael@0: (NS_FRAME_OUT_OF_FLOW | NS_FRAME_IS_OVERFLOW_CONTAINER)) { michael@0: nsContainerFrame::DeleteNextInFlowChild(aNextInFlow, aDeletingEmptyFrames); michael@0: } michael@0: else { michael@0: #ifdef DEBUG michael@0: if (aDeletingEmptyFrames) { michael@0: nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(aNextInFlow); michael@0: } michael@0: #endif michael@0: DoRemoveFrame(aNextInFlow, michael@0: aDeletingEmptyFrames ? FRAMES_ARE_EMPTY : 0); michael@0: } michael@0: } michael@0: michael@0: const nsStyleText* michael@0: nsBlockFrame::StyleTextForLineLayout() michael@0: { michael@0: // Return the pointer to an unmodified style text michael@0: return StyleText(); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////// michael@0: // Float support michael@0: michael@0: nsRect michael@0: nsBlockFrame::AdjustFloatAvailableSpace(nsBlockReflowState& aState, michael@0: const nsRect& aFloatAvailableSpace, michael@0: nsIFrame* aFloatFrame) michael@0: { michael@0: // Compute the available width. By default, assume the width of the michael@0: // containing block. michael@0: nscoord availWidth; michael@0: const nsStyleDisplay* floatDisplay = aFloatFrame->StyleDisplay(); michael@0: michael@0: if (NS_STYLE_DISPLAY_TABLE != floatDisplay->mDisplay || michael@0: eCompatibility_NavQuirks != aState.mPresContext->CompatibilityMode() ) { michael@0: availWidth = aState.mContentArea.width; michael@0: } michael@0: else { michael@0: // This quirk matches the one in nsBlockReflowState::FlowAndPlaceFloat michael@0: // give tables only the available space michael@0: // if they can shrink we may not be constrained to place michael@0: // them in the next line michael@0: availWidth = aFloatAvailableSpace.width; michael@0: } michael@0: michael@0: nscoord availHeight = NS_UNCONSTRAINEDSIZE == aState.mContentArea.height michael@0: ? NS_UNCONSTRAINEDSIZE michael@0: : std::max(0, aState.mContentArea.YMost() - aState.mY); michael@0: michael@0: #ifdef DISABLE_FLOAT_BREAKING_IN_COLUMNS michael@0: if (availHeight != NS_UNCONSTRAINEDSIZE && michael@0: nsLayoutUtils::GetClosestFrameOfType(this, nsGkAtoms::columnSetFrame)) { michael@0: // Tell the float it has unrestricted height, so it won't break. michael@0: // If the float doesn't actually fit in the column it will fail to be michael@0: // placed, and either move to the top of the next column or just michael@0: // overflow. michael@0: availHeight = NS_UNCONSTRAINEDSIZE; michael@0: } michael@0: #endif michael@0: michael@0: return nsRect(aState.mContentArea.x, michael@0: aState.mContentArea.y, michael@0: availWidth, availHeight); michael@0: } michael@0: michael@0: nscoord michael@0: nsBlockFrame::ComputeFloatWidth(nsBlockReflowState& aState, michael@0: const nsRect& aFloatAvailableSpace, michael@0: nsIFrame* aFloat) michael@0: { michael@0: NS_PRECONDITION(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW, michael@0: "aFloat must be an out-of-flow frame"); michael@0: // Reflow the float. michael@0: nsRect availSpace = AdjustFloatAvailableSpace(aState, aFloatAvailableSpace, michael@0: aFloat); michael@0: michael@0: nsHTMLReflowState floatRS(aState.mPresContext, aState.mReflowState, aFloat, michael@0: availSpace.Size()); michael@0: return floatRS.ComputedWidth() + floatRS.ComputedPhysicalBorderPadding().LeftRight() + michael@0: floatRS.ComputedPhysicalMargin().LeftRight(); michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::ReflowFloat(nsBlockReflowState& aState, michael@0: const nsRect& aAdjustedAvailableSpace, michael@0: nsIFrame* aFloat, michael@0: nsMargin& aFloatMargin, michael@0: nsMargin& aFloatOffsets, michael@0: bool aFloatPushedDown, michael@0: nsReflowStatus& aReflowStatus) michael@0: { michael@0: NS_PRECONDITION(aFloat->GetStateBits() & NS_FRAME_OUT_OF_FLOW, michael@0: "aFloat must be an out-of-flow frame"); michael@0: // Reflow the float. michael@0: aReflowStatus = NS_FRAME_COMPLETE; michael@0: michael@0: #ifdef NOISY_FLOAT michael@0: printf("Reflow Float %p in parent %p, availSpace(%d,%d,%d,%d)\n", michael@0: aFloat, this, michael@0: aFloatAvailableSpace.x, aFloatAvailableSpace.y, michael@0: aFloatAvailableSpace.width, aFloatAvailableSpace.height michael@0: ); michael@0: #endif michael@0: michael@0: nsHTMLReflowState floatRS(aState.mPresContext, aState.mReflowState, aFloat, michael@0: nsSize(aAdjustedAvailableSpace.width, michael@0: aAdjustedAvailableSpace.height)); michael@0: michael@0: // Normally the mIsTopOfPage state is copied from the parent reflow michael@0: // state. However, when reflowing a float, if we've placed other michael@0: // floats that force this float *down* or *narrower*, we should unset michael@0: // the mIsTopOfPage state. michael@0: // FIXME: This is somewhat redundant with the |isAdjacentWithTop| michael@0: // variable below, which has the exact same effect. Perhaps it should michael@0: // be merged into that, except that the test for narrowing here is not michael@0: // about adjacency with the top, so it seems misleading. michael@0: if (floatRS.mFlags.mIsTopOfPage && michael@0: (aFloatPushedDown || michael@0: aAdjustedAvailableSpace.width != aState.mContentArea.width)) { michael@0: floatRS.mFlags.mIsTopOfPage = false; michael@0: } michael@0: michael@0: // Setup a block reflow context to reflow the float. michael@0: nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState); michael@0: michael@0: // Reflow the float michael@0: bool isAdjacentWithTop = aState.IsAdjacentWithTop(); michael@0: michael@0: nsIFrame* clearanceFrame = nullptr; michael@0: nsresult rv; michael@0: do { michael@0: nsCollapsingMargin margin; michael@0: bool mayNeedRetry = false; michael@0: floatRS.mDiscoveredClearance = nullptr; michael@0: // Only first in flow gets a top margin. michael@0: if (!aFloat->GetPrevInFlow()) { michael@0: nsBlockReflowContext::ComputeCollapsedTopMargin(floatRS, &margin, michael@0: clearanceFrame, &mayNeedRetry); michael@0: michael@0: if (mayNeedRetry && !clearanceFrame) { michael@0: floatRS.mDiscoveredClearance = &clearanceFrame; michael@0: // We don't need to push the float manager state because the the block has its own michael@0: // float manager that will be destroyed and recreated michael@0: } michael@0: } michael@0: michael@0: rv = brc.ReflowBlock(aAdjustedAvailableSpace, true, margin, michael@0: 0, isAdjacentWithTop, michael@0: nullptr, floatRS, michael@0: aReflowStatus, aState); michael@0: } while (NS_SUCCEEDED(rv) && clearanceFrame); michael@0: michael@0: if (!NS_FRAME_IS_FULLY_COMPLETE(aReflowStatus) && michael@0: ShouldAvoidBreakInside(floatRS)) { michael@0: aReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: } else if (NS_FRAME_IS_NOT_COMPLETE(aReflowStatus) && michael@0: (NS_UNCONSTRAINEDSIZE == aAdjustedAvailableSpace.height)) { michael@0: // An incomplete reflow status means we should split the float michael@0: // if the height is constrained (bug 145305). michael@0: aReflowStatus = NS_FRAME_COMPLETE; michael@0: } michael@0: michael@0: if (aReflowStatus & NS_FRAME_REFLOW_NEXTINFLOW) { michael@0: aState.mReflowStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: } michael@0: michael@0: if (aFloat->GetType() == nsGkAtoms::letterFrame) { michael@0: // We never split floating first letters; an incomplete state for michael@0: // such frames simply means that there is more content to be michael@0: // reflowed on the line. michael@0: if (NS_FRAME_IS_NOT_COMPLETE(aReflowStatus)) michael@0: aReflowStatus = NS_FRAME_COMPLETE; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Capture the margin and offsets information for the caller michael@0: aFloatMargin = floatRS.ComputedPhysicalMargin(); // float margins don't collapse michael@0: aFloatOffsets = floatRS.ComputedPhysicalOffsets(); michael@0: michael@0: const nsHTMLReflowMetrics& metrics = brc.GetMetrics(); michael@0: michael@0: // Set the rect, make sure the view is properly sized and positioned, michael@0: // and tell the frame we're done reflowing it michael@0: // XXXldb This seems like the wrong place to be doing this -- shouldn't michael@0: // we be doing this in nsBlockReflowState::FlowAndPlaceFloat after michael@0: // we've positioned the float, and shouldn't we be doing the equivalent michael@0: // of |PlaceFrameView| here? michael@0: aFloat->SetSize(nsSize(metrics.Width(), metrics.Height())); michael@0: if (aFloat->HasView()) { michael@0: nsContainerFrame::SyncFrameViewAfterReflow(aState.mPresContext, aFloat, michael@0: aFloat->GetView(), michael@0: metrics.VisualOverflow(), michael@0: NS_FRAME_NO_MOVE_VIEW); michael@0: } michael@0: // Pass floatRS so the frame hierarchy can be used (redoFloatRS has the same hierarchy) michael@0: aFloat->DidReflow(aState.mPresContext, &floatRS, michael@0: nsDidReflowStatus::FINISHED); michael@0: michael@0: #ifdef NOISY_FLOAT michael@0: printf("end ReflowFloat %p, sized to %d,%d\n", michael@0: aFloat, metrics.Width(), metrics.Height()); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: uint8_t michael@0: nsBlockFrame::FindTrailingClear() michael@0: { michael@0: // find the break type of the last line michael@0: for (nsIFrame* b = this; b; b = b->GetPrevInFlow()) { michael@0: nsBlockFrame* block = static_cast(b); michael@0: line_iterator endLine = block->end_lines(); michael@0: if (endLine != block->begin_lines()) { michael@0: --endLine; michael@0: return endLine->GetBreakTypeAfter(); michael@0: } michael@0: } michael@0: return NS_STYLE_CLEAR_NONE; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::ReflowPushedFloats(nsBlockReflowState& aState, michael@0: nsOverflowAreas& aOverflowAreas, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: // Pushed floats live at the start of our float list; see comment michael@0: // above nsBlockFrame::DrainPushedFloats. michael@0: for (nsIFrame* f = mFloats.FirstChild(), *next; michael@0: f && (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT); michael@0: f = next) { michael@0: // save next sibling now, since reflowing could push the entire michael@0: // float, changing its siblings michael@0: next = f->GetNextSibling(); michael@0: michael@0: // When we push a first-continuation float in a non-initial reflow, michael@0: // it's possible that we end up with two continuations with the same michael@0: // parent. This happens if, on the previous reflow of the block or michael@0: // a previous reflow of the line containing the block, the float was michael@0: // split between continuations A and B of the parent, but on the michael@0: // current reflow, none of the float can fit in A. michael@0: // michael@0: // When this happens, we might even have the two continuations michael@0: // out-of-order due to the management of the pushed floats. In michael@0: // particular, if the float's placeholder was in a pushed line that michael@0: // we reflowed before it was pushed, and we split the float during michael@0: // that reflow, we might have the continuation of the float before michael@0: // the float itself. (In the general case, however, it's correct michael@0: // for floats in the pushed floats list to come before floats michael@0: // anchored in pushed lines; however, in this case it's wrong. We michael@0: // should probably find a way to fix it somehow, since it leads to michael@0: // incorrect layout in some cases.) michael@0: // michael@0: // When we have these out-of-order continuations, we might hit the michael@0: // next-continuation before the previous-continuation. When that michael@0: // happens, just push it. When we reflow the next continuation, michael@0: // we'll either pull all of its content back and destroy it (by michael@0: // calling DeleteNextInFlowChild), or nsBlockFrame::SplitFloat will michael@0: // pull it out of its current position and push it again (and michael@0: // potentially repeat this cycle for the next continuation, although michael@0: // hopefully then they'll be in the right order). michael@0: // michael@0: // We should also need this code for the in-order case if the first michael@0: // continuation of a float gets moved across more than one michael@0: // continuation of the containing block. In this case we'd manage michael@0: // to push the second continuation without this check, but not the michael@0: // third and later. michael@0: nsIFrame *prevContinuation = f->GetPrevContinuation(); michael@0: if (prevContinuation && prevContinuation->GetParent() == f->GetParent()) { michael@0: mFloats.RemoveFrame(f); michael@0: aState.AppendPushedFloat(f); michael@0: continue; michael@0: } michael@0: michael@0: // Always call FlowAndPlaceFloat; we might need to place this float michael@0: // if didn't belong to this block the last time it was reflowed. michael@0: aState.FlowAndPlaceFloat(f); michael@0: michael@0: ConsiderChildOverflow(aOverflowAreas, f); michael@0: } michael@0: michael@0: // If there are continued floats, then we may need to continue BR clearance michael@0: if (0 != aState.ClearFloats(0, NS_STYLE_CLEAR_BOTH)) { michael@0: aState.mFloatBreakType = static_cast(GetPrevInFlow()) michael@0: ->FindTrailingClear(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::RecoverFloats(nsFloatManager& aFloatManager) michael@0: { michael@0: // Recover our own floats michael@0: nsIFrame* stop = nullptr; // Stop before we reach pushed floats that michael@0: // belong to our next-in-flow michael@0: for (nsIFrame* f = mFloats.FirstChild(); f && f != stop; f = f->GetNextSibling()) { michael@0: nsRect region = nsFloatManager::GetRegionFor(f); michael@0: aFloatManager.AddFloat(f, region); michael@0: if (!stop && f->GetNextInFlow()) michael@0: stop = f->GetNextInFlow(); michael@0: } michael@0: michael@0: // Recurse into our overflow container children michael@0: for (nsIFrame* oc = GetFirstChild(kOverflowContainersList); michael@0: oc; oc = oc->GetNextSibling()) { michael@0: RecoverFloatsFor(oc, aFloatManager); michael@0: } michael@0: michael@0: // Recurse into our normal children michael@0: for (nsBlockFrame::line_iterator line = begin_lines(); line != end_lines(); ++line) { michael@0: if (line->IsBlock()) { michael@0: RecoverFloatsFor(line->mFirstChild, aFloatManager); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::RecoverFloatsFor(nsIFrame* aFrame, michael@0: nsFloatManager& aFloatManager) michael@0: { michael@0: NS_PRECONDITION(aFrame, "null frame"); michael@0: // Only blocks have floats michael@0: nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aFrame); michael@0: // Don't recover any state inside a block that has its own space manager michael@0: // (we don't currently have any blocks like this, though, thanks to our michael@0: // use of extra frames for 'overflow') michael@0: if (block && !nsBlockFrame::BlockNeedsFloatManager(block)) { michael@0: // If the element is relatively positioned, then adjust x and y michael@0: // accordingly so that we consider relatively positioned frames michael@0: // at their original position. michael@0: nsPoint pos = block->GetNormalPosition(); michael@0: aFloatManager.Translate(pos.x, pos.y); michael@0: block->RecoverFloats(aFloatManager); michael@0: aFloatManager.Translate(-pos.x, -pos.y); michael@0: } michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////// michael@0: // Painting, event handling michael@0: michael@0: #ifdef DEBUG michael@0: static void ComputeVisualOverflowArea(nsLineList& aLines, michael@0: nscoord aWidth, nscoord aHeight, michael@0: nsRect& aResult) michael@0: { michael@0: nscoord xa = 0, ya = 0, xb = aWidth, yb = aHeight; michael@0: for (nsLineList::iterator line = aLines.begin(), line_end = aLines.end(); michael@0: line != line_end; michael@0: ++line) { michael@0: // Compute min and max x/y values for the reflowed frame's michael@0: // combined areas michael@0: nsRect visOverflow(line->GetVisualOverflowArea()); michael@0: nscoord x = visOverflow.x; michael@0: nscoord y = visOverflow.y; michael@0: nscoord xmost = x + visOverflow.width; michael@0: nscoord ymost = y + visOverflow.height; michael@0: if (x < xa) { michael@0: xa = x; michael@0: } michael@0: if (xmost > xb) { michael@0: xb = xmost; michael@0: } michael@0: if (y < ya) { michael@0: ya = y; michael@0: } michael@0: if (ymost > yb) { michael@0: yb = ymost; michael@0: } michael@0: } michael@0: michael@0: aResult.x = xa; michael@0: aResult.y = ya; michael@0: aResult.width = xb - xa; michael@0: aResult.height = yb - ya; michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: nsBlockFrame::IsVisibleInSelection(nsISelection* aSelection) michael@0: { michael@0: if (mContent->IsHTML() && (mContent->Tag() == nsGkAtoms::html || michael@0: mContent->Tag() == nsGkAtoms::body)) michael@0: return true; michael@0: michael@0: nsCOMPtr node(do_QueryInterface(mContent)); michael@0: bool visible; michael@0: nsresult rv = aSelection->ContainsNode(node, true, &visible); michael@0: return NS_SUCCEEDED(rv) && visible; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static void DebugOutputDrawLine(int32_t aDepth, nsLineBox* aLine, bool aDrawn) { michael@0: if (nsBlockFrame::gNoisyDamageRepair) { michael@0: nsFrame::IndentBy(stdout, aDepth+1); michael@0: nsRect lineArea = aLine->GetVisualOverflowArea(); michael@0: printf("%s line=%p bounds=%d,%d,%d,%d ca=%d,%d,%d,%d\n", michael@0: aDrawn ? "draw" : "skip", michael@0: static_cast(aLine), michael@0: aLine->IStart(), aLine->BStart(), michael@0: aLine->ISize(), aLine->BSize(), michael@0: lineArea.x, lineArea.y, michael@0: lineArea.width, lineArea.height); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: static void michael@0: DisplayLine(nsDisplayListBuilder* aBuilder, const nsRect& aLineArea, michael@0: const nsRect& aDirtyRect, nsBlockFrame::line_iterator& aLine, michael@0: int32_t aDepth, int32_t& aDrawnLines, const nsDisplayListSet& aLists, michael@0: nsBlockFrame* aFrame, TextOverflow* aTextOverflow) { michael@0: // If the line's combined area (which includes child frames that michael@0: // stick outside of the line's bounding box or our bounding box) michael@0: // intersects the dirty rect then paint the line. michael@0: bool intersect = aLineArea.Intersects(aDirtyRect); michael@0: #ifdef DEBUG michael@0: if (nsBlockFrame::gLamePaintMetrics) { michael@0: aDrawnLines++; michael@0: } michael@0: DebugOutputDrawLine(aDepth, aLine.get(), intersect); michael@0: #endif michael@0: // The line might contain a placeholder for a visible out-of-flow, in which michael@0: // case we need to descend into it. If there is such a placeholder, we will michael@0: // have NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO set. michael@0: // In particular, we really want to check ShouldDescendIntoFrame() michael@0: // on all the frames on the line, but that might be expensive. So michael@0: // we approximate it by checking it on aFrame; if it's true for any michael@0: // frame in the line, it's also true for aFrame. michael@0: bool lineInline = aLine->IsInline(); michael@0: bool lineMayHaveTextOverflow = aTextOverflow && lineInline; michael@0: if (!intersect && !aBuilder->ShouldDescendIntoFrame(aFrame) && michael@0: !lineMayHaveTextOverflow) michael@0: return; michael@0: michael@0: // Collect our line's display items in a temporary nsDisplayListCollection, michael@0: // so that we can apply any "text-overflow" clipping to the entire collection michael@0: // without affecting previous lines. michael@0: nsDisplayListCollection collection; michael@0: michael@0: // Block-level child backgrounds go on the blockBorderBackgrounds list ... michael@0: // Inline-level child backgrounds go on the regular child content list. michael@0: nsDisplayListSet childLists(collection, michael@0: lineInline ? collection.Content() : collection.BlockBorderBackgrounds()); michael@0: michael@0: uint32_t flags = lineInline ? nsIFrame::DISPLAY_CHILD_INLINE : 0; michael@0: michael@0: nsIFrame* kid = aLine->mFirstChild; michael@0: int32_t n = aLine->GetChildCount(); michael@0: while (--n >= 0) { michael@0: aFrame->BuildDisplayListForChild(aBuilder, kid, aDirtyRect, michael@0: childLists, flags); michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: michael@0: if (lineMayHaveTextOverflow) { michael@0: aTextOverflow->ProcessLine(collection, aLine.get()); michael@0: } michael@0: michael@0: collection.MoveTo(aLists); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: int32_t drawnLines; // Will only be used if set (gLamePaintMetrics). michael@0: int32_t depth = 0; michael@0: #ifdef DEBUG michael@0: if (gNoisyDamageRepair) { michael@0: depth = GetDepth(); michael@0: nsRect ca; michael@0: ::ComputeVisualOverflowArea(mLines, mRect.width, mRect.height, ca); michael@0: nsFrame::IndentBy(stdout, depth); michael@0: ListTag(stdout); michael@0: printf(": bounds=%d,%d,%d,%d dirty(absolute)=%d,%d,%d,%d ca=%d,%d,%d,%d\n", michael@0: mRect.x, mRect.y, mRect.width, mRect.height, michael@0: aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height, michael@0: ca.x, ca.y, ca.width, ca.height); michael@0: } michael@0: PRTime start = 0; // Initialize these variables to silence the compiler. michael@0: if (gLamePaintMetrics) { michael@0: start = PR_Now(); michael@0: drawnLines = 0; michael@0: } michael@0: #endif michael@0: michael@0: DisplayBorderBackgroundOutline(aBuilder, aLists); michael@0: michael@0: if (GetPrevInFlow()) { michael@0: DisplayOverflowContainers(aBuilder, aDirtyRect, aLists); michael@0: for (nsIFrame* f = mFloats.FirstChild(); f; f = f->GetNextSibling()) { michael@0: if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT) michael@0: BuildDisplayListForChild(aBuilder, f, aDirtyRect, aLists); michael@0: } michael@0: } michael@0: michael@0: aBuilder->MarkFramesForDisplayList(this, mFloats, aDirtyRect); michael@0: michael@0: // Prepare for text-overflow processing. michael@0: nsAutoPtr textOverflow( michael@0: TextOverflow::WillProcessLines(aBuilder, this)); michael@0: michael@0: // We'll collect our lines' display items here, & then append this to aLists. michael@0: nsDisplayListCollection linesDisplayListCollection; michael@0: michael@0: // Don't use the line cursor if we might have a descendant placeholder ... michael@0: // it might skip lines that contain placeholders but don't themselves michael@0: // intersect with the dirty area. michael@0: // In particular, we really want to check ShouldDescendIntoFrame() michael@0: // on all our child frames, but that might be expensive. So we michael@0: // approximate it by checking it on |this|; if it's true for any michael@0: // frame in our child list, it's also true for |this|. michael@0: nsLineBox* cursor = aBuilder->ShouldDescendIntoFrame(this) ? michael@0: nullptr : GetFirstLineContaining(aDirtyRect.y); michael@0: line_iterator line_end = end_lines(); michael@0: michael@0: if (cursor) { michael@0: for (line_iterator line = mLines.begin(cursor); michael@0: line != line_end; michael@0: ++line) { michael@0: nsRect lineArea = line->GetVisualOverflowArea(); michael@0: if (!lineArea.IsEmpty()) { michael@0: // Because we have a cursor, the combinedArea.ys are non-decreasing. michael@0: // Once we've passed aDirtyRect.YMost(), we can never see it again. michael@0: if (lineArea.y >= aDirtyRect.YMost()) { michael@0: break; michael@0: } michael@0: DisplayLine(aBuilder, lineArea, aDirtyRect, line, depth, drawnLines, michael@0: linesDisplayListCollection, this, textOverflow); michael@0: } michael@0: } michael@0: } else { michael@0: bool nonDecreasingYs = true; michael@0: int32_t lineCount = 0; michael@0: nscoord lastY = INT32_MIN; michael@0: nscoord lastYMost = INT32_MIN; michael@0: for (line_iterator line = begin_lines(); michael@0: line != line_end; michael@0: ++line) { michael@0: nsRect lineArea = line->GetVisualOverflowArea(); michael@0: DisplayLine(aBuilder, lineArea, aDirtyRect, line, depth, drawnLines, michael@0: linesDisplayListCollection, this, textOverflow); michael@0: if (!lineArea.IsEmpty()) { michael@0: if (lineArea.y < lastY michael@0: || lineArea.YMost() < lastYMost) { michael@0: nonDecreasingYs = false; michael@0: } michael@0: lastY = lineArea.y; michael@0: lastYMost = lineArea.YMost(); michael@0: } michael@0: lineCount++; michael@0: } michael@0: michael@0: if (nonDecreasingYs && lineCount >= MIN_LINES_NEEDING_CURSOR) { michael@0: SetupLineCursor(); michael@0: } michael@0: } michael@0: michael@0: // Pick up the resulting text-overflow markers. We append them to michael@0: // PositionedDescendants just before we append the lines' display items, michael@0: // so that our text-overflow markers will appear on top of this block's michael@0: // normal content but below any of its its' positioned children. michael@0: if (textOverflow) { michael@0: aLists.PositionedDescendants()->AppendToTop(&textOverflow->GetMarkers()); michael@0: } michael@0: linesDisplayListCollection.MoveTo(aLists); michael@0: michael@0: if (HasOutsideBullet()) { michael@0: // Display outside bullets manually michael@0: nsIFrame* bullet = GetOutsideBullet(); michael@0: BuildDisplayListForChild(aBuilder, bullet, aDirtyRect, aLists); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: if (gLamePaintMetrics) { michael@0: PRTime end = PR_Now(); michael@0: michael@0: int32_t numLines = mLines.size(); michael@0: if (!numLines) numLines = 1; michael@0: PRTime lines, deltaPerLine, delta; michael@0: lines = int64_t(numLines); michael@0: delta = end - start; michael@0: deltaPerLine = delta / lines; michael@0: michael@0: ListTag(stdout); michael@0: char buf[400]; michael@0: PR_snprintf(buf, sizeof(buf), michael@0: ": %lld elapsed (%lld per line) lines=%d drawn=%d skip=%d", michael@0: delta, deltaPerLine, michael@0: numLines, drawnLines, numLines - drawnLines); michael@0: printf("%s\n", buf); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsBlockFrame::AccessibleType() michael@0: { michael@0: // block frame may be for


    michael@0: if (mContent->Tag() == nsGkAtoms::hr) { michael@0: return a11y::eHTMLHRType; michael@0: } michael@0: michael@0: if (!HasBullet() || !PresContext()) { michael@0: if (!mContent->GetParent()) { michael@0: // Don't create accessible objects for the root content node, they are redundant with michael@0: // the nsDocAccessible object created with the document node michael@0: return a11y::eNoType; michael@0: } michael@0: michael@0: nsCOMPtr htmlDoc = michael@0: do_QueryInterface(mContent->GetDocument()); michael@0: if (htmlDoc) { michael@0: nsCOMPtr body; michael@0: htmlDoc->GetBody(getter_AddRefs(body)); michael@0: if (SameCOMIdentity(body, mContent)) { michael@0: // Don't create accessible objects for the body, they are redundant with michael@0: // the nsDocAccessible object created with the document node michael@0: return a11y::eNoType; michael@0: } michael@0: } michael@0: michael@0: // Not a bullet, treat as normal HTML container michael@0: return a11y::eHyperTextType; michael@0: } michael@0: michael@0: // Create special list bullet accessible michael@0: return a11y::eHTMLLiType; michael@0: } michael@0: #endif michael@0: michael@0: void nsBlockFrame::ClearLineCursor() michael@0: { michael@0: if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) { michael@0: return; michael@0: } michael@0: michael@0: Properties().Delete(LineCursorProperty()); michael@0: RemoveStateBits(NS_BLOCK_HAS_LINE_CURSOR); michael@0: } michael@0: michael@0: void nsBlockFrame::SetupLineCursor() michael@0: { michael@0: if (GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR michael@0: || mLines.empty()) { michael@0: return; michael@0: } michael@0: michael@0: Properties().Set(LineCursorProperty(), mLines.front()); michael@0: AddStateBits(NS_BLOCK_HAS_LINE_CURSOR); michael@0: } michael@0: michael@0: nsLineBox* nsBlockFrame::GetFirstLineContaining(nscoord y) michael@0: { michael@0: if (!(GetStateBits() & NS_BLOCK_HAS_LINE_CURSOR)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: FrameProperties props = Properties(); michael@0: michael@0: nsLineBox* property = static_cast michael@0: (props.Get(LineCursorProperty())); michael@0: line_iterator cursor = mLines.begin(property); michael@0: nsRect cursorArea = cursor->GetVisualOverflowArea(); michael@0: michael@0: while ((cursorArea.IsEmpty() || cursorArea.YMost() > y) michael@0: && cursor != mLines.front()) { michael@0: cursor = cursor.prev(); michael@0: cursorArea = cursor->GetVisualOverflowArea(); michael@0: } michael@0: while ((cursorArea.IsEmpty() || cursorArea.YMost() <= y) michael@0: && cursor != mLines.back()) { michael@0: cursor = cursor.next(); michael@0: cursorArea = cursor->GetVisualOverflowArea(); michael@0: } michael@0: michael@0: if (cursor.get() != property) { michael@0: props.Set(LineCursorProperty(), cursor.get()); michael@0: } michael@0: michael@0: return cursor.get(); michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsBlockFrame::ChildIsDirty(nsIFrame* aChild) michael@0: { michael@0: // See if the child is absolutely positioned michael@0: if (aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW && michael@0: aChild->IsAbsolutelyPositioned()) { michael@0: // do nothing michael@0: } else if (aChild == GetOutsideBullet()) { michael@0: // The bullet lives in the first line, unless the first line has michael@0: // height 0 and there is a second line, in which case it lives michael@0: // in the second line. michael@0: line_iterator bulletLine = begin_lines(); michael@0: if (bulletLine != end_lines() && bulletLine->BSize() == 0 && michael@0: bulletLine != mLines.back()) { michael@0: bulletLine = bulletLine.next(); michael@0: } michael@0: michael@0: if (bulletLine != end_lines()) { michael@0: MarkLineDirty(bulletLine, &mLines); michael@0: } michael@0: // otherwise we have an empty line list, and ReflowDirtyLines michael@0: // will handle reflowing the bullet. michael@0: } else { michael@0: // Note that we should go through our children to mark lines dirty michael@0: // before the next reflow. Doing it now could make things O(N^2) michael@0: // since finding the right line is O(N). michael@0: // We don't need to worry about marking lines on the overflow list michael@0: // as dirty; we're guaranteed to reflow them if we take them off the michael@0: // overflow list. michael@0: // However, we might have gotten a float, in which case we need to michael@0: // reflow the line containing its placeholder. So find the michael@0: // ancestor-or-self of the placeholder that's a child of the block, michael@0: // and mark it as NS_FRAME_HAS_DIRTY_CHILDREN too, so that we mark michael@0: // its line dirty when we handle NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. michael@0: // We need to take some care to handle the case where a float is in michael@0: // a different continuation than its placeholder, including marking michael@0: // an extra block with NS_BLOCK_LOOK_FOR_DIRTY_FRAMES. michael@0: if (!(aChild->GetStateBits() & NS_FRAME_OUT_OF_FLOW)) { michael@0: AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); michael@0: } else { michael@0: NS_ASSERTION(aChild->IsFloating(), "should be a float"); michael@0: nsIFrame *thisFC = FirstContinuation(); michael@0: nsIFrame *placeholderPath = michael@0: PresContext()->FrameManager()->GetPlaceholderFrameFor(aChild); michael@0: // SVG code sometimes sends FrameNeedsReflow notifications during michael@0: // frame destruction, leading to null placeholders, but we're safe michael@0: // ignoring those. michael@0: if (placeholderPath) { michael@0: for (;;) { michael@0: nsIFrame *parent = placeholderPath->GetParent(); michael@0: if (parent->GetContent() == mContent && michael@0: parent->FirstContinuation() == thisFC) { michael@0: parent->AddStateBits(NS_BLOCK_LOOK_FOR_DIRTY_FRAMES); michael@0: break; michael@0: } michael@0: placeholderPath = parent; michael@0: } michael@0: placeholderPath->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsBlockFrameSuper::ChildIsDirty(aChild); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: if (aPrevInFlow) { michael@0: // Copy over the inherited block frame bits from the prev-in-flow. michael@0: SetFlags(aPrevInFlow->GetStateBits() & michael@0: (NS_BLOCK_FLAGS_MASK & ~NS_BLOCK_FLAGS_NON_INHERITED_MASK)); michael@0: } michael@0: michael@0: nsBlockFrameSuper::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: if (!aPrevInFlow || michael@0: aPrevInFlow->GetStateBits() & NS_BLOCK_NEEDS_BIDI_RESOLUTION) michael@0: AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); michael@0: michael@0: if ((GetStateBits() & michael@0: (NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) == michael@0: (NS_FRAME_FONT_INFLATION_CONTAINER | NS_BLOCK_FLOAT_MGR)) { michael@0: AddStateBits(NS_FRAME_FONT_INFLATION_FLOW_ROOT); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::SetInitialChildList(ChildListID aListID, michael@0: nsFrameList& aChildList) michael@0: { michael@0: NS_ASSERTION(aListID != kPrincipalList || michael@0: (GetStateBits() & (NS_BLOCK_FRAME_HAS_INSIDE_BULLET | michael@0: NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET)) == 0, michael@0: "how can we have a bullet already?"); michael@0: michael@0: if (kAbsoluteList == aListID) { michael@0: nsContainerFrame::SetInitialChildList(aListID, aChildList); michael@0: } michael@0: else if (kFloatList == aListID) { michael@0: mFloats.SetFrames(aChildList); michael@0: } michael@0: else { michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: #ifdef DEBUG michael@0: // The only times a block that is an anonymous box is allowed to have a michael@0: // first-letter frame are when it's the block inside a non-anonymous cell, michael@0: // the block inside a fieldset, a scrolled content block, or a column michael@0: // content block. Note that this means that blocks which are the anonymous michael@0: // block in {ib} splits do NOT get first-letter frames. Note that michael@0: // NS_BLOCK_HAS_FIRST_LETTER_STYLE gets set on all continuations of the michael@0: // block. michael@0: nsIAtom *pseudo = StyleContext()->GetPseudo(); michael@0: bool haveFirstLetterStyle = michael@0: (!pseudo || michael@0: (pseudo == nsCSSAnonBoxes::cellContent && michael@0: mParent->StyleContext()->GetPseudo() == nullptr) || michael@0: pseudo == nsCSSAnonBoxes::fieldsetContent || michael@0: pseudo == nsCSSAnonBoxes::scrolledContent || michael@0: pseudo == nsCSSAnonBoxes::columnContent || michael@0: pseudo == nsCSSAnonBoxes::mozSVGText) && michael@0: !IsFrameOfType(eMathML) && michael@0: nsRefPtr(GetFirstLetterStyle(presContext)) != nullptr; michael@0: NS_ASSERTION(haveFirstLetterStyle == michael@0: ((mState & NS_BLOCK_HAS_FIRST_LETTER_STYLE) != 0), michael@0: "NS_BLOCK_HAS_FIRST_LETTER_STYLE state out of sync"); michael@0: #endif michael@0: michael@0: AddFrames(aChildList, nullptr); michael@0: michael@0: // Create a list bullet if this is a list-item. Note that this is michael@0: // done here so that RenumberLists will work (it needs the bullets michael@0: // to store the bullet numbers). Also note that due to various michael@0: // wrapper frames (scrollframes, columns) we want to use the michael@0: // outermost (primary, ideally, but it's not set yet when we get michael@0: // here) frame of our content for the display check. On the other michael@0: // hand, we look at ourselves for the GetPrevInFlow() check, since michael@0: // for a columnset we don't want a bullet per column. Note that michael@0: // the outermost frame for the content is the primary frame in michael@0: // most cases; the ones when it's not (like tables) can't be michael@0: // NS_STYLE_DISPLAY_LIST_ITEM). michael@0: nsIFrame* possibleListItem = this; michael@0: while (1) { michael@0: nsIFrame* parent = possibleListItem->GetParent(); michael@0: if (parent->GetContent() != GetContent()) { michael@0: break; michael@0: } michael@0: possibleListItem = parent; michael@0: } michael@0: if (NS_STYLE_DISPLAY_LIST_ITEM == michael@0: possibleListItem->StyleDisplay()->mDisplay && michael@0: !GetPrevInFlow()) { michael@0: // Resolve style for the bullet frame michael@0: const nsStyleList* styleList = StyleList(); michael@0: nsCSSPseudoElements::Type pseudoType; michael@0: switch (styleList->mListStyleType) { michael@0: case NS_STYLE_LIST_STYLE_DISC: michael@0: case NS_STYLE_LIST_STYLE_CIRCLE: michael@0: case NS_STYLE_LIST_STYLE_SQUARE: michael@0: pseudoType = nsCSSPseudoElements::ePseudo_mozListBullet; michael@0: break; michael@0: default: michael@0: pseudoType = nsCSSPseudoElements::ePseudo_mozListNumber; michael@0: break; michael@0: } michael@0: michael@0: nsIPresShell *shell = presContext->PresShell(); michael@0: michael@0: nsStyleContext* parentStyle = michael@0: CorrectStyleParentFrame(this, michael@0: nsCSSPseudoElements::GetPseudoAtom(pseudoType))->StyleContext(); michael@0: nsRefPtr kidSC = shell->StyleSet()-> michael@0: ResolvePseudoElementStyle(mContent->AsElement(), pseudoType, michael@0: parentStyle, nullptr); michael@0: michael@0: // Create bullet frame michael@0: nsBulletFrame* bullet = new (shell) nsBulletFrame(kidSC); michael@0: bullet->Init(mContent, this, nullptr); michael@0: michael@0: // If the list bullet frame should be positioned inside then add michael@0: // it to the flow now. michael@0: if (NS_STYLE_LIST_STYLE_POSITION_INSIDE == michael@0: styleList->mListStylePosition) { michael@0: nsFrameList bulletList(bullet, bullet); michael@0: AddFrames(bulletList, nullptr); michael@0: Properties().Set(InsideBulletProperty(), bullet); michael@0: AddStateBits(NS_BLOCK_FRAME_HAS_INSIDE_BULLET); michael@0: } else { michael@0: nsFrameList* bulletList = new (shell) nsFrameList(bullet, bullet); michael@0: Properties().Set(OutsideBulletProperty(), bulletList); michael@0: AddStateBits(NS_BLOCK_FRAME_HAS_OUTSIDE_BULLET); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::BulletIsEmpty() const michael@0: { michael@0: NS_ASSERTION(mContent->GetPrimaryFrame()->StyleDisplay()->mDisplay == michael@0: NS_STYLE_DISPLAY_LIST_ITEM && HasOutsideBullet(), michael@0: "should only care when we have an outside bullet"); michael@0: const nsStyleList* list = StyleList(); michael@0: return list->mListStyleType == NS_STYLE_LIST_STYLE_NONE && michael@0: !list->GetListStyleImage(); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::GetBulletText(nsAString& aText) const michael@0: { michael@0: aText.Truncate(); michael@0: michael@0: const nsStyleList* myList = StyleList(); michael@0: if (myList->GetListStyleImage() || michael@0: myList->mListStyleType == NS_STYLE_LIST_STYLE_DISC) { michael@0: aText.Assign(kDiscCharacter); michael@0: } michael@0: else if (myList->mListStyleType == NS_STYLE_LIST_STYLE_CIRCLE) { michael@0: aText.Assign(kCircleCharacter); michael@0: } michael@0: else if (myList->mListStyleType == NS_STYLE_LIST_STYLE_SQUARE) { michael@0: aText.Assign(kSquareCharacter); michael@0: } michael@0: else if (myList->mListStyleType != NS_STYLE_LIST_STYLE_NONE) { michael@0: nsBulletFrame* bullet = GetBullet(); michael@0: if (bullet) { michael@0: nsAutoString text; michael@0: bullet->GetListItemText(*myList, text); michael@0: aText = text; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsBlockFrame::FrameStartsCounterScope(nsIFrame* aFrame) michael@0: { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (!content || !content->IsHTML()) michael@0: return false; michael@0: michael@0: nsIAtom *localName = content->NodeInfo()->NameAtom(); michael@0: return localName == nsGkAtoms::ol || michael@0: localName == nsGkAtoms::ul || michael@0: localName == nsGkAtoms::dir || michael@0: localName == nsGkAtoms::menu; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::RenumberLists(nsPresContext* aPresContext) michael@0: { michael@0: if (!FrameStartsCounterScope(this)) { michael@0: // If this frame doesn't start a counter scope then we don't need michael@0: // to renumber child list items. michael@0: return false; michael@0: } michael@0: michael@0: MOZ_ASSERT(mContent->IsHTML(), michael@0: "FrameStartsCounterScope should only return true for HTML elements"); michael@0: michael@0: // Setup initial list ordinal value michael@0: // XXX Map html's start property to counter-reset style michael@0: int32_t ordinal = 1; michael@0: int32_t increment; michael@0: if (mContent->Tag() == nsGkAtoms::ol && michael@0: mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::reversed)) { michael@0: increment = -1; michael@0: } else { michael@0: increment = 1; michael@0: } michael@0: michael@0: nsGenericHTMLElement *hc = nsGenericHTMLElement::FromContent(mContent); michael@0: // Must be non-null, since FrameStartsCounterScope only returns true michael@0: // for HTML elements. michael@0: MOZ_ASSERT(hc, "How is mContent not HTML?"); michael@0: const nsAttrValue* attr = hc->GetParsedAttr(nsGkAtoms::start); michael@0: if (attr && attr->Type() == nsAttrValue::eInteger) { michael@0: ordinal = attr->GetIntegerValue(); michael@0: } else if (increment < 0) { michael@0: //
      case, or some other case with a negative increment: count michael@0: // up the child list michael@0: ordinal = 0; michael@0: for (nsIContent* kid = mContent->GetFirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: if (kid->IsHTML(nsGkAtoms::li)) { michael@0: // FIXME: This isn't right in terms of what CSS says to do for michael@0: // overflow of counters (but it only matters when this node has michael@0: // more than numeric_limits::max() children). michael@0: ordinal -= increment; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Get to first-in-flow michael@0: nsBlockFrame* block = static_cast(FirstInFlow()); michael@0: return RenumberListsInBlock(aPresContext, block, &ordinal, 0, increment); michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::RenumberListsInBlock(nsPresContext* aPresContext, michael@0: nsBlockFrame* aBlockFrame, michael@0: int32_t* aOrdinal, michael@0: int32_t aDepth, michael@0: int32_t aIncrement) michael@0: { michael@0: // Examine each line in the block michael@0: bool foundValidLine; michael@0: nsBlockInFlowLineIterator bifLineIter(aBlockFrame, &foundValidLine); michael@0: michael@0: if (!foundValidLine) michael@0: return false; michael@0: michael@0: bool renumberedABullet = false; michael@0: michael@0: do { michael@0: nsLineList::iterator line = bifLineIter.GetLine(); michael@0: nsIFrame* kid = line->mFirstChild; michael@0: int32_t n = line->GetChildCount(); michael@0: while (--n >= 0) { michael@0: bool kidRenumberedABullet = RenumberListsFor(aPresContext, kid, aOrdinal, michael@0: aDepth, aIncrement); michael@0: if (kidRenumberedABullet) { michael@0: line->MarkDirty(); michael@0: renumberedABullet = true; michael@0: } michael@0: kid = kid->GetNextSibling(); michael@0: } michael@0: } while (bifLineIter.Next()); michael@0: michael@0: // We need to set NS_FRAME_HAS_DIRTY_CHILDREN bits up the tree between michael@0: // the bullet and the caller of RenumberLists. But the caller itself michael@0: // has to be responsible for setting the bit itself, since that caller michael@0: // might be making a FrameNeedsReflow call, which requires that the michael@0: // bit not be set yet. michael@0: if (renumberedABullet && aDepth != 0) { michael@0: aBlockFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); michael@0: } michael@0: michael@0: return renumberedABullet; michael@0: } michael@0: michael@0: bool michael@0: nsBlockFrame::RenumberListsFor(nsPresContext* aPresContext, michael@0: nsIFrame* aKid, michael@0: int32_t* aOrdinal, michael@0: int32_t aDepth, michael@0: int32_t aIncrement) michael@0: { michael@0: NS_PRECONDITION(aPresContext && aKid && aOrdinal, "null params are immoral!"); michael@0: michael@0: // add in a sanity check for absurdly deep frame trees. See bug 42138 michael@0: if (MAX_DEPTH_FOR_LIST_RENUMBERING < aDepth) michael@0: return false; michael@0: michael@0: // if the frame is a placeholder, then get the out of flow frame michael@0: nsIFrame* kid = nsPlaceholderFrame::GetRealFrameFor(aKid); michael@0: const nsStyleDisplay* display = kid->StyleDisplay(); michael@0: michael@0: // drill down through any wrappers to the real frame michael@0: kid = kid->GetContentInsertionFrame(); michael@0: michael@0: // possible there is no content insertion frame michael@0: if (!kid) michael@0: return false; michael@0: michael@0: bool kidRenumberedABullet = false; michael@0: michael@0: // If the frame is a list-item and the frame implements our michael@0: // block frame API then get its bullet and set the list item michael@0: // ordinal. michael@0: if (NS_STYLE_DISPLAY_LIST_ITEM == display->mDisplay) { michael@0: // Make certain that the frame is a block frame in case michael@0: // something foreign has crept in. michael@0: nsBlockFrame* listItem = nsLayoutUtils::GetAsBlock(kid); michael@0: if (listItem) { michael@0: nsBulletFrame* bullet = listItem->GetBullet(); michael@0: if (bullet) { michael@0: bool changed; michael@0: *aOrdinal = bullet->SetListItemOrdinal(*aOrdinal, &changed, aIncrement); michael@0: if (changed) { michael@0: kidRenumberedABullet = true; michael@0: michael@0: // The ordinal changed - mark the bullet frame, and any michael@0: // intermediate frames between it and the block (are there michael@0: // ever any?), dirty. michael@0: // The calling code will make the necessary FrameNeedsReflow michael@0: // call for the list ancestor. michael@0: bullet->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: nsIFrame *f = bullet; michael@0: do { michael@0: nsIFrame *parent = f->GetParent(); michael@0: parent->ChildIsDirty(f); michael@0: f = parent; michael@0: } while (f != listItem); michael@0: } michael@0: } michael@0: michael@0: // XXX temporary? if the list-item has child list-items they michael@0: // should be numbered too; especially since the list-item is michael@0: // itself (ASSUMED!) not to be a counter-resetter. michael@0: bool meToo = RenumberListsInBlock(aPresContext, listItem, aOrdinal, michael@0: aDepth + 1, aIncrement); michael@0: if (meToo) { michael@0: kidRenumberedABullet = true; michael@0: } michael@0: } michael@0: } michael@0: else if (NS_STYLE_DISPLAY_BLOCK == display->mDisplay) { michael@0: if (FrameStartsCounterScope(kid)) { michael@0: // Don't bother recursing into a block frame that is a new michael@0: // counter scope. Any list-items in there will be handled by michael@0: // it. michael@0: } michael@0: else { michael@0: // If the display=block element is a block frame then go ahead michael@0: // and recurse into it, as it might have child list-items. michael@0: nsBlockFrame* kidBlock = nsLayoutUtils::GetAsBlock(kid); michael@0: if (kidBlock) { michael@0: kidRenumberedABullet = RenumberListsInBlock(aPresContext, kidBlock, michael@0: aOrdinal, aDepth + 1, michael@0: aIncrement); michael@0: } michael@0: } michael@0: } michael@0: return kidRenumberedABullet; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::ReflowBullet(nsIFrame* aBulletFrame, michael@0: nsBlockReflowState& aState, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: nscoord aLineTop) michael@0: { michael@0: const nsHTMLReflowState &rs = aState.mReflowState; michael@0: michael@0: // Reflow the bullet now michael@0: nsSize availSize; michael@0: // Make up a width since it doesn't really matter (XXX). michael@0: availSize.width = aState.mContentArea.width; michael@0: availSize.height = NS_UNCONSTRAINEDSIZE; michael@0: michael@0: // Get the reason right. michael@0: // XXXwaterson Should this look just like the logic in michael@0: // nsBlockReflowContext::ReflowBlock and nsLineLayout::ReflowFrame? michael@0: nsHTMLReflowState reflowState(aState.mPresContext, rs, michael@0: aBulletFrame, availSize); michael@0: nsReflowStatus status; michael@0: aBulletFrame->WillReflow(aState.mPresContext); michael@0: aBulletFrame->Reflow(aState.mPresContext, aMetrics, reflowState, status); michael@0: michael@0: // Get the float available space using our saved state from before we michael@0: // started reflowing the block, so that we ignore any floats inside michael@0: // the block. michael@0: // FIXME: aLineTop isn't actually set correctly by some callers, since michael@0: // they reposition the line. michael@0: nsRect floatAvailSpace = michael@0: aState.GetFloatAvailableSpaceWithState(aLineTop, michael@0: &aState.mFloatManagerStateBefore) michael@0: .mRect; michael@0: // FIXME (bug 25888): need to check the entire region that the first michael@0: // line overlaps, not just the top pixel. michael@0: michael@0: // Place the bullet now. We want to place the bullet relative to the michael@0: // border-box of the associated block (using the right/left margin of michael@0: // the bullet frame as separation). However, if a line box would be michael@0: // displaced by floats that are *outside* the associated block, we michael@0: // want to displace it by the same amount. That is, we act as though michael@0: // the edge of the floats is the content-edge of the block, and place michael@0: // the bullet at a position offset from there by the block's padding, michael@0: // the block's border, and the bullet frame's margin. michael@0: michael@0: // IStart from floatAvailSpace gives us the content/float start edge michael@0: // in the current writing mode. Then we subtract out the start michael@0: // border/padding and the bullet's width and margin to offset the position. michael@0: WritingMode wm = rs.GetWritingMode(); michael@0: nscoord containerWidth = floatAvailSpace.XMost(); michael@0: LogicalRect logicalFAS(wm, floatAvailSpace, containerWidth); michael@0: // Get the bullet's margin, converted to our writing mode so that we can michael@0: // combine it with other logical values here. michael@0: WritingMode bulletWM = reflowState.GetWritingMode(); michael@0: LogicalMargin bulletMargin = michael@0: reflowState.ComputedLogicalMargin().ConvertTo(wm, bulletWM); michael@0: nscoord iStart = logicalFAS.IStart(wm) - michael@0: rs.ComputedLogicalBorderPadding().IStart(wm) - michael@0: bulletMargin.IEnd(wm) - michael@0: aMetrics.ISize(); michael@0: michael@0: // Approximate the bullets position; vertical alignment will provide michael@0: // the final vertical location. We pass our writing-mode here, because michael@0: // it may be different from the bullet frame's mode. michael@0: nscoord bStart = logicalFAS.BStart(wm); michael@0: aBulletFrame->SetRect(wm, LogicalRect(wm, LogicalPoint(wm, iStart, bStart), michael@0: LogicalSize(wm, aMetrics.ISize(), michael@0: aMetrics.BSize())), michael@0: containerWidth); michael@0: aBulletFrame->DidReflow(aState.mPresContext, &aState.mReflowState, michael@0: nsDidReflowStatus::FINISHED); michael@0: } michael@0: michael@0: // This is used to scan frames for any float placeholders, add their michael@0: // floats to the list represented by aList, and remove the michael@0: // floats from whatever list they might be in. We don't search descendants michael@0: // that are float containing blocks. Floats that or not children of 'this' michael@0: // are ignored (they are not added to aList). michael@0: void michael@0: nsBlockFrame::DoCollectFloats(nsIFrame* aFrame, nsFrameList& aList, michael@0: bool aCollectSiblings) michael@0: { michael@0: while (aFrame) { michael@0: // Don't descend into float containing blocks. michael@0: if (!aFrame->IsFloatContainingBlock()) { michael@0: nsIFrame *outOfFlowFrame = michael@0: aFrame->GetType() == nsGkAtoms::placeholderFrame ? michael@0: nsLayoutUtils::GetFloatFromPlaceholder(aFrame) : nullptr; michael@0: if (outOfFlowFrame && outOfFlowFrame->GetParent() == this) { michael@0: RemoveFloat(outOfFlowFrame); michael@0: aList.AppendFrame(nullptr, outOfFlowFrame); michael@0: // FIXME: By not pulling floats whose parent is one of our michael@0: // later siblings, are we risking the pushed floats getting michael@0: // out-of-order? michael@0: // XXXmats nsInlineFrame's lazy reparenting depends on NOT doing that. michael@0: } michael@0: michael@0: DoCollectFloats(aFrame->GetFirstPrincipalChild(), aList, true); michael@0: DoCollectFloats(aFrame->GetFirstChild(kOverflowList), aList, true); michael@0: } michael@0: if (!aCollectSiblings) michael@0: break; michael@0: aFrame = aFrame->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::CheckFloats(nsBlockReflowState& aState) michael@0: { michael@0: #ifdef DEBUG michael@0: // If any line is still dirty, that must mean we're going to reflow this michael@0: // block again soon (e.g. because we bailed out after noticing that michael@0: // clearance was imposed), so don't worry if the floats are out of sync. michael@0: bool anyLineDirty = false; michael@0: michael@0: // Check that the float list is what we would have built michael@0: nsAutoTArray lineFloats; michael@0: for (line_iterator line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; ++line) { michael@0: if (line->HasFloats()) { michael@0: nsFloatCache* fc = line->GetFirstFloat(); michael@0: while (fc) { michael@0: lineFloats.AppendElement(fc->mFloat); michael@0: fc = fc->Next(); michael@0: } michael@0: } michael@0: if (line->IsDirty()) { michael@0: anyLineDirty = true; michael@0: } michael@0: } michael@0: michael@0: nsAutoTArray storedFloats; michael@0: bool equal = true; michael@0: uint32_t i = 0; michael@0: for (nsIFrame* f = mFloats.FirstChild(); f; f = f->GetNextSibling()) { michael@0: if (f->GetStateBits() & NS_FRAME_IS_PUSHED_FLOAT) michael@0: continue; michael@0: storedFloats.AppendElement(f); michael@0: if (i < lineFloats.Length() && lineFloats.ElementAt(i) != f) { michael@0: equal = false; michael@0: } michael@0: ++i; michael@0: } michael@0: michael@0: if ((!equal || lineFloats.Length() != storedFloats.Length()) && !anyLineDirty) { michael@0: NS_WARNING("nsBlockFrame::CheckFloats: Explicit float list is out of sync with float cache"); michael@0: #if defined(DEBUG_roc) michael@0: nsFrame::RootFrameList(PresContext(), stdout, 0); michael@0: for (i = 0; i < lineFloats.Length(); ++i) { michael@0: printf("Line float: %p\n", lineFloats.ElementAt(i)); michael@0: } michael@0: for (i = 0; i < storedFloats.Length(); ++i) { michael@0: printf("Stored float: %p\n", storedFloats.ElementAt(i)); michael@0: } michael@0: #endif michael@0: } michael@0: #endif michael@0: michael@0: const nsFrameList* oofs = GetOverflowOutOfFlows(); michael@0: if (oofs && oofs->NotEmpty()) { michael@0: // Floats that were pushed should be removed from our float michael@0: // manager. Otherwise the float manager's YMost or XMost might michael@0: // be larger than necessary, causing this block to get an michael@0: // incorrect desired height (or width). Some of these floats michael@0: // may not actually have been added to the float manager because michael@0: // they weren't reflowed before being pushed; that's OK, michael@0: // RemoveRegions will ignore them. It is safe to do this here michael@0: // because we know from here on the float manager will only be michael@0: // used for its XMost and YMost, not to place new floats and michael@0: // lines. michael@0: aState.mFloatManager->RemoveTrailingRegions(oofs->FirstChild()); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::IsMarginRoot(bool* aTopMarginRoot, bool* aBottomMarginRoot) michael@0: { michael@0: if (!(GetStateBits() & NS_BLOCK_MARGIN_ROOT)) { michael@0: nsIFrame* parent = GetParent(); michael@0: if (!parent || parent->IsFloatContainingBlock()) { michael@0: *aTopMarginRoot = false; michael@0: *aBottomMarginRoot = false; michael@0: return; michael@0: } michael@0: if (parent->GetType() == nsGkAtoms::columnSetFrame) { michael@0: *aTopMarginRoot = GetPrevInFlow() == nullptr; michael@0: *aBottomMarginRoot = GetNextInFlow() == nullptr; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: *aTopMarginRoot = true; michael@0: *aBottomMarginRoot = true; michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: nsBlockFrame::BlockNeedsFloatManager(nsIFrame* aBlock) michael@0: { michael@0: NS_PRECONDITION(aBlock, "Must have a frame"); michael@0: NS_ASSERTION(nsLayoutUtils::GetAsBlock(aBlock), "aBlock must be a block"); michael@0: michael@0: nsIFrame* parent = aBlock->GetParent(); michael@0: return (aBlock->GetStateBits() & NS_BLOCK_FLOAT_MGR) || michael@0: (parent && !parent->IsFloatContainingBlock()); michael@0: } michael@0: michael@0: /* static */ michael@0: bool michael@0: nsBlockFrame::BlockCanIntersectFloats(nsIFrame* aFrame) michael@0: { michael@0: return aFrame->IsFrameOfType(nsIFrame::eBlockFrame) && michael@0: !aFrame->IsFrameOfType(nsIFrame::eReplaced) && michael@0: !(aFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR); michael@0: } michael@0: michael@0: // Note that this width can vary based on the vertical position. michael@0: // However, the cases where it varies are the cases where the width fits michael@0: // in the available space given, which means that variation shouldn't michael@0: // matter. michael@0: /* static */ michael@0: nsBlockFrame::ReplacedElementWidthToClear michael@0: nsBlockFrame::WidthToClearPastFloats(nsBlockReflowState& aState, michael@0: const nsRect& aFloatAvailableSpace, michael@0: nsIFrame* aFrame) michael@0: { michael@0: nscoord leftOffset, rightOffset; michael@0: nsCSSOffsetState offsetState(aFrame, aState.mReflowState.rendContext, michael@0: aState.mContentArea.width); michael@0: michael@0: ReplacedElementWidthToClear result; michael@0: aState.ComputeReplacedBlockOffsetsForFloats(aFrame, aFloatAvailableSpace, michael@0: leftOffset, rightOffset); michael@0: nscoord availWidth = aState.mContentArea.width - leftOffset - rightOffset; michael@0: michael@0: // We actually don't want the min width here; see bug 427782; we only michael@0: // want to displace if the width won't compute to a value small enough michael@0: // to fit. michael@0: // All we really need here is the result of ComputeSize, and we michael@0: // could *almost* get that from an nsCSSOffsetState, except for the michael@0: // last argument. michael@0: nsSize availSpace(availWidth, NS_UNCONSTRAINEDSIZE); michael@0: nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState, michael@0: aFrame, availSpace); michael@0: result.borderBoxWidth = reflowState.ComputedWidth() + michael@0: reflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: // Use the margins from offsetState rather than reflowState so that michael@0: // they aren't reduced by ignoring margins in overconstrained cases. michael@0: result.marginLeft = offsetState.ComputedPhysicalMargin().left; michael@0: result.marginRight = offsetState.ComputedPhysicalMargin().right; michael@0: return result; michael@0: } michael@0: michael@0: /* static */ michael@0: nsBlockFrame* michael@0: nsBlockFrame::GetNearestAncestorBlock(nsIFrame* aCandidate) michael@0: { michael@0: nsBlockFrame* block = nullptr; michael@0: while(aCandidate) { michael@0: block = nsLayoutUtils::GetAsBlock(aCandidate); michael@0: if (block) { michael@0: // yay, candidate is a block! michael@0: return block; michael@0: } michael@0: // Not a block. Check its parent next. michael@0: aCandidate = aCandidate->GetParent(); michael@0: } michael@0: NS_NOTREACHED("Fell off frame tree looking for ancestor block!"); michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::ComputeFinalHeight(const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus* aStatus, michael@0: nscoord aContentHeight, michael@0: const nsMargin& aBorderPadding, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: nscoord aConsumed) michael@0: { michael@0: michael@0: // Figure out how much of the computed height should be michael@0: // applied to this frame. michael@0: nscoord computedHeightLeftOver = GetEffectiveComputedHeight(aReflowState, michael@0: aConsumed); michael@0: NS_ASSERTION(!( IS_TRUE_OVERFLOW_CONTAINER(this) michael@0: && computedHeightLeftOver ), michael@0: "overflow container must not have computedHeightLeftOver"); michael@0: michael@0: aMetrics.Height() = michael@0: NSCoordSaturatingAdd(NSCoordSaturatingAdd(aBorderPadding.top, michael@0: computedHeightLeftOver), michael@0: aBorderPadding.bottom); michael@0: michael@0: if (NS_FRAME_IS_NOT_COMPLETE(*aStatus) michael@0: && aMetrics.Height() < aReflowState.AvailableHeight()) { michael@0: // We ran out of height on this page but we're incomplete michael@0: // Set status to complete except for overflow michael@0: NS_FRAME_SET_OVERFLOW_INCOMPLETE(*aStatus); michael@0: } michael@0: michael@0: if (NS_FRAME_IS_COMPLETE(*aStatus)) { michael@0: if (computedHeightLeftOver > 0 && michael@0: NS_UNCONSTRAINEDSIZE != aReflowState.AvailableHeight() && michael@0: aMetrics.Height() > aReflowState.AvailableHeight()) { michael@0: if (ShouldAvoidBreakInside(aReflowState)) { michael@0: *aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: return; michael@0: } michael@0: // We don't fit and we consumed some of the computed height, michael@0: // so we should consume all the available height and then michael@0: // break. If our bottom border/padding straddles the break michael@0: // point, then this will increase our height and push the michael@0: // border/padding to the next page/column. michael@0: aMetrics.Height() = std::max(aReflowState.AvailableHeight(), michael@0: aContentHeight); michael@0: NS_FRAME_SET_INCOMPLETE(*aStatus); michael@0: if (!GetNextInFlow()) michael@0: *aStatus |= NS_FRAME_REFLOW_NEXTINFLOW; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsBlockFrame::ResolveBidi() michael@0: { michael@0: NS_ASSERTION(!GetPrevInFlow(), michael@0: "ResolveBidi called on non-first continuation"); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: if (!presContext->BidiEnabled()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return nsBidiPresUtils::Resolve(this); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: nsBlockFrame::VerifyLines(bool aFinalCheckOK) michael@0: { michael@0: if (!gVerifyLines) { michael@0: return; michael@0: } michael@0: if (mLines.empty()) { michael@0: return; michael@0: } michael@0: michael@0: nsLineBox* cursor = GetLineCursor(); michael@0: michael@0: // Add up the counts on each line. Also validate that IsFirstLine is michael@0: // set properly. michael@0: int32_t count = 0; michael@0: line_iterator line, line_end; michael@0: for (line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ++line) { michael@0: if (line == cursor) { michael@0: cursor = nullptr; michael@0: } michael@0: if (aFinalCheckOK) { michael@0: NS_ABORT_IF_FALSE(line->GetChildCount(), "empty line"); michael@0: if (line->IsBlock()) { michael@0: NS_ASSERTION(1 == line->GetChildCount(), "bad first line"); michael@0: } michael@0: } michael@0: count += line->GetChildCount(); michael@0: } michael@0: michael@0: // Then count the frames michael@0: int32_t frameCount = 0; michael@0: nsIFrame* frame = mLines.front()->mFirstChild; michael@0: while (frame) { michael@0: frameCount++; michael@0: frame = frame->GetNextSibling(); michael@0: } michael@0: NS_ASSERTION(count == frameCount, "bad line list"); michael@0: michael@0: // Next: test that each line has right number of frames on it michael@0: for (line = begin_lines(), line_end = end_lines(); michael@0: line != line_end; michael@0: ) { michael@0: count = line->GetChildCount(); michael@0: frame = line->mFirstChild; michael@0: while (--count >= 0) { michael@0: frame = frame->GetNextSibling(); michael@0: } michael@0: ++line; michael@0: if ((line != line_end) && (0 != line->GetChildCount())) { michael@0: NS_ASSERTION(frame == line->mFirstChild, "bad line list"); michael@0: } michael@0: } michael@0: michael@0: if (cursor) { michael@0: FrameLines* overflowLines = GetOverflowLines(); michael@0: if (overflowLines) { michael@0: line_iterator line = overflowLines->mLines.begin(); michael@0: line_iterator line_end = overflowLines->mLines.end(); michael@0: for (; line != line_end; ++line) { michael@0: if (line == cursor) { michael@0: cursor = nullptr; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: NS_ASSERTION(!cursor, "stale LineCursorProperty"); michael@0: } michael@0: michael@0: void michael@0: nsBlockFrame::VerifyOverflowSituation() michael@0: { michael@0: nsBlockFrame* flow = static_cast(FirstInFlow()); michael@0: while (flow) { michael@0: FrameLines* overflowLines = flow->GetOverflowLines(); michael@0: if (overflowLines) { michael@0: NS_ASSERTION(!overflowLines->mLines.empty(), michael@0: "should not be empty if present"); michael@0: NS_ASSERTION(overflowLines->mLines.front()->mFirstChild, michael@0: "bad overflow lines"); michael@0: NS_ASSERTION(overflowLines->mLines.front()->mFirstChild == michael@0: overflowLines->mFrames.FirstChild(), michael@0: "bad overflow frames / lines"); michael@0: } michael@0: nsLineBox* cursor = flow->GetLineCursor(); michael@0: if (cursor) { michael@0: line_iterator line = flow->begin_lines(); michael@0: line_iterator line_end = flow->end_lines(); michael@0: for (; line != line_end && line != cursor; ++line) michael@0: ; michael@0: if (line == line_end && overflowLines) { michael@0: line = overflowLines->mLines.begin(); michael@0: line_end = overflowLines->mLines.end(); michael@0: for (; line != line_end && line != cursor; ++line) michael@0: ; michael@0: } michael@0: MOZ_ASSERT(line != line_end, "stale LineCursorProperty"); michael@0: } michael@0: flow = static_cast(flow->GetNextInFlow()); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsBlockFrame::GetDepth() const michael@0: { michael@0: int32_t depth = 0; michael@0: nsIFrame* parent = mParent; michael@0: while (parent) { michael@0: parent = parent->GetParent(); michael@0: depth++; michael@0: } michael@0: return depth; michael@0: } michael@0: #endif