mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	After moving FrameChildListID into mozilla namespace, `kPrincipalList` etc. are also exposed in the mozilla namespace. In the next part, I'll convert FrameChildListID enum into an enum class, so the naming pollution shouldn't be an issue. This patch has a nice side effect that it is now easier to remove all the aliases of FrameChildListID (`kPrincipalList` etc.) defined in multiple places since it is confusion to have the same thing written in different ways, e.g. `nsIFrame::kPrincipalList`, `mozilla::layout::kPrincipalList`, `FrameChildListID::kPrincipalList`, `kPrincipalList`. This patch doesn't change behavior. Differential Revision: https://phabricator.services.mozilla.com/D161863
		
			
				
	
	
		
			268 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
	
		
			8.8 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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
 |