From e0b2f6d9e49b76972343c8d4a8ee22ee8e7eeee0 Mon Sep 17 00:00:00 2001 From: ldm0 Date: Thu, 16 Jul 2026 05:47:22 +0800 Subject: [PATCH] fix(trusted-types): enforce Element attribute sinks --- .../src/context_bootstrap/trusted_types.rs | 20 +- .../detached_surface/element_attributes.rs | 28 ++- moli-renderer-v8/src/native_bridge/element.rs | 3 + .../src/native_bridge/element/attributes.rs | 12 +- .../element/attributes/mutation.rs | 44 +++- .../element/event_handlers/generic.rs | 20 ++ .../element/event_handlers/mod.rs | 1 + .../native_bridge/element/trusted_types.rs | 123 ++++++++++ .../tests/browser_api/trusted_types.rs | 229 ++++++++++++++++++ 9 files changed, 447 insertions(+), 33 deletions(-) diff --git a/moli-renderer-v8/src/context_bootstrap/trusted_types.rs b/moli-renderer-v8/src/context_bootstrap/trusted_types.rs index 3cec491045..2e9ed8f64c 100644 --- a/moli-renderer-v8/src/context_bootstrap/trusted_types.rs +++ b/moli-renderer-v8/src/context_bootstrap/trusted_types.rs @@ -264,7 +264,7 @@ pub(crate) fn trusted_script_url_string_or_throw<'s>( scope: &mut v8::PinScope<'s, '_>, value: v8::Local<'s, v8::Value>, requirements: TrustedTypesForScriptRequirements, - sink: &'static str, + sink: &str, api_name: &'static str, ) -> Option { trusted_type_string_or_throw( @@ -282,7 +282,7 @@ pub(crate) fn trusted_html_string_or_throw<'s>( scope: &mut v8::PinScope<'s, '_>, value: v8::Local<'s, v8::Value>, requirements: TrustedTypesForScriptRequirements, - sink: &'static str, + sink: &str, api_name: &'static str, ) -> Option { trusted_type_string_or_throw( @@ -307,7 +307,7 @@ pub(crate) fn trusted_script_string_or_type_error<'s>( scope: &mut v8::PinScope<'s, '_>, value: v8::Local<'s, v8::Value>, requirements: TrustedTypesForScriptRequirements, - sink: &'static str, + sink: &str, api_name: &'static str, ) -> Option { trusted_type_string_or_throw( @@ -324,7 +324,7 @@ pub(crate) fn trusted_script_string_or_type_error<'s>( pub(crate) fn trusted_script_string_for_script_element_execution( scope: &mut v8::PinScope<'_, '_>, original: &str, - sink: &'static str, + sink: &str, ) -> Option { let default_value = { let try_catch = std::pin::pin!(v8::TryCatch::new(scope)); @@ -572,7 +572,7 @@ fn trusted_type_string_or_throw<'s>( value: v8::Local<'s, v8::Value>, kind: TrustedTypeKind, requirements: TrustedTypesForScriptRequirements, - sink: &'static str, + sink: &str, api_name: &'static str, error_kind: TrustedTypeErrorKind, ) -> Option { @@ -607,7 +607,7 @@ fn apply_default_trusted_type_policy<'s>( scope: &mut v8::PinScope<'s, '_>, input: &str, kind: TrustedTypeKind, - sink: &'static str, + sink: &str, error_kind: TrustedTypeErrorKind, ) -> Option { match apply_default_trusted_type_policy_outcome(scope, input, kind, sink) { @@ -625,7 +625,7 @@ fn apply_default_trusted_type_policy_outcome<'s>( scope: &mut v8::PinScope<'s, '_>, input: &str, kind: TrustedTypeKind, - sink: &'static str, + sink: &str, ) -> DefaultTrustedTypePolicyOutcome { let global = scope.get_current_context().global(scope); let Some(policy) = get_private_value(scope, global, TRUSTED_TYPES_DEFAULT_POLICY_SLOT) @@ -637,7 +637,9 @@ fn apply_default_trusted_type_policy_outcome<'s>( return DefaultTrustedTypePolicyOutcome::Exception; }; let type_name = v8str(scope, kind.constructor_name()); - let sink = v8str(scope, sink); + let Some(sink) = v8_string(scope, sink) else { + return DefaultTrustedTypePolicyOutcome::Exception; + }; let args = [input.into(), type_name.into(), sink.into()]; match invoke_policy_callback(scope, policy, kind, &args) { TrustedTypePolicyCallbackOutcome::Missing => DefaultTrustedTypePolicyOutcome::Unavailable, @@ -1240,7 +1242,7 @@ fn throw_trusted_type_error( error_kind: TrustedTypeErrorKind, api_name: &'static str, kind: TrustedTypeKind, - sink: &'static str, + sink: &str, ) { let message = format!( "Failed to execute '{api_name}': This document requires '{}' assignment for the '{}' sink.", diff --git a/moli-renderer-v8/src/native_bridge/document/detached_surface/element_attributes.rs b/moli-renderer-v8/src/native_bridge/document/detached_surface/element_attributes.rs index 5e1b9d3b1a..4dad14749f 100644 --- a/moli-renderer-v8/src/native_bridge/document/detached_surface/element_attributes.rs +++ b/moli-renderer-v8/src/native_bridge/document/detached_surface/element_attributes.rs @@ -233,11 +233,15 @@ pub(in crate::native_bridge) fn bridge_detached_set_attribute_callback<'a>( ); return; } - let Some(value) = args - .get(2) - .to_string(scope) - .map(|value| value.to_rust_string_lossy(scope)) - else { + let runtime_and_handle = detached_surface_runtime_and_handle(scope, args.this(), element); + let Some(value) = super::super::super::element::trusted_attribute_value_string( + scope, + runtime_and_handle, + None, + &normalized, + args.get(2), + super::super::super::element::TrustedAttributeSetter::SetAttribute, + ) else { return; }; with_detached_surface_attribute_reaction_scope(scope, args.this(), element, |scope| { @@ -293,11 +297,15 @@ pub(in crate::native_bridge) fn bridge_detached_set_attribute_ns_callback<'a>( ); return; } - let Some(value) = args - .get(3) - .to_string(scope) - .map(|value| value.to_rust_string_lossy(scope)) - else { + let runtime_and_handle = detached_surface_runtime_and_handle(scope, args.this(), element); + let Some(value) = super::super::super::element::trusted_attribute_value_string( + scope, + runtime_and_handle, + namespace.as_deref(), + &local_name, + args.get(3), + super::super::super::element::TrustedAttributeSetter::SetAttributeNs, + ) else { return; }; with_detached_surface_attribute_reaction_scope(scope, args.this(), element, |scope| { diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index 62521aaa28..07058c183a 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -54,6 +54,9 @@ mod trusted_types; pub(crate) use script_execution::{ inline_script_source_for_execution, prepare_inline_classic_frame_script_job_for_execution, }; +pub(in crate::native_bridge) use trusted_types::{ + TrustedAttributeSetter, trusted_attribute_value_string, +}; use trusted_types::{ TrustedScriptElementSink, trusted_script_element_sink_string, trusted_script_url_sink_string, }; diff --git a/moli-renderer-v8/src/native_bridge/element/attributes.rs b/moli-renderer-v8/src/native_bridge/element/attributes.rs index 841aab453b..f0f09d396d 100644 --- a/moli-renderer-v8/src/native_bridge/element/attributes.rs +++ b/moli-renderer-v8/src/native_bridge/element/attributes.rs @@ -22,22 +22,22 @@ struct AttributeNamespaceNameArgs { #[derive(crate::webidl::WebIdlArgs)] #[webidl(prefix = "Element setAttribute")] -struct SetAttributeArgs { +struct SetAttributeArgs<'s> { #[webidl(required)] name: String, - #[webidl(required)] - value: String, + #[webidl(required, converter = "raw")] + value: v8::Local<'s, v8::Value>, } #[derive(crate::webidl::WebIdlArgs)] #[webidl(prefix = "Element setAttributeNS")] -struct SetAttributeNsArgs { +struct SetAttributeNsArgs<'s> { #[webidl(required, nullable)] namespace: Option, #[webidl(required)] qualified_name: String, - #[webidl(required)] - value: String, + #[webidl(required, converter = "raw")] + value: v8::Local<'s, v8::Value>, } #[derive(crate::webidl::WebIdlArgs)] diff --git a/moli-renderer-v8/src/native_bridge/element/attributes/mutation.rs b/moli-renderer-v8/src/native_bridge/element/attributes/mutation.rs index 2a635a62a2..1fef558ea6 100644 --- a/moli-renderer-v8/src/native_bridge/element/attributes/mutation.rs +++ b/moli-renderer-v8/src/native_bridge/element/attributes/mutation.rs @@ -12,11 +12,12 @@ use super::super::super::{ throw_dom_exception, validate_attribute_name, validate_qualified_name_and_namespace, }; use super::super::{ - element_has_attribute, remove_live_element_attribute_appending_to_current_reaction_queue, + TrustedAttributeSetter, element_has_attribute, + remove_live_element_attribute_appending_to_current_reaction_queue, remove_live_element_attribute_ns_appending_to_current_reaction_queue, set_live_element_attribute_appending_to_current_reaction_queue, set_live_element_attribute_ns_appending_to_current_reaction_queue, - update_iframe_snapshot_navigation, + trusted_attribute_value_string, update_iframe_snapshot_navigation, }; use super::{ AttributeNameArgs, AttributeNamespaceNameArgs, SetAttributeArgs, SetAttributeNsArgs, @@ -88,12 +89,28 @@ pub(in crate::native_bridge) fn node_set_attribute_callback<'s>( throw_invalid_attribute_name(scope); return; } + let runtime = unsafe { &*runtime_ptr }; + let normalized_name = runtime + .dom_host() + .node(handle) + .and_then(Node::as_element) + .map(|element| element.normalized_attribute_name(&parsed.name)) + .unwrap_or_else(|| parsed.name.clone()); + let Some(value) = trusted_attribute_value_string( + scope, + Some((runtime_ptr, handle)), + None, + &normalized_name, + parsed.value, + TrustedAttributeSetter::SetAttribute, + ) else { + rv.set_undefined(); + return; + }; if parsed.name.eq_ignore_ascii_case("src") - && unsafe { &*runtime_ptr } - .dom_host() - .is_html_element_named(handle, "iframe") + && runtime.dom_host().is_html_element_named(handle, "iframe") { - update_iframe_snapshot_navigation(scope, runtime_ptr, handle, &parsed.value); + update_iframe_snapshot_navigation(scope, runtime_ptr, handle, &value); rv.set_undefined(); return; } @@ -103,7 +120,7 @@ pub(in crate::native_bridge) fn node_set_attribute_callback<'s>( runtime_ptr, handle, &parsed.name, - &parsed.value, + &value, ); }); rv.set_undefined(); @@ -136,6 +153,17 @@ pub(in crate::native_bridge) fn node_set_attribute_ns_callback<'s>( return; } }; + let Some(value) = trusted_attribute_value_string( + scope, + Some((runtime_ptr, handle)), + namespace.as_deref(), + &local_name, + parsed.value, + TrustedAttributeSetter::SetAttributeNs, + ) else { + rv.set_undefined(); + return; + }; custom_elements::with_custom_element_reaction_scope(scope, runtime_ptr, |scope| { let _ = set_live_element_attribute_ns_appending_to_current_reaction_queue( scope, @@ -145,7 +173,7 @@ pub(in crate::native_bridge) fn node_set_attribute_ns_callback<'s>( prefix.as_deref(), &local_name, &parsed.qualified_name, - &parsed.value, + &value, ); }); rv.set_undefined(); diff --git a/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs b/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs index f664b61a1a..4e9611da82 100644 --- a/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs +++ b/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs @@ -1,4 +1,5 @@ use crate::{ + context_bootstrap::WINDOW_EVENT_HANDLER_PROPERTIES, document_runtime::EventTargetHandle, util::{ context_host_ptr_from_global_bridge, node_wrapper_from_handle, throw_type_error, v8_string, @@ -110,6 +111,25 @@ const DOCUMENT_EVENT_HANDLER_PROPERTIES: &[&str] = &[ "onpointerlockerror", "onreadystatechange", ]; +const ELEMENT_SPECIFIC_EVENT_HANDLER_PROPERTIES: &[&str] = &[ + "onencrypted", + "onwaitingforkey", + "onbegin", + "onend", + "onrepeat", +]; + +pub(in crate::native_bridge::element) fn is_element_event_handler_content_attribute_name( + name: &str, +) -> bool { + GENERIC_EVENT_HANDLER_PROPERTIES + .iter() + .chain(WINDOW_EVENT_HANDLER_PROPERTIES) + .chain(ELEMENT_SPECIFIC_EVENT_HANDLER_PROPERTIES) + .copied() + .chain(["onmessageerror"]) + .any(|candidate| candidate == name) +} #[derive(Clone, Copy)] pub(crate) enum GlobalEventHandlerOwner { diff --git a/moli-renderer-v8/src/native_bridge/element/event_handlers/mod.rs b/moli-renderer-v8/src/native_bridge/element/event_handlers/mod.rs index 3c1c6e7c32..445540dedb 100644 --- a/moli-renderer-v8/src/native_bridge/element/event_handlers/mod.rs +++ b/moli-renderer-v8/src/native_bridge/element/event_handlers/mod.rs @@ -14,6 +14,7 @@ pub(crate) use body_window::compile_window_body_onmessageerror_attribute; pub(super) use body_window::{ body_onmessageerror_getter_function, body_onmessageerror_setter_function, }; +pub(super) use generic::is_element_event_handler_content_attribute_name; pub(crate) use generic::{ GlobalEventHandlerOwner, install_global_event_handler_template_bindings, node_event_handler_getter_function, node_event_handler_setter_function, 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 2872ce2f48..bc76d75763 100644 --- a/moli-renderer-v8/src/native_bridge/element/trusted_types.rs +++ b/moli-renderer-v8/src/native_bridge/element/trusted_types.rs @@ -1,6 +1,33 @@ use super::JsContextHost; use crate::document_runtime::DomHandle; +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub(in crate::native_bridge) enum TrustedAttributeSetter { + SetAttribute, + SetAttributeNs, +} + +impl TrustedAttributeSetter { + fn api_name(self) -> &'static str { + match self { + Self::SetAttribute => "setAttribute", + Self::SetAttributeNs => "setAttributeNS", + } + } + + fn conversion_context(self) -> crate::webidl::Context { + match self { + Self::SetAttribute => crate::webidl::Context::argument("Element setAttribute", 2), + Self::SetAttributeNs => crate::webidl::Context::argument("Element setAttributeNS", 3), + } + } +} + +enum TrustedAttributeSink { + Script(String), + ScriptUrl(&'static str), +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(in crate::native_bridge::element) enum TrustedHtmlSink { ElementInnerHtml, @@ -126,6 +153,102 @@ pub(in crate::native_bridge::element) fn trusted_script_url_sink_string<'s>( ) } +pub(in crate::native_bridge) fn trusted_attribute_value_string<'s>( + scope: &mut v8::PinScope<'s, '_>, + runtime_and_handle: Option<(*mut JsContextHost, DomHandle)>, + attribute_namespace: Option<&str>, + local_name: &str, + value: v8::Local<'s, v8::Value>, + setter: TrustedAttributeSetter, +) -> Option { + let sink = runtime_and_handle.and_then(|(runtime_ptr, handle)| { + let element = unsafe { &*runtime_ptr } + .dom_host() + .node(handle) + .and_then(|node| node.as_element())?; + let element_namespace = element.namespace(); + + if attribute_namespace.is_none() + && matches!( + element_namespace, + "http://www.w3.org/1999/xhtml" + | "http://www.w3.org/2000/svg" + | "http://www.w3.org/1998/Math/MathML" + ) + && super::event_handlers::is_element_event_handler_content_attribute_name(local_name) + { + return Some(( + runtime_ptr, + TrustedAttributeSink::Script(format!("Element {local_name}")), + )); + } + + let sink = match ( + element_namespace, + element.local_name(), + attribute_namespace, + local_name, + ) { + ("http://www.w3.org/1999/xhtml", "script", None, "src") => { + Some(TrustedAttributeSink::ScriptUrl("HTMLScriptElement src")) + } + ("http://www.w3.org/1999/xhtml", "embed", None, "src") => { + Some(TrustedAttributeSink::ScriptUrl("HTMLEmbedElement src")) + } + ("http://www.w3.org/1999/xhtml", "object", None, "data") => { + Some(TrustedAttributeSink::ScriptUrl("HTMLObjectElement data")) + } + ("http://www.w3.org/1999/xhtml", "object", None, "codebase") => Some( + TrustedAttributeSink::ScriptUrl("HTMLObjectElement codebase"), + ), + ( + "http://www.w3.org/2000/svg", + "script", + None | Some("http://www.w3.org/1999/xlink"), + "href", + ) => Some(TrustedAttributeSink::ScriptUrl("SVGScriptElement href")), + _ => None, + }?; + Some((runtime_ptr, sink)) + }); + + if let Some((runtime_ptr, sink)) = sink { + let requirements = unsafe { &*runtime_ptr }.trusted_types_for_script_requirements(scope); + return match sink { + TrustedAttributeSink::Script(sink) => { + crate::context_bootstrap::trusted_script_string_or_type_error( + scope, + value, + requirements, + &sink, + setter.api_name(), + ) + } + TrustedAttributeSink::ScriptUrl(sink) => { + crate::context_bootstrap::trusted_script_url_string_or_throw( + scope, + value, + requirements, + sink, + setter.api_name(), + ) + } + }; + } + + match crate::webidl::convert::( + scope, + value, + setter.conversion_context(), + ) { + Ok(value) => Some(value.0), + Err(error) => { + crate::webidl::throw_error(scope, &error); + None + } + } +} + 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 b03be16d29..e45ad4c36b 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 @@ -958,6 +958,235 @@ fn script_src_enforces_trusted_script_url_and_applies_the_default_policy() { ); } +#[test] +fn event_handler_attribute_writes_enforce_trusted_script_at_the_attribute_boundary() { + let mut vm = new_storage_test_vm("https://event-handler-attribute-trusted-types.test/"); + vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]); + + let result = vm + .eval( + r#" +(() => { + globalThis.__eventHandlerAttributeRuns = []; + const root = document.body || + (document.documentElement || document.appendChild(document.createElement("html"))) + .appendChild(document.createElement("body")); + const policy = trustedTypes.createPolicy("event-handler-script", { + createScript: value => value, + createHTML: value => value + }); + + const trusted = document.createElement("button"); + trusted.setAttribute( + "onclick", + policy.createScript("globalThis.__eventHandlerAttributeRuns.push('trusted')") + ); + root.appendChild(trusted); + trusted.click(); + + const trustedNs = document.createElement("button"); + trustedNs.setAttributeNS( + null, + "onclick", + policy.createScript("globalThis.__eventHandlerAttributeRuns.push('trusted-ns')") + ); + root.appendChild(trustedNs); + trustedNs.click(); + + const unsuitable = document.createElement("button"); + let unsuitableRejected = false; + try { + unsuitable.setAttribute( + "onclick", + policy.createHTML("globalThis.__eventHandlerAttributeRuns.push('unsuitable')") + ); + } catch (error) { + unsuitableRejected = error instanceof TypeError; + } + + const plain = document.createElement("button"); + let plainRejected = false; + try { + plain.setAttribute( + "onclick", + "globalThis.__eventHandlerAttributeRuns.push('plain')" + ); + } catch (error) { + plainRejected = error instanceof TypeError; + } + + const plainNs = document.createElement("button"); + let plainNsRejected = false; + try { + plainNs.setAttributeNS( + null, + "onclick", + "globalThis.__eventHandlerAttributeRuns.push('plain-ns')" + ); + } catch (error) { + plainNsRejected = error instanceof TypeError; + } + + const ordinary = document.createElement("div"); + ordinary.setAttribute("data-onclick", "plain-data"); + ordinary.setAttribute("ondoesnotexist", "plain-unknown"); + ordinary.setAttributeNS("urn:test", "onclick", "plain-namespaced"); + const uppercaseSvg = document.createElementNS("http://www.w3.org/2000/svg", "g"); + uppercaseSvg.setAttributeNS(null, "ONCLICK", "plain-uppercase"); + const elementSpecificRejected = ["onwaitingforkey", "onbegin"].map(name => { + try { + document.createElement("div").setAttribute(name, "plain-special"); + return false; + } catch (error) { + return error instanceof TypeError; + } + }); + + const defaultCalls = []; + trustedTypes.createPolicy("default", { + createScript: (value, type, sink) => { + defaultCalls.push([value, type, sink]); + return "globalThis.__eventHandlerAttributeRuns.push('default')"; + } + }); + const defaulted = document.createElement("button"); + defaulted.setAttribute("onclick", "default-input"); + root.appendChild(defaulted); + defaulted.click(); + + return JSON.stringify({ + runs: globalThis.__eventHandlerAttributeRuns, + unsuitableRejected, + unsuitableAttribute: unsuitable.getAttribute("onclick"), + plainRejected, + plainAttribute: plain.getAttribute("onclick"), + plainNsRejected, + plainNsAttribute: plainNs.getAttribute("onclick"), + ordinary: [ + ordinary.getAttribute("data-onclick"), + ordinary.getAttribute("ondoesnotexist"), + ordinary.getAttributeNS("urn:test", "onclick"), + uppercaseSvg.getAttribute("ONCLICK") + ], + elementSpecificRejected, + defaultedAttribute: defaulted.getAttribute("onclick"), + defaultCalls + }); +})() +"#, + ) + .expect("event handler attribute Trusted Types sink probe should evaluate"); + + assert_eq!( + result, + r#"{"runs":["trusted","trusted-ns","default"],"unsuitableRejected":true,"unsuitableAttribute":null,"plainRejected":true,"plainAttribute":null,"plainNsRejected":true,"plainNsAttribute":null,"ordinary":["plain-data","plain-unknown","plain-namespaced","plain-uppercase"],"elementSpecificRejected":[true,true],"defaultedAttribute":"globalThis.__eventHandlerAttributeRuns.push('default')","defaultCalls":[["default-input","TrustedScript","Element onclick"]]}"# + ); +} + +#[test] +fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() { + let mut vm = new_storage_test_vm("https://url-attribute-trusted-types.test/"); + vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]); + + let result = vm + .eval( + r#" +(() => { + const xlink = "http://www.w3.org/1999/xlink"; + const svg = "http://www.w3.org/2000/svg"; + const policy = trustedTypes.createPolicy("attribute-url", { + createScript: value => value, + createScriptURL: value => value + }); + const trusted = value => policy.createScriptURL(value); + + const htmlScript = document.createElement("script"); + htmlScript.setAttribute("src", trusted("script.js")); + const htmlScriptNs = document.createElement("script"); + htmlScriptNs.setAttributeNS(null, "src", trusted("script-ns.js")); + const svgScript = document.createElementNS(svg, "script"); + svgScript.setAttribute("href", trusted("svg.js")); + const svgScriptXlink = document.createElementNS(svg, "script"); + svgScriptXlink.setAttributeNS(xlink, "xlink:href", trusted("svg-xlink.js")); + const embed = document.createElement("embed"); + embed.setAttribute("src", trusted("embed.js")); + const object = document.createElement("object"); + object.setAttribute("data", trusted("object-data.js")); + object.setAttribute("codebase", trusted("object-codebase.js")); + + const rejected = []; + for (const [element, setter] of [ + [document.createElement("script"), element => element.setAttribute("src", "plain")], + [document.createElement("script"), element => element.setAttributeNS(null, "src", null)], + [document.createElementNS(svg, "script"), element => element.setAttribute("href", "plain")], + [document.createElementNS(svg, "script"), element => element.setAttributeNS(xlink, "xlink:href", policy.createScript("wrong"))], + [document.createElement("embed"), element => element.setAttribute("src", "plain")], + [document.createElement("object"), element => element.setAttribute("data", "plain")], + [document.createElement("object"), element => element.setAttribute("codebase", "plain")] + ]) { + try { + setter(element); + rejected.push(false); + } catch (error) { + rejected.push(error instanceof TypeError); + } + } + + const ordinary = document.createElement("script"); + ordinary.setAttributeNS("urn:test", "src", "namespaced"); + const uppercaseSvg = document.createElementNS(svg, "script"); + uppercaseSvg.setAttributeNS(null, "HREF", "uppercase"); + const div = document.createElement("div"); + div.setAttribute("src", "plain-div"); + + const defaultCalls = []; + trustedTypes.createPolicy("default", { + createScriptURL: (value, type, sink) => { + defaultCalls.push([value, type, sink]); + return `safe-${value}`; + } + }); + const defaultHtml = document.createElement("script"); + defaultHtml.setAttribute("src", "default-html"); + const defaultSvg = document.createElementNS(svg, "script"); + defaultSvg.setAttributeNS(xlink, "xlink:href", "default-svg"); + const defaultObject = document.createElement("object"); + defaultObject.setAttribute("codebase", "default-object"); + + return JSON.stringify({ + trustedValues: [ + htmlScript.getAttribute("src"), + htmlScriptNs.getAttribute("src"), + svgScript.getAttribute("href"), + svgScriptXlink.getAttributeNS(xlink, "href"), + embed.getAttribute("src"), + object.getAttribute("data"), + object.getAttribute("codebase") + ], + rejected, + ordinary: [ + ordinary.getAttributeNS("urn:test", "src"), + uppercaseSvg.getAttribute("HREF"), + div.getAttribute("src") + ], + defaultValues: [ + defaultHtml.getAttribute("src"), + defaultSvg.getAttributeNS(xlink, "href"), + defaultObject.getAttribute("codebase") + ], + defaultCalls + }); +})() +"#, + ) + .expect("URL attribute Trusted Types sink table probe should evaluate"); + + assert_eq!( + result, + r#"{"trustedValues":["script.js","script-ns.js","svg.js","svg-xlink.js","embed.js","object-data.js","object-codebase.js"],"rejected":[true,true,true,true,true,true,true],"ordinary":["namespaced","uppercase","plain-div"],"defaultValues":["safe-default-html","safe-default-svg","safe-default-object"],"defaultCalls":[["default-html","TrustedScriptURL","HTMLScriptElement src"],["default-svg","TrustedScriptURL","SVGScriptElement href"],["default-object","TrustedScriptURL","HTMLObjectElement codebase"]]}"# + ); +} + #[test] fn empty_default_policy_reports_each_rejected_element_sink() { let mut vm = new_storage_test_vm("https://empty-default-policy.test/");