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