tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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