mirror of
https://github.com/lexmount/moli.git
synced 2026-10-07 16:01:01 +00:00
fix(svg): implement live point lists
This commit is contained in:
@@ -14,6 +14,7 @@ const DOM_POINT_Y_SLOT: &str = "__moliDomPointY";
|
||||
const DOM_POINT_Z_SLOT: &str = "__moliDomPointZ";
|
||||
const DOM_POINT_W_SLOT: &str = "__moliDomPointW";
|
||||
const DOM_POINT_RESTRICTED_NUMBER_SLOT: &str = "__moliDomPointRestrictedNumber";
|
||||
const DOM_POINT_READ_ONLY_SLOT: &str = "__moliDomPointReadOnly";
|
||||
|
||||
const DOM_MATRIX_M11_SLOT: &str = "__moliDomMatrixM11";
|
||||
const DOM_MATRIX_M12_SLOT: &str = "__moliDomMatrixM12";
|
||||
@@ -788,7 +789,15 @@ pub(in crate::context_bootstrap) fn build_dom_point_object<'s>(
|
||||
pub(in crate::context_bootstrap) fn build_svg_point_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let object = build_dom_point_object(scope, 0.0, 0.0, 0.0, 1.0);
|
||||
build_svg_point_object_with_values(scope, 0.0, 0.0)
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn build_svg_point_object_with_values<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
x: f64,
|
||||
y: f64,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let object = build_dom_point_object(scope, x, y, 0.0, 1.0);
|
||||
let restricted = v8::Boolean::new(scope, true);
|
||||
set_private_value(
|
||||
scope,
|
||||
@@ -799,6 +808,19 @@ pub(in crate::context_bootstrap) fn build_svg_point_object<'s>(
|
||||
object
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn set_svg_point_read_only<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
point: v8::Local<'s, v8::Object>,
|
||||
read_only: bool,
|
||||
) {
|
||||
set_private_value(
|
||||
scope,
|
||||
point,
|
||||
DOM_POINT_READ_ONLY_SLOT,
|
||||
v8::Boolean::new(scope, read_only).into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn build_dom_point_readonly_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
x: f64,
|
||||
@@ -910,6 +932,17 @@ fn dom_point_setter_callback<'s>(
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
if get_private_value(scope, args.this(), DOM_POINT_READ_ONLY_SLOT)
|
||||
.is_some_and(|value| value.boolean_value(scope))
|
||||
{
|
||||
throw_dom_exception(
|
||||
scope,
|
||||
"NoModificationAllowedError",
|
||||
7,
|
||||
"The SVG point is read-only.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let context = webidl::Context::member("DOMPoint", slot);
|
||||
let value = if get_private_value(scope, args.this(), DOM_POINT_RESTRICTED_NUMBER_SLOT)
|
||||
.is_some_and(|value| value.boolean_value(scope))
|
||||
@@ -933,6 +966,7 @@ fn dom_point_setter_callback<'s>(
|
||||
slot,
|
||||
v8::Number::new(scope, value).into(),
|
||||
);
|
||||
super::svg_runtime::reflect_svg_point_mutation(scope, args.this());
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
|
||||
@@ -1907,6 +1907,7 @@ pub(crate) fn finish_context_bootstrap(
|
||||
("SVGAnimatedNumber", "SVGAnimatedNumber"),
|
||||
("SVGAnimatedInteger", "SVGAnimatedInteger"),
|
||||
("SVGNumberList", "SVGNumberList"),
|
||||
("SVGPointList", "SVGPointList"),
|
||||
("SVGStringList", "SVGStringList"),
|
||||
("SVGAnimatedNumberList", "SVGAnimatedNumberList"),
|
||||
("SVGAnimatedBoolean", "SVGAnimatedBoolean"),
|
||||
|
||||
@@ -148,6 +148,10 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
interface: web_api_interfaces::SVGNumberList::DESCRIPTOR,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::SVGPointList::DESCRIPTOR,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::SVGStringList::DESCRIPTOR,
|
||||
kind: ConstructorKind::Illegal,
|
||||
|
||||
@@ -184,6 +184,51 @@ struct SvgNumberListTemplateMethodsDeclaration {
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGPointList, enumerable)]
|
||||
struct SvgPointListTemplateMethodsDeclaration {
|
||||
#[webapi(method = "clear", length = 0, callback = svg_point_list_clear_callback)]
|
||||
clear: (),
|
||||
|
||||
#[webapi(
|
||||
method = "initialize",
|
||||
length = 1,
|
||||
callback = svg_point_list_initialize_callback
|
||||
)]
|
||||
initialize: (),
|
||||
|
||||
#[webapi(method = "getItem", length = 1, callback = svg_point_list_get_item_callback)]
|
||||
get_item: (),
|
||||
|
||||
#[webapi(
|
||||
method = "insertItemBefore",
|
||||
length = 2,
|
||||
callback = svg_point_list_insert_item_before_callback
|
||||
)]
|
||||
insert_item_before: (),
|
||||
|
||||
#[webapi(
|
||||
method = "replaceItem",
|
||||
length = 2,
|
||||
callback = svg_point_list_replace_item_callback
|
||||
)]
|
||||
replace_item: (),
|
||||
|
||||
#[webapi(
|
||||
method = "removeItem",
|
||||
length = 1,
|
||||
callback = svg_point_list_remove_item_callback
|
||||
)]
|
||||
remove_item: (),
|
||||
|
||||
#[webapi(
|
||||
method = "appendItem",
|
||||
length = 1,
|
||||
callback = svg_point_list_append_item_callback
|
||||
)]
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGStringList, enumerable)]
|
||||
struct SvgStringListTemplateMethodsDeclaration {
|
||||
@@ -1180,6 +1225,19 @@ struct SvgNumberListTemplateAccessorsDeclaration {
|
||||
number_of_items: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGPointList, enumerable)]
|
||||
struct SvgPointListTemplateAccessorsDeclaration {
|
||||
#[webapi(accessor_property = "length", getter = svg_point_list_length_getter)]
|
||||
length: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "numberOfItems",
|
||||
getter = svg_point_list_length_getter
|
||||
)]
|
||||
number_of_items: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGStringList, enumerable)]
|
||||
struct SvgStringListTemplateAccessorsDeclaration {
|
||||
@@ -1485,6 +1543,24 @@ struct SvgGeometryElementPrototypeAccessorsDeclaration {
|
||||
path_length: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGAnimatedPoints", enumerable)]
|
||||
struct SvgAnimatedPointsPrototypeAccessorsDeclaration {
|
||||
#[webapi(
|
||||
accessor_property = "points",
|
||||
getter = svg_animated_points_getter,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
points: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "animatedPoints",
|
||||
getter = svg_animated_points_getter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
animated_points: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(name = "SVGFitToViewBox", enumerable)]
|
||||
struct SvgFitToViewBoxPrototypeAccessorsDeclaration {
|
||||
@@ -2189,6 +2265,13 @@ pub(super) fn install_svg_number_list_bindings<'s>(
|
||||
install_svg_value_list_bindings(scope, template, SvgListKind::Number);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_point_list_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
) {
|
||||
install_svg_value_list_bindings(scope, template, SvgListKind::Point);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_string_list_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
@@ -2283,6 +2366,10 @@ pub(super) fn install_svg_value_list_bindings<'s>(
|
||||
SvgNumberListTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
|
||||
SvgNumberListTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
|
||||
}
|
||||
SvgListKind::Point => {
|
||||
SvgPointListTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
|
||||
SvgPointListTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2607,6 +2694,11 @@ pub(super) fn install_svg_element_accessor_bindings<'s>(
|
||||
scope, prototype,
|
||||
);
|
||||
}
|
||||
"SVGPolygonElement" | "SVGPolylineElement" => {
|
||||
SvgAnimatedPointsPrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, prototype,
|
||||
);
|
||||
}
|
||||
"SVGMarkerElement" => {
|
||||
SvgMarkerElementPrototypeAccessorsDeclaration::initialize_prototype_template(
|
||||
scope, prototype,
|
||||
|
||||
@@ -298,6 +298,33 @@ struct SvgNumberListObjectDeclaration<'scope> {
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::SVGPointList, own_to_string_tag = "SVGPointList")]
|
||||
struct SvgPointListObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_POINT_LIST_ITEMS_SLOT)]
|
||||
items: Vec<v8::Local<'scope, v8::Value>>,
|
||||
#[webapi(slot = SVG_VALUE_LIST_READ_ONLY_SLOT)]
|
||||
read_only: bool,
|
||||
#[webapi(method, callback = svg_point_list_clear_callback, length = 0)]
|
||||
clear: (),
|
||||
#[webapi(method, callback = svg_point_list_initialize_callback, length = 1)]
|
||||
initialize: (),
|
||||
#[webapi(method, callback = svg_point_list_get_item_callback, length = 1)]
|
||||
get_item: (),
|
||||
#[webapi(
|
||||
method,
|
||||
callback = svg_point_list_insert_item_before_callback,
|
||||
length = 2
|
||||
)]
|
||||
insert_item_before: (),
|
||||
#[webapi(method, callback = svg_point_list_replace_item_callback, length = 2)]
|
||||
replace_item: (),
|
||||
#[webapi(method, callback = svg_point_list_remove_item_callback, length = 1)]
|
||||
remove_item: (),
|
||||
#[webapi(method, callback = svg_point_list_append_item_callback, length = 1)]
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::SVGStringList, own_to_string_tag = "SVGStringList")]
|
||||
struct SvgStringListObjectDeclaration<'scope> {
|
||||
@@ -521,6 +548,7 @@ pub(super) fn build_svg_animated_value_list_for_attribute<'s>(
|
||||
let object = match kind {
|
||||
SvgListKind::Length => build_svg_animated_length_list(scope),
|
||||
SvgListKind::Number => build_svg_animated_number_list(scope),
|
||||
SvgListKind::Point => unreachable!("SVGPointList is not an animated wrapper"),
|
||||
};
|
||||
sync_svg_animated_value_list_from_owner_attribute(scope, object, owner, attribute, kind);
|
||||
if let Some(base_val) = svg_animated_value_list_member(scope, object, "baseVal", kind) {
|
||||
@@ -951,6 +979,32 @@ pub(super) fn build_svg_number_list<'s>(
|
||||
object
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_point_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
read_only: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let template = v8::ObjectTemplate::new(scope);
|
||||
configure_svg_value_list_indexed_property_handler(template);
|
||||
let object = template
|
||||
.new_instance(scope)
|
||||
.expect("SVGPointList object template should instantiate");
|
||||
SvgPointListObjectDeclaration::new(Vec::new(), read_only)
|
||||
.bind_into(scope, object)
|
||||
.expect("SVGPointList declaration should bind");
|
||||
object
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_point_list_for_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
read_only: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let list = build_svg_point_list(scope, read_only);
|
||||
set_svg_value_list_owner_attribute(scope, list, owner, "points");
|
||||
sync_svg_value_list_from_owner_attribute(scope, list, SvgListKind::Point);
|
||||
list
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_string_list_for_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
@@ -1834,16 +1888,22 @@ pub(super) fn svg_value_list_item_or_throw<'s>(
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
kind: SvgListKind,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let (brand_slot, interface) = match kind {
|
||||
SvgListKind::Length => (SVG_LENGTH_VALUE_SLOT, "SVGLength"),
|
||||
SvgListKind::Number => (SVG_NUMBER_VALUE_SLOT, "SVGNumber"),
|
||||
};
|
||||
let object = v8::Local::<v8::Object>::try_from(value).ok();
|
||||
if let Some(object) = object
|
||||
&& get_private_value(scope, object, brand_slot).is_some()
|
||||
{
|
||||
return Some(object);
|
||||
let valid = object.is_some_and(|object| match kind {
|
||||
SvgListKind::Length => get_private_value(scope, object, SVG_LENGTH_VALUE_SLOT).is_some(),
|
||||
SvgListKind::Number => get_private_value(scope, object, SVG_NUMBER_VALUE_SLOT).is_some(),
|
||||
SvgListKind::Point => {
|
||||
dom_point_clone_data(scope, object).is_some_and(|(mutable, _)| mutable)
|
||||
}
|
||||
});
|
||||
if valid {
|
||||
return object;
|
||||
}
|
||||
let interface = match kind {
|
||||
SvgListKind::Length => "SVGLength",
|
||||
SvgListKind::Number => "SVGNumber",
|
||||
SvgListKind::Point => "DOMPoint",
|
||||
};
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("Argument 1 can not be converted to {interface}"),
|
||||
@@ -1867,6 +1927,7 @@ pub(super) fn svg_value_list_items<'s>(
|
||||
let slot = match kind {
|
||||
SvgListKind::Length => SVG_LENGTH_LIST_ITEMS_SLOT,
|
||||
SvgListKind::Number => SVG_NUMBER_LIST_ITEMS_SLOT,
|
||||
SvgListKind::Point => SVG_POINT_LIST_ITEMS_SLOT,
|
||||
};
|
||||
get_private_value(scope, object, slot)
|
||||
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
||||
@@ -1882,12 +1943,23 @@ pub(super) fn set_svg_value_list_items<'s>(
|
||||
let slot = match kind {
|
||||
SvgListKind::Length => SVG_LENGTH_LIST_ITEMS_SLOT,
|
||||
SvgListKind::Number => SVG_NUMBER_LIST_ITEMS_SLOT,
|
||||
SvgListKind::Point => SVG_POINT_LIST_ITEMS_SLOT,
|
||||
};
|
||||
if let Some(current) = get_private_value(scope, object, slot)
|
||||
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
||||
{
|
||||
detach_svg_value_list_items(scope, current);
|
||||
}
|
||||
if matches!(kind, SvgListKind::Point) && svg_value_list_is_read_only(scope, object) {
|
||||
for index in 0..items.length() {
|
||||
if let Some(point) = items
|
||||
.get_index(scope, index)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
{
|
||||
set_svg_point_read_only(scope, point, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
attach_svg_value_list_items(scope, object, items);
|
||||
set_private_value(scope, object, slot, items.into());
|
||||
}
|
||||
@@ -2060,6 +2132,11 @@ pub(super) fn build_svg_value_list_item_values_from_attribute<'s>(
|
||||
.into_iter()
|
||||
.map(|value| build_svg_number(scope, value))
|
||||
.collect(),
|
||||
SvgListKind::Point => svg_geometry::parse_point_list(raw)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|(x, y)| build_svg_point_object_with_values(scope, x, y))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2160,6 +2237,14 @@ pub(super) fn serialize_svg_value_list_item<'s>(
|
||||
let value = svg_number_slot(scope, item, SVG_NUMBER_VALUE_SLOT)?;
|
||||
Some(svg_geometry::serialize_number(value))
|
||||
}
|
||||
SvgListKind::Point => {
|
||||
let point = dom_point_init_from_object(scope, item);
|
||||
Some(format!(
|
||||
"{} {}",
|
||||
svg_geometry::serialize_number(point.x),
|
||||
svg_geometry::serialize_number(point.y)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,11 @@ const SVG_TEXT_POSITIONING_LIST_ATTRIBUTES: &[(&str, &str, SvgListKind)] = &[
|
||||
),
|
||||
];
|
||||
|
||||
const SVG_ANIMATED_POINTS_PROPERTIES: &[(&str, &str, bool)] = &[
|
||||
("points", SVG_POINTS_SLOT, false),
|
||||
("animatedPoints", SVG_ANIMATED_POINTS_SLOT, true),
|
||||
];
|
||||
|
||||
fn require_svg_receiver<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
@@ -954,6 +959,56 @@ pub(super) fn svg_geometry_path_length_getter<'s>(
|
||||
rv.set(value.into());
|
||||
}
|
||||
|
||||
pub(super) fn svg_animated_points_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some((name, cache_slot, read_only)) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_ANIMATED_POINTS_PROPERTIES,
|
||||
"SVGAnimatedPoints properties",
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let owner = args.this();
|
||||
let Ok((runtime_ptr, handle)) =
|
||||
crate::native_bridge::node_runtime_and_handle_from_object_or_detached(scope, owner)
|
||||
else {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGAnimatedPoints.{name} called on incompatible receiver."),
|
||||
);
|
||||
return;
|
||||
};
|
||||
let is_animated_points = unsafe { &*runtime_ptr }
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(|node| node.as_element())
|
||||
.is_some_and(|element| {
|
||||
element.is_svg_element("polygon") || element.is_svg_element("polyline")
|
||||
});
|
||||
if !is_animated_points {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGAnimatedPoints.{name} called on incompatible receiver."),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if let Some(value) = get_private_value(scope, owner, cache_slot) {
|
||||
if let Ok(list) = v8::Local::<v8::Object>::try_from(value) {
|
||||
sync_svg_value_list_from_owner_attribute(scope, list, SvgListKind::Point);
|
||||
}
|
||||
rv.set(value);
|
||||
return;
|
||||
}
|
||||
let list = build_svg_point_list_for_attribute(scope, owner, read_only);
|
||||
set_private_value(scope, owner, cache_slot, list.into());
|
||||
rv.set(list.into());
|
||||
}
|
||||
|
||||
pub(super) fn svg_text_content_text_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -2458,6 +2513,7 @@ fn svg_value_list_metadata(kind: SvgListKind) -> (&'static str, &'static str) {
|
||||
match kind {
|
||||
SvgListKind::Length => (SVG_LENGTH_LIST_ITEMS_SLOT, "SVGLengthList"),
|
||||
SvgListKind::Number => (SVG_NUMBER_LIST_ITEMS_SLOT, "SVGNumberList"),
|
||||
SvgListKind::Point => (SVG_POINT_LIST_ITEMS_SLOT, "SVGPointList"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2469,6 +2525,8 @@ fn svg_value_list_kind<'s>(
|
||||
Some(SvgListKind::Length)
|
||||
} else if web_api_interfaces::SVGNumberList::is_instance(scope, list) {
|
||||
Some(SvgListKind::Number)
|
||||
} else if get_private_value(scope, list, SVG_POINT_LIST_ITEMS_SLOT).is_some() {
|
||||
Some(SvgListKind::Point)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -2498,6 +2556,45 @@ fn require_svg_value_list_items<'s>(
|
||||
Some(svg_value_list_items(scope, list, kind))
|
||||
}
|
||||
|
||||
macro_rules! define_svg_point_list_callback {
|
||||
($name:ident, $callback:ident) => {
|
||||
pub(super) fn $name<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
$callback(scope, args, rv, SvgListKind::Point);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_svg_point_list_callback!(svg_point_list_length_getter, svg_value_list_length_getter);
|
||||
define_svg_point_list_callback!(svg_point_list_clear_callback, svg_value_list_clear_callback);
|
||||
define_svg_point_list_callback!(
|
||||
svg_point_list_initialize_callback,
|
||||
svg_value_list_initialize_callback
|
||||
);
|
||||
define_svg_point_list_callback!(
|
||||
svg_point_list_get_item_callback,
|
||||
svg_value_list_get_item_callback
|
||||
);
|
||||
define_svg_point_list_callback!(
|
||||
svg_point_list_insert_item_before_callback,
|
||||
svg_value_list_insert_item_before_callback
|
||||
);
|
||||
define_svg_point_list_callback!(
|
||||
svg_point_list_replace_item_callback,
|
||||
svg_value_list_replace_item_callback
|
||||
);
|
||||
define_svg_point_list_callback!(
|
||||
svg_point_list_remove_item_callback,
|
||||
svg_value_list_remove_item_callback
|
||||
);
|
||||
define_svg_point_list_callback!(
|
||||
svg_point_list_append_item_callback,
|
||||
svg_value_list_append_item_callback
|
||||
);
|
||||
|
||||
pub(super) fn svg_length_list_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use super::{
|
||||
build_dom_point_object, build_dom_rect_object, optional_dom_point_init_arg,
|
||||
build_dom_point_object, build_dom_rect_object,
|
||||
geometry_runtime::{
|
||||
build_svg_point_object_with_values, dom_point_clone_data, dom_point_init_from_object,
|
||||
set_svg_point_read_only,
|
||||
},
|
||||
optional_dom_point_init_arg,
|
||||
selection::{selection_clear, selection_dispatch_change, selection_has_range},
|
||||
selection_value_for_window,
|
||||
};
|
||||
@@ -85,6 +90,9 @@ const SVG_ANIMATED_INTEGER_PROPERTY_INDEX_SLOT: &str = "__moliSvgAnimatedInteger
|
||||
const SVG_ANIMATED_NUMBER_LIST_BASE_VAL_SLOT: &str = "__moliSvgAnimatedNumberListBaseVal";
|
||||
const SVG_ANIMATED_NUMBER_LIST_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedNumberListAnimVal";
|
||||
const SVG_NUMBER_LIST_ITEMS_SLOT: &str = "__moliSvgNumberListItems";
|
||||
const SVG_POINT_LIST_ITEMS_SLOT: &str = "__moliSvgPointListItems";
|
||||
const SVG_POINTS_SLOT: &str = "__moliSvgPoints";
|
||||
const SVG_ANIMATED_POINTS_SLOT: &str = "__moliSvgAnimatedPoints";
|
||||
const SVG_STRING_LIST_ITEMS_SLOT: &str = "__moliSvgStringListItems";
|
||||
const SVG_STRING_LIST_OWNER_ELEMENT_SLOT: &str = "__moliSvgStringListOwnerElement";
|
||||
const SVG_STRING_LIST_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgStringListOwnerAttribute";
|
||||
@@ -163,6 +171,7 @@ const SVG_TEST_STRING_LIST_ATTRIBUTES: &[(&str, &str)] = &[
|
||||
enum SvgListKind {
|
||||
Length,
|
||||
Number,
|
||||
Point,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -1289,6 +1298,7 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
|
||||
"SVGAnimatedNumber" => bindings::install_svg_animated_number_bindings(scope, template),
|
||||
"SVGAnimatedInteger" => bindings::install_svg_animated_integer_bindings(scope, template),
|
||||
"SVGNumberList" => bindings::install_svg_number_list_bindings(scope, template),
|
||||
"SVGPointList" => bindings::install_svg_point_list_bindings(scope, template),
|
||||
"SVGStringList" => bindings::install_svg_string_list_bindings(scope, template),
|
||||
"SVGAnimatedNumberList" => {
|
||||
bindings::install_svg_animated_number_list_bindings(scope, template)
|
||||
@@ -1316,3 +1326,10 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn reflect_svg_point_mutation<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
point: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
builders::reflect_svg_value_list_item_to_owner_list(scope, point, SvgListKind::Point);
|
||||
}
|
||||
|
||||
+148
@@ -3102,3 +3102,151 @@ fn svg_value_lists_enforce_item_types_indices_and_read_only_anim_values() {
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_point_lists_are_live_mutable_and_clear_invalid_content() {
|
||||
let mut vm = new_storage_test_vm("https://svg-point-list-semantics.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const errorName = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error.name;
|
||||
}
|
||||
};
|
||||
const ns = "http://www.w3.org/2000/svg";
|
||||
const polygon = document.createElementNS(ns, "polygon");
|
||||
const polyline = document.createElementNS(ns, "polyline");
|
||||
const svg = document.createElementNS(ns, "svg");
|
||||
|
||||
assert(typeof SVGPointList === "function", "constructor exposed");
|
||||
assert(errorName(() => new SVGPointList()) === "TypeError",
|
||||
"illegal constructor");
|
||||
for (const [constructor, element] of [
|
||||
[SVGPolygonElement, polygon],
|
||||
[SVGPolylineElement, polyline],
|
||||
]) {
|
||||
const pointsDescriptor = Object.getOwnPropertyDescriptor(
|
||||
constructor.prototype,
|
||||
"points",
|
||||
);
|
||||
const animatedDescriptor = Object.getOwnPropertyDescriptor(
|
||||
constructor.prototype,
|
||||
"animatedPoints",
|
||||
);
|
||||
assert(typeof pointsDescriptor.get === "function" &&
|
||||
pointsDescriptor.set === undefined, `${constructor.name}.points descriptor`);
|
||||
assert(typeof animatedDescriptor.get === "function" &&
|
||||
animatedDescriptor.set === undefined,
|
||||
`${constructor.name}.animatedPoints descriptor`);
|
||||
assert(pointsDescriptor.enumerable && pointsDescriptor.configurable &&
|
||||
animatedDescriptor.enumerable && animatedDescriptor.configurable,
|
||||
`${constructor.name} descriptor flags`);
|
||||
assert(element.points instanceof SVGPointList &&
|
||||
element.animatedPoints instanceof SVGPointList,
|
||||
`${constructor.name} point list interfaces`);
|
||||
assert(element.points === element.points &&
|
||||
element.animatedPoints === element.animatedPoints &&
|
||||
element.points !== element.animatedPoints,
|
||||
`${constructor.name} SameObject lists`);
|
||||
}
|
||||
|
||||
polygon.setAttribute("points", "0,0 100,0 100,100 0,100");
|
||||
const points = polygon.points;
|
||||
const animatedPoints = polygon.animatedPoints;
|
||||
assert(Object.prototype.toString.call(points) === "[object SVGPointList]",
|
||||
"point list tag");
|
||||
assert(points.length === 4 && points.numberOfItems === 4,
|
||||
"valid content points");
|
||||
assert(points.getItem(1) === points[1] && points[1] instanceof DOMPoint &&
|
||||
points[1] instanceof SVGPoint && points[1].x === 100 && points[1].y === 0,
|
||||
"indexed point identity and values");
|
||||
|
||||
polygon.setAttribute("points", "0,0 100,0 INVALID");
|
||||
assert(points.numberOfItems === 0,
|
||||
"invalid token clears the whole point list");
|
||||
polygon.setAttribute("points", "0,0 100,0 20");
|
||||
assert(points.numberOfItems === 2,
|
||||
"missing final y coordinate truncates the point list");
|
||||
polygon.setAttribute("points", "0,0 100,0 20,");
|
||||
assert(points.numberOfItems === 2,
|
||||
"trailing comma with missing y truncates the point list");
|
||||
|
||||
polygon.setAttribute("points", "0,0 10,20");
|
||||
const first = points[0];
|
||||
first.x = 2;
|
||||
assert(polygon.getAttribute("points") === "2 0 10 20",
|
||||
"point coordinate mutation reflects to content");
|
||||
|
||||
const point = svg.createSVGPoint();
|
||||
point.x = 5;
|
||||
point.y = 6;
|
||||
points.clear();
|
||||
assert(points.length === 0 && polygon.getAttribute("points") === "",
|
||||
"clear reflects an empty list");
|
||||
assert(points.initialize(point) === point && points[0] === point,
|
||||
"initialize keeps point identity");
|
||||
assert(polygon.getAttribute("points") === "5 6", "initialize reflection");
|
||||
point.x = 7;
|
||||
assert(polygon.getAttribute("points") === "7 6", "owned point stays live");
|
||||
|
||||
const second = svg.createSVGPoint();
|
||||
second.x = 8;
|
||||
second.y = 9;
|
||||
assert(points.appendItem(second) === second && points.length === 2,
|
||||
"appendItem");
|
||||
const third = svg.createSVGPoint();
|
||||
third.x = 10;
|
||||
third.y = 11;
|
||||
assert(points.insertItemBefore(third, 1) === third && points.length === 3,
|
||||
"insertItemBefore");
|
||||
const replacement = svg.createSVGPoint();
|
||||
replacement.x = 12;
|
||||
replacement.y = 13;
|
||||
assert(points.replaceItem(replacement, 0) === replacement &&
|
||||
points[0] === replacement,
|
||||
"replaceItem");
|
||||
assert(points.removeItem(1) === third && points.length === 2,
|
||||
"removeItem");
|
||||
points[0] = point;
|
||||
assert(points[0] === point, "indexed setter");
|
||||
|
||||
for (const invalid of [1, "point", polygon, null]) {
|
||||
assert(errorName(() => points.appendItem(invalid)) === "TypeError",
|
||||
"point item type enforcement");
|
||||
}
|
||||
assert(errorName(() => points.getItem(99)) === "IndexSizeError",
|
||||
"getItem bounds");
|
||||
|
||||
polygon.setAttribute("points", "1,2 3,4");
|
||||
assert(animatedPoints.length === 2 && animatedPoints[1].x === 3,
|
||||
"animatedPoints live synchronization");
|
||||
assert(errorName(() => animatedPoints.clear()) === "NoModificationAllowedError",
|
||||
"animatedPoints list is read-only");
|
||||
assert(errorName(() => { animatedPoints[0].x = 9; }) ===
|
||||
"NoModificationAllowedError", "animated point is read-only");
|
||||
assert(errorName(() => SVGPointList.prototype.clear.call({})) === "TypeError",
|
||||
"point list receiver brand");
|
||||
|
||||
const pointsDescriptor = Object.getOwnPropertyDescriptor(
|
||||
SVGPolygonElement.prototype,
|
||||
"points",
|
||||
);
|
||||
assert(errorName(() => pointsDescriptor.get.call(svg)) === "TypeError",
|
||||
"animated points receiver brand");
|
||||
return "ok";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("SVG point list semantics probe should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
@@ -459,6 +459,7 @@ interfaces! {
|
||||
SVGTextPositioningElement: SVGTextContentElement;
|
||||
SVGTitleElement: SVGElement;
|
||||
SVGUnitTypes;
|
||||
SVGPointList;
|
||||
SVGStringList;
|
||||
SVGTransform;
|
||||
SVGTransformList;
|
||||
|
||||
@@ -82,6 +82,19 @@ pub fn parse_number_list(raw: &str) -> Option<Vec<f64>> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn parse_point_list(raw: &str) -> Option<Vec<(f64, f64)>> {
|
||||
let values = parse_number_list(raw).or_else(|| {
|
||||
let raw = raw.trim_end();
|
||||
parse_number_list(raw.strip_suffix(',')?)
|
||||
})?;
|
||||
Some(
|
||||
values
|
||||
.chunks_exact(2)
|
||||
.map(|point| (point[0], point[1]))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn svg_length(length: SvgTypesLength) -> SvgLength {
|
||||
SvgLength {
|
||||
value: length.number,
|
||||
|
||||
+20
-2
@@ -12,6 +12,7 @@ pub use geometry::{
|
||||
};
|
||||
pub use length::{
|
||||
SvgLength, SvgLengthUnit, parse_length, parse_length_list, parse_number, parse_number_list,
|
||||
parse_point_list,
|
||||
};
|
||||
pub use matrix::{SvgMatrixComponents, serialize_number};
|
||||
pub use transform::{
|
||||
@@ -26,14 +27,31 @@ mod tests {
|
||||
SvgMatrixComponents, SvgTransform, SvgTransformKind, bounding_box_for_element,
|
||||
bounding_box_for_segments, bounding_box_for_transformed_element,
|
||||
consolidate_transform_matrices, is_point_in_fill, parse_length, parse_length_list,
|
||||
parse_number, parse_number_list, parse_transform_attribute, point_at_length,
|
||||
segments_for_element, serialize_number, serialize_transform_list,
|
||||
parse_number, parse_number_list, parse_point_list, parse_transform_attribute,
|
||||
point_at_length, segments_for_element, serialize_number, serialize_transform_list,
|
||||
};
|
||||
|
||||
fn path_segments(raw: &str) -> Vec<SvgGeometrySegment> {
|
||||
segments_for_element(SvgGeometryElement::Path { d: raw.to_owned() })
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_list_parser_rejects_invalid_tokens_and_truncates_incomplete_pairs() {
|
||||
assert_eq!(
|
||||
parse_point_list("0,0 100,0 100,100 0,100"),
|
||||
Some(vec![(0.0, 0.0), (100.0, 0.0), (100.0, 100.0), (0.0, 100.0)])
|
||||
);
|
||||
assert_eq!(
|
||||
parse_point_list("0,0 100,0 20"),
|
||||
Some(vec![(0.0, 0.0), (100.0, 0.0)])
|
||||
);
|
||||
assert_eq!(
|
||||
parse_point_list("0,0 100,0 20,"),
|
||||
Some(vec![(0.0, 0.0), (100.0, 0.0)])
|
||||
);
|
||||
assert_eq!(parse_point_list("0,0 100,0 INVALID"), None);
|
||||
}
|
||||
|
||||
fn polyline_segments(raw: &str) -> Vec<SvgGeometrySegment> {
|
||||
segments_for_element(SvgGeometryElement::Polyline {
|
||||
points: raw.to_owned(),
|
||||
|
||||
Reference in New Issue
Block a user