AbsoluteContainingBlock.cpp (81460B)
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 * code for managing absolutely positioned children of a rendering 9 * object that is a containing block for them 10 */ 11 12 #include "mozilla/AbsoluteContainingBlock.h" 13 14 #include "AnchorPositioningUtils.h" 15 #include "fmt/format.h" 16 #include "mozilla/CSSAlignUtils.h" 17 #include "mozilla/DebugOnly.h" 18 #include "mozilla/PresShell.h" 19 #include "mozilla/ReflowInput.h" 20 #include "mozilla/ScrollContainerFrame.h" 21 #include "mozilla/ViewportFrame.h" 22 #include "mozilla/dom/ViewTransition.h" 23 #include "nsCSSFrameConstructor.h" 24 #include "nsContainerFrame.h" 25 #include "nsGridContainerFrame.h" 26 #include "nsIFrameInlines.h" 27 #include "nsPlaceholderFrame.h" 28 #include "nsPresContext.h" 29 #include "nsPresContextInlines.h" 30 31 #ifdef DEBUG 32 # include "nsBlockFrame.h" 33 #endif 34 35 using namespace mozilla; 36 37 void AbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame, 38 FrameChildListID aListID, 39 nsFrameList&& aChildList) { 40 MOZ_ASSERT(mChildListID == aListID, "unexpected child list name"); 41 #ifdef DEBUG 42 nsIFrame::VerifyDirtyBitSet(aChildList); 43 for (nsIFrame* f : aChildList) { 44 MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent"); 45 } 46 #endif 47 mAbsoluteFrames = std::move(aChildList); 48 } 49 50 void AbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame, 51 FrameChildListID aListID, 52 nsFrameList&& aFrameList) { 53 NS_ASSERTION(mChildListID == aListID, "unexpected child list"); 54 55 // Append the frames to our list of absolutely positioned frames 56 #ifdef DEBUG 57 nsIFrame::VerifyDirtyBitSet(aFrameList); 58 #endif 59 mAbsoluteFrames.AppendFrames(nullptr, std::move(aFrameList)); 60 61 // no damage to intrinsic widths, since absolutely positioned frames can't 62 // change them 63 aDelegatingFrame->PresShell()->FrameNeedsReflow( 64 aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN); 65 } 66 67 void AbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame, 68 FrameChildListID aListID, 69 nsIFrame* aPrevFrame, 70 nsFrameList&& aFrameList) { 71 NS_ASSERTION(mChildListID == aListID, "unexpected child list"); 72 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame, 73 "inserting after sibling frame with different parent"); 74 75 #ifdef DEBUG 76 nsIFrame::VerifyDirtyBitSet(aFrameList); 77 #endif 78 mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, std::move(aFrameList)); 79 80 // no damage to intrinsic widths, since absolutely positioned frames can't 81 // change them 82 aDelegatingFrame->PresShell()->FrameNeedsReflow( 83 aDelegatingFrame, IntrinsicDirty::None, NS_FRAME_HAS_DIRTY_CHILDREN); 84 } 85 86 void AbsoluteContainingBlock::RemoveFrame(FrameDestroyContext& aContext, 87 FrameChildListID aListID, 88 nsIFrame* aOldFrame) { 89 NS_ASSERTION(mChildListID == aListID, "unexpected child list"); 90 91 if (!aOldFrame->PresContext()->FragmentainerAwarePositioningEnabled()) { 92 if (nsIFrame* nif = aOldFrame->GetNextInFlow()) { 93 nif->GetParent()->DeleteNextInFlowChild(aContext, nif, false); 94 } 95 mAbsoluteFrames.DestroyFrame(aContext, aOldFrame); 96 return; 97 } 98 99 AutoTArray<nsIFrame*, 8> delFrames; 100 for (nsIFrame* f = aOldFrame; f; f = f->GetNextInFlow()) { 101 delFrames.AppendElement(f); 102 } 103 for (nsIFrame* delFrame : Reversed(delFrames)) { 104 delFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame(delFrame); 105 delFrame->Destroy(aContext); 106 } 107 } 108 109 nsFrameList AbsoluteContainingBlock::StealPushedChildList() { 110 return std::move(mPushedAbsoluteFrames); 111 } 112 113 void AbsoluteContainingBlock::DrainPushedChildList( 114 const nsIFrame* aDelegatingFrame) { 115 MOZ_ASSERT(aDelegatingFrame->GetAbsoluteContainingBlock() == this, 116 "aDelegatingFrame's absCB should be us!"); 117 118 // Our pushed absolute child list might be non-empty if our next-in-flow 119 // hasn't reflowed yet. Move any child in that list that is a first-in-flow, 120 // or whose prev-in-flow is not in our absolute child list, into our absolute 121 // child list. 122 for (auto iter = mPushedAbsoluteFrames.begin(); 123 iter != mPushedAbsoluteFrames.end();) { 124 // Advance the iterator first, so it's safe to move |child|. 125 nsIFrame* const child = *iter++; 126 if (!child->GetPrevInFlow() || 127 child->GetPrevInFlow()->GetParent() != aDelegatingFrame) { 128 mPushedAbsoluteFrames.RemoveFrame(child); 129 mAbsoluteFrames.AppendFrame(nullptr, child); 130 } 131 } 132 } 133 134 bool AbsoluteContainingBlock::PrepareAbsoluteFrames( 135 nsContainerFrame* aDelegatingFrame) { 136 if (!aDelegatingFrame->PresContext() 137 ->FragmentainerAwarePositioningEnabled()) { 138 return HasAbsoluteFrames(); 139 } 140 141 if (const nsIFrame* prevInFlow = aDelegatingFrame->GetPrevInFlow()) { 142 AbsoluteContainingBlock* prevAbsCB = 143 prevInFlow->GetAbsoluteContainingBlock(); 144 MOZ_ASSERT(prevAbsCB, 145 "If this delegating frame has an absCB, its prev-in-flow must " 146 "have one, too!"); 147 148 // Prepend the pushed absolute frames from the previous absCB to our 149 // absolute child list. 150 nsFrameList pushedFrames = prevAbsCB->StealPushedChildList(); 151 if (pushedFrames.NotEmpty()) { 152 mAbsoluteFrames.InsertFrames(aDelegatingFrame, nullptr, 153 std::move(pushedFrames)); 154 } 155 } 156 157 DrainPushedChildList(aDelegatingFrame); 158 159 // Steal absolute frame's first-in-flow from our next-in-flow's child lists. 160 for (const nsIFrame* nextInFlow = aDelegatingFrame->GetNextInFlow(); 161 nextInFlow; nextInFlow = nextInFlow->GetNextInFlow()) { 162 AbsoluteContainingBlock* nextAbsCB = 163 nextInFlow->GetAbsoluteContainingBlock(); 164 MOZ_ASSERT(nextAbsCB, 165 "If this delegating frame has an absCB, its next-in-flow must " 166 "have one, too!"); 167 168 nextAbsCB->DrainPushedChildList(nextInFlow); 169 170 for (auto iter = nextAbsCB->GetChildList().begin(); 171 iter != nextAbsCB->GetChildList().end();) { 172 nsIFrame* const child = *iter++; 173 if (!child->GetPrevInFlow()) { 174 nextAbsCB->StealFrame(child); 175 mAbsoluteFrames.AppendFrame(aDelegatingFrame, child); 176 } 177 } 178 } 179 180 return HasAbsoluteFrames(); 181 } 182 183 void AbsoluteContainingBlock::StealFrame(nsIFrame* aFrame) { 184 const DebugOnly<bool> frameRemoved = 185 mAbsoluteFrames.StartRemoveFrame(aFrame) || 186 mPushedAbsoluteFrames.ContinueRemoveFrame(aFrame); 187 MOZ_ASSERT(frameRemoved, "Failed to find aFrame from our child lists!"); 188 } 189 190 static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos( 191 nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) { 192 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)); 193 if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) { 194 return; 195 } 196 // We should have set the bit when reflowing the previous continuations 197 // already. 198 if (aFrame->GetPrevContinuation()) { 199 return; 200 } 201 202 auto* placeholder = aFrame->GetPlaceholderFrame(); 203 MOZ_ASSERT(placeholder); 204 205 // Only fixed-pos frames can escape their containing block. 206 if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) { 207 return; 208 } 209 210 for (nsIFrame* ancestor = placeholder->GetParent(); ancestor; 211 ancestor = ancestor->GetParent()) { 212 // Walk towards the ancestor's first continuation. That's the only one that 213 // really matters, since it's the only one restyling will look at. We also 214 // flag the following continuations just so it's caught on the first 215 // early-return ones just to avoid walking them over and over. 216 do { 217 if (ancestor->DescendantMayDependOnItsStaticPosition()) { 218 return; 219 } 220 // Moving the containing block or anything above it would move our static 221 // position as well, so no need to flag it or any of its ancestors. 222 if (aFrame == aContainingBlockFrame) { 223 return; 224 } 225 ancestor->SetDescendantMayDependOnItsStaticPosition(true); 226 nsIFrame* prev = ancestor->GetPrevContinuation(); 227 if (!prev) { 228 break; 229 } 230 ancestor = prev; 231 } while (true); 232 } 233 } 234 235 static bool IsSnapshotContainingBlock(const nsIFrame* aFrame) { 236 return aFrame->Style()->GetPseudoType() == 237 PseudoStyleType::mozSnapshotContainingBlock; 238 } 239 240 static PhysicalAxes CheckEarlyCompensatingForScroll(const nsIFrame* aKidFrame) { 241 // Three conditions to compensate for scroll, once a default anchor 242 // exists: 243 // * Used alignment property is `anchor-center`, 244 // * `position-area` is not `none`, or 245 // * `anchor()` function refers to default anchor, or an anchor that 246 // shares the same scroller with it. 247 // Second condition is checkable right now, so do that. 248 if (!aKidFrame->StylePosition()->mPositionArea.IsNone()) { 249 return PhysicalAxes{PhysicalAxis::Horizontal, PhysicalAxis::Vertical}; 250 } 251 return PhysicalAxes{}; 252 } 253 254 static AnchorPosResolutionCache PopulateAnchorResolutionCache( 255 const nsIFrame* aKidFrame, AnchorPosReferenceData* aData) { 256 MOZ_ASSERT(aKidFrame->HasAnchorPosReference()); 257 // If the default anchor exists, it will likely be referenced (Except when 258 // authors then use `anchor()` without referring to anchors whose nearest 259 // scroller that of the default anchor, but that seems 260 // counter-productive). This is a prerequisite for scroll compensation. We 261 // also need to check for `anchor()` resolutions, so cache information for 262 // default anchor and its scrollers right now. 263 AnchorPosResolutionCache result{aData, {}}; 264 // Let this call populate the cache. 265 const auto defaultAnchorInfo = AnchorPositioningUtils::ResolveAnchorPosRect( 266 aKidFrame, aKidFrame->GetParent(), nullptr, false, &result); 267 if (defaultAnchorInfo) { 268 aData->AdjustCompensatingForScroll( 269 CheckEarlyCompensatingForScroll(aKidFrame)); 270 } 271 return result; 272 } 273 274 void AbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame, 275 nsPresContext* aPresContext, 276 const ReflowInput& aReflowInput, 277 nsReflowStatus& aReflowStatus, 278 const nsRect& aContainingBlock, 279 AbsPosReflowFlags aFlags, 280 OverflowAreas* aOverflowAreas) { 281 const auto scrollableContainingBlock = [&]() -> nsRect { 282 switch (aDelegatingFrame->Style()->GetPseudoType()) { 283 case PseudoStyleType::scrolledContent: 284 case PseudoStyleType::scrolledCanvas: { 285 // FIXME(bug 2004432): This is close enough to what we want. In practice 286 // we don't want to account for relative positioning and so on, but this 287 // seems good enough for now. 288 ScrollContainerFrame* sf = do_QueryFrame(aDelegatingFrame->GetParent()); 289 // Clamp to the scrollable range. 290 return sf->GetUnsnappedScrolledRectInternal( 291 aOverflowAreas->ScrollableOverflow(), aContainingBlock.Size()); 292 } 293 default: 294 break; 295 } 296 return aContainingBlock; 297 }(); 298 299 nsReflowStatus reflowStatus; 300 const bool reflowAll = aReflowInput.ShouldReflowAllKids(); 301 const bool cbWidthChanged = aFlags.contains(AbsPosReflowFlag::CBWidthChanged); 302 const bool cbHeightChanged = 303 aFlags.contains(AbsPosReflowFlag::CBHeightChanged); 304 nsOverflowContinuationTracker tracker(aDelegatingFrame, true); 305 for (nsIFrame* kidFrame : mAbsoluteFrames) { 306 Maybe<AnchorPosResolutionCache> anchorPosResolutionCache; 307 if (kidFrame->HasAnchorPosReference()) { 308 auto* referenceData = kidFrame->SetOrUpdateDeletableProperty( 309 nsIFrame::AnchorPosReferences()); 310 anchorPosResolutionCache = 311 Some(PopulateAnchorResolutionCache(kidFrame, referenceData)); 312 } else { 313 kidFrame->RemoveProperty(nsIFrame::AnchorPosReferences()); 314 } 315 316 bool kidNeedsReflow = 317 reflowAll || kidFrame->IsSubtreeDirty() || 318 FrameDependsOnContainer(kidFrame, cbWidthChanged, cbHeightChanged, 319 anchorPosResolutionCache.ptrOr(nullptr)); 320 if (kidFrame->IsSubtreeDirty()) { 321 MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos( 322 kidFrame, aDelegatingFrame); 323 } 324 const nscoord availBSize = aReflowInput.AvailableBSize(); 325 const WritingMode containerWM = aReflowInput.GetWritingMode(); 326 if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) { 327 // If we need to redo pagination on the kid, we need to reflow it. 328 // This can happen either if the available height shrunk and the 329 // kid (or its overflow that creates overflow containers) is now 330 // too large to fit in the available height, or if the available 331 // height has increased and the kid has a next-in-flow that we 332 // might need to pull from. 333 WritingMode kidWM = kidFrame->GetWritingMode(); 334 if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) { 335 // Not sure what the right test would be here. 336 kidNeedsReflow = true; 337 } else { 338 nscoord kidBEnd = 339 kidFrame->GetLogicalRect(aContainingBlock.Size()).BEnd(kidWM); 340 nscoord kidOverflowBEnd = 341 LogicalRect(containerWM, 342 // Use ...RelativeToSelf to ignore transforms 343 kidFrame->ScrollableOverflowRectRelativeToSelf() + 344 kidFrame->GetPosition(), 345 aContainingBlock.Size()) 346 .BEnd(containerWM); 347 NS_ASSERTION(kidOverflowBEnd >= kidBEnd, 348 "overflow area should be at least as large as frame rect"); 349 if (kidOverflowBEnd > availBSize || 350 (kidBEnd < availBSize && kidFrame->GetNextInFlow())) { 351 kidNeedsReflow = true; 352 } 353 } 354 } 355 if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) { 356 // Reflow the frame 357 nsReflowStatus kidStatus; 358 ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, 359 aContainingBlock, scrollableContainingBlock, aFlags, 360 kidFrame, kidStatus, aOverflowAreas, 361 anchorPosResolutionCache.ptrOr(nullptr)); 362 MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(), 363 "ShouldAvoidBreakInside should prevent this from happening"); 364 nsIFrame* nextFrame = kidFrame->GetNextInFlow(); 365 if (aPresContext->FragmentainerAwarePositioningEnabled()) { 366 if (!kidStatus.IsFullyComplete()) { 367 if (!nextFrame) { 368 nextFrame = aPresContext->PresShell() 369 ->FrameConstructor() 370 ->CreateContinuingFrame(kidFrame, aDelegatingFrame); 371 nextFrame->AddStateBits(NS_FRAME_IS_PUSHED_OUT_OF_FLOW); 372 mPushedAbsoluteFrames.AppendFrame(nullptr, nextFrame); 373 } else if (nextFrame->GetParent() != 374 aDelegatingFrame->GetNextInFlow()) { 375 nextFrame->GetParent()->GetAbsoluteContainingBlock()->StealFrame( 376 nextFrame); 377 mPushedAbsoluteFrames.AppendFrame(aDelegatingFrame, nextFrame); 378 } 379 reflowStatus.MergeCompletionStatusFrom(kidStatus); 380 } else if (nextFrame) { 381 // kidFrame is fully-complete. Delete all its next-in-flows. 382 FrameDestroyContext context(aPresContext->PresShell()); 383 nextFrame->GetParent()->GetAbsoluteContainingBlock()->RemoveFrame( 384 context, FrameChildListID::Absolute, nextFrame); 385 } 386 } else { 387 if (!kidStatus.IsFullyComplete() && 388 aDelegatingFrame->CanContainOverflowContainers()) { 389 // Need a continuation 390 if (!nextFrame) { 391 nextFrame = aPresContext->PresShell() 392 ->FrameConstructor() 393 ->CreateContinuingFrame(kidFrame, aDelegatingFrame); 394 } 395 // Add it as an overflow container. 396 // XXXfr This is a hack to fix some of our printing dataloss. 397 // See bug 154892. Not sure how to do it "right" yet; probably want 398 // to keep continuations within an AbsoluteContainingBlock eventually. 399 // 400 // NOTE(TYLin): we're now trying to conditionally do this "right" in 401 // the other branch here, inside of the StaticPrefs pref-check. 402 tracker.Insert(nextFrame, kidStatus); 403 reflowStatus.MergeCompletionStatusFrom(kidStatus); 404 } else if (nextFrame) { 405 // Delete any continuations 406 nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame); 407 FrameDestroyContext context(aPresContext->PresShell()); 408 nextFrame->GetParent()->DeleteNextInFlowChild(context, nextFrame, 409 true); 410 } 411 } 412 } else { 413 if (aOverflowAreas) { 414 if (!aPresContext->FragmentainerAwarePositioningEnabled()) { 415 tracker.Skip(kidFrame, reflowStatus); 416 } 417 aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame); 418 } 419 } 420 421 // Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That 422 // will make sure that we end up reflowing aDelegatingFrame in cases when 423 // one of our kids interrupted. Otherwise we'd set the dirty or 424 // dirty-children bit on the kid in the condition below, and then when 425 // reflow completes and we go to mark dirty bits on all ancestors of that 426 // kid we'll immediately bail out, because the kid already has a dirty bit. 427 // In particular, we won't set any dirty bits on aDelegatingFrame, so when 428 // the following reflow happens we won't reflow the kid in question. This 429 // might be slightly suboptimal in cases where |kidFrame| itself did not 430 // interrupt, since we'll trigger a reflow of it too when it's not strictly 431 // needed. But the logic to not do that is enough more complicated, and 432 // the case enough of an edge case, that this is probably better. 433 if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) { 434 if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) { 435 kidFrame->MarkSubtreeDirty(); 436 } else { 437 kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 438 } 439 } 440 } 441 442 // Abspos frames can't cause their parent to be incomplete, 443 // only overflow incomplete. 444 if (reflowStatus.IsIncomplete()) { 445 reflowStatus.SetOverflowIncomplete(); 446 reflowStatus.SetNextInFlowNeedsReflow(); 447 } 448 449 aReflowStatus.MergeCompletionStatusFrom(reflowStatus); 450 } 451 452 static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) { 453 return aCoord.ConvertsToLength(); 454 } 455 static inline bool IsFixedMarginSize(const AnchorResolvedMargin& aCoord) { 456 return aCoord->ConvertsToLength(); 457 } 458 static inline bool IsFixedOffset(const AnchorResolvedInset& aInset) { 459 // For anchor positioning functions, even if the computed value may be a 460 // fixed length, it depends on the absolute containing block's size. 461 return aInset->ConvertsToLength(); 462 } 463 464 bool AbsoluteContainingBlock::FrameDependsOnContainer( 465 nsIFrame* f, bool aCBWidthChanged, bool aCBHeightChanged, 466 AnchorPosResolutionCache* aAnchorPosResolutionCache) { 467 const nsStylePosition* pos = f->StylePosition(); 468 // See if f's position might have changed because it depends on a 469 // placeholder's position. 470 if (pos->NeedsHypotheticalPositionIfAbsPos()) { 471 return true; 472 } 473 if (!aCBWidthChanged && !aCBHeightChanged) { 474 // skip getting style data 475 return false; 476 } 477 const nsStylePadding* padding = f->StylePadding(); 478 const nsStyleMargin* margin = f->StyleMargin(); 479 WritingMode wm = f->GetWritingMode(); 480 const auto anchorResolutionParams = 481 AnchorPosResolutionParams::From(f, aAnchorPosResolutionCache); 482 if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) { 483 // See if f's inline-size might have changed. 484 // If margin-inline-start/end, padding-inline-start/end, 485 // inline-size, min/max-inline-size are all lengths, 'none', or enumerated, 486 // then our frame isize does not depend on the parent isize. 487 // Note that borders never depend on the parent isize. 488 // XXX All of the enumerated values except -moz-available are ok too. 489 if (nsStylePosition::ISizeDependsOnContainer( 490 pos->ISize(wm, anchorResolutionParams)) || 491 nsStylePosition::MinISizeDependsOnContainer( 492 pos->MinISize(wm, anchorResolutionParams)) || 493 nsStylePosition::MaxISizeDependsOnContainer( 494 pos->MaxISize(wm, anchorResolutionParams)) || 495 !IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) || 496 !IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) { 497 return true; 498 } 499 500 // See if f's position might have changed. If we're RTL then the 501 // rules are slightly different. We'll assume percentage or auto 502 // margins will always induce a dependency on the size 503 if (!IsFixedMarginSize(margin->GetMargin(LogicalSide::IStart, wm, 504 anchorResolutionParams)) || 505 !IsFixedMarginSize( 506 margin->GetMargin(LogicalSide::IEnd, wm, anchorResolutionParams))) { 507 return true; 508 } 509 } 510 if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) { 511 // See if f's block-size might have changed. 512 // If margin-block-start/end, padding-block-start/end, 513 // min-block-size, and max-block-size are all lengths or 'none', 514 // and bsize is a length or bsize and bend are auto and bstart is not auto, 515 // then our frame bsize does not depend on the parent bsize. 516 // Note that borders never depend on the parent bsize. 517 // 518 // FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum 519 // lengths? 520 const auto bSize = pos->BSize(wm, anchorResolutionParams); 521 const auto anchorOffsetResolutionParams = 522 AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams); 523 if ((nsStylePosition::BSizeDependsOnContainer(bSize) && 524 !(bSize->IsAuto() && 525 pos->GetAnchorResolvedInset(LogicalSide::BEnd, wm, 526 anchorOffsetResolutionParams) 527 ->IsAuto() && 528 !pos->GetAnchorResolvedInset(LogicalSide::BStart, wm, 529 anchorOffsetResolutionParams) 530 ->IsAuto())) || 531 nsStylePosition::MinBSizeDependsOnContainer( 532 pos->MinBSize(wm, anchorResolutionParams)) || 533 nsStylePosition::MaxBSizeDependsOnContainer( 534 pos->MaxBSize(wm, anchorResolutionParams)) || 535 !IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) || 536 !IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) { 537 return true; 538 } 539 540 // See if f's position might have changed. 541 if (!IsFixedMarginSize(margin->GetMargin(LogicalSide::BStart, wm, 542 anchorResolutionParams)) || 543 !IsFixedMarginSize( 544 margin->GetMargin(LogicalSide::BEnd, wm, anchorResolutionParams))) { 545 return true; 546 } 547 } 548 549 // Since we store coordinates relative to top and left, the position 550 // of a frame depends on that of its container if it is fixed relative 551 // to the right or bottom, or if it is positioned using percentages 552 // relative to the left or top. Because of the dependency on the 553 // sides (left and top) that we use to store coordinates, these tests 554 // are easier to do using physical coordinates rather than logical. 555 if (aCBWidthChanged) { 556 const auto anchorOffsetResolutionParams = 557 AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams); 558 if (!IsFixedOffset(pos->GetAnchorResolvedInset( 559 eSideLeft, anchorOffsetResolutionParams))) { 560 return true; 561 } 562 // Note that even if 'left' is a length, our position can still 563 // depend on the containing block width, because if our direction or 564 // writing-mode moves from right to left (in either block or inline 565 // progression) and 'right' is not 'auto', we will discard 'left' 566 // and be positioned relative to the containing block right edge. 567 // 'left' length and 'right' auto is the only combination we can be 568 // sure of. 569 if ((wm.GetInlineDir() == WritingMode::InlineDir::RTL || 570 wm.GetBlockDir() == WritingMode::BlockDir::RL) && 571 !pos->GetAnchorResolvedInset(eSideRight, anchorOffsetResolutionParams) 572 ->IsAuto()) { 573 return true; 574 } 575 } 576 if (aCBHeightChanged) { 577 const auto anchorOffsetResolutionParams = 578 AnchorPosOffsetResolutionParams::UseCBFrameSize(anchorResolutionParams); 579 if (!IsFixedOffset(pos->GetAnchorResolvedInset( 580 eSideTop, anchorOffsetResolutionParams))) { 581 return true; 582 } 583 // See comment above for width changes. 584 if (wm.GetInlineDir() == WritingMode::InlineDir::BTT && 585 !pos->GetAnchorResolvedInset(eSideBottom, anchorOffsetResolutionParams) 586 ->IsAuto()) { 587 return true; 588 } 589 } 590 591 return false; 592 } 593 594 void AbsoluteContainingBlock::DestroyFrames(DestroyContext& aContext) { 595 mAbsoluteFrames.DestroyFrames(aContext); 596 mPushedAbsoluteFrames.DestroyFrames(aContext); 597 } 598 599 void AbsoluteContainingBlock::MarkSizeDependentFramesDirty() { 600 DoMarkFramesDirty(false); 601 } 602 603 void AbsoluteContainingBlock::MarkAllFramesDirty() { DoMarkFramesDirty(true); } 604 605 void AbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) { 606 for (nsIFrame* kidFrame : mAbsoluteFrames) { 607 if (aMarkAllDirty) { 608 kidFrame->MarkSubtreeDirty(); 609 } else if (FrameDependsOnContainer(kidFrame, true, true)) { 610 // Add the weakest flags that will make sure we reflow this frame later 611 kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 612 } 613 } 614 } 615 616 // Given an out-of-flow frame, this method returns the parent frame of its 617 // placeholder frame or null if it doesn't have a placeholder for some reason. 618 static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) { 619 nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame(); 620 return placeholder ? placeholder->GetParent() : nullptr; 621 } 622 623 struct NonAutoAlignParams { 624 nscoord mCurrentStartInset; 625 nscoord mCurrentEndInset; 626 627 NonAutoAlignParams(nscoord aStartInset, nscoord aEndInset) 628 : mCurrentStartInset(aStartInset), mCurrentEndInset(aEndInset) {} 629 }; 630 631 /** 632 * This function returns the offset of an abs/fixed-pos child's static 633 * position, with respect to the "start" corner of its alignment container, 634 * according to CSS Box Alignment. This function only operates in a single 635 * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis| 636 * parameter. This is called under two scenarios: 637 * 1. We're statically positioning this absolutely positioned box, meaning 638 * that the offsets are auto and will change depending on the alignment 639 * of the box. 640 * 2. The offsets are non-auto, but the element may not fill the inset-reduced 641 * containing block, so its margin box needs to be aligned in that axis. 642 * This is the step 4 of [1]. Should also be noted that, unlike static 643 * positioning, where we may confine the alignment area for flex/grid 644 * parent containers, we explicitly align to the inset-reduced absolute 645 * container size. 646 * 647 * [1]: https://drafts.csswg.org/css-position-3/#abspos-layout 648 * 649 * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child. 650 * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given 651 * the opportunity to reflow), in terms of 652 * aAbsPosCBWM. 653 * @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM. 654 * @param aPlaceholderContainer The parent of the child frame's corresponding 655 * placeholder frame, cast to a nsContainerFrame. 656 * (This will help us choose which alignment enum 657 * we should use for the child.) 658 * @param aAbsPosCBWM The child frame's containing block's WritingMode. 659 * @param aAbsPosCBAxis The axis (of the containing block) that we should 660 * be doing this computation for. 661 * @param aNonAutoAlignParams Parameters, if specified, indicating that we're 662 * handling scenario 2. 663 */ 664 static nscoord OffsetToAlignedStaticPos( 665 const ReflowInput& aKidReflowInput, const LogicalSize& aKidSizeInAbsPosCBWM, 666 const LogicalSize& aAbsPosCBSize, 667 const nsContainerFrame* aPlaceholderContainer, WritingMode aAbsPosCBWM, 668 LogicalAxis aAbsPosCBAxis, Maybe<NonAutoAlignParams> aNonAutoAlignParams, 669 const StylePositionArea& aPositionArea) { 670 if (!aPlaceholderContainer) { 671 // (The placeholder container should be the thing that kicks this whole 672 // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it 673 // should exist... but bail gracefully if it doesn't.) 674 NS_ERROR( 675 "Missing placeholder-container when computing a " 676 "CSS Box Alignment static position"); 677 return 0; 678 } 679 680 // (Most of this function is simply preparing args that we'll pass to 681 // AlignJustifySelf at the end.) 682 683 // NOTE: Our alignment container is aPlaceholderContainer's content-box 684 // (or an area within it, if aPlaceholderContainer is a grid). So, we'll 685 // perform most of our arithmetic/alignment in aPlaceholderContainer's 686 // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder 687 // container" in variables below. 688 WritingMode pcWM = aPlaceholderContainer->GetWritingMode(); 689 LogicalSize absPosCBSizeInPCWM = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM); 690 691 // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's 692 // writing-mode. 693 const LogicalAxis pcAxis = aAbsPosCBWM.ConvertAxisTo(aAbsPosCBAxis, pcWM); 694 const LogicalSize alignAreaSize = [&]() { 695 if (!aNonAutoAlignParams) { 696 const bool placeholderContainerIsContainingBlock = 697 aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame; 698 699 LayoutFrameType parentType = aPlaceholderContainer->Type(); 700 LogicalSize alignAreaSize(pcWM); 701 if (parentType == LayoutFrameType::FlexContainer) { 702 // We store the frame rect in FinishAndStoreOverflow, which runs _after_ 703 // reflowing the absolute frames, so handle the special case of the 704 // frame being the actual containing block here, by getting the size 705 // from aAbsPosCBSize. 706 // 707 // The alignment container is the flex container's content box. 708 if (placeholderContainerIsContainingBlock) { 709 alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM); 710 // aAbsPosCBSize is the padding-box, so substract the padding to get 711 // the content box. 712 alignAreaSize -= 713 aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM); 714 } else { 715 alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM); 716 LogicalMargin pcBorderPadding = 717 aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM); 718 alignAreaSize -= pcBorderPadding.Size(pcWM); 719 } 720 return alignAreaSize; 721 } 722 if (parentType == LayoutFrameType::GridContainer) { 723 // This abspos elem's parent is a grid container. Per CSS Grid 10.1 724 // & 10.2: 725 // - If the grid container *also* generates the abspos containing block 726 // (a 727 // grid area) for this abspos child, we use that abspos containing block 728 // as the alignment container, too. (And its size is aAbsPosCBSize.) 729 // - Otherwise, we use the grid's padding box as the alignment 730 // container. 731 // https://drafts.csswg.org/css-grid/#static-position 732 if (placeholderContainerIsContainingBlock) { 733 // The alignment container is the grid area that we're using as the 734 // absolute containing block. 735 alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM); 736 } else { 737 // The alignment container is a the grid container's content box 738 // (which we can get by subtracting away its border & padding from 739 // frame's size): 740 alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM); 741 LogicalMargin pcBorderPadding = 742 aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM); 743 alignAreaSize -= pcBorderPadding.Size(pcWM); 744 } 745 return alignAreaSize; 746 } 747 } 748 // Either we're in scenario 1 but within a non-flex/grid parent, or in 749 // scenario 2. 750 return aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM); 751 }(); 752 753 const nscoord existingOffset = aNonAutoAlignParams 754 ? aNonAutoAlignParams->mCurrentStartInset + 755 aNonAutoAlignParams->mCurrentEndInset 756 : 0; 757 const nscoord alignAreaSizeInAxis = 758 ((pcAxis == LogicalAxis::Inline) ? alignAreaSize.ISize(pcWM) 759 : alignAreaSize.BSize(pcWM)) - 760 existingOffset; 761 762 using AlignJustifyFlag = CSSAlignUtils::AlignJustifyFlag; 763 CSSAlignUtils::AlignJustifyFlags flags(AlignJustifyFlag::IgnoreAutoMargins); 764 // Given that scenario 2 ignores the parent container type, special handling 765 // of absolutely-positioned child is also ignored. 766 StyleAlignFlags alignConst = 767 aNonAutoAlignParams 768 ? aPlaceholderContainer 769 ->CSSAlignmentForAbsPosChildWithinContainingBlock( 770 aKidReflowInput, pcAxis, aPositionArea, absPosCBSizeInPCWM) 771 : aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput, 772 pcAxis); 773 // If the safe bit in alignConst is set, set the safe flag in |flags|. 774 const auto safetyBits = 775 alignConst & (StyleAlignFlags::SAFE | StyleAlignFlags::UNSAFE); 776 alignConst &= ~StyleAlignFlags::FLAG_BITS; 777 if (safetyBits & StyleAlignFlags::SAFE) { 778 flags += AlignJustifyFlag::OverflowSafe; 779 } 780 781 // Find out if placeholder-container & the OOF child have the same start-sides 782 // in the placeholder-container's pcAxis. 783 WritingMode kidWM = aKidReflowInput.GetWritingMode(); 784 if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) { 785 flags += AlignJustifyFlag::SameSide; 786 } 787 788 if (aNonAutoAlignParams) { 789 flags += AlignJustifyFlag::AligningMarginBox; 790 } 791 792 // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've 793 // converted 'baseline'/'last baseline' enums to their fallback values.) 794 const nscoord baselineAdjust = nscoord(0); 795 796 // AlignJustifySelf operates in the kid's writing mode, so we need to 797 // represent the child's size and the desired axis in that writing mode: 798 LogicalSize kidSizeInOwnWM = 799 aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM); 800 const LogicalAxis kidAxis = aAbsPosCBWM.ConvertAxisTo(aAbsPosCBAxis, kidWM); 801 802 // Build an Inset Modified anchor info from the anchor which can be used to 803 // align to the anchor-center, if AlignJustifySelf is AnchorCenter. 804 Maybe<CSSAlignUtils::AnchorAlignInfo> anchorAlignInfo; 805 if (alignConst == StyleAlignFlags::ANCHOR_CENTER && 806 aKidReflowInput.mAnchorPosResolutionCache) { 807 auto* referenceData = 808 aKidReflowInput.mAnchorPosResolutionCache->mReferenceData; 809 if (referenceData) { 810 const auto* cachedData = 811 referenceData->Lookup(referenceData->mDefaultAnchorName); 812 if (cachedData && *cachedData) { 813 referenceData->AdjustCompensatingForScroll( 814 aAbsPosCBWM.PhysicalAxis(aAbsPosCBAxis)); 815 const auto& data = cachedData->ref(); 816 if (data.mOffsetData) { 817 const nsSize containerSize = 818 aAbsPosCBSize.GetPhysicalSize(aAbsPosCBWM); 819 const nsRect anchorRect(data.mOffsetData->mOrigin, data.mSize); 820 const LogicalRect logicalAnchorRect{aAbsPosCBWM, anchorRect, 821 containerSize}; 822 const auto axisInAbsPosCBWM = 823 kidWM.ConvertAxisTo(kidAxis, aAbsPosCBWM); 824 const auto anchorStart = 825 logicalAnchorRect.Start(axisInAbsPosCBWM, aAbsPosCBWM); 826 const auto anchorSize = 827 logicalAnchorRect.Size(axisInAbsPosCBWM, aAbsPosCBWM); 828 anchorAlignInfo = 829 Some(CSSAlignUtils::AnchorAlignInfo{anchorStart, anchorSize}); 830 if (aNonAutoAlignParams) { 831 anchorAlignInfo->mAnchorStart -= 832 aNonAutoAlignParams->mCurrentStartInset; 833 } 834 } 835 } 836 } 837 } 838 839 nscoord offset = CSSAlignUtils::AlignJustifySelf( 840 alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis, 841 aKidReflowInput, kidSizeInOwnWM, anchorAlignInfo); 842 843 // Safe alignment clamping for anchor-center. 844 // When using anchor-center with the safe keyword, or when both insets are 845 // auto (which defaults to safe behavior), clamp the element to stay within 846 // the containing block. 847 if ((!aNonAutoAlignParams || (safetyBits & StyleAlignFlags::SAFE)) && 848 alignConst == StyleAlignFlags::ANCHOR_CENTER) { 849 const auto cbSize = aAbsPosCBSize.Size(aAbsPosCBAxis, aAbsPosCBWM); 850 const auto kidSize = aKidSizeInAbsPosCBWM.Size(aAbsPosCBAxis, aAbsPosCBWM); 851 852 if (aNonAutoAlignParams) { 853 const nscoord currentStartInset = aNonAutoAlignParams->mCurrentStartInset; 854 const nscoord finalStart = currentStartInset + offset; 855 const nscoord clampedStart = 856 CSSMinMax(finalStart, nscoord(0), cbSize - kidSize); 857 offset = clampedStart - currentStartInset; 858 } else { 859 offset = CSSMinMax(offset, nscoord(0), cbSize - kidSize); 860 } 861 } 862 863 const auto rawAlignConst = 864 (pcAxis == LogicalAxis::Inline) 865 ? aKidReflowInput.mStylePosition->mJustifySelf._0 866 : aKidReflowInput.mStylePosition->mAlignSelf._0; 867 if (aNonAutoAlignParams && !safetyBits && 868 (rawAlignConst != StyleAlignFlags::AUTO || 869 alignConst == StyleAlignFlags::ANCHOR_CENTER)) { 870 // No `safe` or `unsafe` specified - "in-between" behaviour for relevant 871 // alignment values: https://drafts.csswg.org/css-position-3/#abspos-layout 872 // Skip if the raw self alignment for this element is `auto` to preserve 873 // legacy behaviour, except in the case where the resolved value is 874 // anchor-center (where "legacy behavior" is not a concern). 875 // Follows https://drafts.csswg.org/css-align-3/#auto-safety-position 876 const auto cbSize = aAbsPosCBSize.Size(aAbsPosCBAxis, aAbsPosCBWM); 877 // IMCB stands for "Inset-Modified Containing Block." 878 const auto imcbStart = aNonAutoAlignParams->mCurrentStartInset; 879 const auto imcbEnd = cbSize - aNonAutoAlignParams->mCurrentEndInset; 880 const auto kidSize = aKidSizeInAbsPosCBWM.Size(aAbsPosCBAxis, aAbsPosCBWM); 881 const auto kidStart = aNonAutoAlignParams->mCurrentStartInset + offset; 882 const auto kidEnd = kidStart + kidSize; 883 // "[...] the overflow limit rect is the bounding rectangle of the alignment 884 // subject’s inset-modified containing block and its original containing 885 // block." 886 const auto overflowLimitRectStart = std::min(0, imcbStart); 887 const auto overflowLimitRectEnd = std::max(cbSize, imcbEnd); 888 889 if (kidStart >= imcbStart && kidEnd <= imcbEnd) { 890 // 1. We fit inside the IMCB, no action needed. 891 } else if (kidSize <= overflowLimitRectEnd - overflowLimitRectStart) { 892 // 2. We overflowed IMCB, try to cover IMCB completely, if it's not. 893 if (kidStart <= imcbStart && kidEnd >= imcbEnd) { 894 // IMCB already covered, ensure that we aren't escaping the limit rect. 895 if (kidStart < overflowLimitRectStart) { 896 offset += overflowLimitRectStart - kidStart; 897 } else if (kidEnd > overflowLimitRectEnd) { 898 offset -= kidEnd - overflowLimitRectEnd; 899 } 900 } else if (kidEnd < imcbEnd && kidStart < imcbStart) { 901 // Space to end, overflowing on start - nudge to end. 902 offset += std::min(imcbStart - kidStart, imcbEnd - kidEnd); 903 } else if (kidStart > imcbStart && kidEnd > imcbEnd) { 904 // Space to start, overflowing on end - nudge to start. 905 offset -= std::min(kidEnd - imcbEnd, kidStart - imcbStart); 906 } 907 } else { 908 // 3. We'll overflow the limit rect. Start align the subject int overflow 909 // limit rect. 910 offset = 911 -aNonAutoAlignParams->mCurrentStartInset + overflowLimitRectStart; 912 } 913 } 914 915 // "offset" is in terms of the CSS Box Alignment container (i.e. it's in 916 // terms of pcWM). But our return value needs to in terms of the containing 917 // block's writing mode, which might have the opposite directionality in the 918 // given axis. In that case, we just need to negate "offset" when returning, 919 // to make it have the right effect as an offset for coordinates in the 920 // containing block's writing mode. 921 if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) { 922 return -offset; 923 } 924 return offset; 925 } 926 927 void AbsoluteContainingBlock::ResolveSizeDependentOffsets( 928 ReflowInput& aKidReflowInput, const LogicalSize& aCBSize, 929 const LogicalSize& aKidSize, const LogicalMargin& aMargin, 930 const StylePositionArea& aResolvedPositionArea, LogicalMargin& aOffsets) { 931 WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode(); 932 933 // Now that we know the child's size, we resolve any sentinel values in its 934 // IStart/BStart offset coordinates that depend on that size. 935 // * NS_AUTOOFFSET indicates that the child's position in the given axis 936 // is determined by its end-wards offset property, combined with its size and 937 // available space. e.g.: "top: auto; height: auto; bottom: 50px" 938 // * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its 939 // static position in that axis, *and* its static position is determined by 940 // the axis-appropriate css-align property (which may require the child's 941 // size, e.g. to center it within the parent). 942 if ((NS_AUTOOFFSET == aOffsets.IStart(outerWM)) || 943 (NS_AUTOOFFSET == aOffsets.BStart(outerWM)) || 944 aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign || 945 aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) { 946 // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign 947 // clauses. We declare it at this scope so we can avoid having to look 948 // it up twice (and only look it up if it's needed). 949 nsContainerFrame* placeholderContainer = nullptr; 950 951 if (NS_AUTOOFFSET == aOffsets.IStart(outerWM)) { 952 NS_ASSERTION(NS_AUTOOFFSET != aOffsets.IEnd(outerWM), 953 "Can't solve for both start and end"); 954 aOffsets.IStart(outerWM) = 955 aCBSize.ISize(outerWM) - aOffsets.IEnd(outerWM) - 956 aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM); 957 } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) { 958 placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame); 959 nscoord offset = OffsetToAlignedStaticPos( 960 aKidReflowInput, aKidSize, aCBSize, placeholderContainer, outerWM, 961 LogicalAxis::Inline, Nothing{}, aResolvedPositionArea); 962 // Shift IStart from its current position (at start corner of the 963 // alignment container) by the returned offset. And set IEnd to the 964 // distance between the kid's end edge to containing block's end edge. 965 aOffsets.IStart(outerWM) += offset; 966 aOffsets.IEnd(outerWM) = 967 aCBSize.ISize(outerWM) - 968 (aOffsets.IStart(outerWM) + aKidSize.ISize(outerWM)); 969 } 970 971 if (NS_AUTOOFFSET == aOffsets.BStart(outerWM)) { 972 aOffsets.BStart(outerWM) = 973 aCBSize.BSize(outerWM) - aOffsets.BEnd(outerWM) - 974 aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM); 975 } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) { 976 if (!placeholderContainer) { 977 placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame); 978 } 979 nscoord offset = OffsetToAlignedStaticPos( 980 aKidReflowInput, aKidSize, aCBSize, placeholderContainer, outerWM, 981 LogicalAxis::Block, Nothing{}, aResolvedPositionArea); 982 // Shift BStart from its current position (at start corner of the 983 // alignment container) by the returned offset. And set BEnd to the 984 // distance between the kid's end edge to containing block's end edge. 985 aOffsets.BStart(outerWM) += offset; 986 aOffsets.BEnd(outerWM) = 987 aCBSize.BSize(outerWM) - 988 (aOffsets.BStart(outerWM) + aKidSize.BSize(outerWM)); 989 } 990 aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets); 991 } 992 } 993 994 void AbsoluteContainingBlock::ResolveAutoMarginsAfterLayout( 995 ReflowInput& aKidReflowInput, const LogicalSize& aCBSize, 996 const LogicalSize& aKidSize, LogicalMargin& aMargin, 997 const LogicalMargin& aOffsets) { 998 WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode(); 999 const auto& styleMargin = aKidReflowInput.mStyleMargin; 1000 const auto anchorResolutionParams = 1001 AnchorPosResolutionParams::From(&aKidReflowInput); 1002 1003 auto ResolveMarginsInAxis = [&](LogicalAxis aAxis) { 1004 const auto startSide = MakeLogicalSide(aAxis, LogicalEdge::Start); 1005 const auto endSide = MakeLogicalSide(aAxis, LogicalEdge::End); 1006 1007 // No need to substract border sizes because aKidSize has it included 1008 // already. Also, if any offset is auto, the auto margin resolves to zero. 1009 // https://drafts.csswg.org/css-position-3/#abspos-margins 1010 const bool autoOffset = 1011 aOffsets.Side(startSide, outerWM) == NS_AUTOOFFSET || 1012 aOffsets.Side(endSide, outerWM) == NS_AUTOOFFSET; 1013 1014 nscoord availMarginSpace; 1015 if (autoOffset) { 1016 availMarginSpace = 0; 1017 } else { 1018 const nscoord stretchFitSize = std::max( 1019 0, aCBSize.Size(aAxis, outerWM) - aOffsets.StartEnd(aAxis, outerWM) - 1020 aMargin.StartEnd(aAxis, outerWM)); 1021 availMarginSpace = stretchFitSize - aKidSize.Size(aAxis, outerWM); 1022 } 1023 1024 const bool startSideMarginIsAuto = 1025 styleMargin->GetMargin(startSide, outerWM, anchorResolutionParams) 1026 ->IsAuto(); 1027 const bool endSideMarginIsAuto = 1028 styleMargin->GetMargin(endSide, outerWM, anchorResolutionParams) 1029 ->IsAuto(); 1030 1031 if (aAxis == LogicalAxis::Inline) { 1032 ReflowInput::ComputeAbsPosInlineAutoMargin(availMarginSpace, outerWM, 1033 startSideMarginIsAuto, 1034 endSideMarginIsAuto, aMargin); 1035 } else { 1036 ReflowInput::ComputeAbsPosBlockAutoMargin(availMarginSpace, outerWM, 1037 startSideMarginIsAuto, 1038 endSideMarginIsAuto, aMargin); 1039 } 1040 }; 1041 1042 ResolveMarginsInAxis(LogicalAxis::Inline); 1043 ResolveMarginsInAxis(LogicalAxis::Block); 1044 aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin); 1045 1046 nsMargin* propValue = 1047 aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty()); 1048 // InitOffsets should've created a UsedMarginProperty for us, if any margin is 1049 // auto. 1050 MOZ_ASSERT_IF( 1051 styleMargin->HasInlineAxisAuto(outerWM, anchorResolutionParams) || 1052 styleMargin->HasBlockAxisAuto(outerWM, anchorResolutionParams), 1053 propValue); 1054 if (propValue) { 1055 *propValue = aMargin.GetPhysicalMargin(outerWM); 1056 } 1057 } 1058 1059 struct None {}; 1060 using OldCacheState = Variant<None, AnchorPosResolutionCache::PositionTryBackup, 1061 AnchorPosResolutionCache::PositionTryFullBackup>; 1062 1063 struct MOZ_STACK_CLASS MOZ_RAII AutoFallbackStyleSetter { 1064 AutoFallbackStyleSetter(nsIFrame* aFrame, ComputedStyle* aFallbackStyle, 1065 AnchorPosResolutionCache* aCache, bool aIsFirstTry) 1066 : mFrame(aFrame), mCache{aCache}, mOldCacheState{None{}} { 1067 if (aFallbackStyle) { 1068 mOldStyle = aFrame->SetComputedStyleWithoutNotification(aFallbackStyle); 1069 } 1070 // We need to be able to "go back" to the old, first try (Which is not 1071 // necessarily base style) cache. 1072 if (!aIsFirstTry && aCache) { 1073 // New fallback could just be a flip keyword. 1074 if (mOldStyle && mOldStyle->StylePosition()->mPositionAnchor != 1075 aFrame->StylePosition()->mPositionAnchor) { 1076 mOldCacheState = 1077 OldCacheState{aCache->TryPositionWithDifferentDefaultAnchor()}; 1078 *aCache = PopulateAnchorResolutionCache(aFrame, aCache->mReferenceData); 1079 } else { 1080 mOldCacheState = 1081 OldCacheState{aCache->TryPositionWithSameDefaultAnchor()}; 1082 if (aCache->mDefaultAnchorCache.mAnchor) { 1083 aCache->mReferenceData->AdjustCompensatingForScroll( 1084 CheckEarlyCompensatingForScroll(aFrame)); 1085 } 1086 } 1087 } 1088 } 1089 1090 ~AutoFallbackStyleSetter() { 1091 if (mOldStyle) { 1092 mFrame->SetComputedStyleWithoutNotification(std::move(mOldStyle)); 1093 } 1094 std::move(mOldCacheState) 1095 .match( 1096 [](None&&) {}, 1097 [&](AnchorPosResolutionCache::PositionTryBackup&& aBackup) { 1098 mCache->UndoTryPositionWithSameDefaultAnchor(std::move(aBackup)); 1099 }, 1100 [&](AnchorPosResolutionCache::PositionTryFullBackup&& aBackup) { 1101 mCache->UndoTryPositionWithDifferentDefaultAnchor( 1102 std::move(aBackup)); 1103 }); 1104 } 1105 1106 void CommitCurrentFallback() { 1107 mOldCacheState = OldCacheState{None{}}; 1108 // If we have a non-layout dependent margin / paddings, which are different 1109 // from our original style, we need to make sure to commit it into the frame 1110 // property so that it doesn't get lost after returning from reflow. 1111 nsMargin margin; 1112 if (mOldStyle && 1113 !mOldStyle->StyleMargin()->MarginEquals(*mFrame->StyleMargin()) && 1114 mFrame->StyleMargin()->GetMargin(margin)) { 1115 mFrame->SetOrUpdateDeletableProperty(nsIFrame::UsedMarginProperty(), 1116 margin); 1117 } 1118 } 1119 1120 private: 1121 nsIFrame* const mFrame; 1122 RefPtr<ComputedStyle> mOldStyle; 1123 AnchorPosResolutionCache* const mCache; 1124 OldCacheState mOldCacheState; 1125 }; 1126 1127 struct AnchorShiftInfo { 1128 nsPoint mOffset; 1129 StylePositionArea mResolvedArea; 1130 }; 1131 1132 struct ContainingBlockRect { 1133 Maybe<AnchorShiftInfo> mAnchorShiftInfo; 1134 nsRect mMaybeScrollableRect; 1135 nsRect mFinalRect; 1136 1137 explicit ContainingBlockRect(const nsRect& aRect) 1138 : mMaybeScrollableRect{aRect}, mFinalRect{aRect} {} 1139 ContainingBlockRect(const nsRect& aMaybeScrollableRect, 1140 const nsRect& aFinalRect) 1141 : mMaybeScrollableRect{aMaybeScrollableRect}, mFinalRect{aFinalRect} {} 1142 ContainingBlockRect(const nsPoint& aOffset, 1143 const StylePositionArea& aResolvedArea, 1144 const nsRect& aMaybeScrollableRect, 1145 const nsRect& aFinalRect) 1146 : mAnchorShiftInfo{Some(AnchorShiftInfo{aOffset, aResolvedArea})}, 1147 mMaybeScrollableRect{aMaybeScrollableRect}, 1148 mFinalRect{aFinalRect} {} 1149 1150 StylePositionArea ResolvedPositionArea() const { 1151 return mAnchorShiftInfo 1152 .map([](const AnchorShiftInfo& aInfo) { return aInfo.mResolvedArea; }) 1153 .valueOr(StylePositionArea{}); 1154 } 1155 }; 1156 1157 static SideBits GetScrollCompensatedSidesFor( 1158 const StylePositionArea& aPositionArea) { 1159 SideBits sides{SideBits::eNone}; 1160 // The opposite side of the direction keyword is attached to the 1161 // position-anchor grid, which is then attached to the anchor, and so is 1162 // scroll compensated. `center` is constrained by the position-area grid 1163 // on both sides. `span-all` is unconstrained in that axis. 1164 if (aPositionArea.first == StylePositionAreaKeyword::Left || 1165 aPositionArea.first == StylePositionAreaKeyword::SpanLeft) { 1166 sides |= SideBits::eRight; 1167 } else if (aPositionArea.first == StylePositionAreaKeyword::Right || 1168 aPositionArea.first == StylePositionAreaKeyword::SpanRight) { 1169 sides |= SideBits::eLeft; 1170 } else if (aPositionArea.first == StylePositionAreaKeyword::Center) { 1171 sides |= SideBits::eLeftRight; 1172 } 1173 1174 if (aPositionArea.second == StylePositionAreaKeyword::Top || 1175 aPositionArea.second == StylePositionAreaKeyword::SpanTop) { 1176 sides |= SideBits::eBottom; 1177 } else if (aPositionArea.second == StylePositionAreaKeyword::Bottom || 1178 aPositionArea.second == StylePositionAreaKeyword::SpanBottom) { 1179 sides |= SideBits::eTop; 1180 } else if (aPositionArea.first == StylePositionAreaKeyword::Center) { 1181 sides |= SideBits::eTopBottom; 1182 } 1183 1184 return sides; 1185 } 1186 1187 // XXX Optimize the case where it's a resize reflow and the absolutely 1188 // positioned child has the exact same size and position and skip the 1189 // reflow... 1190 void AbsoluteContainingBlock::ReflowAbsoluteFrame( 1191 nsContainerFrame* aDelegatingFrame, nsPresContext* aPresContext, 1192 const ReflowInput& aReflowInput, const nsRect& aOriginalContainingBlockRect, 1193 const nsRect& aOriginalScrollableContainingBlockRect, 1194 AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus, 1195 OverflowAreas* aOverflowAreas, 1196 AnchorPosResolutionCache* aAnchorPosResolutionCache) { 1197 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!"); 1198 1199 #ifdef DEBUG 1200 if (nsBlockFrame::gNoisyReflow) { 1201 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent); 1202 fmt::println( 1203 FMT_STRING("abspos {}: begin reflow: availSize={}, orig cbRect={}"), 1204 aKidFrame->ListTag(), ToString(aReflowInput.AvailableSize()), 1205 ToString(aOriginalContainingBlockRect)); 1206 } 1207 AutoNoisyIndenter indent(nsBlockFrame::gNoisy); 1208 #endif // DEBUG 1209 1210 const WritingMode outerWM = aReflowInput.GetWritingMode(); 1211 const WritingMode wm = aKidFrame->GetWritingMode(); 1212 1213 const bool isGrid = aFlags.contains(AbsPosReflowFlag::IsGridContainerCB); 1214 auto fallbacks = 1215 aKidFrame->StylePosition()->mPositionTryFallbacks._0.AsSpan(); 1216 Maybe<uint32_t> currentFallbackIndex; 1217 const StylePositionTryFallbacksItem* currentFallback = nullptr; 1218 RefPtr<ComputedStyle> currentFallbackStyle; 1219 RefPtr<ComputedStyle> firstTryStyle; 1220 Maybe<uint32_t> firstTryIndex; 1221 // If non-'normal' position-try-order is in effect, we keep track of the 1222 // index of the "best" option seen, and its size in the relevant axis, so 1223 // that once all fallbacks have been considered we can reset to the one 1224 // that provided the most space. 1225 Maybe<uint32_t> bestIndex; 1226 nscoord bestSize = -1; 1227 // Flag to indicate that we've determined which fallback to use and should 1228 // exit the loop. 1229 bool finalizing = false; 1230 1231 auto tryOrder = aKidFrame->StylePosition()->mPositionTryOrder; 1232 // If position-try-order is a logical value, resolve to physical using 1233 // the containing block's writing mode. 1234 switch (tryOrder) { 1235 case StylePositionTryOrder::MostInlineSize: 1236 tryOrder = outerWM.IsVertical() ? StylePositionTryOrder::MostHeight 1237 : StylePositionTryOrder::MostWidth; 1238 break; 1239 case StylePositionTryOrder::MostBlockSize: 1240 tryOrder = outerWM.IsVertical() ? StylePositionTryOrder::MostWidth 1241 : StylePositionTryOrder::MostHeight; 1242 break; 1243 default: 1244 break; 1245 } 1246 1247 // Set the current fallback to the given index, or reset to the base position 1248 // if Nothing() is passed. 1249 auto SeekFallbackTo = [&](Maybe<uint32_t> aIndex) -> bool { 1250 if (!aIndex) { 1251 currentFallbackIndex = Nothing(); 1252 currentFallback = nullptr; 1253 currentFallbackStyle = nullptr; 1254 return true; 1255 } 1256 uint32_t index = *aIndex; 1257 if (index >= fallbacks.Length()) { 1258 return false; 1259 } 1260 1261 const StylePositionTryFallbacksItem* nextFallback; 1262 RefPtr<ComputedStyle> nextFallbackStyle; 1263 while (true) { 1264 nextFallback = &fallbacks[index]; 1265 nextFallbackStyle = aPresContext->StyleSet()->ResolvePositionTry( 1266 *aKidFrame->GetContent()->AsElement(), *aKidFrame->Style(), 1267 *nextFallback); 1268 if (nextFallbackStyle) { 1269 break; 1270 } 1271 // No @position-try rule for this name was found, per spec we should 1272 // skip it. 1273 index++; 1274 if (index >= fallbacks.Length()) { 1275 return false; 1276 } 1277 } 1278 currentFallbackIndex = Some(index); 1279 currentFallback = nextFallback; 1280 currentFallbackStyle = std::move(nextFallbackStyle); 1281 return true; 1282 }; 1283 1284 // Advance to the next fallback to be tried. Normally this is simply the next 1285 // index in the position-try-fallbacks list, but we have some special cases: 1286 // - if we're currently at the last-successful fallback (recorded as 1287 // firstTryIndex), we "advance" to the base position 1288 // - we skip the last-successful fallback when we reach its position again 1289 auto TryAdvanceFallback = [&]() -> bool { 1290 if (fallbacks.IsEmpty()) { 1291 return false; 1292 } 1293 if (firstTryIndex && currentFallbackIndex == firstTryIndex) { 1294 return SeekFallbackTo(Nothing()); 1295 } 1296 uint32_t nextFallbackIndex = 1297 currentFallbackIndex ? *currentFallbackIndex + 1 : 0; 1298 if (firstTryIndex && nextFallbackIndex == *firstTryIndex) { 1299 ++nextFallbackIndex; 1300 } 1301 return SeekFallbackTo(Some(nextFallbackIndex)); 1302 }; 1303 1304 Maybe<nsRect> firstTryNormalRect; 1305 if (auto* lastSuccessfulPosition = 1306 aKidFrame->GetProperty(nsIFrame::LastSuccessfulPositionFallback())) { 1307 if (SeekFallbackTo(Some(lastSuccessfulPosition->mIndex))) { 1308 // Remember which fallback we're trying first; also record its style, 1309 // in case we need to restore it later. 1310 firstTryIndex = Some(lastSuccessfulPosition->mIndex); 1311 firstTryStyle = currentFallbackStyle; 1312 } else { 1313 aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); 1314 } 1315 } 1316 1317 // Assume we *are* overflowing the CB and if we find a fallback that doesn't 1318 // overflow, we set this to false and break the loop. 1319 bool isOverflowingCB = true; 1320 1321 do { 1322 AutoFallbackStyleSetter fallback(aKidFrame, currentFallbackStyle, 1323 aAnchorPosResolutionCache, 1324 firstTryIndex == currentFallbackIndex); 1325 auto cb = [&]() { 1326 // The current containing block, with ongoing modifications. 1327 // Starts as a local containing block. 1328 nsRect containingBlock = aOriginalContainingBlockRect; 1329 const auto defaultAnchorInfo = [&]() -> Maybe<AnchorPosInfo> { 1330 if (!aAnchorPosResolutionCache) { 1331 return Nothing{}; 1332 } 1333 return AnchorPositioningUtils::ResolveAnchorPosRect( 1334 aKidFrame, aDelegatingFrame, nullptr, false, 1335 aAnchorPosResolutionCache); 1336 }(); 1337 if (defaultAnchorInfo) { 1338 // Presence of a valid default anchor causes us to use the scrollable 1339 // containing block. 1340 // https://github.com/w3c/csswg-drafts/issues/12552#issuecomment-3210696721 1341 containingBlock = aOriginalScrollableContainingBlockRect; 1342 } 1343 1344 // https://drafts.csswg.org/css-position/#original-cb 1345 // Handle grid-based adjustment first... 1346 if (isGrid) { 1347 const auto border = aDelegatingFrame->GetUsedBorder(); 1348 const nsPoint borderShift{border.left, border.top}; 1349 // Shift in by border of the overall grid container. 1350 containingBlock = 1351 nsGridContainerFrame::GridItemCB(aKidFrame) + borderShift; 1352 if (!defaultAnchorInfo) { 1353 return ContainingBlockRect{containingBlock}; 1354 } 1355 } 1356 // ... Then the position-area based adjustment. 1357 if (defaultAnchorInfo) { 1358 auto positionArea = aKidFrame->StylePosition()->mPositionArea; 1359 if (!positionArea.IsNone()) { 1360 // Offset should be up to, but not including the containing block's 1361 // scroll offset. 1362 const auto offset = AnchorPositioningUtils::GetScrollOffsetFor( 1363 aAnchorPosResolutionCache->mReferenceData 1364 ->CompensatingForScrollAxes(), 1365 aKidFrame, aAnchorPosResolutionCache->mDefaultAnchorCache); 1366 // Imagine an abspos container with a scroller in it, and then an 1367 // anchor in it, where the anchor is visually in the middle of the 1368 // scrollport. Then, when the scroller moves such that the anchor's 1369 // left edge is on that of the scrollports, w.r.t. containing block, 1370 // the anchor is zero left offset horizontally. The position-area 1371 // grid needs to account for this. 1372 const auto scrolledAnchorRect = defaultAnchorInfo->mRect - offset; 1373 StylePositionArea resolvedPositionArea{}; 1374 const auto scrolledAnchorCb = AnchorPositioningUtils:: 1375 AdjustAbsoluteContainingBlockRectForPositionArea( 1376 scrolledAnchorRect + aOriginalContainingBlockRect.TopLeft(), 1377 containingBlock, aKidFrame->GetWritingMode(), 1378 aDelegatingFrame->GetWritingMode(), positionArea, 1379 &resolvedPositionArea); 1380 // By definition, we're using the default anchor, and are scroll 1381 // compensated. 1382 aAnchorPosResolutionCache->mReferenceData->mScrollCompensatedSides = 1383 GetScrollCompensatedSidesFor(resolvedPositionArea); 1384 return ContainingBlockRect{ 1385 offset, resolvedPositionArea, 1386 aOriginalScrollableContainingBlockRect, 1387 // Unscroll the CB by canceling out the previously applied 1388 // scroll offset (See above), the offset will be applied later. 1389 scrolledAnchorCb + offset}; 1390 } 1391 return ContainingBlockRect{aOriginalScrollableContainingBlockRect, 1392 containingBlock}; 1393 } 1394 1395 if (ViewportFrame* viewport = do_QueryFrame(aDelegatingFrame)) { 1396 if (!IsSnapshotContainingBlock(aKidFrame)) { 1397 return ContainingBlockRect{ 1398 viewport->GetContainingBlockAdjustedForScrollbars(aReflowInput)}; 1399 } 1400 return ContainingBlockRect{ 1401 dom::ViewTransition::SnapshotContainingBlockRect( 1402 viewport->PresContext())}; 1403 } 1404 return ContainingBlockRect{containingBlock}; 1405 }(); 1406 if (aAnchorPosResolutionCache) { 1407 const auto& originalCb = cb.mMaybeScrollableRect; 1408 aAnchorPosResolutionCache->mReferenceData->mOriginalContainingBlockRect = 1409 originalCb; 1410 // Stash the adjusted containing block as well, since the insets need to 1411 // resolve against the adjusted CB, e.g. With `position-area: bottom 1412 // right;`, + `left: anchor(right);` 1413 // resolves to 0. 1414 aAnchorPosResolutionCache->mReferenceData->mAdjustedContainingBlock = 1415 cb.mFinalRect; 1416 } 1417 const LogicalSize cbSize(outerWM, cb.mFinalRect.Size()); 1418 1419 ReflowInput::InitFlags initFlags; 1420 const bool staticPosIsCBOrigin = [&] { 1421 if (aFlags.contains(AbsPosReflowFlag::IsGridContainerCB)) { 1422 // When a grid container generates the abs.pos. CB for a *child* then 1423 // the static position is determined via CSS Box Alignment within the 1424 // abs.pos. CB (a grid area, i.e. a piece of the grid). In this 1425 // scenario, due to the multiple coordinate spaces in play, we use a 1426 // convenience flag to simply have the child's ReflowInput give it a 1427 // static position at its abs.pos. CB origin, and then we'll align & 1428 // offset it from there. 1429 nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame(); 1430 if (placeholder && placeholder->GetParent() == aDelegatingFrame) { 1431 return true; 1432 } 1433 } 1434 if (aKidFrame->IsMenuPopupFrame()) { 1435 // Popups never use their static pos. 1436 return true; 1437 } 1438 // TODO(emilio): Either reparent the top layer placeholder frames to the 1439 // viewport, or return true here for top layer frames more generally (not 1440 // only menupopups), see https://github.com/w3c/csswg-drafts/issues/8040. 1441 return false; 1442 }(); 1443 1444 if (staticPosIsCBOrigin) { 1445 initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin; 1446 } 1447 1448 const bool kidFrameMaySplit = 1449 aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE && 1450 1451 // Don't split if told not to (e.g. for fixed frames) 1452 aFlags.contains(AbsPosReflowFlag::AllowFragmentation) && 1453 1454 // XXX we don't handle splitting frames for inline absolute containing 1455 // blocks yet 1456 !aDelegatingFrame->IsInlineFrame() && 1457 1458 // Bug 1588623: Support splitting absolute positioned multicol 1459 // containers. 1460 !aKidFrame->IsColumnSetWrapperFrame() && 1461 1462 // Don't split things below the fold. (Ideally we shouldn't *have* 1463 // anything totally below the fold, but we can't position frames 1464 // across next-in-flow breaks yet. (Bug 1994346) 1465 (aKidFrame->GetLogicalRect(cb.mFinalRect.Size()).BStart(wm) <= 1466 aReflowInput.AvailableBSize()); 1467 1468 // Get the border values 1469 const LogicalMargin border = 1470 aDelegatingFrame->GetLogicalUsedBorder(outerWM).ApplySkipSides( 1471 aDelegatingFrame->PreReflowBlockLevelLogicalSkipSides()); 1472 1473 const nsIFrame* kidPrevInFlow = aKidFrame->GetPrevInFlow(); 1474 nscoord availBSize; 1475 if (kidFrameMaySplit) { 1476 availBSize = aReflowInput.AvailableBSize(); 1477 // If aKidFrame is a first-in-flow, we subtract our containing block's 1478 // border-block-start, to consider the available space as starting at the 1479 // containing block's padding-edge. 1480 // 1481 // If aKidFrame is *not* a first-in-flow, then we don't need to subtract 1482 // the containing block's border. Instead, we consider this whole fragment 1483 // as our available space, i.e., we allow abspos continuations to overlap 1484 // any border that their containing block parent might have (including 1485 // borders generated by 'box-decoration-break:clone'). 1486 if (!kidPrevInFlow) { 1487 availBSize -= border.BStart(outerWM); 1488 } 1489 } else { 1490 availBSize = NS_UNCONSTRAINEDSIZE; 1491 } 1492 const LogicalSize availSize(outerWM, cbSize.ISize(outerWM), availBSize); 1493 ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame, 1494 availSize.ConvertTo(wm, outerWM), 1495 Some(cbSize.ConvertTo(wm, outerWM)), initFlags, 1496 {}, {}, aAnchorPosResolutionCache); 1497 1498 // ReflowInput's constructor may change the available block-size to 1499 // unconstrained, e.g. in orthogonal reflow, so we retrieve it again and 1500 // account for kid's constraints in its own writing-mode if needed. 1501 if (!kidPrevInFlow) { 1502 nscoord kidAvailBSize = kidReflowInput.AvailableBSize(); 1503 if (kidAvailBSize != NS_UNCONSTRAINEDSIZE) { 1504 kidAvailBSize -= kidReflowInput.ComputedLogicalMargin(wm).BStart(wm); 1505 const nscoord kidOffsetBStart = 1506 kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm); 1507 if (kidOffsetBStart != NS_AUTOOFFSET) { 1508 kidAvailBSize -= kidOffsetBStart; 1509 } 1510 kidReflowInput.SetAvailableBSize(kidAvailBSize); 1511 } 1512 } 1513 1514 // Do the reflow 1515 ReflowOutput kidDesiredSize(kidReflowInput); 1516 aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus); 1517 1518 nsMargin insets; 1519 if (aKidFrame->IsMenuPopupFrame()) { 1520 // Do nothing. Popup frame will handle its own positioning. 1521 } else if (kidPrevInFlow) { 1522 // aKidFrame is a next-in-flow. Place it at the block-edge start of its 1523 // containing block, with the same inline-position as its prev-in-flow. 1524 const nsSize cbBorderBoxSize = 1525 (cbSize + border.Size(outerWM)).GetPhysicalSize(outerWM); 1526 const LogicalPoint kidPos( 1527 outerWM, kidPrevInFlow->IStart(outerWM, cbBorderBoxSize), 0); 1528 const LogicalSize kidSize = kidDesiredSize.Size(outerWM); 1529 const LogicalRect kidRect(outerWM, kidPos, kidSize); 1530 aKidFrame->SetRect(outerWM, kidRect, cbBorderBoxSize); 1531 } else { 1532 // Position the child relative to our padding edge. 1533 const LogicalSize kidSize = kidDesiredSize.Size(outerWM); 1534 1535 LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM); 1536 LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM); 1537 1538 // If we're doing CSS Box Alignment in either axis, that will apply the 1539 // margin for us in that axis (since the thing that's aligned is the 1540 // margin box). So, we clear out the margin here to avoid applying it 1541 // twice. 1542 if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) { 1543 margin.IStart(outerWM) = margin.IEnd(outerWM) = 0; 1544 } 1545 if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) { 1546 margin.BStart(outerWM) = margin.BEnd(outerWM) = 0; 1547 } 1548 1549 // If we're solving for start in either inline or block direction, 1550 // then compute it now that we know the dimensions. 1551 ResolveSizeDependentOffsets(kidReflowInput, cbSize, kidSize, margin, 1552 cb.ResolvedPositionArea(), offsets); 1553 1554 ResolveAutoMarginsAfterLayout(kidReflowInput, cbSize, kidSize, margin, 1555 offsets); 1556 1557 // If the inset is constrained as non-auto, we may have a child that does 1558 // not fill out the inset-reduced containing block. In this case, we need 1559 // to align the child by its margin box: 1560 // https://drafts.csswg.org/css-position-3/#abspos-layout 1561 const auto* stylePos = aKidFrame->StylePosition(); 1562 const auto anchorResolutionParams = 1563 AnchorPosOffsetResolutionParams::ExplicitCBFrameSize( 1564 AnchorPosResolutionParams::From(aKidFrame, 1565 aAnchorPosResolutionCache), 1566 &cbSize); 1567 const bool iStartInsetAuto = 1568 stylePos 1569 ->GetAnchorResolvedInset(LogicalSide::IStart, outerWM, 1570 anchorResolutionParams) 1571 ->IsAuto(); 1572 const bool iEndInsetAuto = 1573 stylePos 1574 ->GetAnchorResolvedInset(LogicalSide::IEnd, outerWM, 1575 anchorResolutionParams) 1576 ->IsAuto(); 1577 const bool iInsetAuto = iStartInsetAuto || iEndInsetAuto; 1578 1579 const bool bStartInsetAuto = 1580 stylePos 1581 ->GetAnchorResolvedInset(LogicalSide::BStart, outerWM, 1582 anchorResolutionParams) 1583 ->IsAuto(); 1584 const bool bEndInsetAuto = 1585 stylePos 1586 ->GetAnchorResolvedInset(LogicalSide::BEnd, outerWM, 1587 anchorResolutionParams) 1588 ->IsAuto(); 1589 const bool bInsetAuto = bStartInsetAuto || bEndInsetAuto; 1590 const LogicalSize kidMarginBox{ 1591 outerWM, margin.IStartEnd(outerWM) + kidSize.ISize(outerWM), 1592 margin.BStartEnd(outerWM) + kidSize.BSize(outerWM)}; 1593 const auto* placeholderContainer = 1594 GetPlaceholderContainer(kidReflowInput.mFrame); 1595 1596 insets = [&]() { 1597 auto result = offsets; 1598 // Zero out weaker insets, if one exists - This offset gets forced to 1599 // the margin edge of the child on that side, and for the purposes of 1600 // overflow checks, we consider them to be zero. 1601 if (iStartInsetAuto && !iEndInsetAuto) { 1602 result.IStart(outerWM) = 0; 1603 } else if (iInsetAuto) { 1604 result.IEnd(outerWM) = 0; 1605 } 1606 if (bStartInsetAuto && !bEndInsetAuto) { 1607 result.BStart(outerWM) = 0; 1608 } else if (bInsetAuto) { 1609 result.BEnd(outerWM) = 0; 1610 } 1611 return result.GetPhysicalMargin(outerWM); 1612 }(); 1613 if (aAnchorPosResolutionCache) { 1614 aAnchorPosResolutionCache->mReferenceData->mInsets = insets; 1615 } 1616 if (!iInsetAuto) { 1617 MOZ_ASSERT( 1618 !kidReflowInput.mFlags.mIOffsetsNeedCSSAlign, 1619 "Non-auto inline inset but requires CSS alignment for static " 1620 "position?"); 1621 auto alignOffset = OffsetToAlignedStaticPos( 1622 kidReflowInput, kidMarginBox, cbSize, placeholderContainer, outerWM, 1623 LogicalAxis::Inline, 1624 Some(NonAutoAlignParams{ 1625 offsets.IStart(outerWM), 1626 offsets.IEnd(outerWM), 1627 }), 1628 cb.ResolvedPositionArea()); 1629 1630 offsets.IStart(outerWM) += alignOffset; 1631 offsets.IEnd(outerWM) = 1632 cbSize.ISize(outerWM) - 1633 (offsets.IStart(outerWM) + kidMarginBox.ISize(outerWM)); 1634 } 1635 if (!bInsetAuto) { 1636 MOZ_ASSERT(!kidReflowInput.mFlags.mBOffsetsNeedCSSAlign, 1637 "Non-auto block inset but requires CSS alignment for static " 1638 "position?"); 1639 auto alignOffset = OffsetToAlignedStaticPos( 1640 kidReflowInput, kidMarginBox, cbSize, placeholderContainer, outerWM, 1641 LogicalAxis::Block, 1642 Some(NonAutoAlignParams{ 1643 offsets.BStart(outerWM), 1644 offsets.BEnd(outerWM), 1645 }), 1646 cb.ResolvedPositionArea()); 1647 offsets.BStart(outerWM) += alignOffset; 1648 offsets.BEnd(outerWM) = 1649 cbSize.BSize(outerWM) - 1650 (offsets.BStart(outerWM) + kidMarginBox.BSize(outerWM)); 1651 } 1652 1653 LogicalRect rect( 1654 outerWM, offsets.StartOffset(outerWM) + margin.StartOffset(outerWM), 1655 kidSize); 1656 nsRect r = rect.GetPhysicalRect(outerWM, cbSize.GetPhysicalSize(outerWM)); 1657 1658 // So far, we've positioned against the padding edge of the containing 1659 // block, which is necessary for inset computation. However, the position 1660 // of a frame originates against the border box. 1661 r += cb.mFinalRect.TopLeft(); 1662 1663 aKidFrame->SetRect(r); 1664 } 1665 1666 aKidFrame->DidReflow(aPresContext, &kidReflowInput); 1667 1668 [&]() { 1669 const auto rect = aKidFrame->GetRect(); 1670 if (!firstTryNormalRect) { 1671 firstTryNormalRect = Some(rect); 1672 } 1673 if (!aAnchorPosResolutionCache) { 1674 return; 1675 } 1676 auto* referenceData = aAnchorPosResolutionCache->mReferenceData; 1677 if (referenceData->CompensatingForScrollAxes().isEmpty()) { 1678 return; 1679 } 1680 // Now that all the anchor-related values are resolved, completing the 1681 // scroll compensation flag, compute the scroll offsets. 1682 const auto offset = [&]() { 1683 if (cb.mAnchorShiftInfo) { 1684 // Already resolved. 1685 return cb.mAnchorShiftInfo->mOffset; 1686 } 1687 return AnchorPositioningUtils::GetScrollOffsetFor( 1688 referenceData->CompensatingForScrollAxes(), aKidFrame, 1689 aAnchorPosResolutionCache->mDefaultAnchorCache); 1690 }(); 1691 // Apply the hypothetical scroll offset. 1692 // Set initial scroll position. TODO(dshin, bug 1987962): Need 1693 // additional work for remembered scroll offset here. 1694 aKidFrame->SetProperty(nsIFrame::NormalPositionProperty(), 1695 rect.TopLeft()); 1696 if (offset != nsPoint{}) { 1697 aKidFrame->SetPosition(rect.TopLeft() - offset); 1698 // Ensure that the positioned frame's overflow is updated. Absolutely 1699 // containing block's overflow will be updated shortly below. 1700 aKidFrame->UpdateOverflow(); 1701 } 1702 aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift = offset; 1703 }(); 1704 1705 const auto FitsInContainingBlock = [&]() { 1706 if (aAnchorPosResolutionCache) { 1707 return AnchorPositioningUtils::FitsInContainingBlock( 1708 aKidFrame, *aAnchorPosResolutionCache->mReferenceData); 1709 } 1710 auto imcbSize = cb.mFinalRect.Size(); 1711 imcbSize -= nsSize{insets.LeftRight(), insets.TopBottom()}; 1712 return aKidFrame->GetMarginRectRelativeToSelf().Size() <= imcbSize; 1713 }; 1714 1715 // FIXME(bug 2004495): Per spec this should be the inset-modified 1716 // containing-block, see: 1717 // https://drafts.csswg.org/css-anchor-position-1/#fallback-apply 1718 const auto fits = aStatus.IsComplete() && FitsInContainingBlock(); 1719 if (fallbacks.IsEmpty() || finalizing || 1720 (fits && (tryOrder == StylePositionTryOrder::Normal || 1721 currentFallbackIndex == firstTryIndex))) { 1722 // We completed the reflow - Either we had a fallback that fit, or we 1723 // didn't have any to try in the first place. 1724 isOverflowingCB = !fits; 1725 fallback.CommitCurrentFallback(); 1726 if (currentFallbackIndex == Nothing()) { 1727 aKidFrame->RemoveProperty(nsIFrame::LastSuccessfulPositionFallback()); 1728 } 1729 break; 1730 } 1731 1732 if (fits) { 1733 auto imcbSize = cb.mFinalRect.Size(); 1734 imcbSize -= nsSize{insets.LeftRight(), insets.TopBottom()}; 1735 switch (tryOrder) { 1736 case StylePositionTryOrder::MostWidth: 1737 if (imcbSize.Width() > bestSize) { 1738 bestSize = imcbSize.Width(); 1739 bestIndex = currentFallbackIndex; 1740 } 1741 break; 1742 case StylePositionTryOrder::MostHeight: 1743 if (imcbSize.Height() > bestSize) { 1744 bestSize = imcbSize.Height(); 1745 bestIndex = currentFallbackIndex; 1746 } 1747 break; 1748 default: 1749 MOZ_ASSERT_UNREACHABLE("unexpected try-order value"); 1750 break; 1751 } 1752 } 1753 1754 if (!TryAdvanceFallback()) { 1755 // If there are no further fallbacks, we're done. 1756 if (bestSize >= 0) { 1757 SeekFallbackTo(bestIndex); 1758 } else { 1759 // If we're going to roll back to the first try position, and the 1760 // target's size was different, we need to do a "finalizing" reflow 1761 // to ensure the inner layout is correct. If the size is unchanged, 1762 // we can just break the fallback loop now. 1763 if (isOverflowingCB && firstTryNormalRect && 1764 firstTryNormalRect->Size() != aKidFrame->GetSize()) { 1765 SeekFallbackTo(firstTryIndex); 1766 } else { 1767 break; 1768 } 1769 } 1770 // The fallback we've just selected is the final choice, regardless of 1771 // whether it overflows. 1772 finalizing = true; 1773 } 1774 1775 // Try with the next fallback. 1776 aKidFrame->AddStateBits(NS_FRAME_IS_DIRTY); 1777 aStatus.Reset(); 1778 } while (true); 1779 1780 [&]() { 1781 if (!isOverflowingCB || !firstTryNormalRect) { 1782 return; 1783 } 1784 // We gave up applying fallbacks. Recover previous values, if changed, and 1785 // reset currentFallbackIndex/Style to match. 1786 // Because we rolled back to first try data, our cache should be up-to-date. 1787 currentFallbackIndex = firstTryIndex; 1788 currentFallbackStyle = firstTryStyle; 1789 const auto normalRect = *firstTryNormalRect; 1790 const auto oldNormalPosition = aKidFrame->GetNormalPosition(); 1791 if (normalRect.TopLeft() != oldNormalPosition) { 1792 aKidFrame->SetProperty(nsIFrame::NormalPositionProperty(), 1793 normalRect.TopLeft()); 1794 } 1795 auto rect = normalRect; 1796 if (aAnchorPosResolutionCache) { 1797 rect.MoveBy( 1798 -aAnchorPosResolutionCache->mReferenceData->mDefaultScrollShift); 1799 } 1800 1801 if (isOverflowingCB && 1802 !aKidFrame->StylePosition()->mPositionArea.IsNone()) { 1803 // The anchored element overflows the IMCB of its position-area. Would it 1804 // have fit within the original CB? If so, shift it to stay within that. 1805 if (rect.width <= aOriginalContainingBlockRect.width && 1806 rect.height <= aOriginalContainingBlockRect.height) { 1807 if (rect.x < aOriginalContainingBlockRect.x) { 1808 rect.x = aOriginalContainingBlockRect.x; 1809 } else if (rect.XMost() > aOriginalContainingBlockRect.XMost()) { 1810 rect.x = aOriginalContainingBlockRect.XMost() - rect.width; 1811 } 1812 if (rect.y < aOriginalContainingBlockRect.y) { 1813 rect.y = aOriginalContainingBlockRect.y; 1814 } else if (rect.YMost() > aOriginalContainingBlockRect.YMost()) { 1815 rect.y = aOriginalContainingBlockRect.YMost() - rect.height; 1816 } 1817 } 1818 } 1819 1820 const auto oldPosition = aKidFrame->GetPosition(); 1821 if (rect.TopLeft() == oldPosition) { 1822 return; 1823 } 1824 aKidFrame->SetPosition(rect.TopLeft()); 1825 aKidFrame->UpdateOverflow(); 1826 }(); 1827 1828 if (currentFallbackIndex) { 1829 aKidFrame->SetOrUpdateDeletableProperty( 1830 nsIFrame::LastSuccessfulPositionFallback(), 1831 LastSuccessfulPositionData{currentFallbackStyle, *currentFallbackIndex, 1832 isOverflowingCB}); 1833 } 1834 1835 #ifdef DEBUG 1836 if (nsBlockFrame::gNoisyReflow) { 1837 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1); 1838 fmt::println(FMT_STRING("abspos {}: rect {}"), aKidFrame->ListTag().get(), 1839 ToString(aKidFrame->GetRect())); 1840 } 1841 #endif 1842 // If author asked for `position-visibility: no-overflow` and we overflow 1843 // `usedCB`, treat as "strongly hidden". Note that for anchored frames this 1844 // happens in ComputePositionVisibility. But no-overflow also applies to 1845 // non-anchored frames. 1846 if (!aAnchorPosResolutionCache) { 1847 aKidFrame->AddOrRemoveStateBits( 1848 NS_FRAME_POSITION_VISIBILITY_HIDDEN, 1849 isOverflowingCB && aKidFrame->StylePosition()->mPositionVisibility & 1850 StylePositionVisibility::NO_OVERFLOW); 1851 } 1852 1853 if (aOverflowAreas) { 1854 aOverflowAreas->UnionWith(aKidFrame->GetOverflowAreasRelativeToParent()); 1855 } 1856 }