fix(svg): reflect filter enumerations with native receiver validation

Expose GaussianBlur edgeMode with its none default and shared edge-mode keywords/constants. Preserve arithmetic as composite operator 6 and keep the existing lighter extension at 7. Use derived SVGAnimatedEnumeration receiver and argument validation, canonicalize registered native proxies, and reflect mutations back to native owner attributes. Cover live/windowless documents, invalid keywords, cross-realm bindings, conversion order, and registered/author proxies.
This commit is contained in:
ldm0
2026-10-11 18:06:48 +08:00
parent 4e340095e2
commit e5a566253e
7 changed files with 338 additions and 38 deletions
@@ -718,6 +718,17 @@ define_svg_enumeration_constants!(
}
);
define_svg_enumeration_constants!(
SvgFeGaussianBlurElementTemplateConstantsDeclaration,
"SVGFEGaussianBlurElement",
{
unknown => "SVG_EDGEMODE_UNKNOWN" = SVG_EDGE_MODE_UNKNOWN,
duplicate => "SVG_EDGEMODE_DUPLICATE" = SVG_EDGE_MODE_DUPLICATE,
wrap => "SVG_EDGEMODE_WRAP" = SVG_EDGE_MODE_WRAP,
none => "SVG_EDGEMODE_NONE" = SVG_EDGE_MODE_NONE,
}
);
define_svg_enumeration_constants!(
SvgFeDisplacementMapElementTemplateConstantsDeclaration,
"SVGFEDisplacementMapElement",
@@ -1250,7 +1261,7 @@ struct SvgAnimatedNumberListTemplateAccessorsDeclaration {
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGAnimatedEnumeration, enumerable)]
#[webapi(interface = web_api_interfaces::SVGAnimatedEnumeration, enumerable, receiver)]
struct SvgAnimatedEnumerationTemplateAccessorsDeclaration {
#[webapi(
accessor_property = "baseVal",
@@ -1896,6 +1907,17 @@ struct SvgFeCompositeElementPrototypeAccessorsDeclaration {
operator: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGFEGaussianBlurElement, enumerable, receiver)]
struct SvgFeGaussianBlurElementPrototypeAccessorsDeclaration {
#[webapi(
accessor_property = "edgeMode",
getter = svg_element_animated_enumeration_getter,
data = callback_data_index_value(scope, 25)
)]
edge_mode: (),
}
#[derive(WebApiFunctionTemplate)]
#[webapi(interface = web_api_interfaces::SVGFEConvolveMatrixElement, enumerable, receiver)]
struct SvgFeConvolveMatrixElementPrototypeAccessorsDeclaration {
@@ -2427,6 +2449,9 @@ pub(super) fn install_svg_enumeration_constant_bindings<'s>(
"SVGFEConvolveMatrixElement" => {
install_constants!(SvgFeConvolveMatrixElementTemplateConstantsDeclaration);
}
"SVGFEGaussianBlurElement" => {
install_constants!(SvgFeGaussianBlurElementTemplateConstantsDeclaration);
}
"SVGFEDisplacementMapElement" => {
install_constants!(SvgFeDisplacementMapElementTemplateConstantsDeclaration);
}
@@ -2682,6 +2707,11 @@ pub(super) fn install_svg_element_accessor_bindings<'s>(
scope, prototype,
);
}
"SVGFEGaussianBlurElement" => {
SvgFeGaussianBlurElementPrototypeAccessorsDeclaration::initialize_prototype_template(
scope, prototype,
);
}
"SVGFEConvolveMatrixElement" => {
SvgFeConvolveMatrixElementPrototypeAccessorsDeclaration::initialize_prototype_template(
scope, prototype,
@@ -1932,14 +1932,8 @@ pub(super) fn svg_animated_enumeration_getter<'s>(
rv.set_undefined();
return;
};
if !require_svg_receiver(
scope,
args.this(),
"SVGAnimatedEnumeration",
&format!("{name} getter"),
) {
return;
}
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("generated receiver check validates native SVGAnimatedEnumeration identity");
let slot = match name {
"baseVal" => SVG_ANIMATED_ENUMERATION_BASE_VAL_SLOT,
"animVal" => SVG_ANIMATED_ENUMERATION_ANIM_VAL_SLOT,
@@ -1948,11 +1942,11 @@ pub(super) fn svg_animated_enumeration_getter<'s>(
return;
}
};
sync_svg_animated_enumeration_from_owner_attribute(scope, args.this());
let initial_value = svg_animated_enumeration_property(scope, args.this())
sync_svg_animated_enumeration_from_owner_attribute(scope, receiver);
let initial_value = svg_animated_enumeration_property(scope, receiver)
.map(|property| property.initial_value)
.unwrap_or(SVG_LENGTH_ADJUST_SPACING);
let value = svg_length_number_slot(scope, args.this(), slot).unwrap_or(initial_value as f64);
let value = svg_length_number_slot(scope, receiver, slot).unwrap_or(initial_value as f64);
rv.set(v8::Integer::new_from_unsigned(scope, value as u32).into());
}
@@ -1969,28 +1963,16 @@ pub(super) fn svg_animated_enumeration_setter<'s>(
) else {
return;
};
if !require_svg_receiver(
scope,
args.this(),
"SVGAnimatedEnumeration",
&format!("{name} setter"),
) || name != "baseVal"
{
if name != "baseVal" {
return;
}
let value = match webidl::convert::<webidl::UnsignedShort>(
scope,
args.get(0),
webidl::Context::member("SVGAnimatedEnumeration", "baseVal"),
) {
Ok(value) => value.0,
Err(error) => {
webidl::throw_error(scope, &error);
return;
}
let Some(parsed) = webidl::parse_args::<SvgAnimatedEnumerationBaseValArgs>(scope, &args) else {
return;
};
let value = u32::from(value);
let Some(property) = svg_animated_enumeration_property(scope, args.this()) else {
let receiver = moli_webapi_declare::web_api_object_target(scope, args.this())
.expect("generated receiver check validates native SVGAnimatedEnumeration identity");
let value = u32::from(parsed.value);
let Some(property) = svg_animated_enumeration_property(scope, receiver) else {
webidl::throw_type_error(
scope,
"SVGAnimatedEnumeration has no enumeration definition.",
@@ -2001,8 +1983,8 @@ pub(super) fn svg_animated_enumeration_setter<'s>(
webidl::throw_type_error(scope, "Invalid SVGAnimatedEnumeration value.");
return;
}
set_svg_animated_enumeration_values(scope, args.this(), value);
reflect_svg_animated_enumeration_to_owner_attribute(scope, args.this());
set_svg_animated_enumeration_values(scope, receiver, value);
reflect_svg_animated_enumeration_to_owner_attribute(scope, receiver);
}
pub(super) fn svg_animated_number_setter<'s>(
@@ -139,6 +139,7 @@ const SVG_FE_BLEND_MODE_SLOT: &str = "__moliSvgFeBlendMode";
const SVG_FE_COLOR_MATRIX_TYPE_SLOT: &str = "__moliSvgFeColorMatrixType";
const SVG_FE_COMPOSITE_OPERATOR_SLOT: &str = "__moliSvgFeCompositeOperator";
const SVG_FE_CONVOLVE_MATRIX_EDGE_MODE_SLOT: &str = "__moliSvgFeConvolveMatrixEdgeMode";
const SVG_FE_GAUSSIAN_BLUR_EDGE_MODE_SLOT: &str = "__moliSvgFeGaussianBlurEdgeMode";
const SVG_FE_CONVOLVE_MATRIX_ORDER_X_SLOT: &str = "__moliSvgFeConvolveMatrixOrderX";
const SVG_FE_CONVOLVE_MATRIX_ORDER_Y_SLOT: &str = "__moliSvgFeConvolveMatrixOrderY";
const SVG_FE_CONVOLVE_MATRIX_TARGET_X_SLOT: &str = "__moliSvgFeConvolveMatrixTargetX";
@@ -436,6 +437,13 @@ struct SvgAngleConvertToSpecifiedUnitsArgs {
unit_type: u16,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVGAnimatedEnumeration.baseVal")]
struct SvgAnimatedEnumerationBaseValArgs {
#[webidl(required, converter = "unsigned_short")]
value: u16,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "SVGGeometryElement.getPointAtLength")]
struct SvgGeometryPointAtLengthArgs {
@@ -567,8 +575,8 @@ const SVG_FE_COMPOSITE_OPERATOR_IN: u32 = 2;
const SVG_FE_COMPOSITE_OPERATOR_OUT: u32 = 3;
const SVG_FE_COMPOSITE_OPERATOR_ATOP: u32 = 4;
const SVG_FE_COMPOSITE_OPERATOR_XOR: u32 = 5;
const SVG_FE_COMPOSITE_OPERATOR_LIGHTER: u32 = 6;
const SVG_FE_COMPOSITE_OPERATOR_ARITHMETIC: u32 = 7;
const SVG_FE_COMPOSITE_OPERATOR_ARITHMETIC: u32 = 6;
const SVG_FE_COMPOSITE_OPERATOR_LIGHTER: u32 = 7;
const SVG_EDGE_MODE_UNKNOWN: u32 = 0;
const SVG_EDGE_MODE_DUPLICATE: u32 = 1;
@@ -877,6 +885,13 @@ const SVG_ANIMATED_ENUMERATION_PROPERTIES: &[SvgAnimatedEnumerationProperty] = &
initial_value: SVG_TEXT_PATH_SIDE_TYPE_LEFT,
kind: SvgAnimatedEnumerationKind::Keywords(SVG_TEXT_PATH_SIDE_TYPE_VALUES),
},
SvgAnimatedEnumerationProperty {
index: 25,
attribute: "edgeMode",
cache_slot: SVG_FE_GAUSSIAN_BLUR_EDGE_MODE_SLOT,
initial_value: SVG_EDGE_MODE_NONE,
kind: SvgAnimatedEnumerationKind::Keywords(SVG_EDGE_MODE_VALUES),
},
];
const fn svg_animated_number_property(
@@ -961,8 +961,8 @@ fn svg_filter_and_text_path_enumerations_reflect_typed_content_attributes() {
["SVG_FECOMPOSITE_OPERATOR_OUT", 3],
["SVG_FECOMPOSITE_OPERATOR_ATOP", 4],
["SVG_FECOMPOSITE_OPERATOR_XOR", 5],
["SVG_FECOMPOSITE_OPERATOR_LIGHTER", 6],
["SVG_FECOMPOSITE_OPERATOR_ARITHMETIC", 7],
["SVG_FECOMPOSITE_OPERATOR_LIGHTER", 7],
["SVG_FECOMPOSITE_OPERATOR_ARITHMETIC", 6],
]],
[SVGFEConvolveMatrixElement, [
["SVG_EDGEMODE_UNKNOWN", 0],
@@ -1029,7 +1029,7 @@ fn svg_filter_and_text_path_enumerations_reflect_typed_content_attributes() {
["feFuncR", SVGComponentTransferFunctionElement, "type", 1, 5, "gamma", 6],
["feBlend", SVGFEBlendElement, "mode", 1, 16, "luminosity", 17],
["feColorMatrix", SVGFEColorMatrixElement, "type", 1, 4, "luminanceToAlpha", 5],
["feComposite", SVGFECompositeElement, "operator", 1, 7, "arithmetic", 8],
["feComposite", SVGFECompositeElement, "operator", 1, 6, "arithmetic", 8],
["feConvolveMatrix", SVGFEConvolveMatrixElement, "edgeMode", 1, 3, "none", 4],
["feDisplacementMap", SVGFEDisplacementMapElement, "xChannelSelector", 4, 1, "R", 5],
["feDisplacementMap", SVGFEDisplacementMapElement, "yChannelSelector", 4, 3, "B", 5],
@@ -2329,6 +2329,7 @@ mod streams;
mod string_timers;
mod svg_animated_number;
mod svg_element_receivers;
mod svg_filter_enumerations;
mod svg_filter_interfaces;
mod svg_geometry_methods;
mod svg_geometry_receivers;
@@ -0,0 +1,160 @@
(() => {
const checks = [];
const check = (name, fn) => {
try { checks.push({name, passed: fn() === true}); }
catch (error) { checks.push({name, passed: false, error: String(error)}); }
};
const ns = 'http://www.w3.org/2000/svg';
const realms = [window, document.querySelector('iframe').contentWindow];
const edgeConstants = [['SVG_EDGEMODE_UNKNOWN', 0], ['SVG_EDGEMODE_DUPLICATE', 1],
['SVG_EDGEMODE_WRAP', 2], ['SVG_EDGEMODE_NONE', 3]];
const cases = [
['feComposite', 'SVGFECompositeElement', 'operator', 1,
[['over', 1], ['in', 2], ['out', 3], ['atop', 4], ['xor', 5], ['arithmetic', 6], ['lighter', 7]],
[['SVG_FECOMPOSITE_OPERATOR_UNKNOWN', 0], ['SVG_FECOMPOSITE_OPERATOR_OVER', 1],
['SVG_FECOMPOSITE_OPERATOR_IN', 2], ['SVG_FECOMPOSITE_OPERATOR_OUT', 3],
['SVG_FECOMPOSITE_OPERATOR_ATOP', 4], ['SVG_FECOMPOSITE_OPERATOR_XOR', 5],
['SVG_FECOMPOSITE_OPERATOR_ARITHMETIC', 6]]],
['feGaussianBlur', 'SVGFEGaussianBlurElement', 'edgeMode', 3,
[['duplicate', 1], ['wrap', 2], ['none', 3]], edgeConstants],
['feConvolveMatrix', 'SVGFEConvolveMatrixElement', 'edgeMode', 1,
[['duplicate', 1], ['wrap', 2], ['none', 3]], edgeConstants],
['feMorphology', 'SVGFEMorphologyElement', 'operator', 1,
[['erode', 1], ['dilate', 2]], [['SVG_MORPHOLOGY_OPERATOR_UNKNOWN', 0],
['SVG_MORPHOLOGY_OPERATOR_ERODE', 1], ['SVG_MORPHOLOGY_OPERATOR_DILATE', 2]]],
['feDisplacementMap', 'SVGFEDisplacementMapElement', 'xChannelSelector', 4,
[['R', 1], ['G', 2], ['B', 3], ['A', 4]], [['SVG_CHANNEL_UNKNOWN', 0],
['SVG_CHANNEL_R', 1], ['SVG_CHANNEL_G', 2], ['SVG_CHANNEL_B', 3], ['SVG_CHANNEL_A', 4]]],
];
const throwsTypeError = (realm, fn) => {
try { fn(); } catch (error) { return Object.getPrototypeOf(error) === realm.TypeError.prototype; }
return false;
};
for (const [ownerIndex, owner] of realms.entries()) {
for (const [calleeIndex, realm] of realms.entries()) {
const documents = [owner.document, owner.document.implementation.createHTMLDocument(''),
owner.document.implementation.createDocument(ns, 'svg'),
new owner.DOMParser().parseFromString('<svg xmlns="' + ns + '"/>', 'image/svg+xml')];
for (const [documentIndex, doc] of documents.entries()) {
for (const [tag, iface, property, initial, keywords, constants] of cases) {
const prefix = `${ownerIndex}/${calleeIndex}/${documentIndex}/${tag}/${property}`;
const element = doc.createElementNS(ns, tag);
(doc.body || doc.documentElement).appendChild(element);
const ctor = realm[iface];
const getter = Object.getOwnPropertyDescriptor(ctor.prototype, property)?.get;
const enumProto = realm.SVGAnimatedEnumeration.prototype;
const base = Object.getOwnPropertyDescriptor(enumProto, 'baseVal');
const anim = Object.getOwnPropertyDescriptor(enumProto, 'animVal');
const value = element[property];
check(prefix + '/attribute-descriptor', () => {
const descriptor = Object.getOwnPropertyDescriptor(ctor.prototype, property);
return descriptor.enumerable && descriptor.configurable && descriptor.set === undefined &&
descriptor.get.length === 0 && typeof descriptor.get === 'function';
});
check(prefix + '/enum-descriptors', () => base.enumerable && base.configurable &&
base.get.length === 0 && base.set.length === 1 && anim.enumerable && anim.configurable &&
anim.get.length === 0 && anim.set === undefined);
check(prefix + '/owner-realm-and-identity', () =>
Object.getPrototypeOf(value) === owner.SVGAnimatedEnumeration.prototype &&
getter.call(element) === value && element[property] === value);
check(prefix + '/missing-initial', () => value.baseVal === initial && value.animVal === initial &&
!element.hasAttribute(property));
for (const [name, expected] of constants) {
for (const [targetName, target] of [['constructor', ctor], ['prototype', ctor.prototype]]) {
check(prefix + '/constant/' + targetName + '/' + name, () => {
const descriptor = Object.getOwnPropertyDescriptor(target, name);
return descriptor.value === expected && descriptor.enumerable &&
!descriptor.writable && !descriptor.configurable;
});
}
}
for (const [keyword, expected] of keywords) {
check(prefix + '/attribute-to-enum/' + keyword, () => {
element.setAttribute(property, keyword);
return base.get.call(value) === expected && anim.get.call(value) === expected &&
getter.call(element) === value;
});
check(prefix + '/enum-to-attribute/' + keyword, () => {
base.set.call(value, expected);
return element.getAttribute(property) === keyword && value.baseVal === expected &&
value.animVal === expected;
});
}
for (const invalid of ['', 'invalid', 'NONE', ' duplicate ', '\u0000']) {
check(prefix + '/invalid-content-initial/' + JSON.stringify(invalid), () => {
element.setAttribute(property, invalid);
return value.baseVal === initial && value.animVal === initial &&
element.getAttribute(property) === invalid;
});
}
check(prefix + '/remove-initial-and-identity', () => {
element.removeAttribute(property);
return value.baseVal === initial && value.animVal === initial && getter.call(element) === value;
});
const [keyword, expected] = keywords[0];
for (const [name, argument] of [['wrapped', 65536 + expected], ['negative-wrapped', expected - 65536],
['fractional', expected + 0.75], ['string', String(expected)]]) {
check(prefix + '/unsigned-short/' + name, () => {
base.set.call(value, argument);
return value.baseVal === expected && element.getAttribute(property) === keyword;
});
}
for (const [name, argument] of [['zero', 0], ['outside-domain', 42], ['nan', NaN],
['infinite', Infinity], ['undefined', undefined], ['null', null], ['negative', -1],
['symbol', Symbol('enum')], ['bigint', 1n]]) {
check(prefix + '/invalid-setter/' + name, () => {
element.setAttribute(property, keyword);
return throwsTypeError(realm, () => base.set.call(value, argument)) &&
element.getAttribute(property) === keyword && value.baseVal === expected;
});
}
check(prefix + '/conversion-once-and-live-mutation', () => {
let conversions = 0;
base.set.call(value, {valueOf() { conversions++; element.setAttribute(property, 'invalid'); return expected; }});
return conversions === 1 && element.getAttribute(property) === keyword && value.baseVal === expected;
});
check(prefix + '/original-conversion-exception', () => {
const sentinel = {}; let conversions = 0;
try { base.set.call(value, {valueOf() { conversions++; throw sentinel; }}); }
catch (error) { return error === sentinel && conversions === 1 && element.getAttribute(property) === keyword; }
return false;
});
check(prefix + '/clone-independent-identity', () => {
const clone = element.cloneNode();
const cloned = getter.call(clone);
clone.setAttribute(property, keywords[1][0]);
return cloned !== value && cloned.baseVal === keywords[1][1] &&
value.baseVal === expected && Object.getPrototypeOf(cloned) === owner.SVGAnimatedEnumeration.prototype;
});
check(prefix + '/retained-value-after-detach', () => {
element.remove(); element.setAttribute(property, keywords[1][0]);
return getter.call(element) === value && value.baseVal === keywords[1][1];
});
const wrongElement = doc.createElementNS(ns, tag === 'feMorphology' ? 'feConvolveMatrix' : 'feMorphology');
for (const [index, receiver] of [wrongElement, doc.createElement('div'), {}, Object.create(element)].entries()) {
check(prefix + '/element-receiver/' + index, () => throwsTypeError(realm, () => getter.call(receiver)));
}
let traps = 0;
const authorProxy = new Proxy(value || {}, {get() {traps++; throw 42;}, getPrototypeOf() {traps++; throw 42;}});
const revoked = Proxy.revocable(value || {}, {}); revoked.revoke();
const invalidReceivers = [{}, Object.create(enumProto), Object.create(value || enumProto),
authorProxy, revoked.proxy, element, doc, doc.createElementNS(ns, 'circle')];
for (const [index, receiver] of invalidReceivers.entries()) {
for (const [name, fn] of [['base', base.get], ['anim', anim.get]]) {
check(prefix + '/enum-receiver/' + index + '/' + name, () =>
throwsTypeError(realm, () => fn.call(receiver)) && traps === 0);
}
check(prefix + '/enum-receiver/' + index + '/set-before-conversion', () => {
let conversions = 0;
return throwsTypeError(realm, () => base.set.call(receiver, {valueOf() {conversions++; return expected;}})) &&
conversions === 0 && traps === 0;
});
}
}
}
}
}
globalThis.__uiEventResults = {complete: true, total: checks.length,
passed: checks.filter(row => row.passed).length, checks};
return true;
})()
@@ -0,0 +1,112 @@
use super::*;
#[test]
fn svg_filter_enumerations_reflect_native_attributes_with_webidl_receiver_order() {
let mut vm = new_storage_page_task_executor_test_vm("https://svg-filter-enums.test/");
vm.eval("document.body.innerHTML = '<iframe></iframe>'")
.unwrap();
vm.eval(include_str!("svg_filter_enumerations.js")).unwrap();
assert_eq!(vm.eval("__uiEventResults.complete").unwrap(), "true");
assert_eq!(vm.eval("__uiEventResults.total").unwrap(), "5744");
assert_eq!(
vm.eval("JSON.stringify(__uiEventResults.checks.filter(row => !row.passed))")
.unwrap(),
"[]"
);
}
#[test]
fn svg_filter_enumerations_accept_registered_native_proxies_for_values_and_elements() {
let mut vm = new_storage_page_task_executor_test_vm("https://svg-filter-enum-proxies.test/");
vm.eval(
r#"
document.body.innerHTML = '<iframe></iframe>';
globalThis.childWindow = document.querySelector('iframe').contentWindow;
const doc = childWindow.document.implementation.createHTMLDocument('');
globalThis.definitions = [
['feComposite', 'SVGFECompositeElement', 'operator', 'arithmetic', 6],
['feGaussianBlur', 'SVGFEGaussianBlurElement', 'edgeMode', 'wrap', 2],
['feConvolveMatrix', 'SVGFEConvolveMatrixElement', 'edgeMode', 'none', 3],
['feMorphology', 'SVGFEMorphologyElement', 'operator', 'dilate', 2],
['feDisplacementMap', 'SVGFEDisplacementMapElement', 'xChannelSelector', 'A', 4],
];
for (const [tag, , property, keyword] of definitions) {
const element = doc.createElementNS('http://www.w3.org/2000/svg', tag);
element.setAttribute(property, keyword);
globalThis[tag] = element;
globalThis[tag + 'Value'] = element[property];
}
"#,
)
.unwrap();
let context_ptr = &vm.page_default_context as *const _;
vm.with_context_scope_by_ptr_and_checkpoint_for_test(context_ptr, |scope, _| {
let global = scope.get_current_context().global(scope);
for tag in [
"feComposite",
"feGaussianBlur",
"feConvolveMatrix",
"feMorphology",
"feDisplacementMap",
] {
for suffix in ["", "Value"] {
let name = format!("{tag}{suffix}");
let key = crate::util::v8_string(scope, &name).unwrap();
let object = global.get(scope, key.into()).unwrap();
let object = v8::Local::<v8::Object>::try_from(object).unwrap();
let handler = crate::util::new_null_prototype_object(scope);
let proxy = v8::Proxy::new(scope, object, handler).unwrap();
moli_webapi_declare::register_web_api_proxy(scope, proxy).unwrap();
let key = crate::util::v8_string(scope, &format!("{name}Proxy")).unwrap();
assert_eq!(
global.create_data_property(scope, key.into(), proxy.into()),
Some(true)
);
}
}
Ok(())
})
.unwrap();
assert_eq!(
vm.eval(
r#"(() => {
for (const realm of [window, childWindow]) {
const prototype = realm.SVGAnimatedEnumeration.prototype;
const base = Object.getOwnPropertyDescriptor(prototype, 'baseVal');
const anim = Object.getOwnPropertyDescriptor(prototype, 'animVal');
for (const [tag, iface, property, keyword, expected] of definitions) {
const element = globalThis[tag];
const value = globalThis[tag + 'Value'];
const nativeValue = globalThis[tag + 'ValueProxy'];
const getter = Object.getOwnPropertyDescriptor(realm[iface].prototype, property).get;
if (getter.call(globalThis[tag + 'Proxy']) !== value ||
base.get.call(nativeValue) !== expected || anim.get.call(nativeValue) !== expected) {
throw Error('registered proxy lost identity or reflected value');
}
let conversions = 0;
base.set.call(nativeValue, {valueOf() {conversions++; return expected + 65536;}});
if (conversions !== 1 || element.getAttribute(property) !== keyword ||
value.baseVal !== expected || value.animVal !== expected) throw Error('native proxy writeback');
let traps = 0;
const author = new Proxy(nativeValue, {
get() {traps++; throw 42;}, getPrototypeOf() {traps++; throw 42;}
});
const revoked = Proxy.revocable(nativeValue, {}); revoked.revoke();
for (const receiver of [author, revoked.proxy, Object.create(nativeValue)]) {
for (const fn of [base.get, anim.get, base.set]) {
let error;
try {fn.call(receiver, {valueOf() {conversions++; return expected;}});}
catch (caught) {error = caught;}
if (!error || Object.getPrototypeOf(error) !== realm.TypeError.prototype ||
conversions !== 1 || traps !== 0) throw Error('invalid proxy receiver');
}
}
}
}
return true;
})()"#,
)
.unwrap(),
"true"
);
}