|
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 "nsAutoPtr.h" |
|
15 #include "nsClassHashtable.h" |
|
16 #include "mozilla/Likely.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 { |
|
72 } |
|
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 // The same structure passed through the style system: an array |
|
80 // containing the values in the counter() or counters() in the order |
|
81 // given in the CSS spec. |
|
82 nsRefPtr<nsCSSValue::Array> mCounterStyle; |
|
83 |
|
84 // false for counter(), true for counters() |
|
85 bool mAllCounters; |
|
86 |
|
87 // args go directly to member variables here and of nsGenConNode |
|
88 nsCounterUseNode(nsCSSValue::Array* aCounterStyle, |
|
89 uint32_t aContentIndex, bool aAllCounters) |
|
90 : nsCounterNode(aContentIndex, USE) |
|
91 , mCounterStyle(aCounterStyle) |
|
92 , mAllCounters(aAllCounters) |
|
93 { |
|
94 NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range"); |
|
95 } |
|
96 |
|
97 virtual bool InitTextFrame(nsGenConList* aList, |
|
98 nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) MOZ_OVERRIDE; |
|
99 |
|
100 // assign the correct |mValueAfter| value to a node that has been inserted |
|
101 // Should be called immediately after calling |Insert|. |
|
102 void Calc(nsCounterList* aList); |
|
103 |
|
104 // The text that should be displayed for this counter. |
|
105 void GetText(nsString& aResult); |
|
106 }; |
|
107 |
|
108 struct nsCounterChangeNode : public nsCounterNode { |
|
109 int32_t mChangeValue; // the numeric value of the increment or reset |
|
110 |
|
111 // |aPseudoFrame| is not necessarily a pseudo-element's frame, but |
|
112 // since it is for every other subclass of nsGenConNode, we follow |
|
113 // the naming convention here. |
|
114 // |aPropIndex| is the index of the value within the list in the |
|
115 // 'counter-increment' or 'counter-reset' property. |
|
116 nsCounterChangeNode(nsIFrame* aPseudoFrame, |
|
117 nsCounterNode::Type aChangeType, |
|
118 int32_t aChangeValue, |
|
119 int32_t aPropIndex) |
|
120 : nsCounterNode(// Fake a content index for resets and increments |
|
121 // that comes before all the real content, with |
|
122 // the resets first, in order, and then the increments. |
|
123 aPropIndex + (aChangeType == RESET |
|
124 ? (INT32_MIN) |
|
125 : (INT32_MIN / 2)), |
|
126 aChangeType) |
|
127 , mChangeValue(aChangeValue) |
|
128 { |
|
129 NS_ASSERTION(aPropIndex >= 0, "out of range"); |
|
130 NS_ASSERTION(aChangeType == INCREMENT || aChangeType == RESET, |
|
131 "bad type"); |
|
132 mPseudoFrame = aPseudoFrame; |
|
133 CheckFrameAssertions(); |
|
134 } |
|
135 |
|
136 // assign the correct |mValueAfter| value to a node that has been inserted |
|
137 // Should be called immediately after calling |Insert|. |
|
138 void Calc(nsCounterList* aList); |
|
139 }; |
|
140 |
|
141 inline nsCounterUseNode* nsCounterNode::UseNode() |
|
142 { |
|
143 NS_ASSERTION(mType == USE, "wrong type"); |
|
144 return static_cast<nsCounterUseNode*>(this); |
|
145 } |
|
146 |
|
147 inline nsCounterChangeNode* nsCounterNode::ChangeNode() |
|
148 { |
|
149 NS_ASSERTION(mType == INCREMENT || mType == RESET, "wrong type"); |
|
150 return static_cast<nsCounterChangeNode*>(this); |
|
151 } |
|
152 |
|
153 inline void nsCounterNode::Calc(nsCounterList* aList) |
|
154 { |
|
155 if (mType == USE) |
|
156 UseNode()->Calc(aList); |
|
157 else |
|
158 ChangeNode()->Calc(aList); |
|
159 } |
|
160 |
|
161 class nsCounterList : public nsGenConList { |
|
162 public: |
|
163 nsCounterList() : nsGenConList(), |
|
164 mDirty(false) |
|
165 {} |
|
166 |
|
167 void Insert(nsCounterNode* aNode) { |
|
168 nsGenConList::Insert(aNode); |
|
169 // Don't SetScope if we're dirty -- we'll reset all the scopes anyway, |
|
170 // and we can't usefully compute scopes right now. |
|
171 if (MOZ_LIKELY(!IsDirty())) { |
|
172 SetScope(aNode); |
|
173 } |
|
174 } |
|
175 |
|
176 nsCounterNode* First() { |
|
177 return static_cast<nsCounterNode*>(mFirstNode); |
|
178 } |
|
179 |
|
180 static nsCounterNode* Next(nsCounterNode* aNode) { |
|
181 return static_cast<nsCounterNode*>(nsGenConList::Next(aNode)); |
|
182 } |
|
183 static nsCounterNode* Prev(nsCounterNode* aNode) { |
|
184 return static_cast<nsCounterNode*>(nsGenConList::Prev(aNode)); |
|
185 } |
|
186 |
|
187 static int32_t ValueBefore(nsCounterNode* aNode) { |
|
188 return aNode->mScopePrev ? aNode->mScopePrev->mValueAfter : 0; |
|
189 } |
|
190 |
|
191 // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev| |
|
192 void SetScope(nsCounterNode *aNode); |
|
193 |
|
194 // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for |
|
195 // all nodes and update text in text content nodes. |
|
196 void RecalcAll(); |
|
197 |
|
198 bool IsDirty() { return mDirty; } |
|
199 void SetDirty() { mDirty = true; } |
|
200 |
|
201 private: |
|
202 bool mDirty; |
|
203 }; |
|
204 |
|
205 /** |
|
206 * The counter manager maintains an |nsCounterList| for each named |
|
207 * counter to keep track of all scopes with that name. |
|
208 */ |
|
209 class nsCounterManager { |
|
210 public: |
|
211 nsCounterManager(); |
|
212 // Returns true if dirty |
|
213 bool AddCounterResetsAndIncrements(nsIFrame *aFrame); |
|
214 |
|
215 // Gets the appropriate counter list, creating it if necessary. |
|
216 // Returns null only on out-of-memory. |
|
217 nsCounterList* CounterListFor(const nsSubstring& aCounterName); |
|
218 |
|
219 // Clean up data in any dirty counter lists. |
|
220 void RecalcAll(); |
|
221 |
|
222 // Destroy nodes for the frame in any lists, and return whether any |
|
223 // nodes were destroyed. |
|
224 bool DestroyNodesFor(nsIFrame *aFrame); |
|
225 |
|
226 // Clear all data. |
|
227 void Clear() { mNames.Clear(); } |
|
228 |
|
229 #ifdef DEBUG |
|
230 void Dump(); |
|
231 #endif |
|
232 |
|
233 static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement) |
|
234 { |
|
235 // Addition of unsigned values is defined to be arithmetic |
|
236 // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4); |
|
237 // addition of signed values is undefined (and clang does |
|
238 // something very strange if we use it here). Likewise integral |
|
239 // conversion from signed to unsigned is also defined as modulo |
|
240 // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion |
|
241 // from unsigned to signed is however undefined (ibid., clause 3), |
|
242 // but to do what we want we must nonetheless depend on that |
|
243 // small piece of undefined behavior. |
|
244 int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement)); |
|
245 // The CSS Working Group resolved that a counter-increment that |
|
246 // exceeds internal limits should not increment at all. |
|
247 // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html |
|
248 // (This means, for example, that if aIncrement is 5, the |
|
249 // counter will get stuck at the largest multiple of 5 less than |
|
250 // the maximum 32-bit integer.) |
|
251 if ((aIncrement > 0) != (newValue > aOldValue)) { |
|
252 newValue = aOldValue; |
|
253 } |
|
254 return newValue; |
|
255 } |
|
256 |
|
257 private: |
|
258 // for |AddCounterResetsAndIncrements| only |
|
259 bool AddResetOrIncrement(nsIFrame *aFrame, int32_t aIndex, |
|
260 const nsStyleCounterData *aCounterData, |
|
261 nsCounterNode::Type aType); |
|
262 |
|
263 nsClassHashtable<nsStringHashKey, nsCounterList> mNames; |
|
264 }; |
|
265 |
|
266 #endif /* nsCounterManager_h_ */ |