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"
19 struct nsCounterUseNode
;
20 struct nsCounterChangeNode
;
24 class ContainStyleScope
;
26 } // namespace mozilla
28 struct nsCounterNode
: public nsGenConNode
{
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'
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
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
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
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::CounterStylePtr mCounterStyle
;
100 // false for counter(), true for counters()
101 bool mAllCounters
= false;
103 bool mForLegacyBullet
= false;
105 enum ForLegacyBullet
{ ForLegacyBullet
};
106 nsCounterUseNode(enum ForLegacyBullet
, mozilla::CounterStylePtr aCounterStyle
)
107 : nsCounterNode(0, USE
),
108 mCounterStyle(std::move(aCounterStyle
)),
109 mForLegacyBullet(true) {}
111 // args go directly to member variables here and of nsGenConNode
112 nsCounterUseNode(mozilla::CounterStylePtr aCounterStyle
, nsString aSeparator
,
113 uint32_t aContentIndex
, bool aAllCounters
)
114 : nsCounterNode(aContentIndex
, USE
),
115 mCounterStyle(std::move(aCounterStyle
)),
116 mSeparator(std::move(aSeparator
)),
117 mAllCounters(aAllCounters
) {
118 NS_ASSERTION(aContentIndex
<= INT32_MAX
, "out of range");
121 bool InitTextFrame(nsGenConList
* aList
, nsIFrame
* aPseudoFrame
,
122 nsIFrame
* aTextFrame
) override
;
124 // assign the correct |mValueAfter| value to a node that has been inserted,
125 // and update the value of the text node, notifying if `aNotify` is true.
126 // Should be called immediately after calling |Insert|.
127 void Calc(nsCounterList
* aList
, bool aNotify
);
129 // The text that should be displayed for this counter.
130 void GetText(nsString
& aResult
);
131 void GetText(mozilla::WritingMode aWM
, mozilla::CounterStyle
* aStyle
,
135 struct nsCounterChangeNode
: public nsCounterNode
{
136 // |aPseudoFrame| is not necessarily a pseudo-element's frame, but
137 // since it is for every other subclass of nsGenConNode, we follow
138 // the naming convention here.
139 // |aPropIndex| is the index of the value within the list in the
140 // 'counter-increment', 'counter-reset' or 'counter-set' property.
141 nsCounterChangeNode(nsIFrame
* aPseudoFrame
, nsCounterNode::Type aChangeType
,
142 int32_t aChangeValue
, int32_t aPropIndex
,
144 : nsCounterNode( // Fake a content index for resets, increments and sets
145 // that comes before all the real content, with
146 // the resets first, in order, and then the increments
147 // and then the sets.
148 aPropIndex
+ (aChangeType
== RESET
? (INT32_MIN
)
149 : (aChangeType
== INCREMENT
150 ? ((INT32_MIN
/ 3) * 2)
153 mChangeValue(aChangeValue
),
154 mIsReversed(aIsReversed
),
155 mSeenSetNode(false) {
156 NS_ASSERTION(aPropIndex
>= 0, "out of range");
158 aChangeType
== INCREMENT
|| aChangeType
== SET
|| aChangeType
== RESET
,
160 mPseudoFrame
= aPseudoFrame
;
161 CheckFrameAssertions();
164 // assign the correct |mValueAfter| value to a node that has been inserted
165 // Should be called immediately after calling |Insert|.
166 void Calc(nsCounterList
* aList
);
168 // The numeric value of the INCREMENT, SET or RESET.
169 // Note: numeric_limits<int32_t>::min() is used for content-based reversed()
170 // RESET nodes, and temporarily on INCREMENT nodes to signal that it should be
171 // initialized to -1 or 1 depending on if the scope is reversed() or not.
172 int32_t mChangeValue
;
174 // True if the counter is reversed(). Only used on RESET nodes.
175 bool mIsReversed
: 1;
176 // True if we've seen a SET node during the initialization of
177 // an IsContentBasedReset() node; always false on other nodes.
178 bool mSeenSetNode
: 1;
181 inline nsCounterUseNode
* nsCounterNode::UseNode() {
182 NS_ASSERTION(mType
== USE
, "wrong type");
183 return static_cast<nsCounterUseNode
*>(this);
186 inline nsCounterChangeNode
* nsCounterNode::ChangeNode() {
187 MOZ_ASSERT(mType
== INCREMENT
|| mType
== SET
|| mType
== RESET
);
188 return static_cast<nsCounterChangeNode
*>(this);
191 inline void nsCounterNode::Calc(nsCounterList
* aList
, bool aNotify
) {
193 UseNode()->Calc(aList
, aNotify
);
195 ChangeNode()->Calc(aList
);
198 inline bool nsCounterNode::IsContentBasedReset() {
199 return mType
== RESET
&&
200 ChangeNode()->mChangeValue
== std::numeric_limits
<int32_t>::min();
203 inline bool nsCounterNode::IsReversed() {
204 return mType
== RESET
&& ChangeNode()->mIsReversed
;
207 inline bool nsCounterNode::IsUnitializedIncrementNode() {
208 return mType
== INCREMENT
&&
209 ChangeNode()->mChangeValue
== std::numeric_limits
<int32_t>::min();
212 class nsCounterList
: public nsGenConList
{
214 nsCounterList(nsAtom
* aCounterName
, mozilla::ContainStyleScope
* aScope
)
215 : nsGenConList(), mCounterName(aCounterName
), mScope(aScope
) {
219 // Return the first node for aFrame on this list, or nullptr.
220 nsCounterNode
* GetFirstNodeFor(nsIFrame
* aFrame
) const {
221 return static_cast<nsCounterNode
*>(nsGenConList::GetFirstNodeFor(aFrame
));
224 void Insert(nsCounterNode
* aNode
) {
225 nsGenConList::Insert(aNode
);
226 // Don't SetScope if we're dirty -- we'll reset all the scopes anyway,
227 // and we can't usefully compute scopes right now.
228 if (MOZ_LIKELY(!IsDirty())) {
233 nsCounterNode
* First() {
234 return static_cast<nsCounterNode
*>(mList
.getFirst());
237 static nsCounterNode
* Next(nsCounterNode
* aNode
) {
238 return static_cast<nsCounterNode
*>(nsGenConList::Next(aNode
));
240 static nsCounterNode
* Prev(nsCounterNode
* aNode
) {
241 return static_cast<nsCounterNode
*>(nsGenConList::Prev(aNode
));
244 static int32_t ValueBefore(nsCounterNode
* aNode
) {
245 if (!aNode
->mScopePrev
) {
249 if (aNode
->mType
!= nsCounterNode::USE
&&
250 aNode
->mScopePrev
->mCrossesContainStyleBoundaries
) {
254 return aNode
->mScopePrev
->mValueAfter
;
257 // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev|
258 void SetScope(nsCounterNode
* aNode
);
260 // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for
261 // all nodes and update text in text content nodes.
264 bool IsDirty() const;
266 bool IsRecalculatingAll() const { return mRecalculatingAll
; }
269 bool SetScopeByWalkingBackwardThroughList(
270 nsCounterNode
* aNodeToSetScopeFor
, const nsIContent
* aNodeContent
,
271 nsCounterNode
* aNodeToBeginLookingAt
);
273 RefPtr
<nsAtom
> mCounterName
;
274 mozilla::ContainStyleScope
* mScope
;
275 bool mRecalculatingAll
= false;
279 * The counter manager maintains an |nsCounterList| for each named
280 * counter to keep track of all scopes with that name.
282 class nsCounterManager
{
284 explicit nsCounterManager(mozilla::ContainStyleScope
* scope
)
287 // Returns true if dirty
288 bool AddCounterChanges(nsIFrame
* aFrame
);
290 // Gets the appropriate counter list, creating it if necessary.
291 // Guaranteed to return non-null. (Uses an infallible hashtable API.)
292 nsCounterList
* GetOrCreateCounterList(nsAtom
* aCounterName
);
294 // Gets the appropriate counter list, returning null if it doesn't exist.
295 nsCounterList
* GetCounterList(nsAtom
* aCounterName
);
297 // Clean up data in any dirty counter lists.
300 // Set all counter lists dirty
303 // Destroy nodes for the frame in any lists, and return whether any
304 // nodes were destroyed.
305 bool DestroyNodesFor(nsIFrame
* aFrame
);
308 void Clear() { mNames
.Clear(); }
311 // Set |aOrdinal| to the first used counter value for the given frame and
312 // return true. If no USE node for the given frame can be found, return false
313 // and do not change the value of |aOrdinal|.
314 bool GetFirstCounterValueForFrame(nsIFrame
* aFrame
,
315 mozilla::CounterValue
& aOrdinal
) const;
318 #if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
322 static int32_t IncrementCounter(int32_t aOldValue
, int32_t aIncrement
) {
323 // Addition of unsigned values is defined to be arithmetic
324 // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4);
325 // addition of signed values is undefined (and clang does
326 // something very strange if we use it here). Likewise integral
327 // conversion from signed to unsigned is also defined as modulo
328 // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion
329 // from unsigned to signed is however undefined (ibid., clause 3),
330 // but to do what we want we must nonetheless depend on that
331 // small piece of undefined behavior.
332 int32_t newValue
= int32_t(uint32_t(aOldValue
) + uint32_t(aIncrement
));
333 // The CSS Working Group resolved that a counter-increment that
334 // exceeds internal limits should not increment at all.
335 // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html
336 // (This means, for example, that if aIncrement is 5, the
337 // counter will get stuck at the largest multiple of 5 less than
338 // the maximum 32-bit integer.)
339 if ((aIncrement
> 0) != (newValue
> aOldValue
)) {
340 newValue
= aOldValue
;
346 mozilla::ContainStyleScope
* mScope
;
347 nsClassHashtable
<nsRefPtrHashKey
<nsAtom
>, nsCounterList
> mNames
;
350 #endif /* nsCounterManager_h_ */