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