DOMSVGTransformList.cpp (13174B)
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 "DOMSVGTransformList.h" 8 9 #include <algorithm> 10 11 #include "DOMSVGTransform.h" 12 #include "SVGAnimatedTransformList.h" 13 #include "mozilla/dom/SVGElement.h" 14 #include "mozilla/dom/SVGMatrix.h" 15 #include "mozilla/dom/SVGTransformListBinding.h" 16 #include "nsError.h" 17 18 // local helper functions 19 namespace { 20 21 void UpdateListIndicesFromIndex( 22 FallibleTArray<mozilla::dom::DOMSVGTransform*>& aItemsArray, 23 uint32_t aStartingIndex) { 24 uint32_t length = aItemsArray.Length(); 25 26 for (uint32_t i = aStartingIndex; i < length; ++i) { 27 if (aItemsArray[i]) { 28 aItemsArray[i]->UpdateListIndex(i); 29 } 30 } 31 } 32 33 } // namespace 34 35 namespace mozilla::dom { 36 37 // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to 38 // clear our SVGAnimatedTransformList's weak ref to us to be safe. (The other 39 // option would be to not unlink and rely on the breaking of the other edges in 40 // the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) 41 NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGTransformList) 42 43 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGTransformList) 44 if (tmp->mAList) { 45 if (tmp->IsAnimValList()) { 46 tmp->mAList->mAnimVal = nullptr; 47 } else { 48 tmp->mAList->mBaseVal = nullptr; 49 } 50 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAList) 51 } 52 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 53 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGTransformList) 55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAList) 56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 57 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGTransformList) 58 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER 59 NS_IMPL_CYCLE_COLLECTION_TRACE_END 60 61 NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGTransformList) 62 NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGTransformList) 63 64 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGTransformList) 65 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 66 NS_INTERFACE_MAP_ENTRY(nsISupports) 67 NS_INTERFACE_MAP_END 68 69 //---------------------------------------------------------------------- 70 // DOMSVGTransformList methods: 71 72 JSObject* DOMSVGTransformList::WrapObject(JSContext* cx, 73 JS::Handle<JSObject*> aGivenProto) { 74 return mozilla::dom::SVGTransformList_Binding::Wrap(cx, this, aGivenProto); 75 } 76 77 void DOMSVGTransformList::InternalListLengthWillChange(uint32_t aNewLength) { 78 uint32_t oldLength = mItems.Length(); 79 80 if (aNewLength > DOMSVGTransform::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 = DOMSVGTransform::MaxListIndex(); 84 } 85 86 RefPtr<DOMSVGTransformList> 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 SVGTransformList& DOMSVGTransformList::InternalList() const { 114 SVGAnimatedTransformList* alist = Element()->GetAnimatedTransformList(); 115 return IsAnimValList() && alist->mAnimVal ? *alist->mAnimVal 116 : alist->mBaseVal; 117 } 118 119 //---------------------------------------------------------------------- 120 void DOMSVGTransformList::Clear(ErrorResult& error) { 121 if (IsAnimValList()) { 122 error.ThrowNoModificationAllowedError("Animated values cannot be set"); 123 return; 124 } 125 126 if (LengthNoFlush() > 0) { 127 AutoChangeTransformListNotifier notifier(this); 128 // Notify any existing DOM items of removal *before* truncating the lists 129 // so that they can find their DOMSVGTransform internal counterparts and 130 // copy their values. This also notifies the animVal list: 131 mAList->InternalBaseValListWillChangeLengthTo(0); 132 133 mItems.Clear(); 134 auto* alist = Element()->GetAnimatedTransformList(); 135 alist->mBaseVal.Clear(); 136 alist->mIsBaseSet = false; 137 } 138 } 139 140 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::Initialize( 141 DOMSVGTransform& newItem, ErrorResult& error) { 142 if (IsAnimValList()) { 143 error.ThrowNoModificationAllowedError("Animated values cannot be set"); 144 return nullptr; 145 } 146 147 // If newItem is already in a list we should insert a clone of newItem, and 148 // for consistency, this should happen even if *this* is the list that 149 // newItem is currently in. Note that in the case of newItem being in this 150 // list, the Clear() call before the InsertItemBefore() call would remove it 151 // from this list, and so the InsertItemBefore() call would not insert a 152 // clone of newItem, it would actually insert newItem. To prevent that from 153 // happening we have to do the clone here, if necessary. 154 155 RefPtr<DOMSVGTransform> domItem = &newItem; 156 if (domItem->HasOwner()) { 157 domItem = newItem.Clone(); 158 } 159 160 Clear(error); 161 MOZ_ASSERT(!error.Failed(), "How could this fail?"); 162 return InsertItemBefore(*domItem, 0, error); 163 } 164 165 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::GetItem( 166 uint32_t index, ErrorResult& error) { 167 bool found; 168 RefPtr<DOMSVGTransform> item = IndexedGetter(index, found, error); 169 if (!found) { 170 error.ThrowIndexSizeError("Index out of range"); 171 } 172 return item.forget(); 173 } 174 175 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::IndexedGetter( 176 uint32_t index, bool& found, ErrorResult& error) { 177 if (IsAnimValList()) { 178 Element()->FlushAnimations(); 179 } 180 found = index < LengthNoFlush(); 181 if (found) { 182 return GetItemAt(index); 183 } 184 return nullptr; 185 } 186 187 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::InsertItemBefore( 188 DOMSVGTransform& newItem, uint32_t index, ErrorResult& error) { 189 if (IsAnimValList()) { 190 error.ThrowNoModificationAllowedError("Animated values cannot be set"); 191 return nullptr; 192 } 193 194 index = std::min(index, LengthNoFlush()); 195 if (index >= DOMSVGTransform::MaxListIndex()) { 196 error.ThrowIndexSizeError("Index out of range"); 197 return nullptr; 198 } 199 200 RefPtr<DOMSVGTransform> domItem = &newItem; 201 if (newItem.HasOwner()) { 202 domItem = newItem.Clone(); // must do this before changing anything! 203 } 204 205 // Ensure we have enough memory so we can avoid complex error handling below: 206 if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || 207 !InternalList().SetCapacity(InternalList().Length() + 1)) { 208 error.Throw(NS_ERROR_OUT_OF_MEMORY); 209 return nullptr; 210 } 211 if (AnimListMirrorsBaseList()) { 212 if (!mAList->mAnimVal->mItems.SetCapacity( 213 mAList->mAnimVal->mItems.Length() + 1, fallible)) { 214 error.Throw(NS_ERROR_OUT_OF_MEMORY); 215 return nullptr; 216 } 217 } 218 219 AutoChangeTransformListNotifier notifier(this); 220 // Now that we know we're inserting, keep animVal list in sync as necessary. 221 MaybeInsertNullInAnimValListAt(index); 222 223 InternalList().InsertItem(index, domItem->ToSVGTransform()); 224 MOZ_ALWAYS_TRUE(mItems.InsertElementAt(index, domItem.get(), fallible)); 225 226 // This MUST come after the insertion into InternalList(), or else under the 227 // insertion into InternalList() the values read from domItem would be bad 228 // data from InternalList() itself!: 229 domItem->InsertingIntoList(this, index, IsAnimValList()); 230 231 UpdateListIndicesFromIndex(mItems, index + 1); 232 233 return domItem.forget(); 234 } 235 236 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::ReplaceItem( 237 DOMSVGTransform& newItem, uint32_t index, ErrorResult& error) { 238 if (IsAnimValList()) { 239 error.ThrowNoModificationAllowedError("Animated values cannot be set"); 240 return nullptr; 241 } 242 243 if (index >= LengthNoFlush()) { 244 error.ThrowIndexSizeError("Index out of range"); 245 return nullptr; 246 } 247 248 RefPtr<DOMSVGTransform> domItem = &newItem; 249 if (newItem.HasOwner()) { 250 domItem = newItem.Clone(); // must do this before changing anything! 251 } 252 253 AutoChangeTransformListNotifier notifier(this); 254 if (mItems[index]) { 255 // Notify any existing DOM item of removal *before* modifying the lists so 256 // that the DOM item can copy the *old* value at its index: 257 mItems[index]->RemovingFromList(); 258 } 259 260 InternalList()[index] = domItem->ToSVGTransform(); 261 mItems[index] = domItem; 262 263 // This MUST come after the ToSVGPoint() call, otherwise that call 264 // would end up reading bad data from InternalList()! 265 domItem->InsertingIntoList(this, index, IsAnimValList()); 266 267 return domItem.forget(); 268 } 269 270 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::RemoveItem( 271 uint32_t index, ErrorResult& error) { 272 if (IsAnimValList()) { 273 error.ThrowNoModificationAllowedError("Animated values cannot be set"); 274 return nullptr; 275 } 276 277 if (index >= LengthNoFlush()) { 278 error.ThrowIndexSizeError("Index out of range"); 279 return nullptr; 280 } 281 282 AutoChangeTransformListNotifier notifier(this); 283 // Now that we know we're removing, keep animVal list in sync as necessary. 284 // Do this *before* touching InternalList() so the removed item can get its 285 // internal value. 286 MaybeRemoveItemFromAnimValListAt(index); 287 288 // We have to return the removed item, so get it, creating it if necessary: 289 RefPtr<DOMSVGTransform> result = GetItemAt(index); 290 291 // Notify the DOM item of removal *before* modifying the lists so that the 292 // DOM item can copy its *old* value: 293 result->RemovingFromList(); 294 295 InternalList().RemoveItem(index); 296 mItems.RemoveElementAt(index); 297 298 UpdateListIndicesFromIndex(mItems, index); 299 300 return result.forget(); 301 } 302 303 already_AddRefed<DOMSVGTransform> 304 DOMSVGTransformList::CreateSVGTransformFromMatrix(const DOMMatrix2DInit& matrix, 305 ErrorResult& aRv) { 306 RefPtr<DOMSVGTransform> result = new DOMSVGTransform(matrix, aRv); 307 return result.forget(); 308 } 309 310 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::Consolidate( 311 ErrorResult& error) { 312 if (IsAnimValList()) { 313 error.ThrowNoModificationAllowedError("Animated values cannot be set"); 314 return nullptr; 315 } 316 317 if (LengthNoFlush() == 0) { 318 return nullptr; 319 } 320 321 // Note that SVG 1.1 says, "The consolidation operation creates new 322 // SVGTransform object as the first and only item in the list" hence, even if 323 // LengthNoFlush() == 1 we can't return that one item (after making it a 324 // matrix type). We must orphan the existing item and then make a new one. 325 326 // First calculate our matrix 327 gfxMatrix mx = InternalList().GetConsolidationMatrix(); 328 329 // Then orphan the existing items 330 Clear(error); 331 MOZ_ASSERT(!error.Failed(), "How could this fail?"); 332 333 // And append the new transform 334 RefPtr<DOMSVGTransform> transform = new DOMSVGTransform(mx); 335 return InsertItemBefore(*transform, LengthNoFlush(), error); 336 } 337 338 //---------------------------------------------------------------------- 339 // Implementation helpers: 340 341 already_AddRefed<DOMSVGTransform> DOMSVGTransformList::GetItemAt( 342 uint32_t aIndex) { 343 MOZ_ASSERT(aIndex < mItems.Length()); 344 345 if (!mItems[aIndex]) { 346 mItems[aIndex] = new DOMSVGTransform(this, aIndex, IsAnimValList()); 347 } 348 RefPtr<DOMSVGTransform> result = mItems[aIndex]; 349 return result.forget(); 350 } 351 352 void DOMSVGTransformList::MaybeInsertNullInAnimValListAt(uint32_t aIndex) { 353 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); 354 355 if (!AnimListMirrorsBaseList()) { 356 return; 357 } 358 359 DOMSVGTransformList* animVal = mAList->mAnimVal; 360 361 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); 362 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), 363 "animVal list not in sync!"); 364 MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, nullptr, fallible)); 365 366 UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1); 367 } 368 369 void DOMSVGTransformList::MaybeRemoveItemFromAnimValListAt(uint32_t aIndex) { 370 MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); 371 372 if (!AnimListMirrorsBaseList()) { 373 return; 374 } 375 376 // This needs to be a strong reference; otherwise, the RemovingFromList call 377 // below might drop the last reference to animVal before we're done with it. 378 RefPtr<DOMSVGTransformList> animVal = mAList->mAnimVal; 379 380 MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); 381 MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), 382 "animVal list not in sync!"); 383 384 if (animVal->mItems[aIndex]) { 385 animVal->mItems[aIndex]->RemovingFromList(); 386 } 387 animVal->mItems.RemoveElementAt(aIndex); 388 389 UpdateListIndicesFromIndex(animVal->mItems, aIndex); 390 } 391 392 } // namespace mozilla::dom