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 /* class that a parent frame uses to reflow a block frame */
8 
9 #include "nsBlockReflowContext.h"
10 #include "BlockReflowInput.h"
11 #include "nsFloatManager.h"
12 #include "nsColumnSetFrame.h"
13 #include "nsContainerFrame.h"
14 #include "nsBlockFrame.h"
15 #include "nsLineBox.h"
16 #include "nsLayoutUtils.h"
17 
18 using namespace mozilla;
19 
20 #ifdef DEBUG
21 #  include "nsBlockDebugFlags.h"  // For NOISY_BLOCK_DIR_MARGINS
22 #endif
23 
nsBlockReflowContext(nsPresContext * aPresContext,const ReflowInput & aParentRI)24 nsBlockReflowContext::nsBlockReflowContext(nsPresContext* aPresContext,
25                                            const ReflowInput& aParentRI)
26     : mPresContext(aPresContext),
27       mOuterReflowInput(aParentRI),
28       mFrame(nullptr),
29       mSpace(aParentRI.GetWritingMode()),
30       mICoord(0),
31       mBCoord(0),
32       mMetrics(aParentRI) {}
33 
DescendIntoBlockLevelFrame(nsIFrame * aFrame)34 static nsIFrame* DescendIntoBlockLevelFrame(nsIFrame* aFrame) {
35   LayoutFrameType type = aFrame->Type();
36   if (type == LayoutFrameType::ColumnSet) {
37     static_cast<nsColumnSetFrame*>(aFrame)->DrainOverflowColumns();
38     nsIFrame* child = aFrame->PrincipalChildList().FirstChild();
39     if (child) {
40       return DescendIntoBlockLevelFrame(child);
41     }
42   }
43   return aFrame;
44 }
45 
ComputeCollapsedBStartMargin(const ReflowInput & aRI,nsCollapsingMargin * aMargin,nsIFrame * aClearanceFrame,bool * aMayNeedRetry,bool * aBlockIsEmpty)46 bool nsBlockReflowContext::ComputeCollapsedBStartMargin(
47     const ReflowInput& aRI, nsCollapsingMargin* aMargin,
48     nsIFrame* aClearanceFrame, bool* aMayNeedRetry, bool* aBlockIsEmpty) {
49   WritingMode wm = aRI.GetWritingMode();
50   WritingMode parentWM = mMetrics.GetWritingMode();
51 
52   // Include block-start element of frame's margin
53   aMargin->Include(
54       aRI.ComputedLogicalMargin().ConvertTo(parentWM, wm).BStart(parentWM));
55 
56   // The inclusion of the block-end margin when empty is done by the caller
57   // since it doesn't need to be done by the top-level (non-recursive)
58   // caller.
59 
60 #ifdef NOISY_BLOCK_DIR_MARGINS
61   aRI.mFrame->ListTag(stdout);
62   printf(": %d => %d\n", aRI.ComputedLogicalMargin().BStart(wm),
63          aMargin->get());
64 #endif
65 
66   bool dirtiedLine = false;
67   bool setBlockIsEmpty = false;
68 
69   // Calculate the frame's generational block-start-margin from its child
70   // blocks. Note that if the frame has a non-zero block-start-border or
71   // block-start-padding then this step is skipped because it will be a margin
72   // root.  It is also skipped if the frame is a margin root for other
73   // reasons.
74   nsIFrame* frame = DescendIntoBlockLevelFrame(aRI.mFrame);
75   nsPresContext* prescontext = frame->PresContext();
76   nsBlockFrame* block = nullptr;
77   if (0 == aRI.ComputedLogicalBorderPadding().BStart(wm)) {
78     block = do_QueryFrame(frame);
79     if (block) {
80       bool bStartMarginRoot, unused;
81       block->IsMarginRoot(&bStartMarginRoot, &unused);
82       if (bStartMarginRoot) {
83         block = nullptr;
84       }
85     }
86   }
87 
88   // iterate not just through the lines of 'block' but also its
89   // overflow lines and the normal and overflow lines of its next in
90   // flows. Note that this will traverse some frames more than once:
91   // for example, if A contains B and A->nextinflow contains
92   // B->nextinflow, we'll traverse B->nextinflow twice. But this is
93   // OK because our traversal is idempotent.
94   for (; block; block = static_cast<nsBlockFrame*>(block->GetNextInFlow())) {
95     for (int overflowLines = 0; overflowLines <= 1; ++overflowLines) {
96       nsBlockFrame::LineIterator line;
97       nsBlockFrame::LineIterator line_end;
98       bool anyLines = true;
99       if (overflowLines) {
100         nsBlockFrame::FrameLines* frames = block->GetOverflowLines();
101         nsLineList* lines = frames ? &frames->mLines : nullptr;
102         if (!lines) {
103           anyLines = false;
104         } else {
105           line = lines->begin();
106           line_end = lines->end();
107         }
108       } else {
109         line = block->LinesBegin();
110         line_end = block->LinesEnd();
111       }
112       for (; anyLines && line != line_end; ++line) {
113         if (!aClearanceFrame && line->HasClearance()) {
114           // If we don't have a clearance frame, then we're computing
115           // the collapsed margin in the first pass, assuming that all
116           // lines have no clearance. So clear their clearance flags.
117           line->ClearHasClearance();
118           line->MarkDirty();
119           dirtiedLine = true;
120         }
121 
122         bool isEmpty;
123         if (line->IsInline()) {
124           isEmpty = line->IsEmpty();
125         } else {
126           nsIFrame* kid = line->mFirstChild;
127           if (kid == aClearanceFrame) {
128             line->SetHasClearance();
129             line->MarkDirty();
130             dirtiedLine = true;
131             if (!setBlockIsEmpty && aBlockIsEmpty) {
132               setBlockIsEmpty = true;
133               *aBlockIsEmpty = false;
134             }
135             goto done;
136           }
137           // Here is where we recur. Now that we have determined that a
138           // generational collapse is required we need to compute the
139           // child blocks margin and so in so that we can look into
140           // it. For its margins to be computed we need to have a reflow
141           // input for it.
142 
143           // We may have to construct an extra reflow input here if
144           // we drilled down through a block wrapper. At the moment
145           // we can only drill down one level so we only have to support
146           // one extra reflow input.
147           const ReflowInput* outerReflowInput = &aRI;
148           if (frame != aRI.mFrame) {
149             NS_ASSERTION(frame->GetParent() == aRI.mFrame,
150                          "Can only drill through one level of block wrapper");
151             LogicalSize availSpace = aRI.ComputedSize(frame->GetWritingMode());
152             outerReflowInput =
153                 new ReflowInput(prescontext, aRI, frame, availSpace);
154           }
155           {
156             LogicalSize availSpace =
157                 outerReflowInput->ComputedSize(kid->GetWritingMode());
158             ReflowInput innerReflowInput(prescontext, *outerReflowInput, kid,
159                                          availSpace);
160             // Record that we're being optimistic by assuming the kid
161             // has no clearance
162             if (kid->StyleDisplay()->mBreakType != StyleClear::None ||
163                 !nsBlockFrame::BlockCanIntersectFloats(kid)) {
164               *aMayNeedRetry = true;
165             }
166             if (ComputeCollapsedBStartMargin(innerReflowInput, aMargin,
167                                              aClearanceFrame, aMayNeedRetry,
168                                              &isEmpty)) {
169               line->MarkDirty();
170               dirtiedLine = true;
171             }
172             if (isEmpty) {
173               WritingMode innerWM = innerReflowInput.GetWritingMode();
174               LogicalMargin innerMargin =
175                   innerReflowInput.ComputedLogicalMargin().ConvertTo(parentWM,
176                                                                      innerWM);
177               aMargin->Include(innerMargin.BEnd(parentWM));
178             }
179           }
180           if (outerReflowInput != &aRI) {
181             delete const_cast<ReflowInput*>(outerReflowInput);
182           }
183         }
184         if (!isEmpty) {
185           if (!setBlockIsEmpty && aBlockIsEmpty) {
186             setBlockIsEmpty = true;
187             *aBlockIsEmpty = false;
188           }
189           goto done;
190         }
191       }
192       if (!setBlockIsEmpty && aBlockIsEmpty) {
193         // The first time we reach here is when this is the first block
194         // and we have processed all its normal lines.
195         setBlockIsEmpty = true;
196         // All lines are empty, or we wouldn't be here!
197         *aBlockIsEmpty = aRI.mFrame->IsSelfEmpty();
198       }
199     }
200   }
201 done:
202 
203   if (!setBlockIsEmpty && aBlockIsEmpty) {
204     *aBlockIsEmpty = aRI.mFrame->IsEmpty();
205   }
206 
207 #ifdef NOISY_BLOCK_DIR_MARGINS
208   aRI.mFrame->ListTag(stdout);
209   printf(": => %d\n", aMargin->get());
210 #endif
211 
212   return dirtiedLine;
213 }
214 
ReflowBlock(const LogicalRect & aSpace,bool aApplyBStartMargin,nsCollapsingMargin & aPrevMargin,nscoord aClearance,bool aIsAdjacentWithBStart,nsLineBox * aLine,ReflowInput & aFrameRI,nsReflowStatus & aFrameReflowStatus,BlockReflowInput & aState)215 void nsBlockReflowContext::ReflowBlock(
216     const LogicalRect& aSpace, bool aApplyBStartMargin,
217     nsCollapsingMargin& aPrevMargin, nscoord aClearance,
218     bool aIsAdjacentWithBStart, nsLineBox* aLine, ReflowInput& aFrameRI,
219     nsReflowStatus& aFrameReflowStatus, BlockReflowInput& aState) {
220   mFrame = aFrameRI.mFrame;
221   mWritingMode = aState.mReflowInput.GetWritingMode();
222   mContainerSize = aState.ContainerSize();
223   mSpace = aSpace;
224 
225   if (!aIsAdjacentWithBStart) {
226     aFrameRI.mFlags.mIsTopOfPage = false;  // make sure this is cleared
227   }
228 
229   if (aApplyBStartMargin) {
230     mBStartMargin = aPrevMargin;
231 
232 #ifdef NOISY_BLOCK_DIR_MARGINS
233     mOuterReflowInput.mFrame->ListTag(stdout);
234     printf(": reflowing ");
235     mFrame->ListTag(stdout);
236     printf(" margin => %d, clearance => %d\n", mBStartMargin.get(), aClearance);
237 #endif
238 
239     // Adjust the available size if it's constrained so that the
240     // child frame doesn't think it can reflow into its margin area.
241     if (mWritingMode.IsOrthogonalTo(mFrame->GetWritingMode())) {
242       if (NS_UNCONSTRAINEDSIZE != aFrameRI.AvailableISize()) {
243         aFrameRI.AvailableISize() -= mBStartMargin.get() + aClearance;
244         aFrameRI.AvailableISize() = std::max(0, aFrameRI.AvailableISize());
245       }
246     } else {
247       if (NS_UNCONSTRAINEDSIZE != aFrameRI.AvailableBSize()) {
248         aFrameRI.AvailableBSize() -= mBStartMargin.get() + aClearance;
249         aFrameRI.AvailableBSize() = std::max(0, aFrameRI.AvailableBSize());
250       }
251     }
252   } else {
253     // nsBlockFrame::ReflowBlock might call us multiple times with
254     // *different* values of aApplyBStartMargin.
255     mBStartMargin.Zero();
256   }
257 
258   nscoord tI = 0, tB = 0;
259   // The values of x and y do not matter for floats, so don't bother
260   // calculating them. Floats are guaranteed to have their own float
261   // manager, so tI and tB don't matter.  mICoord and mBCoord don't
262   // matter becacuse they are only used in PlaceBlock, which is not used
263   // for floats.
264   if (aLine) {
265     // Compute inline/block coordinate where reflow will begin. Use the
266     // rules from 10.3.3 to determine what to apply. At this point in the
267     // reflow auto inline-start/end margins will have a zero value.
268 
269     WritingMode frameWM = aFrameRI.GetWritingMode();
270     LogicalMargin usedMargin =
271         aFrameRI.ComputedLogicalMargin().ConvertTo(mWritingMode, frameWM);
272     mICoord = mSpace.IStart(mWritingMode) + usedMargin.IStart(mWritingMode);
273     mBCoord = mSpace.BStart(mWritingMode) + mBStartMargin.get() + aClearance;
274 
275     LogicalRect space(
276         mWritingMode, mICoord, mBCoord,
277         mSpace.ISize(mWritingMode) - usedMargin.IStartEnd(mWritingMode),
278         mSpace.BSize(mWritingMode) - usedMargin.BStartEnd(mWritingMode));
279     tI = space.LineLeft(mWritingMode, mContainerSize);
280     tB = mBCoord;
281 
282     if ((mFrame->GetStateBits() & NS_BLOCK_FLOAT_MGR) == 0)
283       aFrameRI.mBlockDelta =
284           mOuterReflowInput.mBlockDelta + mBCoord - aLine->BStart();
285   }
286 
287 #ifdef DEBUG
288   mMetrics.ISize(mWritingMode) = nscoord(0xdeadbeef);
289   mMetrics.BSize(mWritingMode) = nscoord(0xdeadbeef);
290 #endif
291 
292   mOuterReflowInput.mFloatManager->Translate(tI, tB);
293   mFrame->Reflow(mPresContext, mMetrics, aFrameRI, aFrameReflowStatus);
294   mOuterReflowInput.mFloatManager->Translate(-tI, -tB);
295 
296 #ifdef DEBUG
297   if (!aFrameReflowStatus.IsInlineBreakBefore()) {
298     if ((CRAZY_SIZE(mMetrics.ISize(mWritingMode)) ||
299          CRAZY_SIZE(mMetrics.BSize(mWritingMode))) &&
300         !mFrame->GetParent()->IsCrazySizeAssertSuppressed()) {
301       printf("nsBlockReflowContext: ");
302       mFrame->ListTag(stdout);
303       printf(" metrics=%d,%d!\n", mMetrics.ISize(mWritingMode),
304              mMetrics.BSize(mWritingMode));
305     }
306     if ((mMetrics.ISize(mWritingMode) == nscoord(0xdeadbeef)) ||
307         (mMetrics.BSize(mWritingMode) == nscoord(0xdeadbeef))) {
308       printf("nsBlockReflowContext: ");
309       mFrame->ListTag(stdout);
310       printf(" didn't set i/b %d,%d!\n", mMetrics.ISize(mWritingMode),
311              mMetrics.BSize(mWritingMode));
312     }
313   }
314 #endif
315 
316   if (!mFrame->HasOverflowAreas()) {
317     mMetrics.SetOverflowAreasToDesiredBounds();
318   }
319 
320   if (!aFrameReflowStatus.IsInlineBreakBefore() &&
321       aFrameReflowStatus.IsFullyComplete()) {
322     // If frame is complete and has a next-in-flow, we need to delete
323     // them now. Do not do this when a break-before is signaled because
324     // the frame is going to get reflowed again (whether the frame is
325     // (in)complete is undefined in that case anyway).
326     if (nsIFrame* kidNextInFlow = mFrame->GetNextInFlow()) {
327       // Remove all of the childs next-in-flows. Make sure that we ask
328       // the right parent to do the removal (it's possible that the
329       // parent is not this because we are executing pullup code).
330       // Floats will eventually be removed via nsBlockFrame::RemoveFloat
331       // which detaches the placeholder from the float.
332       nsOverflowContinuationTracker::AutoFinish fini(aState.mOverflowTracker,
333                                                      mFrame);
334       kidNextInFlow->GetParent()->DeleteNextInFlowChild(kidNextInFlow, true);
335     }
336   }
337 }
338 
339 /**
340  * Attempt to place the block frame within the available space.  If
341  * it fits, apply inline-dir ("horizontal") positioning (CSS 10.3.3),
342  * collapse margins (CSS2 8.3.1). Also apply relative positioning.
343  */
PlaceBlock(const ReflowInput & aReflowInput,bool aForceFit,nsLineBox * aLine,nsCollapsingMargin & aBEndMarginResult,nsOverflowAreas & aOverflowAreas,const nsReflowStatus & aReflowStatus)344 bool nsBlockReflowContext::PlaceBlock(const ReflowInput& aReflowInput,
345                                       bool aForceFit, nsLineBox* aLine,
346                                       nsCollapsingMargin& aBEndMarginResult,
347                                       nsOverflowAreas& aOverflowAreas,
348                                       const nsReflowStatus& aReflowStatus) {
349   // Compute collapsed block-end margin value.
350   WritingMode wm = aReflowInput.GetWritingMode();
351   WritingMode parentWM = mMetrics.GetWritingMode();
352 
353   // Don't apply the block-end margin if the block has a *later* sibling across
354   // column-span split.
355   if (aReflowStatus.IsComplete() && !mFrame->HasColumnSpanSiblings()) {
356     aBEndMarginResult = mMetrics.mCarriedOutBEndMargin;
357     aBEndMarginResult.Include(aReflowInput.ComputedLogicalMargin()
358                                   .ConvertTo(parentWM, wm)
359                                   .BEnd(parentWM));
360   } else {
361     // The used block-end-margin is set to zero before a break.
362     aBEndMarginResult.Zero();
363   }
364 
365   nscoord backupContainingBlockAdvance = 0;
366 
367   // Check whether the block's block-end margin collapses with its block-start
368   // margin. See CSS 2.1 section 8.3.1; those rules seem to match
369   // nsBlockFrame::IsEmpty(). Any such block must have zero block-size so
370   // check that first. Note that a block can have clearance and still
371   // have adjoining block-start/end margins, because the clearance goes
372   // above the block-start margin.
373   // Mark the frame as non-dirty; it has been reflowed (or we wouldn't
374   // be here), and we don't want to assert in CachedIsEmpty()
375   mFrame->RemoveStateBits(NS_FRAME_IS_DIRTY);
376   bool empty = 0 == mMetrics.BSize(parentWM) && aLine->CachedIsEmpty();
377   if (empty) {
378     // Collapse the block-end margin with the block-start margin that was
379     // already applied.
380     aBEndMarginResult.Include(mBStartMargin);
381 
382 #ifdef NOISY_BLOCK_DIR_MARGINS
383     printf("  ");
384     mOuterReflowInput.mFrame->ListTag(stdout);
385     printf(": ");
386     mFrame->ListTag(stdout);
387     printf(
388         " -- collapsing block start & end margin together; BStart=%d "
389         "spaceBStart=%d\n",
390         mBCoord, mSpace.BStart(mWritingMode));
391 #endif
392     // Section 8.3.1 of CSS 2.1 says that blocks with adjoining
393     // "top/bottom" (i.e. block-start/end) margins whose top margin collapses
394     // with their parent's top margin should have their top border-edge at the
395     // top border-edge of their parent. We actually don't have to do
396     // anything special to make this happen. In that situation,
397     // nsBlockFrame::ShouldApplyBStartMargin will have returned false,
398     // and mBStartMargin and aClearance will have been zero in
399     // ReflowBlock.
400 
401     // If we did apply our block-start margin, but now we're collapsing it
402     // into the block-end margin, we need to back up the containing
403     // block's bCoord-advance by our block-start margin so that it doesn't get
404     // counted twice. Note that here we're allowing the line's bounds
405     // to become different from the block's position; we do this
406     // because the containing block will place the next line at the
407     // line's BEnd, and it must place the next line at a different
408     // point from where this empty block will be.
409     backupContainingBlockAdvance = mBStartMargin.get();
410   }
411 
412   // See if the frame fit. If it's the first frame or empty then it
413   // always fits. If the block-size is unconstrained then it always fits,
414   // even if there's some sort of integer overflow that makes bCoord +
415   // mMetrics.BSize() appear to go beyond the available block size.
416   if (!empty && !aForceFit &&
417       mSpace.BSize(mWritingMode) != NS_UNCONSTRAINEDSIZE) {
418     nscoord bEnd =
419         mBCoord - backupContainingBlockAdvance + mMetrics.BSize(mWritingMode);
420     if (bEnd > mSpace.BEnd(mWritingMode)) {
421       // didn't fit, we must acquit.
422       mFrame->DidReflow(mPresContext, &aReflowInput);
423       return false;
424     }
425   }
426 
427   aLine->SetBounds(mWritingMode, mICoord,
428                    mBCoord - backupContainingBlockAdvance,
429                    mMetrics.ISize(mWritingMode), mMetrics.BSize(mWritingMode),
430                    mContainerSize);
431 
432   // Now place the frame and complete the reflow process
433   nsContainerFrame::FinishReflowChild(
434       mFrame, mPresContext, mMetrics, &aReflowInput, mWritingMode,
435       LogicalPoint(mWritingMode, mICoord, mBCoord), mContainerSize,
436       nsIFrame::ReflowChildFlags::ApplyRelativePositioning);
437 
438   aOverflowAreas = mMetrics.mOverflowAreas + mFrame->GetPosition();
439 
440   return true;
441 }
442