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 "DOMSVGPointList.h"
9 #include "nsContentUtils.h"
10 #include "DOMSVGPoint.h"
12 #include "SVGAnimatedPointList.h"
13 #include "SVGAttrTearoffTable.h"
14 #include "mozilla/dom/SVGElement.h"
15 #include "mozilla/dom/SVGPointListBinding.h"
18 // See the comment in this file's header.
20 // local helper functions
23 void UpdateListIndicesFromIndex(
24 FallibleTArray
<mozilla::dom::DOMSVGPoint
*>& aItemsArray
,
25 uint32_t aStartingIndex
) {
26 uint32_t length
= aItemsArray
.Length();
28 for (uint32_t i
= aStartingIndex
; i
< length
; ++i
) {
30 aItemsArray
[i
]->UpdateListIndex(i
);
37 namespace mozilla::dom
{
39 static inline SVGAttrTearoffTable
<void, DOMSVGPointList
>&
40 SVGPointListTearoffTable() {
41 static SVGAttrTearoffTable
<void, DOMSVGPointList
> sSVGPointListTearoffTable
;
42 return sSVGPointListTearoffTable
;
45 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPointList
)
47 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPointList
)
48 // No unlinking of mElement, we'd need to null out the value pointer (the
49 // object it points to is held by the element) and null-check it everywhere.
50 tmp
->RemoveFromTearoffTable();
51 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPointList
)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement
)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
56 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPointList
)
57 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
58 NS_IMPL_CYCLE_COLLECTION_TRACE_END
60 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPointList
)
61 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPointList
)
63 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPointList
)
64 NS_INTERFACE_MAP_ENTRY_CONCRETE(DOMSVGPointList
)
65 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
66 NS_INTERFACE_MAP_ENTRY(nsISupports
)
70 already_AddRefed
<DOMSVGPointList
> DOMSVGPointList::GetDOMWrapper(
71 void* aList
, SVGElement
* aElement
, bool aIsAnimValList
) {
72 RefPtr
<DOMSVGPointList
> wrapper
=
73 SVGPointListTearoffTable().GetTearoff(aList
);
75 wrapper
= new DOMSVGPointList(aElement
, aIsAnimValList
);
76 SVGPointListTearoffTable().AddTearoff(aList
, wrapper
);
78 return wrapper
.forget();
82 DOMSVGPointList
* DOMSVGPointList::GetDOMWrapperIfExists(void* aList
) {
83 return SVGPointListTearoffTable().GetTearoff(aList
);
86 void DOMSVGPointList::RemoveFromTearoffTable() {
87 // Called from Unlink and the destructor.
89 // There are now no longer any references to us held by script or list items.
90 // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()!
91 void* key
= mIsAnimValList
? InternalAList().GetAnimValKey()
92 : InternalAList().GetBaseValKey();
93 SVGPointListTearoffTable().RemoveTearoff(key
);
96 DOMSVGPointList::~DOMSVGPointList() { RemoveFromTearoffTable(); }
98 JSObject
* DOMSVGPointList::WrapObject(JSContext
* cx
,
99 JS::Handle
<JSObject
*> aGivenProto
) {
100 return mozilla::dom::SVGPointList_Binding::Wrap(cx
, this, aGivenProto
);
103 void DOMSVGPointList::InternalListWillChangeTo(const SVGPointList
& aNewValue
) {
104 // When the number of items in our internal counterpart changes, we MUST stay
105 // in sync. Everything in the scary comment in
106 // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here too!
108 uint32_t oldLength
= mItems
.Length();
110 uint32_t newLength
= aNewValue
.Length();
111 if (newLength
> DOMSVGPoint::MaxListIndex()) {
112 // It's safe to get out of sync with our internal list as long as we have
113 // FEWER items than it does.
114 newLength
= DOMSVGPoint::MaxListIndex();
117 RefPtr
<DOMSVGPointList
> kungFuDeathGrip
;
118 if (newLength
< oldLength
) {
119 // RemovingFromList() might clear last reference to |this|.
120 // Retain a temporary reference to keep from dying before returning.
121 kungFuDeathGrip
= this;
124 // If our length will decrease, notify the items that will be removed:
125 for (uint32_t i
= newLength
; i
< oldLength
; ++i
) {
127 mItems
[i
]->RemovingFromList();
131 if (!mItems
.SetLength(newLength
, fallible
)) {
132 // We silently ignore SetLength OOM failure since being out of sync is safe
133 // so long as we have *fewer* items than our internal list.
138 // If our length has increased, null out the new pointers:
139 for (uint32_t i
= oldLength
; i
< newLength
; ++i
) {
144 bool DOMSVGPointList::AttrIsAnimating() const {
145 return InternalAList().IsAnimating();
148 bool DOMSVGPointList::AnimListMirrorsBaseList() const {
149 return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
153 SVGPointList
& DOMSVGPointList::InternalList() const {
154 SVGAnimatedPointList
* alist
= mElement
->GetAnimatedPointList();
155 return mIsAnimValList
&& alist
->IsAnimating() ? *alist
->mAnimVal
159 SVGAnimatedPointList
& DOMSVGPointList::InternalAList() const {
160 MOZ_ASSERT(mElement
->GetAnimatedPointList(), "Internal error");
161 return *mElement
->GetAnimatedPointList();
164 // ----------------------------------------------------------------------------
165 // nsIDOMSVGPointList implementation:
167 void DOMSVGPointList::Clear(ErrorResult
& aError
) {
168 if (IsAnimValList()) {
169 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
173 if (LengthNoFlush() > 0) {
174 AutoChangePointListNotifier
notifier(this);
175 // DOM list items that are to be removed must be removed before we change
176 // the internal list, otherwise they wouldn't be able to copy their
177 // internal counterparts' values!
179 InternalListWillChangeTo(SVGPointList()); // clears mItems
181 if (!AttrIsAnimating()) {
182 // The anim val list is in sync with the base val list
183 DOMSVGPointList
* animList
=
184 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
186 animList
->InternalListWillChangeTo(
187 SVGPointList()); // clears its mItems
191 InternalList().Clear();
195 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::Initialize(DOMSVGPoint
& aNewItem
,
196 ErrorResult
& aError
) {
197 if (IsAnimValList()) {
198 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
202 // If aNewItem is already in a list we should insert a clone of aNewItem,
203 // and for consistency, this should happen even if *this* is the list that
204 // aNewItem is currently in. Note that in the case of aNewItem being in this
205 // list, the Clear() call before the InsertItemBefore() call would remove it
206 // from this list, and so the InsertItemBefore() call would not insert a
207 // clone of aNewItem, it would actually insert aNewItem. To prevent that
208 // from happening we have to do the clone here, if necessary.
210 RefPtr
<DOMSVGPoint
> domItem
= &aNewItem
;
211 if (domItem
->HasOwner()) {
212 domItem
= domItem
->Copy(); // must do this before changing anything!
217 MOZ_ASSERT(!rv
.Failed());
218 return InsertItemBefore(*domItem
, 0, aError
);
221 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::GetItem(uint32_t index
,
222 ErrorResult
& error
) {
224 RefPtr
<DOMSVGPoint
> item
= IndexedGetter(index
, found
, error
);
226 error
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
228 return item
.forget();
231 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::IndexedGetter(
232 uint32_t aIndex
, bool& aFound
, ErrorResult
& aError
) {
233 if (IsAnimValList()) {
234 Element()->FlushAnimations();
236 aFound
= aIndex
< LengthNoFlush();
238 return GetItemAt(aIndex
);
243 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::InsertItemBefore(
244 DOMSVGPoint
& aNewItem
, uint32_t aIndex
, ErrorResult
& aError
) {
245 if (IsAnimValList()) {
246 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
250 aIndex
= std::min(aIndex
, LengthNoFlush());
251 if (aIndex
>= DOMSVGPoint::MaxListIndex()) {
252 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
256 RefPtr
<DOMSVGPoint
> domItem
= &aNewItem
;
257 if (domItem
->HasOwner()) {
258 domItem
= domItem
->Copy(); // must do this before changing anything!
261 // Ensure we have enough memory so we can avoid complex error handling below:
262 if (!mItems
.SetCapacity(mItems
.Length() + 1, fallible
) ||
263 !InternalList().SetCapacity(InternalList().Length() + 1)) {
264 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
267 if (AnimListMirrorsBaseList()) {
268 DOMSVGPointList
* animVal
=
269 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
270 MOZ_ASSERT(animVal
, "animVal must be a valid pointer");
271 if (!animVal
->mItems
.SetCapacity(animVal
->mItems
.Length() + 1, fallible
)) {
272 aError
.Throw(NS_ERROR_OUT_OF_MEMORY
);
277 AutoChangePointListNotifier
notifier(this);
278 // Now that we know we're inserting, keep animVal list in sync as necessary.
279 MaybeInsertNullInAnimValListAt(aIndex
);
281 InternalList().InsertItem(aIndex
, domItem
->ToSVGPoint());
282 MOZ_ALWAYS_TRUE(mItems
.InsertElementAt(aIndex
, domItem
, fallible
));
284 // This MUST come after the insertion into InternalList(), or else under the
285 // insertion into InternalList() the values read from domItem would be bad
286 // data from InternalList() itself!:
287 domItem
->InsertingIntoList(this, aIndex
, IsAnimValList());
289 UpdateListIndicesFromIndex(mItems
, aIndex
+ 1);
291 return domItem
.forget();
294 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::ReplaceItem(
295 DOMSVGPoint
& aNewItem
, uint32_t aIndex
, ErrorResult
& aError
) {
296 if (IsAnimValList()) {
297 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
301 if (aIndex
>= LengthNoFlush()) {
302 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
306 RefPtr
<DOMSVGPoint
> domItem
= &aNewItem
;
307 if (domItem
->HasOwner()) {
308 domItem
= domItem
->Copy(); // must do this before changing anything!
311 AutoChangePointListNotifier
notifier(this);
312 if (mItems
[aIndex
]) {
313 // Notify any existing DOM item of removal *before* modifying the lists so
314 // that the DOM item can copy the *old* value at its index:
315 mItems
[aIndex
]->RemovingFromList();
318 InternalList()[aIndex
] = domItem
->ToSVGPoint();
319 mItems
[aIndex
] = domItem
;
321 // This MUST come after the ToSVGPoint() call, otherwise that call
322 // would end up reading bad data from InternalList()!
323 domItem
->InsertingIntoList(this, aIndex
, IsAnimValList());
325 return domItem
.forget();
328 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::RemoveItem(uint32_t aIndex
,
329 ErrorResult
& aError
) {
330 if (IsAnimValList()) {
331 aError
.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR
);
335 if (aIndex
>= LengthNoFlush()) {
336 aError
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
340 AutoChangePointListNotifier
notifier(this);
341 // Now that we know we're removing, keep animVal list in sync as necessary.
342 // Do this *before* touching InternalList() so the removed item can get its
344 MaybeRemoveItemFromAnimValListAt(aIndex
);
346 // We have to return the removed item, so get it, creating it if necessary:
347 RefPtr
<DOMSVGPoint
> result
= GetItemAt(aIndex
);
349 // Notify the DOM item of removal *before* modifying the lists so that the
350 // DOM item can copy its *old* value:
351 mItems
[aIndex
]->RemovingFromList();
353 InternalList().RemoveItem(aIndex
);
354 mItems
.RemoveElementAt(aIndex
);
356 UpdateListIndicesFromIndex(mItems
, aIndex
);
358 return result
.forget();
361 already_AddRefed
<DOMSVGPoint
> DOMSVGPointList::GetItemAt(uint32_t aIndex
) {
362 MOZ_ASSERT(aIndex
< mItems
.Length());
364 if (!mItems
[aIndex
]) {
365 mItems
[aIndex
] = new DOMSVGPoint(this, aIndex
, IsAnimValList());
367 RefPtr
<DOMSVGPoint
> result
= mItems
[aIndex
];
368 return result
.forget();
371 void DOMSVGPointList::MaybeInsertNullInAnimValListAt(uint32_t aIndex
) {
372 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
374 if (!AnimListMirrorsBaseList()) {
378 // The anim val list is in sync with the base val list
379 DOMSVGPointList
* animVal
=
380 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
382 MOZ_ASSERT(animVal
, "AnimListMirrorsBaseList() promised a non-null animVal");
383 MOZ_ASSERT(animVal
->mItems
.Length() == mItems
.Length(),
384 "animVal list not in sync!");
385 MOZ_ALWAYS_TRUE(animVal
->mItems
.InsertElementAt(aIndex
, nullptr, fallible
));
387 UpdateListIndicesFromIndex(animVal
->mItems
, aIndex
+ 1);
390 void DOMSVGPointList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex
) {
391 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
393 if (!AnimListMirrorsBaseList()) {
397 // This needs to be a strong reference; otherwise, the RemovingFromList call
398 // below might drop the last reference to animVal before we're done with it.
399 RefPtr
<DOMSVGPointList
> animVal
=
400 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
402 MOZ_ASSERT(animVal
, "AnimListMirrorsBaseList() promised a non-null animVal");
403 MOZ_ASSERT(animVal
->mItems
.Length() == mItems
.Length(),
404 "animVal list not in sync!");
406 if (animVal
->mItems
[aIndex
]) {
407 animVal
->mItems
[aIndex
]->RemovingFromList();
409 animVal
->mItems
.RemoveElementAt(aIndex
);
411 UpdateListIndicesFromIndex(animVal
->mItems
, aIndex
);
414 } // namespace mozilla::dom