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 */