fix(geometry): track DOMMatrix dimension state

This commit is contained in:
ldm0
2026-09-27 19:23:02 +08:00
parent 570e031538
commit e7bd71ebeb
7 changed files with 410 additions and 146 deletions
@@ -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
@@ -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
+32 -7
View File
@@ -36,23 +36,48 @@ pub fn dom_matrix_components_from_values(values: &[f64]) -> Option<DomMatrixComp
}
pub fn parse_dom_matrix_value(text: &str) -> Option<DomMatrixComponents> {
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(
+27 -2
View File
@@ -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());
}
}
+12 -4
View File
@@ -498,15 +498,23 @@ impl DomMatrixComponents {
}
pub fn css_text(self) -> Option<String> {
self.text(css_number)
self.css_text_with_dimension(self.is_2d())
}
pub fn css_text_with_dimension(self, is_2d: bool) -> Option<String> {
self.text(is_2d, css_number)
}
pub fn dom_matrix_text(self) -> Option<String> {
self.text(ecmascript_number)
self.dom_matrix_text_with_dimension(self.is_2d())
}
fn text(self, serialize_number: fn(f64) -> String) -> Option<String> {
if self.is_2d() {
pub fn dom_matrix_text_with_dimension(self, is_2d: bool) -> Option<String> {
self.text(is_2d, ecmascript_number)
}
fn text(self, is_2d: bool, serialize_number: fn(f64) -> String) -> Option<String> {
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;
@@ -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<f64>,
}
#[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<DomMatrixComponents> {
prefix: &'static str,
) -> Option<DomMatrixValue> {
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<DomMatrixValue> {
let init = match webidl::parse_dictionary::<DomMatrixInit>(
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<DomMatrixComponents> {
) -> 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)?;
@@ -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<DomMatrixComponents> {
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<DomMatrixValue> {
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<DomMatrixComponents> {
) -> Option<DomMatrixValue> {
let expected = match kind {
DomMatrixTypedArrayKind::Float32 => v8::Local::<v8::Float32Array>::try_from(value).is_ok(),
DomMatrixTypedArrayKind::Float64 => v8::Local::<v8::Float64Array>::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<DomMatrixComponents> {
) -> Option<DomMatrixValue> {
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::<v8::Float32Array>::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::<v8::Float64Array>::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::<v8::Array>::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::<v8::Object>::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<String> {
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<Option<f64>> {
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>,
@@ -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":["<b>ok</b>","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"
)
);
}