diff options
Diffstat (limited to 'layout/generic/ColumnSetWrapperFrame.cpp')
-rw-r--r-- | layout/generic/ColumnSetWrapperFrame.cpp | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/layout/generic/ColumnSetWrapperFrame.cpp b/layout/generic/ColumnSetWrapperFrame.cpp new file mode 100644 index 0000000000..0f142ef4f3 --- /dev/null +++ b/layout/generic/ColumnSetWrapperFrame.cpp @@ -0,0 +1,315 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "ColumnSetWrapperFrame.h" + +#include "mozilla/ColumnUtils.h" +#include "mozilla/PresShell.h" +#include "nsContentUtils.h" +#include "nsIFrame.h" +#include "nsIFrameInlines.h" + +using namespace mozilla; + +nsBlockFrame* NS_NewColumnSetWrapperFrame(PresShell* aPresShell, + ComputedStyle* aStyle, + nsFrameState aStateFlags) { + ColumnSetWrapperFrame* frame = new (aPresShell) + ColumnSetWrapperFrame(aStyle, aPresShell->GetPresContext()); + + // CSS Multi-column level 1 section 2: A multi-column container + // establishes a new block formatting context, as per CSS 2.1 section + // 9.4.1. + frame->AddStateBits(aStateFlags | NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS); + return frame; +} + +NS_IMPL_FRAMEARENA_HELPERS(ColumnSetWrapperFrame) + +NS_QUERYFRAME_HEAD(ColumnSetWrapperFrame) + NS_QUERYFRAME_ENTRY(ColumnSetWrapperFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) + +ColumnSetWrapperFrame::ColumnSetWrapperFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsBlockFrame(aStyle, aPresContext, kClassID) {} + +void ColumnSetWrapperFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) { + nsBlockFrame::Init(aContent, aParent, aPrevInFlow); + + // ColumnSetWrapperFrame doesn't need to call ResolveBidi(). + RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); +} + +nsContainerFrame* ColumnSetWrapperFrame::GetContentInsertionFrame() { + nsIFrame* columnSet = PrincipalChildList().OnlyChild(); + if (columnSet) { + // We have only one child, which means we don't have any column-span + // descendants. Thus we can safely return our only ColumnSet child's + // insertion frame as ours. + MOZ_ASSERT(columnSet->IsColumnSetFrame()); + return columnSet->GetContentInsertionFrame(); + } + + // We have column-span descendants. Return ourselves as the insertion + // frame to let nsCSSFrameConstructor::WipeContainingBlock() figure out + // what to do. + return this; +} + +void ColumnSetWrapperFrame::AppendDirectlyOwnedAnonBoxes( + nsTArray<OwnedAnonBox>& aResult) { + MOZ_ASSERT(!GetPrevContinuation(), + "Who set NS_FRAME_OWNS_ANON_BOXES on our continuations?"); + + // It's sufficient to append the first ColumnSet child, which is the first + // continuation of all the other ColumnSets. + // + // We don't need to append -moz-column-span-wrapper children because + // they're non-inheriting anon boxes, and they cannot have any directly + // owned anon boxes nor generate any native anonymous content themselves. + // Thus, no need to restyle them. AssertColumnSpanWrapperSubtreeIsSane() + // asserts all the conditions above which allow us to skip appending + // -moz-column-span-wrappers. + auto FindFirstChildInChildLists = [this]() -> nsIFrame* { + const ChildListID listIDs[] = {FrameChildListID::Principal, + FrameChildListID::Overflow}; + for (nsIFrame* frag = this; frag; frag = frag->GetNextInFlow()) { + for (ChildListID id : listIDs) { + const nsFrameList& list = frag->GetChildList(id); + if (nsIFrame* firstChild = list.FirstChild()) { + return firstChild; + } + } + } + return nullptr; + }; + + nsIFrame* columnSet = FindFirstChildInChildLists(); + MOZ_ASSERT(columnSet && columnSet->IsColumnSetFrame(), + "The first child should always be ColumnSet!"); + aResult.AppendElement(OwnedAnonBox(columnSet)); +} + +#ifdef DEBUG_FRAME_DUMP +nsresult ColumnSetWrapperFrame::GetFrameName(nsAString& aResult) const { + return MakeFrameName(u"ColumnSetWrapper"_ns, aResult); +} +#endif + +// Disallow any append, insert, or remove operations after building the +// column hierarchy since any change to the column hierarchy in the column +// sub-tree need to be re-created. +void ColumnSetWrapperFrame::AppendFrames(ChildListID aListID, + nsFrameList&& aFrameList) { +#ifdef DEBUG + MOZ_ASSERT(!mFinishedBuildingColumns, "Should only call once!"); + mFinishedBuildingColumns = true; +#endif + + nsBlockFrame::AppendFrames(aListID, std::move(aFrameList)); + +#ifdef DEBUG + nsIFrame* firstColumnSet = PrincipalChildList().FirstChild(); + for (nsIFrame* child : PrincipalChildList()) { + if (child->IsColumnSpan()) { + AssertColumnSpanWrapperSubtreeIsSane(child); + } else if (child != firstColumnSet) { + // All the other ColumnSets are the continuation of the first ColumnSet. + MOZ_ASSERT(child->IsColumnSetFrame() && child->GetPrevContinuation(), + "ColumnSet's prev-continuation is not set properly?"); + } + } +#endif +} + +void ColumnSetWrapperFrame::InsertFrames( + ChildListID aListID, nsIFrame* aPrevFrame, + const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { + MOZ_ASSERT_UNREACHABLE("Unsupported operation!"); + nsBlockFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, + std::move(aFrameList)); +} + +void ColumnSetWrapperFrame::RemoveFrame(DestroyContext& aContext, + ChildListID aListID, + nsIFrame* aOldFrame) { + MOZ_ASSERT_UNREACHABLE("Unsupported operation!"); + nsBlockFrame::RemoveFrame(aContext, aListID, aOldFrame); +} + +void ColumnSetWrapperFrame::MarkIntrinsicISizesDirty() { + nsBlockFrame::MarkIntrinsicISizesDirty(); + + // The parent's method adds NS_BLOCK_NEEDS_BIDI_RESOLUTION to all our + // continuations. Clear the bit because we don't want to call ResolveBidi(). + for (nsIFrame* f = FirstContinuation(); f; f = f->GetNextContinuation()) { + f->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); + } +} + +nscoord ColumnSetWrapperFrame::GetMinISize(gfxContext* aRenderingContext) { + nscoord iSize = 0; + DISPLAY_MIN_INLINE_SIZE(this, iSize); + + if (Maybe<nscoord> containISize = + ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE)) { + // If we're size-contained in inline axis and contain-intrinsic-inline-size + // is not 'none', then use that size. + if (*containISize != NS_UNCONSTRAINEDSIZE) { + return *containISize; + } + + // In the 'none' case, we determine our minimum intrinsic size purely from + // our column styling, as if we had no descendants. This should match what + // happens in nsColumnSetFrame::GetMinISize in an actual no-descendants + // scenario. + const nsStyleColumn* colStyle = StyleColumn(); + if (colStyle->mColumnWidth.IsLength()) { + // As available inline size reduces to zero, our number of columns reduces + // to one, so no column gaps contribute to our minimum intrinsic size. + // Also, column-width doesn't set a lower bound on our minimum intrinsic + // size, either. Just use 0 because we're size-contained. + iSize = 0; + } else { + MOZ_ASSERT(colStyle->mColumnCount != nsStyleColumn::kColumnCountAuto, + "column-count and column-width can't both be auto!"); + // As available inline size reduces to zero, we still have mColumnCount + // columns, so compute our minimum intrinsic size based on N zero-width + // columns, with specified gap size between them. + const nscoord colGap = + ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE); + iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount, colGap, 0); + } + } else { + for (nsIFrame* f : PrincipalChildList()) { + iSize = std::max(iSize, f->GetMinISize(aRenderingContext)); + } + } + + return iSize; +} + +nscoord ColumnSetWrapperFrame::GetPrefISize(gfxContext* aRenderingContext) { + nscoord iSize = 0; + DISPLAY_PREF_INLINE_SIZE(this, iSize); + + if (Maybe<nscoord> containISize = + ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE)) { + if (*containISize != NS_UNCONSTRAINEDSIZE) { + return *containISize; + } + + const nsStyleColumn* colStyle = StyleColumn(); + nscoord colISize; + if (colStyle->mColumnWidth.IsLength()) { + colISize = + ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength()); + } else { + MOZ_ASSERT(colStyle->mColumnCount != nsStyleColumn::kColumnCountAuto, + "column-count and column-width can't both be auto!"); + colISize = 0; + } + + // If column-count is auto, assume one column. + const uint32_t numColumns = + colStyle->mColumnCount == nsStyleColumn::kColumnCountAuto + ? 1 + : colStyle->mColumnCount; + const nscoord colGap = + ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE); + iSize = ColumnUtils::IntrinsicISize(numColumns, colGap, colISize); + } else { + for (nsIFrame* f : PrincipalChildList()) { + iSize = std::max(iSize, f->GetPrefISize(aRenderingContext)); + } + } + + return iSize; +} + +template <typename Iterator> +Maybe<nscoord> ColumnSetWrapperFrame::GetBaselineBOffset( + Iterator aStart, Iterator aEnd, WritingMode aWM, + BaselineSharingGroup aBaselineGroup, + BaselineExportContext aExportContext) const { + // Either forward iterator + first baseline, or reverse iterator + last + // baseline + MOZ_ASSERT((*aStart == PrincipalChildList().FirstChild() && + aBaselineGroup == BaselineSharingGroup::First) || + (*aStart == PrincipalChildList().LastChild() && + aBaselineGroup == BaselineSharingGroup::Last), + "Iterator direction must match baseline sharing group."); + if (StyleDisplay()->IsContainLayout()) { + return Nothing{}; + } + + // Start from start/end of principal child list, and use the first valid + // baseline. + for (auto itr = aStart; itr != aEnd; ++itr) { + const nsIFrame* kid = *itr; + auto kidBaseline = + kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext); + if (!kidBaseline) { + continue; + } + // Baseline is offset from the kid's rectangle, so find the offset to the + // kid's rectangle. + LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()), + kid->GetLogicalSize(aWM)}; + if (aBaselineGroup == BaselineSharingGroup::First) { + *kidBaseline += kidRect.BStart(aWM); + } else { + *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM)); + } + return kidBaseline; + } + return Nothing{}; +} + +Maybe<nscoord> ColumnSetWrapperFrame::GetNaturalBaselineBOffset( + WritingMode aWM, BaselineSharingGroup aBaselineGroup, + BaselineExportContext aExportContext) const { + if (aBaselineGroup == BaselineSharingGroup::First) { + return GetBaselineBOffset(PrincipalChildList().cbegin(), + PrincipalChildList().cend(), aWM, aBaselineGroup, + aExportContext); + } + return GetBaselineBOffset(PrincipalChildList().crbegin(), + PrincipalChildList().crend(), aWM, aBaselineGroup, + aExportContext); +} + +#ifdef DEBUG + +/* static */ +void ColumnSetWrapperFrame::AssertColumnSpanWrapperSubtreeIsSane( + const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame->IsColumnSpan(), "aFrame is not column-span?"); + + if (!nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aFrame)) + ->Style() + ->IsAnonBox()) { + // aFrame's style frame has "column-span: all". Traverse no further. + return; + } + + MOZ_ASSERT( + aFrame->Style()->GetPseudoType() == PseudoStyleType::columnSpanWrapper, + "aFrame should be ::-moz-column-span-wrapper"); + + MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES), + "::-moz-column-span-wrapper anonymous blocks cannot own " + "other types of anonymous blocks!"); + + for (const nsIFrame* child : aFrame->PrincipalChildList()) { + AssertColumnSpanWrapperSubtreeIsSane(child); + } +} + +#endif |