nsFrameList.h (15444B)
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 #ifndef nsFrameList_h___ 8 #define nsFrameList_h___ 9 10 #include <stdio.h> /* for FILE* */ 11 12 #include "mozilla/EnumSet.h" 13 #include "mozilla/FunctionTypeTraits.h" 14 #include "nsDebug.h" 15 #include "nsTArray.h" 16 17 #if defined(DEBUG) || defined(MOZ_DUMP_PAINTING) || defined(MOZ_LAYOUT_DEBUGGER) 18 // DEBUG_FRAME_DUMP enables nsIFrame::List and related methods. 19 // You can also define this in a non-DEBUG build if you need frame dumps. 20 # define DEBUG_FRAME_DUMP 1 21 #endif 22 23 class nsContainerFrame; 24 class nsIContent; 25 class nsIFrame; 26 class nsPresContext; 27 28 namespace mozilla { 29 30 struct FrameDestroyContext; 31 32 class PresShell; 33 class FrameChildList; 34 enum class FrameChildListID { 35 // The individual concrete child lists. 36 Principal, 37 ColGroup, 38 Absolute, 39 PushedAbsolute, 40 Fixed, 41 Overflow, 42 OverflowContainers, 43 ExcessOverflowContainers, 44 OverflowOutOfFlow, 45 Float, 46 Marker, 47 PushedFloats, 48 // A special alias for FrameChildListID::Principal that suppress the reflow 49 // request that is normally done when manipulating child lists. 50 NoReflowPrincipal, 51 }; 52 53 } // namespace mozilla 54 55 // Uncomment this to enable expensive frame-list integrity checking 56 // #define DEBUG_FRAME_LIST 57 58 /** 59 * A class for managing a list of frames. 60 */ 61 class nsFrameList { 62 // Next()/Prev() need to know about nsIFrame. To make them inline, their 63 // implementations are in nsIFrame.h. 64 struct ForwardFrameTraversal final { 65 static inline nsIFrame* Next(nsIFrame*); 66 static inline nsIFrame* Prev(nsIFrame*); 67 }; 68 struct BackwardFrameTraversal final { 69 static inline nsIFrame* Next(nsIFrame*); 70 static inline nsIFrame* Prev(nsIFrame*); 71 }; 72 73 public: 74 template <typename FrameTraversal> 75 class Iterator; 76 class Slice; 77 78 using iterator = Iterator<ForwardFrameTraversal>; 79 using const_iterator = Iterator<ForwardFrameTraversal>; 80 using reverse_iterator = Iterator<BackwardFrameTraversal>; 81 using const_reverse_iterator = Iterator<BackwardFrameTraversal>; 82 83 constexpr nsFrameList() : mFirstChild(nullptr), mLastChild(nullptr) {} 84 85 nsFrameList(nsIFrame* aFirstFrame, nsIFrame* aLastFrame) 86 : mFirstChild(aFirstFrame), mLastChild(aLastFrame) { 87 VerifyList(); 88 } 89 90 // nsFrameList is a move-only class by default. Use Clone() if you really want 91 // a copy of this list. 92 nsFrameList(const nsFrameList& aOther) = delete; 93 nsFrameList& operator=(const nsFrameList& aOther) = delete; 94 nsFrameList Clone() const { return nsFrameList(mFirstChild, mLastChild); } 95 96 /** 97 * Transfer frames in aOther to this list. aOther becomes empty after this 98 * operation. 99 */ 100 nsFrameList(nsFrameList&& aOther) 101 : mFirstChild(aOther.mFirstChild), mLastChild(aOther.mLastChild) { 102 aOther.Clear(); 103 VerifyList(); 104 } 105 nsFrameList& operator=(nsFrameList&& aOther) { 106 if (this != &aOther) { 107 MOZ_ASSERT(IsEmpty(), "Assigning to a non-empty list will lose frames!"); 108 mFirstChild = aOther.FirstChild(); 109 mLastChild = aOther.LastChild(); 110 aOther.Clear(); 111 } 112 return *this; 113 } 114 115 /** 116 * Infallibly allocate a nsFrameList from the shell arena. 117 */ 118 void* operator new(size_t sz, mozilla::PresShell* aPresShell); 119 120 /** 121 * Deallocate this list that was allocated from the shell arena. 122 * The list is required to be empty. 123 */ 124 void Delete(mozilla::PresShell* aPresShell); 125 126 /** 127 * For each frame in this list: remove it from the list then call 128 * Destroy() on it with the passed context as an argument. 129 */ 130 void DestroyFrames(mozilla::FrameDestroyContext&); 131 132 void Clear() { mFirstChild = mLastChild = nullptr; } 133 134 /** 135 * Append aFrameList to this list. If aParent is not null, 136 * reparents the newly added frames. Clears out aFrameList and 137 * returns a list slice represening the newly-appended frames. 138 */ 139 Slice AppendFrames(nsContainerFrame* aParent, nsFrameList&& aFrameList) { 140 return InsertFrames(aParent, LastChild(), std::move(aFrameList)); 141 } 142 143 /** 144 * Append aFrame to this list. If aParent is not null, 145 * reparents the newly added frame. 146 */ 147 void AppendFrame(nsContainerFrame* aParent, nsIFrame* aFrame) { 148 AppendFrames(aParent, nsFrameList(aFrame, aFrame)); 149 } 150 151 /** 152 * Take aFrame out of the frame list. This also disconnects aFrame 153 * from the sibling list. The frame must be non-null and present on 154 * this list. 155 */ 156 void RemoveFrame(nsIFrame* aFrame); 157 158 /** 159 * Take all the frames before aFrame out of the frame list; aFrame and all the 160 * frames after it stay in this list. If aFrame is nullptr, remove the entire 161 * frame list. 162 * @param aFrame a frame in this frame list, or nullptr. 163 * @return the removed frames, if any. 164 */ 165 [[nodiscard]] nsFrameList TakeFramesBefore(nsIFrame* aFrame); 166 167 /** 168 * Take all the frames after aFrame out of the frame list; aFrame and all the 169 * frames before it stay in this list. If aFrame is nullptr, removes the 170 * entire list. 171 * @param aFrame a frame in this list, or nullptr. 172 * @return the removed frames, if any. 173 */ 174 [[nodiscard]] nsFrameList TakeFramesAfter(nsIFrame* aFrame); 175 176 /** 177 * Take the first (or last) child (if any) out of the frame list. 178 * @return the first (or last) child, or nullptr if the list is empty 179 */ 180 nsIFrame* RemoveFirstChild(); 181 nsIFrame* RemoveLastChild(); 182 183 /** 184 * The following two functions are intended to be used in concert for 185 * removing a frame from its frame list when the set of possible frame 186 * lists is known in advance, but the exact frame list is unknown. 187 * aFrame must be non-null. 188 * Example use: 189 * bool removed = frameList1.StartRemoveFrame(aFrame) || 190 * frameList2.ContinueRemoveFrame(aFrame) || 191 * frameList3.ContinueRemoveFrame(aFrame); 192 * MOZ_ASSERT(removed); 193 * 194 * @note One of the frame lists MUST contain aFrame, if it's on some other 195 * frame list then the example above will likely lead to crashes. 196 * This function is O(1). 197 * @return true iff aFrame was removed from /some/ list, not necessarily 198 * this one. If it was removed from a different list then it is 199 * guaranteed that that list is still non-empty. 200 * (this method is implemented in nsIFrame.h to be able to inline) 201 */ 202 inline bool StartRemoveFrame(nsIFrame* aFrame); 203 204 /** 205 * Precondition: StartRemoveFrame MUST be called before this. 206 * This function is O(1). 207 * @see StartRemoveFrame 208 * @return true iff aFrame was removed from this list 209 * (this method is implemented in nsIFrame.h to be able to inline) 210 */ 211 inline bool ContinueRemoveFrame(nsIFrame* aFrame); 212 213 /** 214 * Take a frame out of the frame list and then destroy it. 215 * The frame must be non-null and present on this list. 216 */ 217 void DestroyFrame(mozilla::FrameDestroyContext&, nsIFrame*); 218 219 /** 220 * Insert aFrame right after aPrevSibling, or prepend it to this 221 * list if aPrevSibling is null. If aParent is not null, also 222 * reparents newly-added frame. Note that this method always 223 * sets the frame's nextSibling pointer. 224 */ 225 void InsertFrame(nsContainerFrame* aParent, nsIFrame* aPrevSibling, 226 nsIFrame* aFrame) { 227 InsertFrames(aParent, aPrevSibling, nsFrameList(aFrame, aFrame)); 228 } 229 230 /** 231 * Inserts aFrameList into this list after aPrevSibling (at the beginning if 232 * aPrevSibling is null). If aParent is not null, reparents the newly added 233 * frames. Clears out aFrameList and returns a list slice representing the 234 * newly-inserted frames. 235 */ 236 Slice InsertFrames(nsContainerFrame* aParent, nsIFrame* aPrevSibling, 237 nsFrameList&& aFrameList); 238 239 /** 240 * Split this list just before the first frame that matches aPredicate, 241 * and return a nsFrameList containing all the frames before it. The 242 * matched frame and all frames after it stay in this list. If no matched 243 * frame exists, all the frames are drained into the returned list, and 244 * this list ends up empty. 245 * 246 * aPredicate should be of this function signature: bool(nsIFrame*). 247 */ 248 template <typename Predicate> 249 nsFrameList Split(Predicate&& aPredicate) { 250 static_assert( 251 std::is_same< 252 typename mozilla::FunctionTypeTraits<Predicate>::ReturnType, 253 bool>::value && 254 mozilla::FunctionTypeTraits<Predicate>::arity == 1 && 255 std::is_same<typename mozilla::FunctionTypeTraits< 256 Predicate>::template ParameterType<0>, 257 nsIFrame*>::value, 258 "aPredicate should be of this function signature: bool(nsIFrame*)"); 259 260 for (nsIFrame* f : *this) { 261 if (aPredicate(f)) { 262 return TakeFramesBefore(f); 263 } 264 } 265 return std::move(*this); 266 } 267 268 nsIFrame* FirstChild() const { return mFirstChild; } 269 270 nsIFrame* LastChild() const { return mLastChild; } 271 272 nsIFrame* FrameAt(int32_t aIndex) const; 273 int32_t IndexOf(const nsIFrame* aFrame) const; 274 275 bool IsEmpty() const { return nullptr == mFirstChild; } 276 277 bool NotEmpty() const { return nullptr != mFirstChild; } 278 279 /** 280 * Return true if aFrame is on this list. 281 * @note this method has O(n) time complexity over the length of the list 282 * XXXmats: ideally, we should make this function #ifdef DEBUG 283 */ 284 bool ContainsFrame(const nsIFrame* aFrame) const; 285 286 /** 287 * Get the number of frames in this list. Note that currently the 288 * implementation has O(n) time complexity. Do not call it repeatedly in hot 289 * code. 290 * XXXmats: ideally, we should make this function #ifdef DEBUG 291 */ 292 int32_t GetLength() const; 293 294 /** 295 * If this frame list has only one frame, return that frame. 296 * Otherwise, return null. 297 */ 298 nsIFrame* OnlyChild() const { 299 if (FirstChild() == LastChild()) { 300 return FirstChild(); 301 } 302 return nullptr; 303 } 304 305 /** 306 * Call SetParent(aParent) for each frame in this list. 307 * @param aParent the new parent frame, must be non-null 308 */ 309 void ApplySetParent(nsContainerFrame* aParent) const; 310 311 /** 312 * If this frame list is non-empty then append it to aLists as the 313 * aListID child list. 314 */ 315 inline void AppendIfNonempty(nsTArray<mozilla::FrameChildList>* aLists, 316 mozilla::FrameChildListID aListID) const { 317 if (NotEmpty()) { 318 aLists->EmplaceBack(*this, aListID); 319 } 320 } 321 322 /** 323 * Return the frame before this frame in visual order (after Bidi reordering). 324 * If aFrame is null, return the last frame in visual order. 325 */ 326 nsIFrame* GetPrevVisualFor(nsIFrame* aFrame) const; 327 328 /** 329 * Return the frame after this frame in visual order (after Bidi reordering). 330 * If aFrame is null, return the first frame in visual order. 331 */ 332 nsIFrame* GetNextVisualFor(nsIFrame* aFrame) const; 333 334 #ifdef DEBUG_FRAME_DUMP 335 void List(FILE* out) const; 336 #endif 337 338 static inline const nsFrameList& EmptyList() { return sEmptyList; }; 339 340 /** 341 * A class representing a slice of a frame list. 342 */ 343 class Slice { 344 public: 345 // Implicit on purpose, so that we can easily create Slice from nsFrameList 346 // via this impicit constructor. 347 MOZ_IMPLICIT Slice(const nsFrameList& aList) 348 : mStart(aList.FirstChild()), mEnd(nullptr) {} 349 Slice(nsIFrame* aStart, nsIFrame* aEnd) : mStart(aStart), mEnd(aEnd) {} 350 351 iterator begin() const { return iterator(mStart); } 352 const_iterator cbegin() const { return begin(); } 353 iterator end() const { return iterator(mEnd); } 354 const_iterator cend() const { return end(); } 355 356 private: 357 // Our starting frame. 358 nsIFrame* const mStart; 359 360 // The first frame that is NOT in the slice. May be null. 361 nsIFrame* const mEnd; 362 }; 363 364 template <typename FrameTraversal> 365 class Iterator final { 366 public: 367 // It is disputable whether these type definitions are correct, since 368 // operator* doesn't return a reference at all. Also, the iterator_category 369 // can be at most std::input_iterator_tag (rather than 370 // std::bidrectional_iterator_tag, as it might seem), because it is a 371 // stashing iterator. See also, e.g., 372 // https://stackoverflow.com/questions/50909701/what-should-be-iterator-category-for-a-stashing-iterator 373 using value_type = nsIFrame* const; 374 using pointer = value_type*; 375 using reference = value_type&; 376 using difference_type = ptrdiff_t; 377 using iterator_category = std::input_iterator_tag; 378 379 explicit constexpr Iterator(nsIFrame* aCurrent) : mCurrent(aCurrent) {} 380 381 nsIFrame* operator*() const { return mCurrent; } 382 383 Iterator& operator++() { 384 mCurrent = FrameTraversal::Next(mCurrent); 385 return *this; 386 } 387 Iterator& operator--() { 388 mCurrent = FrameTraversal::Prev(mCurrent); 389 return *this; 390 } 391 392 Iterator operator++(int) { 393 auto ret = *this; 394 ++*this; 395 return ret; 396 } 397 Iterator operator--(int) { 398 auto ret = *this; 399 --*this; 400 return ret; 401 } 402 403 bool operator==(const Iterator<FrameTraversal>& aOther) const = default; 404 bool operator!=(const Iterator<FrameTraversal>& aOther) const = default; 405 406 private: 407 nsIFrame* mCurrent; 408 }; 409 410 iterator begin() const { return iterator(mFirstChild); } 411 const_iterator cbegin() const { return begin(); } 412 iterator end() const { return iterator(nullptr); } 413 const_iterator cend() const { return end(); } 414 reverse_iterator rbegin() const { return reverse_iterator(mLastChild); } 415 const_reverse_iterator crbegin() const { return rbegin(); } 416 reverse_iterator rend() const { return reverse_iterator(nullptr); } 417 const_reverse_iterator crend() const { return rend(); } 418 419 private: 420 void operator delete(void*) = delete; 421 422 static const nsFrameList sEmptyList; 423 424 #ifdef DEBUG_FRAME_LIST 425 void VerifyList() const; 426 #else 427 void VerifyList() const {} 428 #endif 429 430 protected: 431 /** 432 * Disconnect aFrame from its siblings. This must only be called if aFrame 433 * is NOT the first or last sibling, because otherwise its nsFrameList will 434 * have a stale mFirst/LastChild pointer. This precondition is asserted. 435 * This function is O(1). 436 */ 437 static void UnhookFrameFromSiblings(nsIFrame* aFrame); 438 439 nsIFrame* mFirstChild; 440 nsIFrame* mLastChild; 441 }; 442 443 namespace mozilla { 444 445 #ifdef DEBUG_FRAME_DUMP 446 extern const char* ChildListName(FrameChildListID aListID); 447 #endif 448 449 using FrameChildListIDs = EnumSet<FrameChildListID>; 450 451 class FrameChildList { 452 public: 453 FrameChildList(const nsFrameList& aList, FrameChildListID aID) 454 : mList(aList.Clone()), mID(aID) {} 455 nsFrameList mList; 456 FrameChildListID mID; 457 }; 458 459 /** 460 * Simple "auto_ptr" for nsFrameLists allocated from the shell arena. 461 * The frame list given to the constructor will be deallocated (if non-null) 462 * in the destructor. The frame list must then be empty. 463 */ 464 class MOZ_RAII AutoFrameListPtr final { 465 public: 466 AutoFrameListPtr(nsPresContext* aPresContext, nsFrameList* aFrameList) 467 : mPresContext(aPresContext), mFrameList(aFrameList) {} 468 ~AutoFrameListPtr(); 469 operator nsFrameList*() const { return mFrameList; } 470 nsFrameList* operator->() const { return mFrameList; } 471 472 private: 473 nsPresContext* mPresContext; 474 nsFrameList* mFrameList; 475 }; 476 477 } // namespace mozilla 478 479 #endif /* nsFrameList_h___ */