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