1use std::cell::Cell;
6use std::{f64, ptr};
7
8use cssparser::{Parser, ParserInput};
9use dom_struct::dom_struct;
10use euclid::Angle;
11use euclid::default::{Transform2D, Transform3D};
12use js::context::NoGC;
13use js::conversions::jsstr_to_string;
14use js::jsapi::JSObject;
15use js::jsval;
16use js::rust::{CustomAutoRooterGuard, HandleObject, ToString};
17use js::typedarray::{Float32Array, Float64Array, HeapFloat32Array, HeapFloat64Array};
18use rustc_hash::FxHashMap;
19use script_bindings::cell::{DomRefCell, Ref};
20use script_bindings::cformat;
21use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
22use script_bindings::trace::RootedTraceableBox;
23use servo_base::id::{DomMatrixId, DomMatrixIndex};
24use servo_constellation_traits::DomMatrix;
25use style::stylesheets::CssRuleType;
26use style_traits::ParsingMode;
27
28use crate::css::{ANONYMOUS_CONTENT_URL_DATA, parser_context_for_anonymous_content};
29use crate::dom::bindings::buffer_source::create_buffer_source;
30use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::{
31 DOMMatrix2DInit, DOMMatrixInit, DOMMatrixMethods,
32};
33use crate::dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods;
34use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
35use crate::dom::bindings::codegen::UnionTypes::StringOrUnrestrictedDoubleSequence;
36use crate::dom::bindings::error;
37use crate::dom::bindings::error::Fallible;
38use crate::dom::bindings::inheritance::Castable;
39use crate::dom::bindings::reflector::DomGlobal;
40use crate::dom::bindings::root::DomRoot;
41use crate::dom::bindings::serializable::Serializable;
42use crate::dom::bindings::str::DOMString;
43use crate::dom::bindings::structuredclone::StructuredData;
44use crate::dom::dommatrix::DOMMatrix;
45use crate::dom::dompoint::DOMPoint;
46use crate::dom::globalscope::GlobalScope;
47use crate::dom::window::Window;
48
49#[dom_struct]
50#[expect(non_snake_case)]
51pub(crate) struct DOMMatrixReadOnly {
52 reflector_: Reflector,
53 #[no_trace]
54 matrix: DomRefCell<Transform3D<f64>>,
55 is2D: Cell<bool>,
56}
57
58#[expect(non_snake_case)]
59impl DOMMatrixReadOnly {
60 pub(crate) fn new(
61 cx: &mut js::context::JSContext,
62 global: &GlobalScope,
63 is2D: bool,
64 matrix: Transform3D<f64>,
65 ) -> DomRoot<Self> {
66 Self::new_with_proto(cx, global, None, is2D, matrix)
67 }
68
69 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
70 fn new_with_proto(
71 cx: &mut js::context::JSContext,
72 global: &GlobalScope,
73 proto: Option<HandleObject>,
74 is2D: bool,
75 matrix: Transform3D<f64>,
76 ) -> DomRoot<Self> {
77 let dommatrix = Self::new_inherited(is2D, matrix);
78 reflect_dom_object_with_proto(cx, Box::new(dommatrix), global, proto)
79 }
80
81 pub(crate) fn new_inherited(is2D: bool, matrix: Transform3D<f64>) -> Self {
82 DOMMatrixReadOnly {
83 reflector_: Reflector::new(),
84 matrix: DomRefCell::new(matrix),
85 is2D: Cell::new(is2D),
86 }
87 }
88
89 pub(crate) fn matrix(&self) -> Ref<'_, Transform3D<f64>> {
90 self.matrix.borrow()
91 }
92
93 pub(crate) fn set_matrix(&self, value: Transform3D<f64>) {
94 self.set_m11(value.m11);
95 self.set_m12(value.m12);
96 self.set_m13(value.m13);
97 self.set_m14(value.m14);
98 self.set_m21(value.m21);
99 self.set_m22(value.m22);
100 self.set_m23(value.m23);
101 self.set_m24(value.m24);
102 self.set_m31(value.m31);
103 self.set_m32(value.m32);
104 self.set_m33(value.m33);
105 self.set_m34(value.m34);
106 self.set_m41(value.m41);
107 self.set_m42(value.m42);
108 self.set_m43(value.m43);
109 self.set_m44(value.m44);
110 }
111
112 pub(crate) fn is2D(&self) -> bool {
113 self.is2D.get()
114 }
115
116 pub(crate) fn set_is2D(&self, value: bool) {
117 self.is2D.set(value);
118 }
119
120 pub(crate) fn set_m11(&self, value: f64) {
122 self.matrix.borrow_mut().m11 = value;
123 }
124
125 pub(crate) fn set_m12(&self, value: f64) {
127 self.matrix.borrow_mut().m12 = value;
128 }
129
130 pub(crate) fn set_m13(&self, value: f64) {
132 self.matrix.borrow_mut().m13 = value;
136 if value.abs() != 0. {
137 self.is2D.set(false);
138 }
139 }
140
141 pub(crate) fn set_m14(&self, value: f64) {
143 self.matrix.borrow_mut().m14 = value;
146
147 if value.abs() != 0. {
148 self.is2D.set(false);
149 }
150 }
151
152 pub(crate) fn set_m21(&self, value: f64) {
154 self.matrix.borrow_mut().m21 = value;
155 }
156
157 pub(crate) fn set_m22(&self, value: f64) {
159 self.matrix.borrow_mut().m22 = value;
160 }
161
162 pub(crate) fn set_m23(&self, value: f64) {
164 self.matrix.borrow_mut().m23 = value;
167
168 if value.abs() != 0. {
169 self.is2D.set(false);
170 }
171 }
172
173 pub(crate) fn set_m24(&self, value: f64) {
175 self.matrix.borrow_mut().m24 = value;
178
179 if value.abs() != 0. {
180 self.is2D.set(false);
181 }
182 }
183
184 pub(crate) fn set_m31(&self, value: f64) {
186 self.matrix.borrow_mut().m31 = value;
189
190 if value.abs() != 0. {
191 self.is2D.set(false);
192 }
193 }
194
195 pub(crate) fn set_m32(&self, value: f64) {
197 self.matrix.borrow_mut().m32 = value;
200
201 if value.abs() != 0. {
202 self.is2D.set(false);
203 }
204 }
205
206 pub(crate) fn set_m33(&self, value: f64) {
208 self.matrix.borrow_mut().m33 = value;
211
212 if value != 1. {
213 self.is2D.set(false);
214 }
215 }
216
217 pub(crate) fn set_m34(&self, value: f64) {
219 self.matrix.borrow_mut().m34 = value;
222
223 if value.abs() != 0. {
224 self.is2D.set(false);
225 }
226 }
227
228 pub(crate) fn set_m41(&self, value: f64) {
230 self.matrix.borrow_mut().m41 = value;
231 }
232
233 pub(crate) fn set_m42(&self, value: f64) {
235 self.matrix.borrow_mut().m42 = value;
236 }
237
238 pub(crate) fn set_m43(&self, value: f64) {
240 self.matrix.borrow_mut().m43 = value;
243
244 if value.abs() != 0. {
245 self.is2D.set(false);
246 }
247 }
248
249 pub(crate) fn set_m44(&self, value: f64) {
251 self.matrix.borrow_mut().m44 = value;
254
255 if value != 1. {
256 self.is2D.set(false);
257 }
258 }
259
260 pub(crate) fn multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
262 dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
264 let mut matrix = self.matrix.borrow_mut();
266 *matrix = other_matrix.then(&matrix);
267 if !is2D {
269 self.is2D.set(false);
270 }
271 })
273 }
274
275 pub(crate) fn pre_multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
277 dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
279 let mut matrix = self.matrix.borrow_mut();
281 *matrix = matrix.then(&other_matrix);
282 if !is2D {
284 self.is2D.set(false);
285 }
286 })
288 }
289
290 pub(crate) fn translate_self(&self, tx: f64, ty: f64, tz: f64) {
292 let translation = Transform3D::translation(tx, ty, tz);
294 let mut matrix = self.matrix.borrow_mut();
295 *matrix = translation.then(&matrix);
296 if tz != 0.0 {
298 self.is2D.set(false);
299 }
300 }
302
303 pub(crate) fn scale_self(
305 &self,
306 scaleX: f64,
307 scaleY: Option<f64>,
308 scaleZ: f64,
309 mut originX: f64,
310 mut originY: f64,
311 mut originZ: f64,
312 ) {
313 self.translate_self(originX, originY, originZ);
315 let scaleY = scaleY.unwrap_or(scaleX);
317 {
319 let scale3D = Transform3D::scale(scaleX, scaleY, scaleZ);
320 let mut matrix = self.matrix.borrow_mut();
321 *matrix = scale3D.then(&matrix);
322 }
323 originX = -originX;
325 originY = -originY;
326 originZ = -originZ;
327 self.translate_self(originX, originY, originZ);
329 if scaleZ != 1.0 || originZ != 0.0 {
331 self.is2D.set(false);
332 }
333 }
335
336 pub(crate) fn scale_3d_self(&self, scale: f64, originX: f64, originY: f64, originZ: f64) {
338 self.translate_self(originX, originY, originZ);
340 {
342 let scale3D = Transform3D::scale(scale, scale, scale);
343 let mut matrix = self.matrix.borrow_mut();
344 *matrix = scale3D.then(&matrix);
345 }
346 self.translate_self(-originX, -originY, -originZ);
348 if scale != 1.0 {
350 self.is2D.set(false);
351 }
352 }
354
355 pub(crate) fn rotate_self(&self, mut rotX: f64, mut rotY: Option<f64>, mut rotZ: Option<f64>) {
357 if rotY.is_none() && rotZ.is_none() {
359 rotZ = Some(rotX);
360 rotX = 0.0;
361 rotY = Some(0.0);
362 }
363 let rotY = rotY.unwrap_or(0.0);
365 let rotZ = rotZ.unwrap_or(0.0);
367 if rotX != 0.0 || rotY != 0.0 {
369 self.is2D.set(false);
370 }
371 if rotZ != 0.0 {
372 let rotation = Transform3D::rotation(0.0, 0.0, 1.0, Angle::radians(rotZ.to_radians()));
374 let mut matrix = self.matrix.borrow_mut();
375 *matrix = rotation.then(&matrix);
376 }
377 if rotY != 0.0 {
378 let rotation = Transform3D::rotation(0.0, 1.0, 0.0, Angle::radians(rotY.to_radians()));
380 let mut matrix = self.matrix.borrow_mut();
381 *matrix = rotation.then(&matrix);
382 }
383 if rotX != 0.0 {
384 let rotation = Transform3D::rotation(1.0, 0.0, 0.0, Angle::radians(rotX.to_radians()));
386 let mut matrix = self.matrix.borrow_mut();
387 *matrix = rotation.then(&matrix);
388 }
389 }
391
392 pub(crate) fn rotate_from_vector_self(&self, x: f64, y: f64) {
394 if y != 0.0 || x < 0.0 {
396 let rotZ = Angle::radians(f64::atan2(y, x));
398 let rotation = Transform3D::rotation(0.0, 0.0, 1.0, rotZ);
399 let mut matrix = self.matrix.borrow_mut();
400 *matrix = rotation.then(&matrix);
401 }
402 }
404
405 pub(crate) fn rotate_axis_angle_self(&self, x: f64, y: f64, z: f64, angle: f64) {
407 let (norm_x, norm_y, norm_z) = normalize_point(x, y, z);
409 let rotation =
411 Transform3D::rotation(norm_x, norm_y, norm_z, Angle::radians(angle.to_radians()));
412 let mut matrix = self.matrix.borrow_mut();
413 *matrix = rotation.then(&matrix);
414 if x != 0.0 || y != 0.0 {
416 self.is2D.set(false);
417 }
418 }
420
421 pub(crate) fn skew_x_self(&self, sx: f64) {
423 let skew = Transform3D::skew(Angle::radians(sx.to_radians()), Angle::radians(0.0));
425 let mut matrix = self.matrix.borrow_mut();
426 *matrix = skew.then(&matrix);
427 }
429
430 pub(crate) fn skew_y_self(&self, sy: f64) {
432 let skew = Transform3D::skew(Angle::radians(0.0), Angle::radians(sy.to_radians()));
434 let mut matrix = self.matrix.borrow_mut();
435 *matrix = skew.then(&matrix);
436 }
438
439 pub(crate) fn invert_self(&self) {
441 let mut matrix = self.matrix.borrow_mut();
442 let inverted = match self.is2D() {
444 true => matrix.to_2d().inverse().map(|m| m.to_3d()),
445 false => matrix.inverse(),
446 };
447
448 *matrix = inverted.unwrap_or_else(|| -> Transform3D<f64> {
451 self.is2D.set(false);
452 Transform3D::new(
453 f64::NAN,
454 f64::NAN,
455 f64::NAN,
456 f64::NAN,
457 f64::NAN,
458 f64::NAN,
459 f64::NAN,
460 f64::NAN,
461 f64::NAN,
462 f64::NAN,
463 f64::NAN,
464 f64::NAN,
465 f64::NAN,
466 f64::NAN,
467 f64::NAN,
468 f64::NAN,
469 )
470 });
471 }
473}
474
475#[expect(non_snake_case)]
476impl DOMMatrixReadOnlyMethods<crate::DomTypeHolder> for DOMMatrixReadOnly {
477 fn Constructor(
479 cx: &mut js::context::JSContext,
480 global: &GlobalScope,
481 proto: Option<HandleObject>,
482 init: Option<StringOrUnrestrictedDoubleSequence>,
483 ) -> Fallible<DomRoot<Self>> {
484 if init.is_none() {
485 return Ok(Self::new_with_proto(
486 cx,
487 global,
488 proto,
489 true,
490 Transform3D::identity(),
491 ));
492 }
493 match init.unwrap() {
494 StringOrUnrestrictedDoubleSequence::String(ref s) => {
495 if !global.is::<Window>() {
496 return Err(error::Error::Type(
497 c"String constructor is only supported in the main thread.".to_owned(),
498 ));
499 }
500 if s.is_empty() {
501 return Ok(Self::new(cx, global, true, Transform3D::identity()));
502 }
503 transform_to_matrix(&s.str())
504 .map(|(is2D, matrix)| Self::new_with_proto(cx, global, proto, is2D, matrix))
505 },
506 StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => {
507 entries_to_matrix(&entries[..])
508 .map(|(is2D, matrix)| Self::new_with_proto(cx, global, proto, is2D, matrix))
509 },
510 }
511 }
512
513 fn FromMatrix(
515 cx: &mut js::context::JSContext,
516 global: &GlobalScope,
517 other: &DOMMatrixInit,
518 ) -> Fallible<DomRoot<Self>> {
519 dommatrixinit_to_matrix(other).map(|(is2D, matrix)| Self::new(cx, global, is2D, matrix))
520 }
521
522 fn FromFloat32Array(
524 cx: &mut js::context::JSContext,
525 global: &GlobalScope,
526 array: CustomAutoRooterGuard<Float32Array>,
527 ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
528 let vec: Vec<f64> = array
529 .to_vec()
530 .unwrap_or_default()
531 .iter()
532 .map(|&x| x as f64)
533 .collect();
534 DOMMatrixReadOnly::Constructor(
535 cx,
536 global,
537 None,
538 Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
539 )
540 }
541
542 fn FromFloat64Array(
544 cx: &mut js::context::JSContext,
545 global: &GlobalScope,
546 array: CustomAutoRooterGuard<Float64Array>,
547 ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
548 let vec: Vec<f64> = array.to_vec().unwrap_or_default();
549 DOMMatrixReadOnly::Constructor(
550 cx,
551 global,
552 None,
553 Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
554 )
555 }
556
557 fn M11(&self) -> f64 {
559 self.matrix.borrow().m11
560 }
561
562 fn M12(&self) -> f64 {
564 self.matrix.borrow().m12
565 }
566
567 fn M13(&self) -> f64 {
569 self.matrix.borrow().m13
570 }
571
572 fn M14(&self) -> f64 {
574 self.matrix.borrow().m14
575 }
576
577 fn M21(&self) -> f64 {
579 self.matrix.borrow().m21
580 }
581
582 fn M22(&self) -> f64 {
584 self.matrix.borrow().m22
585 }
586
587 fn M23(&self) -> f64 {
589 self.matrix.borrow().m23
590 }
591
592 fn M24(&self) -> f64 {
594 self.matrix.borrow().m24
595 }
596
597 fn M31(&self) -> f64 {
599 self.matrix.borrow().m31
600 }
601
602 fn M32(&self) -> f64 {
604 self.matrix.borrow().m32
605 }
606
607 fn M33(&self) -> f64 {
609 self.matrix.borrow().m33
610 }
611
612 fn M34(&self) -> f64 {
614 self.matrix.borrow().m34
615 }
616
617 fn M41(&self) -> f64 {
619 self.matrix.borrow().m41
620 }
621
622 fn M42(&self) -> f64 {
624 self.matrix.borrow().m42
625 }
626
627 fn M43(&self) -> f64 {
629 self.matrix.borrow().m43
630 }
631
632 fn M44(&self) -> f64 {
634 self.matrix.borrow().m44
635 }
636
637 fn A(&self) -> f64 {
639 self.M11()
640 }
641
642 fn B(&self) -> f64 {
644 self.M12()
645 }
646
647 fn C(&self) -> f64 {
649 self.M21()
650 }
651
652 fn D(&self) -> f64 {
654 self.M22()
655 }
656
657 fn E(&self) -> f64 {
659 self.M41()
660 }
661
662 fn F(&self) -> f64 {
664 self.M42()
665 }
666
667 fn Is2D(&self) -> bool {
669 self.is2D.get()
670 }
671
672 fn IsIdentity(&self) -> bool {
674 let matrix = self.matrix.borrow();
675 matrix.m12 == 0.0 &&
676 matrix.m13 == 0.0 &&
677 matrix.m14 == 0.0 &&
678 matrix.m21 == 0.0 &&
679 matrix.m23 == 0.0 &&
680 matrix.m24 == 0.0 &&
681 matrix.m31 == 0.0 &&
682 matrix.m32 == 0.0 &&
683 matrix.m34 == 0.0 &&
684 matrix.m41 == 0.0 &&
685 matrix.m42 == 0.0 &&
686 matrix.m43 == 0.0 &&
687 matrix.m11 == 1.0 &&
688 matrix.m22 == 1.0 &&
689 matrix.m33 == 1.0 &&
690 matrix.m44 == 1.0
691 }
692
693 fn Translate(
695 &self,
696 cx: &mut js::context::JSContext,
697 tx: f64,
698 ty: f64,
699 tz: f64,
700 ) -> DomRoot<DOMMatrix> {
701 DOMMatrix::from_readonly(&self.global(), self, cx).TranslateSelf(tx, ty, tz)
702 }
703
704 fn Scale(
706 &self,
707 cx: &mut js::context::JSContext,
708 scaleX: f64,
709 scaleY: Option<f64>,
710 scaleZ: f64,
711 originX: f64,
712 originY: f64,
713 originZ: f64,
714 ) -> DomRoot<DOMMatrix> {
715 DOMMatrix::from_readonly(&self.global(), self, cx)
716 .ScaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
717 }
718
719 fn ScaleNonUniform(
721 &self,
722 cx: &mut js::context::JSContext,
723 scaleX: f64,
724 scaleY: f64,
725 ) -> DomRoot<DOMMatrix> {
726 DOMMatrix::from_readonly(&self.global(), self, cx).ScaleSelf(
727 scaleX,
728 Some(scaleY),
729 1.0,
730 0.0,
731 0.0,
732 0.0,
733 )
734 }
735
736 fn Scale3d(
738 &self,
739 cx: &mut js::context::JSContext,
740 scale: f64,
741 originX: f64,
742 originY: f64,
743 originZ: f64,
744 ) -> DomRoot<DOMMatrix> {
745 DOMMatrix::from_readonly(&self.global(), self, cx)
746 .Scale3dSelf(scale, originX, originY, originZ)
747 }
748
749 fn Rotate(
751 &self,
752 cx: &mut js::context::JSContext,
753 rotX: f64,
754 rotY: Option<f64>,
755 rotZ: Option<f64>,
756 ) -> DomRoot<DOMMatrix> {
757 DOMMatrix::from_readonly(&self.global(), self, cx).RotateSelf(rotX, rotY, rotZ)
758 }
759
760 fn RotateFromVector(
762 &self,
763 cx: &mut js::context::JSContext,
764 x: f64,
765 y: f64,
766 ) -> DomRoot<DOMMatrix> {
767 DOMMatrix::from_readonly(&self.global(), self, cx).RotateFromVectorSelf(x, y)
768 }
769
770 fn RotateAxisAngle(
772 &self,
773 cx: &mut js::context::JSContext,
774 x: f64,
775 y: f64,
776 z: f64,
777 angle: f64,
778 ) -> DomRoot<DOMMatrix> {
779 DOMMatrix::from_readonly(&self.global(), self, cx).RotateAxisAngleSelf(x, y, z, angle)
780 }
781
782 fn SkewX(&self, cx: &mut js::context::JSContext, sx: f64) -> DomRoot<DOMMatrix> {
784 DOMMatrix::from_readonly(&self.global(), self, cx).SkewXSelf(sx)
785 }
786
787 fn SkewY(&self, cx: &mut js::context::JSContext, sy: f64) -> DomRoot<DOMMatrix> {
789 DOMMatrix::from_readonly(&self.global(), self, cx).SkewYSelf(sy)
790 }
791
792 fn Multiply(
794 &self,
795 cx: &mut js::context::JSContext,
796 other: &DOMMatrixInit,
797 ) -> Fallible<DomRoot<DOMMatrix>> {
798 DOMMatrix::from_readonly(&self.global(), self, cx).MultiplySelf(other)
799 }
800
801 fn FlipX(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
803 let is2D = self.is2D.get();
804 let flip = Transform3D::new(
805 -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
806 );
807 let matrix = flip.then(&self.matrix.borrow());
808 DOMMatrix::new(cx, &self.global(), is2D, matrix)
809 }
810
811 fn FlipY(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
813 let is2D = self.is2D.get();
814 let flip = Transform3D::new(
815 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
816 );
817 let matrix = flip.then(&self.matrix.borrow());
818 DOMMatrix::new(cx, &self.global(), is2D, matrix)
819 }
820
821 fn Inverse(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
823 DOMMatrix::from_readonly(&self.global(), self, cx).InvertSelf()
824 }
825
826 fn TransformPoint(
828 &self,
829 cx: &mut js::context::JSContext,
830 point: &DOMPointInit,
831 ) -> DomRoot<DOMPoint> {
832 let mat = self.matrix.borrow();
839 let x = point.x * mat.m11 + point.y * mat.m21 + point.z * mat.m31 + point.w * mat.m41;
840 let y = point.x * mat.m12 + point.y * mat.m22 + point.z * mat.m32 + point.w * mat.m42;
841 let z = point.x * mat.m13 + point.y * mat.m23 + point.z * mat.m33 + point.w * mat.m43;
842 let w = point.x * mat.m14 + point.y * mat.m24 + point.z * mat.m34 + point.w * mat.m44;
843
844 DOMPoint::new(cx, &self.global(), x, y, z, w)
845 }
846
847 fn ToFloat32Array(
849 &self,
850 cx: &mut js::context::JSContext,
851 ) -> RootedTraceableBox<HeapFloat32Array> {
852 let vec: Vec<f32> = self
853 .matrix
854 .borrow()
855 .to_array()
856 .iter()
857 .map(|&x| x as f32)
858 .collect();
859 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
860 create_buffer_source(cx, &vec, array.handle_mut())
861 .expect("Converting matrix to float32 array should never fail")
862 }
863
864 fn ToFloat64Array(
866 &self,
867 cx: &mut js::context::JSContext,
868 ) -> RootedTraceableBox<HeapFloat64Array> {
869 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
870 create_buffer_source(cx, &self.matrix.borrow().to_array(), array.handle_mut())
871 .expect("Converting matrix to float64 array should never fail")
872 }
873
874 #[expect(unsafe_code)]
876 fn Stringifier(&self, cx: &mut js::context::JSContext) -> Fallible<DOMString> {
877 let mat = self.matrix.borrow();
880 if !mat.m11.is_finite() ||
881 !mat.m12.is_finite() ||
882 !mat.m13.is_finite() ||
883 !mat.m14.is_finite() ||
884 !mat.m21.is_finite() ||
885 !mat.m22.is_finite() ||
886 !mat.m23.is_finite() ||
887 !mat.m24.is_finite() ||
888 !mat.m31.is_finite() ||
889 !mat.m32.is_finite() ||
890 !mat.m33.is_finite() ||
891 !mat.m34.is_finite() ||
892 !mat.m41.is_finite() ||
893 !mat.m42.is_finite() ||
894 !mat.m43.is_finite() ||
895 !mat.m44.is_finite()
896 {
897 return Err(error::Error::InvalidState(None));
898 }
899
900 let mut to_string = |f: f64| {
901 rooted!(&in(cx) let rooted_value = jsval::DoubleValue(f));
902 let serialization =
903 std::ptr::NonNull::new(unsafe { ToString(cx, rooted_value.handle()) })
904 .expect("Pointer cannot be null");
905 unsafe { jsstr_to_string(cx, serialization) }
906 };
907
908 let string = if self.is2D() {
911 format!(
925 "matrix({}, {}, {}, {}, {}, {})",
926 to_string(mat.m11),
927 to_string(mat.m12),
928 to_string(mat.m21),
929 to_string(mat.m22),
930 to_string(mat.m41),
931 to_string(mat.m42)
932 )
933 .into()
934 }
935 else {
937 format!(
966 "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
967 to_string(mat.m11),
968 to_string(mat.m12),
969 to_string(mat.m13),
970 to_string(mat.m14),
971 to_string(mat.m21),
972 to_string(mat.m22),
973 to_string(mat.m23),
974 to_string(mat.m24),
975 to_string(mat.m31),
976 to_string(mat.m32),
977 to_string(mat.m33),
978 to_string(mat.m34),
979 to_string(mat.m41),
980 to_string(mat.m42),
981 to_string(mat.m43),
982 to_string(mat.m44)
983 )
984 .into()
985 };
986
987 Ok(string)
988 }
989}
990
991impl Serializable for DOMMatrixReadOnly {
992 type Index = DomMatrixIndex;
993 type Data = DomMatrix;
994
995 fn serialize(&self, _no_gc: &NoGC) -> Result<(DomMatrixId, Self::Data), ()> {
996 let serialized = if self.is2D() {
997 DomMatrix {
998 matrix: Transform3D::new(
999 self.M11(),
1000 self.M12(),
1001 f64::NAN,
1002 f64::NAN,
1003 self.M21(),
1004 self.M22(),
1005 f64::NAN,
1006 f64::NAN,
1007 f64::NAN,
1008 f64::NAN,
1009 f64::NAN,
1010 f64::NAN,
1011 self.M41(),
1012 self.M42(),
1013 f64::NAN,
1014 f64::NAN,
1015 ),
1016 is_2d: true,
1017 }
1018 } else {
1019 DomMatrix {
1020 matrix: *self.matrix(),
1021 is_2d: false,
1022 }
1023 };
1024 Ok((DomMatrixId::new(), serialized))
1025 }
1026
1027 fn deserialize(
1028 cx: &mut js::context::JSContext,
1029 owner: &GlobalScope,
1030 serialized: Self::Data,
1031 ) -> Result<DomRoot<Self>, ()>
1032 where
1033 Self: Sized,
1034 {
1035 if serialized.is_2d {
1036 Ok(Self::new(
1037 cx,
1038 owner,
1039 true,
1040 Transform3D::new(
1041 serialized.matrix.m11,
1042 serialized.matrix.m12,
1043 0.0,
1044 0.0,
1045 serialized.matrix.m21,
1046 serialized.matrix.m22,
1047 0.0,
1048 0.0,
1049 0.0,
1050 0.0,
1051 1.0,
1052 0.0,
1053 serialized.matrix.m41,
1054 serialized.matrix.m42,
1055 0.0,
1056 1.0,
1057 ),
1058 ))
1059 } else {
1060 Ok(Self::new(cx, owner, false, serialized.matrix))
1061 }
1062 }
1063
1064 fn serialized_storage<'a>(
1065 data: StructuredData<'a, '_>,
1066 ) -> &'a mut Option<FxHashMap<DomMatrixId, Self::Data>> {
1067 match data {
1068 StructuredData::Reader(reader) => &mut reader.matrices,
1069 StructuredData::Writer(writer) => &mut writer.matrices,
1070 }
1071 }
1072}
1073
1074pub(crate) fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Transform3D<f64>)> {
1076 if let Ok(array) = entries.try_into() {
1077 Ok((true, Transform2D::from_array(array).to_3d()))
1078 } else if let Ok(array) = entries.try_into() {
1079 Ok((false, Transform3D::from_array(array)))
1080 } else {
1081 let err_msg = cformat!("Expected 6 or 16 entries, but found {}.", entries.len());
1082 Err(error::Error::Type(err_msg))
1083 }
1084}
1085
1086fn validate_and_fixup_2d(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1088 let same_value_zero = |x: f64, y: f64| -> bool { x.is_nan() && y.is_nan() || x == y };
1090
1091 if dict.a.is_some() &&
1094 dict.m11.is_some() &&
1095 !same_value_zero(dict.a.unwrap(), dict.m11.unwrap()) ||
1096 dict.b.is_some() &&
1097 dict.m12.is_some() &&
1098 !same_value_zero(dict.b.unwrap(), dict.m12.unwrap()) ||
1099 dict.c.is_some() &&
1100 dict.m21.is_some() &&
1101 !same_value_zero(dict.c.unwrap(), dict.m21.unwrap()) ||
1102 dict.d.is_some() &&
1103 dict.m22.is_some() &&
1104 !same_value_zero(dict.d.unwrap(), dict.m22.unwrap()) ||
1105 dict.e.is_some() &&
1106 dict.m41.is_some() &&
1107 !same_value_zero(dict.e.unwrap(), dict.m41.unwrap()) ||
1108 dict.f.is_some() &&
1109 dict.m42.is_some() &&
1110 !same_value_zero(dict.f.unwrap(), dict.m42.unwrap())
1111 {
1112 return Err(error::Error::Type(
1113 c"Property mismatch on matrix initialization.".to_owned(),
1114 ));
1115 }
1116
1117 let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0));
1120
1121 let m12 = dict.m12.unwrap_or(dict.b.unwrap_or(0.0));
1124
1125 let m21 = dict.m21.unwrap_or(dict.c.unwrap_or(0.0));
1128
1129 let m22 = dict.m22.unwrap_or(dict.d.unwrap_or(1.0));
1132
1133 let m41 = dict.m41.unwrap_or(dict.e.unwrap_or(0.0));
1136
1137 let m42 = dict.m42.unwrap_or(dict.f.unwrap_or(0.0));
1140
1141 Ok(Transform2D::new(m11, m12, m21, m22, m41, m42))
1142}
1143
1144fn validate_and_fixup(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1146 let transform2d = validate_and_fixup_2d(&dict.parent)?;
1148
1149 if dict.is2D == Some(true) &&
1154 (dict.m13 != 0.0 ||
1155 dict.m14 != 0.0 ||
1156 dict.m23 != 0.0 ||
1157 dict.m24 != 0.0 ||
1158 dict.m31 != 0.0 ||
1159 dict.m32 != 0.0 ||
1160 dict.m34 != 0.0 ||
1161 dict.m43 != 0.0 ||
1162 dict.m33 != 1.0 ||
1163 dict.m44 != 1.0)
1164 {
1165 return Err(error::Error::Type(
1166 c"The is2D member is set to true but the input matrix is a 3d matrix.".to_owned(),
1167 ));
1168 }
1169
1170 let mut is_2d = dict.is2D;
1171
1172 if is_2d.is_none() &&
1177 (dict.m13 != 0.0 ||
1178 dict.m14 != 0.0 ||
1179 dict.m23 != 0.0 ||
1180 dict.m24 != 0.0 ||
1181 dict.m31 != 0.0 ||
1182 dict.m32 != 0.0 ||
1183 dict.m34 != 0.0 ||
1184 dict.m43 != 0.0 ||
1185 dict.m33 != 1.0 ||
1186 dict.m44 != 1.0)
1187 {
1188 is_2d = Some(false);
1189 }
1190
1191 let is_2d = is_2d.unwrap_or(true);
1193
1194 let mut transform = transform2d.to_3d();
1195 transform.m13 = dict.m13;
1196 transform.m14 = dict.m14;
1197 transform.m23 = dict.m23;
1198 transform.m24 = dict.m24;
1199 transform.m31 = dict.m31;
1200 transform.m32 = dict.m32;
1201 transform.m33 = dict.m33;
1202 transform.m34 = dict.m34;
1203 transform.m43 = dict.m43;
1204 transform.m44 = dict.m44;
1205
1206 Ok((is_2d, transform))
1207}
1208
1209pub(crate) fn dommatrix2dinit_to_matrix(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1211 validate_and_fixup_2d(dict)
1217}
1218
1219pub(crate) fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1221 validate_and_fixup(dict)
1227}
1228
1229#[inline]
1230fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
1231 let len = (x * x + y * y + z * z).sqrt();
1232 if len == 0.0 {
1233 (0.0, 0.0, 0.0)
1234 } else {
1235 (x / len, y / len, z / len)
1236 }
1237}
1238
1239pub(crate) fn transform_to_matrix(value: &str) -> Fallible<(bool, Transform3D<f64>)> {
1240 use style::properties::longhands::transform;
1241
1242 let mut input = ParserInput::new(value);
1243 let mut parser = Parser::new(&mut input);
1244 let context = parser_context_for_anonymous_content(
1245 CssRuleType::Style,
1246 ParsingMode::DEFAULT,
1247 &ANONYMOUS_CONTENT_URL_DATA,
1248 );
1249
1250 let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) {
1251 Ok(result) => result,
1252 Err(..) => return Err(error::Error::Syntax(None)),
1253 };
1254
1255 let (m, is_3d) = match transform.to_transform_3d_matrix_f64(None) {
1256 Ok(result) => result,
1257 Err(..) => return Err(error::Error::Syntax(None)),
1258 };
1259
1260 Ok((!is_3d, m))
1261}