mirror of
https://github.com/lexmount/moli.git
synced 2026-10-08 16:01:05 +00:00
fix(svg): reflect conditional processing string lists
This commit is contained in:
@@ -1734,6 +1734,7 @@ pub(crate) fn finish_context_bootstrap(
|
||||
("SVGAnimatedLengthList", "SVGAnimatedLengthList"),
|
||||
("SVGAnimatedNumber", "SVGAnimatedNumber"),
|
||||
("SVGNumberList", "SVGNumberList"),
|
||||
("SVGStringList", "SVGStringList"),
|
||||
("SVGAnimatedNumberList", "SVGAnimatedNumberList"),
|
||||
("SVGAnimatedBoolean", "SVGAnimatedBoolean"),
|
||||
("SVGAnimatedEnumeration", "SVGAnimatedEnumeration"),
|
||||
|
||||
@@ -120,6 +120,10 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
interface: web_api_interfaces::SVGNumberList::DESCRIPTOR,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::SVGStringList::DESCRIPTOR,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::SVGAnimatedNumberList::DESCRIPTOR,
|
||||
kind: ConstructorKind::Illegal,
|
||||
|
||||
@@ -148,6 +148,51 @@ struct SvgNumberListTemplateMethodsDeclaration {
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGStringList, enumerable)]
|
||||
struct SvgStringListTemplateMethodsDeclaration {
|
||||
#[webapi(method = "clear", length = 0, callback = svg_string_list_clear_callback)]
|
||||
clear: (),
|
||||
|
||||
#[webapi(
|
||||
method = "initialize",
|
||||
length = 1,
|
||||
callback = svg_string_list_initialize_callback
|
||||
)]
|
||||
initialize: (),
|
||||
|
||||
#[webapi(method = "getItem", length = 1, callback = svg_string_list_get_item_callback)]
|
||||
get_item: (),
|
||||
|
||||
#[webapi(
|
||||
method = "insertItemBefore",
|
||||
length = 2,
|
||||
callback = svg_string_list_insert_item_before_callback
|
||||
)]
|
||||
insert_item_before: (),
|
||||
|
||||
#[webapi(
|
||||
method = "replaceItem",
|
||||
length = 2,
|
||||
callback = svg_string_list_replace_item_callback
|
||||
)]
|
||||
replace_item: (),
|
||||
|
||||
#[webapi(
|
||||
method = "removeItem",
|
||||
length = 1,
|
||||
callback = svg_string_list_remove_item_callback
|
||||
)]
|
||||
remove_item: (),
|
||||
|
||||
#[webapi(
|
||||
method = "appendItem",
|
||||
length = 1,
|
||||
callback = svg_string_list_append_item_callback
|
||||
)]
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGTransformList, enumerable)]
|
||||
struct SvgTransformListTemplateMethodsDeclaration {
|
||||
@@ -721,6 +766,19 @@ struct SvgNumberListTemplateAccessorsDeclaration {
|
||||
number_of_items: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGStringList, enumerable)]
|
||||
struct SvgStringListTemplateAccessorsDeclaration {
|
||||
#[webapi(accessor_property = "length", getter = svg_string_list_length_getter)]
|
||||
length: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "numberOfItems",
|
||||
getter = svg_string_list_length_getter
|
||||
)]
|
||||
number_of_items: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(interface = web_api_interfaces::SVGAnimatedTransformList, enumerable)]
|
||||
struct SvgAnimatedTransformListTemplateAccessorsDeclaration {
|
||||
@@ -844,6 +902,20 @@ struct SvgElementPrototypeAccessorsDeclaration {
|
||||
struct SvgGraphicsElementPrototypeAccessorsDeclaration {
|
||||
#[webapi(accessor_property = "transform", getter = svg_graphics_transform_getter)]
|
||||
transform: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "requiredExtensions",
|
||||
getter = svg_graphics_test_string_list_getter,
|
||||
data = callback_data_index_value(scope, 0)
|
||||
)]
|
||||
required_extensions: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property = "systemLanguage",
|
||||
getter = svg_graphics_test_string_list_getter,
|
||||
data = callback_data_index_value(scope, 1)
|
||||
)]
|
||||
system_language: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
@@ -1171,6 +1243,15 @@ pub(super) fn install_svg_number_list_bindings<'s>(
|
||||
install_svg_value_list_bindings(scope, template, SvgListKind::Number);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_string_list_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
) {
|
||||
let proto = template.prototype_template(scope);
|
||||
SvgStringListTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto);
|
||||
SvgStringListTemplateMethodsDeclaration::initialize_prototype_template(scope, proto);
|
||||
}
|
||||
|
||||
pub(super) fn install_svg_animated_enumeration_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
|
||||
@@ -215,6 +215,13 @@ struct SvgNumberListObjectDeclaration<'scope> {
|
||||
append_item: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::SVGStringList, own_to_string_tag = "SVGStringList")]
|
||||
struct SvgStringListObjectDeclaration<'scope> {
|
||||
#[webapi(slot = SVG_STRING_LIST_ITEMS_SLOT)]
|
||||
items: Vec<v8::Local<'scope, v8::Value>>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::SVGTransformList, own_to_string_tag = "SVGTransformList")]
|
||||
struct SvgTransformListObjectDeclaration<'scope> {
|
||||
@@ -668,6 +675,182 @@ pub(super) fn build_svg_number_list<'s>(
|
||||
.expect("SVGNumberList declaration should bind")
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_string_list_for_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
owner: v8::Local<'s, v8::Object>,
|
||||
attribute: &str,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let template = v8::ObjectTemplate::new(scope);
|
||||
configure_svg_string_list_indexed_property_handler(template);
|
||||
let object = template
|
||||
.new_instance(scope)
|
||||
.expect("SVGStringList object template should instantiate");
|
||||
SvgStringListObjectDeclaration::new(Vec::new())
|
||||
.bind_into(scope, object)
|
||||
.expect("SVGStringList declaration should bind");
|
||||
set_private_value(
|
||||
scope,
|
||||
object,
|
||||
SVG_STRING_LIST_OWNER_ELEMENT_SLOT,
|
||||
owner.into(),
|
||||
);
|
||||
set_private_value(
|
||||
scope,
|
||||
object,
|
||||
SVG_STRING_LIST_OWNER_ATTRIBUTE_SLOT,
|
||||
v8_string(scope, attribute)
|
||||
.unwrap_or_else(|| v8str(scope, ""))
|
||||
.into(),
|
||||
);
|
||||
sync_svg_string_list_from_owner_attribute(scope, object);
|
||||
object
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_items<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Array>> {
|
||||
get_private_value(scope, list, SVG_STRING_LIST_ITEMS_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
||||
}
|
||||
|
||||
pub(super) fn set_svg_string_list_items<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
items: v8::Local<'s, v8::Array>,
|
||||
) {
|
||||
set_private_value(scope, list, SVG_STRING_LIST_ITEMS_SLOT, items.into());
|
||||
}
|
||||
|
||||
pub(super) fn sync_svg_string_list_from_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some((owner, attribute)) = svg_string_list_owner_attribute(scope, list) else {
|
||||
return;
|
||||
};
|
||||
let raw = svg_owner_attribute_value(scope, owner, &attribute);
|
||||
let synced_value = get_private_value(scope, list, SVG_STRING_LIST_SYNCED_ATTRIBUTE_VALUE_SLOT)
|
||||
.and_then(|value| value.to_string(scope))
|
||||
.map(|value| value.to_rust_string_lossy(scope));
|
||||
let synced_present =
|
||||
get_private_value(scope, list, SVG_STRING_LIST_SYNCED_ATTRIBUTE_PRESENT_SLOT)
|
||||
.map(|value| value.is_true());
|
||||
if synced_present == Some(raw.is_some())
|
||||
&& synced_value.as_deref() == Some(raw.as_deref().unwrap_or_default())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let values = parse_svg_string_list_attribute(&attribute, raw.as_deref());
|
||||
let length = i32::try_from(values.len()).unwrap_or(i32::MAX);
|
||||
let items = v8::Array::new(scope, length);
|
||||
for (index, value) in values.into_iter().enumerate() {
|
||||
let Ok(index) = u32::try_from(index) else {
|
||||
break;
|
||||
};
|
||||
if let Some(value) = v8_string(scope, &value) {
|
||||
let _ = items.set_index(scope, index, value.into());
|
||||
}
|
||||
}
|
||||
set_svg_string_list_items(scope, list, items);
|
||||
set_svg_string_list_synced_attribute(scope, list, raw.as_deref());
|
||||
}
|
||||
|
||||
pub(super) fn reflect_svg_string_list_to_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
let Some((owner, attribute)) = svg_string_list_owner_attribute(scope, list) else {
|
||||
return;
|
||||
};
|
||||
let value = serialize_svg_string_list(scope, list, &attribute);
|
||||
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);
|
||||
set_svg_string_list_synced_attribute(scope, list, Some(&value));
|
||||
}
|
||||
|
||||
fn svg_string_list_owner_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) -> Option<(v8::Local<'s, v8::Object>, String)> {
|
||||
let owner = get_private_value(scope, list, SVG_STRING_LIST_OWNER_ELEMENT_SLOT)
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
||||
let attribute = get_private_value(scope, list, SVG_STRING_LIST_OWNER_ATTRIBUTE_SLOT)?
|
||||
.to_string(scope)?
|
||||
.to_rust_string_lossy(scope);
|
||||
(!attribute.is_empty()).then_some((owner, attribute))
|
||||
}
|
||||
|
||||
fn set_svg_string_list_synced_attribute<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
raw: Option<&str>,
|
||||
) {
|
||||
set_private_value(
|
||||
scope,
|
||||
list,
|
||||
SVG_STRING_LIST_SYNCED_ATTRIBUTE_VALUE_SLOT,
|
||||
v8_string(scope, raw.unwrap_or_default())
|
||||
.unwrap_or_else(|| v8str(scope, ""))
|
||||
.into(),
|
||||
);
|
||||
set_private_value(
|
||||
scope,
|
||||
list,
|
||||
SVG_STRING_LIST_SYNCED_ATTRIBUTE_PRESENT_SLOT,
|
||||
v8::Boolean::new(scope, raw.is_some()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn parse_svg_string_list_attribute(attribute: &str, raw: Option<&str>) -> Vec<String> {
|
||||
let Some(raw) = raw else {
|
||||
return Vec::new();
|
||||
};
|
||||
if attribute == "systemLanguage" {
|
||||
return raw
|
||||
.split(',')
|
||||
.map(|token| token.trim_matches(is_html_ascii_whitespace).to_owned())
|
||||
.collect();
|
||||
}
|
||||
raw.split(is_html_ascii_whitespace)
|
||||
.filter(|token| !token.is_empty())
|
||||
.map(str::to_owned)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn serialize_svg_string_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
attribute: &str,
|
||||
) -> String {
|
||||
let Some(items) = svg_string_list_items(scope, list) else {
|
||||
return String::new();
|
||||
};
|
||||
let values = (0..items.length())
|
||||
.filter_map(|index| items.get_index(scope, index))
|
||||
.filter_map(|value| value.to_string(scope))
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
.collect::<Vec<_>>();
|
||||
values.join(if attribute == "systemLanguage" {
|
||||
","
|
||||
} else {
|
||||
" "
|
||||
})
|
||||
}
|
||||
|
||||
fn is_html_ascii_whitespace(character: char) -> bool {
|
||||
matches!(
|
||||
character,
|
||||
'\u{0009}' | '\u{000A}' | '\u{000C}' | '\u{000D}' | '\u{0020}'
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn build_svg_transform_list<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
|
||||
@@ -2,6 +2,7 @@ use super::builders::*;
|
||||
use super::*;
|
||||
use crate::util::serialize_v8_array;
|
||||
use crate::web_api_interfaces;
|
||||
use moli_webapi_declare::DataPropertyDescriptorDeclaration;
|
||||
|
||||
const SVG_RECT_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["x", "y", "width", "height", "rx", "ry"];
|
||||
const SVG_CIRCLE_ANIMATED_LENGTH_ATTRIBUTES: &[&str] = &["cx", "cy", "r"];
|
||||
@@ -18,6 +19,21 @@ const SVG_ANIMATED_ACCESSOR_NAMES: &[&str] = &["baseVal", "animVal"];
|
||||
const SVG_TRANSFORM_ACCESSOR_NAMES: &[&str] = &["type", "matrix", "angle"];
|
||||
const SVG_MATRIX_ACCESSOR_NAMES: &[&str] = &["a", "b", "c", "d", "e", "f"];
|
||||
|
||||
pub(super) fn configure_svg_string_list_indexed_property_handler(
|
||||
template: v8::Local<'_, v8::ObjectTemplate>,
|
||||
) {
|
||||
template.set_indexed_property_handler(
|
||||
v8::IndexedPropertyHandlerConfiguration::new()
|
||||
.getter(svg_string_list_indexed_getter)
|
||||
.setter(svg_string_list_indexed_setter)
|
||||
.query(svg_string_list_indexed_query)
|
||||
.deleter(svg_string_list_indexed_deleter)
|
||||
.enumerator(svg_string_list_indexed_enumerator)
|
||||
.definer(svg_string_list_indexed_definer)
|
||||
.descriptor(svg_string_list_indexed_descriptor),
|
||||
);
|
||||
}
|
||||
|
||||
fn svg_dom_matrix_2d_init_arg<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
@@ -380,6 +396,82 @@ pub(super) fn svg_graphics_transform_getter<'s>(
|
||||
svg_transform_attribute_getter(scope, args, rv, SVG_GRAPHICS_TRANSFORM_SLOT, "transform");
|
||||
}
|
||||
|
||||
pub(super) fn svg_graphics_test_string_list_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some((attribute, slot)) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
SVG_TEST_STRING_LIST_ATTRIBUTES,
|
||||
"SVGTests string list attributes",
|
||||
) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let receiver = args.this();
|
||||
if !require_svg_graphics_element_receiver(scope, receiver, attribute) {
|
||||
return;
|
||||
}
|
||||
if let Some(value) = get_private_value(scope, receiver, slot) {
|
||||
if let Ok(list) = v8::Local::<v8::Object>::try_from(value) {
|
||||
sync_svg_string_list_from_owner_attribute(scope, list);
|
||||
}
|
||||
rv.set(value);
|
||||
return;
|
||||
}
|
||||
let list = build_svg_string_list_for_attribute(scope, receiver, attribute);
|
||||
set_private_value(scope, receiver, slot, list.into());
|
||||
rv.set(list.into());
|
||||
}
|
||||
|
||||
fn require_svg_graphics_element_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!("SVGGraphicsElement.{member} called on incompatible receiver."),
|
||||
);
|
||||
return false;
|
||||
};
|
||||
let runtime = unsafe { &*runtime_ptr };
|
||||
let Some(mut interface_name) = runtime
|
||||
.dom_host()
|
||||
.node(handle)
|
||||
.and_then(|node| node.as_element())
|
||||
.map(|element| element.wrapper_prototype_name())
|
||||
else {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGGraphicsElement.{member} called on incompatible receiver."),
|
||||
);
|
||||
return false;
|
||||
};
|
||||
loop {
|
||||
if interface_name == "SVGGraphicsElement" {
|
||||
return true;
|
||||
}
|
||||
let Some(parent) =
|
||||
crate::context_bootstrap::bridge_descriptor::node_bridge_descriptor(interface_name)
|
||||
.and_then(|descriptor| descriptor.parent_constructor)
|
||||
else {
|
||||
break;
|
||||
};
|
||||
interface_name = parent;
|
||||
}
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGGraphicsElement.{member} called on incompatible receiver."),
|
||||
);
|
||||
false
|
||||
}
|
||||
|
||||
pub(super) fn svg_pattern_transform_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -1105,6 +1197,354 @@ pub(super) fn svg_animated_transform_list_getter<'s>(
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(items) = require_svg_string_list_items(scope, args.this(), "length getter") else {
|
||||
return;
|
||||
};
|
||||
rv.set_uint32(items.length());
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_clear_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if require_svg_string_list_items(scope, args.this(), "clear").is_none() {
|
||||
return;
|
||||
}
|
||||
set_svg_string_list_items(scope, args.this(), v8::Array::new(scope, 0));
|
||||
reflect_svg_string_list_to_owner_attribute(scope, args.this());
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_initialize_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if require_svg_string_list_items(scope, args.this(), "initialize").is_none() {
|
||||
return;
|
||||
}
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(item) = svg_string_list_dom_string(scope, parsed.item, "SVGStringList.initialize", 1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let items = v8::Array::new(scope, 1);
|
||||
let _ = items.set_index(scope, 0, item);
|
||||
set_svg_string_list_items(scope, args.this(), items);
|
||||
reflect_svg_string_list_to_owner_attribute(scope, args.this());
|
||||
rv.set(item);
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_get_item_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(items) = require_svg_string_list_items(scope, args.this(), "getItem") else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(item) = svg_list_item_or_throw(scope, items, parsed.index) else {
|
||||
return;
|
||||
};
|
||||
rv.set(item);
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_insert_item_before_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(items) = require_svg_string_list_items(scope, args.this(), "insertItemBefore") else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(item) =
|
||||
svg_string_list_dom_string(scope, parsed.item, "SVGStringList.insertItemBefore", 1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let length = items.length();
|
||||
let index = parsed.index.min(length);
|
||||
let array_length = i32::try_from(length.saturating_add(1)).unwrap_or(i32::MAX);
|
||||
let next = v8::Array::new(scope, array_length);
|
||||
for old_index in 0..length {
|
||||
let new_index = if old_index < index {
|
||||
old_index
|
||||
} else {
|
||||
old_index + 1
|
||||
};
|
||||
if let Some(value) = items.get_index(scope, old_index) {
|
||||
let _ = next.set_index(scope, new_index, value);
|
||||
}
|
||||
}
|
||||
let _ = next.set_index(scope, index, item);
|
||||
set_svg_string_list_items(scope, args.this(), next);
|
||||
reflect_svg_string_list_to_owner_attribute(scope, args.this());
|
||||
rv.set(item);
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_replace_item_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(items) = require_svg_string_list_items(scope, args.this(), "replaceItem") else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(item) = svg_string_list_dom_string(scope, parsed.item, "SVGStringList.replaceItem", 1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if parsed.index >= items.length() {
|
||||
webidl::throw_index_size_error(scope);
|
||||
return;
|
||||
}
|
||||
let _ = items.set_index(scope, parsed.index, item);
|
||||
reflect_svg_string_list_to_owner_attribute(scope, args.this());
|
||||
rv.set(item);
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_remove_item_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(items) = require_svg_string_list_items(scope, args.this(), "removeItem") else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListIndexArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let length = items.length();
|
||||
let Some(removed) = svg_list_item_or_throw(scope, items, parsed.index) else {
|
||||
return;
|
||||
};
|
||||
let next_length = i32::try_from(length.saturating_sub(1)).unwrap_or(i32::MAX);
|
||||
let next = v8::Array::new(scope, next_length);
|
||||
for old_index in 0..length {
|
||||
if old_index == parsed.index {
|
||||
continue;
|
||||
}
|
||||
let new_index = if old_index < parsed.index {
|
||||
old_index
|
||||
} else {
|
||||
old_index - 1
|
||||
};
|
||||
if let Some(value) = items.get_index(scope, old_index) {
|
||||
let _ = next.set_index(scope, new_index, value);
|
||||
}
|
||||
}
|
||||
set_svg_string_list_items(scope, args.this(), next);
|
||||
reflect_svg_string_list_to_owner_attribute(scope, args.this());
|
||||
rv.set(removed);
|
||||
}
|
||||
|
||||
pub(super) fn svg_string_list_append_item_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(items) = require_svg_string_list_items(scope, args.this(), "appendItem") else {
|
||||
return;
|
||||
};
|
||||
let Some(parsed) = webidl::parse_args::<SvgListItemArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
let Some(item) = svg_string_list_dom_string(scope, parsed.item, "SVGStringList.appendItem", 1)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = items.set_index(scope, items.length(), item);
|
||||
reflect_svg_string_list_to_owner_attribute(scope, args.this());
|
||||
rv.set(item);
|
||||
}
|
||||
|
||||
fn require_svg_string_list_items<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
member: &str,
|
||||
) -> Option<v8::Local<'s, v8::Array>> {
|
||||
if svg_string_list_items(scope, list).is_none() {
|
||||
webidl::throw_type_error(
|
||||
scope,
|
||||
&format!("SVGStringList.{member} called on incompatible receiver."),
|
||||
);
|
||||
return None;
|
||||
}
|
||||
sync_svg_string_list_from_owner_attribute(scope, list);
|
||||
svg_string_list_items(scope, list)
|
||||
}
|
||||
|
||||
fn svg_string_list_dom_string<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
operation: &'static str,
|
||||
argument_index: usize,
|
||||
) -> Option<v8::Local<'s, v8::Value>> {
|
||||
let value = match webidl::convert::<webidl::DomString>(
|
||||
scope,
|
||||
value,
|
||||
webidl::Context::argument(operation, argument_index),
|
||||
) {
|
||||
Ok(value) => value.0,
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
v8_string(scope, &value).map(Into::into)
|
||||
}
|
||||
|
||||
fn svg_string_list_intercepted_items<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
list: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Local<'s, v8::Array>> {
|
||||
svg_string_list_items(scope, list)?;
|
||||
sync_svg_string_list_from_owner_attribute(scope, list);
|
||||
svg_string_list_items(scope, list)
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(value) = svg_string_list_intercepted_items(scope, args.holder())
|
||||
.filter(|items| index < items.length())
|
||||
.and_then(|items| items.get_index(scope, index))
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(value);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let list = args.holder();
|
||||
let Some(items) = svg_string_list_intercepted_items(scope, list) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Some(value) = svg_string_list_dom_string(scope, value, "SVGStringList indexed setter", 2)
|
||||
else {
|
||||
return v8::Intercepted::kYes;
|
||||
};
|
||||
if index >= items.length() {
|
||||
webidl::throw_index_size_error(scope);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let _ = items.set_index(scope, index, value);
|
||||
reflect_svg_string_list_to_owner_attribute(scope, list);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_query<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Integer>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(items) = svg_string_list_intercepted_items(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if index >= items.length() {
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
rv.set_int32(v8::PropertyAttribute::NONE.as_u32() as i32);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_deleter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(items) = svg_string_list_intercepted_items(scope, args.holder()) else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
if index >= items.length() {
|
||||
return v8::Intercepted::kNo;
|
||||
}
|
||||
rv.set_bool(false);
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_definer<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
descriptor: &v8::PropertyDescriptor,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Boolean>,
|
||||
) -> v8::Intercepted {
|
||||
if descriptor.has_get() || descriptor.has_set() {
|
||||
rv.set_bool(false);
|
||||
return v8::Intercepted::kYes;
|
||||
}
|
||||
let value = if descriptor.has_value() {
|
||||
v8::Local::new(scope, descriptor.value())
|
||||
} else {
|
||||
v8::undefined(scope).into()
|
||||
};
|
||||
svg_string_list_indexed_setter(scope, index, value, args, rv)
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_enumerator<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Array>,
|
||||
) {
|
||||
let length = svg_string_list_intercepted_items(scope, args.holder())
|
||||
.map(|items| items.length())
|
||||
.unwrap_or(0);
|
||||
let keys = (0..length)
|
||||
.map(|index| v8::Integer::new_from_unsigned(scope, index).into())
|
||||
.collect::<Vec<_>>();
|
||||
rv.set(v8::Array::new_with_elements(scope, &keys));
|
||||
}
|
||||
|
||||
fn svg_string_list_indexed_descriptor<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
index: u32,
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) -> v8::Intercepted {
|
||||
let Some(value) = svg_string_list_intercepted_items(scope, args.holder())
|
||||
.filter(|items| index < items.length())
|
||||
.and_then(|items| items.get_index(scope, index))
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
let Ok(descriptor) = DataPropertyDescriptorDeclaration::new(value, true, true).bind(scope)
|
||||
else {
|
||||
return v8::Intercepted::kNo;
|
||||
};
|
||||
rv.set(descriptor.into());
|
||||
v8::Intercepted::kYes
|
||||
}
|
||||
|
||||
pub(super) fn svg_length_list_length_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
|
||||
@@ -19,6 +19,8 @@ mod callbacks;
|
||||
mod rect;
|
||||
|
||||
const SVG_GRAPHICS_TRANSFORM_SLOT: &str = "__moliSvgGraphicsTransform";
|
||||
const SVG_GRAPHICS_REQUIRED_EXTENSIONS_SLOT: &str = "__moliSvgGraphicsRequiredExtensions";
|
||||
const SVG_GRAPHICS_SYSTEM_LANGUAGE_SLOT: &str = "__moliSvgGraphicsSystemLanguage";
|
||||
const SVG_PATTERN_TRANSFORM_SLOT: &str = "__moliSvgPatternTransform";
|
||||
const SVG_GRADIENT_TRANSFORM_SLOT: &str = "__moliSvgGradientTransform";
|
||||
const SVG_GEOMETRY_PATH_LENGTH_SLOT: &str = "__moliSvgGeometryPathLength";
|
||||
@@ -52,6 +54,12 @@ const SVG_ANIMATED_NUMBER_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedNumberAnimVal"
|
||||
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_STRING_LIST_ITEMS_SLOT: &str = "__moliSvgStringListItems";
|
||||
const SVG_STRING_LIST_OWNER_ELEMENT_SLOT: &str = "__moliSvgStringListOwnerElement";
|
||||
const SVG_STRING_LIST_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgStringListOwnerAttribute";
|
||||
const SVG_STRING_LIST_SYNCED_ATTRIBUTE_VALUE_SLOT: &str = "__moliSvgStringListSyncedAttributeValue";
|
||||
const SVG_STRING_LIST_SYNCED_ATTRIBUTE_PRESENT_SLOT: &str =
|
||||
"__moliSvgStringListSyncedAttributePresent";
|
||||
const SVG_VALUE_LIST_OWNER_ELEMENT_SLOT: &str = "__moliSvgValueListOwnerElement";
|
||||
const SVG_VALUE_LIST_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgValueListOwnerAttribute";
|
||||
const SVG_VALUE_LIST_ITEM_OWNER_LIST_SLOT: &str = "__moliSvgValueListItemOwnerList";
|
||||
@@ -96,6 +104,11 @@ const SVG_TEXT_POSITIONING_DX_SLOT: &str = "__moliSvgTextPositioningDx";
|
||||
const SVG_TEXT_POSITIONING_DY_SLOT: &str = "__moliSvgTextPositioningDy";
|
||||
const SVG_TEXT_POSITIONING_ROTATE_SLOT: &str = "__moliSvgTextPositioningRotate";
|
||||
|
||||
const SVG_TEST_STRING_LIST_ATTRIBUTES: &[(&str, &str)] = &[
|
||||
("requiredExtensions", SVG_GRAPHICS_REQUIRED_EXTENSIONS_SLOT),
|
||||
("systemLanguage", SVG_GRAPHICS_SYSTEM_LANGUAGE_SLOT),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SvgListKind {
|
||||
Length,
|
||||
@@ -382,6 +395,7 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>(
|
||||
}
|
||||
"SVGAnimatedNumber" => bindings::install_svg_animated_number_bindings(scope, template),
|
||||
"SVGNumberList" => bindings::install_svg_number_list_bindings(scope, template),
|
||||
"SVGStringList" => bindings::install_svg_string_list_bindings(scope, template),
|
||||
"SVGAnimatedNumberList" => {
|
||||
bindings::install_svg_animated_number_list_bindings(scope, template)
|
||||
}
|
||||
|
||||
@@ -1923,6 +1923,194 @@ fn svg_animated_boolean_reflects_preserve_alpha() {
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_string_lists_reflect_conditional_processing_attributes() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://svg-string-list.test/",
|
||||
"<!doctype html><html><body></body></html>",
|
||||
);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r##"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const ns = "http://www.w3.org/2000/svg";
|
||||
const text = document.createElementNS(ns, "text");
|
||||
|
||||
assert(typeof SVGStringList === "function", "SVGStringList constructor");
|
||||
let illegalConstructor = false;
|
||||
try {
|
||||
new SVGStringList();
|
||||
} catch (error) {
|
||||
illegalConstructor = error instanceof TypeError;
|
||||
}
|
||||
assert(illegalConstructor, "SVGStringList illegal constructor");
|
||||
|
||||
for (const name of ["requiredExtensions", "systemLanguage"]) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(
|
||||
SVGGraphicsElement.prototype,
|
||||
name,
|
||||
);
|
||||
assert(typeof descriptor.get === "function" && descriptor.set === undefined,
|
||||
`${name} readonly descriptor`);
|
||||
assert(descriptor.enumerable && descriptor.configurable, `${name} flags`);
|
||||
}
|
||||
for (const name of ["length", "numberOfItems"]) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(SVGStringList.prototype, name);
|
||||
assert(typeof descriptor.get === "function" && descriptor.set === undefined,
|
||||
`${name} descriptor`);
|
||||
assert(descriptor.enumerable && descriptor.configurable, `${name} flags`);
|
||||
}
|
||||
const methodLengths = {
|
||||
clear: 0,
|
||||
initialize: 1,
|
||||
getItem: 1,
|
||||
insertItemBefore: 2,
|
||||
replaceItem: 2,
|
||||
removeItem: 1,
|
||||
appendItem: 1,
|
||||
};
|
||||
for (const [name, length] of Object.entries(methodLengths)) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(SVGStringList.prototype, name);
|
||||
assert(typeof descriptor.value === "function", `${name} method`);
|
||||
assert(descriptor.value.length === length, `${name} arity`);
|
||||
assert(descriptor.enumerable && descriptor.configurable && descriptor.writable,
|
||||
`${name} flags`);
|
||||
}
|
||||
|
||||
const languages = text.systemLanguage;
|
||||
assert(languages instanceof SVGStringList, "systemLanguage interface");
|
||||
assert(Object.prototype.toString.call(languages) === "[object SVGStringList]",
|
||||
"SVGStringList tag");
|
||||
assert(text.systemLanguage === languages, "systemLanguage SameObject");
|
||||
assert(languages.length === 0 && languages.numberOfItems === 0,
|
||||
"absent attribute empty list");
|
||||
assert(!text.hasAttribute("systemLanguage"), "getter does not create attribute");
|
||||
|
||||
const parsingCases = [
|
||||
["en,fr,de", ["en", "fr", "de"]],
|
||||
["en, fr, de", ["en", "fr", "de"]],
|
||||
["en ,fr ,de", ["en", "fr", "de"]],
|
||||
["en , fr , de", ["en", "fr", "de"]],
|
||||
[" en, fr ", ["en", "fr"]],
|
||||
[" \t\nen, fr\t\n ", ["en", "fr"]],
|
||||
["en", ["en"]],
|
||||
["en-US, zh-Hans, pt-BR", ["en-US", "zh-Hans", "pt-BR"]],
|
||||
["en,,fr", ["en", "", "fr"]],
|
||||
["", [""]],
|
||||
[",", ["", ""]],
|
||||
["123, 456", ["123", "456"]],
|
||||
["not-a-lang, ???, @#$", ["not-a-lang", "???", "@#$"]],
|
||||
];
|
||||
for (const [raw, expected] of parsingCases) {
|
||||
text.setAttribute("systemLanguage", raw);
|
||||
assert(text.systemLanguage === languages, `SameObject after ${raw}`);
|
||||
assert(languages.length === expected.length, `length for ${raw}`);
|
||||
assert(languages.numberOfItems === expected.length,
|
||||
`numberOfItems for ${raw}`);
|
||||
assert(Object.keys(languages).join() === expected.map((_, index) => index).join(),
|
||||
`supported indices for ${raw}`);
|
||||
for (let index = 0; index < expected.length; index++) {
|
||||
assert(languages.getItem(index) === expected[index],
|
||||
`getItem ${index} for ${raw}`);
|
||||
assert(languages[index] === expected[index], `index ${index} for ${raw}`);
|
||||
}
|
||||
}
|
||||
|
||||
text.removeAttribute("systemLanguage");
|
||||
assert(languages.length === 0, "removed attribute empty list");
|
||||
const extensions = text.requiredExtensions;
|
||||
assert(text.requiredExtensions === extensions, "requiredExtensions SameObject");
|
||||
text.setAttribute("requiredExtensions", " one\t two\nthree ");
|
||||
assert(extensions.length === 3 && extensions[0] === "one" &&
|
||||
extensions[1] === "two" && extensions[2] === "three",
|
||||
"requiredExtensions space-separated parsing");
|
||||
|
||||
assert(languages.initialize("en") === "en", "initialize return");
|
||||
assert(text.getAttribute("systemLanguage") === "en", "initialize reflection");
|
||||
assert(languages.appendItem("fr") === "fr", "append return");
|
||||
assert(text.getAttribute("systemLanguage") === "en,fr", "append reflection");
|
||||
assert(languages.insertItemBefore("de", 1) === "de", "insert return");
|
||||
assert(text.getAttribute("systemLanguage") === "en,de,fr", "insert reflection");
|
||||
assert(languages.insertItemBefore("it", 99) === "it", "clamped insert return");
|
||||
assert(text.getAttribute("systemLanguage") === "en,de,fr,it",
|
||||
"clamped insert reflection");
|
||||
assert(languages.replaceItem("zh", 1) === "zh", "replace return");
|
||||
assert(text.getAttribute("systemLanguage") === "en,zh,fr,it",
|
||||
"replace reflection");
|
||||
languages[2] = "pt-BR";
|
||||
assert(text.getAttribute("systemLanguage") === "en,zh,pt-BR,it",
|
||||
"indexed setter reflection");
|
||||
Object.defineProperty(languages, "0", {value: "es"});
|
||||
assert(text.getAttribute("systemLanguage") === "es,zh,pt-BR,it",
|
||||
"indexed definer reflection");
|
||||
const indexDescriptor = Object.getOwnPropertyDescriptor(languages, "0");
|
||||
assert(indexDescriptor.value === "es" && indexDescriptor.writable &&
|
||||
indexDescriptor.enumerable && indexDescriptor.configurable,
|
||||
"indexed property descriptor");
|
||||
assert(delete languages[0] === false, "supported index cannot be deleted");
|
||||
assert(languages.removeItem(1) === "zh", "remove return");
|
||||
assert(text.getAttribute("systemLanguage") === "es,pt-BR,it",
|
||||
"remove reflection");
|
||||
languages.clear();
|
||||
assert(text.getAttribute("systemLanguage") === "" && languages.length === 0,
|
||||
"clear reflection");
|
||||
|
||||
extensions.initialize("alpha");
|
||||
extensions.appendItem("beta");
|
||||
assert(text.getAttribute("requiredExtensions") === "alpha beta",
|
||||
"requiredExtensions space serialization");
|
||||
assert(extensions.appendItem(null) === "null", "DOMString conversion");
|
||||
assert(text.getAttribute("requiredExtensions") === "alpha beta null",
|
||||
"converted string reflection");
|
||||
|
||||
text.setAttribute("systemLanguage", "en,fr");
|
||||
for (const operation of [
|
||||
() => languages.getItem(9),
|
||||
() => languages.replaceItem("x", 9),
|
||||
() => languages.removeItem(9),
|
||||
() => { languages[9] = "x"; },
|
||||
]) {
|
||||
let indexError = false;
|
||||
try {
|
||||
operation();
|
||||
} catch (error) {
|
||||
indexError = error instanceof DOMException && error.name === "IndexSizeError";
|
||||
}
|
||||
assert(indexError, "out-of-range operation");
|
||||
}
|
||||
|
||||
let incompatibleListReceiver = false;
|
||||
try {
|
||||
SVGStringList.prototype.getItem.call({}, 0);
|
||||
} catch (error) {
|
||||
incompatibleListReceiver = error instanceof TypeError;
|
||||
}
|
||||
assert(incompatibleListReceiver, "SVGStringList receiver brand");
|
||||
|
||||
const systemLanguageGetter = Object.getOwnPropertyDescriptor(
|
||||
SVGGraphicsElement.prototype,
|
||||
"systemLanguage",
|
||||
).get;
|
||||
let incompatibleElementReceiver = false;
|
||||
try {
|
||||
systemLanguageGetter.call(document.createElementNS(ns, "filter"));
|
||||
} catch (error) {
|
||||
incompatibleElementReceiver = error instanceof TypeError;
|
||||
}
|
||||
assert(incompatibleElementReceiver, "SVGGraphicsElement receiver brand");
|
||||
return "ok";
|
||||
})()
|
||||
"##,
|
||||
)
|
||||
.expect("SVG string list probe should evaluate");
|
||||
|
||||
assert_eq!(result, "ok");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svg_geometry_queries_use_computed_paths_live_tree_and_kurbo_bounds() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
|
||||
@@ -415,6 +415,7 @@ interfaces! {
|
||||
SVGTextPositioningElement: SVGTextContentElement;
|
||||
SVGTitleElement: SVGElement;
|
||||
SVGUnitTypes;
|
||||
SVGStringList;
|
||||
SVGTransform;
|
||||
SVGTransformList;
|
||||
SVGUseElement: SVGGraphicsElement;
|
||||
|
||||
Reference in New Issue
Block a user