Coverage Report

Created: 2018-09-25 14:53

/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