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