diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index 31363f1768..0264a0be1c 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2475,7 +2475,6 @@ css/geometry/DOMMatrix-attributes.html css/geometry/DOMMatrix-stringifier.html css/geometry/DOMMatrix2DInit-validate-fixup.html css/geometry/DOMMatrixInit-validate-fixup.html -css/geometry/DOMPoint-002.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 8284224ea0..0a9a59c041 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -3347,6 +3347,7 @@ css/geometry/DOMMatrix-invert-preserves-2d.html css/geometry/DOMMatrix-invertSelf.html css/geometry/DOMMatrix-newobject.html css/geometry/DOMPoint-001.html +css/geometry/DOMPoint-002.html css/geometry/DOMRect-001.html css/geometry/DOMRect-002.html css/geometry/DOMRect-nan.html diff --git a/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs b/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs index 3e1f986288..bb3d3df17f 100644 --- a/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/geometry_runtime.rs @@ -213,6 +213,13 @@ struct DomPointPrototypeAccessorsDeclaration { #[derive(WebApiFunctionTemplate)] #[webapi(interface = web_api_interfaces::DOMPointReadOnly)] struct DomPointReadOnlyPrototypeMethodsDeclaration { + #[webapi( + method = "matrixTransform", + length = 0, + callback = dom_point_matrix_transform_callback + )] + matrix_transform: (), + #[webapi(method = "toJSON", enumerable, callback = dom_point_to_json_callback)] to_json: (), } @@ -528,6 +535,35 @@ impl Default for DomPointInit { } } +#[derive(Default, webidl::WebIdlDictionary)] +#[webidl(prefix = "DOMMatrixInit")] +struct DomMatrixInit { + a: Option, + b: Option, + c: Option, + d: Option, + e: Option, + f: Option, + #[webidl(name = "is2D")] + is_2d: Option, + m11: Option, + m12: Option, + m13: Option, + m14: Option, + m21: Option, + m22: Option, + m23: Option, + m24: Option, + m31: Option, + m32: Option, + m33: Option, + m34: Option, + m41: Option, + m42: Option, + m43: Option, + m44: Option, +} + pub(super) fn dom_point_constructor_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, @@ -835,6 +871,133 @@ fn dom_point_readonly_from_point_callback<'s>( rv.set(build_dom_point_readonly_object(scope, init.x, init.y, init.z, init.w).into()); } +fn dom_point_matrix_transform_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + if !dom_point_receiver_branded(scope, args.this()) { + throw_type_error(scope, "Illegal invocation"); + return; + } + let Some(matrix) = dom_matrix_init_arg(scope, &args, 0) else { + return; + }; + 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), + z: dom_point_slot(scope, args.this(), DOM_POINT_Z_SLOT, 0.0), + w: dom_point_slot(scope, args.this(), DOM_POINT_W_SLOT, 1.0), + }; + rv.set( + build_dom_point_object( + scope, + matrix.m11 * point.x + + matrix.m21 * point.y + + matrix.m31 * point.z + + matrix.m41 * point.w, + matrix.m12 * point.x + + matrix.m22 * point.y + + matrix.m32 * point.z + + matrix.m42 * point.w, + matrix.m13 * point.x + + matrix.m23 * point.y + + matrix.m33 * point.z + + matrix.m43 * point.w, + matrix.m14 * point.x + + matrix.m24 * point.y + + matrix.m34 * point.z + + matrix.m44 * point.w, + ) + .into(), + ); +} + +fn dom_matrix_init_arg<'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 init = match webidl::parse_dictionary::( + scope, + args.get(index), + webidl::Context::argument("DOMPointReadOnly.matrixTransform", (index + 1) as usize), + ) { + Ok(Some(init)) => init, + Ok(None) => DomMatrixInit::default(), + Err(error) => { + webidl::throw_error(scope, &error); + return None; + } + }; + validated_dom_matrix_init(scope, init) +} + +fn validated_dom_matrix_init( + scope: &mut v8::PinScope<'_, '_>, + init: DomMatrixInit, +) -> 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)?; + let m22 = validated_dom_matrix_alias(scope, "d", init.d, "m22", init.m22, 1.0)?; + let m41 = validated_dom_matrix_alias(scope, "e", init.e, "m41", init.m41, 0.0)?; + let m42 = validated_dom_matrix_alias(scope, "f", init.f, "m42", init.m42, 0.0)?; + let components = DomMatrixComponents { + m11, + m12, + m13: init.m13.unwrap_or(0.0), + m14: init.m14.unwrap_or(0.0), + m21, + m22, + m23: init.m23.unwrap_or(0.0), + m24: init.m24.unwrap_or(0.0), + m31: init.m31.unwrap_or(0.0), + m32: init.m32.unwrap_or(0.0), + m33: init.m33.unwrap_or(1.0), + m34: init.m34.unwrap_or(0.0), + m41, + m42, + m43: init.m43.unwrap_or(0.0), + m44: init.m44.unwrap_or(1.0), + }; + if init.is_2d == Some(true) && !components.is_2d() { + throw_type_error( + scope, + "DOMMatrixInit is2D is true, but the matrix contains 3D values.", + ); + return None; + } + Some(components) +} + +fn validated_dom_matrix_alias( + scope: &mut v8::PinScope<'_, '_>, + alias_name: &'static str, + alias: Option, + matrix_name: &'static str, + matrix: Option, + default: f64, +) -> Option { + if let (Some(alias), Some(matrix)) = (alias, matrix) + && !same_dom_matrix_number(alias, matrix) + { + throw_type_error( + scope, + &format!("DOMMatrixInit {alias_name} and {matrix_name} values differ."), + ); + return None; + } + Some(matrix.or(alias).unwrap_or(default)) +} + +fn same_dom_matrix_number(lhs: f64, rhs: f64) -> bool { + lhs == rhs || (lhs.is_nan() && rhs.is_nan()) +} + fn dom_matrix_from_matrix_callback<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/misc.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/misc.rs index 752a74b461..0e51c6237b 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/misc.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/misc.rs @@ -5670,6 +5670,66 @@ fn dom_point_readonly_from_point_uses_dictionary_conversion_and_function_realm() assert_eq!(result, "0|true|false|false|1,0,3,4|0,0,0,1"); } +#[test] +fn dom_point_matrix_transform_validates_matrix_init_and_returns_in_the_function_realm() { + let mut vm = new_storage_test_vm("https://dompoint-matrix-transform.test/"); + + vm.eval( + r#" +(() => { + const root = document.documentElement || + document.appendChild(document.createElement("html")); + const body = document.body || root.appendChild(document.createElement("body")); + const frame = document.createElement("iframe"); + frame.id = "dompoint-matrix-transform-realm"; + body.appendChild(frame); +})() +"#, + ) + .expect("DOMPoint matrixTransform child frame should be created"); + materialize_single_child_default_realm_for_test( + &mut vm, + "DOMPoint matrixTransform child Realm", + ); + + let result = vm + .eval( + r#" +(() => { + const other = document.getElementById("dompoint-matrix-transform-realm").contentWindow; + const source = new DOMPointReadOnly(5, 4, 3, 2); + const transformed = other.DOMPointReadOnly.prototype.matrixTransform.call(source, { + a: 2, m11: 2, d: 3, e: 10, f: 20, m33: 4, m44: 5 + }); + const outcome = init => { + try { + source.matrixTransform(init); + return "no throw"; + } catch (error) { + return error.name; + } + }; + return [ + other.DOMPointReadOnly.prototype.matrixTransform.length, + transformed instanceof other.DOMPoint, + transformed instanceof DOMPoint, + [transformed.x, transformed.y, transformed.z, transformed.w].join(","), + outcome({a: 1, m11: 2}), + outcome({is2D: true, m33: 1.0000001}), + outcome({a: NaN, m11: NaN}), + outcome({a: 0, m11: -0}) + ].join("|"); +})() +"#, + ) + .expect("DOMPoint matrixTransform should evaluate"); + + assert_eq!( + result, + "0|true|false|30,52,12,10|TypeError|TypeError|no throw|no throw" + ); +} + #[test] fn dom_matrix_objects_keep_declared_brand_and_own_slots() { let mut vm = new_storage_test_vm("https://dommatrix-declared-slots.test/");