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