CSSOrderAwareFrameIterator.h (9031B)
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 /* Iterator class for frame lists that respect CSS "order" during layout */ 8 9 #ifndef mozilla_CSSOrderAwareFrameIterator_h 10 #define mozilla_CSSOrderAwareFrameIterator_h 11 12 #include <limits> 13 14 #include "mozilla/Assertions.h" 15 #include "mozilla/Maybe.h" 16 #include "nsFrameList.h" 17 #include "nsIFrame.h" 18 19 namespace mozilla { 20 21 /** 22 * CSSOrderAwareFrameIteratorT is a base class for iterators that traverse 23 * child frame lists in a way that respects their CSS "order" property. 24 * https://drafts.csswg.org/css-flexbox-1/#order-property 25 * This class isn't meant to be directly used; instead, use its specializations 26 * CSSOrderAwareFrameIterator and ReverseCSSOrderAwareFrameIterator. 27 * 28 * Client code can use a CSSOrderAwareFrameIterator to traverse lower-"order" 29 * frames before higher-"order" ones (as required for correct flex/grid 30 * layout), without modifying the frames' actual ordering within the frame 31 * tree. Any frames with equal "order" values will be traversed consecutively, 32 * in frametree order (which is generally equivalent to DOM order). 33 * 34 * By default, the iterator will skip past placeholder frames during 35 * iteration. You can adjust this behavior via the ChildFilter constructor arg. 36 * 37 * By default, the iterator will use the frames' CSS "order" property to 38 * determine its traversal order. However, it can be customized to instead use 39 * the (prefixed) legacy "box-ordinal-group" CSS property instead, as part of 40 * emulating "display:-webkit-box" containers. This behavior can be customized 41 * using the OrderingProperty constructor arg. 42 * 43 * A few notes on performance: 44 * - If you're iterating multiple times in a row, it's a good idea to reuse 45 * the same iterator (calling Reset() to start each new iteration), rather than 46 * instantiating a new one each time. 47 * - If you have foreknowledge of the list's orderedness, you can save some 48 * time by passing eKnownOrdered or eKnownUnordered to the constructor (which 49 * will skip some checks during construction). 50 * 51 * Warning: if the given frame list changes, it makes the iterator invalid and 52 * bad things will happen if it's used further. 53 */ 54 template <typename Iterator> 55 class CSSOrderAwareFrameIteratorT { 56 public: 57 enum class OrderState { Unknown, Ordered, Unordered }; 58 enum class ChildFilter { SkipPlaceholders, IncludeAll }; 59 enum class OrderingProperty { 60 Order, // Default behavior: use "order". 61 BoxOrdinalGroup // Legacy behavior: use prefixed "box-ordinal-group". 62 }; 63 CSSOrderAwareFrameIteratorT( 64 nsIFrame* aContainer, FrameChildListID aListID, 65 ChildFilter aFilter = ChildFilter::SkipPlaceholders, 66 OrderState aState = OrderState::Unknown, 67 OrderingProperty aOrderProp = OrderingProperty::Order) 68 : mChildren(aContainer->GetChildList(aListID)), 69 mArrayIndex(0), 70 mItemIndex(0), 71 mSkipPlaceholders(aFilter == ChildFilter::SkipPlaceholders) 72 #ifdef DEBUG 73 , 74 mContainer(aContainer), 75 mListID(aListID) 76 #endif 77 { 78 MOZ_ASSERT(CanUse(aContainer), 79 "Only use this iterator in a container that honors 'order'"); 80 81 size_t count = 0; 82 bool isOrdered = aState != OrderState::Unordered; 83 if (aState == OrderState::Unknown) { 84 auto maxOrder = std::numeric_limits<int32_t>::min(); 85 for (auto* child : mChildren) { 86 ++count; 87 88 int32_t order = aOrderProp == OrderingProperty::BoxOrdinalGroup 89 ? child->StyleXUL()->mBoxOrdinal 90 : child->StylePosition()->mOrder; 91 92 if (order < maxOrder) { 93 isOrdered = false; 94 break; 95 } 96 maxOrder = order; 97 } 98 } 99 if (isOrdered) { 100 mIter.emplace(begin(mChildren)); 101 mIterEnd.emplace(end(mChildren)); 102 } else { 103 count *= 2; // XXX somewhat arbitrary estimate for now... 104 mArray.emplace(count); 105 for (Iterator i(begin(mChildren)), iEnd(end(mChildren)); i != iEnd; ++i) { 106 mArray->AppendElement(*i); 107 } 108 auto comparator = aOrderProp == OrderingProperty::BoxOrdinalGroup 109 ? CSSBoxOrdinalGroupComparator 110 : CSSOrderComparator; 111 mArray->StableSort(comparator); 112 } 113 114 if (mSkipPlaceholders) { 115 SkipPlaceholders(); 116 } 117 } 118 119 CSSOrderAwareFrameIteratorT(CSSOrderAwareFrameIteratorT&&) = default; 120 121 ~CSSOrderAwareFrameIteratorT() { 122 MOZ_ASSERT(IsForward() == mItemCount.isNothing()); 123 } 124 125 bool IsForward() const; 126 127 nsIFrame* get() const { 128 MOZ_ASSERT(!AtEnd()); 129 if (mIter.isSome()) { 130 return **mIter; 131 } 132 return (*mArray)[mArrayIndex]; 133 } 134 135 nsIFrame* operator*() const { return get(); } 136 137 /** 138 * Return the child index of the current item, placeholders not counted. 139 * It's forbidden to call this method when the current frame is placeholder. 140 */ 141 size_t ItemIndex() const { 142 MOZ_ASSERT(!AtEnd()); 143 MOZ_ASSERT(!(**this)->IsPlaceholderFrame(), 144 "MUST not call this when at a placeholder"); 145 MOZ_ASSERT(IsForward() || mItemIndex < *mItemCount, 146 "Returning an out-of-range mItemIndex..."); 147 return mItemIndex; 148 } 149 150 void SetItemCount(size_t aItemCount) { 151 MOZ_ASSERT(mIter.isSome() || aItemCount <= mArray->Length(), 152 "item count mismatch"); 153 mItemCount.emplace(aItemCount); 154 // Note: it's OK if mItemIndex underflows -- ItemIndex() 155 // will not be called unless there is at least one item. 156 mItemIndex = IsForward() ? 0 : *mItemCount - 1; 157 } 158 159 /** 160 * Skip over placeholder children. 161 */ 162 void SkipPlaceholders() { 163 if (mIter.isSome()) { 164 for (; *mIter != *mIterEnd; ++*mIter) { 165 nsIFrame* child = **mIter; 166 if (!child->IsPlaceholderFrame()) { 167 return; 168 } 169 } 170 } else { 171 for (; mArrayIndex < mArray->Length(); ++mArrayIndex) { 172 nsIFrame* child = (*mArray)[mArrayIndex]; 173 if (!child->IsPlaceholderFrame()) { 174 return; 175 } 176 } 177 } 178 } 179 180 bool AtEnd() const { 181 MOZ_ASSERT(mIter.isSome() || mArrayIndex <= mArray->Length()); 182 return mIter ? (*mIter == *mIterEnd) : mArrayIndex >= mArray->Length(); 183 } 184 185 void Next() { 186 #ifdef DEBUG 187 MOZ_ASSERT(!AtEnd()); 188 const nsFrameList& list = mContainer->GetChildList(mListID); 189 MOZ_ASSERT(list.FirstChild() == mChildren.FirstChild() && 190 list.LastChild() == mChildren.LastChild(), 191 "the list of child frames must not change while iterating!"); 192 #endif 193 if (mSkipPlaceholders || !(**this)->IsPlaceholderFrame()) { 194 IsForward() ? ++mItemIndex : --mItemIndex; 195 } 196 if (mIter.isSome()) { 197 ++*mIter; 198 } else { 199 ++mArrayIndex; 200 } 201 if (mSkipPlaceholders) { 202 SkipPlaceholders(); 203 } 204 } 205 206 void Reset(ChildFilter aFilter = ChildFilter::SkipPlaceholders) { 207 if (mIter.isSome()) { 208 mIter.reset(); 209 mIter.emplace(begin(mChildren)); 210 mIterEnd.reset(); 211 mIterEnd.emplace(end(mChildren)); 212 } else { 213 mArrayIndex = 0; 214 } 215 mItemIndex = IsForward() ? 0 : *mItemCount - 1; 216 mSkipPlaceholders = aFilter == ChildFilter::SkipPlaceholders; 217 if (mSkipPlaceholders) { 218 SkipPlaceholders(); 219 } 220 } 221 222 bool IsValid() const { return mIter.isSome() || mArray.isSome(); } 223 224 void Invalidate() { 225 mIter.reset(); 226 mArray.reset(); 227 } 228 229 bool ItemsAreAlreadyInOrder() const { return mIter.isSome(); } 230 231 private: 232 static bool CanUse(const nsIFrame*); 233 234 Iterator begin(const nsFrameList& aList); 235 Iterator end(const nsFrameList& aList); 236 237 static int CSSOrderComparator(nsIFrame* const& a, nsIFrame* const& b); 238 static int CSSBoxOrdinalGroupComparator(nsIFrame* const& a, 239 nsIFrame* const& b); 240 241 const nsFrameList& mChildren; 242 // Used if child list is already in ascending 'order'. 243 Maybe<Iterator> mIter; 244 Maybe<Iterator> mIterEnd; 245 // Used if child list is *not* in ascending 'order'. 246 // This array is pre-sorted in reverse order for a reverse iterator. 247 Maybe<nsTArray<nsIFrame*>> mArray; 248 size_t mArrayIndex; 249 // The index of the current item (placeholders excluded). 250 size_t mItemIndex; 251 // The number of items (placeholders excluded). 252 // It's only initialized and used in a reverse iterator. 253 Maybe<size_t> mItemCount; 254 // Skip placeholder children in the iteration? 255 bool mSkipPlaceholders; 256 #ifdef DEBUG 257 nsIFrame* mContainer; 258 FrameChildListID mListID; 259 #endif 260 }; 261 262 using CSSOrderAwareFrameIterator = 263 CSSOrderAwareFrameIteratorT<nsFrameList::iterator>; 264 using ReverseCSSOrderAwareFrameIterator = 265 CSSOrderAwareFrameIteratorT<nsFrameList::reverse_iterator>; 266 267 } // namespace mozilla 268 269 #endif // mozilla_CSSOrderAwareFrameIterator_h