/src/mozilla-central/layout/base/nsCounterManager.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
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 "mozilla/Attributes.h" |
13 | | #include "nsGenConList.h" |
14 | | #include "nsClassHashtable.h" |
15 | | #include "mozilla/Likely.h" |
16 | | #include "CounterStyleManager.h" |
17 | | |
18 | | class nsCounterList; |
19 | | struct nsCounterUseNode; |
20 | | struct nsCounterChangeNode; |
21 | | |
22 | | struct nsCounterNode : public nsGenConNode { |
23 | | enum Type { |
24 | | RESET, // a "counter number" pair in 'counter-reset' |
25 | | INCREMENT, // a "counter number" pair in 'counter-increment' |
26 | | USE // counter() or counters() in 'content' |
27 | | }; |
28 | | |
29 | | Type mType; |
30 | | |
31 | | // Counter value after this node |
32 | | int32_t mValueAfter; |
33 | | |
34 | | // mScopeStart points to the node (usually a RESET, but not in the |
35 | | // case of an implied 'counter-reset') that created the scope for |
36 | | // this element (for a RESET, its outer scope, i.e., the one it is |
37 | | // inside rather than the one it creates). |
38 | | |
39 | | // May be null for all types, but only when mScopePrev is also null. |
40 | | // Being null for a non-RESET means that it is an implied |
41 | | // 'counter-reset'. Being null for a RESET means it has no outer |
42 | | // scope. |
43 | | nsCounterNode *mScopeStart; |
44 | | |
45 | | // mScopePrev points to the previous node that is in the same scope, |
46 | | // or for a RESET, the previous node in the scope outside of the |
47 | | // reset. |
48 | | |
49 | | // May be null for all types, but only when mScopeStart is also |
50 | | // null. Following the mScopePrev links will eventually lead to |
51 | | // mScopeStart. Being null for a non-RESET means that it is an |
52 | | // implied 'counter-reset'. Being null for a RESET means it has no |
53 | | // outer scope. |
54 | | nsCounterNode *mScopePrev; |
55 | | |
56 | | inline nsCounterUseNode* UseNode(); |
57 | | inline nsCounterChangeNode* ChangeNode(); |
58 | | |
59 | | // For RESET and INCREMENT nodes, aPseudoFrame need not be a |
60 | | // pseudo-element, and aContentIndex represents the index within the |
61 | | // 'counter-reset' or 'counter-increment' property instead of within |
62 | | // the 'content' property but offset to ensure that (reset, |
63 | | // increment, use) sort in that order. (This slight weirdness |
64 | | // allows sharing a lot of code with 'quotes'.) |
65 | | nsCounterNode(int32_t aContentIndex, Type aType) |
66 | | : nsGenConNode(aContentIndex) |
67 | | , mType(aType) |
68 | | , mValueAfter(0) |
69 | | , mScopeStart(nullptr) |
70 | | , mScopePrev(nullptr) |
71 | 0 | { |
72 | 0 | } |
73 | | |
74 | | // to avoid virtual function calls in the common case |
75 | | inline void Calc(nsCounterList* aList); |
76 | | }; |
77 | | |
78 | | struct nsCounterUseNode : public nsCounterNode { |
79 | | mozilla::CounterStylePtr mCounterStyle; |
80 | | nsString mSeparator; |
81 | | |
82 | | // false for counter(), true for counters() |
83 | | bool mAllCounters; |
84 | | |
85 | | // args go directly to member variables here and of nsGenConNode |
86 | | nsCounterUseNode(nsStyleContentData::CounterFunction* aCounterFunction, |
87 | | uint32_t aContentIndex, bool aAllCounters) |
88 | | : nsCounterNode(aContentIndex, USE) |
89 | | , mCounterStyle(aCounterFunction->mCounterStyle) |
90 | | , mSeparator(aCounterFunction->mSeparator) |
91 | | , mAllCounters(aAllCounters) |
92 | 0 | { |
93 | 0 | NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); |
94 | 0 | } |
95 | | |
96 | | virtual bool InitTextFrame(nsGenConList* aList, |
97 | | nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) override; |
98 | | |
99 | | // assign the correct |mValueAfter| value to a node that has been inserted |
100 | | // Should be called immediately after calling |Insert|. |
101 | | void Calc(nsCounterList* aList); |
102 | | |
103 | | // The text that should be displayed for this counter. |
104 | | void GetText(nsString& aResult); |
105 | | }; |
106 | | |
107 | | struct nsCounterChangeNode : public nsCounterNode { |
108 | | int32_t mChangeValue; // the numeric value of the increment or reset |
109 | | |
110 | | // |aPseudoFrame| is not necessarily a pseudo-element's frame, but |
111 | | // since it is for every other subclass of nsGenConNode, we follow |
112 | | // the naming convention here. |
113 | | // |aPropIndex| is the index of the value within the list in the |
114 | | // 'counter-increment' or 'counter-reset' property. |
115 | | nsCounterChangeNode(nsIFrame* aPseudoFrame, |
116 | | nsCounterNode::Type aChangeType, |
117 | | int32_t aChangeValue, |
118 | | int32_t aPropIndex) |
119 | | : nsCounterNode(// Fake a content index for resets and increments |
120 | | // that comes before all the real content, with |
121 | | // the resets first, in order, and then the increments. |
122 | | aPropIndex + (aChangeType == RESET |
123 | | ? (INT32_MIN) |
124 | | : (INT32_MIN / 2)), |
125 | | aChangeType) |
126 | | , mChangeValue(aChangeValue) |
127 | 0 | { |
128 | 0 | NS_ASSERTION(aPropIndex >= 0, "out of range"); |
129 | 0 | NS_ASSERTION(aChangeType == INCREMENT || aChangeType == RESET, |
130 | 0 | "bad type"); |
131 | 0 | mPseudoFrame = aPseudoFrame; |
132 | 0 | CheckFrameAssertions(); |
133 | 0 | } |
134 | | |
135 | | // assign the correct |mValueAfter| value to a node that has been inserted |
136 | | // Should be called immediately after calling |Insert|. |
137 | | void Calc(nsCounterList* aList); |
138 | | }; |
139 | | |
140 | | inline nsCounterUseNode* nsCounterNode::UseNode() |
141 | 0 | { |
142 | 0 | NS_ASSERTION(mType == USE, "wrong type"); |
143 | 0 | return static_cast<nsCounterUseNode*>(this); |
144 | 0 | } |
145 | | |
146 | | inline nsCounterChangeNode* nsCounterNode::ChangeNode() |
147 | 0 | { |
148 | 0 | NS_ASSERTION(mType == INCREMENT || mType == RESET, "wrong type"); |
149 | 0 | return static_cast<nsCounterChangeNode*>(this); |
150 | 0 | } |
151 | | |
152 | | inline void nsCounterNode::Calc(nsCounterList* aList) |
153 | 0 | { |
154 | 0 | if (mType == USE) |
155 | 0 | UseNode()->Calc(aList); |
156 | 0 | else |
157 | 0 | ChangeNode()->Calc(aList); |
158 | 0 | } |
159 | | |
160 | | class nsCounterList : public nsGenConList { |
161 | | public: |
162 | | nsCounterList() : nsGenConList(), |
163 | | mDirty(false) |
164 | 0 | {} |
165 | | |
166 | 0 | void Insert(nsCounterNode* aNode) { |
167 | 0 | nsGenConList::Insert(aNode); |
168 | 0 | // Don't SetScope if we're dirty -- we'll reset all the scopes anyway, |
169 | 0 | // and we can't usefully compute scopes right now. |
170 | 0 | if (MOZ_LIKELY(!IsDirty())) { |
171 | 0 | SetScope(aNode); |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | 0 | nsCounterNode* First() { |
176 | 0 | return static_cast<nsCounterNode*>(mList.getFirst()); |
177 | 0 | } |
178 | | |
179 | 0 | static nsCounterNode* Next(nsCounterNode* aNode) { |
180 | 0 | return static_cast<nsCounterNode*>(nsGenConList::Next(aNode)); |
181 | 0 | } |
182 | 0 | static nsCounterNode* Prev(nsCounterNode* aNode) { |
183 | 0 | return static_cast<nsCounterNode*>(nsGenConList::Prev(aNode)); |
184 | 0 | } |
185 | | |
186 | 0 | static int32_t ValueBefore(nsCounterNode* aNode) { |
187 | 0 | return aNode->mScopePrev ? aNode->mScopePrev->mValueAfter : 0; |
188 | 0 | } |
189 | | |
190 | | // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev| |
191 | | void SetScope(nsCounterNode *aNode); |
192 | | |
193 | | // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for |
194 | | // all nodes and update text in text content nodes. |
195 | | void RecalcAll(); |
196 | | |
197 | 0 | bool IsDirty() { return mDirty; } |
198 | 0 | void SetDirty() { mDirty = true; } |
199 | | |
200 | | private: |
201 | | bool mDirty; |
202 | | }; |
203 | | |
204 | | /** |
205 | | * The counter manager maintains an |nsCounterList| for each named |
206 | | * counter to keep track of all scopes with that name. |
207 | | */ |
208 | | class nsCounterManager { |
209 | | public: |
210 | | // Returns true if dirty |
211 | | bool AddCounterResetsAndIncrements(nsIFrame *aFrame); |
212 | | |
213 | | // Gets the appropriate counter list, creating it if necessary. |
214 | | // Guaranteed to return non-null. (Uses an infallible hashtable API.) |
215 | | nsCounterList* CounterListFor(const nsAString& aCounterName); |
216 | | |
217 | | // Clean up data in any dirty counter lists. |
218 | | void RecalcAll(); |
219 | | |
220 | | // Set all counter lists dirty |
221 | | void SetAllDirty(); |
222 | | |
223 | | // Destroy nodes for the frame in any lists, and return whether any |
224 | | // nodes were destroyed. |
225 | | bool DestroyNodesFor(nsIFrame *aFrame); |
226 | | |
227 | | // Clear all data. |
228 | 0 | void Clear() { mNames.Clear(); } |
229 | | |
230 | | #ifdef DEBUG |
231 | | void Dump(); |
232 | | #endif |
233 | | |
234 | | static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement) |
235 | 0 | { |
236 | 0 | // Addition of unsigned values is defined to be arithmetic |
237 | 0 | // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4); |
238 | 0 | // addition of signed values is undefined (and clang does |
239 | 0 | // something very strange if we use it here). Likewise integral |
240 | 0 | // conversion from signed to unsigned is also defined as modulo |
241 | 0 | // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion |
242 | 0 | // from unsigned to signed is however undefined (ibid., clause 3), |
243 | 0 | // but to do what we want we must nonetheless depend on that |
244 | 0 | // small piece of undefined behavior. |
245 | 0 | int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement)); |
246 | 0 | // The CSS Working Group resolved that a counter-increment that |
247 | 0 | // exceeds internal limits should not increment at all. |
248 | 0 | // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html |
249 | 0 | // (This means, for example, that if aIncrement is 5, the |
250 | 0 | // counter will get stuck at the largest multiple of 5 less than |
251 | 0 | // the maximum 32-bit integer.) |
252 | 0 | if ((aIncrement > 0) != (newValue > aOldValue)) { |
253 | 0 | newValue = aOldValue; |
254 | 0 | } |
255 | 0 | return newValue; |
256 | 0 | } |
257 | | |
258 | | private: |
259 | | // for |AddCounterResetsAndIncrements| only |
260 | | bool AddResetOrIncrement(nsIFrame* aFrame, int32_t aIndex, |
261 | | const nsStyleCounterData& aCounterData, |
262 | | nsCounterNode::Type aType); |
263 | | |
264 | | nsClassHashtable<nsStringHashKey, nsCounterList> mNames; |
265 | | }; |
266 | | |
267 | | #endif /* nsCounterManager_h_ */ |