nsSplittableFrame.cpp (12102B)
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 http://mozilla.org/MPL/2.0/. */ 6 7 /* 8 * base class for rendering objects that can be split across lines, 9 * columns, or pages 10 */ 11 12 #include "nsSplittableFrame.h" 13 14 #include "nsContainerFrame.h" 15 #include "nsFieldSetFrame.h" 16 #include "nsIFrameInlines.h" 17 18 using namespace mozilla; 19 20 NS_QUERYFRAME_HEAD(nsSplittableFrame) 21 NS_QUERYFRAME_ENTRY(nsSplittableFrame) 22 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame) 23 24 void nsSplittableFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 25 nsIFrame* aPrevInFlow) { 26 if (aPrevInFlow) { 27 // Hook the frame into the flow 28 SetPrevInFlow(aPrevInFlow); 29 aPrevInFlow->SetNextInFlow(this); 30 } 31 nsIFrame::Init(aContent, aParent, aPrevInFlow); 32 } 33 34 void nsSplittableFrame::Destroy(DestroyContext& aContext) { 35 // Disconnect from the flow list 36 if (mPrevContinuation || mNextContinuation) { 37 RemoveFromFlow(this); 38 } 39 40 // Let the base class destroy the frame 41 nsIFrame::Destroy(aContext); 42 } 43 44 nsIFrame* nsSplittableFrame::GetPrevContinuation() const { 45 return mPrevContinuation; 46 } 47 48 void nsSplittableFrame::SetPrevContinuation(nsIFrame* aFrame) { 49 NS_ASSERTION(!aFrame || Type() == aFrame->Type(), 50 "setting a prev continuation with incorrect type!"); 51 NS_ASSERTION(!IsInPrevContinuationChain(aFrame, this), 52 "creating a loop in continuation chain!"); 53 mPrevContinuation = aFrame; 54 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 55 UpdateFirstContinuationAndFirstInFlowCache(); 56 } 57 58 nsIFrame* nsSplittableFrame::GetNextContinuation() const { 59 return mNextContinuation; 60 } 61 62 void nsSplittableFrame::SetNextContinuation(nsIFrame* aFrame) { 63 NS_ASSERTION(!aFrame || Type() == aFrame->Type(), 64 "setting a next continuation with incorrect type!"); 65 NS_ASSERTION(!IsInNextContinuationChain(aFrame, this), 66 "creating a loop in continuation chain!"); 67 mNextContinuation = aFrame; 68 if (mNextContinuation) { 69 mNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 70 } 71 } 72 73 nsIFrame* nsSplittableFrame::FirstContinuation() const { 74 if (mFirstContinuation) { 75 return mFirstContinuation; 76 } 77 78 // We fall back to the slow path during the frame destruction where our 79 // first-continuation cache was purged. 80 auto* firstContinuation = const_cast<nsSplittableFrame*>(this); 81 while (nsIFrame* prev = firstContinuation->GetPrevContinuation()) { 82 firstContinuation = static_cast<nsSplittableFrame*>(prev); 83 } 84 MOZ_ASSERT(firstContinuation); 85 return firstContinuation; 86 } 87 88 nsIFrame* nsSplittableFrame::LastContinuation() const { 89 nsSplittableFrame* lastContinuation = const_cast<nsSplittableFrame*>(this); 90 while (lastContinuation->mNextContinuation) { 91 lastContinuation = 92 static_cast<nsSplittableFrame*>(lastContinuation->mNextContinuation); 93 } 94 MOZ_ASSERT(lastContinuation, "post-condition failed"); 95 return lastContinuation; 96 } 97 98 #ifdef DEBUG 99 bool nsSplittableFrame::IsInPrevContinuationChain(nsIFrame* aFrame1, 100 nsIFrame* aFrame2) { 101 int32_t iterations = 0; 102 while (aFrame1 && iterations < 10) { 103 // Bail out after 10 iterations so we don't bog down debug builds too much 104 if (aFrame1 == aFrame2) return true; 105 aFrame1 = aFrame1->GetPrevContinuation(); 106 ++iterations; 107 } 108 return false; 109 } 110 111 bool nsSplittableFrame::IsInNextContinuationChain(nsIFrame* aFrame1, 112 nsIFrame* aFrame2) { 113 int32_t iterations = 0; 114 while (aFrame1 && iterations < 10) { 115 // Bail out after 10 iterations so we don't bog down debug builds too much 116 if (aFrame1 == aFrame2) return true; 117 aFrame1 = aFrame1->GetNextContinuation(); 118 ++iterations; 119 } 120 return false; 121 } 122 #endif 123 124 nsIFrame* nsSplittableFrame::GetPrevInFlow() const { 125 return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation 126 : nullptr; 127 } 128 129 void nsSplittableFrame::SetPrevInFlow(nsIFrame* aFrame) { 130 NS_ASSERTION(!aFrame || Type() == aFrame->Type(), 131 "setting a prev in flow with incorrect type!"); 132 NS_ASSERTION(!IsInPrevContinuationChain(aFrame, this), 133 "creating a loop in continuation chain!"); 134 mPrevContinuation = aFrame; 135 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 136 UpdateFirstContinuationAndFirstInFlowCache(); 137 } 138 139 nsIFrame* nsSplittableFrame::GetNextInFlow() const { 140 return mNextContinuation && mNextContinuation->HasAnyStateBits( 141 NS_FRAME_IS_FLUID_CONTINUATION) 142 ? mNextContinuation 143 : nullptr; 144 } 145 146 void nsSplittableFrame::SetNextInFlow(nsIFrame* aFrame) { 147 NS_ASSERTION(!aFrame || Type() == aFrame->Type(), 148 "setting a next in flow with incorrect type!"); 149 NS_ASSERTION(!IsInNextContinuationChain(aFrame, this), 150 "creating a loop in continuation chain!"); 151 mNextContinuation = aFrame; 152 if (mNextContinuation) { 153 mNextContinuation->AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 154 } 155 } 156 157 nsIFrame* nsSplittableFrame::FirstInFlow() const { 158 if (mFirstInFlow) { 159 return mFirstInFlow; 160 } 161 162 // We fall back to the slow path during the frame destruction where our 163 // first-in-flow cache was purged. 164 auto* firstInFlow = const_cast<nsSplittableFrame*>(this); 165 while (nsIFrame* prev = firstInFlow->GetPrevInFlow()) { 166 firstInFlow = static_cast<nsSplittableFrame*>(prev); 167 } 168 MOZ_ASSERT(firstInFlow); 169 return firstInFlow; 170 } 171 172 nsIFrame* nsSplittableFrame::LastInFlow() const { 173 nsSplittableFrame* lastInFlow = const_cast<nsSplittableFrame*>(this); 174 while (nsIFrame* next = lastInFlow->GetNextInFlow()) { 175 lastInFlow = static_cast<nsSplittableFrame*>(next); 176 } 177 MOZ_ASSERT(lastInFlow, "post-condition failed"); 178 return lastInFlow; 179 } 180 181 void nsSplittableFrame::RemoveFromFlow(nsIFrame* aFrame) { 182 nsIFrame* prevContinuation = aFrame->GetPrevContinuation(); 183 nsIFrame* nextContinuation = aFrame->GetNextContinuation(); 184 185 // The new continuation is fluid only if the continuation on both sides 186 // of the removed frame was fluid 187 if (aFrame->GetPrevInFlow() && aFrame->GetNextInFlow()) { 188 if (prevContinuation) { 189 prevContinuation->SetNextInFlow(nextContinuation); 190 } 191 if (nextContinuation) { 192 nextContinuation->SetPrevInFlow(prevContinuation); 193 } 194 } else { 195 if (prevContinuation) { 196 prevContinuation->SetNextContinuation(nextContinuation); 197 } 198 if (nextContinuation) { 199 nextContinuation->SetPrevContinuation(prevContinuation); 200 } 201 } 202 203 // **Note: it is important here that we clear the Next link from aFrame 204 // BEFORE clearing its Prev link, because in nsContinuingTextFrame, 205 // SetPrevInFlow() would follow the Next pointers, wiping out the cached 206 // mFirstContinuation field from each following frame in the list. 207 aFrame->SetNextInFlow(nullptr); 208 aFrame->SetPrevInFlow(nullptr); 209 } 210 211 void nsSplittableFrame::UpdateFirstContinuationAndFirstInFlowCache() { 212 nsIFrame* oldCachedFirstContinuation = mFirstContinuation; 213 if (nsIFrame* prevContinuation = GetPrevContinuation()) { 214 nsIFrame* newFirstContinuation = prevContinuation->FirstContinuation(); 215 if (oldCachedFirstContinuation != newFirstContinuation) { 216 // Update the first-continuation cache for us and our next-continuations. 217 for (nsSplittableFrame* f = this; f; 218 f = reinterpret_cast<nsSplittableFrame*>(f->GetNextContinuation())) { 219 f->mFirstContinuation = newFirstContinuation; 220 } 221 } 222 } else { 223 // We become the new first-continuation due to our prev-continuation being 224 // removed. 225 if (oldCachedFirstContinuation) { 226 // It's tempting to update the first-continuation cache for our 227 // next-continuations here, but that would result in overall O(n^2) 228 // behavior when a frame list is destroyed from the front. To avoid that 229 // pathological behavior, we simply purge the cached values. 230 for (nsSplittableFrame* f = this; f; 231 f = reinterpret_cast<nsSplittableFrame*>(f->GetNextContinuation())) { 232 f->mFirstContinuation = nullptr; 233 } 234 } 235 } 236 237 nsIFrame* oldCachedFirstInFlow = mFirstInFlow; 238 if (nsIFrame* prevInFlow = GetPrevInFlow()) { 239 nsIFrame* newFirstInFlow = prevInFlow->FirstInFlow(); 240 if (oldCachedFirstInFlow != newFirstInFlow) { 241 // Update the first-in-flow cache for us and our next-in-flows. 242 for (nsSplittableFrame* f = this; f; 243 f = reinterpret_cast<nsSplittableFrame*>(f->GetNextInFlow())) { 244 f->mFirstInFlow = newFirstInFlow; 245 } 246 } 247 } else { 248 // We become the new first-in-flow due to our prev-in-flow being removed. 249 if (oldCachedFirstInFlow) { 250 // It's tempting to update the first-in-flow cache for our 251 // next-in-flows here, but that would result in overall O(n^2) 252 // behavior when a frame list is destroyed from the front. To avoid that 253 // pathological behavior, we simply purge the cached values. 254 for (nsSplittableFrame* f = this; f; 255 f = reinterpret_cast<nsSplittableFrame*>(f->GetNextInFlow())) { 256 f->mFirstInFlow = nullptr; 257 } 258 } 259 } 260 } 261 262 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(ConsumedBSizeProperty, nscoord); 263 264 nscoord nsSplittableFrame::CalcAndCacheConsumedBSize() { 265 nsIFrame* prev = GetPrevContinuation(); 266 if (!prev) { 267 return 0; 268 } 269 const auto wm = GetWritingMode(); 270 nscoord bSize = 0; 271 for (; prev; prev = prev->GetPrevContinuation()) { 272 if (prev->IsTrueOverflowContainer()) { 273 // Overflow containers might not get reflowed, and they have no bSize 274 // anyways. 275 continue; 276 } 277 278 bSize += prev->ContentBSize(wm); 279 bool found = false; 280 nscoord consumed = prev->GetProperty(ConsumedBSizeProperty(), &found); 281 if (found) { 282 bSize += consumed; 283 break; 284 } 285 MOZ_ASSERT(!prev->GetPrevContinuation(), 286 "Property should always be set on prev continuation if not " 287 "the first continuation"); 288 } 289 SetProperty(ConsumedBSizeProperty(), bSize); 290 return bSize; 291 } 292 293 nscoord nsSplittableFrame::GetEffectiveComputedBSize( 294 const ReflowInput& aReflowInput, nscoord aConsumedBSize) const { 295 nscoord bSize = aReflowInput.ComputedBSize(); 296 if (bSize == NS_UNCONSTRAINEDSIZE) { 297 return NS_UNCONSTRAINEDSIZE; 298 } 299 300 bSize -= aConsumedBSize; 301 302 // nsFieldSetFrame's inner frames are special since some of their content-box 303 // BSize may be consumed by positioning it below the legend. So we always 304 // report zero for true overflow containers here. 305 // XXXmats: hmm, can we fix this so that the sizes actually adds up instead? 306 if (IsTrueOverflowContainer() && 307 Style()->GetPseudoType() == PseudoStyleType::fieldsetContent) { 308 for (nsFieldSetFrame* fieldset = do_QueryFrame(GetParent()); fieldset; 309 fieldset = static_cast<nsFieldSetFrame*>(fieldset->GetPrevInFlow())) { 310 bSize -= fieldset->LegendSpace(); 311 } 312 } 313 314 // We may have stretched the frame beyond its computed height. Oh well. 315 return std::max(0, bSize); 316 } 317 318 LogicalSides nsSplittableFrame::GetBlockLevelLogicalSkipSides( 319 bool aAfterReflow) const { 320 LogicalSides skip(mWritingMode); 321 if (MOZ_UNLIKELY(IsTrueOverflowContainer())) { 322 skip += LogicalSides(mWritingMode, LogicalSides::BBoth); 323 return skip; 324 } 325 326 if (MOZ_UNLIKELY(StyleBorder()->mBoxDecorationBreak == 327 StyleBoxDecorationBreak::Clone)) { 328 return skip; 329 } 330 331 if (GetPrevContinuation()) { 332 skip += LogicalSide::BStart; 333 } 334 335 // Always skip block-end side if we have a *later* sibling across column-span 336 // split. 337 if (HasColumnSpanSiblings()) { 338 skip += LogicalSide::BEnd; 339 } 340 341 if (aAfterReflow) { 342 nsIFrame* nif = GetNextContinuation(); 343 if (nif && !nif->IsTrueOverflowContainer()) { 344 skip += LogicalSide::BEnd; 345 } 346 } 347 348 return skip; 349 }