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