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