fix(svg): align matrix factories with DOMMatrix

This commit is contained in:
ldm0
2026-09-08 02:00:31 +08:00
parent d8200cc65b
commit eb798a7dd1
6 changed files with 144 additions and 40 deletions
@@ -617,6 +617,23 @@ struct DomMatrixInit {
m44: Option<f64>,
}
#[derive(Default, webidl::WebIdlDictionary)]
#[webidl(prefix = "DOMMatrix2DInit")]
struct DomMatrix2DInit {
a: Option<f64>,
b: Option<f64>,
c: Option<f64>,
d: Option<f64>,
e: Option<f64>,
f: Option<f64>,
m11: Option<f64>,
m12: Option<f64>,
m21: Option<f64>,
m22: Option<f64>,
m41: Option<f64>,
m42: Option<f64>,
}
#[derive(Clone, Copy)]
struct DomMatrixValue {
components: DomMatrixComponents,
@@ -1070,6 +1087,37 @@ fn dom_matrix_init_arg<'s>(
dom_matrix_init_value(scope, args.get(index), prefix, (index + 1) as usize)
}
pub(in crate::context_bootstrap) fn dom_matrix_2d_init_arg<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: &v8::FunctionCallbackArguments<'s>,
index: i32,
prefix: &'static str,
) -> Option<[f64; 6]> {
if index >= args.length() || args.get(index).is_undefined() {
return Some([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
}
let init = match webidl::parse_dictionary::<DomMatrix2DInit>(
scope,
args.get(index),
webidl::Context::argument(prefix, (index + 1) as usize),
) {
Ok(Some(init)) => init,
Ok(None) => DomMatrix2DInit::default(),
Err(error) => {
webidl::throw_error(scope, &error);
return None;
}
};
Some([
validated_dom_matrix_alias(scope, "DOMMatrix2DInit", "a", init.a, "m11", init.m11, 1.0)?,
validated_dom_matrix_alias(scope, "DOMMatrix2DInit", "b", init.b, "m12", init.m12, 0.0)?,
validated_dom_matrix_alias(scope, "DOMMatrix2DInit", "c", init.c, "m21", init.m21, 0.0)?,
validated_dom_matrix_alias(scope, "DOMMatrix2DInit", "d", init.d, "m22", init.m22, 1.0)?,
validated_dom_matrix_alias(scope, "DOMMatrix2DInit", "e", init.e, "m41", init.m41, 0.0)?,
validated_dom_matrix_alias(scope, "DOMMatrix2DInit", "f", init.f, "m42", init.m42, 0.0)?,
])
}
fn dom_matrix_init_value<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
@@ -1095,12 +1143,18 @@ fn validated_dom_matrix_init(
scope: &mut v8::PinScope<'_, '_>,
init: DomMatrixInit,
) -> Option<DomMatrixValue> {
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 m11 =
validated_dom_matrix_alias(scope, "DOMMatrixInit", "a", init.a, "m11", init.m11, 1.0)?;
let m12 =
validated_dom_matrix_alias(scope, "DOMMatrixInit", "b", init.b, "m12", init.m12, 0.0)?;
let m21 =
validated_dom_matrix_alias(scope, "DOMMatrixInit", "c", init.c, "m21", init.m21, 0.0)?;
let m22 =
validated_dom_matrix_alias(scope, "DOMMatrixInit", "d", init.d, "m22", init.m22, 1.0)?;
let m41 =
validated_dom_matrix_alias(scope, "DOMMatrixInit", "e", init.e, "m41", init.m41, 0.0)?;
let m42 =
validated_dom_matrix_alias(scope, "DOMMatrixInit", "f", init.f, "m42", init.m42, 0.0)?;
let components = DomMatrixComponents {
m11,
m12,
@@ -1135,6 +1189,7 @@ fn validated_dom_matrix_init(
fn validated_dom_matrix_alias(
scope: &mut v8::PinScope<'_, '_>,
dictionary_name: &'static str,
alias_name: &'static str,
alias: Option<f64>,
matrix_name: &'static str,
@@ -1146,7 +1201,7 @@ fn validated_dom_matrix_alias(
{
throw_type_error(
scope,
&format!("DOMMatrixInit {alias_name} and {matrix_name} values differ."),
&format!("{dictionary_name} {alias_name} and {matrix_name} values differ."),
);
return None;
}
@@ -193,7 +193,7 @@ struct SvgTransformListTemplateMethodsDeclaration {
#[webapi(
method = "createSVGTransformFromMatrix",
length = 1,
length = 0,
callback = svg_transform_list_create_transform_from_matrix_callback
)]
create_svg_transform_from_matrix: (),
@@ -236,7 +236,7 @@ struct SvgTransformTemplateMethodsDeclaration {
#[webapi(constant = "SVG_TRANSFORM_SKEWY", value = SVG_TRANSFORM_TYPE_SKEWY)]
transform_skew_y: (),
#[webapi(method = "setMatrix", length = 1, callback = svg_transform_set_matrix_callback)]
#[webapi(method = "setMatrix", length = 0, callback = svg_transform_set_matrix_callback)]
set_matrix: (),
#[webapi(
@@ -451,7 +451,7 @@ struct SvgSvgElementTemplateMethodsDeclaration {
#[webapi(
method = "createSVGTransformFromMatrix",
length = 1,
length = 0,
callback = svg_svg_element_create_transform_from_matrix_callback
)]
create_svg_transform_from_matrix: (),
@@ -232,7 +232,7 @@ struct SvgTransformListObjectDeclaration<'scope> {
#[webapi(
method = "createSVGTransformFromMatrix",
callback = svg_transform_list_create_transform_from_matrix_callback,
length = 1
length = 0
)]
create_svg_transform_from_matrix: (),
#[webapi(method, callback = svg_transform_list_consolidate_callback, length = 0)]
@@ -248,7 +248,7 @@ struct SvgTransformObjectDeclaration<'scope> {
angle: f64,
#[webapi(slot = SVG_TRANSFORM_MATRIX_SLOT)]
matrix: v8::Local<'scope, v8::Object>,
#[webapi(method, callback = svg_transform_set_matrix_callback, length = 1)]
#[webapi(method, callback = svg_transform_set_matrix_callback, length = 0)]
set_matrix: (),
#[webapi(method, callback = svg_transform_set_translate_callback, length = 2)]
set_translate: (),
@@ -1038,15 +1038,6 @@ pub(super) fn svg_matrix_value_or_throw<'s>(
None
}
pub(super) fn cloned_svg_matrix_value_or_throw<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
) -> Option<v8::Local<'s, v8::Object>> {
let matrix = svg_matrix_value_or_throw(scope, value)?;
let components = svg_matrix_components(scope, matrix);
Some(build_svg_matrix(scope, components))
}
pub(super) fn svg_value_list_item_or_default<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
@@ -17,6 +17,16 @@ const SVG_ANIMATED_ACCESSOR_NAMES: &[&str] = &["baseVal", "animVal"];
const SVG_TRANSFORM_ACCESSOR_NAMES: &[&str] = &["type", "matrix", "angle"];
const SVG_MATRIX_ACCESSOR_NAMES: &[&str] = &["a", "b", "c", "d", "e", "f"];
fn svg_dom_matrix_2d_init_arg<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: &v8::FunctionCallbackArguments<'s>,
prefix: &'static str,
) -> Option<SvgMatrixComponents> {
let [a, b, c, d, e, f] =
super::super::geometry_runtime::dom_matrix_2d_init_arg(scope, args, 0, prefix)?;
Some(SvgMatrixComponents { a, b, c, d, e, f })
}
const SVG_TEXT_POSITIONING_LIST_ATTRIBUTES: &[(&str, &str, SvgListKind)] = &[
("x", SVG_TEXT_POSITIONING_X_SLOT, SvgListKind::Length),
("y", SVG_TEXT_POSITIONING_Y_SLOT, SvgListKind::Length),
@@ -1363,13 +1373,13 @@ pub(super) fn svg_transform_list_create_transform_from_matrix_callback<'s>(
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(parsed) = webidl::parse_args::<SvgListItemArgs>(scope, &args) else {
let Some(components) = svg_dom_matrix_2d_init_arg(
scope,
&args,
"SVGTransformList.createSVGTransformFromMatrix",
) else {
return;
};
let Some(matrix) = cloned_svg_matrix_value_or_throw(scope, parsed.item) else {
return;
};
let components = svg_matrix_components(scope, matrix);
rv.set(build_svg_transform(scope, SvgTransform::matrix(components)).into());
}
@@ -1442,13 +1452,10 @@ pub(super) fn svg_transform_set_matrix_callback<'s>(
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(parsed) = webidl::parse_args::<SvgMatrixArg>(scope, &args) else {
let Some(components) = svg_dom_matrix_2d_init_arg(scope, &args, "SVGTransform.setMatrix")
else {
return;
};
let Some(matrix) = cloned_svg_matrix_value_or_throw(scope, parsed.matrix) else {
return;
};
let components = svg_matrix_components(scope, matrix);
set_svg_transform_state(scope, args.this(), SvgTransform::matrix(components));
reflect_svg_transform_item_to_owner_list(scope, args.this());
rv.set_undefined();
@@ -1459,7 +1466,7 @@ pub(super) fn svg_svg_element_create_matrix_callback<'s>(
_args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
rv.set(build_svg_matrix(scope, SvgMatrixComponents::identity()).into());
rv.set(super::super::geometry_runtime::build_dom_matrix_identity_object(scope).into());
}
pub(super) fn svg_svg_element_create_transform_callback<'s>(
@@ -1477,13 +1484,11 @@ pub(super) fn svg_svg_element_create_transform_from_matrix_callback<'s>(
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
let Some(parsed) = webidl::parse_args::<SvgMatrixArg>(scope, &args) else {
let Some(components) =
svg_dom_matrix_2d_init_arg(scope, &args, "SVGSVGElement.createSVGTransformFromMatrix")
else {
return;
};
let Some(matrix) = cloned_svg_matrix_value_or_throw(scope, parsed.matrix) else {
return;
};
let components = svg_matrix_components(scope, matrix);
rv.set(build_svg_transform(scope, SvgTransform::matrix(components)).into());
}
@@ -5539,6 +5539,7 @@ fn geometry_exposes_legacy_window_aliases() {
const rect = new SVGRect(3, 4, 5, 6);
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
const matrix = svg.createSVGMatrix();
const multiplied = matrix.multiply({a: 3, d: 2});
matrix.a = 2;
return JSON.stringify({
identities: [
@@ -5552,8 +5553,11 @@ fn geometry_exposes_legacy_window_aliases() {
matrix: [
matrix instanceof DOMMatrix,
matrix instanceof SVGMatrix,
DOMMatrix.prototype.isPrototypeOf(matrix),
Object.getPrototypeOf(matrix) === DOMMatrix.prototype,
!Object.hasOwn(matrix, "multiply"),
matrix.a,
multiplied.a,
multiplied.d,
Object.prototype.toString.call(matrix)
].join(","),
descriptors: aliases.map(descriptorShape).join("|")
@@ -5566,13 +5570,61 @@ fn geometry_exposes_legacy_window_aliases() {
assert_eq!(
result,
concat!(
r#"{"identities":"true,true,true,true","point":"true,1,2,[object DOMPoint]","rect":"true,3,6,[object DOMRect]","matrix":"true,true,true,2,[object SVGMatrix]","descriptors":""#,
r#"{"identities":"true,true,true,true","point":"true,1,2,[object DOMPoint]","rect":"true,3,6,[object DOMRect]","matrix":"true,true,true,true,2,3,2,[object DOMMatrix]","descriptors":""#,
"true,true,false,true|true,true,false,true|",
"true,true,false,true|true,true,false,true\"}"
)
);
}
#[test]
fn svg_transform_factories_convert_optional_dom_matrix_2d_init() {
let mut vm = new_storage_test_vm("https://svg-transform-dom-matrix-init.test/");
let result = vm
.eval(
r#"
(() => {
const SVG_NS = "http://www.w3.org/2000/svg";
const svg = document.createElementNS(SVG_NS, "svg");
const group = document.createElementNS(SVG_NS, "g");
const fromElement = svg.createSVGTransformFromMatrix({a: 2, e: 5});
const defaultElement = svg.createSVGTransformFromMatrix();
const fromFactory = svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(9, 10));
const transform = svg.createSVGTransform();
transform.setMatrix({b: 4, f: 6});
const fromList = group.transform.baseVal.createSVGTransformFromMatrix({c: 7, d: 8});
let mismatch;
try {
transform.setMatrix({a: 1, m11: 2});
mismatch = "none";
} catch (error) {
mismatch = error.name;
}
return JSON.stringify({
fromElement: [fromElement.matrix.a, fromElement.matrix.e].join(","),
defaultElement: [defaultElement.matrix.a, defaultElement.matrix.d].join(","),
fromFactory: [fromFactory.matrix.e, fromFactory.matrix.f].join(","),
setMatrix: [transform.matrix.b, transform.matrix.f].join(","),
fromList: [fromList.matrix.c, fromList.matrix.d].join(","),
mismatch,
lengths: [
SVGSVGElement.prototype.createSVGTransformFromMatrix.length,
SVGTransform.prototype.setMatrix.length,
SVGTransformList.prototype.createSVGTransformFromMatrix.length
].join(",")
});
})()
"#,
)
.expect("SVG transform DOMMatrix2DInit conversion should evaluate");
assert_eq!(
result,
r#"{"fromElement":"2,5","defaultElement":"1,1","fromFactory":"9,10","setMatrix":"4,6","fromList":"7,8","mismatch":"TypeError","lengths":"0,0,0"}"#
);
}
#[test]
fn dom_matrix_window_operations_use_webidl_descriptors() {
let mut vm = new_storage_test_vm("https://dommatrix-operation-descriptors.test/");
@@ -8174,6 +8174,7 @@ fn svg_list_matrix_and_transform_declared_methods_keep_descriptors() {
numberInsertItemBefore: methodDescriptor(numberList, "insertItemBefore"),
transformListCreate: methodDescriptor(transformList, "createSVGTransformFromMatrix"),
transformListConsolidate: methodDescriptor(transformList, "consolidate"),
transformSetMatrix: methodDescriptor(transform, "setMatrix"),
transformSetRotate: methodDescriptor(transform, "setRotate"),
transformSetSkewX: methodDescriptor(transform, "setSkewX"),
matrixScaleNonUniform: methodDescriptor(matrix, "scaleNonUniform"),
@@ -8193,7 +8194,7 @@ fn svg_list_matrix_and_transform_declared_methods_keep_descriptors() {
assert_eq!(
result,
r#"{"lengthGetItem":"false,true,true,getItem,1","lengthAppendItem":"false,true,true,appendItem,1","numberInsertItemBefore":"false,true,true,insertItemBefore,2","transformListCreate":"false,true,true,createSVGTransformFromMatrix,1","transformListConsolidate":"false,true,true,consolidate,0","transformSetRotate":"false,true,true,setRotate,3","transformSetSkewX":"false,true,true,setSkewX,1","matrixScaleNonUniform":"false,true,true,scaleNonUniform,2","matrixRotateFromVector":"false,true,true,rotateFromVector,2","matrixFlipX":"false,true,true,flipX,0","transformOwnMethods":["setMatrix","setRotate","setScale","setSkewX","setSkewY","setTranslate"],"matrixOwnMethods":["flipX","flipY","inverse","multiply","rotate","rotateFromVector","scale","scaleNonUniform","skewX","skewY","translate"]}"#
r#"{"lengthGetItem":"false,true,true,getItem,1","lengthAppendItem":"false,true,true,appendItem,1","numberInsertItemBefore":"false,true,true,insertItemBefore,2","transformListCreate":"false,true,true,createSVGTransformFromMatrix,0","transformListConsolidate":"false,true,true,consolidate,0","transformSetMatrix":"false,true,true,setMatrix,0","transformSetRotate":"false,true,true,setRotate,3","transformSetSkewX":"false,true,true,setSkewX,1","matrixScaleNonUniform":"false,true,true,scaleNonUniform,2","matrixRotateFromVector":"false,true,true,rotateFromVector,2","matrixFlipX":"false,true,true,flipX,0","transformOwnMethods":["setMatrix","setRotate","setScale","setSkewX","setSkewY","setTranslate"],"matrixOwnMethods":["flipX","flipY","inverse","multiply","rotate","rotateFromVector","scale","scaleNonUniform","skewX","skewY","translate"]}"#
);
}