Bug 1610775 [wpt PR 21336] - Update urllib3 to 1.25.8, a=testonly
[gecko.git] / dom / svg / DOMSVGPathSegList.cpp
blobf1dd3ba3e5d43a9d7b646521e0e0c3d75963742a
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 "mozAutoDocUpdate.h"
11 #include "nsError.h"
12 #include "SVGAnimatedPathSegList.h"
13 #include "SVGAttrTearoffTable.h"
14 #include "SVGPathSegUtils.h"
15 #include "mozilla/dom/SVGElement.h"
16 #include "mozilla/dom/SVGPathSegListBinding.h"
17 #include "mozilla/RefPtr.h"
19 // See the comment in this file's header.
21 namespace mozilla {
22 namespace dom {
24 static inline SVGAttrTearoffTable<void, DOMSVGPathSegList>&
25 SVGPathSegListTearoffTable() {
26 static SVGAttrTearoffTable<void, DOMSVGPathSegList>
27 sSVGPathSegListTearoffTable;
28 return sSVGPathSegListTearoffTable;
31 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPathSegList)
33 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPathSegList)
34 // No unlinking of mElement, we'd need to null out the value pointer (the
35 // object it points to is held by the element) and null-check it everywhere.
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 //----------------------------------------------------------------------
54 // Helper class: AutoChangePathSegListNotifier
55 // Stack-based helper class to pair calls to WillChangePathSegList and
56 // DidChangePathSegList.
57 class MOZ_RAII AutoChangePathSegListNotifier : public mozAutoDocUpdate {
58 public:
59 explicit AutoChangePathSegListNotifier(
60 DOMSVGPathSegList* aPathSegList MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
61 : mozAutoDocUpdate(aPathSegList->Element()->GetComposedDoc(), true),
62 mPathSegList(aPathSegList) {
63 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
64 MOZ_ASSERT(mPathSegList, "Expecting non-null pathSegList");
65 mEmptyOrOldValue = mPathSegList->Element()->WillChangePathSegList(*this);
68 ~AutoChangePathSegListNotifier() {
69 mPathSegList->Element()->DidChangePathSegList(mEmptyOrOldValue, *this);
70 if (mPathSegList->AttrIsAnimating()) {
71 mPathSegList->Element()->AnimationNeedsResample();
75 private:
76 DOMSVGPathSegList* const mPathSegList;
77 nsAttrValue mEmptyOrOldValue;
78 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
81 /* static */
82 already_AddRefed<DOMSVGPathSegList> DOMSVGPathSegList::GetDOMWrapper(
83 void* aList, SVGElement* aElement, bool aIsAnimValList) {
84 RefPtr<DOMSVGPathSegList> wrapper =
85 SVGPathSegListTearoffTable().GetTearoff(aList);
86 if (!wrapper) {
87 wrapper = new DOMSVGPathSegList(aElement, aIsAnimValList);
88 SVGPathSegListTearoffTable().AddTearoff(aList, wrapper);
90 return wrapper.forget();
93 /* static */
94 DOMSVGPathSegList* DOMSVGPathSegList::GetDOMWrapperIfExists(void* aList) {
95 return SVGPathSegListTearoffTable().GetTearoff(aList);
98 DOMSVGPathSegList::~DOMSVGPathSegList() {
99 // There are now no longer any references to us held by script or list items.
100 // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()!
101 void* key = mIsAnimValList ? InternalAList().GetAnimValKey()
102 : InternalAList().GetBaseValKey();
103 SVGPathSegListTearoffTable().RemoveTearoff(key);
106 JSObject* DOMSVGPathSegList::WrapObject(JSContext* cx,
107 JS::Handle<JSObject*> aGivenProto) {
108 return mozilla::dom::SVGPathSegList_Binding::Wrap(cx, this, aGivenProto);
111 void DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue) {
112 // When the number of items in our internal counterpart changes, we MUST stay
113 // in sync. Everything in the scary comment in
114 // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here just as
115 // much, but we have the additional issue that failing to stay in sync would
116 // mean that - assuming we aren't reading bad memory - we would likely end up
117 // decoding command types from argument floats when looking in our
118 // SVGPathData's data array! Either way, we'll likely then go down
119 // MOZ_ASSERT_UNREACHABLE code paths, or end up reading/setting more bad
120 // memory!!
122 // The only time that our other DOM list type implementations remove items is
123 // if those items become surplus items due to an attribute change or SMIL
124 // animation sample shortening the list. In general though, they try to keep
125 // their existing DOM items, even when things change. To be consistent, we'd
126 // really like to do the same thing. However, because different types of path
127 // segment correspond to different DOMSVGPathSeg subclasses, the type of
128 // items in our list are generally not the same, which makes this harder for
129 // us. We have to remove DOM segments if their type is not the same as the
130 // type of the new internal segment at their index.
132 // We also need to sync up mInternalDataIndex, but since we need to loop over
133 // all the items in the new list checking types anyway, that's almost
134 // insignificant in terms of overhead.
136 // Note that this method is called on every single SMIL animation resample
137 // and we have no way to short circuit the overhead since we don't have a
138 // way to tell if the call is due to a new animation, or a resample of an
139 // existing animation (when the number and type of items would be the same).
140 // (Note that a new animation could start overriding an existing animation at
141 // any time, so checking IsAnimating() wouldn't work.) Because we get called
142 // on every sample, it would not be acceptable alternative to throw away all
143 // our items and let them be recreated lazily, since that would break what
144 // script sees!
146 uint32_t length = mItems.Length();
147 uint32_t index = 0;
149 uint32_t dataLength = aNewValue.mData.Length();
150 uint32_t dataIndex = 0; // index into aNewValue's raw data array
152 uint32_t newSegType;
154 RefPtr<DOMSVGPathSegList> kungFuDeathGrip;
155 if (length) {
156 // RemovingFromList() might clear last reference to |this|.
157 // Retain a temporary reference to keep from dying before returning.
159 // NOTE: For path-seg lists (unlike other list types), we have to do this
160 // *whenever our list is nonempty* (even if we're growing in length).
161 // That's because the path-seg-type of any segment could differ between old
162 // list vs. new list, which will make us destroy & recreate that segment,
163 // which could remove the last reference to us.
165 // (We explicitly *don't* want to create a kungFuDeathGrip in the length=0
166 // case, though, because we do hit this code inside our constructor before
167 // any other owning references have been added, and at that point, the
168 // deathgrip-removal would make us die before we exit our constructor.)
169 kungFuDeathGrip = this;
172 while (index < length && dataIndex < dataLength) {
173 newSegType = SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]);
174 if (ItemAt(index) && ItemAt(index)->Type() != newSegType) {
175 ItemAt(index)->RemovingFromList();
176 ItemAt(index) = nullptr;
178 // Only after the RemovingFromList() can we touch mInternalDataIndex!
179 mItems[index].mInternalDataIndex = dataIndex;
180 ++index;
181 dataIndex += 1 + SVGPathSegUtils::ArgCountForType(newSegType);
184 MOZ_ASSERT((index == length && dataIndex <= dataLength) ||
185 (index <= length && dataIndex == dataLength),
186 "very bad - list corruption?");
188 if (index < length) {
189 // aNewValue has fewer items than our previous internal counterpart
191 uint32_t newLength = index;
193 // Remove excess items from the list:
194 for (; index < length; ++index) {
195 if (ItemAt(index)) {
196 ItemAt(index)->RemovingFromList();
197 ItemAt(index) = nullptr;
201 // Only now may we truncate mItems
202 mItems.TruncateLength(newLength);
203 } else if (dataIndex < dataLength) {
204 // aNewValue has more items than our previous internal counterpart
206 // Sync mItems:
207 while (dataIndex < dataLength) {
208 if (mItems.Length() &&
209 mItems.Length() - 1 > DOMSVGPathSeg::MaxListIndex()) {
210 // It's safe to get out of sync with our internal list as long as we
211 // have FEWER items than it does.
212 return;
214 if (!mItems.AppendElement(ItemProxy(nullptr, dataIndex), fallible)) {
215 // OOM
216 ErrorResult rv;
217 Clear(rv);
218 MOZ_ASSERT(!rv.Failed());
219 return;
221 dataIndex +=
222 1 + SVGPathSegUtils::ArgCountForType(
223 SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]));
227 MOZ_ASSERT(dataIndex == dataLength, "Serious processing error");
228 MOZ_ASSERT(index == length, "Serious counting error");
231 bool DOMSVGPathSegList::AttrIsAnimating() const {
232 return InternalAList().IsAnimating();
235 bool DOMSVGPathSegList::AnimListMirrorsBaseList() const {
236 return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) &&
237 !AttrIsAnimating();
240 SVGPathData& DOMSVGPathSegList::InternalList() const {
241 SVGAnimatedPathSegList* alist = mElement->GetAnimPathSegList();
242 return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal
243 : alist->mBaseVal;
246 SVGAnimatedPathSegList& DOMSVGPathSegList::InternalAList() const {
247 MOZ_ASSERT(mElement->GetAnimPathSegList(), "Internal error");
248 return *mElement->GetAnimPathSegList();
251 // ----------------------------------------------------------------------------
252 // nsIDOMSVGPathSegList implementation:
254 void DOMSVGPathSegList::Clear(ErrorResult& aError) {
255 if (IsAnimValList()) {
256 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
257 return;
260 if (LengthNoFlush() > 0) {
261 AutoChangePathSegListNotifier notifier(this);
262 // DOM list items that are to be removed must be removed before we change
263 // the internal list, otherwise they wouldn't be able to copy their
264 // internal counterparts' values!
266 InternalListWillChangeTo(SVGPathData()); // clears mItems
268 if (!AttrIsAnimating()) {
269 // The anim val list is in sync with the base val list
270 DOMSVGPathSegList* animList =
271 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
272 if (animList) {
273 animList->InternalListWillChangeTo(SVGPathData()); // clears its mItems
277 InternalList().Clear();
281 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::Initialize(
282 DOMSVGPathSeg& aNewItem, ErrorResult& aError) {
283 if (IsAnimValList()) {
284 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
285 return nullptr;
288 // If aNewItem is already in a list we should insert a clone of aNewItem,
289 // and for consistency, this should happen even if *this* is the list that
290 // aNewItem is currently in. Note that in the case of aNewItem being in this
291 // list, the Clear() call before the InsertItemBefore() call would remove it
292 // from this list, and so the InsertItemBefore() call would not insert a
293 // clone of aNewItem, it would actually insert aNewItem. To prevent that
294 // from happening we have to do the clone here, if necessary.
296 RefPtr<DOMSVGPathSeg> domItem = &aNewItem;
297 if (aNewItem.HasOwner()) {
298 domItem = aNewItem.Clone();
301 Clear(aError);
302 MOZ_ASSERT(!aError.Failed(), "How could this fail?");
303 return InsertItemBefore(*domItem, 0, aError);
306 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::GetItem(uint32_t index,
307 ErrorResult& error) {
308 bool found;
309 RefPtr<DOMSVGPathSeg> item = IndexedGetter(index, found, error);
310 if (!found) {
311 error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
313 return item.forget();
316 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::IndexedGetter(
317 uint32_t aIndex, bool& aFound, ErrorResult& aError) {
318 if (IsAnimValList()) {
319 Element()->FlushAnimations();
321 aFound = aIndex < LengthNoFlush();
322 if (aFound) {
323 return GetItemAt(aIndex);
325 return nullptr;
328 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::InsertItemBefore(
329 DOMSVGPathSeg& aNewItem, uint32_t aIndex, ErrorResult& aError) {
330 if (IsAnimValList()) {
331 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
332 return nullptr;
335 uint32_t internalIndex;
336 if (aIndex < LengthNoFlush()) {
337 internalIndex = mItems[aIndex].mInternalDataIndex;
338 } else {
339 aIndex = LengthNoFlush();
340 internalIndex = InternalList().mData.Length();
342 if (aIndex >= DOMSVGPathSeg::MaxListIndex()) {
343 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
344 return nullptr;
347 RefPtr<DOMSVGPathSeg> domItem = &aNewItem;
348 if (domItem->HasOwner()) {
349 domItem = domItem->Clone(); // must do this before changing anything!
352 uint32_t argCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
354 // Ensure we have enough memory so we can avoid complex error handling below:
355 if (!mItems.SetCapacity(mItems.Length() + 1, fallible) ||
356 !InternalList().mData.SetCapacity(
357 InternalList().mData.Length() + 1 + argCount, fallible)) {
358 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
359 return nullptr;
361 if (AnimListMirrorsBaseList()) {
362 DOMSVGPathSegList* animVal =
363 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
364 MOZ_ASSERT(animVal, "animVal should be a valid pointer");
365 if (!animVal->mItems.SetCapacity(animVal->mItems.Length() + 1, fallible)) {
366 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
367 return nullptr;
371 AutoChangePathSegListNotifier notifier(this);
372 // Now that we know we're inserting, keep animVal list in sync as necessary.
373 MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount);
375 float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
376 domItem->ToSVGPathSegEncodedData(segAsRaw);
378 MOZ_ALWAYS_TRUE(InternalList().mData.InsertElementsAt(
379 internalIndex, segAsRaw, 1 + argCount, fallible));
380 MOZ_ALWAYS_TRUE(mItems.InsertElementAt(
381 aIndex, ItemProxy(domItem.get(), internalIndex), fallible));
383 // This MUST come after the insertion into InternalList(), or else under the
384 // insertion into InternalList() the values read from domItem would be bad
385 // data from InternalList() itself!:
386 domItem->InsertingIntoList(this, aIndex, IsAnimValList());
388 UpdateListIndicesFromIndex(aIndex + 1, argCount + 1);
390 return domItem.forget();
393 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::ReplaceItem(
394 DOMSVGPathSeg& aNewItem, uint32_t aIndex, ErrorResult& aError) {
395 if (IsAnimValList()) {
396 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
397 return nullptr;
400 if (aIndex >= LengthNoFlush()) {
401 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
402 return nullptr;
405 RefPtr<DOMSVGPathSeg> domItem = &aNewItem;
406 if (domItem->HasOwner()) {
407 domItem = domItem->Clone(); // must do this before changing anything!
410 AutoChangePathSegListNotifier notifier(this);
411 if (ItemAt(aIndex)) {
412 // Notify any existing DOM item of removal *before* modifying the lists so
413 // that the DOM item can copy the *old* value at its index:
414 ItemAt(aIndex)->RemovingFromList();
417 uint32_t internalIndex = mItems[aIndex].mInternalDataIndex;
418 // We use InternalList() to get oldArgCount since we may not have a DOM
419 // wrapper at the index being replaced.
420 uint32_t oldType =
421 SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]);
423 // NOTE: ArgCountForType returns a (small) unsigned value, but we're
424 // intentionally putting it in a signed variable, because we're going to
425 // subtract these values and might produce something negative.
426 int32_t oldArgCount = SVGPathSegUtils::ArgCountForType(oldType);
427 int32_t newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
429 float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
430 domItem->ToSVGPathSegEncodedData(segAsRaw);
432 if (!InternalList().mData.ReplaceElementsAt(internalIndex, 1 + oldArgCount,
433 segAsRaw, 1 + newArgCount,
434 fallible)) {
435 aError.Throw(NS_ERROR_OUT_OF_MEMORY);
436 return nullptr;
438 ItemAt(aIndex) = domItem;
440 // This MUST come after the ToSVGPathSegEncodedData call, otherwise that call
441 // would end up reading bad data from InternalList()!
442 domItem->InsertingIntoList(this, aIndex, IsAnimValList());
444 int32_t delta = newArgCount - oldArgCount;
445 if (delta != 0) {
446 for (uint32_t i = aIndex + 1; i < LengthNoFlush(); ++i) {
447 mItems[i].mInternalDataIndex += delta;
451 return domItem.forget();
454 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::RemoveItem(
455 uint32_t aIndex, ErrorResult& aError) {
456 if (IsAnimValList()) {
457 aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
458 return nullptr;
461 if (aIndex >= LengthNoFlush()) {
462 aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
463 return nullptr;
465 // We have to return the removed item, so get it, creating it if necessary:
466 RefPtr<DOMSVGPathSeg> result = GetItemAt(aIndex);
468 AutoChangePathSegListNotifier notifier(this);
469 // Notify the DOM item of removal *before* modifying the lists so that the
470 // DOM item can copy its *old* value:
471 ItemAt(aIndex)->RemovingFromList();
473 uint32_t internalIndex = mItems[aIndex].mInternalDataIndex;
474 uint32_t segType =
475 SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]);
476 // NOTE: ArgCountForType returns a (small) unsigned value, but we're
477 // intentionally putting it in a signed value, because we're going to
478 // negate it, and you can't negate an unsigned value.
479 int32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
481 // Now that we know we're removing, keep animVal list in sync as necessary.
482 // Do this *before* touching InternalList() so the removed item can get its
483 // internal value.
484 MaybeRemoveItemFromAnimValListAt(aIndex, argCount);
486 InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount);
487 mItems.RemoveElementAt(aIndex);
489 UpdateListIndicesFromIndex(aIndex, -(argCount + 1));
491 return result.forget();
494 already_AddRefed<DOMSVGPathSeg> DOMSVGPathSegList::GetItemAt(uint32_t aIndex) {
495 MOZ_ASSERT(aIndex < mItems.Length());
497 if (!ItemAt(aIndex)) {
498 ItemAt(aIndex) = DOMSVGPathSeg::CreateFor(this, aIndex, IsAnimValList());
500 RefPtr<DOMSVGPathSeg> result = ItemAt(aIndex);
501 return result.forget();
504 void DOMSVGPathSegList::MaybeInsertNullInAnimValListAt(
505 uint32_t aIndex, uint32_t aInternalIndex, uint32_t aArgCountForItem) {
506 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
508 if (!AnimListMirrorsBaseList()) {
509 return;
512 // The anim val list is in sync with the base val list
513 DOMSVGPathSegList* animVal =
514 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
516 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
517 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
518 "animVal list not in sync!");
519 MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(
520 aIndex, ItemProxy(nullptr, aInternalIndex), fallible));
522 animVal->UpdateListIndicesFromIndex(aIndex + 1, 1 + aArgCountForItem);
525 void DOMSVGPathSegList::MaybeRemoveItemFromAnimValListAt(
526 uint32_t aIndex, int32_t aArgCountForItem) {
527 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal");
529 if (!AnimListMirrorsBaseList()) {
530 return;
533 // This needs to be a strong reference; otherwise, the RemovingFromList call
534 // below might drop the last reference to animVal before we're done with it.
535 RefPtr<DOMSVGPathSegList> animVal =
536 GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
538 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal");
539 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(),
540 "animVal list not in sync!");
542 if (animVal->ItemAt(aIndex)) {
543 animVal->ItemAt(aIndex)->RemovingFromList();
545 animVal->mItems.RemoveElementAt(aIndex);
547 animVal->UpdateListIndicesFromIndex(aIndex, -(1 + aArgCountForItem));
550 void DOMSVGPathSegList::UpdateListIndicesFromIndex(
551 uint32_t aStartingIndex, int32_t aInternalDataIndexDelta) {
552 uint32_t length = mItems.Length();
554 for (uint32_t i = aStartingIndex; i < length; ++i) {
555 mItems[i].mInternalDataIndex += aInternalDataIndexDelta;
556 if (ItemAt(i)) {
557 ItemAt(i)->UpdateListIndex(i);
562 } // namespace dom
563 } // namespace mozilla