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