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