/src/mozilla-central/dom/svg/SVGTransform.cpp
Line | Count | Source (jump to first uncovered line) |
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 SVGTransform_Binding; |
26 | | |
27 | | static nsSVGAttrTearoffTable<SVGTransform, SVGMatrix>& |
28 | | SVGMatrixTearoffTable() |
29 | 0 | { |
30 | 0 | static nsSVGAttrTearoffTable<SVGTransform, SVGMatrix> sSVGMatrixTearoffTable; |
31 | 0 | return sSVGMatrixTearoffTable; |
32 | 0 | } |
33 | | |
34 | | //---------------------------------------------------------------------- |
35 | | |
36 | | // We could use NS_IMPL_CYCLE_COLLECTION(, except that in Unlink() we need to |
37 | | // clear our list's weak ref to us to be safe. (The other option would be to |
38 | | // not unlink and rely on the breaking of the other edges in the cycle, as |
39 | | // NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.) |
40 | | NS_IMPL_CYCLE_COLLECTION_CLASS(SVGTransform) |
41 | | |
42 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SVGTransform) |
43 | 0 | // We may not belong to a list, so we must null check tmp->mList. |
44 | 0 | if (tmp->mList) { |
45 | 0 | tmp->mList->mItems[tmp->mListIndex] = nullptr; |
46 | 0 | } |
47 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mList) |
48 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
49 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
50 | | |
51 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SVGTransform) |
52 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mList) |
53 | 0 | SVGMatrix* matrix = |
54 | 0 | SVGMatrixTearoffTable().GetTearoff(tmp); |
55 | 0 | CycleCollectionNoteChild(cb, matrix, "matrix"); |
56 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
57 | | |
58 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(SVGTransform) |
59 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER |
60 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
61 | | |
62 | | NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(SVGTransform, AddRef) |
63 | | NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(SVGTransform, Release) |
64 | | |
65 | | JSObject* |
66 | | SVGTransform::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
67 | 0 | { |
68 | 0 | return SVGTransform_Binding::Wrap(aCx, this, aGivenProto); |
69 | 0 | } |
70 | | |
71 | | //---------------------------------------------------------------------- |
72 | | // Helper class: AutoChangeTransformNotifier |
73 | | // Stack-based helper class to pair calls to WillChangeTransformList |
74 | | // and DidChangeTransformList. |
75 | | class MOZ_RAII AutoChangeTransformNotifier |
76 | | { |
77 | | public: |
78 | | explicit AutoChangeTransformNotifier(SVGTransform* aTransform MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
79 | | : mTransform(aTransform) |
80 | 0 | { |
81 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
82 | 0 | MOZ_ASSERT(mTransform, "Expecting non-null transform"); |
83 | 0 | if (mTransform->HasOwner()) { |
84 | 0 | mEmptyOrOldValue = |
85 | 0 | mTransform->Element()->WillChangeTransformList(); |
86 | 0 | } |
87 | 0 | } |
88 | | |
89 | | ~AutoChangeTransformNotifier() |
90 | 0 | { |
91 | 0 | if (mTransform->HasOwner()) { |
92 | 0 | mTransform->Element()->DidChangeTransformList(mEmptyOrOldValue); |
93 | 0 | if (mTransform->mList->IsAnimating()) { |
94 | 0 | mTransform->Element()->AnimationNeedsResample(); |
95 | 0 | } |
96 | 0 | } |
97 | 0 | } |
98 | | |
99 | | private: |
100 | | SVGTransform* const mTransform; |
101 | | nsAttrValue mEmptyOrOldValue; |
102 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
103 | | }; |
104 | | |
105 | | //---------------------------------------------------------------------- |
106 | | // Ctors: |
107 | | |
108 | | SVGTransform::SVGTransform(DOMSVGTransformList *aList, |
109 | | uint32_t aListIndex, |
110 | | bool aIsAnimValItem) |
111 | | : mList(aList) |
112 | | , mListIndex(aListIndex) |
113 | | , mIsAnimValItem(aIsAnimValItem) |
114 | | , mTransform(nullptr) |
115 | 0 | { |
116 | 0 | // These shifts are in sync with the members in the header. |
117 | 0 | MOZ_ASSERT(aList && aListIndex <= MaxListIndex(), "bad arg"); |
118 | 0 |
|
119 | 0 | MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGNumber!"); |
120 | 0 | } |
121 | | |
122 | | SVGTransform::SVGTransform() |
123 | | : mList(nullptr) |
124 | | , mListIndex(0) |
125 | | , mIsAnimValItem(false) |
126 | | , mTransform(new nsSVGTransform()) // Default ctor for objects not in a list |
127 | | // initialises to matrix type with identity |
128 | | // matrix |
129 | 0 | { |
130 | 0 | } |
131 | | |
132 | | SVGTransform::SVGTransform(const gfxMatrix &aMatrix) |
133 | | : mList(nullptr) |
134 | | , mListIndex(0) |
135 | | , mIsAnimValItem(false) |
136 | | , mTransform(new nsSVGTransform(aMatrix)) |
137 | 0 | { |
138 | 0 | } |
139 | | |
140 | | SVGTransform::SVGTransform(const nsSVGTransform &aTransform) |
141 | | : mList(nullptr) |
142 | | , mListIndex(0) |
143 | | , mIsAnimValItem(false) |
144 | | , mTransform(new nsSVGTransform(aTransform)) |
145 | 0 | { |
146 | 0 | } |
147 | | |
148 | | SVGTransform::~SVGTransform() |
149 | 0 | { |
150 | 0 | SVGMatrix* matrix = SVGMatrixTearoffTable().GetTearoff(this); |
151 | 0 | if (matrix) { |
152 | 0 | SVGMatrixTearoffTable().RemoveTearoff(this); |
153 | 0 | NS_RELEASE(matrix); |
154 | 0 | } |
155 | 0 | // Our mList's weak ref to us must be nulled out when we die. If GC has |
156 | 0 | // unlinked us using the cycle collector code, then that has already |
157 | 0 | // happened, and mList is null. |
158 | 0 | if (mList) { |
159 | 0 | mList->mItems[mListIndex] = nullptr; |
160 | 0 | } |
161 | 0 | } |
162 | | |
163 | | uint16_t |
164 | | SVGTransform::Type() const |
165 | 0 | { |
166 | 0 | return Transform().Type(); |
167 | 0 | } |
168 | | |
169 | | SVGMatrix* |
170 | | SVGTransform::GetMatrix() |
171 | 0 | { |
172 | 0 | SVGMatrix* wrapper = |
173 | 0 | SVGMatrixTearoffTable().GetTearoff(this); |
174 | 0 | if (!wrapper) { |
175 | 0 | NS_ADDREF(wrapper = new SVGMatrix(*this)); |
176 | 0 | SVGMatrixTearoffTable().AddTearoff(this, wrapper); |
177 | 0 | } |
178 | 0 | return wrapper; |
179 | 0 | } |
180 | | |
181 | | float |
182 | | SVGTransform::Angle() const |
183 | 0 | { |
184 | 0 | return Transform().Angle(); |
185 | 0 | } |
186 | | |
187 | | void |
188 | | SVGTransform::SetMatrix(SVGMatrix& aMatrix, ErrorResult& rv) |
189 | 0 | { |
190 | 0 | if (mIsAnimValItem) { |
191 | 0 | rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
192 | 0 | return; |
193 | 0 | } |
194 | 0 | SetMatrix(aMatrix.GetMatrix()); |
195 | 0 | } |
196 | | |
197 | | void |
198 | | SVGTransform::SetTranslate(float tx, float ty, ErrorResult& rv) |
199 | 0 | { |
200 | 0 | if (mIsAnimValItem) { |
201 | 0 | rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
202 | 0 | return; |
203 | 0 | } |
204 | 0 | |
205 | 0 | if (Transform().Type() == SVG_TRANSFORM_TRANSLATE && |
206 | 0 | Matrixgfx()._31 == tx && Matrixgfx()._32 == ty) { |
207 | 0 | return; |
208 | 0 | } |
209 | 0 | |
210 | 0 | AutoChangeTransformNotifier notifier(this); |
211 | 0 | Transform().SetTranslate(tx, ty); |
212 | 0 | } |
213 | | |
214 | | void |
215 | | SVGTransform::SetScale(float sx, float sy, ErrorResult& rv) |
216 | 0 | { |
217 | 0 | if (mIsAnimValItem) { |
218 | 0 | rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
219 | 0 | return; |
220 | 0 | } |
221 | 0 | |
222 | 0 | if (Transform().Type() == SVG_TRANSFORM_SCALE && |
223 | 0 | Matrixgfx()._11 == sx && Matrixgfx()._22 == sy) { |
224 | 0 | return; |
225 | 0 | } |
226 | 0 | AutoChangeTransformNotifier notifier(this); |
227 | 0 | Transform().SetScale(sx, sy); |
228 | 0 | } |
229 | | |
230 | | void |
231 | | SVGTransform::SetRotate(float angle, float cx, float cy, ErrorResult& rv) |
232 | 0 | { |
233 | 0 | if (mIsAnimValItem) { |
234 | 0 | rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
235 | 0 | return; |
236 | 0 | } |
237 | 0 | |
238 | 0 | if (Transform().Type() == SVG_TRANSFORM_ROTATE) { |
239 | 0 | float currentCx, currentCy; |
240 | 0 | Transform().GetRotationOrigin(currentCx, currentCy); |
241 | 0 | if (Transform().Angle() == angle && currentCx == cx && currentCy == cy) { |
242 | 0 | return; |
243 | 0 | } |
244 | 0 | } |
245 | 0 | |
246 | 0 | AutoChangeTransformNotifier notifier(this); |
247 | 0 | Transform().SetRotate(angle, cx, cy); |
248 | 0 | } |
249 | | |
250 | | void |
251 | | SVGTransform::SetSkewX(float angle, ErrorResult& rv) |
252 | 0 | { |
253 | 0 | if (mIsAnimValItem) { |
254 | 0 | rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
255 | 0 | return; |
256 | 0 | } |
257 | 0 | |
258 | 0 | if (Transform().Type() == SVG_TRANSFORM_SKEWX && |
259 | 0 | Transform().Angle() == angle) { |
260 | 0 | return; |
261 | 0 | } |
262 | 0 | |
263 | 0 | if (!IsFinite(tan(angle * kRadPerDegree))) { |
264 | 0 | rv.ThrowRangeError<MSG_INVALID_TRANSFORM_ANGLE_ERROR>(); |
265 | 0 | return; |
266 | 0 | } |
267 | 0 | |
268 | 0 | AutoChangeTransformNotifier notifier(this); |
269 | 0 | DebugOnly<nsresult> result = Transform().SetSkewX(angle); |
270 | 0 | MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewX unexpectedly failed"); |
271 | 0 | } |
272 | | |
273 | | void |
274 | | SVGTransform::SetSkewY(float angle, ErrorResult& rv) |
275 | 0 | { |
276 | 0 | if (mIsAnimValItem) { |
277 | 0 | rv.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
278 | 0 | return; |
279 | 0 | } |
280 | 0 | |
281 | 0 | if (Transform().Type() == SVG_TRANSFORM_SKEWY && |
282 | 0 | Transform().Angle() == angle) { |
283 | 0 | return; |
284 | 0 | } |
285 | 0 | |
286 | 0 | if (!IsFinite(tan(angle * kRadPerDegree))) { |
287 | 0 | rv.ThrowRangeError<MSG_INVALID_TRANSFORM_ANGLE_ERROR>(); |
288 | 0 | return; |
289 | 0 | } |
290 | 0 | |
291 | 0 | AutoChangeTransformNotifier notifier(this); |
292 | 0 | DebugOnly<nsresult> result = Transform().SetSkewY(angle); |
293 | 0 | MOZ_ASSERT(NS_SUCCEEDED(result), "SetSkewY unexpectedly failed"); |
294 | 0 | } |
295 | | |
296 | | //---------------------------------------------------------------------- |
297 | | // List management methods: |
298 | | |
299 | | void |
300 | | SVGTransform::InsertingIntoList(DOMSVGTransformList *aList, |
301 | | uint32_t aListIndex, |
302 | | bool aIsAnimValItem) |
303 | 0 | { |
304 | 0 | MOZ_ASSERT(!HasOwner(), "Inserting item that is already in a list"); |
305 | 0 |
|
306 | 0 | mList = aList; |
307 | 0 | mListIndex = aListIndex; |
308 | 0 | mIsAnimValItem = aIsAnimValItem; |
309 | 0 | mTransform = nullptr; |
310 | 0 |
|
311 | 0 | MOZ_ASSERT(IndexIsValid(), "Bad index for DOMSVGLength!"); |
312 | 0 | } |
313 | | |
314 | | void |
315 | | SVGTransform::RemovingFromList() |
316 | 0 | { |
317 | 0 | MOZ_ASSERT(!mTransform, |
318 | 0 | "Item in list also has another non-list value associated with it"); |
319 | 0 |
|
320 | 0 | mTransform = new nsSVGTransform(InternalItem()); |
321 | 0 | mList = nullptr; |
322 | 0 | mIsAnimValItem = false; |
323 | 0 | } |
324 | | |
325 | | nsSVGTransform& |
326 | | SVGTransform::InternalItem() |
327 | 0 | { |
328 | 0 | nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList(); |
329 | 0 | return mIsAnimValItem && alist->mAnimVal ? |
330 | 0 | (*alist->mAnimVal)[mListIndex] : |
331 | 0 | alist->mBaseVal[mListIndex]; |
332 | 0 | } |
333 | | |
334 | | const nsSVGTransform& |
335 | | SVGTransform::InternalItem() const |
336 | 0 | { |
337 | 0 | return const_cast<SVGTransform*>(this)->InternalItem(); |
338 | 0 | } |
339 | | |
340 | | #ifdef DEBUG |
341 | | bool |
342 | | SVGTransform::IndexIsValid() |
343 | | { |
344 | | nsSVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList(); |
345 | | return (mIsAnimValItem && |
346 | | mListIndex < alist->GetAnimValue().Length()) || |
347 | | (!mIsAnimValItem && |
348 | | mListIndex < alist->GetBaseValue().Length()); |
349 | | } |
350 | | #endif // DEBUG |
351 | | |
352 | | |
353 | | //---------------------------------------------------------------------- |
354 | | // Interface for SVGMatrix's use |
355 | | |
356 | | void |
357 | | SVGTransform::SetMatrix(const gfxMatrix& aMatrix) |
358 | 0 | { |
359 | 0 | MOZ_ASSERT(!mIsAnimValItem, |
360 | 0 | "Attempting to modify read-only transform"); |
361 | 0 |
|
362 | 0 | if (Transform().Type() == SVG_TRANSFORM_MATRIX && |
363 | 0 | nsSVGTransform::MatricesEqual(Matrixgfx(), aMatrix)) { |
364 | 0 | return; |
365 | 0 | } |
366 | 0 | |
367 | 0 | AutoChangeTransformNotifier notifier(this); |
368 | 0 | Transform().SetMatrix(aMatrix); |
369 | 0 | } |
370 | | |
371 | | } // namespace dom |
372 | | } // namespace mozilla |