From dfbcf3e88aaeb0f08d5be4bb71d669978efbee7c Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 16 Jul 2026 22:29:16 +0800 Subject: [PATCH] fix(svg): reflect animated strings through trusted types --- .../wpt-cross-current/failed-cases.txt | 1 - .../wpt-cross-current/passed-cases.txt | 2 + .../src/context_bootstrap/runtime_state.rs | 1 + .../src/context_bootstrap/specs/registry.rs | 5 + .../context_bootstrap/svg_runtime/bindings.rs | 71 ++++++++++- .../context_bootstrap/svg_runtime/builders.rs | 88 ++++++++++++++ .../svg_runtime/callbacks.rs | 113 ++++++++++++++++++ .../src/context_bootstrap/svg_runtime/mod.rs | 7 ++ moli-renderer-v8/src/native_bridge/element.rs | 1 + .../native_bridge/element/trusted_types.rs | 26 ++++ .../tests/browser_api/trusted_types.rs | 111 +++++++++++++++++ 11 files changed, 424 insertions(+), 2 deletions(-) diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index c6f14d1c3b..e54b76e045 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -3736,7 +3736,6 @@ trusted-types/trusted-types-reporting-for-ServiceWorker-ServiceWorkerContainer-r trusted-types/trusted-types-reporting-for-SharedWorker-ServiceWorkerContainer-register.https.html trusted-types/trusted-types-reporting.html trusted-types/trusted-types-secondary-document.html -trusted-types/trusted-types-svg-script-set-href.html uievents/order-of-events/focus-events/focus-automated-blink-webkit.html url/a-element.html?exclude=(file|javascript|mailto) url/a-element.html?include=file diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 5b1f1aa927..9fb20ee163 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -7913,9 +7913,11 @@ trusted-types/trusted-types-reporting-for-Window-SharedWorker-constructor.html trusted-types/trusted-types-reporting-for-Window-eval.html trusted-types/trusted-types-reporting-for-Window-function-constructor.html trusted-types/trusted-types-reporting-for-Window-setTimeout-setInterval.html +trusted-types/trusted-types-reporting.html trusted-types/trusted-types-sandbox-allow-scripts.html trusted-types/trusted-types-sandbox-no-allow-scripts.html trusted-types/trusted-types-source-file-path.html +trusted-types/trusted-types-svg-script-set-href.html trusted-types/trusted-types-tojson.html trusted-types/tt-block-eval.html uievents/constructors/inputevent-constructor.html diff --git a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs index 1a689469ec..a551f76119 100644 --- a/moli-renderer-v8/src/context_bootstrap/runtime_state.rs +++ b/moli-renderer-v8/src/context_bootstrap/runtime_state.rs @@ -1696,6 +1696,7 @@ pub(crate) fn finish_context_bootstrap( ("SVGLength", "SVGLength"), ("SVGNumber", "SVGNumber"), ("SVGRect", "SVGRect"), + ("SVGAnimatedString", "SVGAnimatedString"), ("SVGAnimatedLength", "SVGAnimatedLength"), ("SVGLengthList", "SVGLengthList"), ("SVGAnimatedLengthList", "SVGAnimatedLengthList"), diff --git a/moli-renderer-v8/src/context_bootstrap/specs/registry.rs b/moli-renderer-v8/src/context_bootstrap/specs/registry.rs index 2422dbe585..17acb7f869 100644 --- a/moli-renderer-v8/src/context_bootstrap/specs/registry.rs +++ b/moli-renderer-v8/src/context_bootstrap/specs/registry.rs @@ -113,6 +113,11 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[ parent: None, kind: ConstructorKind::Illegal, }, + ConstructorSpec { + name: "SVGAnimatedString", + parent: None, + kind: ConstructorKind::Illegal, + }, ConstructorSpec { name: "SVGAnimatedLength", parent: None, diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs index 33f59d28bb..1f67ab4e4d 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/bindings.rs @@ -499,6 +499,26 @@ struct SvgLengthTemplateAccessorsDeclaration { value_as_string: (), } +#[derive(WebApiFunctionTemplate)] +#[webapi(name = "SVGAnimatedString", enumerable)] +struct SvgAnimatedStringTemplateAccessorsDeclaration { + #[webapi( + accessor_property = "baseVal", + getter = svg_animated_string_getter, + setter = svg_animated_string_setter, + data = callback_data_index_value(scope, 0) + )] + base_val: (), + + #[webapi( + accessor_property = "animVal", + getter = svg_animated_string_getter, + data = callback_data_index_value(scope, 1) + )] + anim_val: (), +} + +#[allow(dead_code)] #[derive(WebApiFunctionTemplate)] #[webapi(name = "SVGAnimatedLength", enumerable)] struct SvgAnimatedLengthTemplateAccessorsDeclaration { @@ -876,6 +896,23 @@ struct SvgGraphicsBoxElementPrototypeAccessorsDeclaration { height: (), } +#[derive(WebApiFunctionTemplate)] +#[webapi(name = "SVGElement", enumerable)] +struct SvgElementPrototypeAccessorsDeclaration { + #[webapi( + accessor_property = "className", + getter = svg_element_class_name_getter + )] + class_name: (), +} + +#[derive(WebApiFunctionTemplate)] +#[webapi(name = "SVGURIReference", enumerable)] +struct SvgUriReferencePrototypeAccessorsDeclaration { + #[webapi(accessor_property = "href", getter = svg_uri_href_getter)] + href: (), +} + pub(super) fn install_svg_length_bindings<'s>( scope: &mut v8::PinScope<'s, '_, ()>, template: v8::Local<'s, v8::FunctionTemplate>, @@ -902,6 +939,14 @@ pub(super) fn install_svg_number_bindings<'s>( SvgNumberTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto); } +pub(super) fn install_svg_animated_string_bindings<'s>( + scope: &mut v8::PinScope<'s, '_, ()>, + template: v8::Local<'s, v8::FunctionTemplate>, +) { + let proto = template.prototype_template(scope); + SvgAnimatedStringTemplateAccessorsDeclaration::initialize_prototype_template(scope, proto); +} + pub(super) fn install_svg_animated_length_list_bindings<'s>( scope: &mut v8::PinScope<'s, '_, ()>, template: v8::Local<'s, v8::FunctionTemplate>, @@ -1068,6 +1113,9 @@ pub(super) fn install_svg_element_accessor_bindings<'s>( SvgPatternElementPrototypeAccessorsDeclaration::initialize_prototype_template( scope, prototype, ); + SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); } "SVGGradientElement" => { SvgGradientElementPrototypeAccessorsDeclaration::initialize_prototype_template( @@ -1094,7 +1142,28 @@ pub(super) fn install_svg_element_accessor_bindings<'s>( scope, prototype, ); } - "SVGImageElement" | "SVGUseElement" | "SVGForeignObjectElement" => { + "SVGElement" => { + SvgElementPrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } + "SVGAElement" + | "SVGLinearGradientElement" + | "SVGRadialGradientElement" + | "SVGScriptElement" => { + SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } + "SVGImageElement" | "SVGUseElement" => { + SvgGraphicsBoxElementPrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + SvgUriReferencePrototypeAccessorsDeclaration::initialize_prototype_template( + scope, prototype, + ); + } + "SVGForeignObjectElement" => { SvgGraphicsBoxElementPrototypeAccessorsDeclaration::initialize_prototype_template( scope, prototype, ); diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs index b8ce2f6630..49fa0fa2e3 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/builders.rs @@ -3,6 +3,18 @@ use super::*; use crate::util::serialize_v8_iter_array; use moli_webapi_declare::WebApiObject; +#[derive(WebApiObject)] +#[webapi( + interface = "SVGAnimatedString", + own_to_string_tag = "SVGAnimatedString" +)] +struct SvgAnimatedStringObjectDeclaration { + #[webapi(slot = SVG_ANIMATED_STRING_BASE_VAL_SLOT)] + base_val: String, + #[webapi(slot = SVG_ANIMATED_STRING_ANIM_VAL_SLOT)] + anim_val: String, +} + #[derive(WebApiObject)] #[webapi( interface = "SVGAnimatedNumber", @@ -250,6 +262,19 @@ struct SvgTransformObjectDeclaration<'scope> { set_skew_y: (), } +pub(super) fn build_svg_animated_string_for_attribute<'s>( + scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, + attribute: &str, +) -> v8::Local<'s, v8::Object> { + let value = svg_owner_attribute_value(scope, owner, attribute).unwrap_or_default(); + let object = SvgAnimatedStringObjectDeclaration::new(value.clone(), value) + .bind(scope) + .expect("SVGAnimatedString declaration should bind"); + set_svg_animated_string_owner_attribute(scope, object, owner, attribute); + object +} + pub(super) fn build_svg_animated_length_list<'s>( scope: &mut v8::PinScope<'s, '_>, ) -> v8::Local<'s, v8::Object> { @@ -1787,6 +1812,69 @@ pub(super) fn sync_svg_animated_length_from_owner_attribute<'s>( } } +pub(super) fn set_svg_animated_string_owner_attribute<'s>( + scope: &mut v8::PinScope<'s, '_>, + animated: v8::Local<'s, v8::Object>, + owner: v8::Local<'s, v8::Object>, + attribute: &str, +) { + set_private_value( + scope, + animated, + SVG_ANIMATED_STRING_OWNER_ELEMENT_SLOT, + owner.into(), + ); + set_private_value( + scope, + animated, + SVG_ANIMATED_STRING_OWNER_ATTRIBUTE_SLOT, + v8_string(scope, attribute) + .unwrap_or_else(|| v8str(scope, "")) + .into(), + ); +} + +pub(super) fn sync_svg_animated_string_from_owner_attribute<'s>( + scope: &mut v8::PinScope<'s, '_>, + animated: v8::Local<'s, v8::Object>, +) { + let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_STRING_OWNER_ELEMENT_SLOT) + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(attribute) = + get_private_value(scope, animated, SVG_ANIMATED_STRING_OWNER_ATTRIBUTE_SLOT) + .and_then(|value| value.to_string(scope)) + .map(|value| value.to_rust_string_lossy(scope)) + .filter(|value| !value.is_empty()) + else { + return; + }; + let value = svg_owner_attribute_value(scope, owner, &attribute).unwrap_or_default(); + set_svg_animated_string_values(scope, animated, &value); +} + +pub(super) fn set_svg_animated_string_values( + scope: &mut v8::PinScope<'_, '_>, + animated: v8::Local<'_, v8::Object>, + value: &str, +) { + let value = v8_string(scope, value).unwrap_or_else(|| v8str(scope, "")); + set_private_value( + scope, + animated, + SVG_ANIMATED_STRING_BASE_VAL_SLOT, + value.into(), + ); + set_private_value( + scope, + animated, + SVG_ANIMATED_STRING_ANIM_VAL_SLOT, + value.into(), + ); +} + pub(super) fn set_svg_animated_number_owner_attribute<'s>( scope: &mut v8::PinScope<'s, '_>, animated: v8::Local<'s, v8::Object>, diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs index cf5eeab478..9f8f435537 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/callbacks.rs @@ -46,6 +46,42 @@ fn require_svg_receiver<'s>( false } +pub(super) fn svg_element_class_name_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_animated_string_attribute_getter(scope, args, rv, SVG_ELEMENT_CLASS_NAME_SLOT, "class"); +} + +pub(super) fn svg_uri_href_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + rv: v8::ReturnValue<'_, v8::Value>, +) { + svg_animated_string_attribute_getter(scope, args, rv, SVG_URI_HREF_SLOT, "href"); +} + +fn svg_animated_string_attribute_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, + slot: &str, + attribute: &str, +) { + let owner = args.this(); + if let Some(value) = get_private_value(scope, owner, slot) { + if let Ok(animated) = v8::Local::::try_from(value) { + sync_svg_animated_string_from_owner_attribute(scope, animated); + } + rv.set(value); + return; + } + let value = build_svg_animated_string_for_attribute(scope, owner, attribute); + set_private_value(scope, owner, slot, value.into()); + rv.set(value.into()); +} + pub(super) fn svg_rect_animated_length_getter<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, @@ -282,6 +318,83 @@ pub(super) fn svg_text_positioning_list_getter<'s>( rv.set(value); } +pub(super) fn svg_animated_string_getter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + let Some(name) = callback_data_item( + scope, + &args, + SVG_ANIMATED_ACCESSOR_NAMES, + "SVGAnimatedString attributes", + ) else { + rv.set_undefined(); + return; + }; + if !require_svg_receiver( + scope, + args.this(), + SVG_ANIMATED_STRING_BASE_VAL_SLOT, + "SVGAnimatedString", + &format!("{name} getter"), + ) { + return; + } + let slot = match name { + "baseVal" => SVG_ANIMATED_STRING_BASE_VAL_SLOT, + "animVal" => SVG_ANIMATED_STRING_ANIM_VAL_SLOT, + _ => { + rv.set_undefined(); + return; + } + }; + let animated = args.this(); + sync_svg_animated_string_from_owner_attribute(scope, animated); + rv.set( + get_private_value(scope, animated, slot).unwrap_or_else(|| v8::String::empty(scope).into()), + ); +} + +pub(super) fn svg_animated_string_setter<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + _rv: v8::ReturnValue<'_, v8::Value>, +) { + if !require_svg_receiver( + scope, + args.this(), + SVG_ANIMATED_STRING_BASE_VAL_SLOT, + "SVGAnimatedString", + "baseVal setter", + ) { + return; + } + let animated = args.this(); + let Some(owner) = get_private_value(scope, animated, SVG_ANIMATED_STRING_OWNER_ELEMENT_SLOT) + .and_then(|value| v8::Local::::try_from(value).ok()) + else { + return; + }; + let Some(attribute) = + get_private_value(scope, animated, SVG_ANIMATED_STRING_OWNER_ATTRIBUTE_SLOT) + .and_then(|value| value.to_string(scope)) + .map(|value| value.to_rust_string_lossy(scope)) + .filter(|value| !value.is_empty()) + else { + return; + }; + let Some(value) = crate::native_bridge::element::set_svg_animated_string_base_value( + scope, + owner, + &attribute, + args.get(0), + ) else { + return; + }; + set_svg_animated_string_values(scope, animated, &value); +} + pub(super) fn svg_animated_length_getter<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, diff --git a/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs b/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs index 175d953a79..686ca07408 100644 --- a/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs +++ b/moli-renderer-v8/src/context_bootstrap/svg_runtime/mod.rs @@ -18,6 +18,12 @@ const SVG_GRAPHICS_TRANSFORM_SLOT: &str = "__moliSvgGraphicsTransform"; const SVG_PATTERN_TRANSFORM_SLOT: &str = "__moliSvgPatternTransform"; const SVG_GRADIENT_TRANSFORM_SLOT: &str = "__moliSvgGradientTransform"; const SVG_GEOMETRY_PATH_LENGTH_SLOT: &str = "__moliSvgGeometryPathLength"; +const SVG_ELEMENT_CLASS_NAME_SLOT: &str = "__moliSvgElementClassName"; +const SVG_URI_HREF_SLOT: &str = "__moliSvgUriHref"; +const SVG_ANIMATED_STRING_BASE_VAL_SLOT: &str = "__moliSvgAnimatedStringBaseVal"; +const SVG_ANIMATED_STRING_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedStringAnimVal"; +const SVG_ANIMATED_STRING_OWNER_ELEMENT_SLOT: &str = "__moliSvgAnimatedStringOwnerElement"; +const SVG_ANIMATED_STRING_OWNER_ATTRIBUTE_SLOT: &str = "__moliSvgAnimatedStringOwnerAttribute"; const SVG_ANIMATED_LENGTH_BASE_VAL_SLOT: &str = "__moliSvgAnimatedLengthBaseVal"; const SVG_ANIMATED_LENGTH_ANIM_VAL_SLOT: &str = "__moliSvgAnimatedLengthAnimVal"; const SVG_ANIMATED_LENGTH_LIST_BASE_VAL_SLOT: &str = "__moliSvgAnimatedLengthListBaseVal"; @@ -246,6 +252,7 @@ pub(in crate::context_bootstrap) fn install_svg_template_bindings<'s>( "SVGLength" => bindings::install_svg_length_bindings(scope, template), "SVGNumber" => bindings::install_svg_number_bindings(scope, template), "SVGRect" => rect::install_bindings(scope, template), + "SVGAnimatedString" => bindings::install_svg_animated_string_bindings(scope, template), "SVGAnimatedLength" => bindings::install_svg_animated_length_bindings(scope, template), "SVGLengthList" => bindings::install_svg_length_list_bindings(scope, template), "SVGAnimatedLengthList" => { diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index f21dd32fdf..85203dfa5c 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -54,6 +54,7 @@ mod trusted_types; pub(crate) use script_execution::{ inline_script_source_for_execution, prepare_inline_classic_frame_script_job_for_execution, }; +pub(crate) use trusted_types::set_svg_animated_string_base_value; pub(in crate::native_bridge) use trusted_types::{ TrustedAttributeSetter, trusted_attribute_string_value, trusted_attribute_value_string, }; diff --git a/moli-renderer-v8/src/native_bridge/element/trusted_types.rs b/moli-renderer-v8/src/native_bridge/element/trusted_types.rs index 7ef7d92c49..a3b5beb304 100644 --- a/moli-renderer-v8/src/native_bridge/element/trusted_types.rs +++ b/moli-renderer-v8/src/native_bridge/element/trusted_types.rs @@ -7,6 +7,7 @@ pub(in crate::native_bridge) enum TrustedAttributeSetter { SetAttributeNs, SetAttributeNode, AttrValue, + SvgAnimatedStringBaseVal, } impl TrustedAttributeSetter { @@ -16,6 +17,7 @@ impl TrustedAttributeSetter { Self::SetAttributeNs => "setAttributeNS", Self::SetAttributeNode => "setAttributeNode", Self::AttrValue => "value", + Self::SvgAnimatedStringBaseVal => "baseVal", } } @@ -27,6 +29,9 @@ impl TrustedAttributeSetter { crate::webidl::Context::argument("Element setAttributeNode", 1) } Self::AttrValue => crate::webidl::Context::member("Attr", "value"), + Self::SvgAnimatedStringBaseVal => { + crate::webidl::Context::member("SVGAnimatedString", "baseVal") + } } } } @@ -276,6 +281,27 @@ pub(in crate::native_bridge) fn trusted_attribute_string_value<'s>( ) } +pub(crate) fn set_svg_animated_string_base_value<'s>( + scope: &mut v8::PinScope<'s, '_>, + owner: v8::Local<'s, v8::Object>, + attribute: &str, + value: v8::Local<'s, v8::Value>, +) -> Option { + let (runtime_ptr, handle) = + crate::native_bridge::node_runtime_and_handle_from_object(scope, owner).ok()?; + let value = trusted_attribute_value_string( + scope, + Some((runtime_ptr, handle)), + None, + attribute, + value, + TrustedAttributeSetter::SvgAnimatedStringBaseVal, + )?; + let _ = + unsafe { &mut *runtime_ptr }.set_attribute(scope, runtime_ptr, handle, attribute, &value); + Some(value) +} + pub(crate) fn trusted_script_source_for_execution( scope: &mut v8::PinScope<'_, '_>, runtime_ptr: *mut JsContextHost, diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs index 67fe52e396..84c2c9b8fa 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/trusted_types.rs @@ -1161,6 +1161,117 @@ fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() { ); } +#[test] +fn svg_script_href_base_val_uses_the_owner_reflected_trusted_script_url_sink() { + let mut vm = new_storage_test_vm("https://svg-script-href-trusted-types.test/"); + vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]); + + let result = vm + .eval( + r#" +(() => { + const violations = []; + document.addEventListener("securitypolicyviolation", event => { + violations.push({ + blockedURI: event.blockedURI, + effectiveDirective: event.effectiveDirective, + sample: event.sample + }); + }); + globalThis.__svgScriptHrefViolations = violations; + + const svgScript = document.createElementNS("http://www.w3.org/2000/svg", "script"); + const href = svgScript.href; + let illegalConstructor = false; + try { + new SVGAnimatedString(); + } catch (error) { + illegalConstructor = error instanceof TypeError; + } + let plainRejected = false; + try { + href.baseVal = "plain.js"; + } catch (error) { + plainRejected = error instanceof TypeError; + } + const rejectedAttribute = svgScript.getAttribute("href"); + + const explicit = trustedTypes.createPolicy("svg-script-href", { + createScriptURL: value => value + }); + href.baseVal = explicit.createScriptURL("trusted.js"); + const trustedValues = [ + svgScript.getAttribute("href"), + href.baseVal, + href.animVal + ]; + svgScript.setAttribute("href", explicit.createScriptURL("attribute.js")); + const externallySynced = [href.baseVal, href.animVal]; + const className = svgScript.className; + className.baseVal = explicit.createScriptURL("trusted-class"); + const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); + const useHref = use.href; + useHref.baseVal = explicit.createScriptURL("trusted-use"); + + const defaultCalls = []; + trustedTypes.createPolicy("default", { + createScriptURL: (value, type, sink) => { + defaultCalls.push([value, type, sink]); + return `safe-${value}`; + } + }); + href.baseVal = "default-input"; + className.baseVal = "plain-class"; + useHref.baseVal = "plain-use"; + + return JSON.stringify({ + shape: [ + href instanceof SVGAnimatedString, + Object.prototype.toString.call(href), + svgScript.href === href, + illegalConstructor + ], + plainRejected, + rejectedAttribute, + trustedValues, + externallySynced, + ordinaryAnimatedStrings: [ + className instanceof SVGAnimatedString, + svgScript.className === className, + className.baseVal, + className.animVal, + svgScript.getAttribute("class"), + useHref instanceof SVGAnimatedString, + use.href === useHref, + useHref.baseVal, + useHref.animVal, + use.getAttribute("href") + ], + defaulted: [svgScript.getAttribute("href"), href.baseVal, href.animVal], + defaultCalls, + violations + }); +})() +"#, + ) + .expect("SVGScriptElement href Trusted Types sink probe should evaluate"); + + assert_eq!( + result, + r#"{"shape":[true,"[object SVGAnimatedString]",true,true],"plainRejected":true,"rejectedAttribute":null,"trustedValues":["trusted.js","trusted.js","trusted.js"],"externallySynced":["attribute.js","attribute.js"],"ordinaryAnimatedStrings":[true,true,"plain-class","plain-class","plain-class",true,true,"plain-use","plain-use","plain-use"],"defaulted":["safe-default-input","safe-default-input","safe-default-input"],"defaultCalls":[["default-input","TrustedScriptURL","SVGScriptElement href"]],"violations":[]}"# + ); + + assert_eq!( + drain_pre_domcontentloaded_non_script_page_tasks_for_test(&mut vm), + 1 + ); + assert_eq!( + vm.eval("JSON.stringify(globalThis.__svgScriptHrefViolations)") + .expect("queued SVGScriptElement href violation should be observable"), + r#"[{"blockedURI":"trusted-types-sink","effectiveDirective":"require-trusted-types-for","sample":"SVGScriptElement href|plain.js"}]"# + ); +} + #[test] fn attached_attribute_mutations_recheck_trusted_types_after_domstring_conversion() { let mut vm = new_storage_test_vm("https://attached-attribute-trusted-types.test/");