DOMSVGNumberList.cpp (11619B)
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 "DOMSVGNumberList.h" 8 9 #include <algorithm> 10 11 #include "DOMSVGNumber.h" 12 #include "SVGAnimatedNumberList.h" 13 #include "SVGElement.h" 14 #include "mozilla/RefPtr.h" 15 #include "mozilla/dom/SVGNumberListBinding.h" 16 #include "nsError.h" 17 18 // See the comment in this file's header. 19 20 // local helper functions 21 namespace { 22 23 using mozilla::dom::DOMSVGNumber; 24 25 void UpdateListIndicesFromIndex(FallibleTArray<DOMSVGNumber*>& aItemsArray, 26 uint32_t aStartingIndex) { 27 uint32_t length = aItemsArray.Length(); 28 29 for (uint32_t i = aStartingIndex; i < length; ++i) { 30 if (aItemsArray[i]) { 31 aItemsArray[i]->UpdateListIndex(i); 32 } 33 } 34 } 35 36 } // namespace 37 38 namespace mozilla::dom { 39 40 // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to 41 // clear our DOMSVGAnimatedNumberList's weak ref to us to be safe. (The other 42 // option would be to not unlink and rely on the breaking of the other edges in 43 // the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) 44 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGNumberList) 45 46 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGNumberList) 47 if (tmp->mAList) { 48 if (tmp->IsAnimValList()) { 49 tmp->mAList->mAnimVal = nullptr; 50 } else { 51 tmp->mAList->mBaseVal = nullptr; 52 } 53 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAList) 54 } 55 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 56 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGNumberList) 58 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAList) 59 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 60 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGNumberList) 61 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER 62 NS_IMPL_CYCLE_COLLECTION_TRACE_END 63 64 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGNumberList) 65 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGNumberList) 66 67 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGNumberList) 68 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 69 NS_INTERFACE_MAP_ENTRY(nsISupports) 70 NS_INTERFACE_MAP_END 71 72 JSObject* DOMSVGNumberList::WrapObject(JSContext* cx, 73 JS::Handle<JSObject*> aGivenProto) { 74 return mozilla::dom::SVGNumberList_Binding::Wrap(cx, this, aGivenProto); 75 } 76 77 void DOMSVGNumberList::InternalListLengthWillChange(uint32_t aNewLength) { 78 uint32_t oldLength = mItems.Length(); 79 80 if (aNewLength > DOMSVGNumber::MaxListIndex()) { 81 // It's safe to get out of sync with our internal list as long as we have 82 // FEWER items than it does. 83 aNewLength = DOMSVGNumber::MaxListIndex(); 84 } 85 86 RefPtr<DOMSVGNumberList> kungFuDeathGrip; 87 if (aNewLength < oldLength) { 88 // RemovingFromList() might clear last reference to |this|. 89 // Retain a temporary reference to keep from dying before returning. 90 kungFuDeathGrip = this; 91 } 92 93 // If our length will decrease, notify the items that will be removed: 94 for (uint32_t i = aNewLength; i < oldLength; ++i) { 95 if (mItems[i]) { 96 mItems[i]->RemovingFromList(); 97 } 98 } 99 100 if (!mItems.SetLength(aNewLength, fallible)) { 101 // We silently ignore SetLength OOM failure since being out of sync is safe 102 // so long as we have *fewer* items than our internal list. 103 mItems.Clear(); 104 return; 105 } 106 107 // If our length has increased, null out the new pointers: 108 for (uint32_t i = oldLength; i < aNewLength; ++i) { 109 mItems[i] = nullptr; 110 } 111 } 112 113 SVGNumberList& DOMSVGNumberList::InternalList() const { 114 SVGAnimatedNumberList* alist = Element()->GetAnimatedNumberList(AttrEnum()); 115 return IsAnimValList() && alist->mAnimVal ? *alist->mAnimVal 116 : alist->mBaseVal; 117 } 118 119 void DOMSVGNumberList::Clear(ErrorResult& aRv) { 120 if (IsAnimValList()) { 121 aRv.ThrowNoModificationAllowedError("Animated values cannot be set"); 122 return; 123 } 124 125 if (LengthNoFlush() > 0) { 126 AutoChangeNumberListNotifier notifier(this); 127 // Notify any existing DOM items of removal *before* truncating the lists 128 // so that they can find their SVGNumber internal counterparts and copy 129 // their values. This also notifies the animVal list: 130 mAList->InternalBaseValListWillChangeTo(SVGNumberList()); 131 132 mItems.Clear(); 133 auto* alist = Element()->GetAnimatedNumberList(AttrEnum()); 134 alist->mBaseVal.Clear(); 135 alist->mIsBaseSet = false; 136 } 137 } 138 139 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::Initialize(DOMSVGNumber& aItem, 140 ErrorResult& aRv) { 141 if (IsAnimValList()) { 142 aRv.ThrowNoModificationAllowedError("Animated values cannot be set"); 143 return nullptr; 144 } 145 146 // If newItem is already in a list we should insert a clone of newItem, and 147 // for consistency, this should happen even if *this* is the list that 148 // newItem is currently in. Note that in the case of newItem being in this 149 // list, the Clear() call before the InsertItemBefore() call would remove it 150 // from this list, and so the InsertItemBefore() call would not insert a 151 // clone of newItem, it would actually insert newItem. To prevent that from 152 // happening we have to do the clone here, if necessary. 153 RefPtr<DOMSVGNumber> domItem = aItem.HasOwner() ? aItem.Clone() : &aItem; 154 155 Clear(aRv); 156 MOZ_ASSERT(!aRv.Failed()); 157 return InsertItemBefore(*domItem, 0, aRv); 158 } 159 160 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::GetItem(uint32_t index, 161 ErrorResult& aRv) { 162 bool found; 163 RefPtr<DOMSVGNumber> item = IndexedGetter(index, found, aRv); 164 if (!found) { 165 aRv.ThrowIndexSizeError("Index out of range"); 166 } 167 return item.forget(); 168 } 169 170 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::IndexedGetter( 171 uint32_t index, bool& found, ErrorResult& aRv) { 172 if (IsAnimValList()) { 173 Element()->FlushAnimations(); 174 } 175 found = index < LengthNoFlush(); 176 if (found) { 177 return GetItemAt(index); 178 } 179 return nullptr; 180 } 181 182 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::InsertItemBefore( 183 DOMSVGNumber& aItem, uint32_t index, ErrorResult& aRv) { 184 if (IsAnimValList()) { 185 aRv.ThrowNoModificationAllowedError("Animated values cannot be set"); 186 return nullptr; 187 } 188 189 index = std::min(index, LengthNoFlush()); 190 if (index >= DOMSVGNumber::MaxListIndex()) { 191 aRv.ThrowIndexSizeError("Index out of range"); 192 return nullptr; 193 } 194 195 // must do this before changing anything! 196 RefPtr<DOMSVGNumber> domItem = aItem.HasOwner() ? aItem.Clone() : &aItem; 197 198 // Ensure we have enough memory so we can avoid complex error handling below: 199 if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || 200 !InternalList().SetCapacity(InternalList().Length() + 1)) { 201 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 202 return nullptr; 203 } 204 if (AnimListMirrorsBaseList()) { 205 if (!mAList->mAnimVal->mItems.SetCapacity( 206 mAList->mAnimVal->mItems.Length() + 1, fallible)) { 207 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 208 return nullptr; 209 } 210 } 211 212 AutoChangeNumberListNotifier notifier(this); 213 // Now that we know we're inserting, keep animVal list in sync as necessary. 214 MaybeInsertNullInAnimValListAt(index); 215 216 InternalList().InsertItem(index, domItem->ToSVGNumber()); 217 MOZ_ALWAYS_TRUE(mItems.InsertElementAt(index, domItem, fallible)); 218 219 // This MUST come after the insertion into InternalList(), or else under the 220 // insertion into InternalList() the values read from domItem would be bad 221 // data from InternalList() itself!: 222 domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList()); 223 224 UpdateListIndicesFromIndex(mItems, index + 1); 225 226 return domItem.forget(); 227 } 228 229 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::ReplaceItem( 230 DOMSVGNumber& aItem, uint32_t index, ErrorResult& aRv) { 231 if (IsAnimValList()) { 232 aRv.ThrowNoModificationAllowedError("Animated values cannot be set"); 233 return nullptr; 234 } 235 236 if (index >= LengthNoFlush()) { 237 aRv.ThrowIndexSizeError("Index out of range"); 238 return nullptr; 239 } 240 241 // must do this before changing anything! 242 RefPtr<DOMSVGNumber> domItem = aItem.HasOwner() ? aItem.Clone() : &aItem; 243 244 AutoChangeNumberListNotifier notifier(this); 245 if (mItems[index]) { 246 // Notify any existing DOM item of removal *before* modifying the lists so 247 // that the DOM item can copy the *old* value at its index: 248 mItems[index]->RemovingFromList(); 249 } 250 251 InternalList()[index] = domItem->ToSVGNumber(); 252 mItems[index] = domItem; 253 254 // This MUST come after the ToSVGPoint() call, otherwise that call 255 // would end up reading bad data from InternalList()! 256 domItem->InsertingIntoList(this, AttrEnum(), index, IsAnimValList()); 257 258 return domItem.forget(); 259 } 260 261 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::RemoveItem(uint32_t index, 262 ErrorResult& aRv) { 263 if (IsAnimValList()) { 264 aRv.ThrowNoModificationAllowedError("Animated values cannot be set"); 265 return nullptr; 266 } 267 268 if (index >= LengthNoFlush()) { 269 aRv.ThrowIndexSizeError("Index out of range"); 270 return nullptr; 271 } 272 273 // Now that we know we're removing, keep animVal list in sync as necessary. 274 // Do this *before* touching InternalList() so the removed item can get its 275 // internal value. 276 MaybeRemoveItemFromAnimValListAt(index); 277 278 // We have to return the removed item, so get it, creating it if necessary: 279 RefPtr<DOMSVGNumber> result = GetItemAt(index); 280 281 AutoChangeNumberListNotifier notifier(this); 282 // Notify the DOM item of removal *before* modifying the lists so that the 283 // DOM item can copy its *old* value: 284 mItems[index]->RemovingFromList(); 285 286 InternalList().RemoveItem(index); 287 mItems.RemoveElementAt(index); 288 289 UpdateListIndicesFromIndex(mItems, index); 290 291 return result.forget(); 292 } 293 294 already_AddRefed<DOMSVGNumber> DOMSVGNumberList::GetItemAt(uint32_t aIndex) { 295 MOZ_ASSERT(aIndex < mItems.Length()); 296 297 if (!mItems[aIndex]) { 298 mItems[aIndex] = 299 new DOMSVGNumber(this, AttrEnum(), aIndex, IsAnimValList()); 300 } 301 RefPtr<DOMSVGNumber> result = mItems[aIndex]; 302 return result.forget(); 303 } 304 305 void DOMSVGNumberList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) { 306 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); 307 308 if (!AnimListMirrorsBaseList()) { 309 return; 310 } 311 312 DOMSVGNumberList* animVal = mAList->mAnimVal; 313 314 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); 315 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), 316 "animVal list not in sync!"); 317 MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible)); 318 319 UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1); 320 } 321 322 void DOMSVGNumberList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) { 323 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); 324 325 if (!AnimListMirrorsBaseList()) { 326 return; 327 } 328 329 // This needs to be a strong reference; otherwise, the RemovingFromList call 330 // below might drop the last reference to animVal before we're done with it. 331 RefPtr<DOMSVGNumberList> animVal = mAList->mAnimVal; 332 333 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); 334 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), 335 "animVal list not in sync!"); 336 337 if (animVal->mItems[aIndex]) { 338 animVal->mItems[aIndex]->RemovingFromList(); 339 } 340 animVal->mItems.RemoveElementAt(aIndex); 341 342 UpdateListIndicesFromIndex(animVal->mItems, aIndex); 343 } 344 345 } // namespace mozilla::dom