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::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    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11
121    pub(crate) fn set_m11(&self, value: f64) {
122        self.matrix.borrow_mut().m11 = value;
123    }
124
125    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12
126    pub(crate) fn set_m12(&self, value: f64) {
127        self.matrix.borrow_mut().m12 = value;
128    }
129
130    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13
131    pub(crate) fn set_m13(&self, value: f64) {
132        // For the DOMMatrix interface, setting the m13 attribute must set the
133        // m13 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
134
135        self.matrix.borrow_mut().m13 = value;
136        if value.abs() != 0. {
137            self.is2D.set(false);
138        }
139    }
140
141    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14
142    pub(crate) fn set_m14(&self, value: f64) {
143        // For the DOMMatrix interface, setting the m14 attribute must set the
144        // m14 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
145        self.matrix.borrow_mut().m14 = value;
146
147        if value.abs() != 0. {
148            self.is2D.set(false);
149        }
150    }
151
152    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21
153    pub(crate) fn set_m21(&self, value: f64) {
154        self.matrix.borrow_mut().m21 = value;
155    }
156
157    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22
158    pub(crate) fn set_m22(&self, value: f64) {
159        self.matrix.borrow_mut().m22 = value;
160    }
161
162    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23
163    pub(crate) fn set_m23(&self, value: f64) {
164        // For the DOMMatrix interface, setting the m23 attribute must set the
165        // m23 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
166        self.matrix.borrow_mut().m23 = value;
167
168        if value.abs() != 0. {
169            self.is2D.set(false);
170        }
171    }
172
173    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24
174    pub(crate) fn set_m24(&self, value: f64) {
175        // For the DOMMatrix interface, setting the m24 attribute must set the
176        // m24 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
177        self.matrix.borrow_mut().m24 = value;
178
179        if value.abs() != 0. {
180            self.is2D.set(false);
181        }
182    }
183
184    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31
185    pub(crate) fn set_m31(&self, value: f64) {
186        // For the DOMMatrix interface, setting the m31 attribute must set the
187        // m31 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
188        self.matrix.borrow_mut().m31 = value;
189
190        if value.abs() != 0. {
191            self.is2D.set(false);
192        }
193    }
194
195    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32
196    pub(crate) fn set_m32(&self, value: f64) {
197        // For the DOMMatrix interface, setting the m32 attribute must set the
198        // m32 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
199        self.matrix.borrow_mut().m32 = value;
200
201        if value.abs() != 0. {
202            self.is2D.set(false);
203        }
204    }
205
206    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33
207    pub(crate) fn set_m33(&self, value: f64) {
208        // For the DOMMatrix interface, setting the m33 attribute must set the
209        // m33 element to the new value and, if the new value is not 1, set is 2D to false.
210        self.matrix.borrow_mut().m33 = value;
211
212        if value != 1. {
213            self.is2D.set(false);
214        }
215    }
216
217    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34
218    pub(crate) fn set_m34(&self, value: f64) {
219        // For the DOMMatrix interface, setting the m34 attribute must set the
220        // m34 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
221        self.matrix.borrow_mut().m34 = value;
222
223        if value.abs() != 0. {
224            self.is2D.set(false);
225        }
226    }
227
228    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41
229    pub(crate) fn set_m41(&self, value: f64) {
230        self.matrix.borrow_mut().m41 = value;
231    }
232
233    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42
234    pub(crate) fn set_m42(&self, value: f64) {
235        self.matrix.borrow_mut().m42 = value;
236    }
237
238    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43
239    pub(crate) fn set_m43(&self, value: f64) {
240        // For the DOMMatrix interface, setting the m43 attribute must set the
241        // m43 element to the new value and, if the new value is not 0 or -0, set is 2D to false.
242        self.matrix.borrow_mut().m43 = value;
243
244        if value.abs() != 0. {
245            self.is2D.set(false);
246        }
247    }
248
249    // https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44
250    pub(crate) fn set_m44(&self, value: f64) {
251        // For the DOMMatrix interface, setting the m44 attribute must set the
252        // m44 element to the new value and, if the new value is not 1, set is 2D to false.
253        self.matrix.borrow_mut().m44 = value;
254
255        if value != 1. {
256            self.is2D.set(false);
257        }
258    }
259
260    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-multiplyself
261    pub(crate) fn multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
262        // Step 1.
263        dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
264            // Step 2.
265            let mut matrix = self.matrix.borrow_mut();
266            *matrix = other_matrix.then(&matrix);
267            // Step 3.
268            if !is2D {
269                self.is2D.set(false);
270            }
271            // Step 4 in DOMMatrix.MultiplySelf
272        })
273    }
274
275    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-premultiplyself
276    pub(crate) fn pre_multiply_self(&self, other: &DOMMatrixInit) -> Fallible<()> {
277        // Step 1.
278        dommatrixinit_to_matrix(other).map(|(is2D, other_matrix)| {
279            // Step 2.
280            let mut matrix = self.matrix.borrow_mut();
281            *matrix = matrix.then(&other_matrix);
282            // Step 3.
283            if !is2D {
284                self.is2D.set(false);
285            }
286            // Step 4 in DOMMatrix.PreMultiplySelf
287        })
288    }
289
290    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-translateself
291    pub(crate) fn translate_self(&self, tx: f64, ty: f64, tz: f64) {
292        // Step 1.
293        let translation = Transform3D::translation(tx, ty, tz);
294        let mut matrix = self.matrix.borrow_mut();
295        *matrix = translation.then(&matrix);
296        // Step 2.
297        if tz != 0.0 {
298            self.is2D.set(false);
299        }
300        // Step 3 in DOMMatrix.TranslateSelf
301    }
302
303    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scaleself
304    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        // Step 1.
314        self.translate_self(originX, originY, originZ);
315        // Step 2.
316        let scaleY = scaleY.unwrap_or(scaleX);
317        // Step 3.
318        {
319            let scale3D = Transform3D::scale(scaleX, scaleY, scaleZ);
320            let mut matrix = self.matrix.borrow_mut();
321            *matrix = scale3D.then(&matrix);
322        }
323        // Step 4.
324        originX = -originX;
325        originY = -originY;
326        originZ = -originZ;
327        // Step 5.
328        self.translate_self(originX, originY, originZ);
329        // Step 6.
330        if scaleZ != 1.0 || originZ != 0.0 {
331            self.is2D.set(false);
332        }
333        // Step 7 in DOMMatrix.ScaleSelf
334    }
335
336    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-scale3dself
337    pub(crate) fn scale_3d_self(&self, scale: f64, originX: f64, originY: f64, originZ: f64) {
338        // Step 1.
339        self.translate_self(originX, originY, originZ);
340        // Step 2.
341        {
342            let scale3D = Transform3D::scale(scale, scale, scale);
343            let mut matrix = self.matrix.borrow_mut();
344            *matrix = scale3D.then(&matrix);
345        }
346        // Step 3.
347        self.translate_self(-originX, -originY, -originZ);
348        // Step 4.
349        if scale != 1.0 {
350            self.is2D.set(false);
351        }
352        // Step 5 in DOMMatrix.Scale3dSelf
353    }
354
355    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateself
356    pub(crate) fn rotate_self(&self, mut rotX: f64, mut rotY: Option<f64>, mut rotZ: Option<f64>) {
357        // Step 1.
358        if rotY.is_none() && rotZ.is_none() {
359            rotZ = Some(rotX);
360            rotX = 0.0;
361            rotY = Some(0.0);
362        }
363        // Step 2.
364        let rotY = rotY.unwrap_or(0.0);
365        // Step 3.
366        let rotZ = rotZ.unwrap_or(0.0);
367        // Step 4.
368        if rotX != 0.0 || rotY != 0.0 {
369            self.is2D.set(false);
370        }
371        if rotZ != 0.0 {
372            // Step 5.
373            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            // Step 6.
379            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            // Step 7.
385            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        // Step 8 in DOMMatrix.RotateSelf
390    }
391
392    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotatefromvectorself
393    pub(crate) fn rotate_from_vector_self(&self, x: f64, y: f64) {
394        // don't do anything when the rotation angle is zero or undefined
395        if y != 0.0 || x < 0.0 {
396            // Step 1.
397            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        // Step 2 in DOMMatrix.RotateFromVectorSelf
403    }
404
405    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-rotateaxisangleself
406    pub(crate) fn rotate_axis_angle_self(&self, x: f64, y: f64, z: f64, angle: f64) {
407        // Step 1.
408        let (norm_x, norm_y, norm_z) = normalize_point(x, y, z);
409        // Beware: pass negated value until https://github.com/servo/euclid/issues/354
410        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        // Step 2.
415        if x != 0.0 || y != 0.0 {
416            self.is2D.set(false);
417        }
418        // Step 3 in DOMMatrix.RotateAxisAngleSelf
419    }
420
421    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewxself
422    pub(crate) fn skew_x_self(&self, sx: f64) {
423        // Step 1.
424        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        // Step 2 in DOMMatrix.SkewXSelf
428    }
429
430    // https://drafts.fxtf.org/geometry-1/#dom-dommatrix-skewyself
431    pub(crate) fn skew_y_self(&self, sy: f64) {
432        // Step 1.
433        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        // Step 2 in DOMMatrix.SkewYSelf
437    }
438
439    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrix-invertself>
440    pub(crate) fn invert_self(&self) {
441        let mut matrix = self.matrix.borrow_mut();
442        // Step 1. Invert the current matrix.
443        let inverted = match self.is2D() {
444            true => matrix.to_2d().inverse().map(|m| m.to_3d()),
445            false => matrix.inverse(),
446        };
447
448        // Step 2. If the current matrix is not invertible set all attributes to NaN
449        // and set is 2D to false.
450        *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        // Step 3 in DOMMatrix.InvertSelf
472    }
473}
474
475#[expect(non_snake_case)]
476impl DOMMatrixReadOnlyMethods<crate::DomTypeHolder> for DOMMatrixReadOnly {
477    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly>
478    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-frommatrix>
514    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat32array>
523    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat64array>
543    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11>
558    fn M11(&self) -> f64 {
559        self.matrix.borrow().m11
560    }
561
562    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12>
563    fn M12(&self) -> f64 {
564        self.matrix.borrow().m12
565    }
566
567    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13>
568    fn M13(&self) -> f64 {
569        self.matrix.borrow().m13
570    }
571
572    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14>
573    fn M14(&self) -> f64 {
574        self.matrix.borrow().m14
575    }
576
577    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21>
578    fn M21(&self) -> f64 {
579        self.matrix.borrow().m21
580    }
581
582    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22>
583    fn M22(&self) -> f64 {
584        self.matrix.borrow().m22
585    }
586
587    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23>
588    fn M23(&self) -> f64 {
589        self.matrix.borrow().m23
590    }
591
592    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24>
593    fn M24(&self) -> f64 {
594        self.matrix.borrow().m24
595    }
596
597    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31>
598    fn M31(&self) -> f64 {
599        self.matrix.borrow().m31
600    }
601
602    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32>
603    fn M32(&self) -> f64 {
604        self.matrix.borrow().m32
605    }
606
607    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33>
608    fn M33(&self) -> f64 {
609        self.matrix.borrow().m33
610    }
611
612    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34>
613    fn M34(&self) -> f64 {
614        self.matrix.borrow().m34
615    }
616
617    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41>
618    fn M41(&self) -> f64 {
619        self.matrix.borrow().m41
620    }
621
622    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42>
623    fn M42(&self) -> f64 {
624        self.matrix.borrow().m42
625    }
626
627    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43>
628    fn M43(&self) -> f64 {
629        self.matrix.borrow().m43
630    }
631
632    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44>
633    fn M44(&self) -> f64 {
634        self.matrix.borrow().m44
635    }
636
637    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a>
638    fn A(&self) -> f64 {
639        self.M11()
640    }
641
642    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b>
643    fn B(&self) -> f64 {
644        self.M12()
645    }
646
647    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c>
648    fn C(&self) -> f64 {
649        self.M21()
650    }
651
652    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d>
653    fn D(&self) -> f64 {
654        self.M22()
655    }
656
657    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e>
658    fn E(&self) -> f64 {
659        self.M41()
660    }
661
662    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f>
663    fn F(&self) -> f64 {
664        self.M42()
665    }
666
667    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-is2d>
668    fn Is2D(&self) -> bool {
669        self.is2D.get()
670    }
671
672    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-isidentity>
673    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-translate>
694    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale>
705    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    /// <https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-scalenonuniform>
720    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale3d>
737    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotate>
750    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotatefromvector>
761    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotateaxisangle>
771    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewx>
783    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewy>
788    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-multiply>
793    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipx>
802    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipy>
812    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-inverse>
822    fn Inverse(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
823        DOMMatrix::from_readonly(&self.global(), self, cx).InvertSelf()
824    }
825
826    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-transformpoint>
827    fn TransformPoint(
828        &self,
829        cx: &mut js::context::JSContext,
830        point: &DOMPointInit,
831    ) -> DomRoot<DOMPoint> {
832        // Euclid always normalizes the homogeneous coordinate which is usually the right
833        // thing but may (?) not be compliant with the CSS matrix spec (or at least is
834        // probably not the behavior web authors will expect even if it is mathematically
835        // correct in the context of geometry computations).
836        // Since this is the only place where this is needed, better implement it here
837        // than in euclid (which does not have a notion of 4d points).
838        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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat32array>
848    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    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat64array>
865    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    // https://drafts.fxtf.org/geometry/#dommatrixreadonly-stringification-behavior
875    #[expect(unsafe_code)]
876    fn Stringifier(&self, cx: &mut js::context::JSContext) -> Fallible<DOMString> {
877        // Step 1. If one or more of m11 element through m44 element are a non-finite value,
878        // then throw an "InvalidStateError" DOMException.
879        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        // Step 2. Let string be the empty string.
909        // Step 3. If is 2D is true, then:
910        let string = if self.is2D() {
911            // Step 3.1 Append "matrix(" to string.
912            // Step 3.2 Append ! ToString(m11 element) to string.
913            // Step 3.3 Append ", " to string.
914            // Step 3.4 Append ! ToString(m12 element) to string.
915            // Step 3.5 Append ", " to string.
916            // Step 3.6 Append ! ToString(m21 element) to string.
917            // Step 3.7 Append ", " to string.
918            // Step 3.8 Append ! ToString(m22 element) to string.
919            // Step 3.9 Append ", " to string.
920            // Step 3.10 Append ! ToString(m41 element) to string.
921            // Step 3.11 Append ", " to string.
922            // Step 3.12 Append ! ToString(m42 element) to string.
923            // Step 3.13 Append ")" to string.
924            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        // Step 4. Otherwise:
936        else {
937            // Step 4.1 Append "matrix3d(" to string.
938            // Step 4.2 Append ! ToString(m11 element) to string.
939            // Step 4.3 Append ", " to string.
940            // Step 4.4 Append ! ToString(m12 element) to string.
941            // Step 4.5 Append ", " to string.
942            // Step 4.6 Append ! ToString(m13 element) to string.
943            // Step 4.7 Append ", " to string.
944            // Step 4.8 Append ! ToString(m14 element) to string.
945            // Step 4.9 Append ", " to string.
946            // Step 4.10 Append ! ToString(m21 element) to string.
947            // Step 4.11 Append ", " to string.
948            // Step 4.12 Append ! ToString(m22 element) to string.
949            // Step 4.13 Append ", " to string.
950            // Step 4.14 Append ! ToString(m23 element) to string.
951            // Step 4.15 Append ", " to string.
952            // Step 4.16 Append ! ToString(m24 element) to string.
953            // Step 4.17 Append ", " to string.
954            // Step 4.18 Append ! ToString(m41 element) to string.
955            // Step 4.19 Append ", " to string.
956            // Step 4.20 Append ! ToString(m42 element) to string.
957            // Step 4.21 Append ", " to string.
958            // Step 4.22 Append ! ToString(m43 element) to string.
959            // Step 4.23 Append ", " to string.
960            // Step 4.24 Append ! ToString(m44 element) to string.
961            // Step 4.25 Append ")" to string.
962
963            // NOTE: The spec is wrong and missing the m3* elements.
964            // (https://github.com/w3c/fxtf-drafts/issues/574)
965            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
1074// https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly-numbersequence
1075pub(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
1086/// <https://drafts.fxtf.org/geometry-1/#matrix-validate-and-fixup-2d>
1087fn validate_and_fixup_2d(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1088    // <https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero>
1089    let same_value_zero = |x: f64, y: f64| -> bool { x.is_nan() && y.is_nan() || x == y };
1090
1091    // Step 1. If if at least one of the following conditions are true for dict,
1092    // then throw a TypeError exception and abort these steps.
1093    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    // Step 2. If m11 is not present then set it to the value of member a,
1118    // or value 1 if a is also not present.
1119    let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0));
1120
1121    // Step 3. If m12 is not present then set it to the value of member b,
1122    // or value 0 if b is also not present.
1123    let m12 = dict.m12.unwrap_or(dict.b.unwrap_or(0.0));
1124
1125    // Step 4. If m21 is not present then set it to the value of member c,
1126    // or value 0 if c is also not present.
1127    let m21 = dict.m21.unwrap_or(dict.c.unwrap_or(0.0));
1128
1129    // Step 5. If m22 is not present then set it to the value of member d,
1130    // or value 1 if d is also not present.
1131    let m22 = dict.m22.unwrap_or(dict.d.unwrap_or(1.0));
1132
1133    // Step 6. If m41 is not present then set it to the value of member e,
1134    // or value 0 if e is also not present.
1135    let m41 = dict.m41.unwrap_or(dict.e.unwrap_or(0.0));
1136
1137    // Step 7. If m42 is not present then set it to the value of member f,
1138    // or value 0 if f is also not present.
1139    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
1144/// <https://drafts.fxtf.org/geometry-1/#matrix-validate-and-fixup>
1145fn validate_and_fixup(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1146    // Step 1. Validate and fixup (2D) dict.
1147    let transform2d = validate_and_fixup_2d(&dict.parent)?;
1148
1149    // Step 2. If is2D is true and: at least one of m13, m14, m23, m24, m31,
1150    // m32, m34, m43 are present with a value other than 0 or -0, or at least
1151    // one of m33, m44 are present with a value other than 1, then throw
1152    // a TypeError exception and abort these steps.
1153    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    // Step 3. If is2D is not present and at least one of m13, m14, m23, m24,
1173    // m31, m32, m34, m43 are present with a value other than 0 or -0, or at
1174    // least one of m33, m44 are present with a value other than 1, set is2D
1175    // to false.
1176    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    // Step 4. If is2D is still not present, set it to true.
1192    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
1209/// <https://drafts.fxtf.org/geometry-1/#create-a-dommatrixreadonly-from-the-2d-dictionary>
1210pub(crate) fn dommatrix2dinit_to_matrix(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1211    // Step 1. Validate and fixup (2D) other.
1212    // Step 2. Return the result of invoking create a 2d matrix of type
1213    // DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of
1214    // numbers, the values being the 6 elements m11, m12, m21, m22, m41 and m42
1215    // of other in the given order.
1216    validate_and_fixup_2d(dict)
1217}
1218
1219/// <https://drafts.fxtf.org/geometry-1/#create-a-dommatrix-from-the-dictionary>
1220pub(crate) fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1221    // Step 1. Validate and fixup other.
1222    // Step 2. Return the result of invoking create a 3d matrix of type
1223    // DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of
1224    // numbers, the values being the 16 elements m11, m12, m13, ..., m44
1225    // of other in the given order.
1226    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}