From e7bd71ebeb4c2b45a4e1e11f389c4b66dade5b19 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Sun, 30 Aug 2026 09:20:59 +0800 Subject: [PATCH] fix(geometry): track DOMMatrix dimension state --- .../wpt-cross-current/failed-cases.txt | 3 - .../wpt-cross-current/passed-cases.txt | 3 + moli-geometry/src/css_parse.rs | 39 +- moli-geometry/src/lib.rs | 29 +- moli-geometry/src/matrix.rs | 16 +- .../src/context_bootstrap/geometry_runtime.rs | 389 ++++++++++++------ .../misc/extracted/webidl_surface.rs | 77 ++++ 7 files changed, 410 insertions(+), 146 deletions(-) diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index a55d56c70f..6cb6d9cddf 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2471,12 +2471,9 @@ css/cssom/getComputedStyle-insets-sticky.html css/cssom/getComputedStyle-layout-dependent-replaced-into-ib-split.html css/cssom/idlharness.html css/cssom/style-sheet-interfaces-001.html -css/geometry/DOMMatrix-001.html css/geometry/DOMMatrix-003.html -css/geometry/DOMMatrix-attributes.html css/geometry/DOMMatrix-stringifier.html css/geometry/DOMMatrix2DInit-validate-fixup.html -css/geometry/DOMMatrixInit-validate-fixup.html css/geometry/DOMQuad-001.html css/geometry/DOMQuad-002.html css/geometry/DOMQuad-nan.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 0d1bb6fc0d..330a509a2a 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -3339,13 +3339,16 @@ css/cssom/ttwf-cssom-document-extension.html css/cssom/variable-names.html css/fill-stroke/inheritance.html css/fill-stroke/webkit-text-stroke-computed.html +css/geometry/DOMMatrix-001.html css/geometry/DOMMatrix-002.html css/geometry/DOMMatrix-a-f-alias.html +css/geometry/DOMMatrix-attributes.html css/geometry/DOMMatrix-invert-invertible.html css/geometry/DOMMatrix-invert-non-invertible.html css/geometry/DOMMatrix-invert-preserves-2d.html css/geometry/DOMMatrix-invertSelf.html css/geometry/DOMMatrix-newobject.html +css/geometry/DOMMatrixInit-validate-fixup.html css/geometry/DOMPoint-001.html css/geometry/DOMPoint-002.html css/geometry/DOMRect-001.html diff --git a/moli-geometry/src/css_parse.rs b/moli-geometry/src/css_parse.rs index 77a209f411..6be915789c 100644 --- a/moli-geometry/src/css_parse.rs +++ b/moli-geometry/src/css_parse.rs @@ -36,23 +36,48 @@ pub fn dom_matrix_components_from_values(values: &[f64]) -> Option Option { + parse_dom_matrix_value_with_dimension(text).map(|(components, _)| components) +} + +pub fn parse_dom_matrix_value_with_dimension(text: &str) -> Option<(DomMatrixComponents, bool)> { + if text.is_empty() { + return Some((DomMatrixComponents::identity(), true)); + } let trimmed = text.trim(); - if trimmed.is_empty() - || trimmed.eq_ignore_ascii_case("none") - || css_comments_wrap_none_keyword(trimmed) - { - return Some(DomMatrixComponents::identity()); + if trimmed.eq_ignore_ascii_case("none") || css_comments_wrap_none_keyword(trimmed) { + return Some((DomMatrixComponents::identity(), true)); + } + if trimmed.is_empty() { + return None; } let functions = moli_css_parse::parse_transform_function_list(trimmed)?; if functions.is_empty() { - return Some(DomMatrixComponents::identity()); + return None; } + let is_2d = functions.iter().all(is_2d_transform_function); let mut product = DomMatrixComponents::identity(); for function in functions { product = product.multiply(components_from_transform_function(&function)?); } - Some(product) + Some((product, is_2d)) +} + +fn is_2d_transform_function(function: &CssTransformFunction) -> bool { + matches!( + function.name.as_str(), + "matrix" + | "translate" + | "translatex" + | "translatey" + | "scale" + | "scalex" + | "scaley" + | "rotate" + | "rotatez" + | "skewx" + | "skewy" + ) } fn components_from_transform_function( diff --git a/moli-geometry/src/lib.rs b/moli-geometry/src/lib.rs index a862390b87..192e699651 100644 --- a/moli-geometry/src/lib.rs +++ b/moli-geometry/src/lib.rs @@ -3,12 +3,17 @@ mod css_parse; mod matrix; -pub use css_parse::{dom_matrix_components_from_values, parse_dom_matrix_value}; +pub use css_parse::{ + dom_matrix_components_from_values, parse_dom_matrix_value, + parse_dom_matrix_value_with_dimension, +}; pub use matrix::{DOM_MATRIX_COMPONENT_COUNT, DomMatrixComponents}; #[cfg(test)] mod tests { - use super::{DomMatrixComponents, parse_dom_matrix_value}; + use super::{ + DomMatrixComponents, parse_dom_matrix_value, parse_dom_matrix_value_with_dimension, + }; fn assert_close(actual: f64, expected: f64) { assert!( @@ -162,4 +167,24 @@ mod tests { ); assert!(DomMatrixComponents::nan().css_text().is_none()); } + + #[test] + fn parsing_tracks_transform_syntax_dimension_independently_of_components() { + let (matrix_2d, is_2d) = parse_dom_matrix_value_with_dimension("matrix(1,0,0,1,0,0)") + .expect("2D identity matrix should parse"); + assert!(matrix_2d.is_identity()); + assert!(is_2d); + + let (matrix_3d, is_2d) = + parse_dom_matrix_value_with_dimension("matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)") + .expect("3D identity matrix should parse"); + assert!(matrix_3d.is_identity()); + assert!(!is_2d); + } + + #[test] + fn parsing_accepts_an_empty_string_but_rejects_only_whitespace() { + assert!(parse_dom_matrix_value("").is_some()); + assert!(parse_dom_matrix_value(" ").is_none()); + } } diff --git a/moli-geometry/src/matrix.rs b/moli-geometry/src/matrix.rs index 7412c2c865..6c109e9e53 100644 --- a/moli-geometry/src/matrix.rs +++ b/moli-geometry/src/matrix.rs @@ -498,15 +498,23 @@ impl DomMatrixComponents { } pub fn css_text(self) -> Option { - self.text(css_number) + self.css_text_with_dimension(self.is_2d()) + } + + pub fn css_text_with_dimension(self, is_2d: bool) -> Option { + self.text(is_2d, css_number) } pub fn dom_matrix_text(self) -> Option { - self.text(ecmascript_number) + self.dom_matrix_text_with_dimension(self.is_2d()) } - fn text(self, serialize_number: fn(f64) -> String) -> Option { - if self.is_2d() { + pub fn dom_matrix_text_with_dimension(self, is_2d: bool) -> Option { + self.text(is_2d, ecmascript_number) + } + + fn text(self, is_2d: bool, serialize_number: fn(f64) -> String) -> Option { + if is_2d { let values = [self.m11, self.m12, self.m21, self.m22, self.m41, self.m42]; if !values.iter().all(|value| value.is_finite()) { return None; diff --git a/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs b/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs index bb3d3df17f..0987404148 100644 --- a/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs @@ -5,7 +5,7 @@ use crate::web_api_interfaces; use crate::webidl; use moli_geometry::{ DOM_MATRIX_COMPONENT_COUNT, DomMatrixComponents, dom_matrix_components_from_values, - parse_dom_matrix_value, + parse_dom_matrix_value_with_dimension, }; use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject}; @@ -30,6 +30,7 @@ const DOM_MATRIX_M41_SLOT: &str = "__moliDomMatrixM41"; const DOM_MATRIX_M42_SLOT: &str = "__moliDomMatrixM42"; const DOM_MATRIX_M43_SLOT: &str = "__moliDomMatrixM43"; const DOM_MATRIX_M44_SLOT: &str = "__moliDomMatrixM44"; +const DOM_MATRIX_IS_2D_SLOT: &str = "__moliDomMatrixIs2D"; const DOM_MATRIX_TYPED_ARRAY_LENGTH: usize = DOM_MATRIX_COMPONENT_COUNT; #[derive(WebApiObject)] @@ -101,10 +102,13 @@ macro_rules! dom_matrix_object_declaration { m43: f64, #[webapi(slot = DOM_MATRIX_M44_SLOT)] m44: f64, + #[webapi(slot = DOM_MATRIX_IS_2D_SLOT)] + is_2d: bool, } impl $name { - fn from_components(components: DomMatrixComponents) -> Self { + fn from_value(value: DomMatrixValue) -> Self { + let components = value.components; Self { m11: components.m11, m12: components.m12, @@ -122,11 +126,12 @@ macro_rules! dom_matrix_object_declaration { m42: components.m42, m43: components.m43, m44: components.m44, + is_2d: value.is_2d, } } fn identity() -> Self { - Self::from_components(DomMatrixComponents::identity()) + Self::from_value(DomMatrixValue::identity()) } } }; @@ -564,6 +569,21 @@ struct DomMatrixInit { m44: Option, } +#[derive(Clone, Copy)] +struct DomMatrixValue { + components: DomMatrixComponents, + is_2d: bool, +} + +impl DomMatrixValue { + fn identity() -> Self { + Self { + components: DomMatrixComponents::identity(), + is_2d: true, + } + } +} + pub(super) fn dom_point_constructor_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, @@ -845,6 +865,9 @@ fn dom_matrix_setter_callback<'s>( return; }; set_dom_matrix_slot(scope, args.this(), slot, value); + if !dom_matrix_attribute_value_preserves_2d(slot, value) { + set_dom_matrix_is_2d(scope, args.this(), false); + } rv.set_undefined(); } @@ -880,9 +903,11 @@ fn dom_point_matrix_transform_callback<'s>( throw_type_error(scope, "Illegal invocation"); return; } - let Some(matrix) = dom_matrix_init_arg(scope, &args, 0) else { + let Some(matrix) = dom_matrix_init_arg(scope, &args, 0, "DOMPointReadOnly.matrixTransform") + else { return; }; + let matrix = matrix.components; let point = DomPointInit { x: dom_point_slot(scope, args.this(), DOM_POINT_X_SLOT, 0.0), y: dom_point_slot(scope, args.this(), DOM_POINT_Y_SLOT, 0.0), @@ -917,14 +942,24 @@ fn dom_matrix_init_arg<'s>( scope: &mut v8::PinScope<'s, '_>, args: &v8::FunctionCallbackArguments<'s>, index: i32, -) -> Option { + prefix: &'static str, +) -> Option { if index >= args.length() || args.get(index).is_undefined() { - return Some(DomMatrixComponents::identity()); + return Some(DomMatrixValue::identity()); } + dom_matrix_init_value(scope, args.get(index), prefix, (index + 1) as usize) +} + +fn dom_matrix_init_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + value: v8::Local<'s, v8::Value>, + prefix: &'static str, + argument_index: usize, +) -> Option { let init = match webidl::parse_dictionary::( scope, - args.get(index), - webidl::Context::argument("DOMPointReadOnly.matrixTransform", (index + 1) as usize), + value, + webidl::Context::argument(prefix, argument_index), ) { Ok(Some(init)) => init, Ok(None) => DomMatrixInit::default(), @@ -939,7 +974,7 @@ fn dom_matrix_init_arg<'s>( fn validated_dom_matrix_init( scope: &mut v8::PinScope<'_, '_>, init: DomMatrixInit, -) -> Option { +) -> Option { let m11 = validated_dom_matrix_alias(scope, "a", init.a, "m11", init.m11, 1.0)?; let m12 = validated_dom_matrix_alias(scope, "b", init.b, "m12", init.m12, 0.0)?; let m21 = validated_dom_matrix_alias(scope, "c", init.c, "m21", init.m21, 0.0)?; @@ -964,14 +999,18 @@ fn validated_dom_matrix_init( m43: init.m43.unwrap_or(0.0), m44: init.m44.unwrap_or(1.0), }; - if init.is_2d == Some(true) && !components.is_2d() { + let components_are_2d = components.is_2d(); + if init.is_2d == Some(true) && !components_are_2d { throw_type_error( scope, "DOMMatrixInit is2D is true, but the matrix contains 3D values.", ); return None; } - Some(components) + Some(DomMatrixValue { + components, + is_2d: init.is_2d.unwrap_or(components_are_2d), + }) } fn validated_dom_matrix_alias( @@ -1003,13 +1042,11 @@ fn dom_matrix_from_matrix_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let matrix = build_dom_matrix_identity_object(scope); - if args.length() > 0 - && !args.get(0).is_undefined() - && !apply_dom_matrix_init(scope, matrix, args.get(0)) - { + let Some(value) = dom_matrix_init_arg(scope, &args, 0, "DOMMatrix.fromMatrix") else { return; - } + }; + let matrix = build_dom_matrix_identity_object(scope); + set_dom_matrix_value(scope, matrix, value); rv.set(matrix.into()); } @@ -1018,13 +1055,11 @@ fn dom_matrix_readonly_from_matrix_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let matrix = build_dom_matrix_readonly_identity_object(scope); - if args.length() > 0 - && !args.get(0).is_undefined() - && !apply_dom_matrix_init(scope, matrix, args.get(0)) - { + let Some(value) = dom_matrix_init_arg(scope, &args, 0, "DOMMatrixReadOnly.fromMatrix") else { return; - } + }; + let matrix = build_dom_matrix_readonly_identity_object(scope); + set_dom_matrix_value(scope, matrix, value); rv.set(matrix.into()); } @@ -1033,15 +1068,13 @@ fn dom_matrix_from_float32_array_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(components) = dom_matrix_components_from_typed_array( - scope, - args.get(0), - DomMatrixTypedArrayKind::Float32, - ) else { + let Some(value) = + dom_matrix_value_from_typed_array(scope, args.get(0), DomMatrixTypedArrayKind::Float32) + else { return; }; let matrix = build_dom_matrix_identity_object(scope); - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); rv.set(matrix.into()); } @@ -1050,15 +1083,13 @@ fn dom_matrix_from_float64_array_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(components) = dom_matrix_components_from_typed_array( - scope, - args.get(0), - DomMatrixTypedArrayKind::Float64, - ) else { + let Some(value) = + dom_matrix_value_from_typed_array(scope, args.get(0), DomMatrixTypedArrayKind::Float64) + else { return; }; let matrix = build_dom_matrix_identity_object(scope); - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); rv.set(matrix.into()); } @@ -1067,15 +1098,13 @@ fn dom_matrix_readonly_from_float32_array_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(components) = dom_matrix_components_from_typed_array( - scope, - args.get(0), - DomMatrixTypedArrayKind::Float32, - ) else { + let Some(value) = + dom_matrix_value_from_typed_array(scope, args.get(0), DomMatrixTypedArrayKind::Float32) + else { return; }; let matrix = build_dom_matrix_readonly_identity_object(scope); - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); rv.set(matrix.into()); } @@ -1084,15 +1113,13 @@ fn dom_matrix_readonly_from_float64_array_callback<'s>( args: v8::FunctionCallbackArguments<'s>, mut rv: v8::ReturnValue<'_, v8::Value>, ) { - let Some(components) = dom_matrix_components_from_typed_array( - scope, - args.get(0), - DomMatrixTypedArrayKind::Float64, - ) else { + let Some(value) = + dom_matrix_value_from_typed_array(scope, args.get(0), DomMatrixTypedArrayKind::Float64) + else { return; }; let matrix = build_dom_matrix_readonly_identity_object(scope); - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); rv.set(matrix.into()); } @@ -1569,7 +1596,7 @@ fn dom_matrix_multiply_callback<'s>( return; } let matrix = copied_dom_matrix(scope, args.this()); - let Some(other) = dom_matrix_argument_components(scope, &args, 0) else { + let Some(other) = dom_matrix_argument_value(scope, &args, 0) else { return; }; apply_dom_matrix_multiply(scope, matrix, other); @@ -1584,7 +1611,7 @@ fn dom_matrix_multiply_self_callback<'s>( if !dom_matrix_require_mutable_receiver(scope, args.this()) { return; } - let Some(other) = dom_matrix_argument_components(scope, &args, 0) else { + let Some(other) = dom_matrix_argument_value(scope, &args, 0) else { return; }; apply_dom_matrix_multiply(scope, args.this(), other); @@ -1599,11 +1626,18 @@ fn dom_matrix_pre_multiply_self_callback<'s>( if !dom_matrix_require_mutable_receiver(scope, args.this()) { return; } - let Some(other) = dom_matrix_argument_components(scope, &args, 0) else { + let Some(other) = dom_matrix_argument_value(scope, &args, 0) else { return; }; - let current = dom_matrix_components(scope, args.this()); - set_dom_matrix_components(scope, args.this(), other.multiply(current)); + let current = dom_matrix_value(scope, args.this()); + set_dom_matrix_value( + scope, + args.this(), + DomMatrixValue { + components: other.components.multiply(current.components), + is_2d: current.is_2d && other.is_2d, + }, + ); rv.set(args.this().into()); } @@ -1724,24 +1758,17 @@ fn copied_dom_matrix<'s>( source: v8::Local<'s, v8::Object>, ) -> v8::Local<'s, v8::Object> { let matrix = build_dom_matrix_identity_object(scope); - let components = dom_matrix_components(scope, source); - set_dom_matrix_components(scope, matrix, components); + let value = dom_matrix_value(scope, source); + set_dom_matrix_value(scope, matrix, value); matrix } -fn dom_matrix_argument_components<'s>( +fn dom_matrix_argument_value<'s>( scope: &mut v8::PinScope<'s, '_>, args: &v8::FunctionCallbackArguments<'s>, index: i32, -) -> Option { - if index >= args.length() || args.get(index).is_undefined() { - return Some(DomMatrixComponents::identity()); - } - let matrix = build_dom_matrix_identity_object(scope); - if !apply_dom_matrix_init(scope, matrix, args.get(index)) { - return None; - } - Some(dom_matrix_components(scope, matrix)) +) -> Option { + dom_matrix_init_arg(scope, args, index, "DOMMatrix") } fn dom_matrix_translate_args<'s>( @@ -1803,8 +1830,15 @@ fn apply_dom_matrix_translate<'s>( matrix: v8::Local<'s, v8::Object>, (tx, ty, tz): (f64, f64, f64), ) { - let components = dom_matrix_components(scope, matrix).translated(tx, ty, tz); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.translated(tx, ty, tz), + is_2d: current.is_2d && tz == 0.0, + }, + ); } fn apply_dom_matrix_scale<'s>( @@ -1812,9 +1846,17 @@ fn apply_dom_matrix_scale<'s>( matrix: v8::Local<'s, v8::Object>, (scale_x, scale_y, scale_z, origin_x, origin_y, origin_z): (f64, f64, f64, f64, f64, f64), ) { - let components = dom_matrix_components(scope, matrix) - .scaled_with_origin(scale_x, scale_y, scale_z, origin_x, origin_y, origin_z); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current + .components + .scaled_with_origin(scale_x, scale_y, scale_z, origin_x, origin_y, origin_z), + is_2d: current.is_2d && scale_z == 1.0 && origin_z == 0.0, + }, + ); } fn apply_dom_matrix_scale_2d<'s>( @@ -1823,8 +1865,15 @@ fn apply_dom_matrix_scale_2d<'s>( scale_x: f64, scale_y: f64, ) { - let components = dom_matrix_components(scope, matrix).scaled_2d(scale_x, scale_y); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.scaled_2d(scale_x, scale_y), + is_2d: current.is_2d, + }, + ); } fn apply_dom_matrix_rotate<'s>( @@ -1832,8 +1881,15 @@ fn apply_dom_matrix_rotate<'s>( matrix: v8::Local<'s, v8::Object>, (rot_x, rot_y, rot_z): (f64, f64, f64), ) { - let components = dom_matrix_components(scope, matrix).rotated(rot_x, rot_y, rot_z); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.rotated(rot_x, rot_y, rot_z), + is_2d: current.is_2d && rot_x == 0.0 && rot_y == 0.0, + }, + ); } fn apply_dom_matrix_rotate_z<'s>( @@ -1841,8 +1897,15 @@ fn apply_dom_matrix_rotate_z<'s>( matrix: v8::Local<'s, v8::Object>, degrees: f64, ) { - let components = dom_matrix_components(scope, matrix).rotated_z(degrees); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.rotated_z(degrees), + is_2d: current.is_2d, + }, + ); } fn apply_dom_matrix_rotate_axis_angle<'s>( @@ -1853,8 +1916,15 @@ fn apply_dom_matrix_rotate_axis_angle<'s>( z: f64, degrees: f64, ) { - let components = dom_matrix_components(scope, matrix).rotated_axis_angle(x, y, z, degrees); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.rotated_axis_angle(x, y, z, degrees), + is_2d: current.is_2d && x == 0.0 && y == 0.0, + }, + ); } fn apply_dom_matrix_skew_x<'s>( @@ -1862,8 +1932,15 @@ fn apply_dom_matrix_skew_x<'s>( matrix: v8::Local<'s, v8::Object>, degrees: f64, ) { - let components = dom_matrix_components(scope, matrix).skewed_x(degrees); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.skewed_x(degrees), + is_2d: current.is_2d, + }, + ); } fn apply_dom_matrix_skew_y<'s>( @@ -1871,17 +1948,31 @@ fn apply_dom_matrix_skew_y<'s>( matrix: v8::Local<'s, v8::Object>, degrees: f64, ) { - let components = dom_matrix_components(scope, matrix).skewed_y(degrees); - set_dom_matrix_components(scope, matrix, components); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.skewed_y(degrees), + is_2d: current.is_2d, + }, + ); } fn apply_dom_matrix_multiply<'s>( scope: &mut v8::PinScope<'s, '_>, matrix: v8::Local<'s, v8::Object>, - other: DomMatrixComponents, + other: DomMatrixValue, ) { - let current = dom_matrix_components(scope, matrix); - set_dom_matrix_components(scope, matrix, current.multiply(other)); + let current = dom_matrix_value(scope, matrix); + set_dom_matrix_value( + scope, + matrix, + DomMatrixValue { + components: current.components.multiply(other.components), + is_2d: current.is_2d && other.is_2d, + }, + ); } #[derive(Clone, Copy)] @@ -1890,11 +1981,11 @@ enum DomMatrixTypedArrayKind { Float64, } -fn dom_matrix_components_from_typed_array<'s>( +fn dom_matrix_value_from_typed_array<'s>( scope: &mut v8::PinScope<'s, '_>, value: v8::Local<'s, v8::Value>, kind: DomMatrixTypedArrayKind, -) -> Option { +) -> Option { let expected = match kind { DomMatrixTypedArrayKind::Float32 => v8::Local::::try_from(value).is_ok(), DomMatrixTypedArrayKind::Float64 => v8::Local::::try_from(value).is_ok(), @@ -1934,13 +2025,17 @@ fn dom_matrix_components_from_typed_array<'s>( }; values.push(value); } - dom_matrix_components_from_values(&values) + let components = dom_matrix_components_from_values(&values)?; + Some(DomMatrixValue { + components, + is_2d: length == 6, + }) } -fn dom_matrix_components_from_sequence_array<'s>( +fn dom_matrix_value_from_sequence_array<'s>( scope: &mut v8::PinScope<'s, '_>, array: v8::Local<'s, v8::Array>, -) -> Option { +) -> Option { let length = array.length(); if length != 6 && length != DOM_MATRIX_TYPED_ARRAY_LENGTH as u32 { throw_type_error(scope, "DOMMatrix sequence length must be 6 or 16."); @@ -1957,7 +2052,11 @@ fn dom_matrix_components_from_sequence_array<'s>( values.push(value); } - dom_matrix_components_from_values(&values) + let components = dom_matrix_components_from_values(&values)?; + Some(DomMatrixValue { + components, + is_2d: length == 6, + }) } fn dom_matrix_components<'s>( @@ -1984,6 +2083,16 @@ fn dom_matrix_components<'s>( } } +fn dom_matrix_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, +) -> DomMatrixValue { + DomMatrixValue { + components: dom_matrix_components(scope, object), + is_2d: dom_matrix_is_2d(scope, object), + } +} + fn dom_matrix_slot<'s>( scope: &mut v8::PinScope<'s, '_>, object: v8::Local<'s, v8::Object>, @@ -2000,6 +2109,16 @@ fn set_dom_matrix_components<'s>( object: v8::Local<'s, v8::Object>, components: DomMatrixComponents, ) { + let is_2d = dom_matrix_is_2d(scope, object) && components.is_2d(); + set_dom_matrix_value(scope, object, DomMatrixValue { components, is_2d }); +} + +fn set_dom_matrix_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + value: DomMatrixValue, +) { + let components = value.components; for (slot, value) in [ (DOM_MATRIX_M11_SLOT, components.m11), (DOM_MATRIX_M12_SLOT, components.m12), @@ -2020,6 +2139,7 @@ fn set_dom_matrix_components<'s>( ] { set_dom_matrix_slot(scope, object, slot, value); } + set_dom_matrix_is_2d(scope, object, value.is_2d); } fn set_dom_matrix_slot<'s>( @@ -2031,6 +2151,30 @@ fn set_dom_matrix_slot<'s>( set_private_value(scope, object, slot, v8::Number::new(scope, value).into()); } +fn set_dom_matrix_is_2d<'s>( + scope: &mut v8::PinScope<'s, '_>, + object: v8::Local<'s, v8::Object>, + is_2d: bool, +) { + set_private_value( + scope, + object, + DOM_MATRIX_IS_2D_SLOT, + v8::Boolean::new(scope, is_2d).into(), + ); +} + +fn dom_matrix_attribute_value_preserves_2d(slot: &'static str, value: f64) -> bool { + match slot { + DOM_MATRIX_M13_SLOT | DOM_MATRIX_M14_SLOT | DOM_MATRIX_M23_SLOT | DOM_MATRIX_M24_SLOT + | DOM_MATRIX_M31_SLOT | DOM_MATRIX_M32_SLOT | DOM_MATRIX_M34_SLOT | DOM_MATRIX_M43_SLOT => { + value == 0.0 + } + DOM_MATRIX_M33_SLOT | DOM_MATRIX_M44_SLOT => value == 1.0, + _ => true, + } +} + fn dom_matrix_array_values( components: DomMatrixComponents, ) -> [f64; DOM_MATRIX_TYPED_ARRAY_LENGTH] { @@ -2100,40 +2244,35 @@ fn apply_dom_matrix_init<'s>( ); } if v8::Local::::try_from(init).is_ok() { - if let Some(components) = - dom_matrix_components_from_typed_array(scope, init, DomMatrixTypedArrayKind::Float32) + if let Some(value) = + dom_matrix_value_from_typed_array(scope, init, DomMatrixTypedArrayKind::Float32) { - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); + return true; } - return true; + return false; } if v8::Local::::try_from(init).is_ok() { - if let Some(components) = - dom_matrix_components_from_typed_array(scope, init, DomMatrixTypedArrayKind::Float64) + if let Some(value) = + dom_matrix_value_from_typed_array(scope, init, DomMatrixTypedArrayKind::Float64) { - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); + return true; } - return true; + return false; } if let Ok(array) = v8::Local::::try_from(init) { - let Some(components) = dom_matrix_components_from_sequence_array(scope, array) else { + let Some(value) = dom_matrix_value_from_sequence_array(scope, array) else { return false; }; - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, value); return true; } - if let Ok(object) = v8::Local::::try_from(init) { - for attribute in DOM_MATRIX_MUTABLE_ATTRIBUTES { - if let DomMatrixAttributeKind::Number(slot) = attribute.kind { - match property_number(scope, object, attribute.name, "DOMMatrixInit") { - Some(Some(value)) => { - set_dom_matrix_slot(scope, matrix, slot, value); - } - Some(None) => {} - None => return false, - } - } - } + if init.is_object() { + let Some(value) = dom_matrix_init_value(scope, init, "DOMMatrix", 1) else { + return false; + }; + set_dom_matrix_value(scope, matrix, value); return true; } @@ -2158,7 +2297,7 @@ fn apply_dom_matrix_transform_list_string<'s>( matrix: v8::Local<'s, v8::Object>, text: &str, ) -> bool { - let Some(components) = parse_dom_matrix_value(text) else { + let Some((components, is_2d)) = parse_dom_matrix_value_with_dimension(text) else { throw_dom_exception( scope, "SyntaxError", @@ -2167,7 +2306,7 @@ fn apply_dom_matrix_transform_list_string<'s>( ); return false; }; - set_dom_matrix_components(scope, matrix, components); + set_dom_matrix_value(scope, matrix, DomMatrixValue { components, is_2d }); true } @@ -2184,7 +2323,9 @@ fn dom_matrix_is_2d<'s>( scope: &mut v8::PinScope<'s, '_>, object: v8::Local<'s, v8::Object>, ) -> bool { - dom_matrix_components(scope, object).is_2d() + get_private_value(scope, object, DOM_MATRIX_IS_2D_SLOT) + .map(|value| value.boolean_value(scope)) + .unwrap_or_else(|| dom_matrix_components(scope, object).is_2d()) } fn dom_matrix_is_identity<'s>( @@ -2198,7 +2339,9 @@ fn dom_matrix_css_text<'s>( scope: &mut v8::PinScope<'s, '_>, object: v8::Local<'s, v8::Object>, ) -> Option { - let Some(css_text) = dom_matrix_components(scope, object).dom_matrix_text() else { + let Some(css_text) = dom_matrix_components(scope, object) + .dom_matrix_text_with_dimension(dom_matrix_is_2d(scope, object)) + else { throw_dom_exception( scope, "InvalidStateError", @@ -2258,20 +2401,6 @@ pub(super) fn dom_point_init_value<'s>( } } -fn property_number<'s>( - scope: &mut v8::PinScope<'s, '_>, - object: v8::Local<'s, v8::Object>, - name: &'static str, - prefix: &'static str, -) -> Option> { - let key = v8_string(scope, name)?; - let value = object.get(scope, key.into())?; - if value.is_undefined() { - return Some(None); - } - geometry_number_value(scope, value, webidl::Context::member(prefix, name)).map(Some) -} - fn geometry_number_value<'s>( scope: &mut v8::PinScope<'s, '_>, value: v8::Local<'s, v8::Value>, diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/webidl_surface.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/webidl_surface.rs index fd8a5ff3b8..da2a903b65 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/webidl_surface.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/misc/extracted/webidl_surface.rs @@ -1935,3 +1935,80 @@ fn trusted_type_policy_interface_shares_methods_and_rejects_missing_callbacks() r#"{"htmlOnlyKeys":"","fullKeys":"","ownMethods":[false,false,false],"prototypeMethods":["createHTML:true:true:true:function:createHTML:1","createScript:true:true:true:function:createScript:1","createScriptURL:true:true:true:function:createScriptURL:1"],"name":["html-only","full","function",true,true,true,false],"brands":[true,true,true,true],"missingCallbackErrors":["TypeError","TypeError"],"trusted":[true,true,true],"values":["ok","1 + 1","data:text/javascript,"]}"# ); } + +#[test] +fn dom_matrix_tracks_explicit_dimension_and_validates_init() { + let mut vm = new_storage_test_vm("https://dommatrix-dimension.test/"); + + let result = vm + .eval( + r#" +(() => { + const identity3d = [ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ]; + const outcome = callback => { + try { + callback(); + return "no throw"; + } catch (error) { + return error.name; + } + }; + + const sequence = new DOMMatrix(identity3d); + const typed = DOMMatrixReadOnly.fromFloat64Array(new Float64Array(identity3d)); + const explicit = DOMMatrix.fromMatrix({is2D: false}); + const copied = new DOMMatrix(explicit); + const multiplied = new DOMMatrix().multiply({is2D: false}); + const preMultiplied = new DOMMatrix().preMultiplySelf({is2D: false}); + + const sticky = new DOMMatrix(); + sticky.m13 = 2; + sticky.m13 = 0; + + const reset = new DOMMatrix(identity3d); + reset.setMatrixValue(""); + + const origin3d = new DOMMatrix().scaleSelf(1, 1, 1, 0, 0, 2); + const axis3d = new DOMMatrix().rotateAxisAngleSelf(1, 0, 0, 0); + const negativeZero2d = new DOMMatrix().translateSelf(0, 0, -0); + const parsed3d = new DOMMatrix( + "matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1)" + ); + + return [ + sequence.is2D, + typed.is2D, + explicit.is2D, + copied.is2D, + multiplied.is2D, + preMultiplied.is2D, + sticky.is2D, + reset.is2D, + origin3d.is2D, + axis3d.is2D, + negativeZero2d.is2D, + parsed3d.is2D, + String(explicit), + outcome(() => DOMMatrix.fromMatrix({a: 1, m11: 2})), + outcome(() => DOMMatrix.fromMatrix({is2D: true, m13: 1})), + outcome(() => new DOMMatrix(" ")) + ].join("|"); +})() +"#, + ) + .expect("DOMMatrix dimension probe should evaluate"); + + assert_eq!( + result, + concat!( + "false|false|false|false|false|false|false|true|false|false|true|false|", + "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)|", + "TypeError|TypeError|SyntaxError" + ) + ); +}