|
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 #include "nsCounterManager.h" |
|
10 #include "nsBulletFrame.h" // legacy location for list style type to text code |
|
11 #include "nsContentUtils.h" |
|
12 #include "nsTArray.h" |
|
13 #include "mozilla/Likely.h" |
|
14 #include "nsIContent.h" |
|
15 |
|
16 bool |
|
17 nsCounterUseNode::InitTextFrame(nsGenConList* aList, |
|
18 nsIFrame* aPseudoFrame, nsIFrame* aTextFrame) |
|
19 { |
|
20 nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame); |
|
21 |
|
22 nsCounterList *counterList = static_cast<nsCounterList*>(aList); |
|
23 counterList->Insert(this); |
|
24 bool dirty = counterList->IsDirty(); |
|
25 if (!dirty) { |
|
26 if (counterList->IsLast(this)) { |
|
27 Calc(counterList); |
|
28 nsAutoString contentString; |
|
29 GetText(contentString); |
|
30 aTextFrame->GetContent()->SetText(contentString, false); |
|
31 } else { |
|
32 // In all other cases (list already dirty or node not at the end), |
|
33 // just start with an empty string for now and when we recalculate |
|
34 // the list we'll change the value to the right one. |
|
35 counterList->SetDirty(); |
|
36 return true; |
|
37 } |
|
38 } |
|
39 |
|
40 return false; |
|
41 } |
|
42 |
|
43 // assign the correct |mValueAfter| value to a node that has been inserted |
|
44 // Should be called immediately after calling |Insert|. |
|
45 void nsCounterUseNode::Calc(nsCounterList *aList) |
|
46 { |
|
47 NS_ASSERTION(!aList->IsDirty(), |
|
48 "Why are we calculating with a dirty list?"); |
|
49 mValueAfter = aList->ValueBefore(this); |
|
50 } |
|
51 |
|
52 // assign the correct |mValueAfter| value to a node that has been inserted |
|
53 // Should be called immediately after calling |Insert|. |
|
54 void nsCounterChangeNode::Calc(nsCounterList *aList) |
|
55 { |
|
56 NS_ASSERTION(!aList->IsDirty(), |
|
57 "Why are we calculating with a dirty list?"); |
|
58 if (mType == RESET) { |
|
59 mValueAfter = mChangeValue; |
|
60 } else { |
|
61 NS_ASSERTION(mType == INCREMENT, "invalid type"); |
|
62 mValueAfter = nsCounterManager::IncrementCounter(aList->ValueBefore(this), |
|
63 mChangeValue); |
|
64 } |
|
65 } |
|
66 |
|
67 // The text that should be displayed for this counter. |
|
68 void |
|
69 nsCounterUseNode::GetText(nsString& aResult) |
|
70 { |
|
71 aResult.Truncate(); |
|
72 |
|
73 nsAutoTArray<nsCounterNode*, 8> stack; |
|
74 stack.AppendElement(static_cast<nsCounterNode*>(this)); |
|
75 |
|
76 if (mAllCounters && mScopeStart) |
|
77 for (nsCounterNode *n = mScopeStart; n->mScopePrev; n = n->mScopeStart) |
|
78 stack.AppendElement(n->mScopePrev); |
|
79 |
|
80 const nsCSSValue& styleItem = mCounterStyle->Item(mAllCounters ? 2 : 1); |
|
81 int32_t style = styleItem.GetIntValue(); |
|
82 const char16_t* separator; |
|
83 if (mAllCounters) |
|
84 separator = mCounterStyle->Item(1).GetStringBufferValue(); |
|
85 |
|
86 for (uint32_t i = stack.Length() - 1;; --i) { |
|
87 nsCounterNode *n = stack[i]; |
|
88 bool isTextRTL; |
|
89 nsBulletFrame::AppendCounterText( |
|
90 style, n->mValueAfter, aResult, isTextRTL); |
|
91 if (i == 0) |
|
92 break; |
|
93 NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized"); |
|
94 aResult.Append(separator); |
|
95 } |
|
96 } |
|
97 |
|
98 void |
|
99 nsCounterList::SetScope(nsCounterNode *aNode) |
|
100 { |
|
101 // This function is responsible for setting |mScopeStart| and |
|
102 // |mScopePrev| (whose purpose is described in nsCounterManager.h). |
|
103 // We do this by starting from the node immediately preceding |
|
104 // |aNode| in content tree order, which is reasonably likely to be |
|
105 // the previous element in our scope (or, for a reset, the previous |
|
106 // element in the containing scope, which is what we want). If |
|
107 // we're not in the same scope that it is, then it's too deep in the |
|
108 // frame tree, so we walk up parent scopes until we find something |
|
109 // appropriate. |
|
110 |
|
111 if (aNode == First()) { |
|
112 aNode->mScopeStart = nullptr; |
|
113 aNode->mScopePrev = nullptr; |
|
114 return; |
|
115 } |
|
116 |
|
117 // Get the content node for aNode's rendering object's *parent*, |
|
118 // since scope includes siblings, so we want a descendant check on |
|
119 // parents. |
|
120 nsIContent *nodeContent = aNode->mPseudoFrame->GetContent()->GetParent(); |
|
121 |
|
122 for (nsCounterNode *prev = Prev(aNode), *start; |
|
123 prev; prev = start->mScopePrev) { |
|
124 // If |prev| starts a scope (because it's a real or implied |
|
125 // reset), we want it as the scope start rather than the start |
|
126 // of its enclosing scope. Otherwise, there's no enclosing |
|
127 // scope, so the next thing in prev's scope shares its scope |
|
128 // start. |
|
129 start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart) |
|
130 ? prev : prev->mScopeStart; |
|
131 |
|
132 // |startContent| is analogous to |nodeContent| (see above). |
|
133 nsIContent *startContent = start->mPseudoFrame->GetContent()->GetParent(); |
|
134 NS_ASSERTION(nodeContent || !startContent, |
|
135 "null check on startContent should be sufficient to " |
|
136 "null check nodeContent as well, since if nodeContent " |
|
137 "is for the root, startContent (which is before it) " |
|
138 "must be too"); |
|
139 |
|
140 // A reset's outer scope can't be a scope created by a sibling. |
|
141 if (!(aNode->mType == nsCounterNode::RESET && |
|
142 nodeContent == startContent) && |
|
143 // everything is inside the root (except the case above, |
|
144 // a second reset on the root) |
|
145 (!startContent || |
|
146 nsContentUtils::ContentIsDescendantOf(nodeContent, |
|
147 startContent))) { |
|
148 aNode->mScopeStart = start; |
|
149 aNode->mScopePrev = prev; |
|
150 return; |
|
151 } |
|
152 } |
|
153 |
|
154 aNode->mScopeStart = nullptr; |
|
155 aNode->mScopePrev = nullptr; |
|
156 } |
|
157 |
|
158 void |
|
159 nsCounterList::RecalcAll() |
|
160 { |
|
161 mDirty = false; |
|
162 |
|
163 nsCounterNode *node = First(); |
|
164 if (!node) |
|
165 return; |
|
166 |
|
167 do { |
|
168 SetScope(node); |
|
169 node->Calc(this); |
|
170 |
|
171 if (node->mType == nsCounterNode::USE) { |
|
172 nsCounterUseNode *useNode = node->UseNode(); |
|
173 // Null-check mText, since if the frame constructor isn't |
|
174 // batching, we could end up here while the node is being |
|
175 // constructed. |
|
176 if (useNode->mText) { |
|
177 nsAutoString text; |
|
178 useNode->GetText(text); |
|
179 useNode->mText->SetData(text); |
|
180 } |
|
181 } |
|
182 } while ((node = Next(node)) != First()); |
|
183 } |
|
184 |
|
185 nsCounterManager::nsCounterManager() |
|
186 : mNames(16) |
|
187 { |
|
188 } |
|
189 |
|
190 bool |
|
191 nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame) |
|
192 { |
|
193 const nsStyleContent *styleContent = aFrame->StyleContent(); |
|
194 if (!styleContent->CounterIncrementCount() && |
|
195 !styleContent->CounterResetCount()) |
|
196 return false; |
|
197 |
|
198 // Add in order, resets first, so all the comparisons will be optimized |
|
199 // for addition at the end of the list. |
|
200 int32_t i, i_end; |
|
201 bool dirty = false; |
|
202 for (i = 0, i_end = styleContent->CounterResetCount(); i != i_end; ++i) |
|
203 dirty |= AddResetOrIncrement(aFrame, i, |
|
204 styleContent->GetCounterResetAt(i), |
|
205 nsCounterChangeNode::RESET); |
|
206 for (i = 0, i_end = styleContent->CounterIncrementCount(); i != i_end; ++i) |
|
207 dirty |= AddResetOrIncrement(aFrame, i, |
|
208 styleContent->GetCounterIncrementAt(i), |
|
209 nsCounterChangeNode::INCREMENT); |
|
210 return dirty; |
|
211 } |
|
212 |
|
213 bool |
|
214 nsCounterManager::AddResetOrIncrement(nsIFrame *aFrame, int32_t aIndex, |
|
215 const nsStyleCounterData *aCounterData, |
|
216 nsCounterNode::Type aType) |
|
217 { |
|
218 nsCounterChangeNode *node = |
|
219 new nsCounterChangeNode(aFrame, aType, aCounterData->mValue, aIndex); |
|
220 |
|
221 nsCounterList *counterList = CounterListFor(aCounterData->mCounter); |
|
222 if (!counterList) { |
|
223 NS_NOTREACHED("CounterListFor failed (should only happen on OOM)"); |
|
224 return false; |
|
225 } |
|
226 |
|
227 counterList->Insert(node); |
|
228 if (!counterList->IsLast(node)) { |
|
229 // Tell the caller it's responsible for recalculating the entire |
|
230 // list. |
|
231 counterList->SetDirty(); |
|
232 return true; |
|
233 } |
|
234 |
|
235 // Don't call Calc() if the list is already dirty -- it'll be recalculated |
|
236 // anyway, and trying to calculate with a dirty list doesn't work. |
|
237 if (MOZ_LIKELY(!counterList->IsDirty())) { |
|
238 node->Calc(counterList); |
|
239 } |
|
240 return false; |
|
241 } |
|
242 |
|
243 nsCounterList* |
|
244 nsCounterManager::CounterListFor(const nsSubstring& aCounterName) |
|
245 { |
|
246 // XXX Why doesn't nsTHashtable provide an API that allows us to use |
|
247 // get/put in one hashtable lookup? |
|
248 nsCounterList *counterList; |
|
249 if (!mNames.Get(aCounterName, &counterList)) { |
|
250 counterList = new nsCounterList(); |
|
251 mNames.Put(aCounterName, counterList); |
|
252 } |
|
253 return counterList; |
|
254 } |
|
255 |
|
256 static PLDHashOperator |
|
257 RecalcDirtyLists(const nsAString& aKey, nsCounterList* aList, void* aClosure) |
|
258 { |
|
259 if (aList->IsDirty()) |
|
260 aList->RecalcAll(); |
|
261 return PL_DHASH_NEXT; |
|
262 } |
|
263 |
|
264 void |
|
265 nsCounterManager::RecalcAll() |
|
266 { |
|
267 mNames.EnumerateRead(RecalcDirtyLists, nullptr); |
|
268 } |
|
269 |
|
270 struct DestroyNodesData { |
|
271 DestroyNodesData(nsIFrame *aFrame) |
|
272 : mFrame(aFrame) |
|
273 , mDestroyedAny(false) |
|
274 { |
|
275 } |
|
276 |
|
277 nsIFrame *mFrame; |
|
278 bool mDestroyedAny; |
|
279 }; |
|
280 |
|
281 static PLDHashOperator |
|
282 DestroyNodesInList(const nsAString& aKey, nsCounterList* aList, void* aClosure) |
|
283 { |
|
284 DestroyNodesData *data = static_cast<DestroyNodesData*>(aClosure); |
|
285 if (aList->DestroyNodesFor(data->mFrame)) { |
|
286 data->mDestroyedAny = true; |
|
287 aList->SetDirty(); |
|
288 } |
|
289 return PL_DHASH_NEXT; |
|
290 } |
|
291 |
|
292 bool |
|
293 nsCounterManager::DestroyNodesFor(nsIFrame *aFrame) |
|
294 { |
|
295 DestroyNodesData data(aFrame); |
|
296 mNames.EnumerateRead(DestroyNodesInList, &data); |
|
297 return data.mDestroyedAny; |
|
298 } |
|
299 |
|
300 #ifdef DEBUG |
|
301 static PLDHashOperator |
|
302 DumpList(const nsAString& aKey, nsCounterList* aList, void* aClosure) |
|
303 { |
|
304 printf("Counter named \"%s\":\n", NS_ConvertUTF16toUTF8(aKey).get()); |
|
305 nsCounterNode *node = aList->First(); |
|
306 |
|
307 if (node) { |
|
308 int32_t i = 0; |
|
309 do { |
|
310 const char *types[] = { "RESET", "INCREMENT", "USE" }; |
|
311 printf(" Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n" |
|
312 " scope-start=%p scope-prev=%p", |
|
313 i++, (void*)node, (void*)node->mPseudoFrame, |
|
314 node->mContentIndex, types[node->mType], node->mValueAfter, |
|
315 (void*)node->mScopeStart, (void*)node->mScopePrev); |
|
316 if (node->mType == nsCounterNode::USE) { |
|
317 nsAutoString text; |
|
318 node->UseNode()->GetText(text); |
|
319 printf(" text=%s", NS_ConvertUTF16toUTF8(text).get()); |
|
320 } |
|
321 printf("\n"); |
|
322 } while ((node = aList->Next(node)) != aList->First()); |
|
323 } |
|
324 return PL_DHASH_NEXT; |
|
325 } |
|
326 |
|
327 void |
|
328 nsCounterManager::Dump() |
|
329 { |
|
330 printf("\n\nCounter Manager Lists:\n"); |
|
331 mNames.EnumerateRead(DumpList, nullptr); |
|
332 printf("\n\n"); |
|
333 } |
|
334 #endif |