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