layout/base/RestyleTracker.h

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 /**
michael@0 7 * A class which manages pending restyles. This handles keeping track
michael@0 8 * of what nodes restyles need to happen on and so forth.
michael@0 9 */
michael@0 10
michael@0 11 #ifndef mozilla_RestyleTracker_h
michael@0 12 #define mozilla_RestyleTracker_h
michael@0 13
michael@0 14 #include "mozilla/dom/Element.h"
michael@0 15 #include "nsDataHashtable.h"
michael@0 16 #include "nsIFrame.h"
michael@0 17 #include "mozilla/SplayTree.h"
michael@0 18
michael@0 19 namespace mozilla {
michael@0 20
michael@0 21 class RestyleManager;
michael@0 22
michael@0 23 /**
michael@0 24 * Helper class that collects a list of frames that need
michael@0 25 * UpdateOverflow() called on them, and coalesces them
michael@0 26 * to avoid walking up the same ancestor tree multiple times.
michael@0 27 */
michael@0 28 class OverflowChangedTracker
michael@0 29 {
michael@0 30 public:
michael@0 31 enum ChangeKind {
michael@0 32 /**
michael@0 33 * The frame was explicitly added as a result of
michael@0 34 * nsChangeHint_UpdatePostTransformOverflow and hence may have had a style
michael@0 35 * change that changes its geometry relative to parent, without reflowing.
michael@0 36 */
michael@0 37 TRANSFORM_CHANGED,
michael@0 38 /**
michael@0 39 * The overflow areas of children have changed
michael@0 40 * and we need to call UpdateOverflow on the frame.
michael@0 41 */
michael@0 42 CHILDREN_CHANGED,
michael@0 43 /**
michael@0 44 * The overflow areas of children have changed
michael@0 45 * and we need to call UpdateOverflow on the frame.
michael@0 46 * Also call UpdateOverflow on the parent even if the
michael@0 47 * overflow areas of the frame does not change.
michael@0 48 */
michael@0 49 CHILDREN_AND_PARENT_CHANGED
michael@0 50 };
michael@0 51
michael@0 52 OverflowChangedTracker() :
michael@0 53 mSubtreeRoot(nullptr)
michael@0 54 {}
michael@0 55
michael@0 56 ~OverflowChangedTracker()
michael@0 57 {
michael@0 58 NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
michael@0 59 }
michael@0 60
michael@0 61 /**
michael@0 62 * Add a frame that has had a style change, and needs its
michael@0 63 * overflow updated.
michael@0 64 *
michael@0 65 * If there are pre-transform overflow areas stored for this
michael@0 66 * frame, then we will call FinishAndStoreOverflow with those
michael@0 67 * areas instead of UpdateOverflow().
michael@0 68 *
michael@0 69 * If the overflow area changes, then UpdateOverflow will also
michael@0 70 * be called on the parent.
michael@0 71 */
michael@0 72 void AddFrame(nsIFrame* aFrame, ChangeKind aChangeKind) {
michael@0 73 uint32_t depth = aFrame->GetDepthInFrameTree();
michael@0 74 Entry *entry = nullptr;
michael@0 75 if (!mEntryList.empty()) {
michael@0 76 entry = mEntryList.find(Entry(aFrame, depth));
michael@0 77 }
michael@0 78 if (entry == nullptr) {
michael@0 79 // Add new entry.
michael@0 80 mEntryList.insert(new Entry(aFrame, depth, aChangeKind));
michael@0 81 } else {
michael@0 82 // Update the existing entry if the new value is stronger.
michael@0 83 entry->mChangeKind = std::max(entry->mChangeKind, aChangeKind);
michael@0 84 }
michael@0 85 }
michael@0 86
michael@0 87 /**
michael@0 88 * Remove a frame.
michael@0 89 */
michael@0 90 void RemoveFrame(nsIFrame* aFrame) {
michael@0 91 if (mEntryList.empty()) {
michael@0 92 return;
michael@0 93 }
michael@0 94
michael@0 95 uint32_t depth = aFrame->GetDepthInFrameTree();
michael@0 96 if (mEntryList.find(Entry(aFrame, depth))) {
michael@0 97 delete mEntryList.remove(Entry(aFrame, depth));
michael@0 98 }
michael@0 99 }
michael@0 100
michael@0 101 /**
michael@0 102 * Set the subtree root to limit overflow updates. This must be set if and
michael@0 103 * only if currently reflowing aSubtreeRoot, to ensure overflow changes will
michael@0 104 * still propagate correctly.
michael@0 105 */
michael@0 106 void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) {
michael@0 107 mSubtreeRoot = aSubtreeRoot;
michael@0 108 }
michael@0 109
michael@0 110 /**
michael@0 111 * Update the overflow of all added frames, and clear the entry list.
michael@0 112 *
michael@0 113 * Start from those deepest in the frame tree and works upwards. This stops
michael@0 114 * us from processing the same frame twice.
michael@0 115 */
michael@0 116 void Flush() {
michael@0 117 while (!mEntryList.empty()) {
michael@0 118 Entry *entry = mEntryList.removeMin();
michael@0 119 nsIFrame *frame = entry->mFrame;
michael@0 120
michael@0 121 bool overflowChanged = false;
michael@0 122 if (entry->mChangeKind == CHILDREN_AND_PARENT_CHANGED) {
michael@0 123 // Need to union the overflow areas of the children.
michael@0 124 // Always update the parent, even if the overflow does not change.
michael@0 125 frame->UpdateOverflow();
michael@0 126 overflowChanged = true;
michael@0 127 } else if (entry->mChangeKind == CHILDREN_CHANGED) {
michael@0 128 // Need to union the overflow areas of the children.
michael@0 129 // Only update the parent if the overflow changes.
michael@0 130 overflowChanged = frame->UpdateOverflow();
michael@0 131 } else {
michael@0 132 // Take a faster path that doesn't require unioning the overflow areas
michael@0 133 // of our children.
michael@0 134
michael@0 135 #ifdef DEBUG
michael@0 136 bool hasInitialOverflowPropertyApplied = false;
michael@0 137 frame->Properties().Get(nsIFrame::DebugInitialOverflowPropertyApplied(),
michael@0 138 &hasInitialOverflowPropertyApplied);
michael@0 139 NS_ASSERTION(hasInitialOverflowPropertyApplied,
michael@0 140 "InitialOverflowProperty must be set first.");
michael@0 141 #endif
michael@0 142
michael@0 143 nsOverflowAreas* overflow =
michael@0 144 static_cast<nsOverflowAreas*>(frame->Properties().Get(nsIFrame::InitialOverflowProperty()));
michael@0 145 if (overflow) {
michael@0 146 // FinishAndStoreOverflow will change the overflow areas passed in,
michael@0 147 // so make a copy.
michael@0 148 nsOverflowAreas overflowCopy = *overflow;
michael@0 149 frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize());
michael@0 150 } else {
michael@0 151 nsRect bounds(nsPoint(0, 0), frame->GetSize());
michael@0 152 nsOverflowAreas boundsOverflow;
michael@0 153 boundsOverflow.SetAllTo(bounds);
michael@0 154 frame->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
michael@0 155 }
michael@0 156
michael@0 157 // We can't tell if the overflow changed, so be conservative
michael@0 158 overflowChanged = true;
michael@0 159 }
michael@0 160
michael@0 161 // If the frame style changed (e.g. positioning offsets)
michael@0 162 // then we need to update the parent with the overflow areas of its
michael@0 163 // children.
michael@0 164 if (overflowChanged) {
michael@0 165 nsIFrame *parent = frame->GetParent();
michael@0 166 if (parent && parent != mSubtreeRoot) {
michael@0 167 Entry* parentEntry = mEntryList.find(Entry(parent, entry->mDepth - 1));
michael@0 168 if (parentEntry) {
michael@0 169 parentEntry->mChangeKind = std::max(parentEntry->mChangeKind, CHILDREN_CHANGED);
michael@0 170 } else {
michael@0 171 mEntryList.insert(new Entry(parent, entry->mDepth - 1, CHILDREN_CHANGED));
michael@0 172 }
michael@0 173 }
michael@0 174 }
michael@0 175 delete entry;
michael@0 176 }
michael@0 177 }
michael@0 178
michael@0 179 private:
michael@0 180 struct Entry : SplayTreeNode<Entry>
michael@0 181 {
michael@0 182 Entry(nsIFrame* aFrame, uint32_t aDepth, ChangeKind aChangeKind = CHILDREN_CHANGED)
michael@0 183 : mFrame(aFrame)
michael@0 184 , mDepth(aDepth)
michael@0 185 , mChangeKind(aChangeKind)
michael@0 186 {}
michael@0 187
michael@0 188 bool operator==(const Entry& aOther) const
michael@0 189 {
michael@0 190 return mFrame == aOther.mFrame;
michael@0 191 }
michael@0 192
michael@0 193 /**
michael@0 194 * Sort by *reverse* depth in the tree, and break ties with
michael@0 195 * the frame pointer.
michael@0 196 */
michael@0 197 bool operator<(const Entry& aOther) const
michael@0 198 {
michael@0 199 if (mDepth == aOther.mDepth) {
michael@0 200 return mFrame < aOther.mFrame;
michael@0 201 }
michael@0 202 return mDepth > aOther.mDepth; /* reverse, want "min" to be deepest */
michael@0 203 }
michael@0 204
michael@0 205 static int compare(const Entry& aOne, const Entry& aTwo)
michael@0 206 {
michael@0 207 if (aOne == aTwo) {
michael@0 208 return 0;
michael@0 209 } else if (aOne < aTwo) {
michael@0 210 return -1;
michael@0 211 } else {
michael@0 212 return 1;
michael@0 213 }
michael@0 214 }
michael@0 215
michael@0 216 nsIFrame* mFrame;
michael@0 217 /* Depth in the frame tree */
michael@0 218 uint32_t mDepth;
michael@0 219 ChangeKind mChangeKind;
michael@0 220 };
michael@0 221
michael@0 222 /* A list of frames to process, sorted by their depth in the frame tree */
michael@0 223 SplayTree<Entry, Entry> mEntryList;
michael@0 224
michael@0 225 /* Don't update overflow of this frame or its ancestors. */
michael@0 226 const nsIFrame* mSubtreeRoot;
michael@0 227 };
michael@0 228
michael@0 229 class RestyleTracker {
michael@0 230 public:
michael@0 231 typedef mozilla::dom::Element Element;
michael@0 232
michael@0 233 RestyleTracker(uint32_t aRestyleBits) :
michael@0 234 mRestyleBits(aRestyleBits),
michael@0 235 mHaveLaterSiblingRestyles(false)
michael@0 236 {
michael@0 237 NS_PRECONDITION((mRestyleBits & ~ELEMENT_ALL_RESTYLE_FLAGS) == 0,
michael@0 238 "Why do we have these bits set?");
michael@0 239 NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
michael@0 240 "Must have a restyle flag");
michael@0 241 NS_PRECONDITION((mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS) !=
michael@0 242 ELEMENT_PENDING_RESTYLE_FLAGS,
michael@0 243 "Shouldn't have both restyle flags set");
michael@0 244 NS_PRECONDITION((mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS) != 0,
michael@0 245 "Must have root flag");
michael@0 246 NS_PRECONDITION((mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS) !=
michael@0 247 (ELEMENT_ALL_RESTYLE_FLAGS & ~ELEMENT_PENDING_RESTYLE_FLAGS),
michael@0 248 "Shouldn't have both root flags");
michael@0 249 }
michael@0 250
michael@0 251 void Init(RestyleManager* aRestyleManager) {
michael@0 252 mRestyleManager = aRestyleManager;
michael@0 253 }
michael@0 254
michael@0 255 uint32_t Count() const {
michael@0 256 return mPendingRestyles.Count();
michael@0 257 }
michael@0 258
michael@0 259 /**
michael@0 260 * Add a restyle for the given element to the tracker. Returns true
michael@0 261 * if the element already had eRestyle_LaterSiblings set on it.
michael@0 262 */
michael@0 263 bool AddPendingRestyle(Element* aElement, nsRestyleHint aRestyleHint,
michael@0 264 nsChangeHint aMinChangeHint);
michael@0 265
michael@0 266 /**
michael@0 267 * Process the restyles we've been tracking.
michael@0 268 */
michael@0 269 void ProcessRestyles() {
michael@0 270 // Fast-path the common case (esp. for the animation restyle
michael@0 271 // tracker) of not having anything to do.
michael@0 272 if (mPendingRestyles.Count()) {
michael@0 273 DoProcessRestyles();
michael@0 274 }
michael@0 275 }
michael@0 276
michael@0 277 // Return our ELEMENT_HAS_PENDING_(ANIMATION_)RESTYLE bit
michael@0 278 uint32_t RestyleBit() const {
michael@0 279 return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS;
michael@0 280 }
michael@0 281
michael@0 282 // Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit
michael@0 283 uint32_t RootBit() const {
michael@0 284 return mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS;
michael@0 285 }
michael@0 286
michael@0 287 struct RestyleData {
michael@0 288 nsRestyleHint mRestyleHint; // What we want to restyle
michael@0 289 nsChangeHint mChangeHint; // The minimal change hint for "self"
michael@0 290 };
michael@0 291
michael@0 292 /**
michael@0 293 * If the given Element has a restyle pending for it, return the
michael@0 294 * relevant restyle data. This function will clear everything other
michael@0 295 * than a possible eRestyle_LaterSiblings hint for aElement out of
michael@0 296 * our hashtable. The returned aData will never have an
michael@0 297 * eRestyle_LaterSiblings hint in it.
michael@0 298 *
michael@0 299 * The return value indicates whether any restyle data was found for
michael@0 300 * the element. If false is returned, then the state of *aData is
michael@0 301 * undefined.
michael@0 302 */
michael@0 303 bool GetRestyleData(Element* aElement, RestyleData* aData);
michael@0 304
michael@0 305 /**
michael@0 306 * The document we're associated with.
michael@0 307 */
michael@0 308 inline nsIDocument* Document() const;
michael@0 309
michael@0 310 struct RestyleEnumerateData : public RestyleData {
michael@0 311 nsRefPtr<Element> mElement;
michael@0 312 };
michael@0 313
michael@0 314 private:
michael@0 315 /**
michael@0 316 * Handle a single mPendingRestyles entry. aRestyleHint must not
michael@0 317 * include eRestyle_LaterSiblings; that needs to be dealt with
michael@0 318 * before calling this function.
michael@0 319 */
michael@0 320 inline void ProcessOneRestyle(Element* aElement,
michael@0 321 nsRestyleHint aRestyleHint,
michael@0 322 nsChangeHint aChangeHint);
michael@0 323
michael@0 324 /**
michael@0 325 * The guts of our restyle processing.
michael@0 326 */
michael@0 327 void DoProcessRestyles();
michael@0 328
michael@0 329 typedef nsDataHashtable<nsISupportsHashKey, RestyleData> PendingRestyleTable;
michael@0 330 typedef nsAutoTArray< nsRefPtr<Element>, 32> RestyleRootArray;
michael@0 331 // Our restyle bits. These will be a subset of ELEMENT_ALL_RESTYLE_FLAGS, and
michael@0 332 // will include one flag from ELEMENT_PENDING_RESTYLE_FLAGS and one flag
michael@0 333 // that's not in ELEMENT_PENDING_RESTYLE_FLAGS.
michael@0 334 uint32_t mRestyleBits;
michael@0 335 RestyleManager* mRestyleManager; // Owns us
michael@0 336 // A hashtable that maps elements to RestyleData structs. The
michael@0 337 // values only make sense if the element's current document is our
michael@0 338 // document and it has our RestyleBit() flag set. In particular,
michael@0 339 // said bit might not be set if the element had a restyle posted and
michael@0 340 // then was moved around in the DOM.
michael@0 341 PendingRestyleTable mPendingRestyles;
michael@0 342 // An array that keeps track of our possible restyle roots. This
michael@0 343 // maintains the invariant that if A and B are both restyle roots
michael@0 344 // and A is an ancestor of B then A will come after B in the array.
michael@0 345 // We maintain this invariant by checking whether an element has an
michael@0 346 // ancestor with the restyle root bit set before appending it to the
michael@0 347 // array.
michael@0 348 RestyleRootArray mRestyleRoots;
michael@0 349 // True if we have some entries with the eRestyle_LaterSiblings
michael@0 350 // flag. We need this to avoid enumerating the hashtable looking
michael@0 351 // for such entries when we can't possibly have any.
michael@0 352 bool mHaveLaterSiblingRestyles;
michael@0 353 };
michael@0 354
michael@0 355 inline bool RestyleTracker::AddPendingRestyle(Element* aElement,
michael@0 356 nsRestyleHint aRestyleHint,
michael@0 357 nsChangeHint aMinChangeHint)
michael@0 358 {
michael@0 359 RestyleData existingData;
michael@0 360 existingData.mRestyleHint = nsRestyleHint(0);
michael@0 361 existingData.mChangeHint = NS_STYLE_HINT_NONE;
michael@0 362
michael@0 363 // Check the RestyleBit() flag before doing the hashtable Get, since
michael@0 364 // it's possible that the data in the hashtable isn't actually
michael@0 365 // relevant anymore (if the flag is not set).
michael@0 366 if (aElement->HasFlag(RestyleBit())) {
michael@0 367 mPendingRestyles.Get(aElement, &existingData);
michael@0 368 } else {
michael@0 369 aElement->SetFlags(RestyleBit());
michael@0 370 }
michael@0 371
michael@0 372 bool hadRestyleLaterSiblings =
michael@0 373 (existingData.mRestyleHint & eRestyle_LaterSiblings) != 0;
michael@0 374 existingData.mRestyleHint =
michael@0 375 nsRestyleHint(existingData.mRestyleHint | aRestyleHint);
michael@0 376 NS_UpdateHint(existingData.mChangeHint, aMinChangeHint);
michael@0 377
michael@0 378 mPendingRestyles.Put(aElement, existingData);
michael@0 379
michael@0 380 // We can only treat this element as a restyle root if we would
michael@0 381 // actually restyle its descendants (so either call
michael@0 382 // ReResolveStyleContext on it or just reframe it).
michael@0 383 if ((aRestyleHint & (eRestyle_Self | eRestyle_Subtree)) ||
michael@0 384 (aMinChangeHint & nsChangeHint_ReconstructFrame)) {
michael@0 385 for (const Element* cur = aElement; !cur->HasFlag(RootBit()); ) {
michael@0 386 nsIContent* parent = cur->GetFlattenedTreeParent();
michael@0 387 // Stop if we have no parent or the parent is not an element or
michael@0 388 // we're part of the viewport scrollbars (because those are not
michael@0 389 // frametree descendants of the primary frame of the root
michael@0 390 // element).
michael@0 391 // XXXbz maybe the primary frame of the root should be the root scrollframe?
michael@0 392 if (!parent || !parent->IsElement() ||
michael@0 393 // If we've hit the root via a native anonymous kid and that
michael@0 394 // this native anonymous kid is not obviously a descendant
michael@0 395 // of the root's primary frame, assume we're under the root
michael@0 396 // scrollbars. Since those don't get reresolved when
michael@0 397 // reresolving the root, we need to make sure to add the
michael@0 398 // element to mRestyleRoots.
michael@0 399 (cur->IsInNativeAnonymousSubtree() && !parent->GetParent() &&
michael@0 400 cur->GetPrimaryFrame() &&
michael@0 401 cur->GetPrimaryFrame()->GetParent() != parent->GetPrimaryFrame())) {
michael@0 402 mRestyleRoots.AppendElement(aElement);
michael@0 403 break;
michael@0 404 }
michael@0 405 cur = parent->AsElement();
michael@0 406 }
michael@0 407 // At this point some ancestor of aElement (possibly aElement
michael@0 408 // itself) is in mRestyleRoots. Set the root bit on aElement, to
michael@0 409 // speed up searching for an existing root on its descendants.
michael@0 410 aElement->SetFlags(RootBit());
michael@0 411 }
michael@0 412
michael@0 413 mHaveLaterSiblingRestyles =
michael@0 414 mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
michael@0 415 return hadRestyleLaterSiblings;
michael@0 416 }
michael@0 417
michael@0 418 } // namespace mozilla
michael@0 419
michael@0 420 #endif /* mozilla_RestyleTracker_h */

mercurial