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/. */
7 #include "DOMSVGPathSegList.h"
9 #include "DOMSVGPathSeg.h"
11 #include "SVGAnimatedPathSegList.h"
12 #include "SVGAttrTearoffTable.h"
13 #include "SVGPathSegUtils.h"
14 #include "mozilla/dom/SVGElement.h"
15 #include "mozilla/dom/SVGPathSegListBinding.h"
16 #include "mozilla/RefPtr.h"
18 // See the comment in this file's header.
23 static inline SVGAttrTearoffTable
<void, DOMSVGPathSegList
>&
24 SVGPathSegListTearoffTable() {
25 static SVGAttrTearoffTable
<void, DOMSVGPathSegList
>
26 sSVGPathSegListTearoffTable
;
27 return sSVGPathSegListTearoffTable
;
30 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPathSegList
)
32 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPathSegList
)
33 // No unlinking of mElement, we'd need to null out the value pointer (the
34 // object it points to is held by the element) and null-check it everywhere.
35 tmp
->RemoveFromTearoffTable();
36 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
37 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPathSegList
)
39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement
)
40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
41 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPathSegList
)
42 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
43 NS_IMPL_CYCLE_COLLECTION_TRACE_END
45 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPathSegList
)
46 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPathSegList
)
48 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPathSegList
)
49 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
50 NS_INTERFACE_MAP_ENTRY(nsISupports
)
54 already_AddRefed
<DOMSVGPathSegList
> DOMSVGPathSegList::GetDOMWrapper(
55 void* aList
, SVGElement
* aElement
, bool aIsAnimValList
) {
56 RefPtr
<DOMSVGPathSegList
> wrapper
=
57 SVGPathSegListTearoffTable().GetTearoff(aList
);
59 wrapper
= new DOMSVGPathSegList(aElement
, aIsAnimValList
);
60 SVGPathSegListTearoffTable().AddTearoff(aList
, wrapper
);
62 return wrapper
.forget();
66 DOMSVGPathSegList
* DOMSVGPathSegList::GetDOMWrapperIfExists(void* aList
) {
67 return SVGPathSegListTearoffTable().GetTearoff(aList
);
70 void DOMSVGPathSegList::RemoveFromTearoffTable() {
71 // There are now no longer any references to us held by script or list items.
72 // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()!
73 void* key
= mIsAnimValList
? InternalAList().GetAnimValKey()
74 : InternalAList().GetBaseValKey();
75 SVGPathSegListTearoffTable().RemoveTearoff(key
);
78 DOMSVGPathSegList::~DOMSVGPathSegList() { RemoveFromTearoffTable(); }
80 JSObject
* DOMSVGPathSegList::WrapObject(JSContext
* cx
,
81 JS::Handle
<JSObject
*> aGivenProto
) {
82 return mozilla::dom::SVGPathSegList_Binding::Wrap(cx
, this, aGivenProto
);
85 void DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData
& aNewValue
) {
86 // When the number of items in our internal counterpart changes, we MUST stay
87 // in sync. Everything in the scary comment in
88 // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here just as
89 // much, but we have the additional issue that failing to stay in sync would
90 // mean that - assuming we aren't reading bad memory - we would likely end up
91 // decoding command types from argument floats when looking in our
92 // SVGPathData's data array! Either way, we'll likely then go down
93 // MOZ_ASSERT_UNREACHABLE code paths, or end up reading/setting more bad
96 // The only time that our other DOM list type implementations remove items is
97 // if those items become surplus items due to an attribute change or SMIL
98 // animation sample shortening the list. In general though, they try to keep
99 // their existing DOM items, even when things change. To be consistent, we'd
100 // really like to do the same thing. However, because different types of path
101 // segment correspond to different DOMSVGPathSeg subclasses, the type of
102 // items in our list are generally not the same, which makes this harder for
103 // us. We have to remove DOM segments if their type is not the same as the
104 // type of the new internal segment at their index.
106 // We also need to sync up mInternalDataIndex, but since we need to loop over
107 // all the items in the new list checking types anyway, that's almost
108 // insignificant in terms of overhead.
110 // Note that this method is called on every single SMIL animation resample
111 // and we have no way to short circuit the overhead since we don't have a
112 // way to tell if the call is due to a new animation, or a resample of an
113 // existing animation (when the number and type of items would be the same).
114 // (Note that a new animation could start overriding an existing animation at
115 // any time, so checking IsAnimating() wouldn't work.) Because we get called
116 // on every sample, it would not be acceptable alternative to throw away all
117 // our items and let them be recreated lazily, since that would break what
120 uint32_t length
= mItems
.Length();
123 uint32_t dataLength
= aNewValue
.mData
.Length();
124 uint32_t dataIndex
= 0; // index into aNewValue's raw data array
128 RefPtr
<DOMSVGPathSegList
> kungFuDeathGrip
;
130 // RemovingFromList() might clear last reference to |this|.
131 // Retain a temporary reference to keep from dying before returning.
133 // NOTE: For path-seg lists (unlike other list types), we have to do this
134 // *whenever our list is nonempty* (even if we're growing in length).
135 // That's because the path-seg-type of any segment could differ between old
136 // list vs. new list, which will make us destroy & recreate that segment,
137 // which could remove the last reference to us.
139 // (We explicitly *don't* want to create a kungFuDeathGrip in the length=0
140 // case, though, because we do hit this code inside our constructor before
141 // any other owning references have been added, and at that point, the
142 // deathgrip-removal would make us die before we exit our constructor.)
143 kungFuDeathGrip
= this;
146 while (index
< length
&& dataIndex
< dataLength
) {
147 newSegType
= SVGPathSegUtils::DecodeType(aNewValue
.mData
[dataIndex
]);
148 if (ItemAt(index
) && ItemAt(index
)->Type() != newSegType
) {
149 ItemAt(index
)->RemovingFromList();
150 ItemAt(index
) = nullptr;
152 // Only after the RemovingFromList() can we touch mInternalDataIndex!
153 mItems
[index
].mInternalDataIndex
= dataIndex
;
155 dataIndex
+= 1 + SVGPathSegUtils::ArgCountForType(newSegType
);
158 MOZ_ASSERT((index
== length
&& dataIndex
<= dataLength
) ||
159 (index
<= length
&& dataIndex
== dataLength
),
160 "very bad - list corruption?");
162 if (index
< length
) {
163 // aNewValue has fewer items than our previous internal counterpart
165 uint32_t newLength
= index
;
167 // Remove excess items from the list:
168 for (; index
< length
; ++index
) {
170 ItemAt(index
)->RemovingFromList();
171 ItemAt(index
) = nullptr;
175 // Only now may we truncate mItems
176 mItems
.TruncateLength(newLength
);
177 } else if (dataIndex
< dataLength
) {
178 // aNewValue has more items than our previous internal counterpart
181 while (dataIndex
< dataLength
) {
182 if (mItems
.Length() &&
183 mItems
.Length() - 1 > DOMSVGPathSeg::MaxListIndex()) {
184 // It's safe to get out of sync with our internal list as long as we
185 // have FEWER items than it does.
188 if (!mItems
.AppendElement(ItemProxy(nullptr, dataIndex
), fallible
)) {
192 MOZ_ASSERT(!rv
.Failed());
196 1 + SVGPathSegUtils::ArgCountForType(
197 SVGPathSegUtils::DecodeType(aNewValue
.mData
[dataIndex
]));
201 MOZ_ASSERT(dataIndex
== dataLength
, "Serious processing error");
202 MOZ_ASSERT(index
== length
, "Serious counting error");
205 bool DOMSVGPathSegList::AttrIsAnimating() const {
206 return InternalAList().IsAnimating();
209 bool DOMSVGPathSegList::AnimListMirrorsBaseList() const {
210 return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
214 SVGPathData
& DOMSVGPathSegList::InternalList() const {
215 SVGAnimatedPathSegList
* alist
= mElement
->GetAnimPathSegList();
216 return mIsAnimValList
&& alist
->IsAnimating() ? *alist
->mAnimVal
220 SVGAnimatedPathSegList
& DOMSVGPathSegList::InternalAList() const {
221 MOZ_ASSERT(mElement
->GetAnimPathSegList(), "Internal error");
222 return *mElement
->GetAnimPathSegList();
225 // ----------------------------------------------------------------------------
226 // nsIDOMSVGPathSegList implementation:
228 void DOMSVGPathSegList::Clear(ErrorResult
& aError
) {
229 if (IsAnimValList()) {
230 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
234 if (LengthNoFlush() > 0) {
235 AutoChangePathSegListNotifier
notifier(this);
236 // DOM list items that are to be removed must be removed before we change
237 // the internal list, otherwise they wouldn't be able to copy their
238 // internal counterparts' values!
240 InternalListWillChangeTo(SVGPathData()); // clears mItems
242 if (!AttrIsAnimating()) {
243 // The anim val list is in sync with the base val list
244 DOMSVGPathSegList
* animList
=
245 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
247 animList
->InternalListWillChangeTo(SVGPathData()); // clears its mItems
251 InternalList().Clear();
255 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::Initialize(
256 DOMSVGPathSeg
& aNewItem
, ErrorResult
& aError
) {
257 if (IsAnimValList()) {
258 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
262 // If aNewItem is already in a list we should insert a clone of aNewItem,
263 // and for consistency, this should happen even if *this* is the list that
264 // aNewItem is currently in. Note that in the case of aNewItem being in this
265 // list, the Clear() call before the InsertItemBefore() call would remove it
266 // from this list, and so the InsertItemBefore() call would not insert a
267 // clone of aNewItem, it would actually insert aNewItem. To prevent that
268 // from happening we have to do the clone here, if necessary.
270 RefPtr
<DOMSVGPathSeg
> domItem
= &aNewItem
;
271 if (aNewItem
.HasOwner()) {
272 domItem
= aNewItem
.Clone();
276 MOZ_ASSERT(!aError
.Failed(), "How could this fail?");
277 return InsertItemBefore(*domItem
, 0, aError
);
280 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::GetItem(uint32_t index
,
281 ErrorResult
& error
) {
283 RefPtr
<DOMSVGPathSeg
> item
= IndexedGetter(index
, found
, error
);
285 error
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
287 return item
.forget();
290 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::IndexedGetter(
291 uint32_t aIndex
, bool& aFound
, ErrorResult
& aError
) {
292 if (IsAnimValList()) {
293 Element()->FlushAnimations();
295 aFound
= aIndex
< LengthNoFlush();
297 return GetItemAt(aIndex
);
302 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::InsertItemBefore(
303 DOMSVGPathSeg
& aNewItem
, uint32_t aIndex
, ErrorResult
& aError
) {
304 if (IsAnimValList()) {
305 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
309 uint32_t internalIndex
;
310 if (aIndex
< LengthNoFlush()) {
311 internalIndex
= mItems
[aIndex
].mInternalDataIndex
;
313 aIndex
= LengthNoFlush();
314 internalIndex
= InternalList().mData
.Length();
316 if (aIndex
>= DOMSVGPathSeg::MaxListIndex()) {
317 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
321 RefPtr
<DOMSVGPathSeg
> domItem
= &aNewItem
;
322 if (domItem
->HasOwner()) {
323 domItem
= domItem
->Clone(); // must do this before changing anything!
326 uint32_t argCount
= SVGPathSegUtils::ArgCountForType(domItem
->Type());
328 // Ensure we have enough memory so we can avoid complex error handling below:
329 if (!mItems
.SetCapacity(mItems
.Length() + 1, fallible
) ||
330 !InternalList().mData
.SetCapacity(
331 InternalList().mData
.Length() + 1 + argCount
, fallible
)) {
332 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
335 if (AnimListMirrorsBaseList()) {
336 DOMSVGPathSegList
* animVal
=
337 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
338 MOZ_ASSERT(animVal
, "animVal should be a valid pointer");
339 if (!animVal
->mItems
.SetCapacity(animVal
->mItems
.Length() + 1, fallible
)) {
340 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
345 AutoChangePathSegListNotifier
notifier(this);
346 // Now that we know we're inserting, keep animVal list in sync as necessary.
347 MaybeInsertNullInAnimValListAt(aIndex
, internalIndex
, argCount
);
349 float segAsRaw
[1 + NS_SVG_PATH_SEG_MAX_ARGS
];
350 domItem
->ToSVGPathSegEncodedData(segAsRaw
);
352 MOZ_ALWAYS_TRUE(InternalList().mData
.InsertElementsAt(
353 internalIndex
, segAsRaw
, 1 + argCount
, fallible
));
354 MOZ_ALWAYS_TRUE(mItems
.InsertElementAt(
355 aIndex
, ItemProxy(domItem
.get(), internalIndex
), fallible
));
357 // This MUST come after the insertion into InternalList(), or else under the
358 // insertion into InternalList() the values read from domItem would be bad
359 // data from InternalList() itself!:
360 domItem
->InsertingIntoList(this, aIndex
, IsAnimValList());
362 UpdateListIndicesFromIndex(aIndex
+ 1, argCount
+ 1);
364 return domItem
.forget();
367 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::ReplaceItem(
368 DOMSVGPathSeg
& aNewItem
, uint32_t aIndex
, ErrorResult
& aError
) {
369 if (IsAnimValList()) {
370 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
374 if (aIndex
>= LengthNoFlush()) {
375 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
379 RefPtr
<DOMSVGPathSeg
> domItem
= &aNewItem
;
380 if (domItem
->HasOwner()) {
381 domItem
= domItem
->Clone(); // must do this before changing anything!
384 AutoChangePathSegListNotifier
notifier(this);
385 if (ItemAt(aIndex
)) {
386 // Notify any existing DOM item of removal *before* modifying the lists so
387 // that the DOM item can copy the *old* value at its index:
388 ItemAt(aIndex
)->RemovingFromList();
391 uint32_t internalIndex
= mItems
[aIndex
].mInternalDataIndex
;
392 // We use InternalList() to get oldArgCount since we may not have a DOM
393 // wrapper at the index being replaced.
395 SVGPathSegUtils::DecodeType(InternalList().mData
[internalIndex
]);
397 // NOTE: ArgCountForType returns a (small) unsigned value, but we're
398 // intentionally putting it in a signed variable, because we're going to
399 // subtract these values and might produce something negative.
400 int32_t oldArgCount
= SVGPathSegUtils::ArgCountForType(oldType
);
401 int32_t newArgCount
= SVGPathSegUtils::ArgCountForType(domItem
->Type());
403 float segAsRaw
[1 + NS_SVG_PATH_SEG_MAX_ARGS
];
404 domItem
->ToSVGPathSegEncodedData(segAsRaw
);
406 if (!InternalList().mData
.ReplaceElementsAt(internalIndex
, 1 + oldArgCount
,
407 segAsRaw
, 1 + newArgCount
,
409 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
412 ItemAt(aIndex
) = domItem
;
414 // This MUST come after the ToSVGPathSegEncodedData call, otherwise that call
415 // would end up reading bad data from InternalList()!
416 domItem
->InsertingIntoList(this, aIndex
, IsAnimValList());
418 int32_t delta
= newArgCount
- oldArgCount
;
420 for (uint32_t i
= aIndex
+ 1; i
< LengthNoFlush(); ++i
) {
421 mItems
[i
].mInternalDataIndex
+= delta
;
425 return domItem
.forget();
428 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::RemoveItem(
429 uint32_t aIndex
, ErrorResult
& aError
) {
430 if (IsAnimValList()) {
431 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
435 if (aIndex
>= LengthNoFlush()) {
436 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
439 // We have to return the removed item, so get it, creating it if necessary:
440 RefPtr
<DOMSVGPathSeg
> result
= GetItemAt(aIndex
);
442 AutoChangePathSegListNotifier
notifier(this);
443 // Notify the DOM item of removal *before* modifying the lists so that the
444 // DOM item can copy its *old* value:
445 ItemAt(aIndex
)->RemovingFromList();
447 uint32_t internalIndex
= mItems
[aIndex
].mInternalDataIndex
;
449 SVGPathSegUtils::DecodeType(InternalList().mData
[internalIndex
]);
450 // NOTE: ArgCountForType returns a (small) unsigned value, but we're
451 // intentionally putting it in a signed value, because we're going to
452 // negate it, and you can't negate an unsigned value.
453 int32_t argCount
= SVGPathSegUtils::ArgCountForType(segType
);
455 // Now that we know we're removing, keep animVal list in sync as necessary.
456 // Do this *before* touching InternalList() so the removed item can get its
458 MaybeRemoveItemFromAnimValListAt(aIndex
, argCount
);
460 InternalList().mData
.RemoveElementsAt(internalIndex
, 1 + argCount
);
461 mItems
.RemoveElementAt(aIndex
);
463 UpdateListIndicesFromIndex(aIndex
, -(argCount
+ 1));
465 return result
.forget();
468 already_AddRefed
<DOMSVGPathSeg
> DOMSVGPathSegList::GetItemAt(uint32_t aIndex
) {
469 MOZ_ASSERT(aIndex
< mItems
.Length());
471 if (!ItemAt(aIndex
)) {
472 ItemAt(aIndex
) = DOMSVGPathSeg::CreateFor(this, aIndex
, IsAnimValList());
474 RefPtr
<DOMSVGPathSeg
> result
= ItemAt(aIndex
);
475 return result
.forget();
478 void DOMSVGPathSegList::MaybeInsertNullInAnimValListAt(
479 uint32_t aIndex
, uint32_t aInternalIndex
, uint32_t aArgCountForItem
) {
480 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
482 if (!AnimListMirrorsBaseList()) {
486 // The anim val list is in sync with the base val list
487 DOMSVGPathSegList
* animVal
=
488 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
490 MOZ_ASSERT(animVal
, "AnimListMirrorsBaseList() promised a non-null animVal");
491 MOZ_ASSERT(animVal
->mItems
.Length() == mItems
.Length(),
492 "animVal list not in sync!");
493 MOZ_ALWAYS_TRUE(animVal
->mItems
.InsertElementAt(
494 aIndex
, ItemProxy(nullptr, aInternalIndex
), fallible
));
496 animVal
->UpdateListIndicesFromIndex(aIndex
+ 1, 1 + aArgCountForItem
);
499 void DOMSVGPathSegList::MaybeRemoveItemFromAnimValListAt(
500 uint32_t aIndex
, int32_t aArgCountForItem
) {
501 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
503 if (!AnimListMirrorsBaseList()) {
507 // This needs to be a strong reference; otherwise, the RemovingFromList call
508 // below might drop the last reference to animVal before we're done with it.
509 RefPtr
<DOMSVGPathSegList
> animVal
=
510 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
512 MOZ_ASSERT(animVal
, "AnimListMirrorsBaseList() promised a non-null animVal");
513 MOZ_ASSERT(animVal
->mItems
.Length() == mItems
.Length(),
514 "animVal list not in sync!");
516 if (animVal
->ItemAt(aIndex
)) {
517 animVal
->ItemAt(aIndex
)->RemovingFromList();
519 animVal
->mItems
.RemoveElementAt(aIndex
);
521 animVal
->UpdateListIndicesFromIndex(aIndex
, -(1 + aArgCountForItem
));
524 void DOMSVGPathSegList::UpdateListIndicesFromIndex(
525 uint32_t aStartingIndex
, int32_t aInternalDataIndexDelta
) {
526 uint32_t length
= mItems
.Length();
528 for (uint32_t i
= aStartingIndex
; i
< length
; ++i
) {
529 mItems
[i
].mInternalDataIndex
+= aInternalDataIndexDelta
;
531 ItemAt(i
)->UpdateListIndex(i
);
537 } // namespace mozilla