mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 08:01:00 +00:00
fix(svg): reflect fit-to-view-box attributes
This commit is contained in:
@@ -1791,6 +1791,12 @@ pub(crate) fn finish_context_bootstrap(
|
||||
("SVGAnimatedString", "SVGAnimatedString"),
|
||||
("SVGAnimatedLength", "SVGAnimatedLength"),
|
||||
("SVGAnimatedAngle", "SVGAnimatedAngle"),
|
||||
("SVGAnimatedRect", "SVGAnimatedRect"),
|
||||
("SVGPreserveAspectRatio", "SVGPreserveAspectRatio"),
|
||||
(
|
||||
"SVGAnimatedPreserveAspectRatio",
|
||||
"SVGAnimatedPreserveAspectRatio",
|
||||
),
|
||||
("SVGLengthList", "SVGLengthList"),
|
||||
("SVGAnimatedLengthList", "SVGAnimatedLengthList"),
|
||||
("SVGAnimatedNumber", "SVGAnimatedNumber"),
|
||||
|
||||
@@ -138,6 +138,21 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
parent: None,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "SVGAnimatedRect",
|
||||
parent: None,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "SVGPreserveAspectRatio",
|
||||
parent: None,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "SVGAnimatedPreserveAspectRatio",
|
||||
parent: None,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "SVGLengthList",
|
||||
parent: None,
|
||||
|
||||
@@ -575,6 +575,27 @@ macro_rules! define_svg_enumeration_constants {
|
||||
};
|
||||
}
|
||||
|
||||
define_svg_enumeration_constants!(
|
||||
SvgPreserveAspectRatioTemplateConstantsDeclaration,
|
||||
"SVGPreserveAspectRatio",
|
||||
{
|
||||
preserve_aspect_ratio_unknown => "SVG_PRESERVEASPECTRATIO_UNKNOWN" = SVG_PRESERVE_ASPECT_RATIO_UNKNOWN,
|
||||
preserve_aspect_ratio_none => "SVG_PRESERVEASPECTRATIO_NONE" = SVG_PRESERVE_ASPECT_RATIO_NONE,
|
||||
preserve_aspect_ratio_x_min_y_min => "SVG_PRESERVEASPECTRATIO_XMINYMIN" = SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MIN,
|
||||
preserve_aspect_ratio_x_mid_y_min => "SVG_PRESERVEASPECTRATIO_XMIDYMIN" = SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MIN,
|
||||
preserve_aspect_ratio_x_max_y_min => "SVG_PRESERVEASPECTRATIO_XMAXYMIN" = SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MIN,
|
||||
preserve_aspect_ratio_x_min_y_mid => "SVG_PRESERVEASPECTRATIO_XMINYMID" = SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MID,
|
||||
preserve_aspect_ratio_x_mid_y_mid => "SVG_PRESERVEASPECTRATIO_XMIDYMID" = SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID,
|
||||
preserve_aspect_ratio_x_max_y_mid => "SVG_PRESERVEASPECTRATIO_XMAXYMID" = SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MID,
|
||||
preserve_aspect_ratio_x_min_y_max => "SVG_PRESERVEASPECTRATIO_XMINYMAX" = SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MAX,
|
||||
preserve_aspect_ratio_x_mid_y_max => "SVG_PRESERVEASPECTRATIO_XMIDYMAX" = SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MAX,
|
||||
preserve_aspect_ratio_x_max_y_max => "SVG_PRESERVEASPECTRATIO_XMAXYMAX" = SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MAX,
|
||||
meet_or_slice_unknown => "SVG_MEETORSLICE_UNKNOWN" = SVG_MEET_OR_SLICE_UNKNOWN,
|
||||
meet_or_slice_meet => "SVG_MEETORSLICE_MEET" = SVG_MEET_OR_SLICE_MEET,
|
||||
meet_or_slice_slice => "SVG_MEETORSLICE_SLICE" = SVG_MEET_OR_SLICE_SLICE,
|
||||
}
|
||||
);
|
||||
|
||||
define_svg_enumeration_constants!(
|
||||
SvgComponentTransferFunctionElementTemplateConstantsDeclaration,
|
||||
"SVGComponentTransferFunctionElement",
|
||||
@@ -973,6 +994,62 @@ struct SvgAnimatedAngleTemplateAccessorsDeclaration {
|
||||
anim_val: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGAnimatedRect", enumerable)]
|
||||
struct SvgAnimatedRectTemplateAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "baseVal",
|
||||
getter = svg_animated_rect_getter,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
base_val: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "animVal",
|
||||
getter = svg_animated_rect_getter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
anim_val: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGPreserveAspectRatio", enumerable)]
|
||||
struct SvgPreserveAspectRatioTemplateAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "align",
|
||||
getter = svg_preserve_aspect_ratio_getter,
|
||||
setter = svg_preserve_aspect_ratio_setter,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
align: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "meetOrSlice",
|
||||
getter = svg_preserve_aspect_ratio_getter,
|
||||
setter = svg_preserve_aspect_ratio_setter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
meet_or_slice: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGAnimatedPreserveAspectRatio", enumerable)]
|
||||
struct SvgAnimatedPreserveAspectRatioTemplateAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "baseVal",
|
||||
getter = svg_animated_preserve_aspect_ratio_getter,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
base_val: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "animVal",
|
||||
getter = svg_animated_preserve_aspect_ratio_getter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
anim_val: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGNumber", enumerable)]
|
||||
struct SvgNumberTemplateAccessorsDeclaration {
|
||||
@@ -1252,6 +1329,35 @@ struct SvgGeometryElementPrototypeAccessorsDeclaration {
|
||||
path_length: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGFitToViewBox", enumerable)]
|
||||
struct SvgFitToViewBoxPrototypeAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "viewBox",
|
||||
getter = svg_fit_to_view_box_getter,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
view_box: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "preserveAspectRatio",
|
||||
getter = svg_fit_to_view_box_getter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
preserve_aspect_ratio: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGImageElement", enumerable)]
|
||||
struct SvgImagePreserveAspectRatioPrototypeAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "preserveAspectRatio",
|
||||
getter = svg_fit_to_view_box_getter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
preserve_aspect_ratio: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGTextContentElement", enumerable)]
|
||||
struct SvgTextContentElementPrototypeAccessorsDeclaration {
|
||||
@@ -1829,6 +1935,34 @@ pub(super) fn install_svg_animated_angle_bindings<'s>(
|
||||
SvgAnimatedAngleTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_animated_rect_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
) {
|
||||
let proto = template.prototype_template(scope);
|
||||
SvgAnimatedRectTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_preserve_aspect_ratio_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
) {
|
||||
let proto = template.prototype_template(scope);
|
||||
SvgPreserveAspectRatioTemplateConstantsDeclaration::initialize_template(scope, template);
|
||||
SvgPreserveAspectRatioTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
|
||||
SvgPreserveAspectRatioTemplateConstantsDeclaration::initialize_prototype_template(scope, proto);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_animated_preserve_aspect_ratio_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
) {
|
||||
let proto = template.prototype_template(scope);
|
||||
SvgAnimatedPreserveAspectRatioTemplateAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, proto,
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_number_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
@@ -2082,6 +2216,18 @@ pub(super) fn install_svg_element_accessor_bindings<'s>(
|
||||
interface_name: &str,
|
||||
) {
|
||||
let prototype = template.prototype_template(scope);
|
||||
if matches!(
|
||||
interface_name,
|
||||
"SVGSVGElement"
|
||||
| "SVGSymbolElement"
|
||||
| "SVGMarkerElement"
|
||||
| "SVGPatternElement"
|
||||
| "SVGViewElement"
|
||||
) {
|
||||
SvgFitToViewBoxPrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, prototype,
|
||||
);
|
||||
}
|
||||
if matches!(
|
||||
interface_name,
|
||||
"SVGFEBlendElement"
|
||||
@@ -2256,6 +2402,11 @@ pub(super) fn install_svg_element_accessor_bindings<'s>(
|
||||
SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, prototype,
|
||||
);
|
||||
if interface_name == "SVGImageElement" {
|
||||
SvgImagePreserveAspectRatioPrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, prototype,
|
||||
);
|
||||
}
|
||||
}
|
||||
"SVGForeignObjectElement" => {
|
||||
SvgGraphicsBoxElementPrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
|
||||
@@ -141,6 +141,46 @@ struct SvgAnimatedAngleObjectDeclaration<'scope> {
|
||||
anim_val: v8::Local<'scope, v8::Object>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(
|
||||
interface = "SVGAnimatedRect",
|
||||
fallback_to_string_tag = "SVGAnimatedRect"
|
||||
)]
|
||||
struct SvgAnimatedRectObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_ANIMATED_RECT_BASE_VAL_SLOT)]
|
||||
base_val: v8::Local<'scope, v8::Object>,
|
||||
#[webapi(slot = SVG_ANIMATED_RECT_ANIM_VAL_SLOT)]
|
||||
anim_val: v8::Local<'scope, v8::Object>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(
|
||||
interface = "SVGPreserveAspectRatio",
|
||||
fallback_to_string_tag = "SVGPreserveAspectRatio"
|
||||
)]
|
||||
struct SvgPreserveAspectRatioObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT)]
|
||||
align: u32,
|
||||
#[webapi(slot = SVG_PRESERVE_ASPECT_RATIO_MEET_OR_SLICE_SLOT)]
|
||||
meet_or_slice: u32,
|
||||
#[webapi(slot = SVG_PRESERVE_ASPECT_RATIO_OWNER_ELEMENT_SLOT)]
|
||||
owner: v8::Local<'scope, v8::Object>,
|
||||
#[webapi(slot = SVG_PRESERVE_ASPECT_RATIO_READ_ONLY_SLOT)]
|
||||
read_only: bool,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(
|
||||
interface = "SVGAnimatedPreserveAspectRatio",
|
||||
fallback_to_string_tag = "SVGAnimatedPreserveAspectRatio"
|
||||
)]
|
||||
struct SvgAnimatedPreserveAspectRatioObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_ANIMATED_PRESERVE_ASPECT_RATIO_BASE_VAL_SLOT)]
|
||||
base_val: v8::Local<'scope, v8::Object>,
|
||||
#[webapi(slot = SVG_ANIMATED_PRESERVE_ASPECT_RATIO_ANIM_VAL_SLOT)]
|
||||
anim_val: v8::Local<'scope, v8::Object>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "SVGLength", fallback_to_string_tag = "SVGLength")]
|
||||
struct SvgLengthObjectDeclaration {
|
||||
@@ -1127,6 +1167,34 @@ pub(super) fn build_svg_animated_angle_for_attribute<'s>(
|
||||
.expect("SVGAnimatedAngle declaration should bind")
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_animated_rect_for_view_box<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let values = svg_view_box_attribute_value(scope, owner);
|
||||
let base_val = super::rect::build_svg_view_box_rect(scope, owner, values, false);
|
||||
let anim_val = super::rect::build_svg_view_box_rect(scope, owner, values, true);
|
||||
SvgAnimatedRectObjectDeclaration::new(base_val, anim_val)
|
||||
.bind(scope)
|
||||
.expect("SVGAnimatedRect declaration should bind")
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_animated_preserve_aspect_ratio<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let [align, meet_or_slice] = svg_preserve_aspect_ratio_attribute_value(scope, owner);
|
||||
let base_val = SvgPreserveAspectRatioObjectDeclaration::new(align, meet_or_slice, owner, false)
|
||||
.bind(scope)
|
||||
.expect("SVGPreserveAspectRatio base value declaration should bind");
|
||||
let anim_val = SvgPreserveAspectRatioObjectDeclaration::new(align, meet_or_slice, owner, true)
|
||||
.bind(scope)
|
||||
.expect("SVGPreserveAspectRatio animated value declaration should bind");
|
||||
SvgAnimatedPreserveAspectRatioObjectDeclaration::new(base_val, anim_val)
|
||||
.bind(scope)
|
||||
.expect("SVGAnimatedPreserveAspectRatio declaration should bind")
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_angle<'s>(scope: &mut v8::PinScope<'s, '_>) -> v8::Local<'s, v8::Object> {
|
||||
build_svg_angle_from_parsed(scope, &SvgParsedAngle::default(), false)
|
||||
}
|
||||
@@ -2857,6 +2925,248 @@ fn svg_animated_length_attribute_value<'s>(
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(super) fn sync_svg_animated_rect_from_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
animated: v8::Local<'s, v8::Object>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let values = svg_view_box_attribute_value(scope, owner);
|
||||
for slot in [
|
||||
SVG_ANIMATED_RECT_BASE_VAL_SLOT,
|
||||
SVG_ANIMATED_RECT_ANIM_VAL_SLOT,
|
||||
] {
|
||||
if let Some(rect) = get_private_value(scope, animated, slot)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
super::rect::set_svg_view_box_rect_values(scope, rect, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sync_svg_view_box_rect_from_owner<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
rect: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(owner) = super::rect::svg_view_box_rect_owner(scope, rect) else {
|
||||
return;
|
||||
};
|
||||
if let Some(animated) = get_private_value(scope, owner, SVG_FIT_VIEW_BOX_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
sync_svg_animated_rect_from_owner_attribute(scope, animated, owner);
|
||||
} else {
|
||||
let values = svg_view_box_attribute_value(scope, owner);
|
||||
super::rect::set_svg_view_box_rect_values(scope, rect, values);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn reflect_svg_view_box_rect_mutation<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
rect: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(owner) = super::rect::svg_view_box_rect_owner(scope, rect) else {
|
||||
return;
|
||||
};
|
||||
let values = super::rect::svg_view_box_rect_values(scope, rect);
|
||||
// Keep float round trips, including integral values outside the i64 range.
|
||||
let serialized = values
|
||||
.map(|value| {
|
||||
if value == 0.0 {
|
||||
"0".to_owned()
|
||||
} else {
|
||||
(value as f32).to_string()
|
||||
}
|
||||
})
|
||||
.join(" ");
|
||||
set_svg_owner_attribute_value(scope, owner, "viewBox", &serialized);
|
||||
if let Some(animated) = get_private_value(scope, owner, SVG_FIT_VIEW_BOX_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
sync_svg_animated_rect_from_owner_attribute(scope, animated, owner);
|
||||
}
|
||||
}
|
||||
|
||||
fn svg_view_box_attribute_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> [f64; 4] {
|
||||
svg_owner_attribute_value(scope, owner, "viewBox")
|
||||
.as_deref()
|
||||
.and_then(parse_svg_view_box_value)
|
||||
.unwrap_or([0.0; 4])
|
||||
}
|
||||
|
||||
fn parse_svg_view_box_value(raw: &str) -> Option<[f64; 4]> {
|
||||
let values = svg_geometry::parse_number_list(raw)?;
|
||||
let [x, y, width, height] = values.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
if *width < 0.0 || *height < 0.0 {
|
||||
return None;
|
||||
}
|
||||
let values = [*x, *y, *width, *height].map(|value| f64::from(value as f32));
|
||||
values
|
||||
.iter()
|
||||
.all(|value| value.is_finite())
|
||||
.then_some(values)
|
||||
}
|
||||
|
||||
pub(super) fn sync_svg_animated_preserve_aspect_ratio_from_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
animated: v8::Local<'s, v8::Object>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let value = svg_preserve_aspect_ratio_attribute_value(scope, owner);
|
||||
for slot in [
|
||||
SVG_ANIMATED_PRESERVE_ASPECT_RATIO_BASE_VAL_SLOT,
|
||||
SVG_ANIMATED_PRESERVE_ASPECT_RATIO_ANIM_VAL_SLOT,
|
||||
] {
|
||||
if let Some(aspect_ratio) = get_private_value(scope, animated, slot)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
set_svg_preserve_aspect_ratio_value(scope, aspect_ratio, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn sync_svg_preserve_aspect_ratio_from_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
aspect_ratio: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(owner) = get_private_value(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_OWNER_ELEMENT_SLOT,
|
||||
)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok()) else {
|
||||
return;
|
||||
};
|
||||
let value = svg_preserve_aspect_ratio_attribute_value(scope, owner);
|
||||
set_svg_preserve_aspect_ratio_value(scope, aspect_ratio, value);
|
||||
}
|
||||
|
||||
pub(super) fn set_svg_preserve_aspect_ratio_value(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
aspect_ratio: v8::Local<'_, v8::Object>,
|
||||
[align, meet_or_slice]: [u32; 2],
|
||||
) {
|
||||
set_private_value(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT,
|
||||
v8::Integer::new_from_unsigned(scope, align).into(),
|
||||
);
|
||||
set_private_value(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_MEET_OR_SLICE_SLOT,
|
||||
v8::Integer::new_from_unsigned(scope, meet_or_slice).into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn reflect_svg_preserve_aspect_ratio_to_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
aspect_ratio: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some(owner) = get_private_value(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_OWNER_ELEMENT_SLOT,
|
||||
)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok()) else {
|
||||
return;
|
||||
};
|
||||
let align = svg_number_slot(scope, aspect_ratio, SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT)
|
||||
.unwrap_or(SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID as f64) as u32;
|
||||
let meet_or_slice = svg_number_slot(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_MEET_OR_SLICE_SLOT,
|
||||
)
|
||||
.unwrap_or(SVG_MEET_OR_SLICE_MEET as f64) as u32;
|
||||
let (Some(align), Some(meet_or_slice)) = (
|
||||
serialize_svg_preserve_aspect_ratio_align(align),
|
||||
serialize_svg_meet_or_slice(meet_or_slice),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let serialized = format!("{align} {meet_or_slice}");
|
||||
set_svg_owner_attribute_value(scope, owner, "preserveAspectRatio", &serialized);
|
||||
if let Some(animated) = get_private_value(scope, owner, SVG_FIT_PRESERVE_ASPECT_RATIO_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
sync_svg_animated_preserve_aspect_ratio_from_owner_attribute(scope, animated, owner);
|
||||
}
|
||||
}
|
||||
|
||||
fn svg_preserve_aspect_ratio_attribute_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
) -> [u32; 2] {
|
||||
svg_owner_attribute_value(scope, owner, "preserveAspectRatio")
|
||||
.as_deref()
|
||||
.and_then(parse_svg_preserve_aspect_ratio_value)
|
||||
.unwrap_or([
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID,
|
||||
SVG_MEET_OR_SLICE_MEET,
|
||||
])
|
||||
}
|
||||
|
||||
fn parse_svg_preserve_aspect_ratio_value(raw: &str) -> Option<[u32; 2]> {
|
||||
let mut tokens = raw.split_ascii_whitespace();
|
||||
let mut align = tokens.next()?;
|
||||
if align == "defer" {
|
||||
align = tokens.next()?;
|
||||
}
|
||||
let align = parse_svg_preserve_aspect_ratio_align(align)?;
|
||||
let meet_or_slice = match tokens.next() {
|
||||
None | Some("meet") => SVG_MEET_OR_SLICE_MEET,
|
||||
Some("slice") => SVG_MEET_OR_SLICE_SLICE,
|
||||
Some(_) => return None,
|
||||
};
|
||||
tokens.next().is_none().then_some([align, meet_or_slice])
|
||||
}
|
||||
|
||||
fn parse_svg_preserve_aspect_ratio_align(value: &str) -> Option<u32> {
|
||||
match value {
|
||||
"none" => Some(SVG_PRESERVE_ASPECT_RATIO_NONE),
|
||||
"xMinYMin" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MIN),
|
||||
"xMidYMin" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MIN),
|
||||
"xMaxYMin" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MIN),
|
||||
"xMinYMid" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MID),
|
||||
"xMidYMid" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID),
|
||||
"xMaxYMid" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MID),
|
||||
"xMinYMax" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MAX),
|
||||
"xMidYMax" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MAX),
|
||||
"xMaxYMax" => Some(SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MAX),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_svg_preserve_aspect_ratio_align(value: u32) -> Option<&'static str> {
|
||||
match value {
|
||||
SVG_PRESERVE_ASPECT_RATIO_NONE => Some("none"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MIN => Some("xMinYMin"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MIN => Some("xMidYMin"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MIN => Some("xMaxYMin"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MID => Some("xMinYMid"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID => Some("xMidYMid"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MID => Some("xMaxYMid"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MAX => Some("xMinYMax"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MAX => Some("xMidYMax"),
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MAX => Some("xMaxYMax"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_svg_meet_or_slice(value: u32) -> Option<&'static str> {
|
||||
match value {
|
||||
SVG_MEET_OR_SLICE_MEET => Some("meet"),
|
||||
SVG_MEET_OR_SLICE_SLICE => Some("slice"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn set_svg_animated_string_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
animated: v8::Local<'s, v8::Object>,
|
||||
@@ -3066,6 +3376,21 @@ pub(super) fn svg_owner_attribute_value<'s>(
|
||||
)
|
||||
}
|
||||
|
||||
fn set_svg_owner_attribute_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
attribute: &str,
|
||||
value: &str,
|
||||
) {
|
||||
let Ok((runtime_ptr, handle)) =
|
||||
crate::native_bridge::node_runtime_and_handle_from_object(scope, owner)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let runtime = unsafe { &mut *runtime_ptr };
|
||||
let _ = runtime.set_attribute(scope, runtime_ptr, handle, attribute, value);
|
||||
}
|
||||
|
||||
pub(super) fn parse_svg_length_value(raw: &str) -> Option<SvgParsedLength> {
|
||||
svg_geometry::parse_length(raw).map(svg_parsed_length_from_svg_length)
|
||||
}
|
||||
|
||||
@@ -61,6 +61,8 @@ const SVG_LENGTH_ACCESSOR_NAMES: &[&str] = &[
|
||||
"valueAsString",
|
||||
];
|
||||
const SVG_ANIMATED_ACCESSOR_NAMES: &[&str] = &["baseVal", "animVal"];
|
||||
const SVG_FIT_TO_VIEW_BOX_ACCESSOR_NAMES: &[&str] = &["viewBox", "preserveAspectRatio"];
|
||||
const SVG_PRESERVE_ASPECT_RATIO_ACCESSOR_NAMES: &[&str] = &["align", "meetOrSlice"];
|
||||
const SVG_TRANSFORM_ACCESSOR_NAMES: &[&str] = &["type", "matrix", "angle"];
|
||||
const SVG_MATRIX_ACCESSOR_NAMES: &[&str] = &["a", "b", "c", "d", "e", "f"];
|
||||
|
||||
@@ -133,6 +135,103 @@ fn require_svg_receiver<'s>(
|
||||
false
|
||||
}
|
||||
|
||||
fn require_svg_fit_to_view_box_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
member: &str,
|
||||
) -> bool {
|
||||
let Ok((runtime_ptr, handle)) =
|
||||
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, receiver)
|
||||
else {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGFitToViewBox.{member} called on incompatible receiver."),
|
||||
);
|
||||
return false;
|
||||
};
|
||||
let is_supported_element = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.is_some_and(|node| {
|
||||
node.namespace() == Some(crate::native_bridge::document::SVG_NS)
|
||||
&& node.local_name().is_some_and(|local_name| match member {
|
||||
"viewBox" => {
|
||||
matches!(local_name, "svg" | "symbol" | "marker" | "pattern" | "view")
|
||||
}
|
||||
"preserveAspectRatio" => matches!(
|
||||
local_name,
|
||||
"svg" | "symbol" | "marker" | "pattern" | "view" | "image"
|
||||
),
|
||||
_ => false,
|
||||
})
|
||||
});
|
||||
if !is_supported_element {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGFitToViewBox.{member} called on incompatible receiver."),
|
||||
);
|
||||
}
|
||||
is_supported_element
|
||||
}
|
||||
|
||||
pub(super) fn svg_fit_to_view_box_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(member) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_FIT_TO_VIEW_BOX_ACCESSOR_NAMES,
|
||||
"SVGFitToViewBox attributes",
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let owner = args.this();
|
||||
if !require_svg_fit_to_view_box_receiver(scope, owner, member) {
|
||||
return;
|
||||
}
|
||||
let (slot, value) = match member {
|
||||
"viewBox" => {
|
||||
let slot = SVG_FIT_VIEW_BOX_SLOT;
|
||||
if let Some(value) = get_private_value(scope, owner, slot) {
|
||||
if let Ok(animated) = v8::Local::<v8::Object>::try_from(value) {
|
||||
sync_svg_animated_rect_from_owner_attribute(scope, animated, owner);
|
||||
}
|
||||
(slot, value)
|
||||
} else {
|
||||
(
|
||||
slot,
|
||||
build_svg_animated_rect_for_view_box(scope, owner).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
"preserveAspectRatio" => {
|
||||
let slot = SVG_FIT_PRESERVE_ASPECT_RATIO_SLOT;
|
||||
if let Some(value) = get_private_value(scope, owner, slot) {
|
||||
if let Ok(animated) = v8::Local::<v8::Object>::try_from(value) {
|
||||
sync_svg_animated_preserve_aspect_ratio_from_owner_attribute(
|
||||
scope, animated, owner,
|
||||
);
|
||||
}
|
||||
(slot, value)
|
||||
} else {
|
||||
(
|
||||
slot,
|
||||
build_svg_animated_preserve_aspect_ratio(scope, owner).into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
};
|
||||
set_private_value(scope, owner, slot, value);
|
||||
rv.set(value);
|
||||
}
|
||||
|
||||
pub(super) fn svg_element_class_name_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1113,6 +1212,192 @@ pub(super) fn svg_animated_angle_getter<'s>(
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn svg_animated_rect_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(name) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_ANIMATED_ACCESSOR_NAMES,
|
||||
"SVGAnimatedRect attributes",
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
if !require_svg_receiver(
|
||||
scope,
|
||||
args.this(),
|
||||
SVG_ANIMATED_RECT_BASE_VAL_SLOT,
|
||||
"SVGAnimatedRect",
|
||||
&format!("{name} getter"),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let slot = match name {
|
||||
"baseVal" => SVG_ANIMATED_RECT_BASE_VAL_SLOT,
|
||||
"animVal" => SVG_ANIMATED_RECT_ANIM_VAL_SLOT,
|
||||
_ => {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
};
|
||||
rv.set(
|
||||
get_private_value(scope, args.this(), slot).unwrap_or_else(|| v8::undefined(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn svg_animated_preserve_aspect_ratio_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(name) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_ANIMATED_ACCESSOR_NAMES,
|
||||
"SVGAnimatedPreserveAspectRatio attributes",
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
if !require_svg_receiver(
|
||||
scope,
|
||||
args.this(),
|
||||
SVG_ANIMATED_PRESERVE_ASPECT_RATIO_BASE_VAL_SLOT,
|
||||
"SVGAnimatedPreserveAspectRatio",
|
||||
&format!("{name} getter"),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let slot = match name {
|
||||
"baseVal" => SVG_ANIMATED_PRESERVE_ASPECT_RATIO_BASE_VAL_SLOT,
|
||||
"animVal" => SVG_ANIMATED_PRESERVE_ASPECT_RATIO_ANIM_VAL_SLOT,
|
||||
_ => {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
};
|
||||
rv.set(
|
||||
get_private_value(scope, args.this(), slot).unwrap_or_else(|| v8::undefined(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn svg_preserve_aspect_ratio_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(name) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_PRESERVE_ASPECT_RATIO_ACCESSOR_NAMES,
|
||||
"SVGPreserveAspectRatio attributes",
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let aspect_ratio = args.this();
|
||||
if !require_svg_receiver(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT,
|
||||
"SVGPreserveAspectRatio",
|
||||
&format!("{name} getter"),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
sync_svg_preserve_aspect_ratio_from_owner_attribute(scope, aspect_ratio);
|
||||
let (slot, initial_value) = match name {
|
||||
"align" => (
|
||||
SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT,
|
||||
SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID,
|
||||
),
|
||||
"meetOrSlice" => (
|
||||
SVG_PRESERVE_ASPECT_RATIO_MEET_OR_SLICE_SLOT,
|
||||
SVG_MEET_OR_SLICE_MEET,
|
||||
),
|
||||
_ => {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
};
|
||||
let value = svg_number_slot(scope, aspect_ratio, slot).unwrap_or(initial_value as f64) as u32;
|
||||
rv.set(v8::Integer::new_from_unsigned(scope, value).into());
|
||||
}
|
||||
|
||||
pub(super) fn svg_preserve_aspect_ratio_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(name) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_PRESERVE_ASPECT_RATIO_ACCESSOR_NAMES,
|
||||
"SVGPreserveAspectRatio attributes",
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let aspect_ratio = args.this();
|
||||
if !require_svg_receiver(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT,
|
||||
"SVGPreserveAspectRatio",
|
||||
&format!("{name} setter"),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if get_private_value(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
SVG_PRESERVE_ASPECT_RATIO_READ_ONLY_SLOT,
|
||||
)
|
||||
.is_some_and(|value| value.boolean_value(scope))
|
||||
{
|
||||
throw_dom_exception(
|
||||
scope,
|
||||
"NoModificationAllowedError",
|
||||
7,
|
||||
"The SVG preserveAspectRatio value is read-only.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let value = match webidl::convert::<webidl::UnsignedShort>(
|
||||
scope,
|
||||
args.get(0),
|
||||
webidl::Context::member("SVGPreserveAspectRatio", name),
|
||||
) {
|
||||
Ok(value) => u32::from(value.0),
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let slot = match name {
|
||||
"align"
|
||||
if (SVG_PRESERVE_ASPECT_RATIO_NONE..=SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MAX)
|
||||
.contains(&value) =>
|
||||
{
|
||||
SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT
|
||||
}
|
||||
"meetOrSlice" if (SVG_MEET_OR_SLICE_MEET..=SVG_MEET_OR_SLICE_SLICE).contains(&value) => {
|
||||
SVG_PRESERVE_ASPECT_RATIO_MEET_OR_SLICE_SLOT
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
sync_svg_preserve_aspect_ratio_from_owner_attribute(scope, aspect_ratio);
|
||||
set_private_value(
|
||||
scope,
|
||||
aspect_ratio,
|
||||
slot,
|
||||
v8::Integer::new_from_unsigned(scope, value).into(),
|
||||
);
|
||||
reflect_svg_preserve_aspect_ratio_to_owner_attribute(scope, aspect_ratio);
|
||||
}
|
||||
|
||||
pub(super) fn svg_animated_length_list_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -43,6 +43,20 @@ const SVG_MARKER_UNITS_SLOT: &str = "__moliSvgMarkerUnits";
|
||||
const SVG_MARKER_ORIENT_TYPE_SLOT: &str = "__moliSvgMarkerOrientType";
|
||||
const SVG_ANIMATED_ANGLE_BASE_VAL_SLOT: &str = "__moliSvgAnimatedAngleBaseVal";
|
||||
const SVG_ANIMATED_ANGLE_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedAngleAnimVal";
|
||||
const SVG_FIT_VIEW_BOX_SLOT: &str = "__moliSvgFitViewBox";
|
||||
const SVG_FIT_PRESERVE_ASPECT_RATIO_SLOT: &str = "__moliSvgFitPreserveAspectRatio";
|
||||
const SVG_ANIMATED_RECT_BASE_VAL_SLOT: &str = "__moliSvgAnimatedRectBaseVal";
|
||||
const SVG_ANIMATED_RECT_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedRectAnimVal";
|
||||
const SVG_ANIMATED_PRESERVE_ASPECT_RATIO_BASE_VAL_SLOT: &str =
|
||||
"__moliSvgAnimatedPreserveAspectRatioBaseVal";
|
||||
const SVG_ANIMATED_PRESERVE_ASPECT_RATIO_ANIM_VAL_SLOT: &str =
|
||||
"__moliSvgAnimatedPreserveAspectRatioAnimVal";
|
||||
const SVG_PRESERVE_ASPECT_RATIO_ALIGN_SLOT: &str = "__moliSvgPreserveAspectRatioAlign";
|
||||
const SVG_PRESERVE_ASPECT_RATIO_MEET_OR_SLICE_SLOT: &str =
|
||||
"__moliSvgPreserveAspectRatioMeetOrSlice";
|
||||
const SVG_PRESERVE_ASPECT_RATIO_OWNER_ELEMENT_SLOT: &str =
|
||||
"__moliSvgPreserveAspectRatioOwnerElement";
|
||||
const SVG_PRESERVE_ASPECT_RATIO_READ_ONLY_SLOT: &str = "__moliSvgPreserveAspectRatioReadOnly";
|
||||
const SVG_ANGLE_OWNER_ELEMENT_SLOT: &str = "__moliSvgAngleOwnerElement";
|
||||
const SVG_ANGLE_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgAngleOwnerAttribute";
|
||||
const SVG_ANGLE_UNIT_TYPE_SLOT: &str = "__moliSvgAngleUnitType";
|
||||
@@ -357,6 +371,22 @@ const SVG_ANGLE_TYPE_DEG: u32 = 2;
|
||||
const SVG_ANGLE_TYPE_RAD: u32 = 3;
|
||||
const SVG_ANGLE_TYPE_GRAD: u32 = 4;
|
||||
|
||||
const SVG_PRESERVE_ASPECT_RATIO_UNKNOWN: u32 = 0;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_NONE: u32 = 1;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MIN: u32 = 2;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MIN: u32 = 3;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MIN: u32 = 4;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MID: u32 = 5;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MID: u32 = 6;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MID: u32 = 7;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MIN_Y_MAX: u32 = 8;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MID_Y_MAX: u32 = 9;
|
||||
const SVG_PRESERVE_ASPECT_RATIO_X_MAX_Y_MAX: u32 = 10;
|
||||
|
||||
const SVG_MEET_OR_SLICE_UNKNOWN: u32 = 0;
|
||||
const SVG_MEET_OR_SLICE_MEET: u32 = 1;
|
||||
const SVG_MEET_OR_SLICE_SLICE: u32 = 2;
|
||||
|
||||
const SVG_MARKER_UNITS_UNKNOWN: u32 = 0;
|
||||
const SVG_MARKER_UNITS_USER_SPACE_ON_USE: u32 = 1;
|
||||
const SVG_MARKER_UNITS_STROKE_WIDTH: u32 = 2;
|
||||
@@ -805,6 +835,13 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
|
||||
"SVGAnimatedBoolean" => bindings::install_svg_animated_boolean_bindings(scope, template),
|
||||
"SVGAnimatedLength" => bindings::install_svg_animated_length_bindings(scope, template),
|
||||
"SVGAnimatedAngle" => bindings::install_svg_animated_angle_bindings(scope, template),
|
||||
"SVGAnimatedRect" => bindings::install_svg_animated_rect_bindings(scope, template),
|
||||
"SVGPreserveAspectRatio" => {
|
||||
bindings::install_svg_preserve_aspect_ratio_bindings(scope, template)
|
||||
}
|
||||
"SVGAnimatedPreserveAspectRatio" => {
|
||||
bindings::install_svg_animated_preserve_aspect_ratio_bindings(scope, template)
|
||||
}
|
||||
"SVGLengthList" => bindings::install_svg_length_list_bindings(scope, template),
|
||||
"SVGAnimatedLengthList" => {
|
||||
bindings::install_svg_animated_length_list_bindings(scope, template)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Detached SVGRect values created by SVGSVGElement.createSVGRect().
|
||||
//! Native SVGRect values, detached or bound to SVGAnimatedRect viewBox values.
|
||||
//!
|
||||
//! JSXGraph uses this method to detect SVG support. Returning a DOMRect would
|
||||
//! JSXGraph uses createSVGRect() to detect SVG support. Returning a DOMRect would
|
||||
//! pass that check but expose the wrong interface and double (not float) fields.
|
||||
|
||||
use crate::{
|
||||
native_bridge::node_runtime_and_handle_from_object_or_detached,
|
||||
native_bridge::{node_runtime_and_handle_from_object_or_detached, throw_dom_exception},
|
||||
util::{callback_data_index_value, callback_data_item, get_private_value, set_private_value},
|
||||
webidl,
|
||||
};
|
||||
@@ -14,6 +14,8 @@ const X: &str = "__moliSvgRectX";
|
||||
const Y: &str = "__moliSvgRectY";
|
||||
const WIDTH: &str = "__moliSvgRectWidth";
|
||||
const HEIGHT: &str = "__moliSvgRectHeight";
|
||||
const VIEW_BOX_OWNER: &str = "__moliSvgRectViewBoxOwner";
|
||||
const READ_ONLY: &str = "__moliSvgRectReadOnly";
|
||||
const FIELDS: &[(&str, &str)] = &[("x", X), ("y", Y), ("width", WIDTH), ("height", HEIGHT)];
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
@@ -81,6 +83,60 @@ pub(super) fn create_svg_rect<'s>(
|
||||
rv.set(rect.into());
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_view_box_rect<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
values: [f64; 4],
|
||||
read_only: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let [x, y, width, height] = values.map(|value| f64::from(value as f32));
|
||||
let rect = SvgRectObjectDeclaration::new(x, y, width, height)
|
||||
.bind(scope)
|
||||
.expect("SVGRect declaration should bind");
|
||||
set_private_value(scope, rect, VIEW_BOX_OWNER, owner.into());
|
||||
set_private_value(
|
||||
scope,
|
||||
rect,
|
||||
READ_ONLY,
|
||||
v8::Boolean::new(scope, read_only).into(),
|
||||
);
|
||||
rect
|
||||
}
|
||||
|
||||
pub(super) fn svg_view_box_rect_owner<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
rect: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
get_private_value(scope, rect, VIEW_BOX_OWNER)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn svg_view_box_rect_values<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
rect: v8::Local<'s, v8::Object>,
|
||||
) -> [f64; 4] {
|
||||
[X, Y, WIDTH, HEIGHT].map(|slot| {
|
||||
get_private_value(scope, rect, slot)
|
||||
.and_then(|value| v8::Local::<v8::Number>::try_from(value).ok())
|
||||
.map_or(0.0, |value| value.value())
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn set_svg_view_box_rect_values<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
rect: v8::Local<'s, v8::Object>,
|
||||
values: [f64; 4],
|
||||
) {
|
||||
for (slot, value) in [X, Y, WIDTH, HEIGHT].into_iter().zip(values) {
|
||||
set_private_value(
|
||||
scope,
|
||||
rect,
|
||||
slot,
|
||||
v8::Number::new(scope, f64::from(value as f32)).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn field_for_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
@@ -100,6 +156,7 @@ fn get_field<'s>(
|
||||
let Some((_, slot)) = field_for_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
super::builders::sync_svg_view_box_rect_from_owner(scope, args.this());
|
||||
if let Some(value) = get_private_value(scope, args.this(), slot) {
|
||||
rv.set(value);
|
||||
}
|
||||
@@ -113,6 +170,17 @@ fn set_field<'s>(
|
||||
let Some((name, slot)) = field_for_receiver(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
if get_private_value(scope, args.this(), READ_ONLY)
|
||||
.is_some_and(|value| value.boolean_value(scope))
|
||||
{
|
||||
throw_dom_exception(
|
||||
scope,
|
||||
"NoModificationAllowedError",
|
||||
7,
|
||||
"The SVG rectangle is read-only.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let value = match webidl::convert::<webidl::Double>(
|
||||
scope,
|
||||
args.get(0),
|
||||
@@ -130,10 +198,14 @@ fn set_field<'s>(
|
||||
webidl::throw_type_error(scope, "SVGRect value is outside the finite float range.");
|
||||
return;
|
||||
}
|
||||
// ToNumber may re-enter JS and change the owner's viewBox. Preserve those
|
||||
// changes to the other fields before applying this one-field mutation.
|
||||
super::builders::sync_svg_view_box_rect_from_owner(scope, args.this());
|
||||
set_private_value(
|
||||
scope,
|
||||
args.this(),
|
||||
slot,
|
||||
v8::Number::new(scope, f64::from(value)).into(),
|
||||
);
|
||||
super::builders::reflect_svg_view_box_rect_mutation(scope, args.this());
|
||||
}
|
||||
|
||||
@@ -1605,6 +1605,295 @@ fn svg_animated_lengths_reflect_initial_and_content_attribute_values() {
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_fit_to_view_box_attributes_are_live_and_reflected() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://svg-fit-to-view-box.test/",
|
||||
"<!doctype html><html><head></head><body></body></html>",
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const throws = (callback, expected, message) => {
|
||||
try {
|
||||
callback();
|
||||
} catch (error) {
|
||||
assert(error.name === expected, `${message}: ${error.name}`);
|
||||
return;
|
||||
}
|
||||
throw new Error(`${message}: did not throw`);
|
||||
};
|
||||
const accessor = (prototype, name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
||||
assert(typeof descriptor?.get === "function", `${prototype.constructor.name}.${name} getter`);
|
||||
assert(descriptor.set === undefined, `${prototype.constructor.name}.${name} setter`);
|
||||
assert(descriptor.enumerable && descriptor.configurable,
|
||||
`${prototype.constructor.name}.${name} flags`);
|
||||
return descriptor;
|
||||
};
|
||||
const ns = "http://www.w3.org/2000/svg";
|
||||
|
||||
for (const constructor of [
|
||||
SVGAnimatedRect,
|
||||
SVGPreserveAspectRatio,
|
||||
SVGAnimatedPreserveAspectRatio,
|
||||
]) {
|
||||
assert(typeof constructor === "function", `${constructor.name} constructor`);
|
||||
throws(() => new constructor(), "TypeError", `${constructor.name} illegal constructor`);
|
||||
}
|
||||
|
||||
const fitCases = [
|
||||
["svg", SVGSVGElement],
|
||||
["symbol", SVGSymbolElement],
|
||||
["marker", SVGMarkerElement],
|
||||
["pattern", SVGPatternElement],
|
||||
["view", SVGViewElement],
|
||||
];
|
||||
for (const [localName, constructor] of fitCases) {
|
||||
accessor(constructor.prototype, "viewBox");
|
||||
accessor(constructor.prototype, "preserveAspectRatio");
|
||||
const element = document.createElementNS(ns, localName);
|
||||
const viewBox = element.viewBox;
|
||||
const aspectRatio = element.preserveAspectRatio;
|
||||
assert(viewBox instanceof SVGAnimatedRect, `${localName}.viewBox interface`);
|
||||
assert(aspectRatio instanceof SVGAnimatedPreserveAspectRatio,
|
||||
`${localName}.preserveAspectRatio interface`);
|
||||
assert(element.viewBox === viewBox, `${localName}.viewBox SameObject`);
|
||||
assert(element.preserveAspectRatio === aspectRatio,
|
||||
`${localName}.preserveAspectRatio SameObject`);
|
||||
}
|
||||
|
||||
const imageDescriptor = accessor(
|
||||
SVGImageElement.prototype,
|
||||
"preserveAspectRatio",
|
||||
);
|
||||
assert(!("viewBox" in SVGImageElement.prototype), "image has no viewBox");
|
||||
const image = document.createElementNS(ns, "image");
|
||||
assert(image.preserveAspectRatio instanceof SVGAnimatedPreserveAspectRatio,
|
||||
"image preserveAspectRatio interface");
|
||||
assert(image.preserveAspectRatio === image.preserveAspectRatio,
|
||||
"image preserveAspectRatio SameObject");
|
||||
|
||||
const svg = document.createElementNS(ns, "svg");
|
||||
const animatedRect = svg.viewBox;
|
||||
const baseRect = animatedRect.baseVal;
|
||||
const animRect = animatedRect.animVal;
|
||||
assert(baseRect instanceof SVGRect && animRect instanceof SVGRect,
|
||||
"viewBox rectangle interfaces");
|
||||
for (const rect of [baseRect, animRect]) {
|
||||
assert(!(rect instanceof DOMRect), "viewBox uses native SVGRect, not DOMRect");
|
||||
assert(Object.getPrototypeOf(rect) === SVGRect.prototype,
|
||||
"viewBox native rectangle prototype");
|
||||
assert(Object.prototype.toString.call(rect) === "[object SVGRect]",
|
||||
"viewBox native rectangle tag");
|
||||
}
|
||||
assert(baseRect !== animRect, "viewBox base and animated rectangles are distinct");
|
||||
assert(Object.prototype.toString.call(animatedRect) === "[object SVGAnimatedRect]",
|
||||
"animated rectangle tag");
|
||||
assert(baseRect.x === 0 && baseRect.y === 0 &&
|
||||
baseRect.width === 0 && baseRect.height === 0, "initial viewBox");
|
||||
assert(!svg.hasAttribute("viewBox"), "viewBox getter does not create attribute");
|
||||
|
||||
svg.setAttribute("viewBox", "10 20 30 40");
|
||||
assert(baseRect.x === 10 && baseRect.y === 20 &&
|
||||
baseRect.width === 30 && baseRect.height === 40,
|
||||
"cached base rectangle tracks content attribute");
|
||||
assert(animRect.x === 10 && animRect.y === 20 &&
|
||||
animRect.width === 30 && animRect.height === 40,
|
||||
"cached animated rectangle tracks content attribute");
|
||||
baseRect.x = 11;
|
||||
assert(svg.getAttribute("viewBox") === "11 20 30 40",
|
||||
"base rectangle mutation reflects to viewBox");
|
||||
assert(baseRect.x === 11 && animRect.x === 11,
|
||||
"viewBox values remain synchronized after mutation");
|
||||
throws(() => { animRect.x = 12; }, "NoModificationAllowedError",
|
||||
"animated rectangle is read-only");
|
||||
svg.setAttribute("viewBox", "0 0 -1 2");
|
||||
assert(baseRect.x === 0 && baseRect.width === 0,
|
||||
"invalid viewBox uses the initial value");
|
||||
svg.removeAttribute("viewBox");
|
||||
assert(baseRect.x === 0 && baseRect.height === 0,
|
||||
"removed viewBox uses the initial value");
|
||||
|
||||
const animatedAspectRatio = svg.preserveAspectRatio;
|
||||
const baseAspectRatio = animatedAspectRatio.baseVal;
|
||||
const animAspectRatio = animatedAspectRatio.animVal;
|
||||
assert(baseAspectRatio instanceof SVGPreserveAspectRatio &&
|
||||
animAspectRatio instanceof SVGPreserveAspectRatio,
|
||||
"preserveAspectRatio value interfaces");
|
||||
assert(baseAspectRatio !== animAspectRatio,
|
||||
"preserveAspectRatio base and animated values are distinct");
|
||||
assert(baseAspectRatio.align ===
|
||||
SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID,
|
||||
"initial preserveAspectRatio align");
|
||||
assert(baseAspectRatio.meetOrSlice ===
|
||||
SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET,
|
||||
"initial preserveAspectRatio meetOrSlice");
|
||||
assert(!svg.hasAttribute("preserveAspectRatio"),
|
||||
"preserveAspectRatio getter does not create attribute");
|
||||
|
||||
svg.setAttribute("preserveAspectRatio", "xMinYMax slice");
|
||||
assert(baseAspectRatio.align ===
|
||||
SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMAX,
|
||||
"cached base aspect ratio tracks content attribute");
|
||||
assert(animAspectRatio.meetOrSlice ===
|
||||
SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE,
|
||||
"cached animated aspect ratio tracks content attribute");
|
||||
baseAspectRatio.align =
|
||||
SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN;
|
||||
baseAspectRatio.meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_SLICE;
|
||||
assert(svg.getAttribute("preserveAspectRatio") === "xMaxYMin slice",
|
||||
"base aspect ratio mutation reflects canonical syntax");
|
||||
assert(animAspectRatio.align ===
|
||||
SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMAXYMIN,
|
||||
"animated aspect ratio tracks base mutation");
|
||||
baseAspectRatio.align = SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_UNKNOWN;
|
||||
baseAspectRatio.meetOrSlice = SVGPreserveAspectRatio.SVG_MEETORSLICE_UNKNOWN;
|
||||
assert(svg.getAttribute("preserveAspectRatio") === "xMaxYMin slice",
|
||||
"unknown enumeration assignments are ignored");
|
||||
throws(() => { animAspectRatio.align = 1; }, "NoModificationAllowedError",
|
||||
"animated aspect ratio align is read-only");
|
||||
throws(() => { animAspectRatio.meetOrSlice = 1; },
|
||||
"NoModificationAllowedError", "animated aspect ratio meetOrSlice is read-only");
|
||||
svg.setAttribute("preserveAspectRatio", "invalid");
|
||||
assert(baseAspectRatio.align ===
|
||||
SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID &&
|
||||
animAspectRatio.meetOrSlice === SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET,
|
||||
"invalid preserveAspectRatio uses the initial value");
|
||||
svg.removeAttribute("preserveAspectRatio");
|
||||
assert(baseAspectRatio.align ===
|
||||
SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMIDYMID,
|
||||
"removed preserveAspectRatio uses the initial value");
|
||||
|
||||
const constants = [
|
||||
["SVG_PRESERVEASPECTRATIO_UNKNOWN", 0],
|
||||
["SVG_PRESERVEASPECTRATIO_NONE", 1],
|
||||
["SVG_PRESERVEASPECTRATIO_XMINYMIN", 2],
|
||||
["SVG_PRESERVEASPECTRATIO_XMIDYMIN", 3],
|
||||
["SVG_PRESERVEASPECTRATIO_XMAXYMIN", 4],
|
||||
["SVG_PRESERVEASPECTRATIO_XMINYMID", 5],
|
||||
["SVG_PRESERVEASPECTRATIO_XMIDYMID", 6],
|
||||
["SVG_PRESERVEASPECTRATIO_XMAXYMID", 7],
|
||||
["SVG_PRESERVEASPECTRATIO_XMINYMAX", 8],
|
||||
["SVG_PRESERVEASPECTRATIO_XMIDYMAX", 9],
|
||||
["SVG_PRESERVEASPECTRATIO_XMAXYMAX", 10],
|
||||
["SVG_MEETORSLICE_UNKNOWN", 0],
|
||||
["SVG_MEETORSLICE_MEET", 1],
|
||||
["SVG_MEETORSLICE_SLICE", 2],
|
||||
];
|
||||
for (const [name, value] of constants) {
|
||||
assert(SVGPreserveAspectRatio[name] === value, `constructor ${name}`);
|
||||
assert(SVGPreserveAspectRatio.prototype[name] === value, `prototype ${name}`);
|
||||
}
|
||||
|
||||
const viewBoxDescriptor = Object.getOwnPropertyDescriptor(
|
||||
SVGSVGElement.prototype,
|
||||
"viewBox",
|
||||
);
|
||||
throws(() => viewBoxDescriptor.get.call(document.createElementNS(ns, "rect")),
|
||||
"TypeError", "viewBox receiver brand");
|
||||
throws(() => imageDescriptor.get.call(document.createElementNS(ns, "rect")),
|
||||
"TypeError", "image preserveAspectRatio receiver brand");
|
||||
const animatedRectDescriptor = Object.getOwnPropertyDescriptor(
|
||||
SVGAnimatedRect.prototype,
|
||||
"baseVal",
|
||||
);
|
||||
throws(() => animatedRectDescriptor.get.call({}), "TypeError",
|
||||
"animated rectangle receiver brand");
|
||||
const alignDescriptor = Object.getOwnPropertyDescriptor(
|
||||
SVGPreserveAspectRatio.prototype,
|
||||
"align",
|
||||
);
|
||||
throws(() => alignDescriptor.get.call({}), "TypeError",
|
||||
"preserveAspectRatio receiver brand");
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("SVG fit-to-view-box reflection probe should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_view_box_rects_preserve_native_float_and_reentrant_setter_semantics() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://svg-view-box-native-rect.test/",
|
||||
"<!doctype html><html><head></head><body></body></html>",
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const throws = (callback, name) => {
|
||||
try { callback(); } catch (error) {
|
||||
assert(error.name === name, `expected ${name}, got ${error.name}`);
|
||||
return;
|
||||
}
|
||||
throw new Error(`expected ${name}`);
|
||||
};
|
||||
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
const fields = ["x", "y", "width", "height"];
|
||||
const values = rect => fields.map(field => rect[field]);
|
||||
svg.setAttribute("viewBox", "1.2 2.4 3.6 4.8");
|
||||
const base = svg.viewBox.baseVal;
|
||||
const anim = svg.viewBox.animVal;
|
||||
const assertValues = expected => {
|
||||
for (const rect of [base, anim]) {
|
||||
assert(JSON.stringify(values(rect)) === JSON.stringify(expected.map(Math.fround)),
|
||||
`float values: ${values(rect)}`);
|
||||
}
|
||||
};
|
||||
assertValues([1.2, 2.4, 3.6, 4.8]);
|
||||
svg.setAttribute("viewBox", "5.5 6.6 7.7 8.8");
|
||||
assertValues([5.5, 6.6, 7.7, 8.8]);
|
||||
base.x = "1.2";
|
||||
base.width = 1e20;
|
||||
assertValues([1.2, 6.6, 1e20, 8.8]);
|
||||
const reflected = svg.getAttribute("viewBox");
|
||||
assert(JSON.stringify(reflected.split(" ").map(Number).map(Math.fround)) ===
|
||||
JSON.stringify(values(base)), "float serialization round trip");
|
||||
for (const invalid of [NaN, Infinity, -Infinity, 1e40, undefined, Symbol()]) {
|
||||
throws(() => { base.x = invalid; }, "TypeError");
|
||||
assert(svg.getAttribute("viewBox") === reflected,
|
||||
"rejected float assignment preserves the attribute");
|
||||
}
|
||||
throws(() => { anim.width = 1; }, "NoModificationAllowedError");
|
||||
const domRectX = Object.getOwnPropertyDescriptor(DOMRect.prototype, "x");
|
||||
throws(() => domRectX.get.call(base), "TypeError");
|
||||
throws(() => domRectX.set.call(anim, 7), "TypeError");
|
||||
assert(svg.getAttribute("viewBox") === reflected,
|
||||
"read-only and foreign-interface setters preserve the attribute");
|
||||
|
||||
base.x = {
|
||||
valueOf() {
|
||||
svg.setAttribute("viewBox", "10 20 30 40");
|
||||
return 9;
|
||||
}
|
||||
};
|
||||
assert(svg.getAttribute("viewBox") === "9 20 30 40",
|
||||
"numeric coercion preserves reentrant changes to the other fields");
|
||||
assertValues([9, 20, 30, 40]);
|
||||
svg.setAttribute("viewBox", "0 0 1e40 1");
|
||||
assertValues([0, 0, 0, 0]);
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("native SVG viewBox rectangle probe should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_historical_interfaces_keep_current_element_constructors_only() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
|
||||
Reference in New Issue
Block a user