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 manages rules for positioning floats */
8 
9 #ifndef nsFloatManager_h_
10 #define nsFloatManager_h_
11 
12 #include "mozilla/Attributes.h"
13 #include "mozilla/TypedEnumBits.h"
14 #include "mozilla/UniquePtr.h"
15 #include "mozilla/WritingModes.h"
16 #include "nsCoord.h"
17 #include "nsFrameList.h"  // for DEBUG_FRAME_DUMP
18 #include "nsIntervalSet.h"
19 #include "nsPoint.h"
20 #include "nsTArray.h"
21 
22 class nsIFrame;
23 class nsPresContext;
24 namespace mozilla {
25 struct ReflowInput;
26 class PresShell;
27 }  // namespace mozilla
28 
29 enum class nsFlowAreaRectFlags : uint32_t {
30   NoFlags = 0,
31   HasFloats = 1 << 0,
32   MayWiden = 1 << 1
33 };
34 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsFlowAreaRectFlags)
35 
36 /**
37  * The available space for content not occupied by floats is divided
38  * into a sequence of rectangles in the block direction.  However, we
39  * need to know not only the rectangle, but also whether it was reduced
40  * (from the content rectangle) by floats that actually intruded into
41  * the content rectangle. If it has been reduced by floats, then we also
42  * track whether the flow area might widen as the floats narrow in the
43  * block direction.
44  */
45 struct nsFlowAreaRect {
46   mozilla::LogicalRect mRect;
47 
48   nsFlowAreaRectFlags mAreaFlags;
49 
nsFlowAreaRectnsFlowAreaRect50   nsFlowAreaRect(mozilla::WritingMode aWritingMode, nscoord aICoord,
51                  nscoord aBCoord, nscoord aISize, nscoord aBSize,
52                  nsFlowAreaRectFlags aAreaFlags)
53       : mRect(aWritingMode, aICoord, aBCoord, aISize, aBSize),
54         mAreaFlags(aAreaFlags) {}
55 
HasFloatsnsFlowAreaRect56   bool HasFloats() const {
57     return (bool)(mAreaFlags & nsFlowAreaRectFlags::HasFloats);
58   }
MayWidennsFlowAreaRect59   bool MayWiden() const {
60     return (bool)(mAreaFlags & nsFlowAreaRectFlags::MayWiden);
61   }
62 };
63 
64 #define NS_FLOAT_MANAGER_CACHE_SIZE 64
65 
66 /**
67  * nsFloatManager is responsible for implementing CSS's rules for
68  * positioning floats. An nsFloatManager object is created during reflow for
69  * any block with NS_BLOCK_FLOAT_MGR. During reflow, the float manager for
70  * the nearest such ancestor block is found in ReflowInput::mFloatManager.
71  *
72  * According to the line-relative mappings in CSS Writing Modes spec [1],
73  * line-right and line-left are calculated with respect to the writing mode
74  * of the containing block of the floats. All the writing modes passed to
75  * nsFloatManager methods should be the containing block's writing mode.
76  *
77  * However, according to the abstract-to-physical mappings table [2], the
78  * 'direction' property of the containing block doesn't affect the
79  * interpretation of line-right and line-left. We actually implement this by
80  * passing in the writing mode of the block formatting context (BFC), i.e.
81  * the of BlockReflowState's writing mode.
82  *
83  * nsFloatManager uses a special logical coordinate space with inline
84  * coordinates on the line-axis and block coordinates on the block-axis
85  * based on the writing mode of the block formatting context. All the
86  * physical types like nsRect, nsPoint, etc. use this coordinate space. See
87  * FloatInfo::mRect for an example.
88  *
89  * [1] https://drafts.csswg.org/css-writing-modes/#line-mappings
90  * [2] https://drafts.csswg.org/css-writing-modes/#logical-to-physical
91  */
92 class nsFloatManager {
93  public:
94   explicit nsFloatManager(mozilla::PresShell* aPresShell,
95                           mozilla::WritingMode aWM);
96   ~nsFloatManager();
97 
98   void* operator new(size_t aSize) noexcept(true);
99   void operator delete(void* aPtr, size_t aSize);
100 
101   static void Shutdown();
102 
103   /**
104    * Get float region stored on the frame. (Defaults to mRect if it's
105    * not there.) The float region is the area impacted by this float;
106    * the coordinates are relative to the containing block frame.
107    */
108   static mozilla::LogicalRect GetRegionFor(mozilla::WritingMode aWM,
109                                            nsIFrame* aFloatFrame,
110                                            const nsSize& aContainerSize);
111   /**
112    * Calculate the float region for this frame using aMargin and the
113    * frame's mRect. The region includes the margins around the float,
114    * but doesn't include the relative offsets.
115    * Note that if the frame is or has a continuation, aMargin's top
116    * and/or bottom must be zeroed by the caller.
117    */
118   static mozilla::LogicalRect CalculateRegionFor(
119       mozilla::WritingMode aWM, nsIFrame* aFloatFrame,
120       const mozilla::LogicalMargin& aMargin, const nsSize& aContainerSize);
121   /**
122    * Store the float region on the frame. The region is stored
123    * as a delta against the mRect, so repositioning the frame will
124    * also reposition the float region.
125    */
126   static void StoreRegionFor(mozilla::WritingMode aWM, nsIFrame* aFloat,
127                              const mozilla::LogicalRect& aRegion,
128                              const nsSize& aContainerSize);
129 
130   // Structure that stores the current state of a float manager for
131   // Save/Restore purposes.
132   struct SavedState {
SavedStateSavedState133     explicit SavedState()
134         : mFloatInfoCount(0),
135           mLineLeft(0),
136           mBlockStart(0),
137           mPushedLeftFloatPastBreak(false),
138           mPushedRightFloatPastBreak(false),
139           mSplitLeftFloatAcrossBreak(false),
140           mSplitRightFloatAcrossBreak(false) {}
141 
142    private:
143     uint32_t mFloatInfoCount;
144     nscoord mLineLeft, mBlockStart;
145     bool mPushedLeftFloatPastBreak;
146     bool mPushedRightFloatPastBreak;
147     bool mSplitLeftFloatAcrossBreak;
148     bool mSplitRightFloatAcrossBreak;
149 
150     friend class nsFloatManager;
151   };
152 
153   /**
154    * Translate the current origin by the specified offsets. This
155    * creates a new local coordinate space relative to the current
156    * coordinate space.
157    */
Translate(nscoord aLineLeft,nscoord aBlockStart)158   void Translate(nscoord aLineLeft, nscoord aBlockStart) {
159     mLineLeft += aLineLeft;
160     mBlockStart += aBlockStart;
161   }
162 
163   /**
164    * Returns the current translation from local coordinate space to
165    * world coordinate space. This represents the accumulated calls to
166    * Translate().
167    */
GetTranslation(nscoord & aLineLeft,nscoord & aBlockStart)168   void GetTranslation(nscoord& aLineLeft, nscoord& aBlockStart) const {
169     aLineLeft = mLineLeft;
170     aBlockStart = mBlockStart;
171   }
172 
173   /**
174    * Get information about the area available to content that flows
175    * around floats.  Two different types of space can be requested:
176    *   BandFromPoint: returns the band containing block-dir coordinate
177    *     |aBCoord| (though actually with the top truncated to begin at
178    *     aBCoord), but up to at most |aBSize| (which may be nscoord_MAX).
179    *     This will return the tallest rectangle whose block start is
180    *     |aBCoord| and in which there are no changes in what floats are
181    *     on the sides of that rectangle, but will limit the block size
182    *     of the rectangle to |aBSize|.  The inline start and end edges
183    *     of the rectangle give the area available for line boxes in that
184    *     space. The inline size of this resulting rectangle will not be
185    *     negative.
186    *   WidthWithinHeight: This returns a rectangle whose block start
187    *     is aBCoord and whose block size is exactly aBSize.  Its inline
188    *     start and end edges give the corresponding edges of the space
189    *     that can be used for line boxes *throughout* that space.  (It
190    *     is possible that more inline space could be used in part of the
191    *     space if a float begins or ends in it.)  The inline size of the
192    *     resulting rectangle can be negative.
193    *
194    * ShapeType can be used to request two different types of flow areas.
195    * (This is the float area defined in CSS Shapes Module Level 1 §1.4):
196    *    Margin: uses the float element's margin-box to request the flow area.
197    *    ShapeOutside: uses the float element's shape-outside value to request
198    *      the float area.
199    *
200    * @param aBCoord [in] block-dir coordinate for block start of available space
201    *          desired, which are positioned relative to the current translation.
202    * @param aBSize [in] see above
203    * @param aContentArea [in] an nsRect representing the content area
204    * @param aState [in] If null, use the current state, otherwise, do
205    *                    computation based only on floats present in the given
206    *                    saved state.
207    * @return An nsFlowAreaRect whose:
208    *           mRect is the resulting rectangle for line boxes.  It will not
209    *             extend beyond aContentArea's inline bounds, but may be
210    *             narrower when floats are present.
211    *           mHasFloats is whether there are floats at the sides of the
212    *             return value including those that do not reduce the line box
213    *             inline size at all (because they are entirely in the margins)
214    */
215   enum class BandInfoType { BandFromPoint, WidthWithinHeight };
216   enum class ShapeType { Margin, ShapeOutside };
217   nsFlowAreaRect GetFlowArea(mozilla::WritingMode aWM, nscoord aBCoord,
218                              nscoord aBSize, BandInfoType aBandInfoType,
219                              ShapeType aShapeType,
220                              mozilla::LogicalRect aContentArea,
221                              SavedState* aState,
222                              const nsSize& aContainerSize) const;
223 
224   /**
225    * Add a float that comes after all floats previously added.  Its
226    * block start must be even with or below the top of all previous
227    * floats.
228    *
229    * aMarginRect is relative to the current translation.  The caller
230    * must ensure aMarginRect.height >= 0 and aMarginRect.width >= 0.
231    */
232   void AddFloat(nsIFrame* aFloatFrame, const mozilla::LogicalRect& aMarginRect,
233                 mozilla::WritingMode aWM, const nsSize& aContainerSize);
234 
235   /**
236    * Notify that we tried to place a float that could not fit at all and
237    * had to be pushed to the next page/column?  (If so, we can't place
238    * any more floats in this page/column because of the rule that the
239    * top of a float cannot be above the top of an earlier float.  It
240    * also means that any clear needs to continue to the next column.)
241    */
SetPushedLeftFloatPastBreak()242   void SetPushedLeftFloatPastBreak() { mPushedLeftFloatPastBreak = true; }
SetPushedRightFloatPastBreak()243   void SetPushedRightFloatPastBreak() { mPushedRightFloatPastBreak = true; }
244 
245   /**
246    * Notify that we split a float, with part of it needing to be pushed
247    * to the next page/column.  (This means that any 'clear' needs to
248    * continue to the next page/column.)
249    */
SetSplitLeftFloatAcrossBreak()250   void SetSplitLeftFloatAcrossBreak() { mSplitLeftFloatAcrossBreak = true; }
SetSplitRightFloatAcrossBreak()251   void SetSplitRightFloatAcrossBreak() { mSplitRightFloatAcrossBreak = true; }
252 
253   /**
254    * Remove the regions associated with this floating frame and its
255    * next-sibling list.  Some of the frames may never have been added;
256    * we just skip those. This is not fully general; it only works as
257    * long as the N frames to be removed are the last N frames to have
258    * been added; if there's a frame in the middle of them that should
259    * not be removed, YOU LOSE.
260    */
261   nsresult RemoveTrailingRegions(nsIFrame* aFrameList);
262 
HasAnyFloats()263   bool HasAnyFloats() const { return !mFloats.IsEmpty(); }
264 
265   /**
266    * Methods for dealing with the propagation of float damage during
267    * reflow.
268    */
HasFloatDamage()269   bool HasFloatDamage() const { return !mFloatDamage.IsEmpty(); }
270 
IncludeInDamage(nscoord aIntervalBegin,nscoord aIntervalEnd)271   void IncludeInDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) {
272     mFloatDamage.IncludeInterval(aIntervalBegin + mBlockStart,
273                                  aIntervalEnd + mBlockStart);
274   }
275 
IntersectsDamage(nscoord aIntervalBegin,nscoord aIntervalEnd)276   bool IntersectsDamage(nscoord aIntervalBegin, nscoord aIntervalEnd) const {
277     return mFloatDamage.Intersects(aIntervalBegin + mBlockStart,
278                                    aIntervalEnd + mBlockStart);
279   }
280 
281   /**
282    * Saves the current state of the float manager into aState.
283    */
284   void PushState(SavedState* aState);
285 
286   /**
287    * Restores the float manager to the saved state.
288    *
289    * These states must be managed using stack discipline. PopState can only
290    * be used after PushState has been used to save the state, and it can only
291    * be used once --- although it can be omitted; saved states can be ignored.
292    * States must be popped in the reverse order they were pushed.  A
293    * call to PopState invalidates any saved states Pushed after the
294    * state passed to PopState was pushed.
295    */
296   void PopState(SavedState* aState);
297 
298   /**
299    * Get the block start of the last float placed into the float
300    * manager, to enforce the rule that a float can't be above an earlier
301    * float. Returns the minimum nscoord value if there are no floats.
302    *
303    * The result is relative to the current translation.
304    */
305   nscoord GetLowestFloatTop() const;
306 
307   /**
308    * Return the coordinate of the lowest float matching aBreakType in
309    * this float manager. Returns aBCoord if there are no matching
310    * floats.
311    *
312    * Both aBCoord and the result are relative to the current translation.
313    */
314   nscoord ClearFloats(nscoord aBCoord, mozilla::StyleClear aBreakType) const;
315 
316   /**
317    * Checks if clear would pass into the floats' BFC's next-in-flow,
318    * i.e. whether floats affecting this clear have continuations.
319    */
320   bool ClearContinues(mozilla::StyleClear aBreakType) const;
321 
AssertStateMatches(SavedState * aState)322   void AssertStateMatches(SavedState* aState) const {
323     NS_ASSERTION(
324         aState->mLineLeft == mLineLeft && aState->mBlockStart == mBlockStart &&
325             aState->mPushedLeftFloatPastBreak == mPushedLeftFloatPastBreak &&
326             aState->mPushedRightFloatPastBreak == mPushedRightFloatPastBreak &&
327             aState->mSplitLeftFloatAcrossBreak == mSplitLeftFloatAcrossBreak &&
328             aState->mSplitRightFloatAcrossBreak ==
329                 mSplitRightFloatAcrossBreak &&
330             aState->mFloatInfoCount == mFloats.Length(),
331         "float manager state should match saved state");
332   }
333 
334 #ifdef DEBUG_FRAME_DUMP
335   /**
336    * Dump the state of the float manager out to a file.
337    */
338   nsresult List(FILE* out) const;
339 #endif
340 
341  private:
342   class ShapeInfo;
343   class RoundedBoxShapeInfo;
344   class EllipseShapeInfo;
345   class PolygonShapeInfo;
346   class ImageShapeInfo;
347 
348   struct FloatInfo {
349     nsIFrame* const mFrame;
350     // The lowest block-ends of left/right floats up to and including
351     // this one.
352     nscoord mLeftBEnd, mRightBEnd;
353 
354     FloatInfo(nsIFrame* aFrame, nscoord aLineLeft, nscoord aBlockStart,
355               const mozilla::LogicalRect& aMarginRect, mozilla::WritingMode aWM,
356               const nsSize& aContainerSize);
357 
LineLeftFloatInfo358     nscoord LineLeft() const { return mRect.x; }
LineRightFloatInfo359     nscoord LineRight() const { return mRect.XMost(); }
ISizeFloatInfo360     nscoord ISize() const { return mRect.width; }
BStartFloatInfo361     nscoord BStart() const { return mRect.y; }
BEndFloatInfo362     nscoord BEnd() const { return mRect.YMost(); }
BSizeFloatInfo363     nscoord BSize() const { return mRect.height; }
IsEmptyFloatInfo364     bool IsEmpty() const { return mRect.IsEmpty(); }
365 
366     // aBStart and aBEnd are the starting and ending coordinate of a band.
367     // LineLeft() and LineRight() return the innermost line-left extent and
368     // line-right extent within the given band, respectively.
369     nscoord LineLeft(ShapeType aShapeType, const nscoord aBStart,
370                      const nscoord aBEnd) const;
371     nscoord LineRight(ShapeType aShapeType, const nscoord aBStart,
372                       const nscoord aBEnd) const;
373     nscoord BStart(ShapeType aShapeType) const;
374     nscoord BEnd(ShapeType aShapeType) const;
375     bool IsEmpty(ShapeType aShapeType) const;
376     bool MayNarrowInBlockDirection(ShapeType aShapeType) const;
377 
378 #ifdef NS_BUILD_REFCNT_LOGGING
379     FloatInfo(FloatInfo&& aOther);
380     ~FloatInfo();
381 #endif
382 
383     // NB! This is really a logical rect in a writing mode suitable for
384     // placing floats, which is not necessarily the actual writing mode
385     // either of the block which created the float manager or the block
386     // that is calling the float manager. The inline coordinates are in
387     // the line-relative axis of the float manager and its block
388     // coordinates are in the float manager's block direction.
389     nsRect mRect;
390     // Pointer to a concrete subclass of ShapeInfo or null, which means that
391     // there is no shape-outside.
392     mozilla::UniquePtr<ShapeInfo> mShapeInfo;
393   };
394 
395 #ifdef DEBUG
396   // Store the writing mode from the block frame which establishes the block
397   // formatting context (BFC) when the nsFloatManager is created.
398   mozilla::WritingMode mWritingMode;
399 #endif
400 
401   // Translation from local to global coordinate space.
402   nscoord mLineLeft, mBlockStart;
403   // We use 11 here in order to fill up the jemalloc allocatoed chunk nicely,
404   // see https://bugzilla.mozilla.org/show_bug.cgi?id=1362876#c6.
405   AutoTArray<FloatInfo, 11> mFloats;
406   nsIntervalSet mFloatDamage;
407 
408   // Did we try to place a float that could not fit at all and had to be
409   // pushed to the next page/column?  If so, we can't place any more
410   // floats in this page/column because of the rule that the top of a
411   // float cannot be above the top of an earlier float.  And we also
412   // need to apply this information to 'clear', and thus need to
413   // separate left and right floats.
414   bool mPushedLeftFloatPastBreak;
415   bool mPushedRightFloatPastBreak;
416 
417   // Did we split a float, with part of it needing to be pushed to the
418   // next page/column.  This means that any 'clear' needs to continue to
419   // the next page/column.
420   bool mSplitLeftFloatAcrossBreak;
421   bool mSplitRightFloatAcrossBreak;
422 
423   static int32_t sCachedFloatManagerCount;
424   static void* sCachedFloatManagers[NS_FLOAT_MANAGER_CACHE_SIZE];
425 
426   nsFloatManager(const nsFloatManager&) = delete;
427   void operator=(const nsFloatManager&) = delete;
428 };
429 
430 /**
431  * A helper class to manage maintenance of the float manager during
432  * nsBlockFrame::Reflow. It automatically restores the old float
433  * manager in the reflow input when the object goes out of scope.
434  */
435 class nsAutoFloatManager {
436   using ReflowInput = mozilla::ReflowInput;
437 
438  public:
nsAutoFloatManager(ReflowInput & aReflowInput)439   explicit nsAutoFloatManager(ReflowInput& aReflowInput)
440       : mReflowInput(aReflowInput), mOld(nullptr) {}
441 
442   ~nsAutoFloatManager();
443 
444   /**
445    * Create a new float manager for the specified frame. This will
446    * `remember' the old float manager, and install the new float
447    * manager in the reflow input.
448    */
449   void CreateFloatManager(nsPresContext* aPresContext);
450 
451  protected:
452   ReflowInput& mReflowInput;
453   mozilla::UniquePtr<nsFloatManager> mNew;
454 
455   // A non-owning pointer, which points to the object owned by
456   // nsAutoFloatManager::mNew.
457   nsFloatManager* mOld;
458 };
459 
460 #endif /* !defined(nsFloatManager_h_) */
461