nsCounterManager.h (12922B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 // vim:cindent:ai:sw=4:ts=4:et: 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 /* implementation of CSS counters (for numbering things) */ 8 9 #ifndef nsCounterManager_h_ 10 #define nsCounterManager_h_ 11 12 #include "CounterStyleManager.h" 13 #include "mozilla/Likely.h" 14 #include "nsClassHashtable.h" 15 #include "nsGenConList.h" 16 17 class nsCounterList; 18 struct nsCounterUseNode; 19 struct nsCounterChangeNode; 20 21 namespace mozilla { 22 23 class ContainStyleScope; 24 25 } // namespace mozilla 26 27 struct nsCounterNode : public nsGenConNode { 28 enum Type { 29 RESET, // a "counter number" pair in 'counter-reset' 30 INCREMENT, // a "counter number" pair in 'counter-increment' 31 SET, // a "counter number" pair in 'counter-set' 32 USE // counter() or counters() in 'content' 33 }; 34 35 Type mType; 36 37 // Counter value after this node 38 int32_t mValueAfter = 0; 39 40 // mScopeStart points to the node (usually a RESET, but not in the 41 // case of an implied 'counter-reset') that created the scope for 42 // this element (for a RESET, its outer scope, i.e., the one it is 43 // inside rather than the one it creates). 44 45 // May be null for all types, but only when mScopePrev is also null. 46 // Being null for a non-RESET means that it is an implied 47 // 'counter-reset'. Being null for a RESET means it has no outer 48 // scope. 49 nsCounterNode* mScopeStart = nullptr; 50 51 // mScopePrev points to the previous node that is in the same scope, 52 // or for a RESET, the previous node in the scope outside of the 53 // reset. 54 55 // May be null for all types, but only when mScopeStart is also 56 // null. Following the mScopePrev links will eventually lead to 57 // mScopeStart. Being null for a non-RESET means that it is an 58 // implied 'counter-reset'. Being null for a RESET means it has no 59 // outer scope. 60 nsCounterNode* mScopePrev = nullptr; 61 62 // Whether or not this node's scope crosses `contain: style` boundaries. 63 // This can happen for USE nodes that come before any other types of 64 // nodes in a `contain: style` boundary's list. 65 bool mCrossesContainStyleBoundaries = false; 66 67 inline nsCounterUseNode* UseNode(); 68 inline nsCounterChangeNode* ChangeNode(); 69 70 // For RESET, INCREMENT and SET nodes, aPseudoFrame need not be a 71 // pseudo-element, and aContentIndex represents the index within the 72 // 'counter-reset', 'counter-increment' or 'counter-set' property 73 // instead of within the 'content' property but offset to ensure 74 // that (reset, increment, set, use) sort in that order. 75 // It is zero for legacy bullet USE counter nodes. 76 // (This slight weirdness allows sharing a lot of code with 'quotes'.) 77 nsCounterNode(int32_t aContentIndex, Type aType) 78 : nsGenConNode(aContentIndex), mType(aType) {} 79 80 // to avoid virtual function calls in the common case 81 inline void Calc(nsCounterList* aList, bool aNotify); 82 83 // Is this a RESET node for a content-based (i.e. without a start value) 84 // reversed() counter? 85 inline bool IsContentBasedReset(); 86 87 // Is this a RESET node for a reversed() counter? 88 inline bool IsReversed(); 89 90 // Is this an INCREMENT node that needs to be initialized to -1 or 1 91 // depending on if our scope is reversed() or not? 92 inline bool IsUnitializedIncrementNode(); 93 }; 94 95 struct nsCounterUseNode : public nsCounterNode { 96 mozilla::StyleCounterStyle mCounterStyle; 97 nsString mSeparator; 98 99 // false for counter(), true for counters() 100 bool mAllCounters = false; 101 102 bool mForLegacyBullet = false; 103 104 enum ForLegacyBullet { ForLegacyBullet }; 105 nsCounterUseNode(enum ForLegacyBullet, 106 const mozilla::StyleCounterStyle& aCounterStyle) 107 : nsCounterNode(0, USE), 108 mCounterStyle(aCounterStyle), 109 mForLegacyBullet(true) {} 110 111 // args go directly to member variables here and of nsGenConNode 112 nsCounterUseNode(const mozilla::StyleCounterStyle& aCounterStyle, 113 nsString aSeparator, uint32_t aContentIndex, 114 bool aAllCounters) 115 : nsCounterNode(aContentIndex, USE), 116 mCounterStyle(aCounterStyle), 117 mSeparator(std::move(aSeparator)), 118 mAllCounters(aAllCounters) { 119 NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); 120 } 121 122 bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame, 123 nsIFrame* aTextFrame) override; 124 125 // assign the correct |mValueAfter| value to a node that has been inserted, 126 // and update the value of the text node, notifying if `aNotify` is true. 127 // Should be called immediately after calling |Insert|. 128 void Calc(nsCounterList* aList, bool aNotify); 129 130 // The text that should be displayed for this counter. 131 void GetText(nsString& aResult); 132 void GetText(mozilla::WritingMode aWM, mozilla::CounterStyle* aStyle, 133 nsString& aResult); 134 }; 135 136 struct nsCounterChangeNode : public nsCounterNode { 137 // |aPseudoFrame| is not necessarily a pseudo-element's frame, but 138 // since it is for every other subclass of nsGenConNode, we follow 139 // the naming convention here. 140 // |aPropIndex| is the index of the value within the list in the 141 // 'counter-increment', 'counter-reset' or 'counter-set' property. 142 nsCounterChangeNode(nsIFrame* aPseudoFrame, nsCounterNode::Type aChangeType, 143 int32_t aChangeValue, int32_t aPropIndex, 144 bool aIsReversed) 145 : nsCounterNode( // Fake a content index for resets, increments and sets 146 // that comes before all the real content, with 147 // the resets first, in order, and then the increments 148 // and then the sets. 149 aPropIndex + (aChangeType == RESET ? (INT32_MIN) 150 : (aChangeType == INCREMENT 151 ? ((INT32_MIN / 3) * 2) 152 : INT32_MIN / 3)), 153 aChangeType), 154 mChangeValue(aChangeValue), 155 mIsReversed(aIsReversed), 156 mSeenSetNode(false) { 157 NS_ASSERTION(aPropIndex >= 0, "out of range"); 158 NS_ASSERTION( 159 aChangeType == INCREMENT || aChangeType == SET || aChangeType == RESET, 160 "bad type"); 161 mPseudoFrame = aPseudoFrame; 162 CheckFrameAssertions(); 163 } 164 165 // assign the correct |mValueAfter| value to a node that has been inserted 166 // Should be called immediately after calling |Insert|. 167 void Calc(nsCounterList* aList); 168 169 // The numeric value of the INCREMENT, SET or RESET. 170 // Note: numeric_limits<int32_t>::min() is used for content-based reversed() 171 // RESET nodes, and temporarily on INCREMENT nodes to signal that it should be 172 // initialized to -1 or 1 depending on if the scope is reversed() or not. 173 int32_t mChangeValue; 174 175 // True if the counter is reversed(). Only used on RESET nodes. 176 bool mIsReversed : 1; 177 // True if we've seen a SET node during the initialization of 178 // an IsContentBasedReset() node; always false on other nodes. 179 bool mSeenSetNode : 1; 180 }; 181 182 inline nsCounterUseNode* nsCounterNode::UseNode() { 183 NS_ASSERTION(mType == USE, "wrong type"); 184 return static_cast<nsCounterUseNode*>(this); 185 } 186 187 inline nsCounterChangeNode* nsCounterNode::ChangeNode() { 188 MOZ_ASSERT(mType == INCREMENT || mType == SET || mType == RESET); 189 return static_cast<nsCounterChangeNode*>(this); 190 } 191 192 inline void nsCounterNode::Calc(nsCounterList* aList, bool aNotify) { 193 if (mType == USE) { 194 UseNode()->Calc(aList, aNotify); 195 } else { 196 ChangeNode()->Calc(aList); 197 } 198 } 199 200 inline bool nsCounterNode::IsContentBasedReset() { 201 return mType == RESET && 202 ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min(); 203 } 204 205 inline bool nsCounterNode::IsReversed() { 206 return mType == RESET && ChangeNode()->mIsReversed; 207 } 208 209 inline bool nsCounterNode::IsUnitializedIncrementNode() { 210 return mType == INCREMENT && 211 ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min(); 212 } 213 214 class nsCounterList : public nsGenConList { 215 public: 216 nsCounterList(nsAtom* aCounterName, mozilla::ContainStyleScope* aScope) 217 : mCounterName(aCounterName), mScope(aScope) { 218 MOZ_ASSERT(aScope); 219 } 220 221 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) 222 void Dump(); 223 #endif 224 225 // Return the first node for aFrame on this list, or nullptr. 226 nsCounterNode* GetFirstNodeFor(nsIFrame* aFrame) const { 227 return static_cast<nsCounterNode*>(nsGenConList::GetFirstNodeFor(aFrame)); 228 } 229 230 void Insert(nsCounterNode* aNode) { 231 nsGenConList::Insert(aNode); 232 // Don't SetScope if we're dirty -- we'll reset all the scopes anyway, 233 // and we can't usefully compute scopes right now. 234 if (MOZ_LIKELY(!IsDirty())) { 235 SetScope(aNode); 236 } 237 } 238 239 nsCounterNode* First() { 240 return static_cast<nsCounterNode*>(mList.getFirst()); 241 } 242 243 static nsCounterNode* Next(nsCounterNode* aNode) { 244 return static_cast<nsCounterNode*>(nsGenConList::Next(aNode)); 245 } 246 static nsCounterNode* Prev(nsCounterNode* aNode) { 247 return static_cast<nsCounterNode*>(nsGenConList::Prev(aNode)); 248 } 249 250 static int32_t ValueBefore(nsCounterNode* aNode) { 251 if (!aNode->mScopePrev) { 252 return 0; 253 } 254 255 if (aNode->mType != nsCounterNode::USE && 256 aNode->mScopePrev->mCrossesContainStyleBoundaries) { 257 return 0; 258 } 259 260 return aNode->mScopePrev->mValueAfter; 261 } 262 263 // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev| 264 void SetScope(nsCounterNode* aNode); 265 266 // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for 267 // all nodes and update text in text content nodes. 268 void RecalcAll(); 269 270 bool IsDirty() const; 271 void SetDirty(); 272 bool IsRecalculatingAll() const { return mRecalculatingAll; } 273 274 private: 275 bool SetScopeByWalkingBackwardThroughList( 276 nsCounterNode* aNodeToSetScopeFor, const nsIContent* aNodeContent, 277 nsCounterNode* aNodeToBeginLookingAt); 278 279 RefPtr<nsAtom> mCounterName; 280 mozilla::ContainStyleScope* mScope; 281 bool mRecalculatingAll = false; 282 }; 283 284 /** 285 * The counter manager maintains an |nsCounterList| for each named 286 * counter to keep track of all scopes with that name. 287 */ 288 class nsCounterManager { 289 public: 290 explicit nsCounterManager(mozilla::ContainStyleScope* scope) 291 : mScope(scope) {} 292 293 // Returns true if dirty 294 bool AddCounterChanges(nsIFrame* aFrame); 295 296 // Gets the appropriate counter list, creating it if necessary. 297 // Guaranteed to return non-null. (Uses an infallible hashtable API.) 298 nsCounterList* GetOrCreateCounterList(nsAtom* aCounterName); 299 300 // Gets the appropriate counter list, returning null if it doesn't exist. 301 nsCounterList* GetCounterList(nsAtom* aCounterName); 302 303 // Clean up data in any dirty counter lists. 304 void RecalcAll(); 305 306 // Set all counter lists dirty 307 void SetAllDirty(); 308 309 // Destroy nodes for the frame in any lists, and return whether any 310 // nodes were destroyed. 311 bool DestroyNodesFor(nsIFrame* aFrame); 312 313 // Clear all data. 314 void Clear() { mNames.Clear(); } 315 316 #ifdef ACCESSIBILITY 317 // Set |aOrdinal| to the first used counter value for the given frame and 318 // return true. If no USE node for the given frame can be found, return false 319 // and do not change the value of |aOrdinal|. 320 bool GetFirstCounterValueForFrame(nsIFrame* aFrame, 321 mozilla::CounterValue& aOrdinal) const; 322 #endif 323 324 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER) 325 void Dump() const; 326 #endif 327 328 static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement) { 329 // Addition of unsigned values is defined to be arithmetic 330 // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4); 331 // addition of signed values is undefined (and clang does 332 // something very strange if we use it here). Likewise integral 333 // conversion from signed to unsigned is also defined as modulo 334 // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion 335 // from unsigned to signed is however undefined (ibid., clause 3), 336 // but to do what we want we must nonetheless depend on that 337 // small piece of undefined behavior. 338 int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement)); 339 // The CSS Working Group resolved that a counter-increment that 340 // exceeds internal limits should not increment at all. 341 // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html 342 // (This means, for example, that if aIncrement is 5, the 343 // counter will get stuck at the largest multiple of 5 less than 344 // the maximum 32-bit integer.) 345 if ((aIncrement > 0) != (newValue > aOldValue)) { 346 newValue = aOldValue; 347 } 348 return newValue; 349 } 350 351 private: 352 mozilla::ContainStyleScope* mScope; 353 nsClassHashtable<nsAtomHashKey, nsCounterList> mNames; 354 }; 355 356 #endif /* nsCounterManager_h_ */