feat(geometry): implement DOMPoint matrixTransform

This commit is contained in:
ldm0
2026-09-23 00:11:51 +08:00
parent 571ec24336
commit b155f11c19
4 changed files with 224 additions and 1 deletions
@@ -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
@@ -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
@@ -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<f64>,
b: Option<f64>,
c: Option<f64>,
d: Option<f64>,
e: Option<f64>,
f: Option<f64>,
#[webidl(name = "is2D")]
is_2d: Option<bool>,
m11: Option<f64>,
m12: Option<f64>,
m13: Option<f64>,
m14: Option<f64>,
m21: Option<f64>,
m22: Option<f64>,
m23: Option<f64>,
m24: Option<f64>,
m31: Option<f64>,
m32: Option<f64>,
m33: Option<f64>,
m34: Option<f64>,
m41: Option<f64>,
m42: Option<f64>,
m43: Option<f64>,
m44: Option<f64>,
}
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<DomMatrixComponents> {
if index >= args.length() || args.get(index).is_undefined() {
return Some(DomMatrixComponents::identity());
}
let init = match webidl::parse_dictionary::<DomMatrixInit>(
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<DomMatrixComponents> {
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<f64>,
matrix_name: &'static str,
matrix: Option<f64>,
default: f64,
) -> Option<f64> {
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>,
@@ -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/");