diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index c109d0a75a..6d14522ec3 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -3639,8 +3639,6 @@ trusted-types/ServiceWorkerContainer-register-from-DedicatedWorker.https.html trusted-types/ServiceWorkerContainer-register-from-ServiceWorker.https.html trusted-types/ServiceWorkerContainer-register-from-SharedWorker.https.html trusted-types/TrustedType-AttributeNodes.html -trusted-types/TrustedTypePolicyFactory-getAttributeType-namespace.html -trusted-types/TrustedTypePolicyFactory-getAttributeType-svg.html trusted-types/TrustedTypePolicyFactory-getAttributeType.html trusted-types/Window-TrustedTypes.html trusted-types/block-Document-execCommand.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index 2dc78a1d01..6893f997bb 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -7994,6 +7994,8 @@ trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.html trusted-types/TrustedTypePolicyFactory-createPolicy-non-tt-policy-name.html trusted-types/TrustedTypePolicyFactory-createPolicy-unenforced.html trusted-types/TrustedTypePolicyFactory-defaultPolicy.html +trusted-types/TrustedTypePolicyFactory-getAttributeType-namespace.html +trusted-types/TrustedTypePolicyFactory-getAttributeType-svg.html trusted-types/TrustedTypePolicyFactory-isXXX.html trusted-types/Window-block-eval-function-constructor.html trusted-types/Window-setTimeout-setInterval.html diff --git a/moli-renderer-v8/src/context_bootstrap/trusted_types.rs b/moli-renderer-v8/src/context_bootstrap/trusted_types.rs index 9ab57afef4..7cdf374fde 100644 --- a/moli-renderer-v8/src/context_bootstrap/trusted_types.rs +++ b/moli-renderer-v8/src/context_bootstrap/trusted_types.rs @@ -90,6 +90,13 @@ struct TrustedTypePolicyFactoryInterfaceDeclaration { enumerable )] empty_script: (), + #[webapi( + method = "getAttributeType", + callback = trusted_types_get_attribute_type_callback, + length = 2, + enumerable + )] + get_attribute_type: (), #[webapi( accessor_property = "defaultPolicy", getter = trusted_types_default_policy_getter_callback, @@ -109,6 +116,19 @@ struct TrustedTypesFactoryObjectDeclaration<'scope> { created_policy_names: v8::Local<'scope, v8::Array>, } +#[derive(webidl::WebIdlArgs)] +#[webidl(prefix = "TrustedTypePolicyFactory.getAttributeType")] +struct TrustedTypesGetAttributeTypeArgs { + #[webidl(required)] + tag_name: String, + #[webidl(required)] + attribute: String, + #[webidl(nullable)] + element_namespace: Option, + #[webidl(nullable)] + attribute_namespace: Option, +} + #[derive(WebApiObject)] #[webapi(plain)] struct TrustedTypeObjectDeclaration<'scope> { @@ -1078,6 +1098,41 @@ fn trusted_types_is_script_url_callback<'s>( rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::ScriptUrl).is_some()); } +fn trusted_types_get_attribute_type_callback<'s>( + scope: &mut v8::PinScope<'s, '_>, + args: v8::FunctionCallbackArguments<'s>, + mut rv: v8::ReturnValue<'_, v8::Value>, +) { + if !trusted_types_factory_receiver_is_valid(scope, args.this()) { + return; + } + let Some(parsed) = webidl::parse_args::(scope, &args) else { + return; + }; + let element_namespace = parsed + .element_namespace + .filter(|namespace| !namespace.is_empty()) + .unwrap_or_else(|| crate::native_bridge::document::XHTML_NS.to_owned()); + let attribute_namespace = parsed + .attribute_namespace + .filter(|namespace| !namespace.is_empty()); + let tag_name = parsed.tag_name.to_ascii_lowercase(); + let attribute = parsed.attribute.to_ascii_lowercase(); + let Some(type_name) = crate::native_bridge::element::trusted_attribute_type_name_for_names( + &element_namespace, + &tag_name, + attribute_namespace.as_deref(), + &attribute, + ) else { + rv.set_null(); + return; + }; + let Some(type_name) = v8_string(scope, type_name) else { + return; + }; + rv.set(type_name.into()); +} + #[derive(Clone, Copy)] enum FunctionConstructorKind { Function, diff --git a/moli-renderer-v8/src/native_bridge/element.rs b/moli-renderer-v8/src/native_bridge/element.rs index 7dc365354d..03d0da0fd0 100644 --- a/moli-renderer-v8/src/native_bridge/element.rs +++ b/moli-renderer-v8/src/native_bridge/element.rs @@ -54,13 +54,15 @@ 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, }; use trusted_types::{ TrustedScriptElementSink, trusted_script_element_sink_string, trusted_script_url_sink_string, }; +pub(crate) use trusted_types::{ + set_svg_animated_string_base_value, trusted_attribute_type_name_for_names, +}; pub(crate) use forms::{ autocomplete_field_name, autofill_related_form_control_elements, form_associated_form_owner, 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 a3b5beb304..882e406c47 100644 --- a/moli-renderer-v8/src/native_bridge/element/trusted_types.rs +++ b/moli-renderer-v8/src/native_bridge/element/trusted_types.rs @@ -41,6 +41,80 @@ enum TrustedAttributeSink { ScriptUrl(&'static str), } +impl TrustedAttributeSink { + fn type_name(&self) -> &'static str { + match self { + Self::Script(_) => "TrustedScript", + Self::ScriptUrl(_) => "TrustedScriptURL", + } + } +} + +fn trusted_attribute_sink_for_names( + element_namespace: &str, + element_local_name: &str, + attribute_namespace: Option<&str>, + attribute_local_name: &str, +) -> Option { + 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( + attribute_local_name, + ) + { + return Some(TrustedAttributeSink::Script(format!( + "Element {attribute_local_name}" + ))); + } + + match ( + element_namespace, + element_local_name, + attribute_namespace, + attribute_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, + } +} + +pub(crate) fn trusted_attribute_type_name_for_names( + element_namespace: &str, + element_local_name: &str, + attribute_namespace: Option<&str>, + attribute_local_name: &str, +) -> Option<&'static str> { + trusted_attribute_sink_for_names( + element_namespace, + element_local_name, + attribute_namespace, + attribute_local_name, + ) + .map(|sink| sink.type_name()) +} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub(in crate::native_bridge::element) enum TrustedHtmlSink { ElementInnerHtml, @@ -179,49 +253,12 @@ pub(in crate::native_bridge) fn trusted_attribute_value_string<'s>( .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, + let sink = trusted_attribute_sink_for_names( + 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)) }); 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 4a5d21b63f..ae924678d0 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 @@ -198,6 +198,86 @@ fn trusted_type_factory_interface_exposes_stable_branded_empty_values() { ); } +#[test] +fn trusted_type_factory_get_attribute_type_reuses_attribute_sink_classification() { + let mut vm = new_storage_test_vm("https://trusted-type-attribute-types.test/"); + + let result = vm + .eval( + r#" +(() => { + const HTML = "http://www.w3.org/1999/xhtml"; + const SVG = "http://www.w3.org/2000/svg"; + const MATHML = "http://www.w3.org/1998/Math/MathML"; + const XLINK = "http://www.w3.org/1999/xlink"; + const OTHER = "https://example.test/namespace"; + const type = (...args) => trustedTypes.getAttributeType(...args); + const errorName = callback => { + try { + callback(); + return "none"; + } catch (error) { + return error.constructor.name; + } + }; + const descriptor = Object.getOwnPropertyDescriptor( + TrustedTypePolicyFactory.prototype, + "getAttributeType" + ); + + return JSON.stringify({ + interface: [ + typeof descriptor.value, + descriptor.value.name, + descriptor.value.length, + descriptor.enumerable, + descriptor.configurable, + Object.hasOwn(trustedTypes, "getAttributeType") + ], + htmlDefaults: [ + type("script", "src"), + type("SCRIPT", "SRC", undefined, undefined), + type("script", "src", null, null), + type("script", "src", "", ""), + type("script", "src", HTML, ""), + type("ſcript", "src"), + type("script", "ſrc") + ], + urls: [ + type("embed", "src"), + type("object", "data"), + type("object", "codebase"), + type("script", "href"), + type("script", "href", SVG), + type("SCRIPT", "HREF", SVG, XLINK), + type("script", "href", SVG, OTHER), + type("script", "href", SVG.toUpperCase()) + ], + handlers: [ + type("div", "onclick"), + type("g", "ondblclick", SVG), + type("mrow", "onmousedown", MATHML), + type("div", "onclick", HTML, OTHER), + type("foo", "onmouseup", OTHER), + type("div", "ondoesnotexist") + ], + errors: [ + errorName(() => type()), + errorName(() => type("script")), + errorName(() => descriptor.value.call({}, "script", "src")) + ] + }); +})() +"#, + ) + .expect("TrustedTypePolicyFactory getAttributeType probe should evaluate"); + + assert_eq!( + result, + r#"{"interface":["function","getAttributeType",2,true,true,false],"htmlDefaults":["TrustedScriptURL","TrustedScriptURL","TrustedScriptURL","TrustedScriptURL","TrustedScriptURL",null,null],"urls":["TrustedScriptURL","TrustedScriptURL","TrustedScriptURL",null,"TrustedScriptURL","TrustedScriptURL",null,null],"handlers":["TrustedScript","TrustedScript","TrustedScript",null,null,null],"errors":["TypeError","TypeError","TypeError"]}"# + ); +} + #[test] fn trusted_type_factory_default_policy_getter_tracks_the_branded_policy() { let mut vm = new_storage_test_vm("https://trusted-type-factory-default-policy.test/");