Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/nsDOMTokenList.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
/*
8
 * Implementation of DOMTokenList specified by HTML5.
9
 */
10
11
#include "nsDOMTokenList.h"
12
#include "nsAttrValue.h"
13
#include "nsAttrValueInlines.h"
14
#include "nsDataHashtable.h"
15
#include "nsError.h"
16
#include "nsHashKeys.h"
17
#include "mozilla/dom/Element.h"
18
#include "mozilla/dom/DOMTokenListBinding.h"
19
#include "mozilla/BloomFilter.h"
20
#include "mozilla/ErrorResult.h"
21
22
using namespace mozilla;
23
using namespace mozilla::dom;
24
25
nsDOMTokenList::nsDOMTokenList(Element* aElement, nsAtom* aAttrAtom,
26
                               const DOMTokenListSupportedTokenArray aSupportedTokens)
27
  : mElement(aElement),
28
    mAttrAtom(aAttrAtom),
29
    mSupportedTokens(aSupportedTokens)
30
0
{
31
0
  // We don't add a reference to our element. If it goes away,
32
0
  // we'll be told to drop our reference
33
0
}
34
35
0
nsDOMTokenList::~nsDOMTokenList() { }
36
37
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMTokenList, mElement)
38
39
0
NS_INTERFACE_MAP_BEGIN(nsDOMTokenList)
40
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
41
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
42
0
  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsDOMTokenList)
43
0
NS_INTERFACE_MAP_END
44
45
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMTokenList)
46
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMTokenList)
47
48
const nsAttrValue*
49
nsDOMTokenList::GetParsedAttr()
50
0
{
51
0
  if (!mElement) {
52
0
    return nullptr;
53
0
  }
54
0
  return mElement->GetAttrInfo(kNameSpaceID_None, mAttrAtom).mValue;
55
0
}
56
57
void
58
nsDOMTokenList::RemoveDuplicates(const nsAttrValue* aAttr)
59
0
{
60
0
  if (!aAttr || aAttr->Type() != nsAttrValue::eAtomArray) {
61
0
    return;
62
0
  }
63
0
64
0
  BloomFilter<8, nsAtom> filter;
65
0
  AtomArray* array = aAttr->GetAtomArrayValue();
66
0
  for (uint32_t i = 0; i < array->Length(); i++) {
67
0
    nsAtom* atom = array->ElementAt(i);
68
0
    if (filter.mightContain(atom)) {
69
0
      // Start again, with a hashtable
70
0
      RemoveDuplicatesInternal(array, i);
71
0
      return;
72
0
    } else {
73
0
      filter.add(atom);
74
0
    }
75
0
  }
76
0
}
77
78
void
79
nsDOMTokenList::RemoveDuplicatesInternal(AtomArray* aArray, uint32_t aStart)
80
0
{
81
0
  nsDataHashtable<nsPtrHashKey<nsAtom>, bool> tokens;
82
0
83
0
  for (uint32_t i = 0; i < aArray->Length(); i++) {
84
0
    nsAtom* atom = aArray->ElementAt(i);
85
0
    // No need to check the hashtable below aStart
86
0
    if (i >= aStart && tokens.Get(atom)) {
87
0
      aArray->RemoveElementAt(i);
88
0
      i--;
89
0
    } else {
90
0
      tokens.Put(atom, true);
91
0
    }
92
0
  }
93
0
}
94
95
uint32_t
96
nsDOMTokenList::Length()
97
0
{
98
0
  const nsAttrValue* attr = GetParsedAttr();
99
0
  if (!attr) {
100
0
    return 0;
101
0
  }
102
0
103
0
  RemoveDuplicates(attr);
104
0
  return attr->GetAtomCount();
105
0
}
106
107
void
108
nsDOMTokenList::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aResult)
109
0
{
110
0
  const nsAttrValue* attr = GetParsedAttr();
111
0
112
0
  if (!attr || aIndex >= static_cast<uint32_t>(attr->GetAtomCount())) {
113
0
    aFound = false;
114
0
    return;
115
0
  }
116
0
117
0
  RemoveDuplicates(attr);
118
0
119
0
  if (attr && aIndex < static_cast<uint32_t>(attr->GetAtomCount())) {
120
0
    aFound = true;
121
0
    attr->AtomAt(aIndex)->ToString(aResult);
122
0
  } else {
123
0
    aFound = false;
124
0
  }
125
0
}
126
127
void
128
nsDOMTokenList::SetValue(const nsAString& aValue, ErrorResult& rv)
129
0
{
130
0
  if (!mElement) {
131
0
    return;
132
0
  }
133
0
134
0
  rv = mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true);
135
0
}
136
137
nsresult
138
nsDOMTokenList::CheckToken(const nsAString& aStr)
139
0
{
140
0
  if (aStr.IsEmpty()) {
141
0
    return NS_ERROR_DOM_SYNTAX_ERR;
142
0
  }
143
0
144
0
  nsAString::const_iterator iter, end;
145
0
  aStr.BeginReading(iter);
146
0
  aStr.EndReading(end);
147
0
148
0
  while (iter != end) {
149
0
    if (nsContentUtils::IsHTMLWhitespace(*iter))
150
0
      return NS_ERROR_DOM_INVALID_CHARACTER_ERR;
151
0
    ++iter;
152
0
  }
153
0
154
0
  return NS_OK;
155
0
}
156
157
nsresult
158
nsDOMTokenList::CheckTokens(const nsTArray<nsString>& aTokens)
159
0
{
160
0
  for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
161
0
    nsresult rv = CheckToken(aTokens[i]);
162
0
    if (NS_FAILED(rv)) {
163
0
      return rv;
164
0
    }
165
0
  }
166
0
167
0
  return NS_OK;
168
0
}
169
170
bool
171
nsDOMTokenList::Contains(const nsAString& aToken)
172
0
{
173
0
  const nsAttrValue* attr = GetParsedAttr();
174
0
  return attr && attr->Contains(aToken);
175
0
}
176
177
void
178
nsDOMTokenList::AddInternal(const nsAttrValue* aAttr,
179
                            const nsTArray<nsString>& aTokens)
180
0
{
181
0
  if (!mElement) {
182
0
    return;
183
0
  }
184
0
185
0
  nsAutoString resultStr;
186
0
187
0
  if (aAttr) {
188
0
    RemoveDuplicates(aAttr);
189
0
    for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
190
0
      if (i != 0) {
191
0
        resultStr.AppendLiteral(" ");
192
0
      }
193
0
      resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
194
0
    }
195
0
  }
196
0
197
0
  AutoTArray<nsString, 10> addedClasses;
198
0
199
0
  for (uint32_t i = 0, l = aTokens.Length(); i < l; ++i) {
200
0
    const nsString& aToken = aTokens[i];
201
0
202
0
    if ((aAttr && aAttr->Contains(aToken)) ||
203
0
        addedClasses.Contains(aToken)) {
204
0
      continue;
205
0
    }
206
0
207
0
    if (!resultStr.IsEmpty()) {
208
0
      resultStr.Append(' ');
209
0
    }
210
0
    resultStr.Append(aToken);
211
0
212
0
    addedClasses.AppendElement(aToken);
213
0
  }
214
0
215
0
  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
216
0
}
217
218
void
219
nsDOMTokenList::Add(const nsTArray<nsString>& aTokens, ErrorResult& aError)
220
0
{
221
0
  aError = CheckTokens(aTokens);
222
0
  if (aError.Failed()) {
223
0
    return;
224
0
  }
225
0
226
0
  const nsAttrValue* attr = GetParsedAttr();
227
0
  AddInternal(attr, aTokens);
228
0
}
229
230
void
231
nsDOMTokenList::Add(const nsAString& aToken, ErrorResult& aError)
232
0
{
233
0
  AutoTArray<nsString, 1> tokens;
234
0
  tokens.AppendElement(aToken);
235
0
  Add(tokens, aError);
236
0
}
237
238
void
239
nsDOMTokenList::RemoveInternal(const nsAttrValue* aAttr,
240
                               const nsTArray<nsString>& aTokens)
241
0
{
242
0
  MOZ_ASSERT(aAttr, "Need an attribute");
243
0
244
0
  RemoveDuplicates(aAttr);
245
0
246
0
  nsAutoString resultStr;
247
0
  for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
248
0
    if (aTokens.Contains(nsDependentAtomString(aAttr->AtomAt(i)))) {
249
0
      continue;
250
0
    }
251
0
    if (!resultStr.IsEmpty()) {
252
0
      resultStr.AppendLiteral(" ");
253
0
    }
254
0
    resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
255
0
  }
256
0
257
0
  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
258
0
}
259
260
void
261
nsDOMTokenList::Remove(const nsTArray<nsString>& aTokens, ErrorResult& aError)
262
0
{
263
0
  aError = CheckTokens(aTokens);
264
0
  if (aError.Failed()) {
265
0
    return;
266
0
  }
267
0
268
0
  const nsAttrValue* attr = GetParsedAttr();
269
0
  if (!attr) {
270
0
    return;
271
0
  }
272
0
273
0
  RemoveInternal(attr, aTokens);
274
0
}
275
276
void
277
nsDOMTokenList::Remove(const nsAString& aToken, ErrorResult& aError)
278
0
{
279
0
  AutoTArray<nsString, 1> tokens;
280
0
  tokens.AppendElement(aToken);
281
0
  Remove(tokens, aError);
282
0
}
283
284
bool
285
nsDOMTokenList::Toggle(const nsAString& aToken,
286
                       const Optional<bool>& aForce,
287
                       ErrorResult& aError)
288
0
{
289
0
  aError = CheckToken(aToken);
290
0
  if (aError.Failed()) {
291
0
    return false;
292
0
  }
293
0
294
0
  const nsAttrValue* attr = GetParsedAttr();
295
0
  const bool forceOn = aForce.WasPassed() && aForce.Value();
296
0
  const bool forceOff = aForce.WasPassed() && !aForce.Value();
297
0
298
0
  bool isPresent = attr && attr->Contains(aToken);
299
0
  AutoTArray<nsString, 1> tokens;
300
0
  (*tokens.AppendElement()).Rebind(aToken.Data(), aToken.Length());
301
0
302
0
  if (isPresent) {
303
0
    if (!forceOn) {
304
0
      RemoveInternal(attr, tokens);
305
0
      isPresent = false;
306
0
    }
307
0
  } else {
308
0
    if (!forceOff) {
309
0
      AddInternal(attr, tokens);
310
0
      isPresent = true;
311
0
    }
312
0
  }
313
0
314
0
  return isPresent;
315
0
}
316
317
bool
318
nsDOMTokenList::Replace(const nsAString& aToken,
319
                        const nsAString& aNewToken,
320
                        ErrorResult& aError)
321
0
{
322
0
  // Doing this here instead of using `CheckToken` because if aToken had invalid
323
0
  // characters, and aNewToken is empty, the returned error should be a
324
0
  // SyntaxError, not an InvalidCharacterError.
325
0
  if (aNewToken.IsEmpty()) {
326
0
    aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
327
0
    return false;
328
0
  }
329
0
330
0
  aError = CheckToken(aToken);
331
0
  if (aError.Failed()) {
332
0
    return false;
333
0
  }
334
0
335
0
  aError = CheckToken(aNewToken);
336
0
  if (aError.Failed()) {
337
0
    return false;
338
0
  }
339
0
340
0
  const nsAttrValue* attr = GetParsedAttr();
341
0
  if (!attr) {
342
0
    return false;
343
0
  }
344
0
345
0
  return ReplaceInternal(attr, aToken, aNewToken);
346
0
}
347
348
bool
349
nsDOMTokenList::ReplaceInternal(const nsAttrValue* aAttr,
350
                                const nsAString& aToken,
351
                                const nsAString& aNewToken)
352
0
{
353
0
  RemoveDuplicates(aAttr);
354
0
355
0
  // Trying to do a single pass here leads to really complicated code.  Just do
356
0
  // the simple thing.
357
0
  bool haveOld = false;
358
0
  for (uint32_t i = 0; i < aAttr->GetAtomCount(); ++i) {
359
0
    if (aAttr->AtomAt(i)->Equals(aToken)) {
360
0
      haveOld = true;
361
0
      break;
362
0
    }
363
0
  }
364
0
  if (!haveOld) {
365
0
    // Make sure to not touch the attribute value in this case.
366
0
    return false;
367
0
  }
368
0
369
0
  bool sawIt = false;
370
0
  nsAutoString resultStr;
371
0
  for (uint32_t i = 0; i < aAttr->GetAtomCount(); i++) {
372
0
    if (aAttr->AtomAt(i)->Equals(aToken) ||
373
0
        aAttr->AtomAt(i)->Equals(aNewToken)) {
374
0
      if (sawIt) {
375
0
        // We keep only the first
376
0
        continue;
377
0
      }
378
0
      sawIt = true;
379
0
      if (!resultStr.IsEmpty()) {
380
0
        resultStr.AppendLiteral(" ");
381
0
      }
382
0
      resultStr.Append(aNewToken);
383
0
      continue;
384
0
    }
385
0
    if (!resultStr.IsEmpty()) {
386
0
      resultStr.AppendLiteral(" ");
387
0
    }
388
0
    resultStr.Append(nsDependentAtomString(aAttr->AtomAt(i)));
389
0
  }
390
0
391
0
  MOZ_ASSERT(sawIt, "How could we not have found our token this time?");
392
0
  mElement->SetAttr(kNameSpaceID_None, mAttrAtom, resultStr, true);
393
0
  return true;
394
0
}
395
396
bool
397
nsDOMTokenList::Supports(const nsAString& aToken,
398
                         ErrorResult& aError)
399
0
{
400
0
  if (!mSupportedTokens) {
401
0
    aError.ThrowTypeError<MSG_TOKENLIST_NO_SUPPORTED_TOKENS>(
402
0
      mElement->LocalName(),
403
0
      nsDependentAtomString(mAttrAtom));
404
0
    return false;
405
0
  }
406
0
407
0
  for (DOMTokenListSupportedToken* supportedToken = mSupportedTokens;
408
0
       *supportedToken;
409
0
       ++supportedToken) {
410
0
    if (aToken.LowerCaseEqualsASCII(*supportedToken)) {
411
0
      return true;
412
0
    }
413
0
  }
414
0
415
0
  return false;
416
0
}
417
418
void
419
nsDOMTokenList::Stringify(nsAString& aResult)
420
0
{
421
0
  if (!mElement) {
422
0
    aResult.Truncate();
423
0
    return;
424
0
  }
425
0
426
0
  mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult);
427
0
}
428
429
DocGroup*
430
nsDOMTokenList::GetDocGroup() const
431
0
{
432
0
  return mElement ? mElement->OwnerDoc()->GetDocGroup() : nullptr;
433
0
}
434
435
JSObject*
436
nsDOMTokenList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
437
0
{
438
0
  return DOMTokenList_Binding::Wrap(cx, this, aGivenProto);
439
0
}
440