ColumnSetWrapperFrame.cpp (12460B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #include "ColumnSetWrapperFrame.h" 8 9 #include "mozilla/ColumnUtils.h" 10 #include "mozilla/PresShell.h" 11 #include "nsContentUtils.h" 12 #include "nsIFrame.h" 13 #include "nsIFrameInlines.h" 14 15 using namespace mozilla; 16 17 nsBlockFrame* NS_NewColumnSetWrapperFrame(PresShell* aPresShell, 18 ComputedStyle* aStyle, 19 nsFrameState aStateFlags) { 20 ColumnSetWrapperFrame* frame = new (aPresShell) 21 ColumnSetWrapperFrame(aStyle, aPresShell->GetPresContext()); 22 frame->AddStateBits(aStateFlags); 23 return frame; 24 } 25 26 NS_IMPL_FRAMEARENA_HELPERS(ColumnSetWrapperFrame) 27 28 NS_QUERYFRAME_HEAD(ColumnSetWrapperFrame) 29 NS_QUERYFRAME_ENTRY(ColumnSetWrapperFrame) 30 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame) 31 32 ColumnSetWrapperFrame::ColumnSetWrapperFrame(ComputedStyle* aStyle, 33 nsPresContext* aPresContext) 34 : nsBlockFrame(aStyle, aPresContext, kClassID) {} 35 36 void ColumnSetWrapperFrame::Init(nsIContent* aContent, 37 nsContainerFrame* aParent, 38 nsIFrame* aPrevInFlow) { 39 nsBlockFrame::Init(aContent, aParent, aPrevInFlow); 40 41 // ColumnSetWrapperFrame doesn't need to call ResolveBidi(). 42 RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); 43 } 44 45 nsContainerFrame* ColumnSetWrapperFrame::GetContentInsertionFrame() { 46 nsIFrame* columnSet = PrincipalChildList().OnlyChild(); 47 if (columnSet) { 48 // We have only one child, which means we don't have any column-span 49 // descendants. Thus we can safely return our only ColumnSet child's 50 // insertion frame as ours. 51 MOZ_ASSERT(columnSet->IsColumnSetFrame()); 52 return columnSet->GetContentInsertionFrame(); 53 } 54 55 // We have column-span descendants. Return ourselves as the insertion 56 // frame to let nsCSSFrameConstructor::WipeContainingBlock() figure out 57 // what to do. 58 return this; 59 } 60 61 void ColumnSetWrapperFrame::AppendDirectlyOwnedAnonBoxes( 62 nsTArray<OwnedAnonBox>& aResult) { 63 MOZ_ASSERT(!GetPrevContinuation(), 64 "Who set NS_FRAME_OWNS_ANON_BOXES on our continuations?"); 65 66 // It's sufficient to append the first ColumnSet child, which is the first 67 // continuation of all the other ColumnSets. 68 // 69 // We don't need to append -moz-column-span-wrapper children because 70 // they're non-inheriting anon boxes, and they cannot have any directly 71 // owned anon boxes nor generate any native anonymous content themselves. 72 // Thus, no need to restyle them. AssertColumnSpanWrapperSubtreeIsSane() 73 // asserts all the conditions above which allow us to skip appending 74 // -moz-column-span-wrappers. 75 auto FindFirstChildInChildLists = [this]() -> nsIFrame* { 76 const ChildListID listIDs[] = {FrameChildListID::Principal, 77 FrameChildListID::Overflow}; 78 for (nsIFrame* frag = this; frag; frag = frag->GetNextInFlow()) { 79 for (ChildListID id : listIDs) { 80 const nsFrameList& list = frag->GetChildList(id); 81 if (nsIFrame* firstChild = list.FirstChild()) { 82 return firstChild; 83 } 84 } 85 } 86 return nullptr; 87 }; 88 89 nsIFrame* columnSet = FindFirstChildInChildLists(); 90 MOZ_ASSERT(columnSet && columnSet->IsColumnSetFrame(), 91 "The first child should always be ColumnSet!"); 92 aResult.AppendElement(OwnedAnonBox(columnSet)); 93 } 94 95 #ifdef DEBUG_FRAME_DUMP 96 nsresult ColumnSetWrapperFrame::GetFrameName(nsAString& aResult) const { 97 return MakeFrameName(u"ColumnSetWrapper"_ns, aResult); 98 } 99 #endif 100 101 // Disallow any append, insert, or remove operations after building the 102 // column hierarchy since any change to the column hierarchy in the column 103 // sub-tree need to be re-created. 104 void ColumnSetWrapperFrame::AppendFrames(ChildListID aListID, 105 nsFrameList&& aFrameList) { 106 #ifdef DEBUG 107 MOZ_ASSERT(!mFinishedBuildingColumns, "Should only call once!"); 108 mFinishedBuildingColumns = true; 109 #endif 110 111 nsBlockFrame::AppendFrames(aListID, std::move(aFrameList)); 112 113 #ifdef DEBUG 114 nsIFrame* firstColumnSet = PrincipalChildList().FirstChild(); 115 for (nsIFrame* child : PrincipalChildList()) { 116 if (child->IsColumnSpan()) { 117 AssertColumnSpanWrapperSubtreeIsSane(child); 118 } else if (child != firstColumnSet) { 119 // All the other ColumnSets are the continuation of the first ColumnSet. 120 MOZ_ASSERT(child->IsColumnSetFrame() && child->GetPrevContinuation(), 121 "ColumnSet's prev-continuation is not set properly?"); 122 } 123 } 124 #endif 125 } 126 127 void ColumnSetWrapperFrame::InsertFrames( 128 ChildListID aListID, nsIFrame* aPrevFrame, 129 const nsLineList::iterator* aPrevFrameLine, nsFrameList&& aFrameList) { 130 MOZ_ASSERT_UNREACHABLE("Unsupported operation!"); 131 nsBlockFrame::InsertFrames(aListID, aPrevFrame, aPrevFrameLine, 132 std::move(aFrameList)); 133 } 134 135 void ColumnSetWrapperFrame::RemoveFrame(DestroyContext& aContext, 136 ChildListID aListID, 137 nsIFrame* aOldFrame) { 138 MOZ_ASSERT_UNREACHABLE("Unsupported operation!"); 139 nsBlockFrame::RemoveFrame(aContext, aListID, aOldFrame); 140 } 141 142 void ColumnSetWrapperFrame::MarkIntrinsicISizesDirty() { 143 nsBlockFrame::MarkIntrinsicISizesDirty(); 144 145 // The parent's method adds NS_BLOCK_NEEDS_BIDI_RESOLUTION to all our 146 // continuations. Clear the bit because we don't want to call ResolveBidi(). 147 for (nsIFrame* f = FirstContinuation(); f; f = f->GetNextContinuation()) { 148 f->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION); 149 } 150 } 151 152 nscoord ColumnSetWrapperFrame::IntrinsicISize(const IntrinsicSizeInput& aInput, 153 IntrinsicISizeType aType) { 154 return mCachedIntrinsics.GetOrSet(*this, aType, aInput, [&] { 155 return aType == IntrinsicISizeType::MinISize ? MinISize(aInput) 156 : PrefISize(aInput); 157 }); 158 } 159 160 nscoord ColumnSetWrapperFrame::MinISize(const IntrinsicSizeInput& aInput) { 161 nscoord iSize = 0; 162 163 if (Maybe<nscoord> containISize = 164 ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE)) { 165 // If we're size-contained in inline axis and contain-intrinsic-inline-size 166 // is not 'none', then use that size. 167 if (*containISize != NS_UNCONSTRAINEDSIZE) { 168 return *containISize; 169 } 170 171 // In the 'none' case, we determine our minimum intrinsic size purely from 172 // our column styling, as if we had no descendants. This should match what 173 // happens in nsColumnSetFrame::MinISize in an actual no-descendants 174 // scenario. 175 const nsStyleColumn* colStyle = StyleColumn(); 176 if (colStyle->mColumnWidth.IsLength()) { 177 // As available inline size reduces to zero, our number of columns reduces 178 // to one, so no column gaps contribute to our minimum intrinsic size. 179 // Also, column-width doesn't set a lower bound on our minimum intrinsic 180 // size, either. Just use 0 because we're size-contained. 181 iSize = 0; 182 } else { 183 MOZ_ASSERT(!colStyle->mColumnCount.IsAuto(), 184 "column-count and column-width can't both be auto!"); 185 // As available inline size reduces to zero, we still have mColumnCount 186 // columns, so compute our minimum intrinsic size based on N zero-width 187 // columns, with specified gap size between them. 188 const nscoord colGap = 189 ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE); 190 iSize = ColumnUtils::IntrinsicISize(colStyle->mColumnCount.AsInteger(), 191 colGap, 0); 192 } 193 } else { 194 for (nsIFrame* f : PrincipalChildList()) { 195 const IntrinsicSizeInput childInput(aInput, f->GetWritingMode(), 196 GetWritingMode()); 197 iSize = std::max(iSize, f->GetMinISize(childInput)); 198 } 199 } 200 201 return iSize; 202 } 203 204 nscoord ColumnSetWrapperFrame::PrefISize(const IntrinsicSizeInput& aInput) { 205 nscoord iSize = 0; 206 207 if (Maybe<nscoord> containISize = 208 ContainIntrinsicISize(NS_UNCONSTRAINEDSIZE)) { 209 if (*containISize != NS_UNCONSTRAINEDSIZE) { 210 return *containISize; 211 } 212 213 const nsStyleColumn* colStyle = StyleColumn(); 214 nscoord colISize; 215 if (colStyle->mColumnWidth.IsLength()) { 216 colISize = 217 ColumnUtils::ClampUsedColumnWidth(colStyle->mColumnWidth.AsLength()); 218 } else { 219 MOZ_ASSERT(!colStyle->mColumnCount.IsAuto(), 220 "column-count and column-width can't both be auto!"); 221 colISize = 0; 222 } 223 224 // If column-count is auto, assume one column. 225 const uint32_t numColumns = colStyle->mColumnCount.IsAuto() 226 ? 1 227 : colStyle->mColumnCount.AsInteger(); 228 const nscoord colGap = 229 ColumnUtils::GetColumnGap(this, NS_UNCONSTRAINEDSIZE); 230 iSize = ColumnUtils::IntrinsicISize(numColumns, colGap, colISize); 231 } else { 232 for (nsIFrame* f : PrincipalChildList()) { 233 const IntrinsicSizeInput childInput(aInput, f->GetWritingMode(), 234 GetWritingMode()); 235 iSize = std::max(iSize, f->GetPrefISize(childInput)); 236 } 237 } 238 239 return iSize; 240 } 241 242 template <typename Iterator> 243 Maybe<nscoord> ColumnSetWrapperFrame::GetBaselineBOffset( 244 Iterator aStart, Iterator aEnd, WritingMode aWM, 245 BaselineSharingGroup aBaselineGroup, 246 BaselineExportContext aExportContext) const { 247 // Either forward iterator + first baseline, or reverse iterator + last 248 // baseline 249 MOZ_ASSERT((*aStart == PrincipalChildList().FirstChild() && 250 aBaselineGroup == BaselineSharingGroup::First) || 251 (*aStart == PrincipalChildList().LastChild() && 252 aBaselineGroup == BaselineSharingGroup::Last), 253 "Iterator direction must match baseline sharing group."); 254 // Start from start/end of principal child list, and use the first valid 255 // baseline. 256 for (auto itr = aStart; itr != aEnd; ++itr) { 257 const nsIFrame* kid = *itr; 258 auto kidBaseline = 259 kid->GetNaturalBaselineBOffset(aWM, aBaselineGroup, aExportContext); 260 if (!kidBaseline) { 261 continue; 262 } 263 // Baseline is offset from the kid's rectangle, so find the offset to the 264 // kid's rectangle. 265 LogicalRect kidRect{aWM, kid->GetLogicalNormalPosition(aWM, GetSize()), 266 kid->GetLogicalSize(aWM)}; 267 if (aBaselineGroup == BaselineSharingGroup::First) { 268 *kidBaseline += kidRect.BStart(aWM); 269 } else { 270 *kidBaseline += (GetLogicalSize().BSize(aWM) - kidRect.BEnd(aWM)); 271 } 272 return kidBaseline; 273 } 274 return Nothing{}; 275 } 276 277 Maybe<nscoord> ColumnSetWrapperFrame::GetNaturalBaselineBOffset( 278 WritingMode aWM, BaselineSharingGroup aBaselineGroup, 279 BaselineExportContext aExportContext) const { 280 if (StyleDisplay()->IsContainLayout()) { 281 return Nothing{}; 282 } 283 if (aBaselineGroup == BaselineSharingGroup::First) { 284 return GetBaselineBOffset(PrincipalChildList().cbegin(), 285 PrincipalChildList().cend(), aWM, aBaselineGroup, 286 aExportContext); 287 } 288 return GetBaselineBOffset(PrincipalChildList().crbegin(), 289 PrincipalChildList().crend(), aWM, aBaselineGroup, 290 aExportContext); 291 } 292 293 #ifdef DEBUG 294 295 /* static */ 296 void ColumnSetWrapperFrame::AssertColumnSpanWrapperSubtreeIsSane( 297 const nsIFrame* aFrame) { 298 MOZ_ASSERT(aFrame->IsColumnSpan(), "aFrame is not column-span?"); 299 300 if (!nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aFrame)) 301 ->Style() 302 ->IsAnonBox()) { 303 // aFrame's style frame has "column-span: all". Traverse no further. 304 return; 305 } 306 307 MOZ_ASSERT( 308 aFrame->Style()->GetPseudoType() == PseudoStyleType::columnSpanWrapper, 309 "aFrame should be ::-moz-column-span-wrapper"); 310 311 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_OWNS_ANON_BOXES), 312 "::-moz-column-span-wrapper anonymous blocks cannot own " 313 "other types of anonymous blocks!"); 314 315 for (const nsIFrame* child : aFrame->PrincipalChildList()) { 316 AssertColumnSpanWrapperSubtreeIsSane(child); 317 } 318 } 319 320 #endif