Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
7 /* implementation of CSS counters (for numbering things) */
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"
16 bool
17 nsCounterUseNode::InitTextFrame(nsGenConList* aList,
18 nsIFrame* aPseudoFrame, nsIFrame* aTextFrame)
19 {
20 nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
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 }
40 return false;
41 }
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 }
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 }
67 // The text that should be displayed for this counter.
68 void
69 nsCounterUseNode::GetText(nsString& aResult)
70 {
71 aResult.Truncate();
73 nsAutoTArray<nsCounterNode*, 8> stack;
74 stack.AppendElement(static_cast<nsCounterNode*>(this));
76 if (mAllCounters && mScopeStart)
77 for (nsCounterNode *n = mScopeStart; n->mScopePrev; n = n->mScopeStart)
78 stack.AppendElement(n->mScopePrev);
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();
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 }
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.
111 if (aNode == First()) {
112 aNode->mScopeStart = nullptr;
113 aNode->mScopePrev = nullptr;
114 return;
115 }
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();
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;
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");
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 }
154 aNode->mScopeStart = nullptr;
155 aNode->mScopePrev = nullptr;
156 }
158 void
159 nsCounterList::RecalcAll()
160 {
161 mDirty = false;
163 nsCounterNode *node = First();
164 if (!node)
165 return;
167 do {
168 SetScope(node);
169 node->Calc(this);
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 }
185 nsCounterManager::nsCounterManager()
186 : mNames(16)
187 {
188 }
190 bool
191 nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame)
192 {
193 const nsStyleContent *styleContent = aFrame->StyleContent();
194 if (!styleContent->CounterIncrementCount() &&
195 !styleContent->CounterResetCount())
196 return false;
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 }
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);
221 nsCounterList *counterList = CounterListFor(aCounterData->mCounter);
222 if (!counterList) {
223 NS_NOTREACHED("CounterListFor failed (should only happen on OOM)");
224 return false;
225 }
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 }
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 }
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 }
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 }
264 void
265 nsCounterManager::RecalcAll()
266 {
267 mNames.EnumerateRead(RecalcDirtyLists, nullptr);
268 }
270 struct DestroyNodesData {
271 DestroyNodesData(nsIFrame *aFrame)
272 : mFrame(aFrame)
273 , mDestroyedAny(false)
274 {
275 }
277 nsIFrame *mFrame;
278 bool mDestroyedAny;
279 };
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 }
292 bool
293 nsCounterManager::DestroyNodesFor(nsIFrame *aFrame)
294 {
295 DestroyNodesData data(aFrame);
296 mNames.EnumerateRead(DestroyNodesInList, &data);
297 return data.mDestroyedAny;
298 }
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();
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 }
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