Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/base/AttrArray.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
 * Storage of the children and attributes of a DOM node; storage for
9
 * the two is unified to minimize footprint.
10
 */
11
12
#include "AttrArray.h"
13
14
#include "mozilla/CheckedInt.h"
15
#include "mozilla/MathAlgorithms.h"
16
#include "mozilla/MemoryReporting.h"
17
18
#include "nsMappedAttributeElement.h"
19
#include "nsString.h"
20
#include "nsHTMLStyleSheet.h"
21
#include "nsMappedAttributes.h"
22
#include "nsUnicharUtils.h"
23
#include "nsContentUtils.h" // nsAutoScriptBlocker
24
25
using mozilla::CheckedUint32;
26
27
AttrArray::Impl::~Impl()
28
0
{
29
0
  for (InternalAttr& attr : NonMappedAttrs()) {
30
0
    attr.~InternalAttr();
31
0
  }
32
0
33
0
  NS_IF_RELEASE(mMappedAttrs);
34
0
}
35
36
const nsAttrValue*
37
AttrArray::GetAttr(nsAtom* aLocalName, int32_t aNamespaceID) const
38
0
{
39
0
  if (aNamespaceID == kNameSpaceID_None) {
40
0
    // This should be the common case so lets make an optimized loop
41
0
    for (const InternalAttr& attr : NonMappedAttrs()) {
42
0
      if (attr.mName.Equals(aLocalName)) {
43
0
        return &attr.mValue;
44
0
      }
45
0
    }
46
0
47
0
    if (mImpl && mImpl->mMappedAttrs) {
48
0
      return mImpl->mMappedAttrs->GetAttr(aLocalName);
49
0
    }
50
0
  } else {
51
0
    for (const InternalAttr& attr : NonMappedAttrs()) {
52
0
      if (attr.mName.Equals(aLocalName, aNamespaceID)) {
53
0
        return &attr.mValue;
54
0
      }
55
0
    }
56
0
  }
57
0
58
0
  return nullptr;
59
0
}
60
61
const nsAttrValue*
62
AttrArray::GetAttr(const nsAString& aLocalName) const
63
0
{
64
0
  for (const InternalAttr& attr : NonMappedAttrs()) {
65
0
    if (attr.mName.Equals(aLocalName)) {
66
0
      return &attr.mValue;
67
0
    }
68
0
  }
69
0
70
0
  if (mImpl && mImpl->mMappedAttrs) {
71
0
    return mImpl->mMappedAttrs->GetAttr(aLocalName);
72
0
  }
73
0
74
0
  return nullptr;
75
0
}
76
77
const nsAttrValue*
78
AttrArray::GetAttr(const nsAString& aName,
79
                   nsCaseTreatment aCaseSensitive) const
80
0
{
81
0
  // Check whether someone is being silly and passing non-lowercase
82
0
  // attr names.
83
0
  if (aCaseSensitive == eIgnoreCase &&
84
0
      nsContentUtils::StringContainsASCIIUpper(aName)) {
85
0
    // Try again with a lowercased name, but make sure we can't reenter this
86
0
    // block by passing eCaseSensitive for aCaseSensitive.
87
0
    nsAutoString lowercase;
88
0
    nsContentUtils::ASCIIToLower(aName, lowercase);
89
0
    return GetAttr(lowercase, eCaseMatters);
90
0
  }
91
0
92
0
  for (const InternalAttr& attr : NonMappedAttrs()) {
93
0
    if (attr.mName.QualifiedNameEquals(aName)) {
94
0
      return &attr.mValue;
95
0
    }
96
0
  }
97
0
98
0
  if (mImpl && mImpl->mMappedAttrs) {
99
0
    return mImpl->mMappedAttrs->GetAttr(aName);
100
0
  }
101
0
102
0
  return nullptr;
103
0
}
104
105
const nsAttrValue*
106
AttrArray::AttrAt(uint32_t aPos) const
107
0
{
108
0
  NS_ASSERTION(aPos < AttrCount(),
109
0
               "out-of-bounds access in AttrArray");
110
0
111
0
  uint32_t nonmapped = NonMappedAttrCount();
112
0
  if (aPos < nonmapped) {
113
0
    return &mImpl->NonMappedAttrs()[aPos].mValue;
114
0
  }
115
0
116
0
  return mImpl->mMappedAttrs->AttrAt(aPos - nonmapped);
117
0
}
118
119
template<typename Name>
120
inline nsresult
121
AttrArray::AddNewAttribute(Name* aName, nsAttrValue& aValue)
122
0
{
123
0
  MOZ_ASSERT(!mImpl || mImpl->mCapacity >= mImpl->mAttrCount);
124
0
  if (!mImpl || mImpl->mCapacity == mImpl->mAttrCount) {
125
0
    if (!GrowBy(1)) {
126
0
      return NS_ERROR_OUT_OF_MEMORY;
127
0
    }
128
0
  }
129
0
130
0
  InternalAttr& attr = mImpl->mBuffer[mImpl->mAttrCount++];
131
0
  new (&attr.mName) nsAttrName(aName);
132
0
  new (&attr.mValue) nsAttrValue();
133
0
  attr.mValue.SwapValueWith(aValue);
134
0
  return NS_OK;
135
0
}
Unexecuted instantiation: nsresult AttrArray::AddNewAttribute<nsAtom>(nsAtom*, nsAttrValue&)
Unexecuted instantiation: nsresult AttrArray::AddNewAttribute<mozilla::dom::NodeInfo>(mozilla::dom::NodeInfo*, nsAttrValue&)
136
137
nsresult
138
AttrArray::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue,
139
                          bool* aHadValue)
140
0
{
141
0
  *aHadValue = false;
142
0
143
0
  for (InternalAttr& attr : NonMappedAttrs()) {
144
0
    if (attr.mName.Equals(aLocalName)) {
145
0
      attr.mValue.SwapValueWith(aValue);
146
0
      *aHadValue = true;
147
0
      return NS_OK;
148
0
    }
149
0
  }
150
0
151
0
  return AddNewAttribute(aLocalName, aValue);
152
0
}
153
154
nsresult
155
AttrArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName,
156
                          nsAttrValue& aValue, bool* aHadValue)
157
0
{
158
0
  int32_t namespaceID = aName->NamespaceID();
159
0
  nsAtom* localName = aName->NameAtom();
160
0
  if (namespaceID == kNameSpaceID_None) {
161
0
    return SetAndSwapAttr(localName, aValue, aHadValue);
162
0
  }
163
0
164
0
  *aHadValue = false;
165
0
  for (InternalAttr& attr : NonMappedAttrs()) {
166
0
    if (attr.mName.Equals(localName, namespaceID)) {
167
0
      attr.mName.SetTo(aName);
168
0
      attr.mValue.SwapValueWith(aValue);
169
0
      *aHadValue = true;
170
0
      return NS_OK;
171
0
    }
172
0
  }
173
0
174
0
  return AddNewAttribute(aName, aValue);
175
0
}
176
177
nsresult
178
AttrArray::RemoveAttrAt(uint32_t aPos, nsAttrValue& aValue)
179
0
{
180
0
  NS_ASSERTION(aPos < AttrCount(), "out-of-bounds");
181
0
182
0
  uint32_t nonmapped = NonMappedAttrCount();
183
0
  if (aPos < nonmapped) {
184
0
    mImpl->mBuffer[aPos].mValue.SwapValueWith(aValue);
185
0
    mImpl->mBuffer[aPos].~InternalAttr();
186
0
187
0
    memmove(mImpl->mBuffer + aPos,
188
0
            mImpl->mBuffer + aPos + 1,
189
0
            (mImpl->mAttrCount - aPos - 1) * sizeof(InternalAttr));
190
0
191
0
    --mImpl->mAttrCount;
192
0
193
0
    return NS_OK;
194
0
  }
195
0
196
0
  if (MappedAttrCount() == 1) {
197
0
    // We're removing the last mapped attribute.  Can't swap in this
198
0
    // case; have to copy.
199
0
    aValue.SetTo(*mImpl->mMappedAttrs->AttrAt(0));
200
0
    NS_RELEASE(mImpl->mMappedAttrs);
201
0
202
0
    return NS_OK;
203
0
  }
204
0
205
0
  RefPtr<nsMappedAttributes> mapped =
206
0
    GetModifiableMapped(nullptr, nullptr, false);
207
0
208
0
  mapped->RemoveAttrAt(aPos - nonmapped, aValue);
209
0
210
0
  return MakeMappedUnique(mapped);
211
0
}
212
213
mozilla::dom::BorrowedAttrInfo
214
AttrArray::AttrInfoAt(uint32_t aPos) const
215
0
{
216
0
  NS_ASSERTION(aPos < AttrCount(),
217
0
               "out-of-bounds access in AttrArray");
218
0
219
0
  uint32_t nonmapped = NonMappedAttrCount();
220
0
  if (aPos < nonmapped) {
221
0
    InternalAttr& attr = mImpl->mBuffer[aPos];
222
0
    return BorrowedAttrInfo(&attr.mName, &attr.mValue);
223
0
  }
224
0
225
0
  return BorrowedAttrInfo(
226
0
      mImpl->mMappedAttrs->NameAt(aPos - nonmapped),
227
0
      mImpl->mMappedAttrs->AttrAt(aPos - nonmapped));
228
0
}
229
230
const nsAttrName*
231
AttrArray::AttrNameAt(uint32_t aPos) const
232
0
{
233
0
  NS_ASSERTION(aPos < AttrCount(),
234
0
               "out-of-bounds access in AttrArray");
235
0
236
0
  uint32_t nonmapped = NonMappedAttrCount();
237
0
  if (aPos < nonmapped) {
238
0
    return &mImpl->mBuffer[aPos].mName;
239
0
  }
240
0
241
0
  return mImpl->mMappedAttrs->NameAt(aPos - nonmapped);
242
0
}
243
244
const nsAttrName*
245
AttrArray::GetSafeAttrNameAt(uint32_t aPos) const
246
0
{
247
0
  uint32_t nonmapped = NonMappedAttrCount();
248
0
  if (aPos < nonmapped) {
249
0
    return &mImpl->mBuffer[aPos].mName;
250
0
  }
251
0
252
0
  if (aPos >= AttrCount()) {
253
0
    return nullptr;
254
0
  }
255
0
256
0
  return mImpl->mMappedAttrs->NameAt(aPos - nonmapped);
257
0
}
258
259
const nsAttrName*
260
AttrArray::GetExistingAttrNameFromQName(const nsAString& aName) const
261
0
{
262
0
  for (const InternalAttr& attr : NonMappedAttrs()) {
263
0
    if (attr.mName.QualifiedNameEquals(aName)) {
264
0
      return &attr.mName;
265
0
    }
266
0
  }
267
0
268
0
  if (mImpl && mImpl->mMappedAttrs) {
269
0
    return mImpl->mMappedAttrs->GetExistingAttrNameFromQName(aName);
270
0
  }
271
0
272
0
  return nullptr;
273
0
}
274
275
int32_t
276
AttrArray::IndexOfAttr(nsAtom* aLocalName, int32_t aNamespaceID) const
277
0
{
278
0
  if (!mImpl) {
279
0
    return -1;
280
0
  }
281
0
282
0
  int32_t idx;
283
0
  if (mImpl->mMappedAttrs && aNamespaceID == kNameSpaceID_None) {
284
0
    idx = mImpl->mMappedAttrs->IndexOfAttr(aLocalName);
285
0
    if (idx >= 0) {
286
0
      return NonMappedAttrCount() + idx;
287
0
    }
288
0
  }
289
0
290
0
  uint32_t i = 0;
291
0
  if (aNamespaceID == kNameSpaceID_None) {
292
0
    // This should be the common case so lets make an optimized loop
293
0
    // Note that here we don't check for AttrSlotIsTaken() in the loop
294
0
    // condition for the sake of performance because comparing aLocalName
295
0
    // against null would fail in the loop body (since Equals() just compares
296
0
    // the raw pointer value of aLocalName to what AttrSlotIsTaken() would be
297
0
    // checking.
298
0
    for (const InternalAttr& attr : NonMappedAttrs()) {
299
0
      if (attr.mName.Equals(aLocalName)) {
300
0
        return i;
301
0
      }
302
0
      ++i;
303
0
    }
304
0
  } else {
305
0
    for (const InternalAttr& attr : NonMappedAttrs()) {
306
0
      if (attr.mName.Equals(aLocalName, aNamespaceID)) {
307
0
        return i;
308
0
      }
309
0
      ++i;
310
0
    }
311
0
  }
312
0
313
0
  return -1;
314
0
}
315
316
nsresult
317
AttrArray::SetAndSwapMappedAttr(nsAtom* aLocalName,
318
                                nsAttrValue& aValue,
319
                                nsMappedAttributeElement* aContent,
320
                                nsHTMLStyleSheet* aSheet,
321
                                bool* aHadValue)
322
0
{
323
0
  bool willAdd = true;
324
0
  if (mImpl && mImpl->mMappedAttrs) {
325
0
    willAdd = !mImpl->mMappedAttrs->GetAttr(aLocalName);
326
0
  }
327
0
328
0
  RefPtr<nsMappedAttributes> mapped =
329
0
    GetModifiableMapped(aContent, aSheet, willAdd);
330
0
331
0
  mapped->SetAndSwapAttr(aLocalName, aValue, aHadValue);
332
0
333
0
  return MakeMappedUnique(mapped);
334
0
}
335
336
nsresult
337
AttrArray::DoSetMappedAttrStyleSheet(nsHTMLStyleSheet* aSheet)
338
0
{
339
0
  MOZ_ASSERT(mImpl && mImpl->mMappedAttrs,
340
0
                  "Should have mapped attrs here!");
341
0
  if (aSheet == mImpl->mMappedAttrs->GetStyleSheet()) {
342
0
    return NS_OK;
343
0
  }
344
0
345
0
  RefPtr<nsMappedAttributes> mapped =
346
0
    GetModifiableMapped(nullptr, nullptr, false);
347
0
348
0
  mapped->SetStyleSheet(aSheet);
349
0
350
0
  return MakeMappedUnique(mapped);
351
0
}
352
353
nsresult
354
AttrArray::DoUpdateMappedAttrRuleMapper(nsMappedAttributeElement& aElement)
355
0
{
356
0
  MOZ_ASSERT(mImpl && mImpl->mMappedAttrs, "Should have mapped attrs here!");
357
0
358
0
  // First two args don't matter if the assert holds.
359
0
  RefPtr<nsMappedAttributes> mapped =
360
0
    GetModifiableMapped(nullptr, nullptr, false);
361
0
362
0
  mapped->SetRuleMapper(aElement.GetAttributeMappingFunction());
363
0
364
0
  return MakeMappedUnique(mapped);
365
0
}
366
367
void
368
AttrArray::Compact()
369
0
{
370
0
  if (!mImpl) {
371
0
    return;
372
0
  }
373
0
374
0
  if (!mImpl->mAttrCount && !mImpl->mMappedAttrs) {
375
0
    mImpl.reset();
376
0
    return;
377
0
  }
378
0
379
0
  // Nothing to do.
380
0
  if (mImpl->mAttrCount == mImpl->mCapacity) {
381
0
    return;
382
0
  }
383
0
384
0
  Impl* impl = mImpl.release();
385
0
  impl = static_cast<Impl*>(
386
0
    realloc(impl, Impl::AllocationSizeForAttributes(impl->mAttrCount)));
387
0
  MOZ_ASSERT(impl, "failed to reallocate to a smaller buffer!");
388
0
  impl->mCapacity = impl->mAttrCount;
389
0
  mImpl.reset(impl);
390
0
}
391
392
uint32_t
393
AttrArray::DoGetMappedAttrCount() const
394
0
{
395
0
  MOZ_ASSERT(mImpl && mImpl->mMappedAttrs);
396
0
  return static_cast<uint32_t>(mImpl->mMappedAttrs->Count());
397
0
}
398
399
nsresult
400
AttrArray::ForceMapped(nsMappedAttributeElement* aContent, nsIDocument* aDocument)
401
0
{
402
0
  nsHTMLStyleSheet* sheet = aDocument->GetAttributeStyleSheet();
403
0
  RefPtr<nsMappedAttributes> mapped = GetModifiableMapped(aContent, sheet, false, 0);
404
0
  return MakeMappedUnique(mapped);
405
0
}
406
407
void
408
AttrArray::ClearMappedServoStyle()
409
0
{
410
0
  if (mImpl && mImpl->mMappedAttrs) {
411
0
    mImpl->mMappedAttrs->ClearServoStyle();
412
0
  }
413
0
}
414
415
nsMappedAttributes*
416
AttrArray::GetModifiableMapped(nsMappedAttributeElement* aContent,
417
                               nsHTMLStyleSheet* aSheet,
418
                               bool aWillAddAttr,
419
                               int32_t aAttrCount)
420
0
{
421
0
  if (mImpl && mImpl->mMappedAttrs) {
422
0
    return mImpl->mMappedAttrs->Clone(aWillAddAttr);
423
0
  }
424
0
425
0
  MOZ_ASSERT(aContent, "Trying to create modifiable without content");
426
0
427
0
  nsMapRuleToAttributesFunc mapRuleFunc =
428
0
    aContent->GetAttributeMappingFunction();
429
0
  return new (aAttrCount) nsMappedAttributes(aSheet, mapRuleFunc);
430
0
}
431
432
nsresult
433
AttrArray::MakeMappedUnique(nsMappedAttributes* aAttributes)
434
0
{
435
0
  NS_ASSERTION(aAttributes, "missing attributes");
436
0
437
0
  if (!mImpl && !GrowBy(1)) {
438
0
    return NS_ERROR_OUT_OF_MEMORY;
439
0
  }
440
0
441
0
  if (!aAttributes->GetStyleSheet()) {
442
0
    // This doesn't currently happen, but it could if we do loading right
443
0
444
0
    RefPtr<nsMappedAttributes> mapped(aAttributes);
445
0
    mapped.swap(mImpl->mMappedAttrs);
446
0
447
0
    return NS_OK;
448
0
  }
449
0
450
0
  RefPtr<nsMappedAttributes> mapped =
451
0
    aAttributes->GetStyleSheet()->UniqueMappedAttributes(aAttributes);
452
0
  NS_ENSURE_TRUE(mapped, NS_ERROR_OUT_OF_MEMORY);
453
0
454
0
  if (mapped != aAttributes) {
455
0
    // Reset the stylesheet of aAttributes so that it doesn't spend time
456
0
    // trying to remove itself from the hash. There is no risk that aAttributes
457
0
    // is in the hash since it will always have come from GetModifiableMapped,
458
0
    // which never returns maps that are in the hash (such hashes are by
459
0
    // nature not modifiable).
460
0
    aAttributes->DropStyleSheetReference();
461
0
  }
462
0
  mapped.swap(mImpl->mMappedAttrs);
463
0
464
0
  return NS_OK;
465
0
}
466
467
const nsMappedAttributes*
468
AttrArray::GetMapped() const
469
0
{
470
0
  return mImpl ? mImpl->mMappedAttrs : nullptr;
471
0
}
472
473
nsresult
474
AttrArray::EnsureCapacityToClone(const AttrArray& aOther)
475
0
{
476
0
  MOZ_ASSERT(!mImpl, "AttrArray::EnsureCapacityToClone requires the array be empty when called");
477
0
478
0
  uint32_t attrCount = aOther.NonMappedAttrCount();
479
0
  if (!attrCount) {
480
0
    return NS_OK;
481
0
  }
482
0
483
0
  // No need to use a CheckedUint32 because we are cloning. We know that we
484
0
  // have already allocated an AttrArray of this size.
485
0
  mImpl.reset(static_cast<Impl*>(malloc(
486
0
    Impl::AllocationSizeForAttributes(attrCount))));
487
0
  NS_ENSURE_TRUE(mImpl, NS_ERROR_OUT_OF_MEMORY);
488
0
489
0
  mImpl->mMappedAttrs = nullptr;
490
0
  mImpl->mCapacity = attrCount;
491
0
  mImpl->mAttrCount = 0;
492
0
493
0
  return NS_OK;
494
0
}
495
496
bool
497
AttrArray::GrowBy(uint32_t aGrowSize)
498
0
{
499
0
  const uint32_t kLinearThreshold = 16;
500
0
  const uint32_t kLinearGrowSize = 4;
501
0
502
0
  CheckedUint32 capacity = mImpl ? mImpl->mCapacity : 0;
503
0
  CheckedUint32 minCapacity = capacity;
504
0
  minCapacity += aGrowSize;
505
0
  if (!minCapacity.isValid()) {
506
0
    return false;
507
0
  }
508
0
509
0
  if (capacity.value() <= kLinearThreshold) {
510
0
    do {
511
0
      capacity += kLinearGrowSize;
512
0
      if (!capacity.isValid()) {
513
0
        return false;
514
0
      }
515
0
    } while (capacity.value() < minCapacity.value());
516
0
  } else {
517
0
    uint32_t shift = mozilla::CeilingLog2(minCapacity.value());
518
0
    if (shift >= 32) {
519
0
      return false;
520
0
    }
521
0
    capacity = 1u << shift;
522
0
  }
523
0
524
0
  CheckedUint32 sizeInBytes = capacity.value();
525
0
  sizeInBytes *= sizeof(InternalAttr);
526
0
  if (!sizeInBytes.isValid()) {
527
0
    return false;
528
0
  }
529
0
530
0
  sizeInBytes += sizeof(Impl);
531
0
  if (!sizeInBytes.isValid()) {
532
0
    return false;
533
0
  }
534
0
535
0
  MOZ_ASSERT(sizeInBytes.value() ==
536
0
               Impl::AllocationSizeForAttributes(capacity.value()));
537
0
538
0
  const bool needToInitialize = !mImpl;
539
0
  Impl* newImpl = static_cast<Impl*>(realloc(mImpl.release(), sizeInBytes.value()));
540
0
  NS_ENSURE_TRUE(newImpl, false);
541
0
542
0
  mImpl.reset(newImpl);
543
0
544
0
  // Set initial counts if we didn't have a buffer before
545
0
  if (needToInitialize) {
546
0
    mImpl->mMappedAttrs = nullptr;
547
0
    mImpl->mAttrCount = 0;
548
0
  }
549
0
550
0
  mImpl->mCapacity = capacity.value();
551
0
  return true;
552
0
}
553
554
size_t
555
AttrArray::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
556
0
{
557
0
  size_t n = 0;
558
0
  if (mImpl) {
559
0
    // Don't add the size taken by *mMappedAttrs because it's shared.
560
0
561
0
    n += aMallocSizeOf(mImpl.get());
562
0
563
0
    for (const InternalAttr& attr : NonMappedAttrs()) {
564
0
      n += attr.mValue.SizeOfExcludingThis(aMallocSizeOf);
565
0
    }
566
0
  }
567
0
568
0
  return n;
569
0
}