Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / base / nsCounterManager.cpp
blob340075be1114173b1cbe1789357dd16ec6100ce5
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
14 * License.
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.
22 * Contributor(s):
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"
44 #include "nsTArray.h"
46 PRBool
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();
55 if (!dirty) {
56 if (counterList->IsLast(this)) {
57 Calc(counterList);
58 nsAutoString contentString;
59 GetText(contentString);
60 aTextFrame->GetContent()->SetText(contentString, PR_FALSE);
61 } else {
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();
66 return PR_TRUE;
70 return PR_FALSE;
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?");
88 if (mType == RESET) {
89 mValueAfter = mChangeValue;
90 } else {
91 NS_ASSERTION(mType == INCREMENT, "invalid type");
92 mValueAfter = aList->ValueBefore(this) + mChangeValue;
96 // The text that should be displayed for this counter.
97 void
98 nsCounterUseNode::GetText(nsString& aResult)
100 aResult.Truncate();
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;
112 if (mAllCounters)
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);
118 if (i == 0)
119 break;
120 NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized");
121 aResult.Append(separator);
125 void
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
136 // appropriate.
138 if (aNode == First()) {
139 aNode->mScopeStart = nsnull;
140 aNode->mScopePrev = nsnull;
141 return;
144 // Get the content node for aNode's rendering object's *parent*,
145 // since scope includes siblings, so we want a descendant check on
146 // parents.
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
155 // start.
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) "
165 "must be too");
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)
172 (!startContent ||
173 nsContentUtils::ContentIsDescendantOf(nodeContent,
174 startContent))) {
175 aNode->mScopeStart = start;
176 aNode->mScopePrev = prev;
177 return;
181 aNode->mScopeStart = nsnull;
182 aNode->mScopePrev = nsnull;
185 void
186 nsCounterList::RecalcAll()
188 mDirty = PR_FALSE;
190 nsCounterNode *node = First();
191 if (!node)
192 return;
194 do {
195 SetScope(node);
196 node->Calc(this);
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
202 // constructed.
203 if (useNode->mText) {
204 nsAutoString text;
205 useNode->GetText(text);
206 useNode->mText->SetData(text);
209 } while ((node = Next(node)) != First());
212 nsCounterManager::nsCounterManager()
214 mNames.Init(16);
217 PRBool
218 nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame)
220 const nsStyleContent *styleContent = aFrame->GetStyleContent();
221 if (!styleContent->CounterIncrementCount() &&
222 !styleContent->CounterResetCount())
223 return PR_FALSE;
225 // Add in order, resets first, so all the comparisons will be optimized
226 // for addition at the end of the list.
227 PRInt32 i, i_end;
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);
237 return dirty;
240 PRBool
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);
247 if (!node)
248 return PR_FALSE;
250 nsCounterList *counterList = CounterListFor(aCounterData->mCounter);
251 if (!counterList) {
252 NS_NOTREACHED("CounterListFor failed (should only happen on OOM)");
253 return PR_FALSE;
256 counterList->Insert(node);
257 if (!counterList->IsLast(node)) {
258 // Tell the caller it's responsible for recalculating the entire
259 // list.
260 counterList->SetDirty();
261 return PR_TRUE;
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);
269 return PR_FALSE;
272 nsCounterList*
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();
280 if (!counterList)
281 return nsnull;
282 if (!mNames.Put(aCounterName, counterList)) {
283 delete counterList;
284 return nsnull;
287 return counterList;
290 static PLDHashOperator
291 RecalcDirtyLists(const nsAString& aKey, nsCounterList* aList, void* aClosure)
293 if (aList->IsDirty())
294 aList->RecalcAll();
295 return PL_DHASH_NEXT;
298 void
299 nsCounterManager::RecalcAll()
301 mNames.EnumerateRead(RecalcDirtyLists, nsnull);
304 struct DestroyNodesData {
305 DestroyNodesData(nsIFrame *aFrame)
306 : mFrame(aFrame)
307 , mDestroyedAny(PR_FALSE)
311 nsIFrame *mFrame;
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;
321 aList->SetDirty();
323 return PL_DHASH_NEXT;
326 PRBool
327 nsCounterManager::DestroyNodesFor(nsIFrame *aFrame)
329 DestroyNodesData data(aFrame);
330 mNames.EnumerateRead(DestroyNodesInList, &data);
331 return data.mDestroyedAny;
334 #ifdef DEBUG
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();
341 if (node) {
342 PRInt32 i = 0;
343 do {
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) {
351 nsAutoString text;
352 node->UseNode()->GetText(text);
353 printf(" text=%s", NS_ConvertUTF16toUTF8(text).get());
355 printf("\n");
356 } while ((node = aList->Next(node)) != aList->First());
358 return PL_DHASH_NEXT;
361 void
362 nsCounterManager::Dump()
364 printf("\n\nCounter Manager Lists:\n");
365 mNames.EnumerateRead(DumpList, nsnull);
366 printf("\n\n");
368 #endif