Coverage Report

Created: 2018-09-25 14:53

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