fix(trusted-types): expose shared attribute sink types

This commit is contained in:
ldm0
2026-09-16 22:02:05 +08:00
parent 5dd31e720a
commit fad89f779b
6 changed files with 217 additions and 43 deletions
@@ -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
@@ -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
@@ -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<String>,
#[webidl(nullable)]
attribute_namespace: Option<String>,
}
#[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::<TrustedTypesGetAttributeTypeArgs>(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,
@@ -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,
@@ -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<TrustedAttributeSink> {
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))
});
@@ -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/");