OverflowChangedTracker.h (7432B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_OverflowChangedTracker_h 8 #define mozilla_OverflowChangedTracker_h 9 10 #include "mozilla/HashTable.h" 11 #include "nsContainerFrame.h" 12 #include "nsIFrame.h" 13 #include "nsTArray.h" 14 15 namespace mozilla { 16 17 /** 18 * Helper class that collects a list of frames that need 19 * UpdateOverflow() called on them, and coalesces them 20 * to avoid walking up the same ancestor tree multiple times. 21 */ 22 class OverflowChangedTracker { 23 public: 24 enum ChangeKind { 25 /** 26 * The frame was explicitly added as a result of 27 * nsChangeHint_UpdatePostTransformOverflow and hence may have had a style 28 * change that changes its geometry relative to parent, without reflowing. 29 */ 30 TRANSFORM_CHANGED, 31 /** 32 * The overflow areas of children have changed 33 * and we need to call UpdateOverflow on the frame. 34 */ 35 CHILDREN_CHANGED, 36 }; 37 38 OverflowChangedTracker() : mSubtreeRoot(nullptr) {} 39 40 ~OverflowChangedTracker() { 41 NS_ASSERTION(mEntries.empty(), "Need to flush before destroying!"); 42 } 43 44 /** 45 * Add a frame that has had a style change, and needs its 46 * overflow updated. 47 * 48 * If there are pre-transform overflow areas stored for this 49 * frame, then we will call FinishAndStoreOverflow with those 50 * areas instead of UpdateOverflow(). 51 * 52 * If the overflow area changes, then UpdateOverflow will also 53 * be called on the parent. 54 */ 55 void AddFrame(nsIFrame* aFrame, ChangeKind aChangeKind) { 56 MOZ_ASSERT( 57 aFrame->FrameMaintainsOverflow(), 58 "Why add a frame that doesn't maintain overflow to the tracker?"); 59 if (auto p = mEntries.lookupForAdd(aFrame)) { 60 p->value() = std::max(p->value(), aChangeKind); 61 } else { 62 // Ignore failure to add an entry; this could result in cosmetic 63 // errors but is non-fatal. 64 (void)mEntries.add(p, aFrame, aChangeKind); 65 } 66 } 67 68 /** 69 * Remove a frame. 70 */ 71 void RemoveFrame(nsIFrame* aFrame) { 72 if (!mEntries.empty()) { 73 mEntries.remove(aFrame); 74 } 75 } 76 77 /** 78 * Set the subtree root to limit overflow updates. This must be set if and 79 * only if currently reflowing aSubtreeRoot, to ensure overflow changes will 80 * still propagate correctly. 81 */ 82 void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) { 83 mSubtreeRoot = aSubtreeRoot; 84 } 85 86 /** 87 * Update the overflow of all added frames, and clear the entry list. 88 * 89 * Start from those deepest in the frame tree and works upwards. This stops 90 * us from processing the same frame twice. 91 */ 92 void Flush() { 93 // Collect all the entries into an array, sort by depth, and process them 94 // from the deepest upwards. 95 96 AutoTArray<Entry, 8> sortedEntries; 97 // We use fallible allocations to avoid crashing on OOM; in the event that 98 // of allocation failure, we'll effectively ignore the entries that weren't 99 // added to the array, which could result in painting glitches but should 100 // be otherwise harmless. 101 (void)sortedEntries.SetCapacity(mEntries.count(), fallible); 102 for (auto iter = mEntries.iter(); !iter.done(); iter.next()) { 103 nsIFrame* frame = iter.get().key(); 104 uint32_t depth = frame->GetDepthInFrameTree(); 105 ChangeKind kind = iter.get().value(); 106 if (!sortedEntries.AppendElement(Entry(frame, depth, kind), fallible)) { 107 break; 108 } 109 } 110 mEntries.clearAndCompact(); 111 sortedEntries.Sort(); 112 113 while (!sortedEntries.IsEmpty()) { 114 Entry entry = sortedEntries.PopLastElement(); 115 nsIFrame* frame = entry.mFrame; 116 117 bool overflowChanged = false; 118 if (entry.mChangeKind == CHILDREN_CHANGED) { 119 // Need to union the overflow areas of the children. 120 // Only update the parent if the overflow changes. 121 overflowChanged = frame->UpdateOverflow(); 122 } else { 123 // Take a faster path that doesn't require unioning the overflow areas 124 // of our children. 125 126 NS_ASSERTION( 127 frame->GetProperty(nsIFrame::DebugInitialOverflowPropertyApplied()), 128 "InitialOverflowProperty must be set first."); 129 130 OverflowAreas* overflow = 131 frame->GetProperty(nsIFrame::InitialOverflowProperty()); 132 if (overflow) { 133 // FinishAndStoreOverflow will change the overflow areas passed in, 134 // so make a copy. 135 OverflowAreas overflowCopy = *overflow; 136 frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize()); 137 } else { 138 nsRect bounds(nsPoint(0, 0), frame->GetSize()); 139 OverflowAreas boundsOverflow; 140 boundsOverflow.SetAllTo(bounds); 141 frame->FinishAndStoreOverflow(boundsOverflow, bounds.Size()); 142 } 143 144 // We can't tell if the overflow changed, so be conservative 145 overflowChanged = true; 146 } 147 148 // If the frame style changed (e.g. positioning offsets) 149 // then we need to update the parent with the overflow areas of its 150 // children. 151 if (overflowChanged) { 152 nsIFrame* parent = frame->GetParent(); 153 154 // It's possible that the parent is already in a nondisplay context, 155 // should not add it to the list if that's true. 156 if (parent && parent != mSubtreeRoot && 157 parent->FrameMaintainsOverflow()) { 158 Entry parentEntry(parent, entry.mDepth - 1, CHILDREN_CHANGED); 159 auto index = sortedEntries.IndexOfFirstElementGt(parentEntry); 160 if (index > 0 && sortedEntries[index - 1] == parentEntry) { 161 // Update the existing entry if the new value is stronger. 162 Entry& existing = sortedEntries[index - 1]; 163 existing.mChangeKind = 164 std::max(existing.mChangeKind, CHILDREN_CHANGED); 165 } else { 166 // Add new entry. We ignore failure here; we'll just potentially 167 // miss some updates that ought to happen. 168 // (Allocation failure seems unlikely here anyhow, as we just 169 // popped an element off the array at the top of the loop.) 170 (void)sortedEntries.InsertElementAt(index, parentEntry, fallible); 171 } 172 } 173 } 174 } 175 } 176 177 private: 178 /* Entry type used for the sorted array in Flush(). */ 179 struct Entry { 180 Entry(nsIFrame* aFrame, uint32_t aDepth, 181 ChangeKind aChangeKind = CHILDREN_CHANGED) 182 : mFrame(aFrame), mDepth(aDepth), mChangeKind(aChangeKind) {} 183 184 /** 185 * Note that "equality" tests only the frame pointer. 186 */ 187 bool operator==(const Entry& aOther) const { 188 return mFrame == aOther.mFrame; 189 } 190 191 /** 192 * Sort by depth in the tree, and break ties with the frame pointer. 193 */ 194 bool operator<(const Entry& aOther) const { 195 if (mDepth == aOther.mDepth) { 196 return mFrame < aOther.mFrame; 197 } 198 return mDepth < aOther.mDepth; 199 } 200 201 nsIFrame* mFrame; 202 /* Depth in the frame tree */ 203 uint32_t mDepth; 204 ChangeKind mChangeKind; 205 }; 206 207 /* A collection of frames to be processed. */ 208 HashMap<nsIFrame*, ChangeKind> mEntries; 209 210 /* Don't update overflow of this frame or its ancestors. */ 211 const nsIFrame* mSubtreeRoot; 212 }; 213 214 } // namespace mozilla 215 216 #endif