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