Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/svg/SVGTransform.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 "SVGTransform.h"
8
9
#include "mozilla/dom/SVGTransform.h"
10
#include "mozilla/dom/SVGMatrix.h"
11
#include "mozilla/dom/SVGTransformBinding.h"
12
#include "nsError.h"
13
#include "nsSVGAnimatedTransformList.h"
14
#include "nsSVGAttrTearoffTable.h"
15
#include "mozilla/DebugOnly.h"
16
#include "mozilla/FloatingPoint.h"
17
18
namespace {
19
  const double kRadPerDegree = 2.0 * M_PI / 360.0;
20
} // namespace
21
22
namespace mozilla {
23
namespace dom {
24
25
using namespace SVGTransform_Binding;
26
27
static nsSVGAttrTearoffTable<SVGTransform, SVGMatrix>&
28
SVGMatrixTearoffTable()
29
0
{
30
0
  static nsSVGAttrTearoffTable<SVGTransform, SVGMatrix> sSVGMatrixTearoffTable;
31
0
  return sSVGMatrixTearoffTable;
32
0
}
33
34
//----------------------------------------------------------------------
35
36
// We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to
37
// clear our list's weak ref to us to be safe. (The other option would be to
38
// not unlink and rely on the breaking of the other edges in the cycle, as
39
// NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.)
40
NS_IMPL_CYCLE_COLLECTION_CLASS(SVGTransform)
41
42
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGTransform)
43
0
  // We may not belong to a list, so we must null check tmp->mList.
44
0
  if (tmp->mList) {
45
0
    tmp->mList->mItems[tmp->mListIndex] = nullptr;
46
0
  }
47
0
NS_IMPL_CYCLE_COLLECTION_UNLINK(mList)
48
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
49
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
50
51
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGTransform)
52
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList)
53
0
  SVGMatrix* matrix =
54
0
    SVGMatrixTearoffTable().GetTearoff(tmp);
55
0
  CycleCollectionNoteChild(cb, matrix, "matrix");
56
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
57
58
0
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SVGTransform)
59
0
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
60
0
NS_IMPL_CYCLE_COLLECTION_TRACE_END
61
62
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(SVGTransform, AddRef)
63
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(SVGTransform, Release)
64
65
JSObject*
66
SVGTransform::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
67
0
{
68
0
  return SVGTransform_Binding::Wrap(aCx, this, aGivenProto);
69
0
}
70
71
//----------------------------------------------------------------------
72
// Helper class: AutoChangeTransformNotifier
73
// Stack-based helper class to pair calls to WillChangeTransformList
74
// and DidChangeTransformList.
75
class MOZ_RAII AutoChangeTransformNotifier
76
{
77
public:
78
  explicit AutoChangeTransformNotifier(SVGTransform* aTransform MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
79
    : mTransform(aTransform)
80
0
  {
81
0
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
82
0
    MOZ_ASSERT(mTransform, "Expecting non-null transform");
83
0
    if (mTransform->HasOwner()) {
84
0
      mEmptyOrOldValue =
85
0
        mTransform->Element()->WillChangeTransformList();
86
0
    }
87
0
  }
88
89
  ~AutoChangeTransformNotifier()
90
0
  {
91
0
    if (mTransform->HasOwner()) {
92
0
      mTransform->Element()->DidChangeTransformList(mEmptyOrOldValue);
93
0
      if (mTransform->mList->IsAnimating()) {
94
0
        mTransform->Element()->AnimationNeedsResample();
95
0
      }
96
0
    }
97
0
  }
98
99
private:
100
  SVGTransform* const mTransform;
101
  nsAttrValue   mEmptyOrOldValue;
102
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
103
};
104
105
//----------------------------------------------------------------------
106
// Ctors:
107
108
SVGTransform::SVGTransform(DOMSVGTransformList *aList,
109
                           uint32_t aListIndex,
110
                           bool aIsAnimValItem)
111
  : mList(aList)
112
  , mListIndex(aListIndex)
113
  , mIsAnimValItem(aIsAnimValItem)
114
  , mTransform(nullptr)
115
0
{
116
0
  // These shifts are in sync with the members in the header.
117
0
  MOZ_ASSERT(aList && aListIndex <= MaxListIndex(), "bad arg");
118
0
119
0
  MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!");
120
0
}
121
122
SVGTransform::SVGTransform()
123
  : mList(nullptr)
124
  , mListIndex(0)
125
  , mIsAnimValItem(false)
126
  , mTransform(new nsSVGTransform()) // Default ctor for objects not in a list
127
                                     // initialises to matrix type with identity
128
                                     // matrix
129
0
{
130
0
}
131
132
SVGTransform::SVGTransform(const gfxMatrix &aMatrix)
133
  : mList(nullptr)
134
  , mListIndex(0)
135
  , mIsAnimValItem(false)
136
  , mTransform(new nsSVGTransform(aMatrix))
137
0
{
138
0
}
139
140
SVGTransform::SVGTransform(const nsSVGTransform &aTransform)
141
  : mList(nullptr)
142
  , mListIndex(0)
143
  , mIsAnimValItem(false)
144
  , mTransform(new nsSVGTransform(aTransform))
145
0
{
146
0
}
147
148
SVGTransform::~SVGTransform()
149
0
{
150
0
  SVGMatrix* matrix = SVGMatrixTearoffTable().GetTearoff(this);
151
0
  if (matrix) {
152
0
    SVGMatrixTearoffTable().RemoveTearoff(this);
153
0
    NS_RELEASE(matrix);
154
0
  }
155
0
  // Our mList's weak ref to us must be nulled out when we die. If GC has
156
0
  // unlinked us using the cycle collector code, then that has already
157
0
  // happened, and mList is null.
158
0
  if (mList) {
159
0
    mList->mItems[mListIndex] = nullptr;
160
0
  }
161
0
}
162
163
uint16_t
164
SVGTransform::Type() const
165
0
{
166
0
  return Transform().Type();
167
0
}
168
169
SVGMatrix*
170
SVGTransform::GetMatrix()
171
0
{
172
0
  SVGMatrix* wrapper =
173
0
    SVGMatrixTearoffTable().GetTearoff(this);
174
0
  if (!wrapper) {
175
0
    NS_ADDREF(wrapper = new SVGMatrix(*this));
176
0
    SVGMatrixTearoffTable().AddTearoff(this, wrapper);
177
0
  }
178
0
  return wrapper;
179
0
}
180
181
float
182
SVGTransform::Angle() const
183
0
{
184
0
  return Transform().Angle();
185
0
}
186
187
void
188
SVGTransform::SetMatrix(SVGMatrix& aMatrix, ErrorResult& rv)
189
0
{
190
0
  if (mIsAnimValItem) {
191
0
    rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
192
0
    return;
193
0
  }
194
0
  SetMatrix(aMatrix.GetMatrix());
195
0
}
196
197
void
198
SVGTransform::SetTranslate(float tx, float ty, ErrorResult& rv)
199
0
{
200
0
  if (mIsAnimValItem) {
201
0
    rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
202
0
    return;
203
0
  }
204
0
205
0
  if (Transform().Type() == SVG_TRANSFORM_TRANSLATE &&
206
0
      Matrixgfx()._31 == tx && Matrixgfx()._32 == ty) {
207
0
    return;
208
0
  }
209
0
210
0
  AutoChangeTransformNotifier notifier(this);
211
0
  Transform().SetTranslate(tx, ty);
212
0
}
213
214
void
215
SVGTransform::SetScale(float sx, float sy, ErrorResult& rv)
216
0
{
217
0
  if (mIsAnimValItem) {
218
0
    rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
219
0
    return;
220
0
  }
221
0
222
0
  if (Transform().Type() == SVG_TRANSFORM_SCALE &&
223
0
      Matrixgfx()._11 == sx && Matrixgfx()._22 == sy) {
224
0
    return;
225
0
  }
226
0
  AutoChangeTransformNotifier notifier(this);
227
0
  Transform().SetScale(sx, sy);
228
0
}
229
230
void
231
SVGTransform::SetRotate(float angle, float cx, float cy, ErrorResult& rv)
232
0
{
233
0
  if (mIsAnimValItem) {
234
0
    rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
235
0
    return;
236
0
  }
237
0
238
0
  if (Transform().Type() == SVG_TRANSFORM_ROTATE) {
239
0
    float currentCx, currentCy;
240
0
    Transform().GetRotationOrigin(currentCx, currentCy);
241
0
    if (Transform().Angle() == angle && currentCx == cx && currentCy == cy) {
242
0
      return;
243
0
    }
244
0
  }
245
0
246
0
  AutoChangeTransformNotifier notifier(this);
247
0
  Transform().SetRotate(angle, cx, cy);
248
0
}
249
250
void
251
SVGTransform::SetSkewX(float angle, ErrorResult& rv)
252
0
{
253
0
  if (mIsAnimValItem) {
254
0
    rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
255
0
    return;
256
0
  }
257
0
258
0
  if (Transform().Type() == SVG_TRANSFORM_SKEWX &&
259
0
      Transform().Angle() == angle) {
260
0
    return;
261
0
  }
262
0
263
0
  if (!IsFinite(tan(angle * kRadPerDegree))) {
264
0
    rv.ThrowRangeError<MSG_INVALID_TRANSFORM_ANGLE_ERROR>();
265
0
    return;
266
0
  }
267
0
268
0
  AutoChangeTransformNotifier notifier(this);
269
0
  DebugOnly<nsresult> result = Transform().SetSkewX(angle);
270
0
  MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewX unexpectedly failed");
271
0
}
272
273
void
274
SVGTransform::SetSkewY(float angle, ErrorResult& rv)
275
0
{
276
0
  if (mIsAnimValItem) {
277
0
    rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
278
0
    return;
279
0
  }
280
0
281
0
  if (Transform().Type() == SVG_TRANSFORM_SKEWY &&
282
0
      Transform().Angle() == angle) {
283
0
    return;
284
0
  }
285
0
286
0
  if (!IsFinite(tan(angle * kRadPerDegree))) {
287
0
    rv.ThrowRangeError<MSG_INVALID_TRANSFORM_ANGLE_ERROR>();
288
0
    return;
289
0
  }
290
0
291
0
  AutoChangeTransformNotifier notifier(this);
292
0
  DebugOnly<nsresult> result = Transform().SetSkewY(angle);
293
0
  MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewY unexpectedly failed");
294
0
}
295
296
//----------------------------------------------------------------------
297
// List management methods:
298
299
void
300
SVGTransform::InsertingIntoList(DOMSVGTransformList *aList,
301
                                uint32_t aListIndex,
302
                                bool aIsAnimValItem)
303
0
{
304
0
  MOZ_ASSERT(!HasOwner(), "Inserting item that is already in a list");
305
0
306
0
  mList = aList;
307
0
  mListIndex = aListIndex;
308
0
  mIsAnimValItem = aIsAnimValItem;
309
0
  mTransform = nullptr;
310
0
311
0
  MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGLength!");
312
0
}
313
314
void
315
SVGTransform::RemovingFromList()
316
0
{
317
0
  MOZ_ASSERT(!mTransform,
318
0
             "Item in list also has another non-list value associated with it");
319
0
320
0
  mTransform = new nsSVGTransform(InternalItem());
321
0
  mList = nullptr;
322
0
  mIsAnimValItem = false;
323
0
}
324
325
nsSVGTransform&
326
SVGTransform::InternalItem()
327
0
{
328
0
  nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList();
329
0
  return mIsAnimValItem && alist->mAnimVal ?
330
0
    (*alist->mAnimVal)[mListIndex] :
331
0
    alist->mBaseVal[mListIndex];
332
0
}
333
334
const nsSVGTransform&
335
SVGTransform::InternalItem() const
336
0
{
337
0
  return const_cast<SVGTransform*>(this)->InternalItem();
338
0
}
339
340
#ifdef DEBUG
341
bool
342
SVGTransform::IndexIsValid()
343
{
344
  nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList();
345
  return (mIsAnimValItem &&
346
          mListIndex < alist->GetAnimValue().Length()) ||
347
         (!mIsAnimValItem &&
348
          mListIndex < alist->GetBaseValue().Length());
349
}
350
#endif // DEBUG
351
352
353
//----------------------------------------------------------------------
354
// Interface for SVGMatrix's use
355
356
void
357
SVGTransform::SetMatrix(const gfxMatrix& aMatrix)
358
0
{
359
0
  MOZ_ASSERT(!mIsAnimValItem,
360
0
             "Attempting to modify read-only transform");
361
0
362
0
  if (Transform().Type() == SVG_TRANSFORM_MATRIX &&
363
0
      nsSVGTransform::MatricesEqual(Matrixgfx(), aMatrix)) {
364
0
    return;
365
0
  }
366
0
367
0
  AutoChangeTransformNotifier notifier(this);
368
0
  Transform().SetMatrix(aMatrix);
369
0
}
370
371
} // namespace dom
372
} // namespace mozilla