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