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 "nsAbsoluteContainingBlock.h"
13
14 #include "nsContainerFrame.h"
15 #include "nsGkAtoms.h"
16 #include "mozilla/CSSAlignUtils.h"
17 #include "mozilla/PresShell.h"
18 #include "mozilla/ReflowInput.h"
19 #include "nsPlaceholderFrame.h"
20 #include "nsPresContext.h"
21 #include "nsCSSFrameConstructor.h"
22 #include "nsGridContainerFrame.h"
23
24 #include "mozilla/Sprintf.h"
25
26 #ifdef DEBUG
27 # include "nsBlockFrame.h"
28
PrettyUC(nscoord aSize,char * aBuf,int aBufSize)29 static void PrettyUC(nscoord aSize, char* aBuf, int aBufSize) {
30 if (NS_UNCONSTRAINEDSIZE == aSize) {
31 strcpy(aBuf, "UC");
32 } else {
33 if ((int32_t)0xdeadbeef == aSize) {
34 strcpy(aBuf, "deadbeef");
35 } else {
36 snprintf(aBuf, aBufSize, "%d", aSize);
37 }
38 }
39 }
40 #endif
41
42 using namespace mozilla;
43
44 typedef mozilla::CSSAlignUtils::AlignJustifyFlags AlignJustifyFlags;
45
SetInitialChildList(nsIFrame * aDelegatingFrame,ChildListID aListID,nsFrameList & aChildList)46 void nsAbsoluteContainingBlock::SetInitialChildList(nsIFrame* aDelegatingFrame,
47 ChildListID aListID,
48 nsFrameList& aChildList) {
49 MOZ_ASSERT(mChildListID == aListID, "unexpected child list name");
50 #ifdef DEBUG
51 nsIFrame::VerifyDirtyBitSet(aChildList);
52 for (nsIFrame* f : aChildList) {
53 MOZ_ASSERT(f->GetParent() == aDelegatingFrame, "Unexpected parent");
54 }
55 #endif
56 mAbsoluteFrames.SetFrames(aChildList);
57 }
58
AppendFrames(nsIFrame * aDelegatingFrame,ChildListID aListID,nsFrameList & aFrameList)59 void nsAbsoluteContainingBlock::AppendFrames(nsIFrame* aDelegatingFrame,
60 ChildListID aListID,
61 nsFrameList& aFrameList) {
62 NS_ASSERTION(mChildListID == aListID, "unexpected child list");
63
64 // Append the frames to our list of absolutely positioned frames
65 #ifdef DEBUG
66 nsIFrame::VerifyDirtyBitSet(aFrameList);
67 #endif
68 mAbsoluteFrames.AppendFrames(nullptr, aFrameList);
69
70 // no damage to intrinsic widths, since absolutely positioned frames can't
71 // change them
72 aDelegatingFrame->PresShell()->FrameNeedsReflow(
73 aDelegatingFrame, IntrinsicDirty::Resize, NS_FRAME_HAS_DIRTY_CHILDREN);
74 }
75
InsertFrames(nsIFrame * aDelegatingFrame,ChildListID aListID,nsIFrame * aPrevFrame,nsFrameList & aFrameList)76 void nsAbsoluteContainingBlock::InsertFrames(nsIFrame* aDelegatingFrame,
77 ChildListID aListID,
78 nsIFrame* aPrevFrame,
79 nsFrameList& aFrameList) {
80 NS_ASSERTION(mChildListID == aListID, "unexpected child list");
81 NS_ASSERTION(!aPrevFrame || aPrevFrame->GetParent() == aDelegatingFrame,
82 "inserting after sibling frame with different parent");
83
84 #ifdef DEBUG
85 nsIFrame::VerifyDirtyBitSet(aFrameList);
86 #endif
87 mAbsoluteFrames.InsertFrames(nullptr, aPrevFrame, aFrameList);
88
89 // no damage to intrinsic widths, since absolutely positioned frames can't
90 // change them
91 aDelegatingFrame->PresShell()->FrameNeedsReflow(
92 aDelegatingFrame, IntrinsicDirty::Resize, NS_FRAME_HAS_DIRTY_CHILDREN);
93 }
94
RemoveFrame(nsIFrame * aDelegatingFrame,ChildListID aListID,nsIFrame * aOldFrame)95 void nsAbsoluteContainingBlock::RemoveFrame(nsIFrame* aDelegatingFrame,
96 ChildListID aListID,
97 nsIFrame* aOldFrame) {
98 NS_ASSERTION(mChildListID == aListID, "unexpected child list");
99 nsIFrame* nif = aOldFrame->GetNextInFlow();
100 if (nif) {
101 nif->GetParent()->DeleteNextInFlowChild(nif, false);
102 }
103
104 mAbsoluteFrames.DestroyFrame(aOldFrame);
105 }
106
MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(nsIFrame * aFrame,nsIFrame * aContainingBlockFrame)107 static void MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
108 nsIFrame* aFrame, nsIFrame* aContainingBlockFrame) {
109 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
110 if (!aFrame->StylePosition()->NeedsHypotheticalPositionIfAbsPos()) {
111 return;
112 }
113 // We should have set the bit when reflowing the previous continuations
114 // already.
115 if (aFrame->GetPrevContinuation()) {
116 return;
117 }
118
119 auto* placeholder = aFrame->GetPlaceholderFrame();
120 MOZ_ASSERT(placeholder);
121
122 // Only fixed-pos frames can escape their containing block.
123 if (!placeholder->HasAnyStateBits(PLACEHOLDER_FOR_FIXEDPOS)) {
124 return;
125 }
126
127 for (nsIFrame* ancestor = placeholder->GetParent(); ancestor;
128 ancestor = ancestor->GetParent()) {
129 // Walk towards the ancestor's first continuation. That's the only one that
130 // really matters, since it's the only one restyling will look at. We also
131 // flag the following continuations just so it's caught on the first
132 // early-return ones just to avoid walking them over and over.
133 do {
134 if (ancestor->DescendantMayDependOnItsStaticPosition()) {
135 return;
136 }
137 // Moving the containing block or anything above it would move our static
138 // position as well, so no need to flag it or any of its ancestors.
139 if (aFrame == aContainingBlockFrame) {
140 return;
141 }
142 ancestor->SetDescendantMayDependOnItsStaticPosition(true);
143 nsIFrame* prev = ancestor->GetPrevContinuation();
144 if (!prev) {
145 break;
146 }
147 ancestor = prev;
148 } while (true);
149 }
150 }
151
Reflow(nsContainerFrame * aDelegatingFrame,nsPresContext * aPresContext,const ReflowInput & aReflowInput,nsReflowStatus & aReflowStatus,const nsRect & aContainingBlock,AbsPosReflowFlags aFlags,OverflowAreas * aOverflowAreas)152 void nsAbsoluteContainingBlock::Reflow(nsContainerFrame* aDelegatingFrame,
153 nsPresContext* aPresContext,
154 const ReflowInput& aReflowInput,
155 nsReflowStatus& aReflowStatus,
156 const nsRect& aContainingBlock,
157 AbsPosReflowFlags aFlags,
158 OverflowAreas* aOverflowAreas) {
159 // PageContentFrame replicates fixed pos children so we really don't want
160 // them contributing to overflow areas because that means we'll create new
161 // pages ad infinitum if one of them overflows the page.
162 if (aDelegatingFrame->IsPageContentFrame()) {
163 MOZ_ASSERT(mChildListID == nsAtomicContainerFrame::kFixedList);
164 aOverflowAreas = nullptr;
165 }
166
167 nsReflowStatus reflowStatus;
168 const bool reflowAll = aReflowInput.ShouldReflowAllKids();
169 const bool isGrid = !!(aFlags & AbsPosReflowFlags::IsGridContainerCB);
170 nsIFrame* kidFrame;
171 nsOverflowContinuationTracker tracker(aDelegatingFrame, true);
172 for (kidFrame = mAbsoluteFrames.FirstChild(); kidFrame;
173 kidFrame = kidFrame->GetNextSibling()) {
174 bool kidNeedsReflow =
175 reflowAll || kidFrame->IsSubtreeDirty() ||
176 FrameDependsOnContainer(
177 kidFrame, !!(aFlags & AbsPosReflowFlags::CBWidthChanged),
178 !!(aFlags & AbsPosReflowFlags::CBHeightChanged));
179
180 if (kidFrame->IsSubtreeDirty()) {
181 MaybeMarkAncestorsAsHavingDescendantDependentOnItsStaticPos(
182 kidFrame, aDelegatingFrame);
183 }
184
185 nscoord availBSize = aReflowInput.AvailableBSize();
186 const nsRect& cb =
187 isGrid ? nsGridContainerFrame::GridItemCB(kidFrame) : aContainingBlock;
188 WritingMode containerWM = aReflowInput.GetWritingMode();
189 if (!kidNeedsReflow && availBSize != NS_UNCONSTRAINEDSIZE) {
190 // If we need to redo pagination on the kid, we need to reflow it.
191 // This can happen either if the available height shrunk and the
192 // kid (or its overflow that creates overflow containers) is now
193 // too large to fit in the available height, or if the available
194 // height has increased and the kid has a next-in-flow that we
195 // might need to pull from.
196 WritingMode kidWM = kidFrame->GetWritingMode();
197 if (containerWM.GetBlockDir() != kidWM.GetBlockDir()) {
198 // Not sure what the right test would be here.
199 kidNeedsReflow = true;
200 } else {
201 nscoord kidBEnd = kidFrame->GetLogicalRect(cb.Size()).BEnd(kidWM);
202 nscoord kidOverflowBEnd =
203 LogicalRect(containerWM,
204 // Use ...RelativeToSelf to ignore transforms
205 kidFrame->ScrollableOverflowRectRelativeToSelf() +
206 kidFrame->GetPosition(),
207 aContainingBlock.Size())
208 .BEnd(containerWM);
209 NS_ASSERTION(kidOverflowBEnd >= kidBEnd,
210 "overflow area should be at least as large as frame rect");
211 if (kidOverflowBEnd > availBSize ||
212 (kidBEnd < availBSize && kidFrame->GetNextInFlow())) {
213 kidNeedsReflow = true;
214 }
215 }
216 }
217 if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
218 // Reflow the frame
219 nsReflowStatus kidStatus;
220 ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
221 aFlags, kidFrame, kidStatus, aOverflowAreas);
222 MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
223 "ShouldAvoidBreakInside should prevent this from happening");
224 nsIFrame* nextFrame = kidFrame->GetNextInFlow();
225 if (!kidStatus.IsFullyComplete() &&
226 aDelegatingFrame->IsFrameOfType(
227 nsIFrame::eCanContainOverflowContainers)) {
228 // Need a continuation
229 if (!nextFrame) {
230 nextFrame = aPresContext->PresShell()
231 ->FrameConstructor()
232 ->CreateContinuingFrame(kidFrame, aDelegatingFrame);
233 }
234 // Add it as an overflow container.
235 // XXXfr This is a hack to fix some of our printing dataloss.
236 // See bug 154892. Not sure how to do it "right" yet; probably want
237 // to keep continuations within an nsAbsoluteContainingBlock eventually.
238 tracker.Insert(nextFrame, kidStatus);
239 reflowStatus.MergeCompletionStatusFrom(kidStatus);
240 } else {
241 // Delete any continuations
242 if (nextFrame) {
243 nsOverflowContinuationTracker::AutoFinish fini(&tracker, kidFrame);
244 nextFrame->GetParent()->DeleteNextInFlowChild(nextFrame, true);
245 }
246 }
247 } else {
248 tracker.Skip(kidFrame, reflowStatus);
249 if (aOverflowAreas) {
250 aDelegatingFrame->ConsiderChildOverflow(*aOverflowAreas, kidFrame);
251 }
252 }
253
254 // Make a CheckForInterrupt call, here, not just HasPendingInterrupt. That
255 // will make sure that we end up reflowing aDelegatingFrame in cases when
256 // one of our kids interrupted. Otherwise we'd set the dirty or
257 // dirty-children bit on the kid in the condition below, and then when
258 // reflow completes and we go to mark dirty bits on all ancestors of that
259 // kid we'll immediately bail out, because the kid already has a dirty bit.
260 // In particular, we won't set any dirty bits on aDelegatingFrame, so when
261 // the following reflow happens we won't reflow the kid in question. This
262 // might be slightly suboptimal in cases where |kidFrame| itself did not
263 // interrupt, since we'll trigger a reflow of it too when it's not strictly
264 // needed. But the logic to not do that is enough more complicated, and
265 // the case enough of an edge case, that this is probably better.
266 if (kidNeedsReflow && aPresContext->CheckForInterrupt(aDelegatingFrame)) {
267 if (aDelegatingFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
268 kidFrame->MarkSubtreeDirty();
269 } else {
270 kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
271 }
272 }
273 }
274
275 // Abspos frames can't cause their parent to be incomplete,
276 // only overflow incomplete.
277 if (reflowStatus.IsIncomplete()) reflowStatus.SetOverflowIncomplete();
278
279 aReflowStatus.MergeCompletionStatusFrom(reflowStatus);
280 }
281
IsFixedPaddingSize(const LengthPercentage & aCoord)282 static inline bool IsFixedPaddingSize(const LengthPercentage& aCoord) {
283 return aCoord.ConvertsToLength();
284 }
IsFixedMarginSize(const LengthPercentageOrAuto & aCoord)285 static inline bool IsFixedMarginSize(const LengthPercentageOrAuto& aCoord) {
286 return aCoord.ConvertsToLength();
287 }
IsFixedOffset(const LengthPercentageOrAuto & aCoord)288 static inline bool IsFixedOffset(const LengthPercentageOrAuto& aCoord) {
289 return aCoord.ConvertsToLength();
290 }
291
FrameDependsOnContainer(nsIFrame * f,bool aCBWidthChanged,bool aCBHeightChanged)292 bool nsAbsoluteContainingBlock::FrameDependsOnContainer(nsIFrame* f,
293 bool aCBWidthChanged,
294 bool aCBHeightChanged) {
295 const nsStylePosition* pos = f->StylePosition();
296 // See if f's position might have changed because it depends on a
297 // placeholder's position.
298 if (pos->NeedsHypotheticalPositionIfAbsPos()) {
299 return true;
300 }
301 if (!aCBWidthChanged && !aCBHeightChanged) {
302 // skip getting style data
303 return false;
304 }
305 const nsStylePadding* padding = f->StylePadding();
306 const nsStyleMargin* margin = f->StyleMargin();
307 WritingMode wm = f->GetWritingMode();
308 if (wm.IsVertical() ? aCBHeightChanged : aCBWidthChanged) {
309 // See if f's inline-size might have changed.
310 // If margin-inline-start/end, padding-inline-start/end,
311 // inline-size, min/max-inline-size are all lengths, 'none', or enumerated,
312 // then our frame isize does not depend on the parent isize.
313 // Note that borders never depend on the parent isize.
314 // XXX All of the enumerated values except -moz-available are ok too.
315 if (pos->ISizeDependsOnContainer(wm) ||
316 pos->MinISizeDependsOnContainer(wm) ||
317 pos->MaxISizeDependsOnContainer(wm) ||
318 !IsFixedPaddingSize(padding->mPadding.GetIStart(wm)) ||
319 !IsFixedPaddingSize(padding->mPadding.GetIEnd(wm))) {
320 return true;
321 }
322
323 // See if f's position might have changed. If we're RTL then the
324 // rules are slightly different. We'll assume percentage or auto
325 // margins will always induce a dependency on the size
326 if (!IsFixedMarginSize(margin->mMargin.GetIStart(wm)) ||
327 !IsFixedMarginSize(margin->mMargin.GetIEnd(wm))) {
328 return true;
329 }
330 }
331 if (wm.IsVertical() ? aCBWidthChanged : aCBHeightChanged) {
332 // See if f's block-size might have changed.
333 // If margin-block-start/end, padding-block-start/end,
334 // min-block-size, and max-block-size are all lengths or 'none',
335 // and bsize is a length or bsize and bend are auto and bstart is not auto,
336 // then our frame bsize does not depend on the parent bsize.
337 // Note that borders never depend on the parent bsize.
338 //
339 // FIXME(emilio): Should the BSize(wm).IsAuto() check also for the extremum
340 // lengths?
341 if ((pos->BSizeDependsOnContainer(wm) &&
342 !(pos->BSize(wm).IsAuto() && pos->mOffset.GetBEnd(wm).IsAuto() &&
343 !pos->mOffset.GetBStart(wm).IsAuto())) ||
344 pos->MinBSizeDependsOnContainer(wm) ||
345 pos->MaxBSizeDependsOnContainer(wm) ||
346 !IsFixedPaddingSize(padding->mPadding.GetBStart(wm)) ||
347 !IsFixedPaddingSize(padding->mPadding.GetBEnd(wm))) {
348 return true;
349 }
350
351 // See if f's position might have changed.
352 if (!IsFixedMarginSize(margin->mMargin.GetBStart(wm)) ||
353 !IsFixedMarginSize(margin->mMargin.GetBEnd(wm))) {
354 return true;
355 }
356 }
357
358 // Since we store coordinates relative to top and left, the position
359 // of a frame depends on that of its container if it is fixed relative
360 // to the right or bottom, or if it is positioned using percentages
361 // relative to the left or top. Because of the dependency on the
362 // sides (left and top) that we use to store coordinates, these tests
363 // are easier to do using physical coordinates rather than logical.
364 if (aCBWidthChanged) {
365 if (!IsFixedOffset(pos->mOffset.Get(eSideLeft))) {
366 return true;
367 }
368 // Note that even if 'left' is a length, our position can still
369 // depend on the containing block width, because if our direction or
370 // writing-mode moves from right to left (in either block or inline
371 // progression) and 'right' is not 'auto', we will discard 'left'
372 // and be positioned relative to the containing block right edge.
373 // 'left' length and 'right' auto is the only combination we can be
374 // sure of.
375 if ((wm.GetInlineDir() == WritingMode::eInlineRTL ||
376 wm.GetBlockDir() == WritingMode::eBlockRL) &&
377 !pos->mOffset.Get(eSideRight).IsAuto()) {
378 return true;
379 }
380 }
381 if (aCBHeightChanged) {
382 if (!IsFixedOffset(pos->mOffset.Get(eSideTop))) {
383 return true;
384 }
385 // See comment above for width changes.
386 if (wm.GetInlineDir() == WritingMode::eInlineBTT &&
387 !pos->mOffset.Get(eSideBottom).IsAuto()) {
388 return true;
389 }
390 }
391
392 return false;
393 }
394
DestroyFrames(nsIFrame * aDelegatingFrame,nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)395 void nsAbsoluteContainingBlock::DestroyFrames(
396 nsIFrame* aDelegatingFrame, nsIFrame* aDestructRoot,
397 PostDestroyData& aPostDestroyData) {
398 mAbsoluteFrames.DestroyFramesFrom(aDestructRoot, aPostDestroyData);
399 }
400
MarkSizeDependentFramesDirty()401 void nsAbsoluteContainingBlock::MarkSizeDependentFramesDirty() {
402 DoMarkFramesDirty(false);
403 }
404
MarkAllFramesDirty()405 void nsAbsoluteContainingBlock::MarkAllFramesDirty() {
406 DoMarkFramesDirty(true);
407 }
408
DoMarkFramesDirty(bool aMarkAllDirty)409 void nsAbsoluteContainingBlock::DoMarkFramesDirty(bool aMarkAllDirty) {
410 for (nsIFrame* kidFrame : mAbsoluteFrames) {
411 if (aMarkAllDirty) {
412 kidFrame->MarkSubtreeDirty();
413 } else if (FrameDependsOnContainer(kidFrame, true, true)) {
414 // Add the weakest flags that will make sure we reflow this frame later
415 kidFrame->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
416 }
417 }
418 }
419
420 // Given an out-of-flow frame, this method returns the parent frame of its
421 // placeholder frame or null if it doesn't have a placeholder for some reason.
GetPlaceholderContainer(nsIFrame * aPositionedFrame)422 static nsContainerFrame* GetPlaceholderContainer(nsIFrame* aPositionedFrame) {
423 nsIFrame* placeholder = aPositionedFrame->GetPlaceholderFrame();
424 return placeholder ? placeholder->GetParent() : nullptr;
425 }
426
427 /**
428 * This function returns the offset of an abs/fixed-pos child's static
429 * position, with respect to the "start" corner of its alignment container,
430 * according to CSS Box Alignment. This function only operates in a single
431 * axis at a time -- callers can choose which axis via the |aAbsPosCBAxis|
432 * parameter.
433 *
434 * @param aKidReflowInput The ReflowInput for the to-be-aligned abspos child.
435 * @param aKidSizeInAbsPosCBWM The child frame's size (after it's been given
436 * the opportunity to reflow), in terms of
437 * aAbsPosCBWM.
438 * @param aAbsPosCBSize The abspos CB size, in terms of aAbsPosCBWM.
439 * @param aPlaceholderContainer The parent of the child frame's corresponding
440 * placeholder frame, cast to a nsContainerFrame.
441 * (This will help us choose which alignment enum
442 * we should use for the child.)
443 * @param aAbsPosCBWM The child frame's containing block's WritingMode.
444 * @param aAbsPosCBAxis The axis (of the containing block) that we should
445 * be doing this computation for.
446 */
OffsetToAlignedStaticPos(const ReflowInput & aKidReflowInput,const LogicalSize & aKidSizeInAbsPosCBWM,const LogicalSize & aAbsPosCBSize,nsContainerFrame * aPlaceholderContainer,WritingMode aAbsPosCBWM,LogicalAxis aAbsPosCBAxis)447 static nscoord OffsetToAlignedStaticPos(const ReflowInput& aKidReflowInput,
448 const LogicalSize& aKidSizeInAbsPosCBWM,
449 const LogicalSize& aAbsPosCBSize,
450 nsContainerFrame* aPlaceholderContainer,
451 WritingMode aAbsPosCBWM,
452 LogicalAxis aAbsPosCBAxis) {
453 if (!aPlaceholderContainer) {
454 // (The placeholder container should be the thing that kicks this whole
455 // process off, by setting PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN. So it
456 // should exist... but bail gracefully if it doesn't.)
457 NS_ERROR(
458 "Missing placeholder-container when computing a "
459 "CSS Box Alignment static position");
460 return 0;
461 }
462
463 // (Most of this function is simply preparing args that we'll pass to
464 // AlignJustifySelf at the end.)
465
466 // NOTE: Our alignment container is aPlaceholderContainer's content-box
467 // (or an area within it, if aPlaceholderContainer is a grid). So, we'll
468 // perform most of our arithmetic/alignment in aPlaceholderContainer's
469 // WritingMode. For brevity, we use the abbreviation "pc" for "placeholder
470 // container" in variables below.
471 WritingMode pcWM = aPlaceholderContainer->GetWritingMode();
472
473 // Find what axis aAbsPosCBAxis corresponds to, in placeholder's parent's
474 // writing-mode.
475 LogicalAxis pcAxis =
476 (pcWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
477 : aAbsPosCBAxis);
478
479 const bool placeholderContainerIsContainingBlock =
480 aPlaceholderContainer == aKidReflowInput.mCBReflowInput->mFrame;
481
482 LayoutFrameType parentType = aPlaceholderContainer->Type();
483 LogicalSize alignAreaSize(pcWM);
484 if (parentType == LayoutFrameType::FlexContainer) {
485 // We store the frame rect in FinishAndStoreOverflow, which runs _after_
486 // reflowing the absolute frames, so handle the special case of the frame
487 // being the actual containing block here, by getting the size from
488 // aAbsPosCBSize.
489 //
490 // The alignment container is the flex container's content box.
491 if (placeholderContainerIsContainingBlock) {
492 alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
493 // aAbsPosCBSize is the padding-box, so substract the padding to get the
494 // content box.
495 alignAreaSize -=
496 aPlaceholderContainer->GetLogicalUsedPadding(pcWM).Size(pcWM);
497 } else {
498 alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
499 LogicalMargin pcBorderPadding =
500 aPlaceholderContainer->GetLogicalUsedBorderAndPadding(pcWM);
501 alignAreaSize -= pcBorderPadding.Size(pcWM);
502 }
503 } else if (parentType == LayoutFrameType::GridContainer) {
504 // This abspos elem's parent is a grid container. Per CSS Grid 10.1 & 10.2:
505 // - If the grid container *also* generates the abspos containing block (a
506 // grid area) for this abspos child, we use that abspos containing block as
507 // the alignment container, too. (And its size is aAbsPosCBSize.)
508 // - Otherwise, we use the grid's padding box as the alignment container.
509 // https://drafts.csswg.org/css-grid/#static-position
510 if (placeholderContainerIsContainingBlock) {
511 // The alignment container is the grid area that we're using as the
512 // absolute containing block.
513 alignAreaSize = aAbsPosCBSize.ConvertTo(pcWM, aAbsPosCBWM);
514 } else {
515 // The alignment container is a the grid container's padding box (which
516 // we can get by subtracting away its border from frame's size):
517 alignAreaSize = aPlaceholderContainer->GetLogicalSize(pcWM);
518 LogicalMargin pcBorder =
519 aPlaceholderContainer->GetLogicalUsedBorder(pcWM);
520 alignAreaSize -= pcBorder.Size(pcWM);
521 }
522 } else {
523 NS_ERROR("Unsupported container for abpsos CSS Box Alignment");
524 return 0; // (leave the child at the start of its alignment container)
525 }
526
527 nscoord alignAreaSizeInAxis = (pcAxis == eLogicalAxisInline)
528 ? alignAreaSize.ISize(pcWM)
529 : alignAreaSize.BSize(pcWM);
530
531 AlignJustifyFlags flags = AlignJustifyFlags::IgnoreAutoMargins;
532 StyleAlignFlags alignConst =
533 aPlaceholderContainer->CSSAlignmentForAbsPosChild(aKidReflowInput,
534 pcAxis);
535 // If the safe bit in alignConst is set, set the safe flag in |flags|.
536 // Note: If no <overflow-position> is specified, we behave as 'unsafe'.
537 // This doesn't quite match the css-align spec, which has an [at-risk]
538 // "smart default" behavior with some extra nuance about scroll containers.
539 if (alignConst & StyleAlignFlags::SAFE) {
540 flags |= AlignJustifyFlags::OverflowSafe;
541 }
542 alignConst &= ~StyleAlignFlags::FLAG_BITS;
543
544 // Find out if placeholder-container & the OOF child have the same start-sides
545 // in the placeholder-container's pcAxis.
546 WritingMode kidWM = aKidReflowInput.GetWritingMode();
547 if (pcWM.ParallelAxisStartsOnSameSide(pcAxis, kidWM)) {
548 flags |= AlignJustifyFlags::SameSide;
549 }
550
551 // (baselineAdjust is unused. CSSAlignmentForAbsPosChild() should've
552 // converted 'baseline'/'last baseline' enums to their fallback values.)
553 const nscoord baselineAdjust = nscoord(0);
554
555 // AlignJustifySelf operates in the kid's writing mode, so we need to
556 // represent the child's size and the desired axis in that writing mode:
557 LogicalSize kidSizeInOwnWM =
558 aKidSizeInAbsPosCBWM.ConvertTo(kidWM, aAbsPosCBWM);
559 LogicalAxis kidAxis =
560 (kidWM.IsOrthogonalTo(aAbsPosCBWM) ? GetOrthogonalAxis(aAbsPosCBAxis)
561 : aAbsPosCBAxis);
562
563 nscoord offset = CSSAlignUtils::AlignJustifySelf(
564 alignConst, kidAxis, flags, baselineAdjust, alignAreaSizeInAxis,
565 aKidReflowInput, kidSizeInOwnWM);
566
567 // "offset" is in terms of the CSS Box Alignment container (i.e. it's in
568 // terms of pcWM). But our return value needs to in terms of the containing
569 // block's writing mode, which might have the opposite directionality in the
570 // given axis. In that case, we just need to negate "offset" when returning,
571 // to make it have the right effect as an offset for coordinates in the
572 // containing block's writing mode.
573 if (!pcWM.ParallelAxisStartsOnSameSide(pcAxis, aAbsPosCBWM)) {
574 return -offset;
575 }
576 return offset;
577 }
578
ResolveSizeDependentOffsets(nsPresContext * aPresContext,ReflowInput & aKidReflowInput,const LogicalSize & aKidSize,const LogicalMargin & aMargin,LogicalMargin * aOffsets,LogicalSize * aLogicalCBSize)579 void nsAbsoluteContainingBlock::ResolveSizeDependentOffsets(
580 nsPresContext* aPresContext, ReflowInput& aKidReflowInput,
581 const LogicalSize& aKidSize, const LogicalMargin& aMargin,
582 LogicalMargin* aOffsets, LogicalSize* aLogicalCBSize) {
583 WritingMode wm = aKidReflowInput.GetWritingMode();
584 WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
585
586 // Now that we know the child's size, we resolve any sentinel values in its
587 // IStart/BStart offset coordinates that depend on that size.
588 // * NS_AUTOOFFSET indicates that the child's position in the given axis
589 // is determined by its end-wards offset property, combined with its size and
590 // available space. e.g.: "top: auto; height: auto; bottom: 50px"
591 // * m{I,B}OffsetsResolvedAfterSize indicate that the child is using its
592 // static position in that axis, *and* its static position is determined by
593 // the axis-appropriate css-align property (which may require the child's
594 // size, e.g. to center it within the parent).
595 if ((NS_AUTOOFFSET == aOffsets->IStart(outerWM)) ||
596 (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) ||
597 aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign ||
598 aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
599 if (-1 == aLogicalCBSize->ISize(wm)) {
600 // Get the containing block width/height
601 const ReflowInput* parentRI = aKidReflowInput.mParentReflowInput;
602 *aLogicalCBSize = aKidReflowInput.ComputeContainingBlockRectangle(
603 aPresContext, parentRI);
604 }
605
606 const LogicalSize logicalCBSizeOuterWM =
607 aLogicalCBSize->ConvertTo(outerWM, wm);
608
609 // placeholderContainer is used in each of the m{I,B}OffsetsNeedCSSAlign
610 // clauses. We declare it at this scope so we can avoid having to look
611 // it up twice (and only look it up if it's needed).
612 nsContainerFrame* placeholderContainer = nullptr;
613
614 if (NS_AUTOOFFSET == aOffsets->IStart(outerWM)) {
615 NS_ASSERTION(NS_AUTOOFFSET != aOffsets->IEnd(outerWM),
616 "Can't solve for both start and end");
617 aOffsets->IStart(outerWM) =
618 logicalCBSizeOuterWM.ISize(outerWM) - aOffsets->IEnd(outerWM) -
619 aMargin.IStartEnd(outerWM) - aKidSize.ISize(outerWM);
620 } else if (aKidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
621 placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
622 nscoord offset = OffsetToAlignedStaticPos(
623 aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
624 outerWM, eLogicalAxisInline);
625 // Shift IStart from its current position (at start corner of the
626 // alignment container) by the returned offset. And set IEnd to the
627 // distance between the kid's end edge to containing block's end edge.
628 aOffsets->IStart(outerWM) += offset;
629 aOffsets->IEnd(outerWM) =
630 logicalCBSizeOuterWM.ISize(outerWM) -
631 (aOffsets->IStart(outerWM) + aKidSize.ISize(outerWM));
632 }
633
634 if (NS_AUTOOFFSET == aOffsets->BStart(outerWM)) {
635 aOffsets->BStart(outerWM) =
636 logicalCBSizeOuterWM.BSize(outerWM) - aOffsets->BEnd(outerWM) -
637 aMargin.BStartEnd(outerWM) - aKidSize.BSize(outerWM);
638 } else if (aKidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
639 if (!placeholderContainer) {
640 placeholderContainer = GetPlaceholderContainer(aKidReflowInput.mFrame);
641 }
642 nscoord offset = OffsetToAlignedStaticPos(
643 aKidReflowInput, aKidSize, logicalCBSizeOuterWM, placeholderContainer,
644 outerWM, eLogicalAxisBlock);
645 // Shift BStart from its current position (at start corner of the
646 // alignment container) by the returned offset. And set BEnd to the
647 // distance between the kid's end edge to containing block's end edge.
648 aOffsets->BStart(outerWM) += offset;
649 aOffsets->BEnd(outerWM) =
650 logicalCBSizeOuterWM.BSize(outerWM) -
651 (aOffsets->BStart(outerWM) + aKidSize.BSize(outerWM));
652 }
653 aKidReflowInput.SetComputedLogicalOffsets(outerWM, *aOffsets);
654 }
655 }
656
ResolveAutoMarginsAfterLayout(ReflowInput & aKidReflowInput,const LogicalSize * aLogicalCBSize,const LogicalSize & aKidSize,LogicalMargin & aMargin,LogicalMargin & aOffsets)657 void nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout(
658 ReflowInput& aKidReflowInput, const LogicalSize* aLogicalCBSize,
659 const LogicalSize& aKidSize, LogicalMargin& aMargin,
660 LogicalMargin& aOffsets) {
661 MOZ_ASSERT(aKidReflowInput.mFrame->HasIntrinsicKeywordForBSize());
662
663 WritingMode wm = aKidReflowInput.GetWritingMode();
664 WritingMode outerWM = aKidReflowInput.mParentReflowInput->GetWritingMode();
665
666 const LogicalSize kidSizeInWM = aKidSize.ConvertTo(wm, outerWM);
667 LogicalMargin marginInWM = aMargin.ConvertTo(wm, outerWM);
668 LogicalMargin offsetsInWM = aOffsets.ConvertTo(wm, outerWM);
669
670 // No need to substract border sizes because aKidSize has it included
671 // already
672 nscoord availMarginSpace = aLogicalCBSize->BSize(wm) - kidSizeInWM.BSize(wm) -
673 offsetsInWM.BStartEnd(wm) -
674 marginInWM.BStartEnd(wm);
675
676 if (availMarginSpace < 0) {
677 availMarginSpace = 0;
678 }
679
680 const auto& styleMargin = aKidReflowInput.mStyleMargin;
681 if (wm.IsOrthogonalTo(outerWM)) {
682 ReflowInput::ComputeAbsPosInlineAutoMargin(
683 availMarginSpace, outerWM,
684 styleMargin->mMargin.GetIStart(outerWM).IsAuto(),
685 styleMargin->mMargin.GetIEnd(outerWM).IsAuto(), aMargin, aOffsets);
686 } else {
687 ReflowInput::ComputeAbsPosBlockAutoMargin(
688 availMarginSpace, outerWM,
689 styleMargin->mMargin.GetBStart(outerWM).IsAuto(),
690 styleMargin->mMargin.GetBEnd(outerWM).IsAuto(), aMargin, aOffsets);
691 }
692
693 aKidReflowInput.SetComputedLogicalMargin(outerWM, aMargin);
694 aKidReflowInput.SetComputedLogicalOffsets(outerWM, aOffsets);
695
696 nsMargin* propValue =
697 aKidReflowInput.mFrame->GetProperty(nsIFrame::UsedMarginProperty());
698 // InitOffsets should've created a UsedMarginProperty for us, if any margin is
699 // auto.
700 MOZ_ASSERT_IF(styleMargin->HasInlineAxisAuto(outerWM) ||
701 styleMargin->HasBlockAxisAuto(outerWM),
702 propValue);
703 if (propValue) {
704 *propValue = aMargin.GetPhysicalMargin(outerWM);
705 }
706 }
707
708 // XXX Optimize the case where it's a resize reflow and the absolutely
709 // positioned child has the exact same size and position and skip the
710 // reflow...
711
712 // When bug 154892 is checked in, make sure that when
713 // mChildListID == kFixedList, the height is unconstrained.
714 // since we don't allow replicated frames to split.
715
ReflowAbsoluteFrame(nsIFrame * aDelegatingFrame,nsPresContext * aPresContext,const ReflowInput & aReflowInput,const nsRect & aContainingBlock,AbsPosReflowFlags aFlags,nsIFrame * aKidFrame,nsReflowStatus & aStatus,OverflowAreas * aOverflowAreas)716 void nsAbsoluteContainingBlock::ReflowAbsoluteFrame(
717 nsIFrame* aDelegatingFrame, nsPresContext* aPresContext,
718 const ReflowInput& aReflowInput, const nsRect& aContainingBlock,
719 AbsPosReflowFlags aFlags, nsIFrame* aKidFrame, nsReflowStatus& aStatus,
720 OverflowAreas* aOverflowAreas) {
721 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
722
723 #ifdef DEBUG
724 if (nsBlockFrame::gNoisyReflow) {
725 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent);
726 printf("abs pos ");
727 nsAutoString name;
728 aKidFrame->GetFrameName(name);
729 printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
730
731 char width[16];
732 char height[16];
733 PrettyUC(aReflowInput.AvailableWidth(), width, 16);
734 PrettyUC(aReflowInput.AvailableHeight(), height, 16);
735 printf(" a=%s,%s ", width, height);
736 PrettyUC(aReflowInput.ComputedWidth(), width, 16);
737 PrettyUC(aReflowInput.ComputedHeight(), height, 16);
738 printf("c=%s,%s \n", width, height);
739 }
740 AutoNoisyIndenter indent(nsBlockFrame::gNoisy);
741 #endif // DEBUG
742
743 WritingMode wm = aKidFrame->GetWritingMode();
744 LogicalSize logicalCBSize(wm, aContainingBlock.Size());
745 nscoord availISize = logicalCBSize.ISize(wm);
746 if (availISize == -1) {
747 NS_ASSERTION(
748 aReflowInput.ComputedSize(wm).ISize(wm) != NS_UNCONSTRAINEDSIZE,
749 "Must have a useful inline-size _somewhere_");
750 availISize = aReflowInput.ComputedSizeWithPadding(wm).ISize(wm);
751 }
752
753 ReflowInput::InitFlags initFlags;
754 if (aFlags & AbsPosReflowFlags::IsGridContainerCB) {
755 // When a grid container generates the abs.pos. CB for a *child* then
756 // the static position is determined via CSS Box Alignment within the
757 // abs.pos. CB (a grid area, i.e. a piece of the grid). In this scenario,
758 // due to the multiple coordinate spaces in play, we use a convenience flag
759 // to simply have the child's ReflowInput give it a static position at its
760 // abs.pos. CB origin, and then we'll align & offset it from there.
761 nsIFrame* placeholder = aKidFrame->GetPlaceholderFrame();
762 if (placeholder && placeholder->GetParent() == aDelegatingFrame) {
763 initFlags += ReflowInput::InitFlag::StaticPosIsCBOrigin;
764 }
765 }
766
767 bool constrainBSize =
768 (aReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) &&
769
770 // Don't split if told not to (e.g. for fixed frames)
771 (aFlags & AbsPosReflowFlags::ConstrainHeight) &&
772
773 // XXX we don't handle splitting frames for inline absolute containing
774 // blocks yet
775 !aDelegatingFrame->IsInlineFrame() &&
776
777 // Bug 1588623: Support splitting absolute positioned multicol containers.
778 !aKidFrame->IsColumnSetWrapperFrame() &&
779
780 // Don't split things below the fold. (Ideally we shouldn't *have*
781 // anything totally below the fold, but we can't position frames
782 // across next-in-flow breaks yet.
783 (aKidFrame->GetLogicalRect(aContainingBlock.Size()).BStart(wm) <=
784 aReflowInput.AvailableBSize());
785
786 // Get the border values
787 const WritingMode outerWM = aReflowInput.GetWritingMode();
788 const LogicalMargin border = aDelegatingFrame->GetLogicalUsedBorder(outerWM);
789
790 const nscoord availBSize = constrainBSize
791 ? aReflowInput.AvailableBSize() -
792 border.ConvertTo(wm, outerWM).BStart(wm)
793 : NS_UNCONSTRAINEDSIZE;
794
795 ReflowInput kidReflowInput(aPresContext, aReflowInput, aKidFrame,
796 LogicalSize(wm, availISize, availBSize),
797 Some(logicalCBSize), initFlags);
798
799 if (kidReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE) {
800 // Shrink available block-size if it's constrained.
801 kidReflowInput.AvailableBSize() -=
802 kidReflowInput.ComputedLogicalMargin(wm).BStart(wm);
803 const nscoord kidOffsetBStart =
804 kidReflowInput.ComputedLogicalOffsets(wm).BStart(wm);
805 if (NS_AUTOOFFSET != kidOffsetBStart) {
806 kidReflowInput.AvailableBSize() -= kidOffsetBStart;
807 }
808 }
809
810 // Do the reflow
811 ReflowOutput kidDesiredSize(kidReflowInput);
812 aKidFrame->Reflow(aPresContext, kidDesiredSize, kidReflowInput, aStatus);
813
814 const LogicalSize kidSize = kidDesiredSize.Size(outerWM);
815
816 LogicalMargin offsets = kidReflowInput.ComputedLogicalOffsets(outerWM);
817 LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(outerWM);
818
819 // If we're doing CSS Box Alignment in either axis, that will apply the
820 // margin for us in that axis (since the thing that's aligned is the margin
821 // box). So, we clear out the margin here to avoid applying it twice.
822 if (kidReflowInput.mFlags.mIOffsetsNeedCSSAlign) {
823 margin.IStart(outerWM) = margin.IEnd(outerWM) = 0;
824 }
825 if (kidReflowInput.mFlags.mBOffsetsNeedCSSAlign) {
826 margin.BStart(outerWM) = margin.BEnd(outerWM) = 0;
827 }
828
829 // If we're solving for start in either inline or block direction,
830 // then compute it now that we know the dimensions.
831 ResolveSizeDependentOffsets(aPresContext, kidReflowInput, kidSize, margin,
832 &offsets, &logicalCBSize);
833
834 if (kidReflowInput.mFrame->HasIntrinsicKeywordForBSize()) {
835 ResolveAutoMarginsAfterLayout(kidReflowInput, &logicalCBSize, kidSize,
836 margin, offsets);
837 }
838
839 // Position the child relative to our padding edge
840 LogicalRect rect(
841 outerWM,
842 border.IStart(outerWM) + offsets.IStart(outerWM) + margin.IStart(outerWM),
843 border.BStart(outerWM) + offsets.BStart(outerWM) + margin.BStart(outerWM),
844 kidSize.ISize(outerWM), kidSize.BSize(outerWM));
845 nsRect r = rect.GetPhysicalRect(
846 outerWM, logicalCBSize.GetPhysicalSize(wm) +
847 border.Size(outerWM).GetPhysicalSize(outerWM));
848
849 // Offset the frame rect by the given origin of the absolute containing block.
850 r.x += aContainingBlock.x;
851 r.y += aContainingBlock.y;
852
853 aKidFrame->SetRect(r);
854
855 nsView* view = aKidFrame->GetView();
856 if (view) {
857 // Size and position the view and set its opacity, visibility, content
858 // transparency, and clip
859 nsContainerFrame::SyncFrameViewAfterReflow(aPresContext, aKidFrame, view,
860 kidDesiredSize.InkOverflow());
861 } else {
862 nsContainerFrame::PositionChildViews(aKidFrame);
863 }
864
865 aKidFrame->DidReflow(aPresContext, &kidReflowInput);
866
867 #ifdef DEBUG
868 if (nsBlockFrame::gNoisyReflow) {
869 nsIFrame::IndentBy(stdout, nsBlockFrame::gNoiseIndent - 1);
870 printf("abs pos ");
871 nsAutoString name;
872 aKidFrame->GetFrameName(name);
873 printf("%s ", NS_LossyConvertUTF16toASCII(name).get());
874 printf("%p rect=%d,%d,%d,%d\n", static_cast<void*>(aKidFrame), r.x, r.y,
875 r.width, r.height);
876 }
877 #endif
878
879 if (aOverflowAreas) {
880 aOverflowAreas->UnionWith(kidDesiredSize.mOverflowAreas + r.TopLeft());
881 }
882 }
883