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_and_cx};
22use script_bindings::trace::RootedTraceableBox;
23use servo_base::id::{DomMatrixId, DomMatrixIndex};
24use servo_constellation_traits::DomMatrix;
25use style::stylesheets::CssRuleType;
26use style_traits::ParsingMode;
27use url::Url;
28
29use crate::css::parser_context_for_anonymous_content;
30use crate::dom::bindings::buffer_source::create_buffer_source;
31use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::{
32 DOMMatrix2DInit, DOMMatrixInit, DOMMatrixMethods,
33};
34use crate::dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods;
35use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
36use crate::dom::bindings::codegen::UnionTypes::StringOrUnrestrictedDoubleSequence;
37use crate::dom::bindings::error;
38use crate::dom::bindings::error::Fallible;
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::reflector::DomGlobal;
41use crate::dom::bindings::root::DomRoot;
42use crate::dom::bindings::serializable::Serializable;
43use crate::dom::bindings::str::DOMString;
44use crate::dom::bindings::structuredclone::StructuredData;
45use crate::dom::dommatrix::DOMMatrix;
46use crate::dom::dompoint::DOMPoint;
47use crate::dom::globalscope::GlobalScope;
48use crate::dom::window::Window;
49
50#[dom_struct]
51#[expect(non_snake_case)]
52pub(crate) struct DOMMatrixReadOnly {
53 reflector_: Reflector,
54 #[no_trace]
55 matrix: DomRefCell<Transform3D<f64>>,
56 is2D: Cell<bool>,
57}
58
59#[expect(non_snake_case)]
60impl DOMMatrixReadOnly {
61 pub(crate) fn new(
62 cx: &mut js::context::JSContext,
63 global: &GlobalScope,
64 is2D: bool,
65 matrix: Transform3D<f64>,
66 ) -> DomRoot<Self> {
67 Self::new_with_proto(cx, global, None, is2D, matrix)
68 }
69
70 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
71 fn new_with_proto(
72 cx: &mut js::context::JSContext,
73 global: &GlobalScope,
74 proto: Option<HandleObject>,
75 is2D: bool,
76 matrix: Transform3D<f64>,
77 ) -> DomRoot<Self> {
78 let dommatrix = Self::new_inherited(is2D, matrix);
79 reflect_dom_object_with_proto_and_cx(Box::new(dommatrix), global, proto, cx)
80 }
81
82 pub(crate) fn new_inherited(is2D: bool, matrix: Transform3D<f64>) -> Self {
83 DOMMatrixReadOnly {
84 reflector_: Reflector::new(),
85 matrix: DomRefCell::new(matrix),
86 is2D: Cell::new(is2D),
87 }
88 }
89
90 pub(crate) fn matrix(&self) -> Ref<'_, Transform3D<f64>> {
91 self.matrix.borrow()
92 }
93
94 pub(crate) fn set_matrix(&self, value: Transform3D<f64>) {
95 self.set_m11(value.m11);
96 self.set_m12(value.m12);
97 self.set_m13(value.m13);
98 self.set_m14(value.m14);
99 self.set_m21(value.m21);
100 self.set_m22(value.m22);
101 self.set_m23(value.m23);
102 self.set_m24(value.m24);
103 self.set_m31(value.m31);
104 self.set_m32(value.m32);
105 self.set_m33(value.m33);
106 self.set_m34(value.m34);
107 self.set_m41(value.m41);
108 self.set_m42(value.m42);
109 self.set_m43(value.m43);
110 self.set_m44(value.m44);
111 }
112
113 pub(crate) fn is2D(&self) -> bool {
114 self.is2D.get()
115 }
116
117 pub(crate) fn set_is2D(&self, value: bool) {
118 self.is2D.set(value);
119 }
120
121 pub(crate) fn set_m11(&self, value: f64) {
123 self.matrix.borrow_mut().m11 = value;
124 }
125
126 pub(crate) fn set_m12(&self, value: f64) {
128 self.matrix.borrow_mut().m12 = value;
129 }
130
131 pub(crate) fn set_m13(&self, value: f64) {
133 self.matrix.borrow_mut().m13 = value;
137 if value.abs() != 0. {
138 self.is2D.set(false);
139 }
140 }
141
142 pub(crate) fn set_m14(&self, value: f64) {
144 self.matrix.borrow_mut().m14 = value;
147
148 if value.abs() != 0. {
149 self.is2D.set(false);
150 }
151 }
152
153 pub(crate) fn set_m21(&self, value: f64) {
155 self.matrix.borrow_mut().m21 = value;
156 }
157
158 pub(crate) fn set_m22(&self, value: f64) {
160 self.matrix.borrow_mut().m22 = value;
161 }
162
163 pub(crate) fn set_m23(&self, value: f64) {
165 self.matrix.borrow_mut().m23 = value;
168
169 if value.abs() != 0. {
170 self.is2D.set(false);
171 }
172 }
173
174 pub(crate) fn set_m24(&self, value: f64) {
176 self.matrix.borrow_mut().m24 = value;
179
180 if value.abs() != 0. {
181 self.is2D.set(false);
182 }
183 }
184
185 pub(crate) fn set_m31(&self, value: f64) {
187 self.matrix.borrow_mut().m31 = value;
190
191 if value.abs() != 0. {
192 self.is2D.set(false);
193 }
194 }
195
196 pub(crate) fn set_m32(&self, value: f64) {
198 self.matrix.borrow_mut().m32 = value;
201
202 if value.abs() != 0. {
203 self.is2D.set(false);
204 }
205 }
206
207 pub(crate) fn set_m33(&self, value: f64) {
209 self.matrix.borrow_mut().m33 = value;
212
213 if value != 1. {
214 self.is2D.set(false);
215 }
216 }
217
218 pub(crate) fn set_m34(&self, value: f64) {
220 self.matrix.borrow_mut().m34 = value;
223
224 if value.abs() != 0. {
225 self.is2D.set(false);
226 }
227 }
228
229 pub(crate) fn set_m41(&self, value: f64) {
231 self.matrix.borrow_mut().m41 = value;
232 }
233
234 pub(crate) fn set_m42(&self, value: f64) {
236 self.matrix.borrow_mut().m42 = value;
237 }
238
239 pub(crate) fn set_m43(&self, value: f64) {
241 self.matrix.borrow_mut().m43 = value;
244
245 if value.abs() != 0. {
246 self.is2D.set(false);
247 }
248 }
249
250 pub(crate) fn set_m44(&self, value: f64) {
252 self.matrix.borrow_mut().m44 = value;
255
256 if value != 1. {
257 self.is2D.set(false);
258 }
259 }
260
261 pub(crate) fn multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
263 dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
265 let mut matrix = self.matrix.borrow_mut();
267 *matrix = other_matrix.then(&matrix);
268 if !is2D {
270 self.is2D.set(false);
271 }
272 })
274 }
275
276 pub(crate) fn pre_multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
278 dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
280 let mut matrix = self.matrix.borrow_mut();
282 *matrix = matrix.then(&other_matrix);
283 if !is2D {
285 self.is2D.set(false);
286 }
287 })
289 }
290
291 pub(crate) fn translate_self(&self, tx: f64, ty: f64, tz: f64) {
293 let translation = Transform3D::translation(tx, ty, tz);
295 let mut matrix = self.matrix.borrow_mut();
296 *matrix = translation.then(&matrix);
297 if tz != 0.0 {
299 self.is2D.set(false);
300 }
301 }
303
304 pub(crate) fn scale_self(
306 &self,
307 scaleX: f64,
308 scaleY: Option<f64>,
309 scaleZ: f64,
310 mut originX: f64,
311 mut originY: f64,
312 mut originZ: f64,
313 ) {
314 self.translate_self(originX, originY, originZ);
316 let scaleY = scaleY.unwrap_or(scaleX);
318 {
320 let scale3D = Transform3D::scale(scaleX, scaleY, scaleZ);
321 let mut matrix = self.matrix.borrow_mut();
322 *matrix = scale3D.then(&matrix);
323 }
324 originX = -originX;
326 originY = -originY;
327 originZ = -originZ;
328 self.translate_self(originX, originY, originZ);
330 if scaleZ != 1.0 || originZ != 0.0 {
332 self.is2D.set(false);
333 }
334 }
336
337 pub(crate) fn scale_3d_self(&self, scale: f64, originX: f64, originY: f64, originZ: f64) {
339 self.translate_self(originX, originY, originZ);
341 {
343 let scale3D = Transform3D::scale(scale, scale, scale);
344 let mut matrix = self.matrix.borrow_mut();
345 *matrix = scale3D.then(&matrix);
346 }
347 self.translate_self(-originX, -originY, -originZ);
349 if scale != 1.0 {
351 self.is2D.set(false);
352 }
353 }
355
356 pub(crate) fn rotate_self(&self, mut rotX: f64, mut rotY: Option<f64>, mut rotZ: Option<f64>) {
358 if rotY.is_none() && rotZ.is_none() {
360 rotZ = Some(rotX);
361 rotX = 0.0;
362 rotY = Some(0.0);
363 }
364 let rotY = rotY.unwrap_or(0.0);
366 let rotZ = rotZ.unwrap_or(0.0);
368 if rotX != 0.0 || rotY != 0.0 {
370 self.is2D.set(false);
371 }
372 if rotZ != 0.0 {
373 let rotation = Transform3D::rotation(0.0, 0.0, 1.0, Angle::radians(rotZ.to_radians()));
375 let mut matrix = self.matrix.borrow_mut();
376 *matrix = rotation.then(&matrix);
377 }
378 if rotY != 0.0 {
379 let rotation = Transform3D::rotation(0.0, 1.0, 0.0, Angle::radians(rotY.to_radians()));
381 let mut matrix = self.matrix.borrow_mut();
382 *matrix = rotation.then(&matrix);
383 }
384 if rotX != 0.0 {
385 let rotation = Transform3D::rotation(1.0, 0.0, 0.0, Angle::radians(rotX.to_radians()));
387 let mut matrix = self.matrix.borrow_mut();
388 *matrix = rotation.then(&matrix);
389 }
390 }
392
393 pub(crate) fn rotate_from_vector_self(&self, x: f64, y: f64) {
395 if y != 0.0 || x < 0.0 {
397 let rotZ = Angle::radians(f64::atan2(y, x));
399 let rotation = Transform3D::rotation(0.0, 0.0, 1.0, rotZ);
400 let mut matrix = self.matrix.borrow_mut();
401 *matrix = rotation.then(&matrix);
402 }
403 }
405
406 pub(crate) fn rotate_axis_angle_self(&self, x: f64, y: f64, z: f64, angle: f64) {
408 let (norm_x, norm_y, norm_z) = normalize_point(x, y, z);
410 let rotation =
412 Transform3D::rotation(norm_x, norm_y, norm_z, Angle::radians(angle.to_radians()));
413 let mut matrix = self.matrix.borrow_mut();
414 *matrix = rotation.then(&matrix);
415 if x != 0.0 || y != 0.0 {
417 self.is2D.set(false);
418 }
419 }
421
422 pub(crate) fn skew_x_self(&self, sx: f64) {
424 let skew = Transform3D::skew(Angle::radians(sx.to_radians()), Angle::radians(0.0));
426 let mut matrix = self.matrix.borrow_mut();
427 *matrix = skew.then(&matrix);
428 }
430
431 pub(crate) fn skew_y_self(&self, sy: f64) {
433 let skew = Transform3D::skew(Angle::radians(0.0), Angle::radians(sy.to_radians()));
435 let mut matrix = self.matrix.borrow_mut();
436 *matrix = skew.then(&matrix);
437 }
439
440 pub(crate) fn invert_self(&self) {
442 let mut matrix = self.matrix.borrow_mut();
443 let inverted = match self.is2D() {
445 true => matrix.to_2d().inverse().map(|m| m.to_3d()),
446 false => matrix.inverse(),
447 };
448
449 *matrix = inverted.unwrap_or_else(|| -> Transform3D<f64> {
452 self.is2D.set(false);
453 Transform3D::new(
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 f64::NAN,
470 )
471 });
472 }
474}
475
476#[expect(non_snake_case)]
477impl DOMMatrixReadOnlyMethods<crate::DomTypeHolder> for DOMMatrixReadOnly {
478 fn Constructor(
480 cx: &mut js::context::JSContext,
481 global: &GlobalScope,
482 proto: Option<HandleObject>,
483 init: Option<StringOrUnrestrictedDoubleSequence>,
484 ) -> Fallible<DomRoot<Self>> {
485 if init.is_none() {
486 return Ok(Self::new_with_proto(
487 cx,
488 global,
489 proto,
490 true,
491 Transform3D::identity(),
492 ));
493 }
494 match init.unwrap() {
495 StringOrUnrestrictedDoubleSequence::String(ref s) => {
496 if !global.is::<Window>() {
497 return Err(error::Error::Type(
498 c"String constructor is only supported in the main thread.".to_owned(),
499 ));
500 }
501 if s.is_empty() {
502 return Ok(Self::new(cx, global, true, Transform3D::identity()));
503 }
504 transform_to_matrix(&s.str())
505 .map(|(is2D, matrix)| Self::new_with_proto(cx, global, proto, is2D, matrix))
506 },
507 StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => {
508 entries_to_matrix(&entries[..])
509 .map(|(is2D, matrix)| Self::new_with_proto(cx, global, proto, is2D, matrix))
510 },
511 }
512 }
513
514 fn FromMatrix(
516 cx: &mut js::context::JSContext,
517 global: &GlobalScope,
518 other: &DOMMatrixInit,
519 ) -> Fallible<DomRoot<Self>> {
520 dommatrixinit_to_matrix(other).map(|(is2D, matrix)| Self::new(cx, global, is2D, matrix))
521 }
522
523 fn FromFloat32Array(
525 cx: &mut js::context::JSContext,
526 global: &GlobalScope,
527 array: CustomAutoRooterGuard<Float32Array>,
528 ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
529 let vec: Vec<f64> = array
530 .to_vec()
531 .unwrap_or_default()
532 .iter()
533 .map(|&x| x as f64)
534 .collect();
535 DOMMatrixReadOnly::Constructor(
536 cx,
537 global,
538 None,
539 Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
540 )
541 }
542
543 fn FromFloat64Array(
545 cx: &mut js::context::JSContext,
546 global: &GlobalScope,
547 array: CustomAutoRooterGuard<Float64Array>,
548 ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
549 let vec: Vec<f64> = array.to_vec().unwrap_or_default();
550 DOMMatrixReadOnly::Constructor(
551 cx,
552 global,
553 None,
554 Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
555 )
556 }
557
558 fn M11(&self) -> f64 {
560 self.matrix.borrow().m11
561 }
562
563 fn M12(&self) -> f64 {
565 self.matrix.borrow().m12
566 }
567
568 fn M13(&self) -> f64 {
570 self.matrix.borrow().m13
571 }
572
573 fn M14(&self) -> f64 {
575 self.matrix.borrow().m14
576 }
577
578 fn M21(&self) -> f64 {
580 self.matrix.borrow().m21
581 }
582
583 fn M22(&self) -> f64 {
585 self.matrix.borrow().m22
586 }
587
588 fn M23(&self) -> f64 {
590 self.matrix.borrow().m23
591 }
592
593 fn M24(&self) -> f64 {
595 self.matrix.borrow().m24
596 }
597
598 fn M31(&self) -> f64 {
600 self.matrix.borrow().m31
601 }
602
603 fn M32(&self) -> f64 {
605 self.matrix.borrow().m32
606 }
607
608 fn M33(&self) -> f64 {
610 self.matrix.borrow().m33
611 }
612
613 fn M34(&self) -> f64 {
615 self.matrix.borrow().m34
616 }
617
618 fn M41(&self) -> f64 {
620 self.matrix.borrow().m41
621 }
622
623 fn M42(&self) -> f64 {
625 self.matrix.borrow().m42
626 }
627
628 fn M43(&self) -> f64 {
630 self.matrix.borrow().m43
631 }
632
633 fn M44(&self) -> f64 {
635 self.matrix.borrow().m44
636 }
637
638 fn A(&self) -> f64 {
640 self.M11()
641 }
642
643 fn B(&self) -> f64 {
645 self.M12()
646 }
647
648 fn C(&self) -> f64 {
650 self.M21()
651 }
652
653 fn D(&self) -> f64 {
655 self.M22()
656 }
657
658 fn E(&self) -> f64 {
660 self.M41()
661 }
662
663 fn F(&self) -> f64 {
665 self.M42()
666 }
667
668 fn Is2D(&self) -> bool {
670 self.is2D.get()
671 }
672
673 fn IsIdentity(&self) -> bool {
675 let matrix = self.matrix.borrow();
676 matrix.m12 == 0.0 &&
677 matrix.m13 == 0.0 &&
678 matrix.m14 == 0.0 &&
679 matrix.m21 == 0.0 &&
680 matrix.m23 == 0.0 &&
681 matrix.m24 == 0.0 &&
682 matrix.m31 == 0.0 &&
683 matrix.m32 == 0.0 &&
684 matrix.m34 == 0.0 &&
685 matrix.m41 == 0.0 &&
686 matrix.m42 == 0.0 &&
687 matrix.m43 == 0.0 &&
688 matrix.m11 == 1.0 &&
689 matrix.m22 == 1.0 &&
690 matrix.m33 == 1.0 &&
691 matrix.m44 == 1.0
692 }
693
694 fn Translate(
696 &self,
697 cx: &mut js::context::JSContext,
698 tx: f64,
699 ty: f64,
700 tz: f64,
701 ) -> DomRoot<DOMMatrix> {
702 DOMMatrix::from_readonly(&self.global(), self, cx).TranslateSelf(tx, ty, tz)
703 }
704
705 fn Scale(
707 &self,
708 cx: &mut js::context::JSContext,
709 scaleX: f64,
710 scaleY: Option<f64>,
711 scaleZ: f64,
712 originX: f64,
713 originY: f64,
714 originZ: f64,
715 ) -> DomRoot<DOMMatrix> {
716 DOMMatrix::from_readonly(&self.global(), self, cx)
717 .ScaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
718 }
719
720 fn ScaleNonUniform(
722 &self,
723 cx: &mut js::context::JSContext,
724 scaleX: f64,
725 scaleY: f64,
726 ) -> DomRoot<DOMMatrix> {
727 DOMMatrix::from_readonly(&self.global(), self, cx).ScaleSelf(
728 scaleX,
729 Some(scaleY),
730 1.0,
731 0.0,
732 0.0,
733 0.0,
734 )
735 }
736
737 fn Scale3d(
739 &self,
740 cx: &mut js::context::JSContext,
741 scale: f64,
742 originX: f64,
743 originY: f64,
744 originZ: f64,
745 ) -> DomRoot<DOMMatrix> {
746 DOMMatrix::from_readonly(&self.global(), self, cx)
747 .Scale3dSelf(scale, originX, originY, originZ)
748 }
749
750 fn Rotate(
752 &self,
753 cx: &mut js::context::JSContext,
754 rotX: f64,
755 rotY: Option<f64>,
756 rotZ: Option<f64>,
757 ) -> DomRoot<DOMMatrix> {
758 DOMMatrix::from_readonly(&self.global(), self, cx).RotateSelf(rotX, rotY, rotZ)
759 }
760
761 fn RotateFromVector(
763 &self,
764 cx: &mut js::context::JSContext,
765 x: f64,
766 y: f64,
767 ) -> DomRoot<DOMMatrix> {
768 DOMMatrix::from_readonly(&self.global(), self, cx).RotateFromVectorSelf(x, y)
769 }
770
771 fn RotateAxisAngle(
773 &self,
774 cx: &mut js::context::JSContext,
775 x: f64,
776 y: f64,
777 z: f64,
778 angle: f64,
779 ) -> DomRoot<DOMMatrix> {
780 DOMMatrix::from_readonly(&self.global(), self, cx).RotateAxisAngleSelf(x, y, z, angle)
781 }
782
783 fn SkewX(&self, cx: &mut js::context::JSContext, sx: f64) -> DomRoot<DOMMatrix> {
785 DOMMatrix::from_readonly(&self.global(), self, cx).SkewXSelf(sx)
786 }
787
788 fn SkewY(&self, cx: &mut js::context::JSContext, sy: f64) -> DomRoot<DOMMatrix> {
790 DOMMatrix::from_readonly(&self.global(), self, cx).SkewYSelf(sy)
791 }
792
793 fn Multiply(
795 &self,
796 cx: &mut js::context::JSContext,
797 other: &DOMMatrixInit,
798 ) -> Fallible<DomRoot<DOMMatrix>> {
799 DOMMatrix::from_readonly(&self.global(), self, cx).MultiplySelf(other)
800 }
801
802 fn FlipX(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
804 let is2D = self.is2D.get();
805 let flip = Transform3D::new(
806 -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,
807 );
808 let matrix = flip.then(&self.matrix.borrow());
809 DOMMatrix::new(cx, &self.global(), is2D, matrix)
810 }
811
812 fn FlipY(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
814 let is2D = self.is2D.get();
815 let flip = Transform3D::new(
816 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,
817 );
818 let matrix = flip.then(&self.matrix.borrow());
819 DOMMatrix::new(cx, &self.global(), is2D, matrix)
820 }
821
822 fn Inverse(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
824 DOMMatrix::from_readonly(&self.global(), self, cx).InvertSelf()
825 }
826
827 fn TransformPoint(
829 &self,
830 cx: &mut js::context::JSContext,
831 point: &DOMPointInit,
832 ) -> DomRoot<DOMPoint> {
833 let mat = self.matrix.borrow();
840 let x = point.x * mat.m11 + point.y * mat.m21 + point.z * mat.m31 + point.w * mat.m41;
841 let y = point.x * mat.m12 + point.y * mat.m22 + point.z * mat.m32 + point.w * mat.m42;
842 let z = point.x * mat.m13 + point.y * mat.m23 + point.z * mat.m33 + point.w * mat.m43;
843 let w = point.x * mat.m14 + point.y * mat.m24 + point.z * mat.m34 + point.w * mat.m44;
844
845 DOMPoint::new(cx, &self.global(), x, y, z, w)
846 }
847
848 fn ToFloat32Array(
850 &self,
851 cx: &mut js::context::JSContext,
852 ) -> RootedTraceableBox<HeapFloat32Array> {
853 let vec: Vec<f32> = self
854 .matrix
855 .borrow()
856 .to_array()
857 .iter()
858 .map(|&x| x as f32)
859 .collect();
860 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
861 create_buffer_source(cx, &vec, array.handle_mut())
862 .expect("Converting matrix to float32 array should never fail")
863 }
864
865 fn ToFloat64Array(
867 &self,
868 cx: &mut js::context::JSContext,
869 ) -> RootedTraceableBox<HeapFloat64Array> {
870 rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
871 create_buffer_source(cx, &self.matrix.borrow().to_array(), array.handle_mut())
872 .expect("Converting matrix to float64 array should never fail")
873 }
874
875 #[expect(unsafe_code)]
877 fn Stringifier(&self, cx: &mut js::context::JSContext) -> Fallible<DOMString> {
878 let mat = self.matrix.borrow();
881 if !mat.m11.is_finite() ||
882 !mat.m12.is_finite() ||
883 !mat.m13.is_finite() ||
884 !mat.m14.is_finite() ||
885 !mat.m21.is_finite() ||
886 !mat.m22.is_finite() ||
887 !mat.m23.is_finite() ||
888 !mat.m24.is_finite() ||
889 !mat.m31.is_finite() ||
890 !mat.m32.is_finite() ||
891 !mat.m33.is_finite() ||
892 !mat.m34.is_finite() ||
893 !mat.m41.is_finite() ||
894 !mat.m42.is_finite() ||
895 !mat.m43.is_finite() ||
896 !mat.m44.is_finite()
897 {
898 return Err(error::Error::InvalidState(None));
899 }
900
901 let mut to_string = |f: f64| {
902 rooted!(&in(cx) let rooted_value = jsval::DoubleValue(f));
903 let serialization =
904 std::ptr::NonNull::new(unsafe { ToString(cx, rooted_value.handle()) })
905 .expect("Pointer cannot be null");
906 unsafe { jsstr_to_string(cx, serialization) }
907 };
908
909 let string = if self.is2D() {
912 format!(
926 "matrix({}, {}, {}, {}, {}, {})",
927 to_string(mat.m11),
928 to_string(mat.m12),
929 to_string(mat.m21),
930 to_string(mat.m22),
931 to_string(mat.m41),
932 to_string(mat.m42)
933 )
934 .into()
935 }
936 else {
938 format!(
967 "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
968 to_string(mat.m11),
969 to_string(mat.m12),
970 to_string(mat.m13),
971 to_string(mat.m14),
972 to_string(mat.m21),
973 to_string(mat.m22),
974 to_string(mat.m23),
975 to_string(mat.m24),
976 to_string(mat.m31),
977 to_string(mat.m32),
978 to_string(mat.m33),
979 to_string(mat.m34),
980 to_string(mat.m41),
981 to_string(mat.m42),
982 to_string(mat.m43),
983 to_string(mat.m44)
984 )
985 .into()
986 };
987
988 Ok(string)
989 }
990}
991
992impl Serializable for DOMMatrixReadOnly {
993 type Index = DomMatrixIndex;
994 type Data = DomMatrix;
995
996 fn serialize(&self, _no_gc: &NoGC) -> Result<(DomMatrixId, Self::Data), ()> {
997 let serialized = if self.is2D() {
998 DomMatrix {
999 matrix: Transform3D::new(
1000 self.M11(),
1001 self.M12(),
1002 f64::NAN,
1003 f64::NAN,
1004 self.M21(),
1005 self.M22(),
1006 f64::NAN,
1007 f64::NAN,
1008 f64::NAN,
1009 f64::NAN,
1010 f64::NAN,
1011 f64::NAN,
1012 self.M41(),
1013 self.M42(),
1014 f64::NAN,
1015 f64::NAN,
1016 ),
1017 is_2d: true,
1018 }
1019 } else {
1020 DomMatrix {
1021 matrix: *self.matrix(),
1022 is_2d: false,
1023 }
1024 };
1025 Ok((DomMatrixId::new(), serialized))
1026 }
1027
1028 fn deserialize(
1029 cx: &mut js::context::JSContext,
1030 owner: &GlobalScope,
1031 serialized: Self::Data,
1032 ) -> Result<DomRoot<Self>, ()>
1033 where
1034 Self: Sized,
1035 {
1036 if serialized.is_2d {
1037 Ok(Self::new(
1038 cx,
1039 owner,
1040 true,
1041 Transform3D::new(
1042 serialized.matrix.m11,
1043 serialized.matrix.m12,
1044 0.0,
1045 0.0,
1046 serialized.matrix.m21,
1047 serialized.matrix.m22,
1048 0.0,
1049 0.0,
1050 0.0,
1051 0.0,
1052 1.0,
1053 0.0,
1054 serialized.matrix.m41,
1055 serialized.matrix.m42,
1056 0.0,
1057 1.0,
1058 ),
1059 ))
1060 } else {
1061 Ok(Self::new(cx, owner, false, serialized.matrix))
1062 }
1063 }
1064
1065 fn serialized_storage<'a>(
1066 data: StructuredData<'a, '_>,
1067 ) -> &'a mut Option<FxHashMap<DomMatrixId, Self::Data>> {
1068 match data {
1069 StructuredData::Reader(reader) => &mut reader.matrices,
1070 StructuredData::Writer(writer) => &mut writer.matrices,
1071 }
1072 }
1073}
1074
1075pub(crate) fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Transform3D<f64>)> {
1077 if let Ok(array) = entries.try_into() {
1078 Ok((true, Transform2D::from_array(array).to_3d()))
1079 } else if let Ok(array) = entries.try_into() {
1080 Ok((false, Transform3D::from_array(array)))
1081 } else {
1082 let err_msg = cformat!("Expected 6 or 16 entries, but found {}.", entries.len());
1083 Err(error::Error::Type(err_msg))
1084 }
1085}
1086
1087fn validate_and_fixup_2d(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1089 let same_value_zero = |x: f64, y: f64| -> bool { x.is_nan() && y.is_nan() || x == y };
1091
1092 if dict.a.is_some() &&
1095 dict.m11.is_some() &&
1096 !same_value_zero(dict.a.unwrap(), dict.m11.unwrap()) ||
1097 dict.b.is_some() &&
1098 dict.m12.is_some() &&
1099 !same_value_zero(dict.b.unwrap(), dict.m12.unwrap()) ||
1100 dict.c.is_some() &&
1101 dict.m21.is_some() &&
1102 !same_value_zero(dict.c.unwrap(), dict.m21.unwrap()) ||
1103 dict.d.is_some() &&
1104 dict.m22.is_some() &&
1105 !same_value_zero(dict.d.unwrap(), dict.m22.unwrap()) ||
1106 dict.e.is_some() &&
1107 dict.m41.is_some() &&
1108 !same_value_zero(dict.e.unwrap(), dict.m41.unwrap()) ||
1109 dict.f.is_some() &&
1110 dict.m42.is_some() &&
1111 !same_value_zero(dict.f.unwrap(), dict.m42.unwrap())
1112 {
1113 return Err(error::Error::Type(
1114 c"Property mismatch on matrix initialization.".to_owned(),
1115 ));
1116 }
1117
1118 let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0));
1121
1122 let m12 = dict.m12.unwrap_or(dict.b.unwrap_or(0.0));
1125
1126 let m21 = dict.m21.unwrap_or(dict.c.unwrap_or(0.0));
1129
1130 let m22 = dict.m22.unwrap_or(dict.d.unwrap_or(1.0));
1133
1134 let m41 = dict.m41.unwrap_or(dict.e.unwrap_or(0.0));
1137
1138 let m42 = dict.m42.unwrap_or(dict.f.unwrap_or(0.0));
1141
1142 Ok(Transform2D::new(m11, m12, m21, m22, m41, m42))
1143}
1144
1145fn validate_and_fixup(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1147 let transform2d = validate_and_fixup_2d(&dict.parent)?;
1149
1150 if dict.is2D == Some(true) &&
1155 (dict.m13 != 0.0 ||
1156 dict.m14 != 0.0 ||
1157 dict.m23 != 0.0 ||
1158 dict.m24 != 0.0 ||
1159 dict.m31 != 0.0 ||
1160 dict.m32 != 0.0 ||
1161 dict.m34 != 0.0 ||
1162 dict.m43 != 0.0 ||
1163 dict.m33 != 1.0 ||
1164 dict.m44 != 1.0)
1165 {
1166 return Err(error::Error::Type(
1167 c"The is2D member is set to true but the input matrix is a 3d matrix.".to_owned(),
1168 ));
1169 }
1170
1171 let mut is_2d = dict.is2D;
1172
1173 if is_2d.is_none() &&
1178 (dict.m13 != 0.0 ||
1179 dict.m14 != 0.0 ||
1180 dict.m23 != 0.0 ||
1181 dict.m24 != 0.0 ||
1182 dict.m31 != 0.0 ||
1183 dict.m32 != 0.0 ||
1184 dict.m34 != 0.0 ||
1185 dict.m43 != 0.0 ||
1186 dict.m33 != 1.0 ||
1187 dict.m44 != 1.0)
1188 {
1189 is_2d = Some(false);
1190 }
1191
1192 let is_2d = is_2d.unwrap_or(true);
1194
1195 let mut transform = transform2d.to_3d();
1196 transform.m13 = dict.m13;
1197 transform.m14 = dict.m14;
1198 transform.m23 = dict.m23;
1199 transform.m24 = dict.m24;
1200 transform.m31 = dict.m31;
1201 transform.m32 = dict.m32;
1202 transform.m33 = dict.m33;
1203 transform.m34 = dict.m34;
1204 transform.m43 = dict.m43;
1205 transform.m44 = dict.m44;
1206
1207 Ok((is_2d, transform))
1208}
1209
1210pub(crate) fn dommatrix2dinit_to_matrix(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1212 validate_and_fixup_2d(dict)
1218}
1219
1220pub(crate) fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1222 validate_and_fixup(dict)
1228}
1229
1230#[inline]
1231fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
1232 let len = (x * x + y * y + z * z).sqrt();
1233 if len == 0.0 {
1234 (0.0, 0.0, 0.0)
1235 } else {
1236 (x / len, y / len, z / len)
1237 }
1238}
1239
1240pub(crate) fn transform_to_matrix(value: &str) -> Fallible<(bool, Transform3D<f64>)> {
1241 use style::properties::longhands::transform;
1242
1243 let mut input = ParserInput::new(value);
1244 let mut parser = Parser::new(&mut input);
1245 let url_data = Url::parse("about:blank").unwrap().into();
1246 let context =
1247 parser_context_for_anonymous_content(CssRuleType::Style, ParsingMode::DEFAULT, &url_data);
1248
1249 let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) {
1250 Ok(result) => result,
1251 Err(..) => return Err(error::Error::Syntax(None)),
1252 };
1253
1254 let (m, is_3d) = match transform.to_transform_3d_matrix_f64(None) {
1255 Ok(result) => result,
1256 Err(..) => return Err(error::Error::Syntax(None)),
1257 };
1258
1259 Ok((!is_3d, m))
1260}