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