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_and_cx};
22use script_bindings::trace::RootedTraceableBox;
23use servo_base::id::{DomMatrixId, DomMatrixIndex};
24use servo_constellation_traits::DomMatrix;
25use style::stylesheets::CssRuleType;
26use style_traits::ParsingMode;
27use url::Url;
28
29use crate::css::parser_context_for_anonymous_content;
30use crate::dom::bindings::buffer_source::create_buffer_source;
31use crate::dom::bindings::codegen::Bindings::DOMMatrixBinding::{
32    DOMMatrix2DInit, DOMMatrixInit, DOMMatrixMethods,
33};
34use crate::dom::bindings::codegen::Bindings::DOMMatrixReadOnlyBinding::DOMMatrixReadOnlyMethods;
35use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
36use crate::dom::bindings::codegen::UnionTypes::StringOrUnrestrictedDoubleSequence;
37use crate::dom::bindings::error;
38use crate::dom::bindings::error::Fallible;
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::reflector::DomGlobal;
41use crate::dom::bindings::root::DomRoot;
42use crate::dom::bindings::serializable::Serializable;
43use crate::dom::bindings::str::DOMString;
44use crate::dom::bindings::structuredclone::StructuredData;
45use crate::dom::dommatrix::DOMMatrix;
46use crate::dom::dompoint::DOMPoint;
47use crate::dom::globalscope::GlobalScope;
48use crate::dom::window::Window;
49
50#[dom_struct]
51#[expect(non_snake_case)]
52pub(crate) struct DOMMatrixReadOnly {
53    reflector_: Reflector,
54    #[no_trace]
55    matrix: DomRefCell<Transform3D<f64>>,
56    is2D: Cell<bool>,
57}
58
59#[expect(non_snake_case)]
60impl DOMMatrixReadOnly {
61    pub(crate) fn new(
62        cx: &mut js::context::JSContext,
63        global: &GlobalScope,
64        is2D: bool,
65        matrix: Transform3D<f64>,
66    ) -> DomRoot<Self> {
67        Self::new_with_proto(cx, global, None, is2D, matrix)
68    }
69
70    #[cfg_attr(crown, expect(crown::unrooted_must_root))]
71    fn new_with_proto(
72        cx: &mut js::context::JSContext,
73        global: &GlobalScope,
74        proto: Option<HandleObject>,
75        is2D: bool,
76        matrix: Transform3D<f64>,
77    ) -> DomRoot<Self> {
78        let dommatrix = Self::new_inherited(is2D, matrix);
79        reflect_dom_object_with_proto_and_cx(Box::new(dommatrix), global, proto, cx)
80    }
81
82    pub(crate) fn new_inherited(is2D: bool, matrix: Transform3D<f64>) -> Self {
83        DOMMatrixReadOnly {
84            reflector_: Reflector::new(),
85            matrix: DomRefCell::new(matrix),
86            is2D: Cell::new(is2D),
87        }
88    }
89
90    pub(crate) fn matrix(&self) -> Ref<'_, Transform3D<f64>> {
91        self.matrix.borrow()
92    }
93
94    pub(crate) fn set_matrix(&self, value: Transform3D<f64>) {
95        self.set_m11(value.m11);
96        self.set_m12(value.m12);
97        self.set_m13(value.m13);
98        self.set_m14(value.m14);
99        self.set_m21(value.m21);
100        self.set_m22(value.m22);
101        self.set_m23(value.m23);
102        self.set_m24(value.m24);
103        self.set_m31(value.m31);
104        self.set_m32(value.m32);
105        self.set_m33(value.m33);
106        self.set_m34(value.m34);
107        self.set_m41(value.m41);
108        self.set_m42(value.m42);
109        self.set_m43(value.m43);
110        self.set_m44(value.m44);
111    }
112
113    pub(crate) fn is2D(&self) -> bool {
114        self.is2D.get()
115    }
116
117    pub(crate) fn set_is2D(&self, value: bool) {
118        self.is2D.set(value);
119    }
120
121    // 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                cx,
488                global,
489                proto,
490                true,
491                Transform3D::identity(),
492            ));
493        }
494        match init.unwrap() {
495            StringOrUnrestrictedDoubleSequence::String(ref s) => {
496                if !global.is::<Window>() {
497                    return Err(error::Error::Type(
498                        c"String constructor is only supported in the main thread.".to_owned(),
499                    ));
500                }
501                if s.is_empty() {
502                    return Ok(Self::new(cx, global, true, Transform3D::identity()));
503                }
504                transform_to_matrix(&s.str())
505                    .map(|(is2D, matrix)| Self::new_with_proto(cx, global, proto, is2D, matrix))
506            },
507            StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(ref entries) => {
508                entries_to_matrix(&entries[..])
509                    .map(|(is2D, matrix)| Self::new_with_proto(cx, global, proto, is2D, matrix))
510            },
511        }
512    }
513
514    /// <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(cx, global, is2D, matrix))
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
530            .to_vec()
531            .unwrap_or_default()
532            .iter()
533            .map(|&x| x as f64)
534            .collect();
535        DOMMatrixReadOnly::Constructor(
536            cx,
537            global,
538            None,
539            Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
540        )
541    }
542
543    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-fromfloat64array>
544    fn FromFloat64Array(
545        cx: &mut js::context::JSContext,
546        global: &GlobalScope,
547        array: CustomAutoRooterGuard<Float64Array>,
548    ) -> Fallible<DomRoot<DOMMatrixReadOnly>> {
549        let vec: Vec<f64> = array.to_vec().unwrap_or_default();
550        DOMMatrixReadOnly::Constructor(
551            cx,
552            global,
553            None,
554            Some(StringOrUnrestrictedDoubleSequence::UnrestrictedDoubleSequence(vec)),
555        )
556    }
557
558    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m11>
559    fn M11(&self) -> f64 {
560        self.matrix.borrow().m11
561    }
562
563    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m12>
564    fn M12(&self) -> f64 {
565        self.matrix.borrow().m12
566    }
567
568    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m13>
569    fn M13(&self) -> f64 {
570        self.matrix.borrow().m13
571    }
572
573    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m14>
574    fn M14(&self) -> f64 {
575        self.matrix.borrow().m14
576    }
577
578    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m21>
579    fn M21(&self) -> f64 {
580        self.matrix.borrow().m21
581    }
582
583    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m22>
584    fn M22(&self) -> f64 {
585        self.matrix.borrow().m22
586    }
587
588    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m23>
589    fn M23(&self) -> f64 {
590        self.matrix.borrow().m23
591    }
592
593    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m24>
594    fn M24(&self) -> f64 {
595        self.matrix.borrow().m24
596    }
597
598    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m31>
599    fn M31(&self) -> f64 {
600        self.matrix.borrow().m31
601    }
602
603    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m32>
604    fn M32(&self) -> f64 {
605        self.matrix.borrow().m32
606    }
607
608    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m33>
609    fn M33(&self) -> f64 {
610        self.matrix.borrow().m33
611    }
612
613    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m34>
614    fn M34(&self) -> f64 {
615        self.matrix.borrow().m34
616    }
617
618    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m41>
619    fn M41(&self) -> f64 {
620        self.matrix.borrow().m41
621    }
622
623    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m42>
624    fn M42(&self) -> f64 {
625        self.matrix.borrow().m42
626    }
627
628    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m43>
629    fn M43(&self) -> f64 {
630        self.matrix.borrow().m43
631    }
632
633    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-m44>
634    fn M44(&self) -> f64 {
635        self.matrix.borrow().m44
636    }
637
638    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-a>
639    fn A(&self) -> f64 {
640        self.M11()
641    }
642
643    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-b>
644    fn B(&self) -> f64 {
645        self.M12()
646    }
647
648    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-c>
649    fn C(&self) -> f64 {
650        self.M21()
651    }
652
653    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-d>
654    fn D(&self) -> f64 {
655        self.M22()
656    }
657
658    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-e>
659    fn E(&self) -> f64 {
660        self.M41()
661    }
662
663    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-f>
664    fn F(&self) -> f64 {
665        self.M42()
666    }
667
668    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-is2d>
669    fn Is2D(&self) -> bool {
670        self.is2D.get()
671    }
672
673    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-isidentity>
674    fn IsIdentity(&self) -> bool {
675        let matrix = self.matrix.borrow();
676        matrix.m12 == 0.0 &&
677            matrix.m13 == 0.0 &&
678            matrix.m14 == 0.0 &&
679            matrix.m21 == 0.0 &&
680            matrix.m23 == 0.0 &&
681            matrix.m24 == 0.0 &&
682            matrix.m31 == 0.0 &&
683            matrix.m32 == 0.0 &&
684            matrix.m34 == 0.0 &&
685            matrix.m41 == 0.0 &&
686            matrix.m42 == 0.0 &&
687            matrix.m43 == 0.0 &&
688            matrix.m11 == 1.0 &&
689            matrix.m22 == 1.0 &&
690            matrix.m33 == 1.0 &&
691            matrix.m44 == 1.0
692    }
693
694    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-translate>
695    fn Translate(
696        &self,
697        cx: &mut js::context::JSContext,
698        tx: f64,
699        ty: f64,
700        tz: f64,
701    ) -> DomRoot<DOMMatrix> {
702        DOMMatrix::from_readonly(&self.global(), self, cx).TranslateSelf(tx, ty, tz)
703    }
704
705    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale>
706    fn Scale(
707        &self,
708        cx: &mut js::context::JSContext,
709        scaleX: f64,
710        scaleY: Option<f64>,
711        scaleZ: f64,
712        originX: f64,
713        originY: f64,
714        originZ: f64,
715    ) -> DomRoot<DOMMatrix> {
716        DOMMatrix::from_readonly(&self.global(), self, cx)
717            .ScaleSelf(scaleX, scaleY, scaleZ, originX, originY, originZ)
718    }
719
720    /// <https://drafts.fxtf.org/geometry/#dom-dommatrixreadonly-scalenonuniform>
721    fn ScaleNonUniform(
722        &self,
723        cx: &mut js::context::JSContext,
724        scaleX: f64,
725        scaleY: f64,
726    ) -> DomRoot<DOMMatrix> {
727        DOMMatrix::from_readonly(&self.global(), self, cx).ScaleSelf(
728            scaleX,
729            Some(scaleY),
730            1.0,
731            0.0,
732            0.0,
733            0.0,
734        )
735    }
736
737    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-scale3d>
738    fn Scale3d(
739        &self,
740        cx: &mut js::context::JSContext,
741        scale: f64,
742        originX: f64,
743        originY: f64,
744        originZ: f64,
745    ) -> DomRoot<DOMMatrix> {
746        DOMMatrix::from_readonly(&self.global(), self, cx)
747            .Scale3dSelf(scale, originX, originY, originZ)
748    }
749
750    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotate>
751    fn Rotate(
752        &self,
753        cx: &mut js::context::JSContext,
754        rotX: f64,
755        rotY: Option<f64>,
756        rotZ: Option<f64>,
757    ) -> DomRoot<DOMMatrix> {
758        DOMMatrix::from_readonly(&self.global(), self, cx).RotateSelf(rotX, rotY, rotZ)
759    }
760
761    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotatefromvector>
762    fn RotateFromVector(
763        &self,
764        cx: &mut js::context::JSContext,
765        x: f64,
766        y: f64,
767    ) -> DomRoot<DOMMatrix> {
768        DOMMatrix::from_readonly(&self.global(), self, cx).RotateFromVectorSelf(x, y)
769    }
770
771    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-rotateaxisangle>
772    fn RotateAxisAngle(
773        &self,
774        cx: &mut js::context::JSContext,
775        x: f64,
776        y: f64,
777        z: f64,
778        angle: f64,
779    ) -> DomRoot<DOMMatrix> {
780        DOMMatrix::from_readonly(&self.global(), self, cx).RotateAxisAngleSelf(x, y, z, angle)
781    }
782
783    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewx>
784    fn SkewX(&self, cx: &mut js::context::JSContext, sx: f64) -> DomRoot<DOMMatrix> {
785        DOMMatrix::from_readonly(&self.global(), self, cx).SkewXSelf(sx)
786    }
787
788    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-skewy>
789    fn SkewY(&self, cx: &mut js::context::JSContext, sy: f64) -> DomRoot<DOMMatrix> {
790        DOMMatrix::from_readonly(&self.global(), self, cx).SkewYSelf(sy)
791    }
792
793    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-multiply>
794    fn Multiply(
795        &self,
796        cx: &mut js::context::JSContext,
797        other: &DOMMatrixInit,
798    ) -> Fallible<DomRoot<DOMMatrix>> {
799        DOMMatrix::from_readonly(&self.global(), self, cx).MultiplySelf(other)
800    }
801
802    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipx>
803    fn FlipX(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
804        let is2D = self.is2D.get();
805        let flip = Transform3D::new(
806            -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
807        );
808        let matrix = flip.then(&self.matrix.borrow());
809        DOMMatrix::new(cx, &self.global(), is2D, matrix)
810    }
811
812    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-flipy>
813    fn FlipY(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
814        let is2D = self.is2D.get();
815        let flip = Transform3D::new(
816            1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
817        );
818        let matrix = flip.then(&self.matrix.borrow());
819        DOMMatrix::new(cx, &self.global(), is2D, matrix)
820    }
821
822    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-inverse>
823    fn Inverse(&self, cx: &mut js::context::JSContext) -> DomRoot<DOMMatrix> {
824        DOMMatrix::from_readonly(&self.global(), self, cx).InvertSelf()
825    }
826
827    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-transformpoint>
828    fn TransformPoint(
829        &self,
830        cx: &mut js::context::JSContext,
831        point: &DOMPointInit,
832    ) -> DomRoot<DOMPoint> {
833        // Euclid always normalizes the homogeneous coordinate which is usually the right
834        // thing but may (?) not be compliant with the CSS matrix spec (or at least is
835        // probably not the behavior web authors will expect even if it is mathematically
836        // correct in the context of geometry computations).
837        // Since this is the only place where this is needed, better implement it here
838        // than in euclid (which does not have a notion of 4d points).
839        let mat = self.matrix.borrow();
840        let x = point.x * mat.m11 + point.y * mat.m21 + point.z * mat.m31 + point.w * mat.m41;
841        let y = point.x * mat.m12 + point.y * mat.m22 + point.z * mat.m32 + point.w * mat.m42;
842        let z = point.x * mat.m13 + point.y * mat.m23 + point.z * mat.m33 + point.w * mat.m43;
843        let w = point.x * mat.m14 + point.y * mat.m24 + point.z * mat.m34 + point.w * mat.m44;
844
845        DOMPoint::new(cx, &self.global(), x, y, z, w)
846    }
847
848    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat32array>
849    fn ToFloat32Array(
850        &self,
851        cx: &mut js::context::JSContext,
852    ) -> RootedTraceableBox<HeapFloat32Array> {
853        let vec: Vec<f32> = self
854            .matrix
855            .borrow()
856            .to_array()
857            .iter()
858            .map(|&x| x as f32)
859            .collect();
860        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
861        create_buffer_source(cx, &vec, array.handle_mut())
862            .expect("Converting matrix to float32 array should never fail")
863    }
864
865    /// <https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-tofloat64array>
866    fn ToFloat64Array(
867        &self,
868        cx: &mut js::context::JSContext,
869    ) -> RootedTraceableBox<HeapFloat64Array> {
870        rooted!(&in(cx) let mut array = ptr::null_mut::<JSObject>());
871        create_buffer_source(cx, &self.matrix.borrow().to_array(), array.handle_mut())
872            .expect("Converting matrix to float64 array should never fail")
873    }
874
875    // https://drafts.fxtf.org/geometry/#dommatrixreadonly-stringification-behavior
876    #[expect(unsafe_code)]
877    fn Stringifier(&self, cx: &mut js::context::JSContext) -> Fallible<DOMString> {
878        // Step 1. If one or more of m11 element through m44 element are a non-finite value,
879        // then throw an "InvalidStateError" DOMException.
880        let mat = self.matrix.borrow();
881        if !mat.m11.is_finite() ||
882            !mat.m12.is_finite() ||
883            !mat.m13.is_finite() ||
884            !mat.m14.is_finite() ||
885            !mat.m21.is_finite() ||
886            !mat.m22.is_finite() ||
887            !mat.m23.is_finite() ||
888            !mat.m24.is_finite() ||
889            !mat.m31.is_finite() ||
890            !mat.m32.is_finite() ||
891            !mat.m33.is_finite() ||
892            !mat.m34.is_finite() ||
893            !mat.m41.is_finite() ||
894            !mat.m42.is_finite() ||
895            !mat.m43.is_finite() ||
896            !mat.m44.is_finite()
897        {
898            return Err(error::Error::InvalidState(None));
899        }
900
901        let mut to_string = |f: f64| {
902            rooted!(&in(cx) let rooted_value = jsval::DoubleValue(f));
903            let serialization =
904                std::ptr::NonNull::new(unsafe { ToString(cx, rooted_value.handle()) })
905                    .expect("Pointer cannot be null");
906            unsafe { jsstr_to_string(cx, serialization) }
907        };
908
909        // Step 2. Let string be the empty string.
910        // Step 3. If is 2D is true, then:
911        let string = if self.is2D() {
912            // Step 3.1 Append "matrix(" to string.
913            // Step 3.2 Append ! ToString(m11 element) to string.
914            // Step 3.3 Append ", " to string.
915            // Step 3.4 Append ! ToString(m12 element) to string.
916            // Step 3.5 Append ", " to string.
917            // Step 3.6 Append ! ToString(m21 element) to string.
918            // Step 3.7 Append ", " to string.
919            // Step 3.8 Append ! ToString(m22 element) to string.
920            // Step 3.9 Append ", " to string.
921            // Step 3.10 Append ! ToString(m41 element) to string.
922            // Step 3.11 Append ", " to string.
923            // Step 3.12 Append ! ToString(m42 element) to string.
924            // Step 3.13 Append ")" to string.
925            format!(
926                "matrix({}, {}, {}, {}, {}, {})",
927                to_string(mat.m11),
928                to_string(mat.m12),
929                to_string(mat.m21),
930                to_string(mat.m22),
931                to_string(mat.m41),
932                to_string(mat.m42)
933            )
934            .into()
935        }
936        // Step 4. Otherwise:
937        else {
938            // Step 4.1 Append "matrix3d(" to string.
939            // Step 4.2 Append ! ToString(m11 element) to string.
940            // Step 4.3 Append ", " to string.
941            // Step 4.4 Append ! ToString(m12 element) to string.
942            // Step 4.5 Append ", " to string.
943            // Step 4.6 Append ! ToString(m13 element) to string.
944            // Step 4.7 Append ", " to string.
945            // Step 4.8 Append ! ToString(m14 element) to string.
946            // Step 4.9 Append ", " to string.
947            // Step 4.10 Append ! ToString(m21 element) to string.
948            // Step 4.11 Append ", " to string.
949            // Step 4.12 Append ! ToString(m22 element) to string.
950            // Step 4.13 Append ", " to string.
951            // Step 4.14 Append ! ToString(m23 element) to string.
952            // Step 4.15 Append ", " to string.
953            // Step 4.16 Append ! ToString(m24 element) to string.
954            // Step 4.17 Append ", " to string.
955            // Step 4.18 Append ! ToString(m41 element) to string.
956            // Step 4.19 Append ", " to string.
957            // Step 4.20 Append ! ToString(m42 element) to string.
958            // Step 4.21 Append ", " to string.
959            // Step 4.22 Append ! ToString(m43 element) to string.
960            // Step 4.23 Append ", " to string.
961            // Step 4.24 Append ! ToString(m44 element) to string.
962            // Step 4.25 Append ")" to string.
963
964            // NOTE: The spec is wrong and missing the m3* elements.
965            // (https://github.com/w3c/fxtf-drafts/issues/574)
966            format!(
967                "matrix3d({}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {})",
968                to_string(mat.m11),
969                to_string(mat.m12),
970                to_string(mat.m13),
971                to_string(mat.m14),
972                to_string(mat.m21),
973                to_string(mat.m22),
974                to_string(mat.m23),
975                to_string(mat.m24),
976                to_string(mat.m31),
977                to_string(mat.m32),
978                to_string(mat.m33),
979                to_string(mat.m34),
980                to_string(mat.m41),
981                to_string(mat.m42),
982                to_string(mat.m43),
983                to_string(mat.m44)
984            )
985            .into()
986        };
987
988        Ok(string)
989    }
990}
991
992impl Serializable for DOMMatrixReadOnly {
993    type Index = DomMatrixIndex;
994    type Data = DomMatrix;
995
996    fn serialize(&self, _no_gc: &NoGC) -> Result<(DomMatrixId, Self::Data), ()> {
997        let serialized = if self.is2D() {
998            DomMatrix {
999                matrix: Transform3D::new(
1000                    self.M11(),
1001                    self.M12(),
1002                    f64::NAN,
1003                    f64::NAN,
1004                    self.M21(),
1005                    self.M22(),
1006                    f64::NAN,
1007                    f64::NAN,
1008                    f64::NAN,
1009                    f64::NAN,
1010                    f64::NAN,
1011                    f64::NAN,
1012                    self.M41(),
1013                    self.M42(),
1014                    f64::NAN,
1015                    f64::NAN,
1016                ),
1017                is_2d: true,
1018            }
1019        } else {
1020            DomMatrix {
1021                matrix: *self.matrix(),
1022                is_2d: false,
1023            }
1024        };
1025        Ok((DomMatrixId::new(), serialized))
1026    }
1027
1028    fn deserialize(
1029        cx: &mut js::context::JSContext,
1030        owner: &GlobalScope,
1031        serialized: Self::Data,
1032    ) -> Result<DomRoot<Self>, ()>
1033    where
1034        Self: Sized,
1035    {
1036        if serialized.is_2d {
1037            Ok(Self::new(
1038                cx,
1039                owner,
1040                true,
1041                Transform3D::new(
1042                    serialized.matrix.m11,
1043                    serialized.matrix.m12,
1044                    0.0,
1045                    0.0,
1046                    serialized.matrix.m21,
1047                    serialized.matrix.m22,
1048                    0.0,
1049                    0.0,
1050                    0.0,
1051                    0.0,
1052                    1.0,
1053                    0.0,
1054                    serialized.matrix.m41,
1055                    serialized.matrix.m42,
1056                    0.0,
1057                    1.0,
1058                ),
1059            ))
1060        } else {
1061            Ok(Self::new(cx, owner, false, serialized.matrix))
1062        }
1063    }
1064
1065    fn serialized_storage<'a>(
1066        data: StructuredData<'a, '_>,
1067    ) -> &'a mut Option<FxHashMap<DomMatrixId, Self::Data>> {
1068        match data {
1069            StructuredData::Reader(reader) => &mut reader.matrices,
1070            StructuredData::Writer(writer) => &mut writer.matrices,
1071        }
1072    }
1073}
1074
1075// https://drafts.fxtf.org/geometry-1/#dom-dommatrixreadonly-dommatrixreadonly-numbersequence
1076pub(crate) fn entries_to_matrix(entries: &[f64]) -> Fallible<(bool, Transform3D<f64>)> {
1077    if let Ok(array) = entries.try_into() {
1078        Ok((true, Transform2D::from_array(array).to_3d()))
1079    } else if let Ok(array) = entries.try_into() {
1080        Ok((false, Transform3D::from_array(array)))
1081    } else {
1082        let err_msg = cformat!("Expected 6 or 16 entries, but found {}.", entries.len());
1083        Err(error::Error::Type(err_msg))
1084    }
1085}
1086
1087/// <https://drafts.fxtf.org/geometry-1/#matrix-validate-and-fixup-2d>
1088fn validate_and_fixup_2d(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1089    // <https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero>
1090    let same_value_zero = |x: f64, y: f64| -> bool { x.is_nan() && y.is_nan() || x == y };
1091
1092    // Step 1. If if at least one of the following conditions are true for dict,
1093    // then throw a TypeError exception and abort these steps.
1094    if dict.a.is_some() &&
1095        dict.m11.is_some() &&
1096        !same_value_zero(dict.a.unwrap(), dict.m11.unwrap()) ||
1097        dict.b.is_some() &&
1098            dict.m12.is_some() &&
1099            !same_value_zero(dict.b.unwrap(), dict.m12.unwrap()) ||
1100        dict.c.is_some() &&
1101            dict.m21.is_some() &&
1102            !same_value_zero(dict.c.unwrap(), dict.m21.unwrap()) ||
1103        dict.d.is_some() &&
1104            dict.m22.is_some() &&
1105            !same_value_zero(dict.d.unwrap(), dict.m22.unwrap()) ||
1106        dict.e.is_some() &&
1107            dict.m41.is_some() &&
1108            !same_value_zero(dict.e.unwrap(), dict.m41.unwrap()) ||
1109        dict.f.is_some() &&
1110            dict.m42.is_some() &&
1111            !same_value_zero(dict.f.unwrap(), dict.m42.unwrap())
1112    {
1113        return Err(error::Error::Type(
1114            c"Property mismatch on matrix initialization.".to_owned(),
1115        ));
1116    }
1117
1118    // Step 2. If m11 is not present then set it to the value of member a,
1119    // or value 1 if a is also not present.
1120    let m11 = dict.m11.unwrap_or(dict.a.unwrap_or(1.0));
1121
1122    // Step 3. If m12 is not present then set it to the value of member b,
1123    // or value 0 if b is also not present.
1124    let m12 = dict.m12.unwrap_or(dict.b.unwrap_or(0.0));
1125
1126    // Step 4. If m21 is not present then set it to the value of member c,
1127    // or value 0 if c is also not present.
1128    let m21 = dict.m21.unwrap_or(dict.c.unwrap_or(0.0));
1129
1130    // Step 5. If m22 is not present then set it to the value of member d,
1131    // or value 1 if d is also not present.
1132    let m22 = dict.m22.unwrap_or(dict.d.unwrap_or(1.0));
1133
1134    // Step 6. If m41 is not present then set it to the value of member e,
1135    // or value 0 if e is also not present.
1136    let m41 = dict.m41.unwrap_or(dict.e.unwrap_or(0.0));
1137
1138    // Step 7. If m42 is not present then set it to the value of member f,
1139    // or value 0 if f is also not present.
1140    let m42 = dict.m42.unwrap_or(dict.f.unwrap_or(0.0));
1141
1142    Ok(Transform2D::new(m11, m12, m21, m22, m41, m42))
1143}
1144
1145/// <https://drafts.fxtf.org/geometry-1/#matrix-validate-and-fixup>
1146fn validate_and_fixup(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1147    // Step 1. Validate and fixup (2D) dict.
1148    let transform2d = validate_and_fixup_2d(&dict.parent)?;
1149
1150    // Step 2. If is2D is true and: at least one of m13, m14, m23, m24, m31,
1151    // m32, m34, m43 are present with a value other than 0 or -0, or at least
1152    // one of m33, m44 are present with a value other than 1, then throw
1153    // a TypeError exception and abort these steps.
1154    if dict.is2D == Some(true) &&
1155        (dict.m13 != 0.0 ||
1156            dict.m14 != 0.0 ||
1157            dict.m23 != 0.0 ||
1158            dict.m24 != 0.0 ||
1159            dict.m31 != 0.0 ||
1160            dict.m32 != 0.0 ||
1161            dict.m34 != 0.0 ||
1162            dict.m43 != 0.0 ||
1163            dict.m33 != 1.0 ||
1164            dict.m44 != 1.0)
1165    {
1166        return Err(error::Error::Type(
1167            c"The is2D member is set to true but the input matrix is a 3d matrix.".to_owned(),
1168        ));
1169    }
1170
1171    let mut is_2d = dict.is2D;
1172
1173    // Step 3. If is2D is not present and at least one of m13, m14, m23, m24,
1174    // m31, m32, m34, m43 are present with a value other than 0 or -0, or at
1175    // least one of m33, m44 are present with a value other than 1, set is2D
1176    // to false.
1177    if is_2d.is_none() &&
1178        (dict.m13 != 0.0 ||
1179            dict.m14 != 0.0 ||
1180            dict.m23 != 0.0 ||
1181            dict.m24 != 0.0 ||
1182            dict.m31 != 0.0 ||
1183            dict.m32 != 0.0 ||
1184            dict.m34 != 0.0 ||
1185            dict.m43 != 0.0 ||
1186            dict.m33 != 1.0 ||
1187            dict.m44 != 1.0)
1188    {
1189        is_2d = Some(false);
1190    }
1191
1192    // Step 4. If is2D is still not present, set it to true.
1193    let is_2d = is_2d.unwrap_or(true);
1194
1195    let mut transform = transform2d.to_3d();
1196    transform.m13 = dict.m13;
1197    transform.m14 = dict.m14;
1198    transform.m23 = dict.m23;
1199    transform.m24 = dict.m24;
1200    transform.m31 = dict.m31;
1201    transform.m32 = dict.m32;
1202    transform.m33 = dict.m33;
1203    transform.m34 = dict.m34;
1204    transform.m43 = dict.m43;
1205    transform.m44 = dict.m44;
1206
1207    Ok((is_2d, transform))
1208}
1209
1210/// <https://drafts.fxtf.org/geometry-1/#create-a-dommatrixreadonly-from-the-2d-dictionary>
1211pub(crate) fn dommatrix2dinit_to_matrix(dict: &DOMMatrix2DInit) -> Fallible<Transform2D<f64>> {
1212    // Step 1. Validate and fixup (2D) other.
1213    // Step 2. Return the result of invoking create a 2d matrix of type
1214    // DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of
1215    // numbers, the values being the 6 elements m11, m12, m21, m22, m41 and m42
1216    // of other in the given order.
1217    validate_and_fixup_2d(dict)
1218}
1219
1220/// <https://drafts.fxtf.org/geometry-1/#create-a-dommatrix-from-the-dictionary>
1221pub(crate) fn dommatrixinit_to_matrix(dict: &DOMMatrixInit) -> Fallible<(bool, Transform3D<f64>)> {
1222    // Step 1. Validate and fixup other.
1223    // Step 2. Return the result of invoking create a 3d matrix of type
1224    // DOMMatrixReadOnly or DOMMatrix as appropriate, with a sequence of
1225    // numbers, the values being the 16 elements m11, m12, m13, ..., m44
1226    // of other in the given order.
1227    validate_and_fixup(dict)
1228}
1229
1230#[inline]
1231fn normalize_point(x: f64, y: f64, z: f64) -> (f64, f64, f64) {
1232    let len = (x * x + y * y + z * z).sqrt();
1233    if len == 0.0 {
1234        (0.0, 0.0, 0.0)
1235    } else {
1236        (x / len, y / len, z / len)
1237    }
1238}
1239
1240pub(crate) fn transform_to_matrix(value: &str) -> Fallible<(bool, Transform3D<f64>)> {
1241    use style::properties::longhands::transform;
1242
1243    let mut input = ParserInput::new(value);
1244    let mut parser = Parser::new(&mut input);
1245    let url_data = Url::parse("about:blank").unwrap().into();
1246    let context =
1247        parser_context_for_anonymous_content(CssRuleType::Style, ParsingMode::DEFAULT, &url_data);
1248
1249    let transform = match parser.parse_entirely(|t| transform::parse(&context, t)) {
1250        Ok(result) => result,
1251        Err(..) => return Err(error::Error::Syntax(None)),
1252    };
1253
1254    let (m, is_3d) = match transform.to_transform_3d_matrix_f64(None) {
1255        Ok(result) => result,
1256        Err(..) => return Err(error::Error::Syntax(None)),
1257    };
1258
1259    Ok((!is_3d, m))
1260}