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