Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/svg/DOMSVGTransformList.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
#include "DOMSVGTransformList.h"
8
#include "mozilla/dom/SVGTransform.h"
9
#include "mozilla/dom/SVGMatrix.h"
10
#include "nsSVGAnimatedTransformList.h"
11
#include "nsSVGElement.h"
12
#include "mozilla/dom/SVGTransformListBinding.h"
13
#include "nsError.h"
14
#include <algorithm>
15
16
// local helper functions
17
namespace {
18
19
void UpdateListIndicesFromIndex(
20
  FallibleTArray<mozilla::dom::SVGTransform*>& aItemsArray,
21
  uint32_t aStartingIndex)
22
0
{
23
0
  uint32_t length = aItemsArray.Length();
24
0
25
0
  for (uint32_t i = aStartingIndex; i < length; ++i) {
26
0
    if (aItemsArray[i]) {
27
0
      aItemsArray[i]->UpdateListIndex(i);
28
0
    }
29
0
  }
30
0
}
31
32
} // namespace
33
34
namespace mozilla {
35
36
using namespace dom;
37
38
// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to
39
// clear our SVGAnimatedTransformList's weak ref to us to be safe. (The other
40
// option would be to not unlink and rely on the breaking of the other edges in
41
// the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.)
42
NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGTransformList)
43
44
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGTransformList)
45
0
  if (tmp->mAList) {
46
0
    if (tmp->IsAnimValList()) {
47
0
      tmp->mAList->mAnimVal = nullptr;
48
0
    } else {
49
0
      tmp->mAList->mBaseVal = nullptr;
50
0
    }
51
0
    NS_IMPL_CYCLE_COLLECTION_UNLINK(mAList)
52
0
  }
53
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
54
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
55
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGTransformList)
56
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAList)
57
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
58
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGTransformList)
59
0
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
60
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
61
62
NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGTransformList)
63
NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGTransformList)
64
65
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGTransformList)
66
0
  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
67
0
  NS_INTERFACE_MAP_ENTRY(nsISupports)
68
0
NS_INTERFACE_MAP_END
69
70
//----------------------------------------------------------------------
71
// DOMSVGTransformList methods:
72
73
JSObject*
74
DOMSVGTransformList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
75
0
{
76
0
  return mozilla::dom::SVGTransformList_Binding::Wrap(cx, this, aGivenProto);
77
0
}
78
79
//----------------------------------------------------------------------
80
// Helper class: AutoChangeTransformListNotifier
81
// Stack-based helper class to pair calls to WillChangeTransformList and
82
// DidChangeTransformList.
83
class MOZ_RAII AutoChangeTransformListNotifier
84
{
85
public:
86
  explicit AutoChangeTransformListNotifier(DOMSVGTransformList* aTransformList MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
87
    : mTransformList(aTransformList)
88
0
  {
89
0
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
90
0
    MOZ_ASSERT(mTransformList, "Expecting non-null transformList");
91
0
    mEmptyOrOldValue =
92
0
      mTransformList->Element()->WillChangeTransformList();
93
0
  }
94
95
  ~AutoChangeTransformListNotifier()
96
0
  {
97
0
    mTransformList->Element()->DidChangeTransformList(mEmptyOrOldValue);
98
0
    if (mTransformList->IsAnimating()) {
99
0
      mTransformList->Element()->AnimationNeedsResample();
100
0
    }
101
0
  }
102
103
private:
104
  DOMSVGTransformList* const mTransformList;
105
  nsAttrValue          mEmptyOrOldValue;
106
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
107
};
108
109
void
110
DOMSVGTransformList::InternalListLengthWillChange(uint32_t aNewLength)
111
0
{
112
0
  uint32_t oldLength = mItems.Length();
113
0
114
0
  if (aNewLength > SVGTransform::MaxListIndex()) {
115
0
    // It's safe to get out of sync with our internal list as long as we have
116
0
    // FEWER items than it does.
117
0
    aNewLength = SVGTransform::MaxListIndex();
118
0
  }
119
0
120
0
  RefPtr<DOMSVGTransformList> kungFuDeathGrip;
121
0
  if (aNewLength < oldLength) {
122
0
    // RemovingFromList() might clear last reference to |this|.
123
0
    // Retain a temporary reference to keep from dying before returning.
124
0
    kungFuDeathGrip = this;
125
0
  }
126
0
127
0
  // If our length will decrease, notify the items that will be removed:
128
0
  for (uint32_t i = aNewLength; i < oldLength; ++i) {
129
0
    if (mItems[i]) {
130
0
      mItems[i]->RemovingFromList();
131
0
    }
132
0
  }
133
0
134
0
  if (!mItems.SetLength(aNewLength, fallible)) {
135
0
    // We silently ignore SetLength OOM failure since being out of sync is safe
136
0
    // so long as we have *fewer* items than our internal list.
137
0
    mItems.Clear();
138
0
    return;
139
0
  }
140
0
141
0
  // If our length has increased, null out the new pointers:
142
0
  for (uint32_t i = oldLength; i < aNewLength; ++i) {
143
0
    mItems[i] = nullptr;
144
0
  }
145
0
}
146
147
SVGTransformList&
148
DOMSVGTransformList::InternalList() const
149
0
{
150
0
  nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList();
151
0
  return IsAnimValList() && alist->mAnimVal ?
152
0
    *alist->mAnimVal :
153
0
    alist->mBaseVal;
154
0
}
155
156
//----------------------------------------------------------------------
157
void
158
DOMSVGTransformList::Clear(ErrorResult& error)
159
0
{
160
0
  if (IsAnimValList()) {
161
0
    error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
162
0
    return;
163
0
  }
164
0
165
0
  if (LengthNoFlush() > 0) {
166
0
    AutoChangeTransformListNotifier notifier(this);
167
0
    // Notify any existing DOM items of removal *before* truncating the lists
168
0
    // so that they can find their SVGTransform internal counterparts and copy
169
0
    // their values. This also notifies the animVal list:
170
0
    mAList->InternalBaseValListWillChangeLengthTo(0);
171
0
172
0
    mItems.Clear();
173
0
    InternalList().Clear();
174
0
  }
175
0
}
176
177
already_AddRefed<SVGTransform>
178
DOMSVGTransformList::Initialize(SVGTransform& newItem, ErrorResult& error)
179
0
{
180
0
  if (IsAnimValList()) {
181
0
    error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
182
0
    return nullptr;
183
0
  }
184
0
185
0
  // If newItem is already in a list we should insert a clone of newItem, and
186
0
  // for consistency, this should happen even if *this* is the list that
187
0
  // newItem is currently in. Note that in the case of newItem being in this
188
0
  // list, the Clear() call before the InsertItemBefore() call would remove it
189
0
  // from this list, and so the InsertItemBefore() call would not insert a
190
0
  // clone of newItem, it would actually insert newItem. To prevent that from
191
0
  // happening we have to do the clone here, if necessary.
192
0
193
0
  RefPtr<SVGTransform> domItem = &newItem;
194
0
  if (domItem->HasOwner()) {
195
0
    domItem = newItem.Clone();
196
0
  }
197
0
198
0
  Clear(error);
199
0
  MOZ_ASSERT(!error.Failed(), "How could this fail?");
200
0
  return InsertItemBefore(*domItem, 0, error);
201
0
}
202
203
already_AddRefed<SVGTransform>
204
DOMSVGTransformList::GetItem(uint32_t index, ErrorResult& error)
205
0
{
206
0
  bool found;
207
0
  RefPtr<SVGTransform> item = IndexedGetter(index, found, error);
208
0
  if (!found) {
209
0
    error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
210
0
  }
211
0
  return item.forget();
212
0
}
213
214
already_AddRefed<SVGTransform>
215
DOMSVGTransformList::IndexedGetter(uint32_t index, bool& found,
216
                                   ErrorResult& error)
217
0
{
218
0
  if (IsAnimValList()) {
219
0
    Element()->FlushAnimations();
220
0
  }
221
0
  found = index < LengthNoFlush();
222
0
  if (found) {
223
0
    return GetItemAt(index);
224
0
  }
225
0
  return nullptr;
226
0
}
227
228
already_AddRefed<SVGTransform>
229
DOMSVGTransformList::InsertItemBefore(SVGTransform& newItem,
230
                                      uint32_t index, ErrorResult& error)
231
0
{
232
0
  if (IsAnimValList()) {
233
0
    error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
234
0
    return nullptr;
235
0
  }
236
0
237
0
  index = std::min(index, LengthNoFlush());
238
0
  if (index >= SVGTransform::MaxListIndex()) {
239
0
    error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
240
0
    return nullptr;
241
0
  }
242
0
243
0
  RefPtr<SVGTransform> domItem = &newItem;
244
0
  if (newItem.HasOwner()) {
245
0
    domItem = newItem.Clone(); // must do this before changing anything!
246
0
  }
247
0
248
0
  // Ensure we have enough memory so we can avoid complex error handling below:
249
0
  if (!mItems.SetCapacity(mItems.Length() + 1, fallible) ||
250
0
      !InternalList().SetCapacity(InternalList().Length() + 1)) {
251
0
    error.Throw(NS_ERROR_OUT_OF_MEMORY);
252
0
    return nullptr;
253
0
  }
254
0
  if (AnimListMirrorsBaseList()) {
255
0
    if (!mAList->mAnimVal->mItems.SetCapacity(
256
0
          mAList->mAnimVal->mItems.Length() + 1, fallible)) {
257
0
      error.Throw(NS_ERROR_OUT_OF_MEMORY);
258
0
      return nullptr;
259
0
    }
260
0
  }
261
0
262
0
  AutoChangeTransformListNotifier notifier(this);
263
0
  // Now that we know we're inserting, keep animVal list in sync as necessary.
264
0
  MaybeInsertNullInAnimValListAt(index);
265
0
266
0
  InternalList().InsertItem(index, domItem->ToSVGTransform());
267
0
  MOZ_ALWAYS_TRUE(mItems.InsertElementAt(index, domItem.get(), fallible));
268
0
269
0
  // This MUST come after the insertion into InternalList(), or else under the
270
0
  // insertion into InternalList() the values read from domItem would be bad
271
0
  // data from InternalList() itself!:
272
0
  domItem->InsertingIntoList(this, index, IsAnimValList());
273
0
274
0
  UpdateListIndicesFromIndex(mItems, index + 1);
275
0
276
0
  return domItem.forget();
277
0
}
278
279
already_AddRefed<SVGTransform>
280
DOMSVGTransformList::ReplaceItem(SVGTransform& newItem,
281
                                 uint32_t index, ErrorResult& error)
282
0
{
283
0
  if (IsAnimValList()) {
284
0
    error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
285
0
    return nullptr;
286
0
  }
287
0
288
0
  if (index >= LengthNoFlush()) {
289
0
    error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
290
0
    return nullptr;
291
0
  }
292
0
293
0
  RefPtr<SVGTransform> domItem = &newItem;
294
0
  if (newItem.HasOwner()) {
295
0
    domItem = newItem.Clone(); // must do this before changing anything!
296
0
  }
297
0
298
0
  AutoChangeTransformListNotifier notifier(this);
299
0
  if (mItems[index]) {
300
0
    // Notify any existing DOM item of removal *before* modifying the lists so
301
0
    // that the DOM item can copy the *old* value at its index:
302
0
    mItems[index]->RemovingFromList();
303
0
  }
304
0
305
0
  InternalList()[index] = domItem->ToSVGTransform();
306
0
  mItems[index] = domItem;
307
0
308
0
  // This MUST come after the ToSVGPoint() call, otherwise that call
309
0
  // would end up reading bad data from InternalList()!
310
0
  domItem->InsertingIntoList(this, index, IsAnimValList());
311
0
312
0
  return domItem.forget();
313
0
}
314
315
already_AddRefed<SVGTransform>
316
DOMSVGTransformList::RemoveItem(uint32_t index, ErrorResult& error)
317
0
{
318
0
  if (IsAnimValList()) {
319
0
    error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
320
0
    return nullptr;
321
0
  }
322
0
323
0
  if (index >= LengthNoFlush()) {
324
0
    error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
325
0
    return nullptr;
326
0
  }
327
0
328
0
  AutoChangeTransformListNotifier notifier(this);
329
0
  // Now that we know we're removing, keep animVal list in sync as necessary.
330
0
  // Do this *before* touching InternalList() so the removed item can get its
331
0
  // internal value.
332
0
  MaybeRemoveItemFromAnimValListAt(index);
333
0
334
0
  // We have to return the removed item, so get it, creating it if necessary:
335
0
  RefPtr<SVGTransform> result = GetItemAt(index);
336
0
337
0
  // Notify the DOM item of removal *before* modifying the lists so that the
338
0
  // DOM item can copy its *old* value:
339
0
  result->RemovingFromList();
340
0
341
0
  InternalList().RemoveItem(index);
342
0
  mItems.RemoveElementAt(index);
343
0
344
0
  UpdateListIndicesFromIndex(mItems, index);
345
0
346
0
  return result.forget();
347
0
}
348
349
already_AddRefed<SVGTransform>
350
DOMSVGTransformList::CreateSVGTransformFromMatrix(dom::SVGMatrix& matrix)
351
0
{
352
0
  RefPtr<SVGTransform> result = new SVGTransform(matrix.GetMatrix());
353
0
  return result.forget();
354
0
}
355
356
already_AddRefed<SVGTransform>
357
DOMSVGTransformList::Consolidate(ErrorResult& error)
358
0
{
359
0
  if (IsAnimValList()) {
360
0
    error.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
361
0
    return nullptr;
362
0
  }
363
0
364
0
  if (LengthNoFlush() == 0) {
365
0
    return nullptr;
366
0
  }
367
0
368
0
  // Note that SVG 1.1 says, "The consolidation operation creates new
369
0
  // SVGTransform object as the first and only item in the list" hence, even if
370
0
  // LengthNoFlush() == 1 we can't return that one item (after making it a
371
0
  // matrix type). We must orphan the existing item and then make a new one.
372
0
373
0
  // First calculate our matrix
374
0
  gfxMatrix mx = InternalList().GetConsolidationMatrix();
375
0
376
0
  // Then orphan the existing items
377
0
  Clear(error);
378
0
  MOZ_ASSERT(!error.Failed(), "How could this fail?");
379
0
380
0
  // And append the new transform
381
0
  RefPtr<SVGTransform> transform = new SVGTransform(mx);
382
0
  return InsertItemBefore(*transform, LengthNoFlush(), error);
383
0
}
384
385
//----------------------------------------------------------------------
386
// Implementation helpers:
387
388
already_AddRefed<SVGTransform>
389
DOMSVGTransformList::GetItemAt(uint32_t aIndex)
390
0
{
391
0
  MOZ_ASSERT(aIndex < mItems.Length());
392
0
393
0
  if (!mItems[aIndex]) {
394
0
    mItems[aIndex] = new SVGTransform(this, aIndex, IsAnimValList());
395
0
  }
396
0
  RefPtr<SVGTransform> result = mItems[aIndex];
397
0
  return result.forget();
398
0
}
399
400
void
401
DOMSVGTransformList::MaybeInsertNullInAnimValListAt(uint32_t aIndex)
402
0
{
403
0
  MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
404
0
405
0
  if (!AnimListMirrorsBaseList()) {
406
0
    return;
407
0
  }
408
0
409
0
  DOMSVGTransformList* animVal = mAList->mAnimVal;
410
0
411
0
  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
412
0
  MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
413
0
             "animVal list not in sync!");
414
0
  MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible));
415
0
416
0
  UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
417
0
}
418
419
void
420
DOMSVGTransformList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex)
421
0
{
422
0
  MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
423
0
424
0
  if (!AnimListMirrorsBaseList()) {
425
0
    return;
426
0
  }
427
0
428
0
  // This needs to be a strong reference; otherwise, the RemovingFromList call
429
0
  // below might drop the last reference to animVal before we're done with it.
430
0
  RefPtr<DOMSVGTransformList> animVal = mAList->mAnimVal;
431
0
432
0
  MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
433
0
  MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
434
0
             "animVal list not in sync!");
435
0
436
0
  if (animVal->mItems[aIndex]) {
437
0
    animVal->mItems[aIndex]->RemovingFromList();
438
0
  }
439
0
  animVal->mItems.RemoveElementAt(aIndex);
440
0
441
0
  UpdateListIndicesFromIndex(animVal->mItems, aIndex);
442
0
}
443
444
} // namespace mozilla