Skip to main content

script/dom/geometry/
dommatrixreadonly.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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::conversions::jsstr_to_string;
13use js::jsapi::JSObject;
14use js::jsval;
15use js::rust::{CustomAutoRooterGuard, HandleObject, ToString};
16use js::typedarray::{Float32Array, Float64Array, HeapFloat32Array, HeapFloat64Array};
17use rustc_hash::FxHashMap;
18use script_bindings::cell::{DomRefCell, Ref};
19use script_bindings::cformat;
20use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
21use script_bindings::trace::RootedTraceableBox;
22use servo_base::id::{DomMatrixId, DomMatrixIndex};
23use servo_constellation_traits::DomMatrix;
24use style::stylesheets::CssRuleType;
25use style_traits::ParsingMode;
26use url::Url;
27
28use crate::css::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;
48use crate::script_runtime::CanGc;
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        global: &GlobalScope,
63        is2D: bool,
64        matrix: Transform3D<f64>,
65        cx: &mut js::context::JSContext,
66    ) -> DomRoot<Self> {
67        Self::new_with_proto(global, None, is2D, matrix, cx)
68    }
69
70    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
71    fn new_with_proto(
72        global: &GlobalScope,
73        proto: Option<HandleObject>,
74        is2D: bool,
75        matrix: Transform3D<f64>,
76        cx: &mut js::context::JSContext,
77    ) -> DomRoot<Self> {
78        let dommatrix = Self::new_inherited(is2D, matrix);
79        reflect_dom_object_with_proto(Box::new(dommatrix), global, proto, CanGc::from_cx(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    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11
122    pub(crate) fn set_m11(&self, value: f64) {
123        self.matrix.borrow_mut().m11 = value;
124    }
125
126    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12
127    pub(crate) fn set_m12(&self, value: f64) {
128        self.matrix.borrow_mut().m12 = value;
129    }
130
131    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13
132    pub(crate) fn set_m13(&self, value: f64) {
133        // For the DOMMatrix interface, setting the m13 attribute must set the
134        // m13 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
135
136        self.matrix.borrow_mut().m13 = value;
137        if value.abs() != 0. {
138            self.is2D.set(false);
139        }
140    }
141
142    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14
143    pub(crate) fn set_m14(&self, value: f64) {
144        // For the DOMMatrix interface, setting the m14 attribute must set the
145        // m14 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
146        self.matrix.borrow_mut().m14 = value;
147
148        if value.abs() != 0. {
149            self.is2D.set(false);
150        }
151    }
152
153    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21
154    pub(crate) fn set_m21(&self, value: f64) {
155        self.matrix.borrow_mut().m21 = value;
156    }
157
158    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22
159    pub(crate) fn set_m22(&self, value: f64) {
160        self.matrix.borrow_mut().m22 = value;
161    }
162
163    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23
164    pub(crate) fn set_m23(&self, value: f64) {
165        // For the DOMMatrix interface, setting the m23 attribute must set the
166        // m23 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
167        self.matrix.borrow_mut().m23 = value;
168
169        if value.abs() != 0. {
170            self.is2D.set(false);
171        }
172    }
173
174    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24
175    pub(crate) fn set_m24(&self, value: f64) {
176        // For the DOMMatrix interface, setting the m24 attribute must set the
177        // m24 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
178        self.matrix.borrow_mut().m24 = value;
179
180        if value.abs() != 0. {
181            self.is2D.set(false);
182        }
183    }
184
185    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31
186    pub(crate) fn set_m31(&self, value: f64) {
187        // For the DOMMatrix interface, setting the m31 attribute must set the
188        // m31 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
189        self.matrix.borrow_mut().m31 = value;
190
191        if value.abs() != 0. {
192            self.is2D.set(false);
193        }
194    }
195
196    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32
197    pub(crate) fn set_m32(&self, value: f64) {
198        // For the DOMMatrix interface, setting the m32 attribute must set the
199        // m32 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
200        self.matrix.borrow_mut().m32 = value;
201
202        if value.abs() != 0. {
203            self.is2D.set(false);
204        }
205    }
206
207    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33
208    pub(crate) fn set_m33(&self, value: f64) {
209        // For the DOMMatrix interface, setting the m33 attribute must set the
210        // m33 element to the new value and, if the new value is not 1, set is 2D to false.
211        self.matrix.borrow_mut().m33 = value;
212
213        if value != 1. {
214            self.is2D.set(false);
215        }
216    }
217
218    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34
219    pub(crate) fn set_m34(&self, value: f64) {
220        // For the DOMMatrix interface, setting the m34 attribute must set the
221        // m34 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
222        self.matrix.borrow_mut().m34 = value;
223
224        if value.abs() != 0. {
225            self.is2D.set(false);
226        }
227    }
228
229    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41
230    pub(crate) fn set_m41(&self, value: f64) {
231        self.matrix.borrow_mut().m41 = value;
232    }
233
234    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42
235    pub(crate) fn set_m42(&self, value: f64) {
236        self.matrix.borrow_mut().m42 = value;
237    }
238
239    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43
240    pub(crate) fn set_m43(&self, value: f64) {
241        // For the DOMMatrix interface, setting the m43 attribute must set the
242        // m43 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
243        self.matrix.borrow_mut().m43 = value;
244
245        if value.abs() != 0. {
246            self.is2D.set(false);
247        }
248    }
249
250    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44
251    pub(crate) fn set_m44(&self, value: f64) {
252        // For the DOMMatrix interface, setting the m44 attribute must set the
253        // m44 element to the new value and, if the new value is not 1, set is 2D to false.
254        self.matrix.borrow_mut().m44 = value;
255
256        if value != 1. {
257            self.is2D.set(false);
258        }
259    }
260
261    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-multiplyself
262    pub(crate) fn multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
263        // Step 1.
264        dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
265            // Step 2.
266            let mut matrix = self.matrix.borrow_mut();
267            *matrix = other_matrix.then(&matrix);
268            // Step 3.
269            if !is2D {
270                self.is2D.set(false);
271            }
272            // Step 4 in DOMMatrix.MultiplySelf
273        })
274    }
275
276    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-premultiplyself
277    pub(crate) fn pre_multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
278        // Step 1.
279        dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
280            // Step 2.
281            let mut matrix = self.matrix.borrow_mut();
282            *matrix = matrix.then(&other_matrix);
283            // Step 3.
284            if !is2D {
285                self.is2D.set(false);
286            }
287            // Step 4 in DOMMatrix.PreMultiplySelf
288        })
289    }
290
291    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-translateself
292    pub(crate) fn translate_self(&self, tx: f64, ty: f64, tz: f64) {
293        // Step 1.
294        let translation = Transform3D::translation(tx, ty, tz);
295        let mut matrix = self.matrix.borrow_mut();
296        *matrix = translation.then(&matrix);
297        // Step 2.
298        if tz != 0.0 {
299            self.is2D.set(false);
300        }
301        // Step 3 in DOMMatrix.TranslateSelf
302    }
303
304    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scaleself
305    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        // Step 1.
315        self.translate_self(originX, originY, originZ);
316        // Step 2.
317        let scaleY = scaleY.unwrap_or(scaleX);
318        // Step 3.
319        {
320            let scale3D = Transform3D::scale(scaleX, scaleY, scaleZ);
321            let mut matrix = self.matrix.borrow_mut();
322            *matrix = scale3D.then(&matrix);
323        }
324        // Step 4.
325        originX = -originX;
326        originY = -originY;
327        originZ = -originZ;
328        // Step 5.
329        self.translate_self(originX, originY, originZ);
330        // Step 6.
331        if scaleZ != 1.0 || originZ != 0.0 {
332            self.is2D.set(false);
333        }
334        // Step 7 in DOMMatrix.ScaleSelf
335    }
336
337    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scale3dself
338    pub(crate) fn scale_3d_self(&self, scale: f64, originX: f64, originY: f64, originZ: f64) {
339        // Step 1.
340        self.translate_self(originX, originY, originZ);
341        // Step 2.
342        {
343            let scale3D = Transform3D::scale(scale, scale, scale);
344            let mut matrix = self.matrix.borrow_mut();
345            *matrix = scale3D.then(&matrix);
346        }
347        // Step 3.
348        self.translate_self(-originX, -originY, -originZ);
349        // Step 4.
350        if scale != 1.0 {
351            self.is2D.set(false);
352        }
353        // Step 5 in DOMMatrix.Scale3dSelf
354    }
355
356    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateself
357    pub(crate) fn rotate_self(&self, mut rotX: f64, mut rotY: Option<f64>, mut rotZ: Option<f64>) {
358        // Step 1.
359        if rotY.is_none() && rotZ.is_none() {
360            rotZ = Some(rotX);
361            rotX = 0.0;
362            rotY = Some(0.0);
363        }
364        // Step 2.
365        let rotY = rotY.unwrap_or(0.0);
366        // Step 3.
367        let rotZ = rotZ.unwrap_or(0.0);
368        // Step 4.
369        if rotX != 0.0 || rotY != 0.0 {
370            self.is2D.set(false);
371        }
372        if rotZ != 0.0 {
373            // Step 5.
374            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            // Step 6.
380            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            // Step 7.
386            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        // Step 8 in DOMMatrix.RotateSelf
391    }
392
393    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotatefromvectorself
394    pub(crate) fn rotate_from_vector_self(&self, x: f64, y: f64) {
395        // don't do anything when the rotation angle is zero or undefined
396        if y != 0.0 || x < 0.0 {
397            // Step 1.
398            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        // Step 2 in DOMMatrix.RotateFromVectorSelf
404    }
405
406    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateaxisangleself
407    pub(crate) fn rotate_axis_angle_self(&self, x: f64, y: f64, z: f64, angle: f64) {
408        // Step 1.
409        let (norm_x, norm_y, norm_z) = normalize_point(x, y, z);
410        // Beware: pass negated value until https://github.com/servo/euclid/issues/354
411        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        // Step 2.
416        if x != 0.0 || y != 0.0 {
417            self.is2D.set(false);
418        }
419        // Step 3 in DOMMatrix.RotateAxisAngleSelf
420    }
421
422    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewxself
423    pub(crate) fn skew_x_self(&self, sx: f64) {
424        // Step 1.
425        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        // Step 2 in DOMMatrix.SkewXSelf
429    }
430
431    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewyself
432    pub(crate) fn skew_y_self(&self, sy: f64) {
433        // Step 1.
434        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        // Step 2 in DOMMatrix.SkewYSelf
438    }
439
440    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrix-invertself>
441    pub(crate) fn invert_self(&self) {
442        let mut matrix = self.matrix.borrow_mut();
443        // Step 1. Invert the current matrix.
444        let inverted = match self.is2D() {
445            true => matrix.to_2d().inverse().map(|m| m.to_3d()),
446            false => matrix.inverse(),
447        };
448
449        // Step 2. If the current matrix is not invertible set all attributes to NaN
450        // and set is 2D to false.
451        *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        // Step 3 in DOMMatrix.InvertSelf
473    }
474}
475
476#[expect(non_snake_case)]
477impl DOMMatrixReadOnlyMethods<crate::DomTypeHolder> for DOMMatrixReadOnly {
478    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly>
479    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                global,
488                proto,
489                true,
490                Transform3D::identity(),
491                cx,
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(global, true, Transform3D::identity(), cx));
503                }
504                transform_to_matrix(&s.str())
505                    .map(|(is2D, matrix)| Self::new_with_proto(global, proto, is2D, matrix, cx))
506            },
507            StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => {
508                entries_to_matrix(&entries[..])
509                    .map(|(is2D, matrix)| Self::new_with_proto(global, proto, is2D, matrix, cx))
510            },
511        }
512    }
513
514    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-frommatrix>
515    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(global, is2D, matrix, cx))
521    }
522
523    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat32array>
524    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.to_vec().iter().map(|&x| x as f64).collect();
530        DOMMatrixReadOnly::Constructor(
531            cx,
532            global,
533            None,
534            Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
535        )
536    }
537
538    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat64array>
539    fn FromFloat64Array(
540        cx: &mut js::context::JSContext,
541        global: &GlobalScope,
542        array: CustomAutoRooterGuard<Float64Array>,
543    ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
544        let vec: Vec<f64> = array.to_vec();
545        DOMMatrixReadOnly::Constructor(
546            cx,
547            global,
548            None,
549            Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
550        )
551    }
552
553    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11>
554    fn M11(&self) -> f64 {
555        self.matrix.borrow().m11
556    }
557
558    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12>
559    fn M12(&self) -> f64 {
560        self.matrix.borrow().m12
561    }
562
563    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13>
564    fn M13(&self) -> f64 {
565        self.matrix.borrow().m13
566    }
567
568    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14>
569    fn M14(&self) -> f64 {
570        self.matrix.borrow().m14
571    }
572
573    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21>
574    fn M21(&self) -> f64 {
575        self.matrix.borrow().m21
576    }
577
578    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22>
579    fn M22(&self) -> f64 {
580        self.matrix.borrow().m22
581    }
582
583    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23>
584    fn M23(&self) -> f64 {
585        self.matrix.borrow().m23
586    }
587
588    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24>
589    fn M24(&self) -> f64 {
590        self.matrix.borrow().m24
591    }
592
593    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31>
594    fn M31(&self) -> f64 {
595        self.matrix.borrow().m31
596    }
597
598    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32>
599    fn M32(&self) -> f64 {
600        self.matrix.borrow().m32
601    }
602
603    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33>
604    fn M33(&self) -> f64 {
605        self.matrix.borrow().m33
606    }
607
608    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34>
609    fn M34(&self) -> f64 {
610        self.matrix.borrow().m34
611    }
612
613    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41>
614    fn M41(&self) -> f64 {
615        self.matrix.borrow().m41
616    }
617
618    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42>
619    fn M42(&self) -> f64 {
620        self.matrix.borrow().m42
621    }
622
623    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43>
624    fn M43(&self) -> f64 {
625        self.matrix.borrow().m43
626    }
627
628    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44>
629    fn M44(&self) -> f64 {
630        self.matrix.borrow().m44
631    }
632
633    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a>
634    fn A(&self) -> f64 {
635        self.M11()
636    }
637
638    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b>
639    fn B(&self) -> f64 {
640        self.M12()
641    }
642
643    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c>
644    fn C(&self) -> f64 {
645        self.M21()
646    }
647
648    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d>
649    fn D(&self) -> f64 {
650        self.M22()
651    }
652
653    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e>
654    fn E(&self) -> f64 {
655        self.M41()
656    }
657
658    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f>
659    fn F(&self) -> f64 {
660        self.M42()
661    }
662
663    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-is2d>
664    fn Is2D(&self) -> bool {
665        self.is2D.get()
666    }
667
668    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-isidentity>
669    fn IsIdentity(&self) -> bool {
670        let matrix = self.matrix.borrow();
671        matrix.m12 == 0.0 &&
672            matrix.m13 == 0.0 &&
673            matrix.m14 == 0.0 &&
674            matrix.m21 == 0.0 &&
675            matrix.m23 == 0.0 &&
676            matrix.m24 == 0.0 &&
677            matrix.m31 == 0.0 &&
678            matrix.m32 == 0.0 &&
679            matrix.m34 == 0.0 &&
680            matrix.m41 == 0.0 &&
681            matrix.m42 == 0.0 &&
682            matrix.m43 == 0.0 &&
683            matrix.m11 == 1.0 &&
684            matrix.m22 == 1.0 &&
685            matrix.m33 == 1.0 &&
686            matrix.m44 == 1.0
687    }
688
689    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-translate>
690    fn Translate(
691        &self,
692        cx: &mut js::context::JSContext,
693        tx: f64,
694        ty: f64,
695        tz: f64,
696    ) -> DomRoot<DOMMatrix> {
697        DOMMatrix::from_readonly(&self.global(), self, cx).TranslateSelf(tx, ty, tz)
698    }
699
700    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale>
701    fn Scale(
702        &self,
703        cx: &mut js::context::JSContext,
704        scaleX: f64,
705        scaleY: Option<f64>,
706        scaleZ: f64,
707        originX: f64,
708        originY: f64,
709        originZ: f64,
710    ) -> DomRoot<DOMMatrix> {
711        DOMMatrix::from_readonly(&self.global(), self, cx)
712            .ScaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
713    }
714
715    /// <https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-scalenonuniform>
716    fn ScaleNonUniform(
717        &self,
718        cx: &mut js::context::JSContext,
719        scaleX: f64,
720        scaleY: f64,
721    ) -> DomRoot<DOMMatrix> {
722        DOMMatrix::from_readonly(&self.global(), self, cx).ScaleSelf(
723            scaleX,
724            Some(scaleY),
725            1.0,
726            0.0,
727            0.0,
728            0.0,
729        )
730    }
731
732    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale3d>
733    fn Scale3d(
734        &self,
735        cx: &mut js::context::JSContext,
736        scale: f64,
737        originX: f64,
738        originY: f64,
739        originZ: f64,
740    ) -> DomRoot<DOMMatrix> {
741        DOMMatrix::from_readonly(&self.global(), self, cx)
742            .Scale3dSelf(scale, originX, originY, originZ)
743    }
744
745    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotate>
746    fn Rotate(
747        &self,
748        cx: &mut js::context::JSContext,
749        rotX: f64,
750        rotY: Option<f64>,
751        rotZ: Option<f64>,
752    ) -> DomRoot<DOMMatrix> {
753        DOMMatrix::from_readonly(&self.global(), self, cx).RotateSelf(rotX, rotY, rotZ)
754    }
755
756    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotatefromvector>
757    fn RotateFromVector(
758        &self,
759        cx: &mut js::context::JSContext,
760        x: f64,
761        y: f64,
762    ) -> DomRoot<DOMMatrix> {
763        DOMMatrix::from_readonly(&self.global(), self, cx).RotateFromVectorSelf(x, y)
764    }
765
766    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotateaxisangle>
767    fn RotateAxisAngle(
768        &self,
769        cx: &mut js::context::JSContext,
770        x: f64,
771        y: f64,
772        z: f64,
773        angle: f64,
774    ) -> DomRoot<DOMMatrix> {
775        DOMMatrix::from_readonly(&self.global(), self, cx).RotateAxisAngleSelf(x, y, z, angle)
776    }
777
778    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewx>
779    fn SkewX(&self, cx: &mut js::context::JSContext, sx: f64) -> DomRoot<DOMMatrix> {
780        DOMMatrix::from_readonly(&self.global(), self, cx).SkewXSelf(sx)
781    }
782
783    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewy>
784    fn SkewY(&self, cx: &mut js::context::JSContext, sy: f64) -> DomRoot<DOMMatrix> {
785        DOMMatrix::from_readonly(&self.global(), self, cx).SkewYSelf(sy)
786    }
787
788    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-multiply>
789    fn Multiply(
790        &self,
791        cx: &mut js::context::JSContext,
792        other: &DOMMatrixInit,
793    ) -> Fallible<DomRoot<DOMMatrix>> {
794        DOMMatrix::from_readonly(&self.global(), self, cx).MultiplySelf(other)
795    }
796
797    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipx>
798    fn FlipX(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
799        let is2D = self.is2D.get();
800        let flip = Transform3D::new(
801            -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,
802        );
803        let matrix = flip.then(&self.matrix.borrow());
804        DOMMatrix::new(&self.global(), is2D, matrix, cx)
805    }
806
807    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipy>
808    fn FlipY(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
809        let is2D = self.is2D.get();
810        let flip = Transform3D::new(
811            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,
812        );
813        let matrix = flip.then(&self.matrix.borrow());
814        DOMMatrix::new(&self.global(), is2D, matrix, cx)
815    }
816
817    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-inverse>
818    fn Inverse(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
819        DOMMatrix::from_readonly(&self.global(), self, cx).InvertSelf()
820    }
821
822    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-transformpoint>
823    fn TransformPoint(
824        &self,
825        cx: &mut js::context::JSContext,
826        point: &DOMPointInit,
827    ) -> DomRoot<DOMPoint> {
828        // Euclid always normalizes the homogeneous coordinate which is usually the right
829        // thing but may (?) not be compliant with the CSS matrix spec (or at least is
830        // probably not the behavior web authors will expect even if it is mathematically
831        // correct in the context of geometry computations).
832        // Since this is the only place where this is needed, better implement it here
833        // than in euclid (which does not have a notion of 4d points).
834        let mat = self.matrix.borrow();
835        let x = point.x * mat.m11 + point.y * mat.m21 + point.z * mat.m31 + point.w * mat.m41;
836        let y = point.x * mat.m12 + point.y * mat.m22 + point.z * mat.m32 + point.w * mat.m42;
837        let z = point.x * mat.m13 + point.y * mat.m23 + point.z * mat.m33 + point.w * mat.m43;
838        let w = point.x * mat.m14 + point.y * mat.m24 + point.z * mat.m34 + point.w * mat.m44;
839
840        DOMPoint::new(cx, &self.global(), x, y, z, w)
841    }
842
843    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat32array>
844    fn ToFloat32Array(
845        &self,
846        cx: &mut js::context::JSContext,
847    ) -> RootedTraceableBox<HeapFloat32Array> {
848        let vec: Vec<f32> = self
849            .matrix
850            .borrow()
851            .to_array()
852            .iter()
853            .map(|&x| x as f32)
854            .collect();
855        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
856        create_buffer_source(cx, &vec, array.handle_mut())
857            .expect("Converting matrix to float32 array should never fail")
858    }
859
860    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat64array>
861    fn ToFloat64Array(
862        &self,
863        cx: &mut js::context::JSContext,
864    ) -> RootedTraceableBox<HeapFloat64Array> {
865        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
866        create_buffer_source(cx, &self.matrix.borrow().to_array(), array.handle_mut())
867            .expect("Converting matrix to float64 array should never fail")
868    }
869
870    // https://drafts.fxtf.org/geometry/#dommatrixreadonly-stringification-behavior
871    #[expect(unsafe_code)]
872    fn Stringifier(&self, cx: &mut js::context::JSContext) -> Fallible<DOMString> {
873        // Step 1. If one or more of m11 element through m44 element are a non-finite value,
874        // then throw an "InvalidStateError" DOMException.
875        let mat = self.matrix.borrow();
876        if !mat.m11.is_finite() ||
877            !mat.m12.is_finite() ||
878            !mat.m13.is_finite() ||
879            !mat.m14.is_finite() ||
880            !mat.m21.is_finite() ||
881            !mat.m22.is_finite() ||
882            !mat.m23.is_finite() ||
883            !mat.m24.is_finite() ||
884            !mat.m31.is_finite() ||
885            !mat.m32.is_finite() ||
886            !mat.m33.is_finite() ||
887            !mat.m34.is_finite() ||
888            !mat.m41.is_finite() ||
889            !mat.m42.is_finite() ||
890            !mat.m43.is_finite() ||
891            !mat.m44.is_finite()
892        {
893            return Err(error::Error::InvalidState(None));
894        }
895
896        let mut to_string = |f: f64| {
897            rooted!(&in(cx) let rooted_value = jsval::DoubleValue(f));
898            let serialization =
899                std::ptr::NonNull::new(unsafe { ToString(cx, rooted_value.handle()) })
900                    .expect("Pointer cannot be null");
901            unsafe { jsstr_to_string(cx, serialization) }
902        };
903
904        // Step 2. Let string be the empty string.
905        // Step 3. If is 2D is true, then:
906        let string = if self.is2D() {
907            // Step 3.1 Append "matrix(" to string.
908            // Step 3.2 Append ! ToString(m11 element) to string.
909            // Step 3.3 Append ", " to string.
910            // Step 3.4 Append ! ToString(m12 element) to string.
911            // Step 3.5 Append ", " to string.
912            // Step 3.6 Append ! ToString(m21 element) to string.
913            // Step 3.7 Append ", " to string.
914            // Step 3.8 Append ! ToString(m22 element) to string.
915            // Step 3.9 Append ", " to string.
916            // Step 3.10 Append ! ToString(m41 element) to string.
917            // Step 3.11 Append ", " to string.
918            // Step 3.12 Append ! ToString(m42 element) to string.
919            // Step 3.13 Append ")" to string.
920            format!(
921                "matrix({}, {}, {}, {}, {}, {})",
922                to_string(mat.m11),
923                to_string(mat.m12),
924                to_string(mat.m21),
925                to_string(mat.m22),
926                to_string(mat.m41),
927                to_string(mat.m42)
928            )
929            .into()
930        }
931        // Step 4. Otherwise:
932        else {
933            // Step 4.1 Append "matrix3d(" to string.
934            // Step 4.2 Append ! ToString(m11 element) to string.
935            // Step 4.3 Append ", " to string.
936            // Step 4.4 Append ! ToString(m12 element) to string.
937            // Step 4.5 Append ", " to string.
938            // Step 4.6 Append ! ToString(m13 element) to string.
939            // Step 4.7 Append ", " to string.
940            // Step 4.8 Append ! ToString(m14 element) to string.
941            // Step 4.9 Append ", " to string.
942            // Step 4.10 Append ! ToString(m21 element) to string.
943            // Step 4.11 Append ", " to string.
944            // Step 4.12 Append ! ToString(m22 element) to string.
945            // Step 4.13 Append ", " to string.
946            // Step 4.14 Append ! ToString(m23 element) to string.
947            // Step 4.15 Append ", " to string.
948            // Step 4.16 Append ! ToString(m24 element) to string.
949            // Step 4.17 Append ", " to string.
950            // Step 4.18 Append ! ToString(m41 element) to string.
951            // Step 4.19 Append ", " to string.
952            // Step 4.20 Append ! ToString(m42 element) to string.
953            // Step 4.21 Append ", " to string.
954            // Step 4.22 Append ! ToString(m43 element) to string.
955            // Step 4.23 Append ", " to string.
956            // Step 4.24 Append ! ToString(m44 element) to string.
957            // Step 4.25 Append ")" to string.
958
959            // NOTE: The spec is wrong and missing the m3* elements.
960            // (https://github.com/w3c/fxtf-drafts/issues/574)
961            format!(
962                "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
963                to_string(mat.m11),
964                to_string(mat.m12),
965                to_string(mat.m13),
966                to_string(mat.m14),
967                to_string(mat.m21),
968                to_string(mat.m22),
969                to_string(mat.m23),
970                to_string(mat.m24),
971                to_string(mat.m31),
972                to_string(mat.m32),
973                to_string(mat.m33),
974                to_string(mat.m34),
975                to_string(mat.m41),
976                to_string(mat.m42),
977                to_string(mat.m43),
978                to_string(mat.m44)
979            )
980            .into()
981        };
982
983        Ok(string)
984    }
985}
986
987impl Serializable for DOMMatrixReadOnly {
988    type Index = DomMatrixIndex;
989    type Data = DomMatrix;
990
991    fn serialize(&self) -> Result<(DomMatrixId, Self::Data), ()> {
992        let serialized = if self.is2D() {
993            DomMatrix {
994                matrix: Transform3D::new(
995                    self.M11(),
996                    self.M12(),
997                    f64::NAN,
998                    f64::NAN,
999                    self.M21(),
1000                    self.M22(),
1001                    f64::NAN,
1002                    f64::NAN,
1003                    f64::NAN,
1004                    f64::NAN,
1005                    f64::NAN,
1006                    f64::NAN,
1007                    self.M41(),
1008                    self.M42(),
1009                    f64::NAN,
1010                    f64::NAN,
1011                ),
1012                is_2d: true,
1013            }
1014        } else {
1015            DomMatrix {
1016                matrix: *self.matrix(),
1017                is_2d: false,
1018            }
1019        };
1020        Ok((DomMatrixId::new(), serialized))
1021    }
1022
1023    fn deserialize(
1024        cx: &mut js::context::JSContext,
1025        owner: &GlobalScope,
1026        serialized: Self::Data,
1027    ) -> Result<DomRoot<Self>, ()>
1028    where
1029        Self: Sized,
1030    {
1031        if serialized.is_2d {
1032            Ok(Self::new(
1033                owner,
1034                true,
1035                Transform3D::new(
1036                    serialized.matrix.m11,
1037                    serialized.matrix.m12,
1038                    0.0,
1039                    0.0,
1040                    serialized.matrix.m21,
1041                    serialized.matrix.m22,
1042                    0.0,
1043                    0.0,
1044                    0.0,
1045                    0.0,
1046                    1.0,
1047                    0.0,
1048                    serialized.matrix.m41,
1049                    serialized.matrix.m42,
1050                    0.0,
1051                    1.0,
1052                ),
1053                cx,
1054            ))
1055        } else {
1056            Ok(Self::new(owner, false, serialized.matrix, cx))
1057        }
1058    }
1059
1060    fn serialized_storage<'a>(
1061        data: StructuredData<'a, '_>,
1062    ) -> &'a mut Option<FxHashMap<DomMatrixId, Self::Data>> {
1063        match data {
1064            StructuredData::Reader(reader) => &mut reader.matrices,
1065            StructuredData::Writer(writer) => &mut writer.matrices,
1066        }
1067    }
1068}
1069
1070// https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly-numbersequence
1071pub(crate) fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Transform3D<f64>)> {
1072    if let Ok(array) = entries.try_into() {
1073        Ok((true, Transform2D::from_array(array).to_3d()))
1074    } else if let Ok(array) = entries.try_into() {
1075        Ok((false, Transform3D::from_array(array)))
1076    } else {
1077        let err_msg = cformat!("Expected 6 or 16 entries, but found {}.", entries.len());
1078        Err(error::Error::Type(err_msg))
1079    }
1080}
1081
1082/// <https://drafts.fxtf.org/geometry-1/#matrix-validate-and-fixup-2d>
1083fn validate_and_fixup_2d(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1084    // <https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero>
1085    let same_value_zero = |x: f64, y: f64| -> bool { x.is_nan() && y.is_nan() || x == y };
1086
1087    // Step 1. If if at least one of the following conditions are true for dict,
1088    // then throw a TypeError exception and abort these steps.
1089    if dict.a.is_some() &&
1090        dict.m11.is_some() &&
1091        !same_value_zero(dict.a.unwrap(), dict.m11.unwrap()) ||
1092        dict.b.is_some() &&
1093            dict.m12.is_some() &&
1094            !same_value_zero(dict.b.unwrap(), dict.m12.unwrap()) ||
1095        dict.c.is_some() &&
1096            dict.m21.is_some() &&
1097            !same_value_zero(dict.c.unwrap(), dict.m21.unwrap()) ||
1098        dict.d.is_some() &&
1099            dict.m22.is_some() &&
1100            !same_value_zero(dict.d.unwrap(), dict.m22.unwrap()) ||
1101        dict.e.is_some() &&
1102            dict.m41.is_some() &&
1103            !same_value_zero(dict.e.unwrap(), dict.m41.unwrap()) ||
1104        dict.f.is_some() &&
1105            dict.m42.is_some() &&
1106            !same_value_zero(dict.f.unwrap(), dict.m42.unwrap())
1107    {
1108        return Err(error::Error::Type(
1109            c"Property mismatch on matrix initialization.".to_owned(),
1110        ));
1111    }
1112
1113    // Step 2. If m11 is not present then set it to the value of member a,
1114    // or value 1 if a is also not present.
1115    let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0));
1116
1117    // Step 3. If m12 is not present then set it to the value of member b,
1118    // or value 0 if b is also not present.
1119    let m12 = dict.m12.unwrap_or(dict.b.unwrap_or(0.0));
1120
1121    // Step 4. If m21 is not present then set it to the value of member c,
1122    // or value 0 if c is also not present.
1123    let m21 = dict.m21.unwrap_or(dict.c.unwrap_or(0.0));
1124
1125    // Step 5. If m22 is not present then set it to the value of member d,
1126    // or value 1 if d is also not present.
1127    let m22 = dict.m22.unwrap_or(dict.d.unwrap_or(1.0));
1128
1129    // Step 6. If m41 is not present then set it to the value of member e,
1130    // or value 0 if e is also not present.
1131    let m41 = dict.m41.unwrap_or(dict.e.unwrap_or(0.0));
1132
1133    // Step 7. If m42 is not present then set it to the value of member f,
1134    // or value 0 if f is also not present.
1135    let m42 = dict.m42.unwrap_or(dict.f.unwrap_or(0.0));
1136
1137    Ok(Transform2D::new(m11, m12, m21, m22, m41, m42))
1138}
1139
1140/// <https://drafts.fxtf.org/geometry-1/#matrix-validate-and-fixup>
1141fn validate_and_fixup(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1142    // Step 1. Validate and fixup (2D) dict.
1143    let transform2d = validate_and_fixup_2d(&dict.parent)?;
1144
1145    // Step 2. If is2D is true and: at least one of m13, m14, m23, m24, m31,
1146    // m32, m34, m43 are present with a value other than 0 or -0, or at least
1147    // one of m33, m44 are present with a value other than 1, then throw
1148    // a TypeError exception and abort these steps.
1149    if dict.is2D == Some(true) &&
1150        (dict.m13 != 0.0 ||
1151            dict.m14 != 0.0 ||
1152            dict.m23 != 0.0 ||
1153            dict.m24 != 0.0 ||
1154            dict.m31 != 0.0 ||
1155            dict.m32 != 0.0 ||
1156            dict.m34 != 0.0 ||
1157            dict.m43 != 0.0 ||
1158            dict.m33 != 1.0 ||
1159            dict.m44 != 1.0)
1160    {
1161        return Err(error::Error::Type(
1162            c"The is2D member is set to true but the input matrix is a 3d matrix.".to_owned(),
1163        ));
1164    }
1165
1166    let mut is_2d = dict.is2D;
1167
1168    // Step 3. If is2D is not present and at least one of m13, m14, m23, m24,
1169    // m31, m32, m34, m43 are present with a value other than 0 or -0, or at
1170    // least one of m33, m44 are present with a value other than 1, set is2D
1171    // to false.
1172    if is_2d.is_none() &&
1173        (dict.m13 != 0.0 ||
1174            dict.m14 != 0.0 ||
1175            dict.m23 != 0.0 ||
1176            dict.m24 != 0.0 ||
1177            dict.m31 != 0.0 ||
1178            dict.m32 != 0.0 ||
1179            dict.m34 != 0.0 ||
1180            dict.m43 != 0.0 ||
1181            dict.m33 != 1.0 ||
1182            dict.m44 != 1.0)
1183    {
1184        is_2d = Some(false);
1185    }
1186
1187    // Step 4. If is2D is still not present, set it to true.
1188    let is_2d = is_2d.unwrap_or(true);
1189
1190    let mut transform = transform2d.to_3d();
1191    transform.m13 = dict.m13;
1192    transform.m14 = dict.m14;
1193    transform.m23 = dict.m23;
1194    transform.m24 = dict.m24;
1195    transform.m31 = dict.m31;
1196    transform.m32 = dict.m32;
1197    transform.m33 = dict.m33;
1198    transform.m34 = dict.m34;
1199    transform.m43 = dict.m43;
1200    transform.m44 = dict.m44;
1201
1202    Ok((is_2d, transform))
1203}
1204
1205/// <https://drafts.fxtf.org/geometry-1/#create-a-dommatrixreadonly-from-the-2d-dictionary>
1206pub(crate) fn dommatrix2dinit_to_matrix(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1207    // Step 1. Validate and fixup (2D) other.
1208    // Step 2. Return the result of invoking create a 2d matrix of type
1209    // DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of
1210    // numbers, the values being the 6 elements m11, m12, m21, m22, m41 and m42
1211    // of other in the given order.
1212    validate_and_fixup_2d(dict)
1213}
1214
1215/// <https://drafts.fxtf.org/geometry-1/#create-a-dommatrix-from-the-dictionary>
1216pub(crate) fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1217    // Step 1. Validate and fixup other.
1218    // Step 2. Return the result of invoking create a 3d matrix of type
1219    // DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of
1220    // numbers, the values being the 16 elements m11, m12, m13, ..., m44
1221    // of other in the given order.
1222    validate_and_fixup(dict)
1223}
1224
1225#[inline]
1226fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
1227    let len = (x * x + y * y + z * z).sqrt();
1228    if len == 0.0 {
1229        (0.0, 0.0, 0.0)
1230    } else {
1231        (x / len, y / len, z / len)
1232    }
1233}
1234
1235pub(crate) fn transform_to_matrix(value: &str) -> Fallible<(bool, Transform3D<f64>)> {
1236    use style::properties::longhands::transform;
1237
1238    let mut input = ParserInput::new(value);
1239    let mut parser = Parser::new(&mut input);
1240    let url_data = Url::parse("about:blank").unwrap().into();
1241    let context =
1242        parser_context_for_anonymous_content(CssRuleType::Style, ParsingMode::DEFAULT, &url_data);
1243
1244    let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) {
1245        Ok(result) => result,
1246        Err(..) => return Err(error::Error::Syntax(None)),
1247    };
1248
1249    let (m, is_3d) = match transform.to_transform_3d_matrix_f64(None) {
1250        Ok(result) => result,
1251        Err(..) => return Err(error::Error::Syntax(None)),
1252    };
1253
1254    Ok((!is_3d, m))
1255}