1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 // vim:cindent:ai:sw=4:ts=4:et:
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is nsCounterManager.
18 * The Initial Developer of the Original Code is the Mozilla Foundation.
19 * Portions created by the Initial Developer are Copyright (C) 2004
20 * the Initial Developer. All Rights Reserved.
23 * L. David Baron <dbaron@dbaron.org> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 /* implementation of CSS counters (for numbering things) */
41 #include "nsCounterManager.h"
42 #include "nsBulletFrame.h" // legacy location for list style type to text code
43 #include "nsContentUtils.h"
47 nsCounterUseNode::InitTextFrame(nsGenConList
* aList
,
48 nsIFrame
* aPseudoFrame
, nsIFrame
* aTextFrame
)
50 nsCounterNode::InitTextFrame(aList
, aPseudoFrame
, aTextFrame
);
52 nsCounterList
*counterList
= static_cast<nsCounterList
*>(aList
);
53 counterList
->Insert(this);
54 PRBool dirty
= counterList
->IsDirty();
56 if (counterList
->IsLast(this)) {
58 nsAutoString contentString
;
59 GetText(contentString
);
60 aTextFrame
->GetContent()->SetText(contentString
, PR_FALSE
);
62 // In all other cases (list already dirty or node not at the end),
63 // just start with an empty string for now and when we recalculate
64 // the list we'll change the value to the right one.
65 counterList
->SetDirty();
73 // assign the correct |mValueAfter| value to a node that has been inserted
74 // Should be called immediately after calling |Insert|.
75 void nsCounterUseNode::Calc(nsCounterList
*aList
)
77 NS_ASSERTION(!aList
->IsDirty(),
78 "Why are we calculating with a dirty list?");
79 mValueAfter
= aList
->ValueBefore(this);
82 // assign the correct |mValueAfter| value to a node that has been inserted
83 // Should be called immediately after calling |Insert|.
84 void nsCounterChangeNode::Calc(nsCounterList
*aList
)
86 NS_ASSERTION(!aList
->IsDirty(),
87 "Why are we calculating with a dirty list?");
89 mValueAfter
= mChangeValue
;
91 NS_ASSERTION(mType
== INCREMENT
, "invalid type");
92 mValueAfter
= aList
->ValueBefore(this) + mChangeValue
;
96 // The text that should be displayed for this counter.
98 nsCounterUseNode::GetText(nsString
& aResult
)
102 nsAutoTArray
<nsCounterNode
*, 8> stack
;
103 stack
.AppendElement(static_cast<nsCounterNode
*>(this));
105 if (mAllCounters
&& mScopeStart
)
106 for (nsCounterNode
*n
= mScopeStart
; n
->mScopePrev
; n
= n
->mScopeStart
)
107 stack
.AppendElement(n
->mScopePrev
);
109 const nsCSSValue
& styleItem
= mCounterStyle
->Item(mAllCounters
? 2 : 1);
110 PRInt32 style
= styleItem
.GetIntValue();
111 const PRUnichar
* separator
;
113 separator
= mCounterStyle
->Item(1).GetStringBufferValue();
115 for (PRUint32 i
= stack
.Length() - 1;; --i
) {
116 nsCounterNode
*n
= stack
[i
];
117 nsBulletFrame::AppendCounterText(style
, n
->mValueAfter
, aResult
);
120 NS_ASSERTION(mAllCounters
, "yikes, separator is uninitialized");
121 aResult
.Append(separator
);
126 nsCounterList::SetScope(nsCounterNode
*aNode
)
128 // This function is responsible for setting |mScopeStart| and
129 // |mScopePrev| (whose purpose is described in nsCounterManager.h).
130 // We do this by starting from the node immediately preceding
131 // |aNode| in content tree order, which is reasonably likely to be
132 // the previous element in our scope (or, for a reset, the previous
133 // element in the containing scope, which is what we want). If
134 // we're not in the same scope that it is, then it's too deep in the
135 // frame tree, so we walk up parent scopes until we find something
138 if (aNode
== First()) {
139 aNode
->mScopeStart
= nsnull
;
140 aNode
->mScopePrev
= nsnull
;
144 // Get the content node for aNode's rendering object's *parent*,
145 // since scope includes siblings, so we want a descendant check on
147 nsIContent
*nodeContent
= aNode
->mPseudoFrame
->GetContent()->GetParent();
149 for (nsCounterNode
*prev
= Prev(aNode
), *start
;
150 prev
; prev
= start
->mScopePrev
) {
151 // If |prev| starts a scope (because it's a real or implied
152 // reset), we want it as the scope start rather than the start
153 // of its enclosing scope. Otherwise, there's no enclosing
154 // scope, so the next thing in prev's scope shares its scope
156 start
= (prev
->mType
== nsCounterNode::RESET
|| !prev
->mScopeStart
)
157 ? prev
: prev
->mScopeStart
;
159 // |startContent| is analogous to |nodeContent| (see above).
160 nsIContent
*startContent
= start
->mPseudoFrame
->GetContent()->GetParent();
161 NS_ASSERTION(nodeContent
|| !startContent
,
162 "null check on startContent should be sufficient to "
163 "null check nodeContent as well, since if nodeContent "
164 "is for the root, startContent (which is before it) "
167 // A reset's outer scope can't be a scope created by a sibling.
168 if (!(aNode
->mType
== nsCounterNode::RESET
&&
169 nodeContent
== startContent
) &&
170 // everything is inside the root (except the case above,
171 // a second reset on the root)
173 nsContentUtils::ContentIsDescendantOf(nodeContent
,
175 aNode
->mScopeStart
= start
;
176 aNode
->mScopePrev
= prev
;
181 aNode
->mScopeStart
= nsnull
;
182 aNode
->mScopePrev
= nsnull
;
186 nsCounterList::RecalcAll()
190 nsCounterNode
*node
= First();
198 if (node
->mType
== nsCounterNode::USE
) {
199 nsCounterUseNode
*useNode
= node
->UseNode();
200 // Null-check mText, since if the frame constructor isn't
201 // batching, we could end up here while the node is being
203 if (useNode
->mText
) {
205 useNode
->GetText(text
);
206 useNode
->mText
->SetData(text
);
209 } while ((node
= Next(node
)) != First());
212 nsCounterManager::nsCounterManager()
218 nsCounterManager::AddCounterResetsAndIncrements(nsIFrame
*aFrame
)
220 const nsStyleContent
*styleContent
= aFrame
->GetStyleContent();
221 if (!styleContent
->CounterIncrementCount() &&
222 !styleContent
->CounterResetCount())
225 // Add in order, resets first, so all the comparisons will be optimized
226 // for addition at the end of the list.
228 PRBool dirty
= PR_FALSE
;
229 for (i
= 0, i_end
= styleContent
->CounterResetCount(); i
!= i_end
; ++i
)
230 dirty
|= AddResetOrIncrement(aFrame
, i
,
231 styleContent
->GetCounterResetAt(i
),
232 nsCounterChangeNode::RESET
);
233 for (i
= 0, i_end
= styleContent
->CounterIncrementCount(); i
!= i_end
; ++i
)
234 dirty
|= AddResetOrIncrement(aFrame
, i
,
235 styleContent
->GetCounterIncrementAt(i
),
236 nsCounterChangeNode::INCREMENT
);
241 nsCounterManager::AddResetOrIncrement(nsIFrame
*aFrame
, PRInt32 aIndex
,
242 const nsStyleCounterData
*aCounterData
,
243 nsCounterNode::Type aType
)
245 nsCounterChangeNode
*node
=
246 new nsCounterChangeNode(aFrame
, aType
, aCounterData
->mValue
, aIndex
);
250 nsCounterList
*counterList
= CounterListFor(aCounterData
->mCounter
);
252 NS_NOTREACHED("CounterListFor failed (should only happen on OOM)");
256 counterList
->Insert(node
);
257 if (!counterList
->IsLast(node
)) {
258 // Tell the caller it's responsible for recalculating the entire
260 counterList
->SetDirty();
264 // Don't call Calc() if the list is already dirty -- it'll be recalculated
265 // anyway, and trying to calculate with a dirty list doesn't work.
266 if (NS_LIKELY(!counterList
->IsDirty())) {
267 node
->Calc(counterList
);
273 nsCounterManager::CounterListFor(const nsSubstring
& aCounterName
)
275 // XXX Why doesn't nsTHashtable provide an API that allows us to use
276 // get/put in one hashtable lookup?
277 nsCounterList
*counterList
;
278 if (!mNames
.Get(aCounterName
, &counterList
)) {
279 counterList
= new nsCounterList();
282 if (!mNames
.Put(aCounterName
, counterList
)) {
290 static PLDHashOperator
291 RecalcDirtyLists(const nsAString
& aKey
, nsCounterList
* aList
, void* aClosure
)
293 if (aList
->IsDirty())
295 return PL_DHASH_NEXT
;
299 nsCounterManager::RecalcAll()
301 mNames
.EnumerateRead(RecalcDirtyLists
, nsnull
);
304 struct DestroyNodesData
{
305 DestroyNodesData(nsIFrame
*aFrame
)
307 , mDestroyedAny(PR_FALSE
)
312 PRBool mDestroyedAny
;
315 static PLDHashOperator
316 DestroyNodesInList(const nsAString
& aKey
, nsCounterList
* aList
, void* aClosure
)
318 DestroyNodesData
*data
= static_cast<DestroyNodesData
*>(aClosure
);
319 if (aList
->DestroyNodesFor(data
->mFrame
)) {
320 data
->mDestroyedAny
= PR_TRUE
;
323 return PL_DHASH_NEXT
;
327 nsCounterManager::DestroyNodesFor(nsIFrame
*aFrame
)
329 DestroyNodesData
data(aFrame
);
330 mNames
.EnumerateRead(DestroyNodesInList
, &data
);
331 return data
.mDestroyedAny
;
335 static PLDHashOperator
336 DumpList(const nsAString
& aKey
, nsCounterList
* aList
, void* aClosure
)
338 printf("Counter named \"%s\":\n", NS_ConvertUTF16toUTF8(aKey
).get());
339 nsCounterNode
*node
= aList
->First();
344 const char *types
[] = { "RESET", "INCREMENT", "USE" };
345 printf(" Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n"
346 " scope-start=%p scope-prev=%p",
347 i
++, (void*)node
, (void*)node
->mPseudoFrame
,
348 node
->mContentIndex
, types
[node
->mType
], node
->mValueAfter
,
349 (void*)node
->mScopeStart
, (void*)node
->mScopePrev
);
350 if (node
->mType
== nsCounterNode::USE
) {
352 node
->UseNode()->GetText(text
);
353 printf(" text=%s", NS_ConvertUTF16toUTF8(text
).get());
356 } while ((node
= aList
->Next(node
)) != aList
->First());
358 return PL_DHASH_NEXT
;
362 nsCounterManager::Dump()
364 printf("\n\nCounter Manager Lists:\n");
365 mNames
.EnumerateRead(DumpList
, nsnull
);