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