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 "DOMSVGPointList.h"
8
9 #include "nsCOMPtr.h"
10 #include "nsContentUtils.h"
11 #include "DOMSVGPoint.h"
12 #include "nsError.h"
13 #include "SVGAnimatedPointList.h"
14 #include "SVGAttrTearoffTable.h"
15 #include "mozAutoDocUpdate.h"
16 #include "mozilla/dom/SVGElement.h"
17 #include "mozilla/dom/SVGPointListBinding.h"
18 #include <algorithm>
19
20 // See the comment in this file's header.
21
22 // local helper functions
23 namespace {
24
UpdateListIndicesFromIndex(FallibleTArray<mozilla::dom::nsISVGPoint * > & aItemsArray,uint32_t aStartingIndex)25 void UpdateListIndicesFromIndex(
26 FallibleTArray<mozilla::dom::nsISVGPoint*>& aItemsArray,
27 uint32_t aStartingIndex) {
28 uint32_t length = aItemsArray.Length();
29
30 for (uint32_t i = aStartingIndex; i < length; ++i) {
31 if (aItemsArray[i]) {
32 aItemsArray[i]->UpdateListIndex(i);
33 }
34 }
35 }
36
37 } // namespace
38
39 namespace mozilla {
40 namespace dom {
41
42 static inline SVGAttrTearoffTable<void, DOMSVGPointList>&
SVGPointListTearoffTable()43 SVGPointListTearoffTable() {
44 static SVGAttrTearoffTable<void, DOMSVGPointList> sSVGPointListTearoffTable;
45 return sSVGPointListTearoffTable;
46 }
47
48 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPointList)
49
50 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPointList)
51 // No unlinking of mElement, we'd need to null out the value pointer (the
52 // object it points to is held by the element) and null-check it everywhere.
53 tmp->RemoveFromTearoffTable();
54 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
55 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPointList)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
59 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPointList)
60 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
61 NS_IMPL_CYCLE_COLLECTION_TRACE_END
62
63 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPointList)
64 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPointList)
65
66 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPointList)
67 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
68 NS_INTERFACE_MAP_ENTRY(nsISupports)
69 NS_INTERFACE_MAP_END
70
71 //----------------------------------------------------------------------
72 // Helper class: AutoChangePointListNotifier
73 // Stack-based helper class to pair calls to WillChangePointList and
74 // DidChangePointList.
75 class MOZ_RAII AutoChangePointListNotifier : public mozAutoDocUpdate {
76 public:
AutoChangePointListNotifier(DOMSVGPointList * aPointList MOZ_GUARD_OBJECT_NOTIFIER_PARAM)77 explicit AutoChangePointListNotifier(
78 DOMSVGPointList* aPointList MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
79 : mozAutoDocUpdate(aPointList->Element()->GetComposedDoc(), true),
80 mPointList(aPointList) {
81 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
82 MOZ_ASSERT(mPointList, "Expecting non-null pointList");
83 mEmptyOrOldValue = mPointList->Element()->WillChangePointList(*this);
84 }
85
~AutoChangePointListNotifier()86 ~AutoChangePointListNotifier() {
87 mPointList->Element()->DidChangePointList(mEmptyOrOldValue, *this);
88 if (mPointList->AttrIsAnimating()) {
89 mPointList->Element()->AnimationNeedsResample();
90 }
91 }
92
93 private:
94 DOMSVGPointList* const mPointList;
95 nsAttrValue mEmptyOrOldValue;
96 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
97 };
98
99 /* static */
GetDOMWrapper(void * aList,SVGElement * aElement,bool aIsAnimValList)100 already_AddRefed<DOMSVGPointList> DOMSVGPointList::GetDOMWrapper(
101 void* aList, SVGElement* aElement, bool aIsAnimValList) {
102 RefPtr<DOMSVGPointList> wrapper =
103 SVGPointListTearoffTable().GetTearoff(aList);
104 if (!wrapper) {
105 wrapper = new DOMSVGPointList(aElement, aIsAnimValList);
106 SVGPointListTearoffTable().AddTearoff(aList, wrapper);
107 }
108 return wrapper.forget();
109 }
110
111 /* static */
GetDOMWrapperIfExists(void * aList)112 DOMSVGPointList* DOMSVGPointList::GetDOMWrapperIfExists(void* aList) {
113 return SVGPointListTearoffTable().GetTearoff(aList);
114 }
115
RemoveFromTearoffTable()116 void DOMSVGPointList::RemoveFromTearoffTable() {
117 // Called from Unlink and the destructor.
118 //
119 // There are now no longer any references to us held by script or list items.
120 // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()!
121 void* key = mIsAnimValList ? InternalAList().GetAnimValKey()
122 : InternalAList().GetBaseValKey();
123 SVGPointListTearoffTable().RemoveTearoff(key);
124 }
125
~DOMSVGPointList()126 DOMSVGPointList::~DOMSVGPointList() { RemoveFromTearoffTable(); }
127
WrapObject(JSContext * cx,JS::Handle<JSObject * > aGivenProto)128 JSObject* DOMSVGPointList::WrapObject(JSContext* cx,
129 JS::Handle<JSObject*> aGivenProto) {
130 return mozilla::dom::SVGPointList_Binding::Wrap(cx, this, aGivenProto);
131 }
132
InternalListWillChangeTo(const SVGPointList & aNewValue)133 void DOMSVGPointList::InternalListWillChangeTo(const SVGPointList& aNewValue) {
134 // When the number of items in our internal counterpart changes, we MUST stay
135 // in sync. Everything in the scary comment in
136 // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here too!
137
138 uint32_t oldLength = mItems.Length();
139
140 uint32_t newLength = aNewValue.Length();
141 if (newLength > nsISVGPoint::MaxListIndex()) {
142 // It's safe to get out of sync with our internal list as long as we have
143 // FEWER items than it does.
144 newLength = nsISVGPoint::MaxListIndex();
145 }
146
147 RefPtr<DOMSVGPointList> kungFuDeathGrip;
148 if (newLength < oldLength) {
149 // RemovingFromList() might clear last reference to |this|.
150 // Retain a temporary reference to keep from dying before returning.
151 kungFuDeathGrip = this;
152 }
153
154 // If our length will decrease, notify the items that will be removed:
155 for (uint32_t i = newLength; i < oldLength; ++i) {
156 if (mItems[i]) {
157 mItems[i]->RemovingFromList();
158 }
159 }
160
161 if (!mItems.SetLength(newLength, fallible)) {
162 // We silently ignore SetLength OOM failure since being out of sync is safe
163 // so long as we have *fewer* items than our internal list.
164 mItems.Clear();
165 return;
166 }
167
168 // If our length has increased, null out the new pointers:
169 for (uint32_t i = oldLength; i < newLength; ++i) {
170 mItems[i] = nullptr;
171 }
172 }
173
AttrIsAnimating() const174 bool DOMSVGPointList::AttrIsAnimating() const {
175 return InternalAList().IsAnimating();
176 }
177
AnimListMirrorsBaseList() const178 bool DOMSVGPointList::AnimListMirrorsBaseList() const {
179 return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
180 !AttrIsAnimating();
181 }
182
InternalList() const183 SVGPointList& DOMSVGPointList::InternalList() const {
184 SVGAnimatedPointList* alist = mElement->GetAnimatedPointList();
185 return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal
186 : alist->mBaseVal;
187 }
188
InternalAList() const189 SVGAnimatedPointList& DOMSVGPointList::InternalAList() const {
190 MOZ_ASSERT(mElement->GetAnimatedPointList(), "Internal error");
191 return *mElement->GetAnimatedPointList();
192 }
193
194 // ----------------------------------------------------------------------------
195 // nsIDOMSVGPointList implementation:
196
Clear(ErrorResult & aError)197 void DOMSVGPointList::Clear(ErrorResult& aError) {
198 if (IsAnimValList()) {
199 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
200 return;
201 }
202
203 if (LengthNoFlush() > 0) {
204 AutoChangePointListNotifier notifier(this);
205 // DOM list items that are to be removed must be removed before we change
206 // the internal list, otherwise they wouldn't be able to copy their
207 // internal counterparts' values!
208
209 InternalListWillChangeTo(SVGPointList()); // clears mItems
210
211 if (!AttrIsAnimating()) {
212 // The anim val list is in sync with the base val list
213 DOMSVGPointList* animList =
214 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
215 if (animList) {
216 animList->InternalListWillChangeTo(
217 SVGPointList()); // clears its mItems
218 }
219 }
220
221 InternalList().Clear();
222 }
223 }
224
Initialize(nsISVGPoint & aNewItem,ErrorResult & aError)225 already_AddRefed<nsISVGPoint> DOMSVGPointList::Initialize(nsISVGPoint& aNewItem,
226 ErrorResult& aError) {
227 if (IsAnimValList()) {
228 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
229 return nullptr;
230 }
231
232 // If aNewItem is already in a list we should insert a clone of aNewItem,
233 // and for consistency, this should happen even if *this* is the list that
234 // aNewItem is currently in. Note that in the case of aNewItem being in this
235 // list, the Clear() call before the InsertItemBefore() call would remove it
236 // from this list, and so the InsertItemBefore() call would not insert a
237 // clone of aNewItem, it would actually insert aNewItem. To prevent that
238 // from happening we have to do the clone here, if necessary.
239
240 nsCOMPtr<nsISVGPoint> domItem = &aNewItem;
241 if (domItem->HasOwner() || domItem->IsReadonly() ||
242 domItem->IsTranslatePoint()) {
243 domItem = domItem->Copy(); // must do this before changing anything!
244 }
245
246 ErrorResult rv;
247 Clear(rv);
248 MOZ_ASSERT(!rv.Failed());
249 return InsertItemBefore(*domItem, 0, aError);
250 }
251
GetItem(uint32_t index,ErrorResult & error)252 already_AddRefed<nsISVGPoint> DOMSVGPointList::GetItem(uint32_t index,
253 ErrorResult& error) {
254 bool found;
255 RefPtr<nsISVGPoint> item = IndexedGetter(index, found, error);
256 if (!found) {
257 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
258 }
259 return item.forget();
260 }
261
IndexedGetter(uint32_t aIndex,bool & aFound,ErrorResult & aError)262 already_AddRefed<nsISVGPoint> DOMSVGPointList::IndexedGetter(
263 uint32_t aIndex, bool& aFound, ErrorResult& aError) {
264 if (IsAnimValList()) {
265 Element()->FlushAnimations();
266 }
267 aFound = aIndex < LengthNoFlush();
268 if (aFound) {
269 return GetItemAt(aIndex);
270 }
271 return nullptr;
272 }
273
InsertItemBefore(nsISVGPoint & aNewItem,uint32_t aIndex,ErrorResult & aError)274 already_AddRefed<nsISVGPoint> DOMSVGPointList::InsertItemBefore(
275 nsISVGPoint& aNewItem, uint32_t aIndex, ErrorResult& aError) {
276 if (IsAnimValList()) {
277 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
278 return nullptr;
279 }
280
281 aIndex = std::min(aIndex, LengthNoFlush());
282 if (aIndex >= nsISVGPoint::MaxListIndex()) {
283 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
284 return nullptr;
285 }
286
287 nsCOMPtr<nsISVGPoint> domItem = &aNewItem;
288 if (domItem->HasOwner() || domItem->IsReadonly() ||
289 domItem->IsTranslatePoint()) {
290 domItem = domItem->Copy(); // must do this before changing anything!
291 }
292
293 // Ensure we have enough memory so we can avoid complex error handling below:
294 if (!mItems.SetCapacity(mItems.Length() + 1, fallible) ||
295 !InternalList().SetCapacity(InternalList().Length() + 1)) {
296 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
297 return nullptr;
298 }
299 if (AnimListMirrorsBaseList()) {
300 DOMSVGPointList* animVal =
301 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
302 MOZ_ASSERT(animVal, "animVal must be a valid pointer");
303 if (!animVal->mItems.SetCapacity(animVal->mItems.Length() + 1, fallible)) {
304 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
305 return nullptr;
306 }
307 }
308
309 AutoChangePointListNotifier notifier(this);
310 // Now that we know we're inserting, keep animVal list in sync as necessary.
311 MaybeInsertNullInAnimValListAt(aIndex);
312
313 InternalList().InsertItem(aIndex, domItem->ToSVGPoint());
314 MOZ_ALWAYS_TRUE(mItems.InsertElementAt(aIndex, domItem, fallible));
315
316 // This MUST come after the insertion into InternalList(), or else under the
317 // insertion into InternalList() the values read from domItem would be bad
318 // data from InternalList() itself!:
319 domItem->InsertingIntoList(this, aIndex, IsAnimValList());
320
321 UpdateListIndicesFromIndex(mItems, aIndex + 1);
322
323 return domItem.forget();
324 }
325
ReplaceItem(nsISVGPoint & aNewItem,uint32_t aIndex,ErrorResult & aError)326 already_AddRefed<nsISVGPoint> DOMSVGPointList::ReplaceItem(
327 nsISVGPoint& aNewItem, uint32_t aIndex, ErrorResult& aError) {
328 if (IsAnimValList()) {
329 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
330 return nullptr;
331 }
332
333 if (aIndex >= LengthNoFlush()) {
334 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
335 return nullptr;
336 }
337
338 nsCOMPtr<nsISVGPoint> domItem = &aNewItem;
339 if (domItem->HasOwner() || domItem->IsReadonly() ||
340 domItem->IsTranslatePoint()) {
341 domItem = domItem->Copy(); // must do this before changing anything!
342 }
343
344 AutoChangePointListNotifier notifier(this);
345 if (mItems[aIndex]) {
346 // Notify any existing DOM item of removal *before* modifying the lists so
347 // that the DOM item can copy the *old* value at its index:
348 mItems[aIndex]->RemovingFromList();
349 }
350
351 InternalList()[aIndex] = domItem->ToSVGPoint();
352 mItems[aIndex] = domItem;
353
354 // This MUST come after the ToSVGPoint() call, otherwise that call
355 // would end up reading bad data from InternalList()!
356 domItem->InsertingIntoList(this, aIndex, IsAnimValList());
357
358 return domItem.forget();
359 }
360
RemoveItem(uint32_t aIndex,ErrorResult & aError)361 already_AddRefed<nsISVGPoint> DOMSVGPointList::RemoveItem(uint32_t aIndex,
362 ErrorResult& aError) {
363 if (IsAnimValList()) {
364 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
365 return nullptr;
366 }
367
368 if (aIndex >= LengthNoFlush()) {
369 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
370 return nullptr;
371 }
372
373 AutoChangePointListNotifier notifier(this);
374 // Now that we know we're removing, keep animVal list in sync as necessary.
375 // Do this *before* touching InternalList() so the removed item can get its
376 // internal value.
377 MaybeRemoveItemFromAnimValListAt(aIndex);
378
379 // We have to return the removed item, so get it, creating it if necessary:
380 RefPtr<nsISVGPoint> result = GetItemAt(aIndex);
381
382 // Notify the DOM item of removal *before* modifying the lists so that the
383 // DOM item can copy its *old* value:
384 mItems[aIndex]->RemovingFromList();
385
386 InternalList().RemoveItem(aIndex);
387 mItems.RemoveElementAt(aIndex);
388
389 UpdateListIndicesFromIndex(mItems, aIndex);
390
391 return result.forget();
392 }
393
GetItemAt(uint32_t aIndex)394 already_AddRefed<nsISVGPoint> DOMSVGPointList::GetItemAt(uint32_t aIndex) {
395 MOZ_ASSERT(aIndex < mItems.Length());
396
397 if (!mItems[aIndex]) {
398 mItems[aIndex] = new DOMSVGPoint(this, aIndex, IsAnimValList());
399 }
400 RefPtr<nsISVGPoint> result = mItems[aIndex];
401 return result.forget();
402 }
403
MaybeInsertNullInAnimValListAt(uint32_t aIndex)404 void DOMSVGPointList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) {
405 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
406
407 if (!AnimListMirrorsBaseList()) {
408 return;
409 }
410
411 // The anim val list is in sync with the base val list
412 DOMSVGPointList* animVal =
413 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
414
415 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
416 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
417 "animVal list not in sync!");
418 MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible));
419
420 UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
421 }
422
MaybeRemoveItemFromAnimValListAt(uint32_t aIndex)423 void DOMSVGPointList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) {
424 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
425
426 if (!AnimListMirrorsBaseList()) {
427 return;
428 }
429
430 // This needs to be a strong reference; otherwise, the RemovingFromList call
431 // below might drop the last reference to animVal before we're done with it.
432 RefPtr<DOMSVGPointList> animVal =
433 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
434
435 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
436 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
437 "animVal list not in sync!");
438
439 if (animVal->mItems[aIndex]) {
440 animVal->mItems[aIndex]->RemovingFromList();
441 }
442 animVal->mItems.RemoveElementAt(aIndex);
443
444 UpdateListIndicesFromIndex(animVal->mItems, aIndex);
445 }
446
447 } // namespace dom
448 } // namespace mozilla
449