tor-browser

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

SVGAnimatedTransformList.cpp (10889B)


      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 "SVGAnimatedTransformList.h"
      8 
      9 #include <utility>
     10 
     11 #include "DOMSVGAnimatedTransformList.h"
     12 #include "SVGTransform.h"
     13 #include "SVGTransformListSMILType.h"
     14 #include "mozilla/SMILValue.h"
     15 #include "mozilla/SVGContentUtils.h"
     16 #include "mozilla/dom/SVGAnimationElement.h"
     17 #include "nsCharSeparatedTokenizer.h"
     18 #include "nsContentUtils.h"
     19 
     20 using namespace mozilla::dom;
     21 using namespace mozilla::dom::SVGTransform_Binding;
     22 
     23 namespace mozilla {
     24 
     25 nsresult SVGAnimatedTransformList::SetBaseValueString(const nsAString& aValue,
     26                                                      SVGElement* aSVGElement) {
     27  SVGTransformList newBaseValue;
     28  nsresult rv = newBaseValue.SetValueFromString(aValue);
     29  if (NS_FAILED(rv)) {
     30    return rv;
     31  }
     32 
     33  return SetBaseValue(newBaseValue, aSVGElement);
     34 }
     35 
     36 nsresult SVGAnimatedTransformList::SetBaseValue(const SVGTransformList& aValue,
     37                                                SVGElement* aSVGElement) {
     38  DOMSVGAnimatedTransformList* domWrapper =
     39      DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
     40  if (domWrapper) {
     41    // We must send this notification *before* changing mBaseVal! If the length
     42    // of our baseVal is being reduced, our baseVal's DOM wrapper list may have
     43    // to remove DOM items from itself, and any removed DOM items need to copy
     44    // their internal counterpart values *before* we change them.
     45    //
     46    domWrapper->InternalBaseValListWillChangeLengthTo(aValue.Length());
     47  }
     48 
     49  // (This bool will be copied to our member-var, if attr-change succeeds.)
     50  bool hadTransform = HasTransform();
     51 
     52  // We don't need to call DidChange* here - we're only called by
     53  // SVGElement::ParseAttribute under Element::SetAttr,
     54  // which takes care of notifying.
     55 
     56  nsresult rv = mBaseVal.CopyFrom(aValue);
     57  if (NS_FAILED(rv) && domWrapper) {
     58    // Attempting to increase mBaseVal's length failed - reduce domWrapper
     59    // back to the same length:
     60    domWrapper->InternalBaseValListWillChangeLengthTo(mBaseVal.Length());
     61  } else {
     62    mIsBaseSet = true;
     63    // We only need to treat this as a creation or removal of a transform if the
     64    // frame already exists and it didn't have an existing one.
     65    mCreatedOrRemovedOnLastChange =
     66        aSVGElement->GetPrimaryFrame() && !hadTransform;
     67  }
     68  return rv;
     69 }
     70 
     71 void SVGAnimatedTransformList::ClearBaseValue() {
     72  mCreatedOrRemovedOnLastChange = !HasTransform();
     73 
     74  DOMSVGAnimatedTransformList* domWrapper =
     75      DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
     76  if (domWrapper) {
     77    // We must send this notification *before* changing mBaseVal! (See above.)
     78    domWrapper->InternalBaseValListWillChangeLengthTo(0);
     79  }
     80  mBaseVal.Clear();
     81  mIsBaseSet = false;
     82  // Caller notifies
     83 }
     84 
     85 nsresult SVGAnimatedTransformList::SetAnimValue(const SVGTransformList& aValue,
     86                                                SVGElement* aElement) {
     87  bool prevSet = HasTransform() || aElement->GetAnimateMotionTransform();
     88  DOMSVGAnimatedTransformList* domWrapper =
     89      DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
     90  if (domWrapper) {
     91    // A new animation may totally change the number of items in the animVal
     92    // list, replacing what was essentially a mirror of the baseVal list, or
     93    // else replacing and overriding an existing animation. When this happens
     94    // we must try and keep our animVal's DOM wrapper in sync (see the comment
     95    // in DOMSVGAnimatedTransformList::InternalBaseValListWillChangeLengthTo).
     96    //
     97    // It's not possible for us to reliably distinguish between calls to this
     98    // method that are setting a new sample for an existing animation, and
     99    // calls that are setting the first sample of an animation that will
    100    // override an existing animation. Happily it's cheap to just blindly
    101    // notify our animVal's DOM wrapper of its internal counterpart's new value
    102    // each time this method is called, so that's what we do.
    103    //
    104    // Note that we must send this notification *before* setting or changing
    105    // mAnimVal! (See the comment in SetBaseValueString above.)
    106    //
    107    domWrapper->InternalAnimValListWillChangeLengthTo(aValue.Length());
    108  }
    109  if (!mAnimVal) {
    110    mAnimVal = MakeUnique<SVGTransformList>();
    111  }
    112  nsresult rv = mAnimVal->CopyFrom(aValue);
    113  if (NS_FAILED(rv)) {
    114    // OOM. We clear the animation, and, importantly, ClearAnimValue() ensures
    115    // that mAnimVal and its DOM wrapper (if any) will have the same length!
    116    ClearAnimValue(aElement);
    117    return rv;
    118  }
    119  mCreatedOrRemovedOnLastChange = !prevSet;
    120  aElement->DidAnimateTransformList();
    121  return NS_OK;
    122 }
    123 
    124 void SVGAnimatedTransformList::ClearAnimValue(SVGElement* aElement) {
    125  DOMSVGAnimatedTransformList* domWrapper =
    126      DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(this);
    127  if (domWrapper) {
    128    // When all animation ends, animVal simply mirrors baseVal, which may have
    129    // a different number of items to the last active animated value. We must
    130    // keep the length of our animVal's DOM wrapper list in sync, and again we
    131    // must do that before touching mAnimVal. See comments above.
    132    //
    133    domWrapper->InternalAnimValListWillChangeLengthTo(mBaseVal.Length());
    134  }
    135  mAnimVal = nullptr;
    136  mCreatedOrRemovedOnLastChange =
    137      !HasTransform() && !aElement->GetAnimateMotionTransform();
    138  aElement->DidAnimateTransformList();
    139 }
    140 
    141 bool SVGAnimatedTransformList::IsExplicitlySet() const {
    142  // Like other methods of this name, we need to know when a transform value has
    143  // been explicitly set.
    144  //
    145  // There are three ways an animated list can become set:
    146  // 1) Markup -- we set mIsBaseSet to true on any successful call to
    147  //    SetBaseValueString and clear it on ClearBaseValue (as called by
    148  //    SVGElement::UnsetAttr or a failed SVGElement::ParseAttribute)
    149  // 2) DOM call -- simply fetching the baseVal doesn't mean the transform value
    150  //    has been set. It is set if that baseVal has one or more transforms in
    151  //    the list.
    152  // 3) Animation -- which will cause the mAnimVal member to be allocated
    153  return mIsBaseSet || !mBaseVal.IsEmpty() || mAnimVal;
    154 }
    155 
    156 UniquePtr<SMILAttr> SVGAnimatedTransformList::ToSMILAttr(
    157    SVGElement* aSVGElement) {
    158  return MakeUnique<SMILAnimatedTransformList>(this, aSVGElement);
    159 }
    160 
    161 nsresult SVGAnimatedTransformList::SMILAnimatedTransformList::ValueFromString(
    162    const nsAString& aStr, const dom::SVGAnimationElement* aSrcElement,
    163    SMILValue& aValue, bool& aPreventCachingOfSandwich) const {
    164  NS_ENSURE_TRUE(aSrcElement, NS_ERROR_FAILURE);
    165  MOZ_ASSERT(aValue.IsNull(),
    166             "aValue should have been cleared before calling ValueFromString");
    167 
    168  const nsAttrValue* typeAttr = aSrcElement->GetParsedAttr(nsGkAtoms::type);
    169  const nsAtom* transformType = nsGkAtoms::translate;  // default val
    170  if (typeAttr) {
    171    if (typeAttr->Type() != nsAttrValue::eAtom) {
    172      // Recognized values of |type| are parsed as an atom -- so if we have
    173      // something other than an atom, then we know already our |type| is
    174      // invalid.
    175      return NS_ERROR_FAILURE;
    176    }
    177    transformType = typeAttr->GetAtomValue();
    178  }
    179 
    180  ParseValue(aStr, transformType, aValue);
    181  return aValue.IsNull() ? NS_ERROR_FAILURE : NS_OK;
    182 }
    183 
    184 void SVGAnimatedTransformList::SMILAnimatedTransformList::ParseValue(
    185    const nsAString& aSpec, const nsAtom* aTransformType, SMILValue& aResult) {
    186  MOZ_ASSERT(aResult.IsNull(), "Unexpected type for SMIL value");
    187 
    188  static_assert(SVGTransformSMILData::NUM_SIMPLE_PARAMS == 3,
    189                "SVGSMILTransform constructor should be expecting array "
    190                "with 3 params");
    191 
    192  float params[3] = {0.f};
    193  int32_t numParsed = ParseParameterList(aSpec, params, 3);
    194  uint16_t transformType;
    195 
    196  if (aTransformType == nsGkAtoms::translate) {
    197    // tx [ty=0]
    198    if (numParsed != 1 && numParsed != 2) return;
    199    transformType = SVG_TRANSFORM_TRANSLATE;
    200  } else if (aTransformType == nsGkAtoms::scale) {
    201    // sx [sy=sx]
    202    if (numParsed != 1 && numParsed != 2) return;
    203    if (numParsed == 1) {
    204      params[1] = params[0];
    205    }
    206    transformType = SVG_TRANSFORM_SCALE;
    207  } else if (aTransformType == nsGkAtoms::rotate) {
    208    // r [cx=0 cy=0]
    209    if (numParsed != 1 && numParsed != 3) return;
    210    transformType = SVG_TRANSFORM_ROTATE;
    211  } else if (aTransformType == nsGkAtoms::skewX) {
    212    // x-angle
    213    if (numParsed != 1) return;
    214    transformType = SVG_TRANSFORM_SKEWX;
    215  } else if (aTransformType == nsGkAtoms::skewY) {
    216    // y-angle
    217    if (numParsed != 1) return;
    218    transformType = SVG_TRANSFORM_SKEWY;
    219  } else {
    220    return;
    221  }
    222 
    223  SMILValue val(SVGTransformListSMILType::Singleton());
    224  SVGTransformSMILData transform(transformType, params);
    225  if (NS_FAILED(SVGTransformListSMILType::AppendTransform(transform, val))) {
    226    return;  // OOM
    227  }
    228 
    229  // Success! Populate our outparam with parsed value.
    230  aResult = std::move(val);
    231 }
    232 
    233 int32_t SVGAnimatedTransformList::SMILAnimatedTransformList::ParseParameterList(
    234    const nsAString& aSpec, float* aVars, int32_t aNVars) {
    235  int numArgsFound = 0;
    236 
    237  for (const auto& token :
    238       nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace,
    239                                        nsTokenizerFlags::SeparatorOptional>(
    240           aSpec, ',')
    241           .ToRange()) {
    242    float f;
    243    if (!SVGContentUtils::ParseNumber(token, f)) {
    244      return -1;
    245    }
    246    if (numArgsFound < aNVars) {
    247      aVars[numArgsFound] = f;
    248    }
    249    numArgsFound++;
    250  }
    251  return numArgsFound;
    252 }
    253 
    254 SMILValue SVGAnimatedTransformList::SMILAnimatedTransformList::GetBaseValue()
    255    const {
    256  // To benefit from Return Value Optimization and avoid copy constructor calls
    257  // due to our use of return-by-value, we must return the exact same object
    258  // from ALL return points. This function must only return THIS variable:
    259  SMILValue val(SVGTransformListSMILType::Singleton());
    260  if (!SVGTransformListSMILType::AppendTransforms(mVal->mBaseVal, val)) {
    261    val = SMILValue();
    262  }
    263 
    264  return val;
    265 }
    266 
    267 nsresult SVGAnimatedTransformList::SMILAnimatedTransformList::SetAnimValue(
    268    const SMILValue& aNewAnimValue) {
    269  MOZ_ASSERT(aNewAnimValue.mType == SVGTransformListSMILType::Singleton(),
    270             "Unexpected type to assign animated value");
    271  SVGTransformList animVal;
    272  if (!SVGTransformListSMILType::GetTransforms(aNewAnimValue, animVal.mItems)) {
    273    return NS_ERROR_FAILURE;
    274  }
    275 
    276  return mVal->SetAnimValue(animVal, mElement);
    277 }
    278 
    279 void SVGAnimatedTransformList::SMILAnimatedTransformList::ClearAnimValue() {
    280  if (mVal->mAnimVal) {
    281    mVal->ClearAnimValue(mElement);
    282  }
    283 }
    284 
    285 }  // namespace mozilla