mirror of
https://github.com/lexmount/moli.git
synced 2026-11-04 08:07:27 +00:00
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:
@@ -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"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user