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