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