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