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 "mozilla/dom/DOMMatrix.h"
8
9 #include <cmath>
10 #include <cstdint>
11 #include <new>
12 #include "ErrorList.h"
13 #include "js/Conversions.h"
14 #include "js/Equality.h"
15 #include "js/StructuredClone.h"
16 #include "js/Value.h"
17 #include "mozilla/Casting.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/FloatingPoint.h"
20 #include "mozilla/MacroForEach.h"
21 #include "mozilla/RefPtr.h"
22 #include "mozilla/ServoCSSParser.h"
23 #include "mozilla/dom/BindingDeclarations.h"
24 #include "mozilla/dom/DOMMatrixBinding.h"
25 #include "mozilla/dom/DOMPoint.h"
26 #include "mozilla/dom/DOMPointBinding.h"
27 #include "mozilla/dom/ToJSValue.h"
28 #include "mozilla/gfx/BasePoint.h"
29 #include "mozilla/gfx/BasePoint4D.h"
30 #include "mozilla/gfx/Point.h"
31 #include "nsIGlobalObject.h"
32 #include "nsPIDOMWindow.h"
33 #include "nsString.h"
34 #include "nsStringFlags.h"
35 #include "nsTArray.h"
36 #include "nsTLiteralString.h"
37
38 namespace mozilla::dom {
39
40 template <typename T>
41 static void SetDataInMatrix(DOMMatrixReadOnly* aMatrix, const T* aData,
42 int aLength, ErrorResult& aRv);
43
44 static const double radPerDegree = 2.0 * M_PI / 360.0;
45
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMMatrixReadOnly,mParent)46 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DOMMatrixReadOnly, mParent)
47
48 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMatrixReadOnly, AddRef)
49 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMatrixReadOnly, Release)
50
51 JSObject* DOMMatrixReadOnly::WrapObject(JSContext* aCx,
52 JS::Handle<JSObject*> aGivenProto) {
53 return DOMMatrixReadOnly_Binding::Wrap(aCx, this, aGivenProto);
54 }
55
56 // https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup-2d
ValidateAndFixupMatrix2DInit(DOMMatrix2DInit & aMatrixInit,ErrorResult & aRv)57 static bool ValidateAndFixupMatrix2DInit(DOMMatrix2DInit& aMatrixInit,
58 ErrorResult& aRv) {
59 #define ValidateAliases(field, alias, fieldName, aliasName) \
60 if ((field).WasPassed() && (alias).WasPassed() && \
61 !JS::SameValueZero((field).Value(), (alias).Value())) { \
62 aRv.ThrowTypeError<MSG_MATRIX_INIT_CONFLICTING_VALUE>((fieldName), \
63 (aliasName)); \
64 return false; \
65 }
66 #define SetFromAliasOrDefault(field, alias, defaultValue) \
67 if (!(field).WasPassed()) { \
68 if ((alias).WasPassed()) { \
69 (field).Construct((alias).Value()); \
70 } else { \
71 (field).Construct(defaultValue); \
72 } \
73 }
74 #define ValidateAndSet(field, alias, fieldName, aliasName, defaultValue) \
75 ValidateAliases((field), (alias), fieldName, aliasName); \
76 SetFromAliasOrDefault((field), (alias), (defaultValue));
77
78 ValidateAndSet(aMatrixInit.mM11, aMatrixInit.mA, "m11", "a", 1);
79 ValidateAndSet(aMatrixInit.mM12, aMatrixInit.mB, "m12", "b", 0);
80 ValidateAndSet(aMatrixInit.mM21, aMatrixInit.mC, "m21", "c", 0);
81 ValidateAndSet(aMatrixInit.mM22, aMatrixInit.mD, "m22", "d", 1);
82 ValidateAndSet(aMatrixInit.mM41, aMatrixInit.mE, "m41", "e", 0);
83 ValidateAndSet(aMatrixInit.mM42, aMatrixInit.mF, "m42", "f", 0);
84
85 return true;
86
87 #undef ValidateAliases
88 #undef SetFromAliasOrDefault
89 #undef ValidateAndSet
90 }
91
92 // https://drafts.fxtf.org/geometry/#matrix-validate-and-fixup
ValidateAndFixupMatrixInit(DOMMatrixInit & aMatrixInit,ErrorResult & aRv)93 static bool ValidateAndFixupMatrixInit(DOMMatrixInit& aMatrixInit,
94 ErrorResult& aRv) {
95 #define Check3DField(field, fieldName, defaultValue) \
96 if ((field) != (defaultValue)) { \
97 if (!aMatrixInit.mIs2D.WasPassed()) { \
98 aMatrixInit.mIs2D.Construct(false); \
99 return true; \
100 } \
101 if (aMatrixInit.mIs2D.Value()) { \
102 aRv.ThrowTypeError<MSG_MATRIX_INIT_EXCEEDS_2D>(fieldName); \
103 return false; \
104 } \
105 }
106
107 if (!ValidateAndFixupMatrix2DInit(aMatrixInit, aRv)) {
108 return false;
109 }
110
111 Check3DField(aMatrixInit.mM13, "m13", 0);
112 Check3DField(aMatrixInit.mM14, "m14", 0);
113 Check3DField(aMatrixInit.mM23, "m23", 0);
114 Check3DField(aMatrixInit.mM24, "m24", 0);
115 Check3DField(aMatrixInit.mM31, "m31", 0);
116 Check3DField(aMatrixInit.mM32, "m32", 0);
117 Check3DField(aMatrixInit.mM34, "m34", 0);
118 Check3DField(aMatrixInit.mM43, "m43", 0);
119 Check3DField(aMatrixInit.mM33, "m33", 1);
120 Check3DField(aMatrixInit.mM44, "m44", 1);
121
122 if (!aMatrixInit.mIs2D.WasPassed()) {
123 aMatrixInit.mIs2D.Construct(true);
124 }
125 return true;
126
127 #undef Check3DField
128 }
129
SetDataFromMatrix2DInit(const DOMMatrix2DInit & aMatrixInit)130 void DOMMatrixReadOnly::SetDataFromMatrix2DInit(
131 const DOMMatrix2DInit& aMatrixInit) {
132 MOZ_ASSERT(Is2D());
133 mMatrix2D->_11 = aMatrixInit.mM11.Value();
134 mMatrix2D->_12 = aMatrixInit.mM12.Value();
135 mMatrix2D->_21 = aMatrixInit.mM21.Value();
136 mMatrix2D->_22 = aMatrixInit.mM22.Value();
137 mMatrix2D->_31 = aMatrixInit.mM41.Value();
138 mMatrix2D->_32 = aMatrixInit.mM42.Value();
139 }
140
SetDataFromMatrixInit(const DOMMatrixInit & aMatrixInit)141 void DOMMatrixReadOnly::SetDataFromMatrixInit(
142 const DOMMatrixInit& aMatrixInit) {
143 const bool is2D = aMatrixInit.mIs2D.Value();
144 MOZ_ASSERT(is2D == Is2D());
145 if (is2D) {
146 SetDataFromMatrix2DInit(aMatrixInit);
147 } else {
148 mMatrix3D->_11 = aMatrixInit.mM11.Value();
149 mMatrix3D->_12 = aMatrixInit.mM12.Value();
150 mMatrix3D->_13 = aMatrixInit.mM13;
151 mMatrix3D->_14 = aMatrixInit.mM14;
152 mMatrix3D->_21 = aMatrixInit.mM21.Value();
153 mMatrix3D->_22 = aMatrixInit.mM22.Value();
154 mMatrix3D->_23 = aMatrixInit.mM23;
155 mMatrix3D->_24 = aMatrixInit.mM24;
156 mMatrix3D->_31 = aMatrixInit.mM31;
157 mMatrix3D->_32 = aMatrixInit.mM32;
158 mMatrix3D->_33 = aMatrixInit.mM33;
159 mMatrix3D->_34 = aMatrixInit.mM34;
160 mMatrix3D->_41 = aMatrixInit.mM41.Value();
161 mMatrix3D->_42 = aMatrixInit.mM42.Value();
162 mMatrix3D->_43 = aMatrixInit.mM43;
163 mMatrix3D->_44 = aMatrixInit.mM44;
164 }
165 }
166
FromMatrix(nsISupports * aParent,const DOMMatrix2DInit & aMatrixInit,ErrorResult & aRv)167 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
168 nsISupports* aParent, const DOMMatrix2DInit& aMatrixInit,
169 ErrorResult& aRv) {
170 DOMMatrix2DInit matrixInit(aMatrixInit);
171 if (!ValidateAndFixupMatrix2DInit(matrixInit, aRv)) {
172 return nullptr;
173 };
174
175 RefPtr<DOMMatrixReadOnly> matrix =
176 new DOMMatrixReadOnly(aParent, /* is2D */ true);
177 matrix->SetDataFromMatrix2DInit(matrixInit);
178 return matrix.forget();
179 }
180
FromMatrix(nsISupports * aParent,const DOMMatrixInit & aMatrixInit,ErrorResult & aRv)181 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
182 nsISupports* aParent, const DOMMatrixInit& aMatrixInit, ErrorResult& aRv) {
183 DOMMatrixInit matrixInit(aMatrixInit);
184 if (!ValidateAndFixupMatrixInit(matrixInit, aRv)) {
185 return nullptr;
186 };
187
188 RefPtr<DOMMatrixReadOnly> rval =
189 new DOMMatrixReadOnly(aParent, matrixInit.mIs2D.Value());
190 rval->SetDataFromMatrixInit(matrixInit);
191 return rval.forget();
192 }
193
FromMatrix(const GlobalObject & aGlobal,const DOMMatrixInit & aMatrixInit,ErrorResult & aRv)194 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromMatrix(
195 const GlobalObject& aGlobal, const DOMMatrixInit& aMatrixInit,
196 ErrorResult& aRv) {
197 RefPtr<DOMMatrixReadOnly> matrix =
198 FromMatrix(aGlobal.GetAsSupports(), aMatrixInit, aRv);
199 return matrix.forget();
200 }
201
FromFloat32Array(const GlobalObject & aGlobal,const Float32Array & aArray32,ErrorResult & aRv)202 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromFloat32Array(
203 const GlobalObject& aGlobal, const Float32Array& aArray32,
204 ErrorResult& aRv) {
205 aArray32.ComputeState();
206
207 const int length = aArray32.Length();
208 const bool is2D = length == 6;
209 RefPtr<DOMMatrixReadOnly> obj =
210 new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
211 SetDataInMatrix(obj, aArray32.Data(), length, aRv);
212
213 return obj.forget();
214 }
215
FromFloat64Array(const GlobalObject & aGlobal,const Float64Array & aArray64,ErrorResult & aRv)216 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::FromFloat64Array(
217 const GlobalObject& aGlobal, const Float64Array& aArray64,
218 ErrorResult& aRv) {
219 aArray64.ComputeState();
220
221 const int length = aArray64.Length();
222 const bool is2D = length == 6;
223 RefPtr<DOMMatrixReadOnly> obj =
224 new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
225 SetDataInMatrix(obj, aArray64.Data(), length, aRv);
226
227 return obj.forget();
228 }
229
Constructor(const GlobalObject & aGlobal,const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly> & aArg,ErrorResult & aRv)230 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::Constructor(
231 const GlobalObject& aGlobal,
232 const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly>&
233 aArg,
234 ErrorResult& aRv) {
235 if (!aArg.WasPassed()) {
236 RefPtr<DOMMatrixReadOnly> rval =
237 new DOMMatrixReadOnly(aGlobal.GetAsSupports());
238 return rval.forget();
239 }
240
241 const auto& arg = aArg.Value();
242 if (arg.IsUTF8String()) {
243 nsCOMPtr<nsPIDOMWindowInner> win =
244 do_QueryInterface(aGlobal.GetAsSupports());
245 if (!win) {
246 aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
247 return nullptr;
248 }
249 RefPtr<DOMMatrixReadOnly> rval =
250 new DOMMatrixReadOnly(aGlobal.GetAsSupports());
251 rval->SetMatrixValue(arg.GetAsUTF8String(), aRv);
252 return rval.forget();
253 }
254 if (arg.IsDOMMatrixReadOnly()) {
255 RefPtr<DOMMatrixReadOnly> obj = new DOMMatrixReadOnly(
256 aGlobal.GetAsSupports(), arg.GetAsDOMMatrixReadOnly());
257 return obj.forget();
258 }
259
260 const auto& sequence = arg.GetAsUnrestrictedDoubleSequence();
261 const int length = sequence.Length();
262 const bool is2D = length == 6;
263 RefPtr<DOMMatrixReadOnly> rval =
264 new DOMMatrixReadOnly(aGlobal.GetAsSupports(), is2D);
265 SetDataInMatrix(rval, sequence.Elements(), length, aRv);
266 return rval.forget();
267 }
268
ReadStructuredClone(JSContext * aCx,nsIGlobalObject * aGlobal,JSStructuredCloneReader * aReader)269 already_AddRefed<DOMMatrixReadOnly> DOMMatrixReadOnly::ReadStructuredClone(
270 JSContext* aCx, nsIGlobalObject* aGlobal,
271 JSStructuredCloneReader* aReader) {
272 uint8_t is2D;
273
274 if (!JS_ReadBytes(aReader, &is2D, 1)) {
275 return nullptr;
276 }
277
278 RefPtr<DOMMatrixReadOnly> rval = new DOMMatrixReadOnly(aGlobal, is2D);
279
280 if (!ReadStructuredCloneElements(aReader, rval)) {
281 return nullptr;
282 };
283
284 return rval.forget();
285 }
286
Translate(double aTx,double aTy,double aTz) const287 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Translate(double aTx, double aTy,
288 double aTz) const {
289 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
290 retval->TranslateSelf(aTx, aTy, aTz);
291
292 return retval.forget();
293 }
294
Scale(double aScaleX,const Optional<double> & aScaleY,double aScaleZ,double aOriginX,double aOriginY,double aOriginZ) const295 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Scale(
296 double aScaleX, const Optional<double>& aScaleY, double aScaleZ,
297 double aOriginX, double aOriginY, double aOriginZ) const {
298 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
299 retval->ScaleSelf(aScaleX, aScaleY, aScaleZ, aOriginX, aOriginY, aOriginZ);
300
301 return retval.forget();
302 }
303
Scale3d(double aScale,double aOriginX,double aOriginY,double aOriginZ) const304 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Scale3d(double aScale,
305 double aOriginX,
306 double aOriginY,
307 double aOriginZ) const {
308 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
309 retval->Scale3dSelf(aScale, aOriginX, aOriginY, aOriginZ);
310
311 return retval.forget();
312 }
313
ScaleNonUniform(double aScaleX,double aScaleY) const314 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::ScaleNonUniform(
315 double aScaleX, double aScaleY) const {
316 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
317 retval->ScaleSelf(aScaleX, Optional<double>(aScaleY), 1, 0, 0, 0);
318
319 return retval.forget();
320 }
321
Rotate(double aRotX,const Optional<double> & aRotY,const Optional<double> & aRotZ) const322 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Rotate(
323 double aRotX, const Optional<double>& aRotY,
324 const Optional<double>& aRotZ) const {
325 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
326 retval->RotateSelf(aRotX, aRotY, aRotZ);
327
328 return retval.forget();
329 }
330
RotateFromVector(double x,double y) const331 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::RotateFromVector(
332 double x, double y) const {
333 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
334 retval->RotateFromVectorSelf(x, y);
335
336 return retval.forget();
337 }
338
RotateAxisAngle(double aX,double aY,double aZ,double aAngle) const339 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::RotateAxisAngle(
340 double aX, double aY, double aZ, double aAngle) const {
341 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
342 retval->RotateAxisAngleSelf(aX, aY, aZ, aAngle);
343
344 return retval.forget();
345 }
346
SkewX(double aSx) const347 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::SkewX(double aSx) const {
348 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
349 retval->SkewXSelf(aSx);
350
351 return retval.forget();
352 }
353
SkewY(double aSy) const354 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::SkewY(double aSy) const {
355 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
356 retval->SkewYSelf(aSy);
357
358 return retval.forget();
359 }
360
Multiply(const DOMMatrixInit & other,ErrorResult & aRv) const361 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Multiply(
362 const DOMMatrixInit& other, ErrorResult& aRv) const {
363 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
364 retval->MultiplySelf(other, aRv);
365
366 return retval.forget();
367 }
368
FlipX() const369 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::FlipX() const {
370 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
371 if (mMatrix3D) {
372 gfx::Matrix4x4Double m;
373 m._11 = -1;
374 retval->mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(m * *mMatrix3D);
375 } else {
376 gfx::MatrixDouble m;
377 m._11 = -1;
378 retval->mMatrix2D =
379 MakeUnique<gfx::MatrixDouble>(mMatrix2D ? m * *mMatrix2D : m);
380 }
381
382 return retval.forget();
383 }
384
FlipY() const385 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::FlipY() const {
386 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
387 if (mMatrix3D) {
388 gfx::Matrix4x4Double m;
389 m._22 = -1;
390 retval->mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(m * *mMatrix3D);
391 } else {
392 gfx::MatrixDouble m;
393 m._22 = -1;
394 retval->mMatrix2D =
395 MakeUnique<gfx::MatrixDouble>(mMatrix2D ? m * *mMatrix2D : m);
396 }
397
398 return retval.forget();
399 }
400
Inverse() const401 already_AddRefed<DOMMatrix> DOMMatrixReadOnly::Inverse() const {
402 RefPtr<DOMMatrix> retval = new DOMMatrix(mParent, *this);
403 retval->InvertSelf();
404
405 return retval.forget();
406 }
407
Is2D() const408 bool DOMMatrixReadOnly::Is2D() const { return !mMatrix3D; }
409
IsIdentity() const410 bool DOMMatrixReadOnly::IsIdentity() const {
411 if (mMatrix3D) {
412 return mMatrix3D->IsIdentity();
413 }
414
415 return mMatrix2D->IsIdentity();
416 }
417
TransformPoint(const DOMPointInit & point) const418 already_AddRefed<DOMPoint> DOMMatrixReadOnly::TransformPoint(
419 const DOMPointInit& point) const {
420 RefPtr<DOMPoint> retval = new DOMPoint(mParent);
421
422 if (mMatrix3D) {
423 gfx::Point4D transformedPoint;
424 transformedPoint.x = point.mX;
425 transformedPoint.y = point.mY;
426 transformedPoint.z = point.mZ;
427 transformedPoint.w = point.mW;
428
429 transformedPoint = mMatrix3D->TransformPoint(transformedPoint);
430
431 retval->SetX(transformedPoint.x);
432 retval->SetY(transformedPoint.y);
433 retval->SetZ(transformedPoint.z);
434 retval->SetW(transformedPoint.w);
435 } else if (point.mZ != 0 || point.mW != 1.0) {
436 gfx::Matrix4x4Double tempMatrix(gfx::Matrix4x4Double::From2D(*mMatrix2D));
437
438 gfx::PointDouble4D transformedPoint;
439 transformedPoint.x = point.mX;
440 transformedPoint.y = point.mY;
441 transformedPoint.z = point.mZ;
442 transformedPoint.w = point.mW;
443
444 transformedPoint = tempMatrix.TransformPoint(transformedPoint);
445
446 retval->SetX(transformedPoint.x);
447 retval->SetY(transformedPoint.y);
448 retval->SetZ(transformedPoint.z);
449 retval->SetW(transformedPoint.w);
450 } else {
451 gfx::PointDouble transformedPoint;
452 transformedPoint.x = point.mX;
453 transformedPoint.y = point.mY;
454
455 transformedPoint = mMatrix2D->TransformPoint(transformedPoint);
456
457 retval->SetX(transformedPoint.x);
458 retval->SetY(transformedPoint.y);
459 retval->SetZ(point.mZ);
460 retval->SetW(point.mW);
461 }
462 return retval.forget();
463 }
464
465 template <typename T>
GetDataFromMatrix(const DOMMatrixReadOnly * aMatrix,T * aData)466 void GetDataFromMatrix(const DOMMatrixReadOnly* aMatrix, T* aData) {
467 aData[0] = static_cast<T>(aMatrix->M11());
468 aData[1] = static_cast<T>(aMatrix->M12());
469 aData[2] = static_cast<T>(aMatrix->M13());
470 aData[3] = static_cast<T>(aMatrix->M14());
471 aData[4] = static_cast<T>(aMatrix->M21());
472 aData[5] = static_cast<T>(aMatrix->M22());
473 aData[6] = static_cast<T>(aMatrix->M23());
474 aData[7] = static_cast<T>(aMatrix->M24());
475 aData[8] = static_cast<T>(aMatrix->M31());
476 aData[9] = static_cast<T>(aMatrix->M32());
477 aData[10] = static_cast<T>(aMatrix->M33());
478 aData[11] = static_cast<T>(aMatrix->M34());
479 aData[12] = static_cast<T>(aMatrix->M41());
480 aData[13] = static_cast<T>(aMatrix->M42());
481 aData[14] = static_cast<T>(aMatrix->M43());
482 aData[15] = static_cast<T>(aMatrix->M44());
483 }
484
ToFloat32Array(JSContext * aCx,JS::MutableHandle<JSObject * > aResult,ErrorResult & aRv) const485 void DOMMatrixReadOnly::ToFloat32Array(JSContext* aCx,
486 JS::MutableHandle<JSObject*> aResult,
487 ErrorResult& aRv) const {
488 AutoTArray<float, 16> arr;
489 arr.SetLength(16);
490 GetDataFromMatrix(this, arr.Elements());
491 JS::Rooted<JS::Value> value(aCx);
492 if (!ToJSValue(aCx, TypedArrayCreator<Float32Array>(arr), &value)) {
493 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
494 return;
495 }
496 aResult.set(&value.toObject());
497 }
498
ToFloat64Array(JSContext * aCx,JS::MutableHandle<JSObject * > aResult,ErrorResult & aRv) const499 void DOMMatrixReadOnly::ToFloat64Array(JSContext* aCx,
500 JS::MutableHandle<JSObject*> aResult,
501 ErrorResult& aRv) const {
502 AutoTArray<double, 16> arr;
503 arr.SetLength(16);
504 GetDataFromMatrix(this, arr.Elements());
505 JS::Rooted<JS::Value> value(aCx);
506 if (!ToJSValue(aCx, TypedArrayCreator<Float64Array>(arr), &value)) {
507 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
508 return;
509 }
510 aResult.set(&value.toObject());
511 }
512
Stringify(nsAString & aResult,ErrorResult & aRv)513 void DOMMatrixReadOnly::Stringify(nsAString& aResult, ErrorResult& aRv) {
514 char cbuf[JS::MaximumNumberToStringLength];
515 nsAutoString matrixStr;
516 auto AppendDouble = [&aRv, &cbuf, &matrixStr](double d,
517 bool isLastItem = false) {
518 if (!mozilla::IsFinite(d)) {
519 aRv.ThrowInvalidStateError(
520 "Matrix with a non-finite element cannot be stringified.");
521 return false;
522 }
523 JS::NumberToString(d, cbuf);
524 matrixStr.AppendASCII(cbuf);
525 if (!isLastItem) {
526 matrixStr.AppendLiteral(", ");
527 }
528 return true;
529 };
530
531 if (mMatrix3D) {
532 // We can't use AppendPrintf here, because it does locale-specific
533 // formatting of floating-point values.
534 matrixStr.AssignLiteral("matrix3d(");
535 if (!AppendDouble(M11()) || !AppendDouble(M12()) || !AppendDouble(M13()) ||
536 !AppendDouble(M14()) || !AppendDouble(M21()) || !AppendDouble(M22()) ||
537 !AppendDouble(M23()) || !AppendDouble(M24()) || !AppendDouble(M31()) ||
538 !AppendDouble(M32()) || !AppendDouble(M33()) || !AppendDouble(M34()) ||
539 !AppendDouble(M41()) || !AppendDouble(M42()) || !AppendDouble(M43()) ||
540 !AppendDouble(M44(), true)) {
541 return;
542 }
543 matrixStr.AppendLiteral(")");
544 } else {
545 // We can't use AppendPrintf here, because it does locale-specific
546 // formatting of floating-point values.
547 matrixStr.AssignLiteral("matrix(");
548 if (!AppendDouble(A()) || !AppendDouble(B()) || !AppendDouble(C()) ||
549 !AppendDouble(D()) || !AppendDouble(E()) || !AppendDouble(F(), true)) {
550 return;
551 }
552 matrixStr.AppendLiteral(")");
553 }
554
555 aResult = matrixStr;
556 }
557
558 // https://drafts.fxtf.org/geometry/#structured-serialization
WriteStructuredClone(JSContext * aCx,JSStructuredCloneWriter * aWriter) const559 bool DOMMatrixReadOnly::WriteStructuredClone(
560 JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
561 #define WriteDouble(d) \
562 JS_WriteUint32Pair(aWriter, (BitwiseCast<uint64_t>(d) >> 32) & 0xffffffff, \
563 BitwiseCast<uint64_t>(d) & 0xffffffff)
564
565 const uint8_t is2D = Is2D();
566
567 if (!JS_WriteBytes(aWriter, &is2D, 1)) {
568 return false;
569 }
570
571 if (is2D == 1) {
572 return WriteDouble(mMatrix2D->_11) && WriteDouble(mMatrix2D->_12) &&
573 WriteDouble(mMatrix2D->_21) && WriteDouble(mMatrix2D->_22) &&
574 WriteDouble(mMatrix2D->_31) && WriteDouble(mMatrix2D->_32);
575 }
576
577 return WriteDouble(mMatrix3D->_11) && WriteDouble(mMatrix3D->_12) &&
578 WriteDouble(mMatrix3D->_13) && WriteDouble(mMatrix3D->_14) &&
579 WriteDouble(mMatrix3D->_21) && WriteDouble(mMatrix3D->_22) &&
580 WriteDouble(mMatrix3D->_23) && WriteDouble(mMatrix3D->_24) &&
581 WriteDouble(mMatrix3D->_31) && WriteDouble(mMatrix3D->_32) &&
582 WriteDouble(mMatrix3D->_33) && WriteDouble(mMatrix3D->_34) &&
583 WriteDouble(mMatrix3D->_41) && WriteDouble(mMatrix3D->_42) &&
584 WriteDouble(mMatrix3D->_43) && WriteDouble(mMatrix3D->_44);
585
586 #undef WriteDouble
587 }
588
ReadStructuredCloneElements(JSStructuredCloneReader * aReader,DOMMatrixReadOnly * matrix)589 bool DOMMatrixReadOnly::ReadStructuredCloneElements(
590 JSStructuredCloneReader* aReader, DOMMatrixReadOnly* matrix) {
591 uint32_t high;
592 uint32_t low;
593
594 #define ReadDouble(d) \
595 if (!JS_ReadUint32Pair(aReader, &high, &low)) { \
596 return false; \
597 } \
598 (*(d) = BitwiseCast<double>(static_cast<uint64_t>(high) << 32 | low))
599
600 if (matrix->Is2D() == 1) {
601 ReadDouble(&(matrix->mMatrix2D->_11));
602 ReadDouble(&(matrix->mMatrix2D->_12));
603 ReadDouble(&(matrix->mMatrix2D->_21));
604 ReadDouble(&(matrix->mMatrix2D->_22));
605 ReadDouble(&(matrix->mMatrix2D->_31));
606 ReadDouble(&(matrix->mMatrix2D->_32));
607 } else {
608 ReadDouble(&(matrix->mMatrix3D->_11));
609 ReadDouble(&(matrix->mMatrix3D->_12));
610 ReadDouble(&(matrix->mMatrix3D->_13));
611 ReadDouble(&(matrix->mMatrix3D->_14));
612 ReadDouble(&(matrix->mMatrix3D->_21));
613 ReadDouble(&(matrix->mMatrix3D->_22));
614 ReadDouble(&(matrix->mMatrix3D->_23));
615 ReadDouble(&(matrix->mMatrix3D->_24));
616 ReadDouble(&(matrix->mMatrix3D->_31));
617 ReadDouble(&(matrix->mMatrix3D->_32));
618 ReadDouble(&(matrix->mMatrix3D->_33));
619 ReadDouble(&(matrix->mMatrix3D->_34));
620 ReadDouble(&(matrix->mMatrix3D->_41));
621 ReadDouble(&(matrix->mMatrix3D->_42));
622 ReadDouble(&(matrix->mMatrix3D->_43));
623 ReadDouble(&(matrix->mMatrix3D->_44));
624 }
625
626 return true;
627
628 #undef ReadDouble
629 }
630
FromMatrix(nsISupports * aParent,const DOMMatrixInit & aMatrixInit,ErrorResult & aRv)631 already_AddRefed<DOMMatrix> DOMMatrix::FromMatrix(
632 nsISupports* aParent, const DOMMatrixInit& aMatrixInit, ErrorResult& aRv) {
633 DOMMatrixInit matrixInit(aMatrixInit);
634 if (!ValidateAndFixupMatrixInit(matrixInit, aRv)) {
635 return nullptr;
636 };
637
638 RefPtr<DOMMatrix> matrix = new DOMMatrix(aParent, matrixInit.mIs2D.Value());
639 matrix->SetDataFromMatrixInit(matrixInit);
640 return matrix.forget();
641 }
642
FromMatrix(const GlobalObject & aGlobal,const DOMMatrixInit & aMatrixInit,ErrorResult & aRv)643 already_AddRefed<DOMMatrix> DOMMatrix::FromMatrix(
644 const GlobalObject& aGlobal, const DOMMatrixInit& aMatrixInit,
645 ErrorResult& aRv) {
646 RefPtr<DOMMatrix> matrix =
647 FromMatrix(aGlobal.GetAsSupports(), aMatrixInit, aRv);
648 return matrix.forget();
649 }
650
FromFloat32Array(const GlobalObject & aGlobal,const Float32Array & aArray32,ErrorResult & aRv)651 already_AddRefed<DOMMatrix> DOMMatrix::FromFloat32Array(
652 const GlobalObject& aGlobal, const Float32Array& aArray32,
653 ErrorResult& aRv) {
654 aArray32.ComputeState();
655
656 const int length = aArray32.Length();
657 const bool is2D = length == 6;
658 RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
659 SetDataInMatrix(obj, aArray32.Data(), length, aRv);
660
661 return obj.forget();
662 }
663
FromFloat64Array(const GlobalObject & aGlobal,const Float64Array & aArray64,ErrorResult & aRv)664 already_AddRefed<DOMMatrix> DOMMatrix::FromFloat64Array(
665 const GlobalObject& aGlobal, const Float64Array& aArray64,
666 ErrorResult& aRv) {
667 aArray64.ComputeState();
668
669 const int length = aArray64.Length();
670 const bool is2D = length == 6;
671 RefPtr<DOMMatrix> obj = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
672 SetDataInMatrix(obj, aArray64.Data(), length, aRv);
673
674 return obj.forget();
675 }
676
Constructor(const GlobalObject & aGlobal,const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly> & aArg,ErrorResult & aRv)677 already_AddRefed<DOMMatrix> DOMMatrix::Constructor(
678 const GlobalObject& aGlobal,
679 const Optional<UTF8StringOrUnrestrictedDoubleSequenceOrDOMMatrixReadOnly>&
680 aArg,
681 ErrorResult& aRv) {
682 if (!aArg.WasPassed()) {
683 RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal.GetAsSupports());
684 return rval.forget();
685 }
686
687 const auto& arg = aArg.Value();
688 if (arg.IsUTF8String()) {
689 nsCOMPtr<nsPIDOMWindowInner> win =
690 do_QueryInterface(aGlobal.GetAsSupports());
691 if (!win) {
692 aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
693 return nullptr;
694 }
695 RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal.GetAsSupports());
696 rval->SetMatrixValue(arg.GetAsUTF8String(), aRv);
697 return rval.forget();
698 }
699 if (arg.IsDOMMatrixReadOnly()) {
700 RefPtr<DOMMatrix> obj =
701 new DOMMatrix(aGlobal.GetAsSupports(), arg.GetAsDOMMatrixReadOnly());
702 return obj.forget();
703 }
704
705 const auto& sequence = arg.GetAsUnrestrictedDoubleSequence();
706 const int length = sequence.Length();
707 const bool is2D = length == 6;
708 RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal.GetAsSupports(), is2D);
709 SetDataInMatrix(rval, sequence.Elements(), length, aRv);
710 return rval.forget();
711 }
712
713 template <typename T>
SetDataInMatrix(DOMMatrixReadOnly * aMatrix,const T * aData,int aLength,ErrorResult & aRv)714 static void SetDataInMatrix(DOMMatrixReadOnly* aMatrix, const T* aData,
715 int aLength, ErrorResult& aRv) {
716 if (aLength == 16) {
717 aMatrix->SetM11(aData[0]);
718 aMatrix->SetM12(aData[1]);
719 aMatrix->SetM13(aData[2]);
720 aMatrix->SetM14(aData[3]);
721 aMatrix->SetM21(aData[4]);
722 aMatrix->SetM22(aData[5]);
723 aMatrix->SetM23(aData[6]);
724 aMatrix->SetM24(aData[7]);
725 aMatrix->SetM31(aData[8]);
726 aMatrix->SetM32(aData[9]);
727 aMatrix->SetM33(aData[10]);
728 aMatrix->SetM34(aData[11]);
729 aMatrix->SetM41(aData[12]);
730 aMatrix->SetM42(aData[13]);
731 aMatrix->SetM43(aData[14]);
732 aMatrix->SetM44(aData[15]);
733 } else if (aLength == 6) {
734 aMatrix->SetA(aData[0]);
735 aMatrix->SetB(aData[1]);
736 aMatrix->SetC(aData[2]);
737 aMatrix->SetD(aData[3]);
738 aMatrix->SetE(aData[4]);
739 aMatrix->SetF(aData[5]);
740 } else {
741 nsAutoCString lengthStr;
742 lengthStr.AppendInt(aLength);
743 aRv.ThrowTypeError<MSG_MATRIX_INIT_LENGTH_WRONG>(lengthStr);
744 }
745 }
746
ReadStructuredClone(JSContext * aCx,nsIGlobalObject * aGlobal,JSStructuredCloneReader * aReader)747 already_AddRefed<DOMMatrix> DOMMatrix::ReadStructuredClone(
748 JSContext* aCx, nsIGlobalObject* aGlobal,
749 JSStructuredCloneReader* aReader) {
750 uint8_t is2D;
751
752 if (!JS_ReadBytes(aReader, &is2D, 1)) {
753 return nullptr;
754 }
755
756 RefPtr<DOMMatrix> rval = new DOMMatrix(aGlobal, is2D);
757
758 if (!ReadStructuredCloneElements(aReader, rval)) {
759 return nullptr;
760 };
761
762 return rval.forget();
763 }
764
Ensure3DMatrix()765 void DOMMatrixReadOnly::Ensure3DMatrix() {
766 if (!mMatrix3D) {
767 mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(
768 gfx::Matrix4x4Double::From2D(*mMatrix2D));
769 mMatrix2D = nullptr;
770 }
771 }
772
MultiplySelf(const DOMMatrixInit & aOtherInit,ErrorResult & aRv)773 DOMMatrix* DOMMatrix::MultiplySelf(const DOMMatrixInit& aOtherInit,
774 ErrorResult& aRv) {
775 RefPtr<DOMMatrix> other = FromMatrix(mParent, aOtherInit, aRv);
776 if (aRv.Failed()) {
777 return nullptr;
778 }
779 MOZ_ASSERT(other);
780 if (other->IsIdentity()) {
781 return this;
782 }
783
784 if (other->Is2D()) {
785 if (mMatrix3D) {
786 *mMatrix3D = gfx::Matrix4x4Double::From2D(*other->mMatrix2D) * *mMatrix3D;
787 } else {
788 *mMatrix2D = *other->mMatrix2D * *mMatrix2D;
789 }
790 } else {
791 Ensure3DMatrix();
792 *mMatrix3D = *other->mMatrix3D * *mMatrix3D;
793 }
794
795 return this;
796 }
797
PreMultiplySelf(const DOMMatrixInit & aOtherInit,ErrorResult & aRv)798 DOMMatrix* DOMMatrix::PreMultiplySelf(const DOMMatrixInit& aOtherInit,
799 ErrorResult& aRv) {
800 RefPtr<DOMMatrix> other = FromMatrix(mParent, aOtherInit, aRv);
801 if (aRv.Failed()) {
802 return nullptr;
803 }
804 MOZ_ASSERT(other);
805 if (other->IsIdentity()) {
806 return this;
807 }
808
809 if (other->Is2D()) {
810 if (mMatrix3D) {
811 *mMatrix3D = *mMatrix3D * gfx::Matrix4x4Double::From2D(*other->mMatrix2D);
812 } else {
813 *mMatrix2D = *mMatrix2D * *other->mMatrix2D;
814 }
815 } else {
816 Ensure3DMatrix();
817 *mMatrix3D = *mMatrix3D * *other->mMatrix3D;
818 }
819
820 return this;
821 }
822
TranslateSelf(double aTx,double aTy,double aTz)823 DOMMatrix* DOMMatrix::TranslateSelf(double aTx, double aTy, double aTz) {
824 if (aTx == 0 && aTy == 0 && aTz == 0) {
825 return this;
826 }
827
828 if (mMatrix3D || aTz != 0) {
829 Ensure3DMatrix();
830 mMatrix3D->PreTranslate(aTx, aTy, aTz);
831 } else {
832 mMatrix2D->PreTranslate(aTx, aTy);
833 }
834
835 return this;
836 }
837
ScaleSelf(double aScaleX,const Optional<double> & aScaleY,double aScaleZ,double aOriginX,double aOriginY,double aOriginZ)838 DOMMatrix* DOMMatrix::ScaleSelf(double aScaleX, const Optional<double>& aScaleY,
839 double aScaleZ, double aOriginX,
840 double aOriginY, double aOriginZ) {
841 const double scaleY = aScaleY.WasPassed() ? aScaleY.Value() : aScaleX;
842
843 TranslateSelf(aOriginX, aOriginY, aOriginZ);
844
845 if (mMatrix3D || aScaleZ != 1.0) {
846 Ensure3DMatrix();
847 gfx::Matrix4x4Double m;
848 m._11 = aScaleX;
849 m._22 = scaleY;
850 m._33 = aScaleZ;
851 *mMatrix3D = m * *mMatrix3D;
852 } else {
853 gfx::MatrixDouble m;
854 m._11 = aScaleX;
855 m._22 = scaleY;
856 *mMatrix2D = m * *mMatrix2D;
857 }
858
859 TranslateSelf(-aOriginX, -aOriginY, -aOriginZ);
860
861 return this;
862 }
863
Scale3dSelf(double aScale,double aOriginX,double aOriginY,double aOriginZ)864 DOMMatrix* DOMMatrix::Scale3dSelf(double aScale, double aOriginX,
865 double aOriginY, double aOriginZ) {
866 ScaleSelf(aScale, Optional<double>(aScale), aScale, aOriginX, aOriginY,
867 aOriginZ);
868
869 return this;
870 }
871
RotateFromVectorSelf(double aX,double aY)872 DOMMatrix* DOMMatrix::RotateFromVectorSelf(double aX, double aY) {
873 const double angle = (aX == 0.0 && aY == 0.0) ? 0 : atan2(aY, aX);
874
875 if (fmod(angle, 2 * M_PI) == 0) {
876 return this;
877 }
878
879 if (mMatrix3D) {
880 RotateAxisAngleSelf(0, 0, 1, angle / radPerDegree);
881 } else {
882 *mMatrix2D = mMatrix2D->PreRotate(angle);
883 }
884
885 return this;
886 }
887
RotateSelf(double aRotX,const Optional<double> & aRotY,const Optional<double> & aRotZ)888 DOMMatrix* DOMMatrix::RotateSelf(double aRotX, const Optional<double>& aRotY,
889 const Optional<double>& aRotZ) {
890 double rotY;
891 double rotZ;
892 if (!aRotY.WasPassed() && !aRotZ.WasPassed()) {
893 rotZ = aRotX;
894 aRotX = 0;
895 rotY = 0;
896 } else {
897 rotY = aRotY.WasPassed() ? aRotY.Value() : 0;
898 rotZ = aRotZ.WasPassed() ? aRotZ.Value() : 0;
899 }
900
901 if (aRotX != 0 || rotY != 0) {
902 Ensure3DMatrix();
903 }
904
905 if (mMatrix3D) {
906 if (fmod(rotZ, 360) != 0) {
907 mMatrix3D->RotateZ(rotZ * radPerDegree);
908 }
909 if (fmod(rotY, 360) != 0) {
910 mMatrix3D->RotateY(rotY * radPerDegree);
911 }
912 if (fmod(aRotX, 360) != 0) {
913 mMatrix3D->RotateX(aRotX * radPerDegree);
914 }
915 } else if (fmod(rotZ, 360) != 0) {
916 *mMatrix2D = mMatrix2D->PreRotate(rotZ * radPerDegree);
917 }
918
919 return this;
920 }
921
RotateAxisAngleSelf(double aX,double aY,double aZ,double aAngle)922 DOMMatrix* DOMMatrix::RotateAxisAngleSelf(double aX, double aY, double aZ,
923 double aAngle) {
924 if (fmod(aAngle, 360) == 0) {
925 return this;
926 }
927
928 aAngle *= radPerDegree;
929
930 Ensure3DMatrix();
931 gfx::Matrix4x4Double m;
932 m.SetRotateAxisAngle(aX, aY, aZ, aAngle);
933
934 *mMatrix3D = m * *mMatrix3D;
935
936 return this;
937 }
938
SkewXSelf(double aSx)939 DOMMatrix* DOMMatrix::SkewXSelf(double aSx) {
940 if (fmod(aSx, 360) == 0) {
941 return this;
942 }
943
944 if (mMatrix3D) {
945 gfx::Matrix4x4Double m;
946 m._21 = tan(aSx * radPerDegree);
947 *mMatrix3D = m * *mMatrix3D;
948 } else {
949 gfx::MatrixDouble m;
950 m._21 = tan(aSx * radPerDegree);
951 *mMatrix2D = m * *mMatrix2D;
952 }
953
954 return this;
955 }
956
SkewYSelf(double aSy)957 DOMMatrix* DOMMatrix::SkewYSelf(double aSy) {
958 if (fmod(aSy, 360) == 0) {
959 return this;
960 }
961
962 if (mMatrix3D) {
963 gfx::Matrix4x4Double m;
964 m._12 = tan(aSy * radPerDegree);
965 *mMatrix3D = m * *mMatrix3D;
966 } else {
967 gfx::MatrixDouble m;
968 m._12 = tan(aSy * radPerDegree);
969 *mMatrix2D = m * *mMatrix2D;
970 }
971
972 return this;
973 }
974
InvertSelf()975 DOMMatrix* DOMMatrix::InvertSelf() {
976 if (mMatrix3D) {
977 if (!mMatrix3D->Invert()) {
978 mMatrix3D->SetNAN();
979 }
980 } else if (!mMatrix2D->Invert()) {
981 mMatrix2D = nullptr;
982
983 mMatrix3D = MakeUnique<gfx::Matrix4x4Double>();
984 mMatrix3D->SetNAN();
985 }
986
987 return this;
988 }
989
SetMatrixValue(const nsACString & aTransformList,ErrorResult & aRv)990 DOMMatrixReadOnly* DOMMatrixReadOnly::SetMatrixValue(
991 const nsACString& aTransformList, ErrorResult& aRv) {
992 // An empty string is a no-op.
993 if (aTransformList.IsEmpty()) {
994 return this;
995 }
996
997 gfx::Matrix4x4 transform;
998 bool contains3dTransform = false;
999 if (!ServoCSSParser::ParseTransformIntoMatrix(
1000 aTransformList, contains3dTransform, transform)) {
1001 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
1002 return nullptr;
1003 }
1004
1005 if (!contains3dTransform) {
1006 mMatrix3D = nullptr;
1007 if (!mMatrix2D) {
1008 mMatrix2D = MakeUnique<gfx::MatrixDouble>();
1009 }
1010
1011 SetA(transform._11);
1012 SetB(transform._12);
1013 SetC(transform._21);
1014 SetD(transform._22);
1015 SetE(transform._41);
1016 SetF(transform._42);
1017 } else {
1018 mMatrix3D = MakeUnique<gfx::Matrix4x4Double>(transform);
1019 mMatrix2D = nullptr;
1020 }
1021
1022 return this;
1023 }
1024
SetMatrixValue(const nsACString & aTransformList,ErrorResult & aRv)1025 DOMMatrix* DOMMatrix::SetMatrixValue(const nsACString& aTransformList,
1026 ErrorResult& aRv) {
1027 DOMMatrixReadOnly::SetMatrixValue(aTransformList, aRv);
1028 return this;
1029 }
1030
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)1031 JSObject* DOMMatrix::WrapObject(JSContext* aCx,
1032 JS::Handle<JSObject*> aGivenProto) {
1033 return DOMMatrix_Binding::Wrap(aCx, this, aGivenProto);
1034 }
1035
1036 } // namespace mozilla::dom
1037