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