layout/base/RestyleTracker.h

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/layout/base/RestyleTracker.h	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,420 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +/**
    1.10 + * A class which manages pending restyles.  This handles keeping track
    1.11 + * of what nodes restyles need to happen on and so forth.
    1.12 + */
    1.13 +
    1.14 +#ifndef mozilla_RestyleTracker_h
    1.15 +#define mozilla_RestyleTracker_h
    1.16 +
    1.17 +#include "mozilla/dom/Element.h"
    1.18 +#include "nsDataHashtable.h"
    1.19 +#include "nsIFrame.h"
    1.20 +#include "mozilla/SplayTree.h"
    1.21 +
    1.22 +namespace mozilla {
    1.23 +
    1.24 +class RestyleManager;
    1.25 +
    1.26 +/** 
    1.27 + * Helper class that collects a list of frames that need
    1.28 + * UpdateOverflow() called on them, and coalesces them
    1.29 + * to avoid walking up the same ancestor tree multiple times.
    1.30 + */
    1.31 +class OverflowChangedTracker
    1.32 +{
    1.33 +public:
    1.34 +  enum ChangeKind {
    1.35 +    /**
    1.36 +     * The frame was explicitly added as a result of
    1.37 +     * nsChangeHint_UpdatePostTransformOverflow and hence may have had a style
    1.38 +     * change that changes its geometry relative to parent, without reflowing.
    1.39 +     */
    1.40 +    TRANSFORM_CHANGED,
    1.41 +    /**
    1.42 +     * The overflow areas of children have changed
    1.43 +     * and we need to call UpdateOverflow on the frame.
    1.44 +     */
    1.45 +    CHILDREN_CHANGED,
    1.46 +    /**
    1.47 +     * The overflow areas of children have changed
    1.48 +     * and we need to call UpdateOverflow on the frame.
    1.49 +     * Also call UpdateOverflow on the parent even if the
    1.50 +     * overflow areas of the frame does not change.
    1.51 +     */
    1.52 +    CHILDREN_AND_PARENT_CHANGED
    1.53 +  };
    1.54 +
    1.55 +  OverflowChangedTracker() :
    1.56 +    mSubtreeRoot(nullptr)
    1.57 +  {}
    1.58 +
    1.59 +  ~OverflowChangedTracker()
    1.60 +  {
    1.61 +    NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
    1.62 +  }
    1.63 +
    1.64 +  /**
    1.65 +   * Add a frame that has had a style change, and needs its
    1.66 +   * overflow updated.
    1.67 +   *
    1.68 +   * If there are pre-transform overflow areas stored for this
    1.69 +   * frame, then we will call FinishAndStoreOverflow with those
    1.70 +   * areas instead of UpdateOverflow().
    1.71 +   *
    1.72 +   * If the overflow area changes, then UpdateOverflow will also
    1.73 +   * be called on the parent.
    1.74 +   */
    1.75 +  void AddFrame(nsIFrame* aFrame, ChangeKind aChangeKind) {
    1.76 +    uint32_t depth = aFrame->GetDepthInFrameTree();
    1.77 +    Entry *entry = nullptr;
    1.78 +    if (!mEntryList.empty()) {
    1.79 +      entry = mEntryList.find(Entry(aFrame, depth));
    1.80 +    }
    1.81 +    if (entry == nullptr) {
    1.82 +      // Add new entry.
    1.83 +      mEntryList.insert(new Entry(aFrame, depth, aChangeKind));
    1.84 +    } else {
    1.85 +      // Update the existing entry if the new value is stronger.
    1.86 +      entry->mChangeKind = std::max(entry->mChangeKind, aChangeKind);
    1.87 +    }
    1.88 +  }
    1.89 +
    1.90 +  /**
    1.91 +   * Remove a frame.
    1.92 +   */
    1.93 +  void RemoveFrame(nsIFrame* aFrame) {
    1.94 +    if (mEntryList.empty()) {
    1.95 +      return;
    1.96 +    }
    1.97 +
    1.98 +    uint32_t depth = aFrame->GetDepthInFrameTree();
    1.99 +    if (mEntryList.find(Entry(aFrame, depth))) {
   1.100 +      delete mEntryList.remove(Entry(aFrame, depth));
   1.101 +    }
   1.102 +  }
   1.103 +
   1.104 +  /**
   1.105 +   * Set the subtree root to limit overflow updates. This must be set if and
   1.106 +   * only if currently reflowing aSubtreeRoot, to ensure overflow changes will
   1.107 +   * still propagate correctly.
   1.108 +   */
   1.109 +  void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) {
   1.110 +    mSubtreeRoot = aSubtreeRoot;
   1.111 +  }
   1.112 +
   1.113 +  /**
   1.114 +   * Update the overflow of all added frames, and clear the entry list.
   1.115 +   *
   1.116 +   * Start from those deepest in the frame tree and works upwards. This stops 
   1.117 +   * us from processing the same frame twice.
   1.118 +   */
   1.119 +  void Flush() {
   1.120 +    while (!mEntryList.empty()) {
   1.121 +      Entry *entry = mEntryList.removeMin();
   1.122 +      nsIFrame *frame = entry->mFrame;
   1.123 +
   1.124 +      bool overflowChanged = false;
   1.125 +      if (entry->mChangeKind == CHILDREN_AND_PARENT_CHANGED) {
   1.126 +        // Need to union the overflow areas of the children.
   1.127 +        // Always update the parent, even if the overflow does not change.
   1.128 +        frame->UpdateOverflow();
   1.129 +        overflowChanged = true;
   1.130 +      } else if (entry->mChangeKind == CHILDREN_CHANGED) {
   1.131 +        // Need to union the overflow areas of the children.
   1.132 +        // Only update the parent if the overflow changes.
   1.133 +        overflowChanged = frame->UpdateOverflow();
   1.134 +      } else {
   1.135 +        // Take a faster path that doesn't require unioning the overflow areas
   1.136 +        // of our children.
   1.137 +
   1.138 +#ifdef DEBUG
   1.139 +        bool hasInitialOverflowPropertyApplied = false;
   1.140 +        frame->Properties().Get(nsIFrame::DebugInitialOverflowPropertyApplied(),
   1.141 +                                 &hasInitialOverflowPropertyApplied);
   1.142 +        NS_ASSERTION(hasInitialOverflowPropertyApplied,
   1.143 +                     "InitialOverflowProperty must be set first.");
   1.144 +#endif
   1.145 +
   1.146 +        nsOverflowAreas* overflow = 
   1.147 +          static_cast<nsOverflowAreas*>(frame->Properties().Get(nsIFrame::InitialOverflowProperty()));
   1.148 +        if (overflow) {
   1.149 +          // FinishAndStoreOverflow will change the overflow areas passed in,
   1.150 +          // so make a copy.
   1.151 +          nsOverflowAreas overflowCopy = *overflow;
   1.152 +          frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize());
   1.153 +        } else {
   1.154 +          nsRect bounds(nsPoint(0, 0), frame->GetSize());
   1.155 +          nsOverflowAreas boundsOverflow;
   1.156 +          boundsOverflow.SetAllTo(bounds);
   1.157 +          frame->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
   1.158 +        }
   1.159 +
   1.160 +        // We can't tell if the overflow changed, so be conservative
   1.161 +        overflowChanged = true;
   1.162 +      }
   1.163 +
   1.164 +      // If the frame style changed (e.g. positioning offsets)
   1.165 +      // then we need to update the parent with the overflow areas of its
   1.166 +      // children.
   1.167 +      if (overflowChanged) {
   1.168 +        nsIFrame *parent = frame->GetParent();
   1.169 +        if (parent && parent != mSubtreeRoot) {
   1.170 +          Entry* parentEntry = mEntryList.find(Entry(parent, entry->mDepth - 1));
   1.171 +          if (parentEntry) {
   1.172 +            parentEntry->mChangeKind = std::max(parentEntry->mChangeKind, CHILDREN_CHANGED);
   1.173 +          } else {
   1.174 +            mEntryList.insert(new Entry(parent, entry->mDepth - 1, CHILDREN_CHANGED));
   1.175 +          }
   1.176 +        }
   1.177 +      }
   1.178 +      delete entry;
   1.179 +    }
   1.180 +  }
   1.181 +  
   1.182 +private:
   1.183 +  struct Entry : SplayTreeNode<Entry>
   1.184 +  {
   1.185 +    Entry(nsIFrame* aFrame, uint32_t aDepth, ChangeKind aChangeKind = CHILDREN_CHANGED)
   1.186 +      : mFrame(aFrame)
   1.187 +      , mDepth(aDepth)
   1.188 +      , mChangeKind(aChangeKind)
   1.189 +    {}
   1.190 +
   1.191 +    bool operator==(const Entry& aOther) const
   1.192 +    {
   1.193 +      return mFrame == aOther.mFrame;
   1.194 +    }
   1.195 + 
   1.196 +    /**
   1.197 +     * Sort by *reverse* depth in the tree, and break ties with
   1.198 +     * the frame pointer.
   1.199 +     */
   1.200 +    bool operator<(const Entry& aOther) const
   1.201 +    {
   1.202 +      if (mDepth == aOther.mDepth) {
   1.203 +        return mFrame < aOther.mFrame;
   1.204 +      }
   1.205 +      return mDepth > aOther.mDepth; /* reverse, want "min" to be deepest */
   1.206 +    }
   1.207 +
   1.208 +    static int compare(const Entry& aOne, const Entry& aTwo)
   1.209 +    {
   1.210 +      if (aOne == aTwo) {
   1.211 +        return 0;
   1.212 +      } else if (aOne < aTwo) {
   1.213 +        return -1;
   1.214 +      } else {
   1.215 +        return 1;
   1.216 +      }
   1.217 +    }
   1.218 +
   1.219 +    nsIFrame* mFrame;
   1.220 +    /* Depth in the frame tree */
   1.221 +    uint32_t mDepth;
   1.222 +    ChangeKind mChangeKind;
   1.223 +  };
   1.224 +
   1.225 +  /* A list of frames to process, sorted by their depth in the frame tree */
   1.226 +  SplayTree<Entry, Entry> mEntryList;
   1.227 +
   1.228 +  /* Don't update overflow of this frame or its ancestors. */
   1.229 +  const nsIFrame* mSubtreeRoot;
   1.230 +};
   1.231 +
   1.232 +class RestyleTracker {
   1.233 +public:
   1.234 +  typedef mozilla::dom::Element Element;
   1.235 +
   1.236 +  RestyleTracker(uint32_t aRestyleBits) :
   1.237 +    mRestyleBits(aRestyleBits),
   1.238 +    mHaveLaterSiblingRestyles(false)
   1.239 +  {
   1.240 +    NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0,
   1.241 +                    "Why do we have these bits set?");
   1.242 +    NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
   1.243 +                    "Must have a restyle flag");
   1.244 +    NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) !=
   1.245 +                      ELEMENT_PENDING_RESTYLE_FLAGS,
   1.246 +                    "Shouldn't have both restyle flags set");
   1.247 +    NS_PRECONDITION((mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
   1.248 +                    "Must have root flag");
   1.249 +    NS_PRECONDITION((mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS) !=
   1.250 +                    (ELEMENT_ALL_RESTYLE_FLAGS & ~ELEMENT_PENDING_RESTYLE_FLAGS),
   1.251 +                    "Shouldn't have both root flags");
   1.252 +  }
   1.253 +
   1.254 +  void Init(RestyleManager* aRestyleManager) {
   1.255 +    mRestyleManager = aRestyleManager;
   1.256 +  }
   1.257 +
   1.258 +  uint32_t Count() const {
   1.259 +    return mPendingRestyles.Count();
   1.260 +  }
   1.261 +
   1.262 +  /**
   1.263 +   * Add a restyle for the given element to the tracker.  Returns true
   1.264 +   * if the element already had eRestyle_LaterSiblings set on it.
   1.265 +   */
   1.266 +  bool AddPendingRestyle(Element* aElement, nsRestyleHint aRestyleHint,
   1.267 +                           nsChangeHint aMinChangeHint);
   1.268 +
   1.269 +  /**
   1.270 +   * Process the restyles we've been tracking.
   1.271 +   */
   1.272 +  void ProcessRestyles() {
   1.273 +    // Fast-path the common case (esp. for the animation restyle
   1.274 +    // tracker) of not having anything to do.
   1.275 +    if (mPendingRestyles.Count()) {
   1.276 +      DoProcessRestyles();
   1.277 +    }
   1.278 +  }
   1.279 +
   1.280 +  // Return our ELEMENT_HAS_PENDING_(ANIMATION_)RESTYLE bit
   1.281 +  uint32_t RestyleBit() const {
   1.282 +    return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS;
   1.283 +  }
   1.284 +
   1.285 +  // Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit
   1.286 +  uint32_t RootBit() const {
   1.287 +    return mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS;
   1.288 +  }
   1.289 +  
   1.290 +  struct RestyleData {
   1.291 +    nsRestyleHint mRestyleHint;  // What we want to restyle
   1.292 +    nsChangeHint  mChangeHint;   // The minimal change hint for "self"
   1.293 +  };
   1.294 +
   1.295 +  /**
   1.296 +   * If the given Element has a restyle pending for it, return the
   1.297 +   * relevant restyle data.  This function will clear everything other
   1.298 +   * than a possible eRestyle_LaterSiblings hint for aElement out of
   1.299 +   * our hashtable.  The returned aData will never have an
   1.300 +   * eRestyle_LaterSiblings hint in it.
   1.301 +   *
   1.302 +   * The return value indicates whether any restyle data was found for
   1.303 +   * the element.  If false is returned, then the state of *aData is
   1.304 +   * undefined.
   1.305 +   */
   1.306 +  bool GetRestyleData(Element* aElement, RestyleData* aData);
   1.307 +
   1.308 +  /**
   1.309 +   * The document we're associated with.
   1.310 +   */
   1.311 +  inline nsIDocument* Document() const;
   1.312 +
   1.313 +  struct RestyleEnumerateData : public RestyleData {
   1.314 +    nsRefPtr<Element> mElement;
   1.315 +  };
   1.316 +
   1.317 +private:
   1.318 +  /**
   1.319 +   * Handle a single mPendingRestyles entry.  aRestyleHint must not
   1.320 +   * include eRestyle_LaterSiblings; that needs to be dealt with
   1.321 +   * before calling this function.
   1.322 +   */
   1.323 +  inline void ProcessOneRestyle(Element* aElement,
   1.324 +                                nsRestyleHint aRestyleHint,
   1.325 +                                nsChangeHint aChangeHint);
   1.326 +
   1.327 +  /**
   1.328 +   * The guts of our restyle processing.
   1.329 +   */
   1.330 +  void DoProcessRestyles();
   1.331 +
   1.332 +  typedef nsDataHashtable<nsISupportsHashKey, RestyleData> PendingRestyleTable;
   1.333 +  typedef nsAutoTArray< nsRefPtr<Element>, 32> RestyleRootArray;
   1.334 +  // Our restyle bits.  These will be a subset of ELEMENT_ALL_RESTYLE_FLAGS, and
   1.335 +  // will include one flag from ELEMENT_PENDING_RESTYLE_FLAGS and one flag
   1.336 +  // that's not in ELEMENT_PENDING_RESTYLE_FLAGS.
   1.337 +  uint32_t mRestyleBits;
   1.338 +  RestyleManager* mRestyleManager; // Owns us
   1.339 +  // A hashtable that maps elements to RestyleData structs.  The
   1.340 +  // values only make sense if the element's current document is our
   1.341 +  // document and it has our RestyleBit() flag set.  In particular,
   1.342 +  // said bit might not be set if the element had a restyle posted and
   1.343 +  // then was moved around in the DOM.
   1.344 +  PendingRestyleTable mPendingRestyles;
   1.345 +  // An array that keeps track of our possible restyle roots.  This
   1.346 +  // maintains the invariant that if A and B are both restyle roots
   1.347 +  // and A is an ancestor of B then A will come after B in the array.
   1.348 +  // We maintain this invariant by checking whether an element has an
   1.349 +  // ancestor with the restyle root bit set before appending it to the
   1.350 +  // array.
   1.351 +  RestyleRootArray mRestyleRoots;
   1.352 +  // True if we have some entries with the eRestyle_LaterSiblings
   1.353 +  // flag.  We need this to avoid enumerating the hashtable looking
   1.354 +  // for such entries when we can't possibly have any.
   1.355 +  bool mHaveLaterSiblingRestyles;
   1.356 +};
   1.357 +
   1.358 +inline bool RestyleTracker::AddPendingRestyle(Element* aElement,
   1.359 +                                                nsRestyleHint aRestyleHint,
   1.360 +                                                nsChangeHint aMinChangeHint)
   1.361 +{
   1.362 +  RestyleData existingData;
   1.363 +  existingData.mRestyleHint = nsRestyleHint(0);
   1.364 +  existingData.mChangeHint = NS_STYLE_HINT_NONE;
   1.365 +
   1.366 +  // Check the RestyleBit() flag before doing the hashtable Get, since
   1.367 +  // it's possible that the data in the hashtable isn't actually
   1.368 +  // relevant anymore (if the flag is not set).
   1.369 +  if (aElement->HasFlag(RestyleBit())) {
   1.370 +    mPendingRestyles.Get(aElement, &existingData);
   1.371 +  } else {
   1.372 +    aElement->SetFlags(RestyleBit());
   1.373 +  }
   1.374 +
   1.375 +  bool hadRestyleLaterSiblings =
   1.376 +    (existingData.mRestyleHint & eRestyle_LaterSiblings) != 0;
   1.377 +  existingData.mRestyleHint =
   1.378 +    nsRestyleHint(existingData.mRestyleHint | aRestyleHint);
   1.379 +  NS_UpdateHint(existingData.mChangeHint, aMinChangeHint);
   1.380 +
   1.381 +  mPendingRestyles.Put(aElement, existingData);
   1.382 +
   1.383 +  // We can only treat this element as a restyle root if we would
   1.384 +  // actually restyle its descendants (so either call
   1.385 +  // ReResolveStyleContext on it or just reframe it).
   1.386 +  if ((aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) ||
   1.387 +      (aMinChangeHint & nsChangeHint_ReconstructFrame)) {
   1.388 +    for (const Element* cur = aElement; !cur->HasFlag(RootBit()); ) {
   1.389 +      nsIContent* parent = cur->GetFlattenedTreeParent();
   1.390 +      // Stop if we have no parent or the parent is not an element or
   1.391 +      // we're part of the viewport scrollbars (because those are not
   1.392 +      // frametree descendants of the primary frame of the root
   1.393 +      // element).
   1.394 +      // XXXbz maybe the primary frame of the root should be the root scrollframe?
   1.395 +      if (!parent || !parent->IsElement() ||
   1.396 +          // If we've hit the root via a native anonymous kid and that
   1.397 +          // this native anonymous kid is not obviously a descendant
   1.398 +          // of the root's primary frame, assume we're under the root
   1.399 +          // scrollbars.  Since those don't get reresolved when
   1.400 +          // reresolving the root, we need to make sure to add the
   1.401 +          // element to mRestyleRoots.
   1.402 +          (cur->IsInNativeAnonymousSubtree() && !parent->GetParent() &&
   1.403 +           cur->GetPrimaryFrame() &&
   1.404 +           cur->GetPrimaryFrame()->GetParent() != parent->GetPrimaryFrame())) {
   1.405 +        mRestyleRoots.AppendElement(aElement);
   1.406 +        break;
   1.407 +      }
   1.408 +      cur = parent->AsElement();
   1.409 +    }
   1.410 +    // At this point some ancestor of aElement (possibly aElement
   1.411 +    // itself) is in mRestyleRoots.  Set the root bit on aElement, to
   1.412 +    // speed up searching for an existing root on its descendants.
   1.413 +    aElement->SetFlags(RootBit());
   1.414 +  }
   1.415 +
   1.416 +  mHaveLaterSiblingRestyles =
   1.417 +    mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
   1.418 +  return hadRestyleLaterSiblings;
   1.419 +}
   1.420 +
   1.421 +} // namespace mozilla
   1.422 +
   1.423 +#endif /* mozilla_RestyleTracker_h */

mercurial