no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / svg / DOMSVGPathSegList.cpp
blob10263a4835391a204921deebd2b5c201a5b3cd03
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"
10 #include "nsError.h"
11 #include "SVGAnimatedPathSegList.h"
12 #include "SVGAttrTearoffTable.h"
13 #include "SVGPathSegUtils.h"
14 #include "mozilla/dom/SVGElement.h"
15 #include "mozilla/dom/SVGPathElement.h"
16 #include "mozilla/dom/SVGPathSegListBinding.h"
17 #include "mozilla/RefPtr.h"
19 // See the comment in this file's header.
21 namespace mozilla::dom {
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)
51 NS_INTERFACE_MAP_END
53 /* static */
54 already_AddRefed<DOMSVGPathSegList> DOMSVGPathSegList::GetDOMWrapper(
55 void* aList, SVGPathElement* aElement) {
56 RefPtr<DOMSVGPathSegList> wrapper =
57 SVGPathSegListTearoffTable().GetTearoff(aList);
58 if (!wrapper) {
59 wrapper = new DOMSVGPathSegList(
60 aElement, aElement->GetAnimPathSegList()->GetAnimValKey() == aList);
61 SVGPathSegListTearoffTable().AddTearoff(aList, wrapper);
63 return wrapper.forget();
66 /* static */
67 DOMSVGPathSegList* DOMSVGPathSegList::GetDOMWrapperIfExists(void* aList) {
68 return SVGPathSegListTearoffTable().GetTearoff(aList);
71 void DOMSVGPathSegList::RemoveFromTearoffTable() {
72 // There are now no longer any references to us held by script or list items.
73 // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()!
74 void* key = mIsAnimValList ? InternalAList().GetAnimValKey()
75 : InternalAList().GetBaseValKey();
76 SVGPathSegListTearoffTable().RemoveTearoff(key);
79 DOMSVGPathSegList::~DOMSVGPathSegList() { RemoveFromTearoffTable(); }
81 JSObject* DOMSVGPathSegList::WrapObject(JSContext* cx,
82 JS::Handle<JSObject*> aGivenProto) {
83 return mozilla::dom::SVGPathSegList_Binding::Wrap(cx, this, aGivenProto);
86 void DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue) {
87 // When the number of items in our internal counterpart changes, we MUST stay
88 // in sync. Everything in the scary comment in
89 // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here just as
90 // much, but we have the additional issue that failing to stay in sync would
91 // mean that - assuming we aren't reading bad memory - we would likely end up
92 // decoding command types from argument floats when looking in our
93 // SVGPathData's data array! Either way, we'll likely then go down
94 // MOZ_ASSERT_UNREACHABLE code paths, or end up reading/setting more bad
95 // memory!!
97 // The only time that our other DOM list type implementations remove items is
98 // if those items become surplus items due to an attribute change or SMIL
99 // animation sample shortening the list. In general though, they try to keep
100 // their existing DOM items, even when things change. To be consistent, we'd
101 // really like to do the same thing. However, because different types of path
102 // segment correspond to different DOMSVGPathSeg subclasses, the type of
103 // items in our list are generally not the same, which makes this harder for
104 // us. We have to remove DOM segments if their type is not the same as the
105 // type of the new internal segment at their index.
107 // We also need to sync up mInternalDataIndex, but since we need to loop over
108 // all the items in the new list checking types anyway, that's almost
109 // insignificant in terms of overhead.
111 // Note that this method is called on every single SMIL animation resample
112 // and we have no way to short circuit the overhead since we don't have a
113 // way to tell if the call is due to a new animation, or a resample of an
114 // existing animation (when the number and type of items would be the same).
115 // (Note that a new animation could start overriding an existing animation at
116 // any time, so checking IsAnimating() wouldn't work.) Because we get called
117 // on every sample, it would not be acceptable alternative to throw away all
118 // our items and let them be recreated lazily, since that would break what
119 // script sees!
121 uint32_t length = mItems.Length();
122 uint32_t index = 0;
124 uint32_t dataLength = aNewValue.mData.Length();
125 uint32_t dataIndex = 0; // index into aNewValue's raw data array
127 uint32_t newSegType;
129 RefPtr<DOMSVGPathSegList> kungFuDeathGrip;
130 if (length) {
131 // RemovingFromList() might clear last reference to |this|.
132 // Retain a temporary reference to keep from dying before returning.
134 // NOTE: For path-seg lists (unlike other list types), we have to do this
135 // *whenever our list is nonempty* (even if we're growing in length).
136 // That's because the path-seg-type of any segment could differ between old
137 // list vs. new list, which will make us destroy & recreate that segment,
138 // which could remove the last reference to us.
140 // (We explicitly *don't* want to create a kungFuDeathGrip in the length=0
141 // case, though, because we do hit this code inside our constructor before
142 // any other owning references have been added, and at that point, the
143 // deathgrip-removal would make us die before we exit our constructor.)
144 kungFuDeathGrip = this;
147 while (index < length && dataIndex < dataLength) {
148 newSegType = SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]);
149 if (ItemAt(index) && ItemAt(index)->Type() != newSegType) {
150 ItemAt(index)->RemovingFromList();
151 ItemAt(index) = nullptr;
153 // Only after the RemovingFromList() can we touch mInternalDataIndex!
154 mItems[index].mInternalDataIndex = dataIndex;
155 ++index;
156 dataIndex += 1 + SVGPathSegUtils::ArgCountForType(newSegType);
159 MOZ_ASSERT((index == length && dataIndex <= dataLength) ||
160 (index <= length && dataIndex == dataLength),
161 "very bad - list corruption?");
163 if (index < length) {
164 // aNewValue has fewer items than our previous internal counterpart
166 uint32_t newLength = index;
168 // Remove excess items from the list:
169 for (; index < length; ++index) {
170 if (ItemAt(index)) {
171 ItemAt(index)->RemovingFromList();
172 ItemAt(index) = nullptr;
176 // Only now may we truncate mItems
177 mItems.TruncateLength(newLength);
178 } else if (dataIndex < dataLength) {
179 // aNewValue has more items than our previous internal counterpart
181 // Sync mItems:
182 while (dataIndex < dataLength) {
183 if (mItems.Length() &&
184 mItems.Length() - 1 > DOMSVGPathSeg::MaxListIndex()) {
185 // It's safe to get out of sync with our internal list as long as we
186 // have FEWER items than it does.
187 return;
189 if (!mItems.AppendElement(ItemProxy(nullptr, dataIndex), fallible)) {
190 // OOM
191 ErrorResult rv;
192 Clear(rv);
193 MOZ_ASSERT(!rv.Failed());
194 return;
196 dataIndex +=
197 1 + SVGPathSegUtils::ArgCountForType(
198 SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]));
202 MOZ_ASSERT(dataIndex == dataLength, "Serious processing error");
203 MOZ_ASSERT(index == length, "Serious counting error");
206 bool DOMSVGPathSegList::AttrIsAnimating() const {
207 return InternalAList().IsAnimating();
210 bool DOMSVGPathSegList::AnimListMirrorsBaseList() const {
211 return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
212 !AttrIsAnimating();
215 SVGPathData& DOMSVGPathSegList::InternalList() const {
216 SVGAnimatedPathSegList* alist = mElement->GetAnimPathSegList();
217 return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal
218 : alist->mBaseVal;
221 SVGAnimatedPathSegList& DOMSVGPathSegList::InternalAList() const {
222 MOZ_ASSERT(mElement->GetAnimPathSegList(), "Internal error");
223 return *mElement->GetAnimPathSegList();
226 // ----------------------------------------------------------------------------
227 // nsIDOMSVGPathSegList implementation:
229 void DOMSVGPathSegList::Clear(ErrorResult& aError) {
230 if (IsAnimValList()) {
231 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
232 return;
235 if (LengthNoFlush() > 0) {
236 AutoChangePathSegListNotifier notifier(this);
237 // DOM list items that are to be removed must be removed before we change
238 // the internal list, otherwise they wouldn't be able to copy their
239 // internal counterparts' values!
241 InternalListWillChangeTo(SVGPathData()); // clears mItems
243 if (!AttrIsAnimating()) {
244 // The anim val list is in sync with the base val list
245 DOMSVGPathSegList* animList =
246 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
247 if (animList) {
248 animList->InternalListWillChangeTo(SVGPathData()); // clears its mItems
252 InternalList().Clear();
256 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::Initialize(
257 DOMSVGPathSeg& aNewItem, ErrorResult& aError) {
258 if (IsAnimValList()) {
259 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
260 return nullptr;
263 // If aNewItem is already in a list we should insert a clone of aNewItem,
264 // and for consistency, this should happen even if *this* is the list that
265 // aNewItem is currently in. Note that in the case of aNewItem being in this
266 // list, the Clear() call before the InsertItemBefore() call would remove it
267 // from this list, and so the InsertItemBefore() call would not insert a
268 // clone of aNewItem, it would actually insert aNewItem. To prevent that
269 // from happening we have to do the clone here, if necessary.
271 RefPtr<DOMSVGPathSeg> domItem = &aNewItem;
272 if (aNewItem.HasOwner()) {
273 domItem = aNewItem.Clone();
276 Clear(aError);
277 MOZ_ASSERT(!aError.Failed(), "How could this fail?");
278 return InsertItemBefore(*domItem, 0, aError);
281 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::GetItem(uint32_t index,
282 ErrorResult& error) {
283 bool found;
284 RefPtr<DOMSVGPathSeg> item = IndexedGetter(index, found, error);
285 if (!found) {
286 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
288 return item.forget();
291 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::IndexedGetter(
292 uint32_t aIndex, bool& aFound, ErrorResult& aError) {
293 if (IsAnimValList()) {
294 Element()->FlushAnimations();
296 aFound = aIndex < LengthNoFlush();
297 if (aFound) {
298 return GetItemAt(aIndex);
300 return nullptr;
303 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::InsertItemBefore(
304 DOMSVGPathSeg& aNewItem, uint32_t aIndex, ErrorResult& aError) {
305 if (IsAnimValList()) {
306 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
307 return nullptr;
310 uint32_t internalIndex;
311 if (aIndex < LengthNoFlush()) {
312 internalIndex = mItems[aIndex].mInternalDataIndex;
313 } else {
314 aIndex = LengthNoFlush();
315 internalIndex = InternalList().mData.Length();
317 if (aIndex >= DOMSVGPathSeg::MaxListIndex()) {
318 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
319 return nullptr;
322 RefPtr<DOMSVGPathSeg> domItem = &aNewItem;
323 if (domItem->HasOwner()) {
324 domItem = domItem->Clone(); // must do this before changing anything!
327 uint32_t argCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
329 // Ensure we have enough memory so we can avoid complex error handling below:
330 if (!mItems.SetCapacity(mItems.Length() + 1, fallible) ||
331 !InternalList().mData.SetCapacity(
332 InternalList().mData.Length() + 1 + argCount, fallible)) {
333 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
334 return nullptr;
336 if (AnimListMirrorsBaseList()) {
337 DOMSVGPathSegList* animVal =
338 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
339 MOZ_ASSERT(animVal, "animVal should be a valid pointer");
340 if (!animVal->mItems.SetCapacity(animVal->mItems.Length() + 1, fallible)) {
341 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
342 return nullptr;
346 AutoChangePathSegListNotifier notifier(this);
347 // Now that we know we're inserting, keep animVal list in sync as necessary.
348 MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount);
350 float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
351 domItem->ToSVGPathSegEncodedData(segAsRaw);
353 MOZ_ALWAYS_TRUE(InternalList().mData.InsertElementsAt(
354 internalIndex, segAsRaw, 1 + argCount, fallible));
355 MOZ_ALWAYS_TRUE(mItems.InsertElementAt(
356 aIndex, ItemProxy(domItem.get(), internalIndex), fallible));
358 // This MUST come after the insertion into InternalList(), or else under the
359 // insertion into InternalList() the values read from domItem would be bad
360 // data from InternalList() itself!:
361 domItem->InsertingIntoList(this, aIndex, IsAnimValList());
363 UpdateListIndicesFromIndex(aIndex + 1, argCount + 1);
365 return domItem.forget();
368 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::ReplaceItem(
369 DOMSVGPathSeg& aNewItem, uint32_t aIndex, ErrorResult& aError) {
370 if (IsAnimValList()) {
371 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
372 return nullptr;
375 if (aIndex >= LengthNoFlush()) {
376 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
377 return nullptr;
380 RefPtr<DOMSVGPathSeg> domItem = &aNewItem;
381 if (domItem->HasOwner()) {
382 domItem = domItem->Clone(); // must do this before changing anything!
385 AutoChangePathSegListNotifier notifier(this);
386 if (ItemAt(aIndex)) {
387 // Notify any existing DOM item of removal *before* modifying the lists so
388 // that the DOM item can copy the *old* value at its index:
389 ItemAt(aIndex)->RemovingFromList();
392 uint32_t internalIndex = mItems[aIndex].mInternalDataIndex;
393 // We use InternalList() to get oldArgCount since we may not have a DOM
394 // wrapper at the index being replaced.
395 uint32_t oldType =
396 SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]);
398 // NOTE: ArgCountForType returns a (small) unsigned value, but we're
399 // intentionally putting it in a signed variable, because we're going to
400 // subtract these values and might produce something negative.
401 int32_t oldArgCount = SVGPathSegUtils::ArgCountForType(oldType);
402 int32_t newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
404 float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
405 domItem->ToSVGPathSegEncodedData(segAsRaw);
407 if (!InternalList().mData.ReplaceElementsAt(internalIndex, 1 + oldArgCount,
408 segAsRaw, 1 + newArgCount,
409 fallible)) {
410 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
411 return nullptr;
413 ItemAt(aIndex) = domItem;
415 // This MUST come after the ToSVGPathSegEncodedData call, otherwise that call
416 // would end up reading bad data from InternalList()!
417 domItem->InsertingIntoList(this, aIndex, IsAnimValList());
419 int32_t delta = newArgCount - oldArgCount;
420 if (delta != 0) {
421 for (uint32_t i = aIndex + 1; i < LengthNoFlush(); ++i) {
422 mItems[i].mInternalDataIndex += delta;
426 return domItem.forget();
429 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::RemoveItem(
430 uint32_t aIndex, ErrorResult& aError) {
431 if (IsAnimValList()) {
432 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
433 return nullptr;
436 if (aIndex >= LengthNoFlush()) {
437 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
438 return nullptr;
440 // We have to return the removed item, so get it, creating it if necessary:
441 RefPtr<DOMSVGPathSeg> result = GetItemAt(aIndex);
443 AutoChangePathSegListNotifier notifier(this);
444 // Notify the DOM item of removal *before* modifying the lists so that the
445 // DOM item can copy its *old* value:
446 ItemAt(aIndex)->RemovingFromList();
448 uint32_t internalIndex = mItems[aIndex].mInternalDataIndex;
449 uint32_t segType =
450 SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]);
451 // NOTE: ArgCountForType returns a (small) unsigned value, but we're
452 // intentionally putting it in a signed value, because we're going to
453 // negate it, and you can't negate an unsigned value.
454 int32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
456 // Now that we know we're removing, keep animVal list in sync as necessary.
457 // Do this *before* touching InternalList() so the removed item can get its
458 // internal value.
459 MaybeRemoveItemFromAnimValListAt(aIndex, argCount);
461 InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount);
462 mItems.RemoveElementAt(aIndex);
464 UpdateListIndicesFromIndex(aIndex, -(argCount + 1));
466 return result.forget();
469 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::GetItemAt(uint32_t aIndex) {
470 MOZ_ASSERT(aIndex < mItems.Length());
472 if (!ItemAt(aIndex)) {
473 ItemAt(aIndex) = DOMSVGPathSeg::CreateFor(this, aIndex, IsAnimValList());
475 RefPtr<DOMSVGPathSeg> result = ItemAt(aIndex);
476 return result.forget();
479 void DOMSVGPathSegList::MaybeInsertNullInAnimValListAt(
480 uint32_t aIndex, uint32_t aInternalIndex, uint32_t aArgCountForItem) {
481 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
483 if (!AnimListMirrorsBaseList()) {
484 return;
487 // The anim val list is in sync with the base val list
488 DOMSVGPathSegList* animVal =
489 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
491 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
492 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
493 "animVal list not in sync!");
494 MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(
495 aIndex, ItemProxy(nullptr, aInternalIndex), fallible));
497 animVal->UpdateListIndicesFromIndex(aIndex + 1, 1 + aArgCountForItem);
500 void DOMSVGPathSegList::MaybeRemoveItemFromAnimValListAt(
501 uint32_t aIndex, int32_t aArgCountForItem) {
502 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
504 if (!AnimListMirrorsBaseList()) {
505 return;
508 // This needs to be a strong reference; otherwise, the RemovingFromList call
509 // below might drop the last reference to animVal before we're done with it.
510 RefPtr<DOMSVGPathSegList> animVal =
511 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
513 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
514 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
515 "animVal list not in sync!");
517 if (animVal->ItemAt(aIndex)) {
518 animVal->ItemAt(aIndex)->RemovingFromList();
520 animVal->mItems.RemoveElementAt(aIndex);
522 animVal->UpdateListIndicesFromIndex(aIndex, -(1 + aArgCountForItem));
525 void DOMSVGPathSegList::UpdateListIndicesFromIndex(
526 uint32_t aStartingIndex, int32_t aInternalDataIndexDelta) {
527 uint32_t length = mItems.Length();
529 for (uint32_t i = aStartingIndex; i < length; ++i) {
530 mItems[i].mInternalDataIndex += aInternalDataIndexDelta;
531 if (ItemAt(i)) {
532 ItemAt(i)->UpdateListIndex(i);
537 } // namespace mozilla::dom