Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
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 "nsAutoPtr.h"
15 #include "nsClassHashtable.h"
16 #include "mozilla/Likely.h"
18 class nsCounterList;
19 struct nsCounterUseNode;
20 struct nsCounterChangeNode;
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 };
29 Type mType;
31 // Counter value after this node
32 int32_t mValueAfter;
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).
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;
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.
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;
56 inline nsCounterUseNode* UseNode();
57 inline nsCounterChangeNode* ChangeNode();
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 }
74 // to avoid virtual function calls in the common case
75 inline void Calc(nsCounterList* aList);
76 };
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;
84 // false for counter(), true for counters()
85 bool mAllCounters;
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 }
97 virtual bool InitTextFrame(nsGenConList* aList,
98 nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) MOZ_OVERRIDE;
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);
104 // The text that should be displayed for this counter.
105 void GetText(nsString& aResult);
106 };
108 struct nsCounterChangeNode : public nsCounterNode {
109 int32_t mChangeValue; // the numeric value of the increment or reset
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 }
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 };
141 inline nsCounterUseNode* nsCounterNode::UseNode()
142 {
143 NS_ASSERTION(mType == USE, "wrong type");
144 return static_cast<nsCounterUseNode*>(this);
145 }
147 inline nsCounterChangeNode* nsCounterNode::ChangeNode()
148 {
149 NS_ASSERTION(mType == INCREMENT || mType == RESET, "wrong type");
150 return static_cast<nsCounterChangeNode*>(this);
151 }
153 inline void nsCounterNode::Calc(nsCounterList* aList)
154 {
155 if (mType == USE)
156 UseNode()->Calc(aList);
157 else
158 ChangeNode()->Calc(aList);
159 }
161 class nsCounterList : public nsGenConList {
162 public:
163 nsCounterList() : nsGenConList(),
164 mDirty(false)
165 {}
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 }
176 nsCounterNode* First() {
177 return static_cast<nsCounterNode*>(mFirstNode);
178 }
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 }
187 static int32_t ValueBefore(nsCounterNode* aNode) {
188 return aNode->mScopePrev ? aNode->mScopePrev->mValueAfter : 0;
189 }
191 // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev|
192 void SetScope(nsCounterNode *aNode);
194 // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for
195 // all nodes and update text in text content nodes.
196 void RecalcAll();
198 bool IsDirty() { return mDirty; }
199 void SetDirty() { mDirty = true; }
201 private:
202 bool mDirty;
203 };
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);
215 // Gets the appropriate counter list, creating it if necessary.
216 // Returns null only on out-of-memory.
217 nsCounterList* CounterListFor(const nsSubstring& aCounterName);
219 // Clean up data in any dirty counter lists.
220 void RecalcAll();
222 // Destroy nodes for the frame in any lists, and return whether any
223 // nodes were destroyed.
224 bool DestroyNodesFor(nsIFrame *aFrame);
226 // Clear all data.
227 void Clear() { mNames.Clear(); }
229 #ifdef DEBUG
230 void Dump();
231 #endif
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 }
257 private:
258 // for |AddCounterResetsAndIncrements| only
259 bool AddResetOrIncrement(nsIFrame *aFrame, int32_t aIndex,
260 const nsStyleCounterData *aCounterData,
261 nsCounterNode::Type aType);
263 nsClassHashtable<nsStringHashKey, nsCounterList> mNames;
264 };
266 #endif /* nsCounterManager_h_ */