mirror of
https://github.com/lexmount/moli.git
synced 2026-11-06 00:07:32 +00:00
fix(trusted-types): align WebIDL interfaces and global accessors
This commit is contained in:
@@ -23,9 +23,6 @@ const TRUSTED_TYPE_SCRIPT_URL_PROTOTYPE_SLOT: &str = "__moliTrustedScriptURLProt
|
||||
const TRUSTED_TYPE_HTML_CONSTRUCTOR_SLOT: &str = "__moliTrustedHTMLConstructor";
|
||||
const TRUSTED_TYPE_SCRIPT_CONSTRUCTOR_SLOT: &str = "__moliTrustedScriptConstructor";
|
||||
const TRUSTED_TYPE_SCRIPT_URL_CONSTRUCTOR_SLOT: &str = "__moliTrustedScriptURLConstructor";
|
||||
const TRUSTED_TYPE_POLICY_CONSTRUCTOR_SLOT: &str = "__moliTrustedTypePolicyConstructor";
|
||||
const TRUSTED_TYPE_POLICY_FACTORY_CONSTRUCTOR_SLOT: &str =
|
||||
"__moliTrustedTypePolicyFactoryConstructor";
|
||||
const TRUSTED_TYPES_DEFAULT_POLICY_SLOT: &str = "__moliTrustedTypesDefaultPolicy";
|
||||
const TRUSTED_TYPES_CREATE_HTML_SLOT: &str = "__moliTrustedTypesCreateHTML";
|
||||
const TRUSTED_TYPES_CREATE_SCRIPT_SLOT: &str = "__moliTrustedTypesCreateScript";
|
||||
@@ -51,27 +48,30 @@ struct TrustedTypePolicyFactoryInterfaceDeclaration {
|
||||
#[webapi(
|
||||
method,
|
||||
callback = trusted_types_create_policy_callback,
|
||||
length = 2,
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
create_policy: (),
|
||||
#[webapi(
|
||||
method = "isHTML",
|
||||
callback = trusted_types_is_html_callback,
|
||||
callback = trusted_types_is_type_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 0),
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
is_html: (),
|
||||
#[webapi(
|
||||
method,
|
||||
callback = trusted_types_is_script_callback,
|
||||
callback = trusted_types_is_type_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 1),
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
is_script: (),
|
||||
#[webapi(
|
||||
method = "isScriptURL",
|
||||
callback = trusted_types_is_script_url_callback,
|
||||
callback = trusted_types_is_type_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 2),
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
@@ -123,6 +123,13 @@ struct TrustedTypesFactoryObjectDeclaration<'scope> {
|
||||
created_policy_names: v8::Local<'scope, v8::Array>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "TrustedTypePolicyFactory")]
|
||||
struct TrustedTypesPredicateArgs<'scope> {
|
||||
#[webidl(required)]
|
||||
value: v8::Local<'scope, v8::Value>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "TrustedTypePolicyFactory.getAttributeType")]
|
||||
struct TrustedTypesGetAttributeTypeArgs {
|
||||
@@ -154,15 +161,15 @@ struct TrustedTypeObjectDeclaration<'scope> {
|
||||
value: v8::Local<'scope, v8::String>,
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(plain)]
|
||||
struct TrustedTypePrototypeDeclaration {
|
||||
#[webapi(method = "toString", callback = trusted_type_to_string_callback, length = 0)]
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(fragment, enumerable)]
|
||||
struct TrustedTypePrototypeDeclaration<'scope> {
|
||||
kind: v8::Local<'scope, v8::String>,
|
||||
|
||||
#[webapi(method = "toString", callback = trusted_type_to_string_callback, data = self.kind, length = 0)]
|
||||
to_string: (),
|
||||
#[webapi(method = "toJSON", callback = trusted_type_to_string_callback, length = 0)]
|
||||
#[webapi(method = "toJSON", callback = trusted_type_to_string_callback, data = self.kind, length = 0)]
|
||||
to_json: (),
|
||||
#[webapi(method, callback = trusted_type_to_string_callback, length = 0)]
|
||||
value_of: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
@@ -745,16 +752,17 @@ fn build_trusted_type_constructor<'s>(
|
||||
kind: TrustedTypeKind,
|
||||
) -> Result<TrustedTypeConstructorBinding<'s>> {
|
||||
let name = kind.constructor_name();
|
||||
let constructor = v8::Function::builder(trusted_type_illegal_constructor_callback)
|
||||
.length(0)
|
||||
.build(scope)
|
||||
let template = v8::FunctionTemplate::new(scope, trusted_type_illegal_constructor_callback);
|
||||
template.read_only_prototype();
|
||||
let constructor = template
|
||||
.get_function(scope)
|
||||
.ok_or_else(|| anyhow!("failed to create {name} constructor"))?;
|
||||
constructor.set_name(v8str(scope, name));
|
||||
let prototype = constructor
|
||||
.get(scope, v8str(scope, "prototype").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("{name}.prototype missing"))?;
|
||||
TrustedTypePrototypeDeclaration::default()
|
||||
TrustedTypePrototypeDeclaration::new(v8str(scope, kind.constructor_name()))
|
||||
.initialize(scope, prototype)
|
||||
.map_err(|error| anyhow!("failed to initialize {name}.prototype declaration: {error}"))?;
|
||||
prototype
|
||||
@@ -762,7 +770,7 @@ fn build_trusted_type_constructor<'s>(
|
||||
scope,
|
||||
v8::Symbol::get_to_string_tag(scope).into(),
|
||||
v8str(scope, name).into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
v8::PropertyAttribute::DONT_ENUM | v8::PropertyAttribute::READ_ONLY,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
.then_some(())
|
||||
@@ -787,7 +795,7 @@ fn install_trusted_script_code_like_constructor<'s>(
|
||||
.get(scope, v8str(scope, "prototype").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("TrustedScript code-like constructor prototype missing"))?;
|
||||
TrustedTypePrototypeDeclaration::default()
|
||||
TrustedTypePrototypeDeclaration::new(v8str(scope, TrustedTypeKind::Script.constructor_name()))
|
||||
.initialize(scope, prototype)
|
||||
.map_err(|error| {
|
||||
anyhow!("failed to initialize TrustedScript code-like carrier: {error}")
|
||||
@@ -865,6 +873,13 @@ fn trusted_type_to_string_callback<'s>(
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let this = args.this();
|
||||
if !trusted_type_kind(scope, this.into()).is_some_and(|kind| {
|
||||
v8str(scope, kind.constructor_name()).strict_equals(args.data())
|
||||
})
|
||||
{
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(value) = get_private_value(scope, this, TRUSTED_TYPE_VALUE_SLOT) else {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
@@ -1108,7 +1123,7 @@ fn trusted_type_policy_create_callback<'s>(
|
||||
rv.set(object.into());
|
||||
}
|
||||
|
||||
fn trusted_types_is_html_callback<'s>(
|
||||
fn trusted_types_is_type_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
@@ -1116,29 +1131,18 @@ fn trusted_types_is_html_callback<'s>(
|
||||
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::Html).is_some());
|
||||
}
|
||||
|
||||
fn trusted_types_is_script_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()) {
|
||||
let Some(parsed) = webidl::parse_args::<TrustedTypesPredicateArgs<'s>>(scope, &args) else {
|
||||
return;
|
||||
}
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::Script).is_some());
|
||||
}
|
||||
|
||||
fn trusted_types_is_script_url_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()) {
|
||||
};
|
||||
let Some(kind) = crate::util::callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
&TRUSTED_TYPE_KINDS,
|
||||
"TrustedTypePolicyFactory type predicates",
|
||||
) else {
|
||||
return;
|
||||
}
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::ScriptUrl).is_some());
|
||||
};
|
||||
rv.set_bool(trusted_type_kind(scope, parsed.value) == Some(kind));
|
||||
}
|
||||
|
||||
fn trusted_types_get_attribute_type_callback<'s>(
|
||||
|
||||
@@ -8,49 +8,18 @@ const TRUSTED_TYPES_LAZY_STATE_INSTALLED_SLOT: &str = "__moliTrustedTypesLazySta
|
||||
const TRUSTED_TYPES_FACTORY_SLOT: &str = "__moliTrustedTypesFactory";
|
||||
const TRUSTED_TYPES_MATERIALIZING_SLOT: &str = "__moliTrustedTypesMaterializing";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum TrustedTypesGlobalProperty {
|
||||
Html,
|
||||
Script,
|
||||
ScriptUrl,
|
||||
Factory,
|
||||
}
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(fragment)]
|
||||
struct TrustedTypesGlobalAccessorDeclaration {
|
||||
window_receiver: bool,
|
||||
|
||||
impl TrustedTypesGlobalProperty {
|
||||
const ALL: [(Self, &'static str); 4] = [
|
||||
(Self::Html, "TrustedHTML"),
|
||||
(Self::Script, "TrustedScript"),
|
||||
(Self::ScriptUrl, "TrustedScriptURL"),
|
||||
(Self::Factory, "trustedTypes"),
|
||||
];
|
||||
|
||||
const fn callback_data(self) -> u32 {
|
||||
match self {
|
||||
Self::Html => 0,
|
||||
Self::Script => 1,
|
||||
Self::ScriptUrl => 2,
|
||||
Self::Factory => 3,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_callback_data(value: u32) -> Option<Self> {
|
||||
match value {
|
||||
0 => Some(Self::Html),
|
||||
1 => Some(Self::Script),
|
||||
2 => Some(Self::ScriptUrl),
|
||||
3 => Some(Self::Factory),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
const fn trusted_type_kind(self) -> Option<TrustedTypeKind> {
|
||||
match self {
|
||||
Self::Html => Some(TrustedTypeKind::Html),
|
||||
Self::Script => Some(TrustedTypeKind::Script),
|
||||
Self::ScriptUrl => Some(TrustedTypeKind::ScriptUrl),
|
||||
Self::Factory => None,
|
||||
}
|
||||
}
|
||||
#[webapi(
|
||||
accessor_property = "trustedTypes",
|
||||
getter = trusted_types_global_getter,
|
||||
data = self.window_receiver,
|
||||
enumerable
|
||||
)]
|
||||
trusted_types: (),
|
||||
}
|
||||
|
||||
pub(super) fn install_lazy_trusted_types_runtime_state<'s>(
|
||||
@@ -74,12 +43,6 @@ pub(super) fn install_lazy_trusted_types_runtime_state<'s>(
|
||||
.unwrap_or(false)
|
||||
.then_some(())
|
||||
.ok_or_else(|| anyhow!("failed to publish TrustedTypePolicy interface"))?;
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
TRUSTED_TYPE_POLICY_CONSTRUCTOR_SLOT,
|
||||
policy_constructor.into(),
|
||||
);
|
||||
let policy_factory_constructor = TrustedTypePolicyFactoryInterfaceDeclaration::build(scope)
|
||||
.get_function(scope)
|
||||
.ok_or_else(|| anyhow!("failed to build TrustedTypePolicyFactory interface"))?;
|
||||
@@ -93,28 +56,55 @@ pub(super) fn install_lazy_trusted_types_runtime_state<'s>(
|
||||
.unwrap_or(false)
|
||||
.then_some(())
|
||||
.ok_or_else(|| anyhow!("failed to publish TrustedTypePolicyFactory interface"))?;
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
TRUSTED_TYPE_POLICY_FACTORY_CONSTRUCTOR_SLOT,
|
||||
policy_factory_constructor.into(),
|
||||
);
|
||||
install_trusted_script_code_like_constructor(scope, global)?;
|
||||
// These eager interfaces are outside the exposed-interface template table.
|
||||
// Register their intrinsic prototypes before author code can replace globals;
|
||||
// WebApiObject binding must never use an author-supplied constructor.
|
||||
for (name, constructor) in [
|
||||
("TrustedTypePolicy", policy_constructor),
|
||||
("TrustedTypePolicyFactory", policy_factory_constructor),
|
||||
] {
|
||||
let prototype = constructor
|
||||
.get(scope, v8str(scope, "prototype").into())
|
||||
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
||||
.ok_or_else(|| anyhow!("{name}.prototype missing during bootstrap"))?;
|
||||
if !crate::util::register_intrinsic_interface(
|
||||
scope,
|
||||
global,
|
||||
name,
|
||||
constructor.into(),
|
||||
prototype,
|
||||
) {
|
||||
return Err(anyhow!("failed to register intrinsic {name} interface"));
|
||||
}
|
||||
}
|
||||
install_function_constructor_brand_guards(scope, global)?;
|
||||
for (property, name) in TrustedTypesGlobalProperty::ALL {
|
||||
let data = v8::Integer::new_from_unsigned(scope, property.callback_data());
|
||||
for (index, kind) in TRUSTED_TYPE_KINDS.into_iter().enumerate() {
|
||||
let name = kind.constructor_name();
|
||||
let data = crate::util::callback_data_index_value(scope, index);
|
||||
global
|
||||
.set_lazy_data_property_with_configuration(
|
||||
scope,
|
||||
v8str(scope, name).into(),
|
||||
v8::LazyDataPropertyConfiguration::new(trusted_types_global_lazy_getter)
|
||||
.data(data.into())
|
||||
.data(data)
|
||||
.property_attribute(v8::PropertyAttribute::DONT_ENUM),
|
||||
)
|
||||
.unwrap_or(false)
|
||||
.then_some(())
|
||||
.ok_or_else(|| anyhow!("failed to install lazy `{name}` global"))?;
|
||||
}
|
||||
// Window owns its mixin attribute; workers inherit it from WorkerGlobalScope.
|
||||
let is_worker = get_private_value(scope, global, crate::worker::WORKER_STATE_SLOT).is_some();
|
||||
let target = if is_worker {
|
||||
crate::util::global_constructor_prototype(scope, "WorkerGlobalScope").ok_or_else(|| {
|
||||
anyhow!("WorkerGlobalScope.prototype missing during Trusted Types bootstrap")
|
||||
})?
|
||||
} else {
|
||||
global
|
||||
};
|
||||
TrustedTypesGlobalAccessorDeclaration::new(!is_worker)
|
||||
.initialize(scope, target)
|
||||
.map_err(|error| anyhow!("failed to install trustedTypes accessor: {error}"))?;
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
@@ -130,10 +120,10 @@ fn trusted_types_global_lazy_getter<'s>(
|
||||
args: v8::PropertyCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(property) = args
|
||||
let Some(kind) = args
|
||||
.data()
|
||||
.uint32_value(scope)
|
||||
.and_then(TrustedTypesGlobalProperty::from_callback_data)
|
||||
.and_then(|index| TrustedTypeKind::from_callback_index(index as usize))
|
||||
else {
|
||||
throw_error(
|
||||
scope,
|
||||
@@ -149,14 +139,64 @@ fn trusted_types_global_lazy_getter<'s>(
|
||||
return;
|
||||
};
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
match ensure_trusted_types_state(target_scope)
|
||||
.and_then(|()| cached_public_value(target_scope, property))
|
||||
{
|
||||
Ok(value) => rv.set(value),
|
||||
Err(error) => throw_error(
|
||||
if let Err(error) = ensure_trusted_types_state(target_scope) {
|
||||
throw_error(
|
||||
target_scope,
|
||||
&format!("Failed to materialize Trusted Types: {error}"),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let global = relevant_context.global(target_scope);
|
||||
if let Some(value) = get_private_value(target_scope, global, kind.constructor_slot()) {
|
||||
rv.set(value);
|
||||
} else {
|
||||
throw_error(
|
||||
target_scope,
|
||||
"Materialized Trusted Types constructor is missing.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn trusted_types_global_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let receiver = args.this();
|
||||
// Reject in the getter's realm before entering a valid receiver's realm.
|
||||
// Borrowing a getter changes neither its TypeError realm nor its brand check.
|
||||
let valid_receiver = if args.data().is_true() {
|
||||
super::super::window_receiver::is_window_receiver(scope, receiver)
|
||||
} else {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
receiver.strict_equals(global.into())
|
||||
};
|
||||
if !valid_receiver {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"trustedTypes getter called on incompatible receiver.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
let relevant_context = receiver
|
||||
.get_creation_context(scope)
|
||||
.unwrap_or_else(|| scope.get_current_context());
|
||||
let target_scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
if let Err(error) = ensure_trusted_types_state(target_scope) {
|
||||
throw_error(
|
||||
target_scope,
|
||||
&format!("Failed to materialize Trusted Types: {error}"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
let global = relevant_context.global(target_scope);
|
||||
if let Some(factory) = cached_object(target_scope, global, TRUSTED_TYPES_FACTORY_SLOT) {
|
||||
rv.set(factory.into());
|
||||
} else {
|
||||
throw_error(
|
||||
target_scope,
|
||||
"Materialized Trusted Types factory is missing.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,24 +262,6 @@ fn build_and_cache_trusted_types_state<'s>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cached_public_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
property: TrustedTypesGlobalProperty,
|
||||
) -> Result<v8::Local<'s, v8::Value>> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let slot = match property {
|
||||
TrustedTypesGlobalProperty::Html
|
||||
| TrustedTypesGlobalProperty::Script
|
||||
| TrustedTypesGlobalProperty::ScriptUrl => property
|
||||
.trusted_type_kind()
|
||||
.expect("trusted type constructor property must have a kind")
|
||||
.constructor_slot(),
|
||||
TrustedTypesGlobalProperty::Factory => TRUSTED_TYPES_FACTORY_SLOT,
|
||||
};
|
||||
get_private_value(scope, global, slot)
|
||||
.ok_or_else(|| anyhow!("materialized Trusted Types value is missing"))
|
||||
}
|
||||
|
||||
fn cached_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -129,24 +129,24 @@ fn trusted_type_prototype_declared_methods_preserve_descriptors() {
|
||||
script: internalNames(script),
|
||||
scriptURL: internalNames(scriptURL)
|
||||
},
|
||||
htmlMethods: ["toString", "valueOf"].map(name => describe(TrustedHTML.prototype, name)),
|
||||
scriptMethods: ["toString", "valueOf"].map(name => describe(TrustedScript.prototype, name)),
|
||||
scriptURLMethods: ["toString", "valueOf"].map(name => describe(TrustedScriptURL.prototype, name)),
|
||||
htmlMethods: ["toString", "toJSON"].map(name => describe(TrustedHTML.prototype, name)),
|
||||
scriptMethods: ["toString", "toJSON"].map(name => describe(TrustedScript.prototype, name)),
|
||||
scriptURLMethods: ["toString", "toJSON"].map(name => describe(TrustedScriptURL.prototype, name)),
|
||||
values: [
|
||||
String(html),
|
||||
html.valueOf(),
|
||||
html.toJSON(),
|
||||
String(script),
|
||||
script.valueOf(),
|
||||
script.toJSON(),
|
||||
String(scriptURL),
|
||||
scriptURL.valueOf()
|
||||
scriptURL.toJSON()
|
||||
],
|
||||
fakeResults: [
|
||||
probe(() => TrustedHTML.prototype.toString.call(fakeHTML)),
|
||||
probe(() => TrustedHTML.prototype.valueOf.call(fakeHTML)),
|
||||
probe(() => TrustedHTML.prototype.toJSON.call(fakeHTML)),
|
||||
probe(() => TrustedScript.prototype.toString.call(fakeScript)),
|
||||
probe(() => TrustedScript.prototype.valueOf.call(fakeScript)),
|
||||
probe(() => TrustedScript.prototype.toJSON.call(fakeScript)),
|
||||
probe(() => TrustedScriptURL.prototype.toString.call(fakeScriptURL)),
|
||||
probe(() => TrustedScriptURL.prototype.valueOf.call(fakeScriptURL))
|
||||
probe(() => TrustedScriptURL.prototype.toJSON.call(fakeScriptURL))
|
||||
].join("|"),
|
||||
trustedChecks: [
|
||||
trustedTypes.isHTML(fakeHTML),
|
||||
@@ -164,7 +164,7 @@ fn trusted_type_prototype_declared_methods_preserve_descriptors() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"prototypes":[true,true,true],"initialOwnSlots":{"html":[],"script":[],"scriptURL":[]},"spoofedOwnSlots":{"html":["__moliTrustedTypeKind","__moliTrustedTypeValue"],"script":["__moliTrustedTypeKind","__moliTrustedTypeValue"],"scriptURL":["__moliTrustedTypeKind","__moliTrustedTypeValue"]},"htmlMethods":["false:true:true:toString:0","false:true:true:valueOf:0"],"scriptMethods":["false:true:true:toString:0","false:true:true:valueOf:0"],"scriptURLMethods":["false:true:true:toString:0","false:true:true:valueOf:0"],"values":["<em>ok</em>","<em>ok</em>","1 + 1","1 + 1","data:text/javascript,","data:text/javascript,"],"fakeResults":"TypeError|TypeError|TypeError|TypeError|TypeError|TypeError","trustedChecks":[false,false,false,true,true,true]}"#
|
||||
r#"{"prototypes":[true,true,true],"initialOwnSlots":{"html":[],"script":[],"scriptURL":[]},"spoofedOwnSlots":{"html":["__moliTrustedTypeKind","__moliTrustedTypeValue"],"script":["__moliTrustedTypeKind","__moliTrustedTypeValue"],"scriptURL":["__moliTrustedTypeKind","__moliTrustedTypeValue"]},"htmlMethods":["true:true:true:toString:0","true:true:true:toJSON:0"],"scriptMethods":["true:true:true:toString:0","true:true:true:toJSON:0"],"scriptURLMethods":["true:true:true:toString:0","true:true:true:toJSON:0"],"values":["<em>ok</em>","<em>ok</em>","1 + 1","1 + 1","data:text/javascript,","data:text/javascript,"],"fakeResults":"TypeError|TypeError|TypeError|TypeError|TypeError|TypeError","trustedChecks":[false,false,false,true,true,true]}"#
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
|
||||
@@ -40,6 +40,87 @@ fn trusted_types_csp_policy_creation_matches_only_valid_name_tokens() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_types_webidl_surface_checks_descriptors_arguments_and_brands() {
|
||||
let mut vm = new_storage_test_vm("https://trusted-types-webidl.test/");
|
||||
assert_eq!(
|
||||
vm.eval(include_str!(
|
||||
"../../../../tests/fixtures/trusted-types-webidl.js"
|
||||
))
|
||||
.expect("Trusted Types WebIDL surface should match its declarations"),
|
||||
"ok"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn trusted_types_webidl_getter_and_stringifiers_preserve_cross_realm_semantics() {
|
||||
let mut vm = new_parsed_test_vm(
|
||||
"https://trusted-types-webidl-realms.test/",
|
||||
"<!doctype html><html><body></body></html>",
|
||||
);
|
||||
vm.eval(
|
||||
r#"
|
||||
const frame = document.createElement('iframe');
|
||||
frame.id = 'trusted-types-webidl-child';
|
||||
frame.srcdoc = '<!doctype html><p>child</p>';
|
||||
document.body.appendChild(frame);
|
||||
"#,
|
||||
)
|
||||
.expect("create child realm for Trusted Types getter checks");
|
||||
run_child_navigation_commit_and_host_load_for_test(&mut vm, "Trusted Types child realm").await;
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const child = document.getElementById('trusted-types-webidl-child').contentWindow;
|
||||
const parentGetter = Object.getOwnPropertyDescriptor(window, 'trustedTypes').get;
|
||||
const childGetter = Object.getOwnPropertyDescriptor(child, 'trustedTypes').get;
|
||||
// Borrow the parent getter before the child's factory has been accessed.
|
||||
const childFactory = parentGetter.call(child);
|
||||
assert(childFactory === child.trustedTypes &&
|
||||
childFactory instanceof child.TrustedTypePolicyFactory &&
|
||||
!(childFactory instanceof TrustedTypePolicyFactory), 'factory receiver realm');
|
||||
assert(childGetter.call(window) === trustedTypes, 'borrowed child getter');
|
||||
assert(childGetter.call(null) === childFactory &&
|
||||
childGetter.call(undefined) === childFactory, 'nullish receiver uses getter realm');
|
||||
for (const [getter, ErrorType] of [[parentGetter, TypeError], [childGetter, child.TypeError]]) {
|
||||
let error;
|
||||
try { getter.call({}); } catch (caught) { error = caught; }
|
||||
assert(error instanceof ErrorType, 'getter exception belongs to function realm');
|
||||
}
|
||||
const policy = childFactory.createPolicy('child', {
|
||||
createHTML: value => value,
|
||||
createScript: value => value,
|
||||
createScriptURL: value => value,
|
||||
});
|
||||
const cases = [
|
||||
[TrustedHTML, child.TrustedHTML, policy.createHTML('html'), 'html', 'isHTML'],
|
||||
[TrustedScript, child.TrustedScript, policy.createScript('script'), 'script', 'isScript'],
|
||||
[TrustedScriptURL, child.TrustedScriptURL, policy.createScriptURL('/app.js'), '/app.js', 'isScriptURL'],
|
||||
];
|
||||
for (const [Parent, Child, value, text, predicate] of cases) {
|
||||
assert(value instanceof Child && !(value instanceof Parent), 'value realm');
|
||||
assert(trustedTypes[predicate](value), 'cross-realm predicate');
|
||||
for (const method of ['toString', 'toJSON']) {
|
||||
assert(Parent.prototype[method].call(value) === text, 'cross-realm stringifier');
|
||||
let error;
|
||||
try { Child.prototype[method].call({}); } catch (caught) { error = caught; }
|
||||
assert(error instanceof child.TypeError && !(error instanceof TypeError),
|
||||
'stringifier exception belongs to function realm');
|
||||
}
|
||||
}
|
||||
return 'ok';
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("Trusted Types getter and stringifier realms should be preserved"),
|
||||
"ok"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_type_policy_callbacks_follow_webidl_dictionary_and_callback_rules() {
|
||||
let mut vm = new_storage_test_vm("https://trusted-type-policy-webidl.test/");
|
||||
|
||||
@@ -495,6 +495,18 @@ fn trusted_types_globals_share_one_lazy_native_realm_state() {
|
||||
let mut vm = new_storage_test_vm("https://lazy-trusted-types.test/");
|
||||
assert!(!default_trusted_types_materialized(&mut vm));
|
||||
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
"String(typeof Object.getOwnPropertyDescriptor(globalThis, 'trustedTypes').get === 'function')"
|
||||
)
|
||||
.expect("reading the trustedTypes getter descriptor should not invoke it"),
|
||||
"true"
|
||||
);
|
||||
assert!(
|
||||
!default_trusted_types_materialized(&mut vm),
|
||||
"reflecting the accessor must not build Trusted Types prototypes or factory"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
@@ -551,6 +563,51 @@ fn trusted_types_globals_share_one_lazy_native_realm_state() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_types_getter_materializes_intrinsics_without_replacing_public_overrides() {
|
||||
let mut vm = new_storage_test_vm("https://lazy-trusted-types-overrides.test/");
|
||||
vm.eval(
|
||||
r#"
|
||||
const replacement = function Replacement() {};
|
||||
globalThis.TrustedHTML = replacement;
|
||||
globalThis.TrustedScript = replacement;
|
||||
globalThis.TrustedScriptURL = replacement;
|
||||
globalThis.TrustedTypePolicy = replacement;
|
||||
globalThis.TrustedTypePolicyFactory = replacement;
|
||||
"#,
|
||||
)
|
||||
.expect("overriding public constructor names should not materialize Trusted Types");
|
||||
assert!(!default_trusted_types_materialized(&mut vm));
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const factory = trustedTypes;
|
||||
const policy = factory.createPolicy('private-intrinsics', {
|
||||
createHTML: value => value,
|
||||
createScript: value => value,
|
||||
createScriptURL: value => value,
|
||||
});
|
||||
const values = [policy.createHTML('html'), policy.createScript('script'),
|
||||
policy.createScriptURL('/app.js')];
|
||||
return String(
|
||||
[TrustedHTML, TrustedScript, TrustedScriptURL, TrustedTypePolicy,
|
||||
TrustedTypePolicyFactory].every(value => value === replacement) &&
|
||||
!(factory instanceof replacement) && !(policy instanceof replacement) &&
|
||||
values.every(value => !(value instanceof replacement)) &&
|
||||
factory.isHTML(values[0]) && factory.isScript(values[1]) &&
|
||||
factory.isScriptURL(values[2]) &&
|
||||
values.map(String).join('|') === 'html|script|/app.js'
|
||||
);
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("lazy Trusted Types materialization should use private intrinsic identities"),
|
||||
"true"
|
||||
);
|
||||
assert!(default_trusted_types_materialized(&mut vm));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn media_source_uses_the_shared_lazy_interface_registry() {
|
||||
let mut vm = new_storage_test_vm("https://lazy-media-source.test/");
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_trusted_types_webidl_surface_checks_descriptors_arguments_and_brands() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
format!(
|
||||
"postMessage({}); close();",
|
||||
include_str!("../../../../tests/fixtures/trusted-types-webidl.js")
|
||||
),
|
||||
"https://trusted-types-webidl.test/worker.js".into(),
|
||||
);
|
||||
let message = timeout(TIMEOUT, handle.recv())
|
||||
.await
|
||||
.expect("worker Trusted Types probe should settle")
|
||||
.expect("worker Trusted Types probe should return a result");
|
||||
assert_eq!(expect_post_json(message), r#""ok""#);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worker_compression_streams_roundtrip_all_formats() {
|
||||
ensure_v8();
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
(() => {
|
||||
const assert = (condition, message) => {
|
||||
if (!condition) throw new Error(message);
|
||||
};
|
||||
const rejects = (callback, message) => {
|
||||
let error;
|
||||
try { callback(); } catch (caught) { error = caught; }
|
||||
assert(error instanceof TypeError, message);
|
||||
};
|
||||
const isWindow = typeof window !== "undefined";
|
||||
const owner = isWindow ? globalThis : WorkerGlobalScope.prototype;
|
||||
const descriptor = Object.getOwnPropertyDescriptor(owner, "trustedTypes");
|
||||
assert(typeof descriptor?.get === "function", "trustedTypes is an accessor");
|
||||
assert(descriptor.set === undefined && !Object.hasOwn(descriptor, "value"),
|
||||
"trustedTypes has no setter or data value");
|
||||
assert(descriptor.enumerable && descriptor.configurable, "trustedTypes flags");
|
||||
const getter = descriptor.get;
|
||||
assert(getter.name === "get trustedTypes" && getter.length === 0, "getter metadata");
|
||||
assert(!Object.hasOwn(getter, "prototype"), "getter is not a constructor");
|
||||
rejects(() => Reflect.construct(getter, []), "new getter");
|
||||
if (!isWindow) assert(!Object.hasOwn(globalThis, "trustedTypes"), "worker placement");
|
||||
|
||||
const factory = trustedTypes;
|
||||
assert(factory instanceof TrustedTypePolicyFactory, "factory interface");
|
||||
for (const receiver of [globalThis, null, undefined]) {
|
||||
assert(getter.call(receiver) === factory, "getter global and nullish receivers");
|
||||
}
|
||||
for (const receiver of [{}, Object.create(globalThis), new Proxy(globalThis, {}), 1]) {
|
||||
rejects(() => getter.call(receiver), "getter receiver brand");
|
||||
}
|
||||
assert(!Reflect.set(globalThis, "trustedTypes", {}), "readonly write fails");
|
||||
rejects(() => { "use strict"; globalThis.trustedTypes = {}; }, "strict readonly write");
|
||||
assert(trustedTypes === factory, "readonly write preserves factory");
|
||||
assert(Object.getOwnPropertyDescriptor(owner, "trustedTypes").get === getter,
|
||||
"getter remains an accessor after materialization");
|
||||
assert(factory.createPolicy.length === 1, "optional policy options do not count in length");
|
||||
const policy = factory.createPolicy("webidl-surface", {
|
||||
createHTML: value => value,
|
||||
createScript: value => value,
|
||||
createScriptURL: value => value,
|
||||
});
|
||||
const entries = [
|
||||
[TrustedHTML, policy.createHTML("<b>html</b>"), "<b>html</b>", "isHTML"],
|
||||
[TrustedScript, policy.createScript("1 + 1"), "1 + 1", "isScript"],
|
||||
[TrustedScriptURL, policy.createScriptURL("/app.js"), "/app.js", "isScriptURL"],
|
||||
];
|
||||
for (const [Constructor, value, text, predicate] of entries) {
|
||||
const proto = Constructor.prototype;
|
||||
const prototypeDescriptor = Object.getOwnPropertyDescriptor(Constructor, "prototype");
|
||||
assert(!prototypeDescriptor.writable && !prototypeDescriptor.enumerable &&
|
||||
!prototypeDescriptor.configurable, `${Constructor.name}.prototype flags`);
|
||||
const constructorDescriptor = Object.getOwnPropertyDescriptor(proto, "constructor");
|
||||
assert(constructorDescriptor.value === Constructor && constructorDescriptor.writable &&
|
||||
!constructorDescriptor.enumerable && constructorDescriptor.configurable,
|
||||
`${Constructor.name} constructor backlink`);
|
||||
rejects(() => Constructor(), `${Constructor.name} call`);
|
||||
rejects(() => new Constructor(), `${Constructor.name} construction`);
|
||||
const tag = Object.getOwnPropertyDescriptor(proto, Symbol.toStringTag);
|
||||
assert(tag.value === Constructor.name && !tag.writable && !tag.enumerable && tag.configurable,
|
||||
`${Constructor.name} tag flags`);
|
||||
assert(!Object.hasOwn(proto, "valueOf") && value.valueOf() === value,
|
||||
`${Constructor.name} inherits Object valueOf`);
|
||||
const fake = Object.create(proto);
|
||||
const revoked = Proxy.revocable(value, {});
|
||||
revoked.revoke();
|
||||
const fakeValues = [fake, Object.create(value), new Proxy(value, {}), revoked.proxy,
|
||||
{}, null, undefined, text];
|
||||
for (const name of ["toString", "toJSON"]) {
|
||||
const method = Object.getOwnPropertyDescriptor(proto, name);
|
||||
assert(method.enumerable && method.configurable && method.writable,
|
||||
`${Constructor.name}.${name} flags`);
|
||||
assert(method.value.name === name && method.value.length === 0 &&
|
||||
!Object.hasOwn(method.value, "prototype"), `${name} metadata`);
|
||||
rejects(() => Reflect.construct(method.value, []), `new ${name}`);
|
||||
assert(method.value.call(value) === text, `${name} data`);
|
||||
for (const other of [...fakeValues, ...entries.filter(entry => entry[0] !== Constructor)
|
||||
.map(entry => entry[1])]) {
|
||||
rejects(() => method.value.call(other), `${Constructor.name}.${name} receiver brand`);
|
||||
}
|
||||
}
|
||||
rejects(() => factory[predicate](), `${predicate} missing argument`);
|
||||
for (const other of fakeValues) {
|
||||
assert(factory[predicate](other) === false, `${predicate} non-instance`);
|
||||
}
|
||||
for (const [Other, other] of entries) {
|
||||
assert(factory[predicate](other) === (Other === Constructor), `${predicate} kind`);
|
||||
}
|
||||
const trap = new Proxy({}, { get() { throw new Error("predicate must not coerce"); } });
|
||||
assert(factory[predicate](trap) === false, `${predicate} does not coerce input`);
|
||||
rejects(() => factory[predicate].call({}, value), `${predicate} factory brand`);
|
||||
assert(String(value) === text && JSON.stringify(value) === JSON.stringify(text),
|
||||
`${Constructor.name} string and JSON conversion`);
|
||||
value.toString = () => "7";
|
||||
assert(Number(value) === 7, `${Constructor.name} default primitive conversion`);
|
||||
delete value.toString;
|
||||
}
|
||||
|
||||
const Policy = TrustedTypePolicy;
|
||||
globalThis.TrustedTypePolicy = function ReplacementPolicy() {};
|
||||
globalThis.TrustedTypePolicyFactory = function ReplacementFactory() {};
|
||||
const afterOverride = factory.createPolicy("after-public-overrides", {createHTML: value => value});
|
||||
assert(afterOverride instanceof Policy && String(afterOverride.createHTML("safe")) === "safe",
|
||||
"policy creation uses intrinsic prototypes after public constructor replacement");
|
||||
|
||||
Object.defineProperty(globalThis, "trustedTypes", {value: "shadowed", configurable: true});
|
||||
assert(getter.call(globalThis) === factory, "captured getter ignores public replacement");
|
||||
return "ok";
|
||||
})()
|
||||
Reference in New Issue
Block a user