mirror of
https://github.com/servo/servo.git
synced 2025-07-18 04:43:41 +01:00
Implement "Create a Trusted Type" algorithm (#36454)
This algorithm is quite straightforward written in the specification, but leads to some type awkwardness in Rust. Most notably, the callbacks have different types and cannot be unified easily. They also return different string types. Similarly, the returning objects are all unique types and don't have a common denominator. Therefore, rather than implementing it in 1-to-1 fashion with the specification text, it instead uses callbacks to instruct the type system of what to call when. This is further complicated by the fact that the callback can exist or not, as well as return a value or not. This requires multiple unwrangling, combined with the fact that the algorithm should throw or not. All in all, the number of lines is relatively low compared to the specification algorithm and the Rust compiler does a lot of heavy lifting figuring out which type is what. Part of https://github.com/servo/servo/issues/36258 Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com> Co-authored-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
parent
0c045fc247
commit
dcc88b53aa
28 changed files with 197 additions and 259 deletions
|
@ -2,11 +2,20 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::jsapi::JSObject;
|
||||
use js::rust::HandleValue;
|
||||
|
||||
use crate::dom::bindings::callback::ExceptionHandling;
|
||||
use crate::dom::bindings::codegen::Bindings::TrustedTypePolicyBinding::TrustedTypePolicyMethods;
|
||||
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
|
||||
use crate::dom::bindings::codegen::Bindings::TrustedTypePolicyFactoryBinding::{
|
||||
CreateHTMLCallback, CreateScriptCallback, CreateScriptURLCallback, TrustedTypePolicyOptions,
|
||||
};
|
||||
use crate::dom::bindings::error::Error::Type;
|
||||
use crate::dom::bindings::error::Fallible;
|
||||
use crate::dom::bindings::reflector::{DomGlobal, DomObject, Reflector, reflect_dom_object};
|
||||
use crate::dom::bindings::root::DomRoot;
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
|
@ -20,19 +29,117 @@ pub struct TrustedTypePolicy {
|
|||
reflector_: Reflector,
|
||||
|
||||
name: String,
|
||||
|
||||
#[ignore_malloc_size_of = "Rc has unclear ownership"]
|
||||
create_html: Option<Rc<CreateHTMLCallback>>,
|
||||
#[ignore_malloc_size_of = "Rc has unclear ownership"]
|
||||
create_script: Option<Rc<CreateScriptCallback>>,
|
||||
#[ignore_malloc_size_of = "Rc has unclear ownership"]
|
||||
create_script_url: Option<Rc<CreateScriptURLCallback>>,
|
||||
}
|
||||
|
||||
impl TrustedTypePolicy {
|
||||
fn new_inherited(name: String) -> Self {
|
||||
fn new_inherited(name: String, options: &TrustedTypePolicyOptions) -> Self {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
name,
|
||||
create_html: options.createHTML.clone(),
|
||||
create_script: options.createScript.clone(),
|
||||
create_script_url: options.createScriptURL.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
pub(crate) fn new(name: String, global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
|
||||
reflect_dom_object(Box::new(Self::new_inherited(name)), global, can_gc)
|
||||
pub(crate) fn new(
|
||||
name: String,
|
||||
options: &TrustedTypePolicyOptions,
|
||||
global: &GlobalScope,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Self> {
|
||||
reflect_dom_object(Box::new(Self::new_inherited(name, options)), global, can_gc)
|
||||
}
|
||||
|
||||
/// This does not take all arguments as specified. That's because the return type of the
|
||||
/// trusted type function and object are not the same. 2 of the 3 string callbacks return
|
||||
/// a DOMString, while the other one returns an USVString. Additionally, all three callbacks
|
||||
/// have a unique type signature in WebIDL.
|
||||
///
|
||||
/// To circumvent these type problems, rather than implementing the full functionality here,
|
||||
/// part of the algorithm is implemented on the caller side. There, we only call the callback
|
||||
/// and create the object. The rest of the machinery is ensuring the right values pass through
|
||||
/// to the relevant callbacks.
|
||||
///
|
||||
/// <https://w3c.github.io/trusted-types/dist/spec/#get-trusted-type-policy-value-algorithm>
|
||||
pub(crate) fn get_trusted_type_policy_value<S, PolicyCallback>(
|
||||
&self,
|
||||
policy_value_callback: PolicyCallback,
|
||||
throw_if_missing: bool,
|
||||
) -> Fallible<Option<S>>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
PolicyCallback: FnOnce() -> Option<Fallible<Option<S>>>,
|
||||
{
|
||||
// Step 1: Let functionName be a function name for the given trustedTypeName, based on the following table:
|
||||
// Step 2: Let function be policy’s options[functionName].
|
||||
let function = policy_value_callback();
|
||||
match function {
|
||||
// Step 3: If function is null, then:
|
||||
None => {
|
||||
// Step 3.1: If throwIfMissing throw a TypeError.
|
||||
if throw_if_missing {
|
||||
Err(Type("Cannot find type".to_owned()))
|
||||
} else {
|
||||
// Step 3.2: Else return null.
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
// Step 4: Let policyValue be the result of invoking function with value as a first argument,
|
||||
// items of arguments as subsequent arguments, and callback **this** value set to null,
|
||||
// rethrowing any exceptions.
|
||||
Some(policy_value) => policy_value,
|
||||
}
|
||||
}
|
||||
|
||||
/// This does not take all arguments as specified. That's because the return type of the
|
||||
/// trusted type function and object are not the same. 2 of the 3 string callbacks return
|
||||
/// a DOMString, while the other one returns an USVString. Additionally, all three callbacks
|
||||
/// have a unique type signature in WebIDL.
|
||||
///
|
||||
/// To circumvent these type problems, rather than implementing the full functionality here,
|
||||
/// part of the algorithm is implemented on the caller side. There, we only call the callback
|
||||
/// and create the object. The rest of the machinery is ensuring the right values pass through
|
||||
/// to the relevant callbacks.
|
||||
///
|
||||
/// <https://w3c.github.io/trusted-types/dist/spec/#create-a-trusted-type-algorithm>
|
||||
pub(crate) fn create_trusted_type<R, S, PolicyCallback, TrustedTypeCallback>(
|
||||
&self,
|
||||
policy_value_callback: PolicyCallback,
|
||||
trusted_type_creation_callback: TrustedTypeCallback,
|
||||
) -> Fallible<DomRoot<R>>
|
||||
where
|
||||
R: DomObject,
|
||||
S: AsRef<str>,
|
||||
PolicyCallback: FnOnce() -> Option<Fallible<Option<S>>>,
|
||||
TrustedTypeCallback: FnOnce(String) -> DomRoot<R>,
|
||||
{
|
||||
// Step 1: Let policyValue be the result of executing Get Trusted Type policy value
|
||||
// with the same arguments as this algorithm and additionally true as throwIfMissing.
|
||||
let policy_value = self.get_trusted_type_policy_value(policy_value_callback, true);
|
||||
match policy_value {
|
||||
// Step 2: If the algorithm threw an error, rethrow the error and abort the following steps.
|
||||
Err(error) => Err(error),
|
||||
Ok(policy_value) => {
|
||||
// Step 3: Let dataString be the result of stringifying policyValue.
|
||||
let data_string = match policy_value {
|
||||
Some(value) => value.as_ref().into(),
|
||||
// Step 4: If policyValue is null or undefined, set dataString to the empty string.
|
||||
None => "".to_owned(),
|
||||
};
|
||||
// Step 5: Return a new instance of an interface with a type name trustedTypeName,
|
||||
// with its associated data value set to dataString.
|
||||
Ok(trusted_type_creation_callback(data_string))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,34 +151,82 @@ impl TrustedTypePolicyMethods<crate::DomTypeHolder> for TrustedTypePolicy {
|
|||
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicy-createhtml>
|
||||
fn CreateHTML(
|
||||
&self,
|
||||
_: JSContext,
|
||||
data: DOMString,
|
||||
_: Vec<HandleValue>,
|
||||
cx: JSContext,
|
||||
input: DOMString,
|
||||
arguments: Vec<HandleValue>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<TrustedHTML> {
|
||||
// TODO(36258): handle arguments
|
||||
TrustedHTML::new(data.to_string(), &self.global(), can_gc)
|
||||
) -> Fallible<DomRoot<TrustedHTML>> {
|
||||
self.create_trusted_type(
|
||||
|| {
|
||||
self.create_html.clone().map(|callback| {
|
||||
rooted!(in(*cx) let this_object: *mut JSObject);
|
||||
// Step 4: Let policyValue be the result of invoking function with value as a first argument,
|
||||
// items of arguments as subsequent arguments, and callback **this** value set to null,
|
||||
// rethrowing any exceptions.
|
||||
callback.Call_(
|
||||
&this_object.handle(),
|
||||
input,
|
||||
arguments,
|
||||
ExceptionHandling::Rethrow,
|
||||
can_gc,
|
||||
)
|
||||
})
|
||||
},
|
||||
|data_string| TrustedHTML::new(data_string, &self.global(), can_gc),
|
||||
)
|
||||
}
|
||||
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicy-createscript>
|
||||
fn CreateScript(
|
||||
&self,
|
||||
_: JSContext,
|
||||
data: DOMString,
|
||||
_: Vec<HandleValue>,
|
||||
cx: JSContext,
|
||||
input: DOMString,
|
||||
arguments: Vec<HandleValue>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<TrustedScript> {
|
||||
// TODO(36258): handle arguments
|
||||
TrustedScript::new(data.to_string(), &self.global(), can_gc)
|
||||
) -> Fallible<DomRoot<TrustedScript>> {
|
||||
self.create_trusted_type(
|
||||
|| {
|
||||
self.create_script.clone().map(|callback| {
|
||||
rooted!(in(*cx) let this_object: *mut JSObject);
|
||||
// Step 4: Let policyValue be the result of invoking function with value as a first argument,
|
||||
// items of arguments as subsequent arguments, and callback **this** value set to null,
|
||||
// rethrowing any exceptions.
|
||||
callback.Call_(
|
||||
&this_object.handle(),
|
||||
input,
|
||||
arguments,
|
||||
ExceptionHandling::Rethrow,
|
||||
can_gc,
|
||||
)
|
||||
})
|
||||
},
|
||||
|data_string| TrustedScript::new(data_string, &self.global(), can_gc),
|
||||
)
|
||||
}
|
||||
/// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicy-createscripturl>
|
||||
fn CreateScriptURL(
|
||||
&self,
|
||||
_: JSContext,
|
||||
data: DOMString,
|
||||
_: Vec<HandleValue>,
|
||||
cx: JSContext,
|
||||
input: DOMString,
|
||||
arguments: Vec<HandleValue>,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<TrustedScriptURL> {
|
||||
// TODO(36258): handle arguments
|
||||
TrustedScriptURL::new(data.to_string(), &self.global(), can_gc)
|
||||
) -> Fallible<DomRoot<TrustedScriptURL>> {
|
||||
self.create_trusted_type(
|
||||
|| {
|
||||
self.create_script_url.clone().map(|callback| {
|
||||
rooted!(in(*cx) let this_object: *mut JSObject);
|
||||
// Step 4: Let policyValue be the result of invoking function with value as a first argument,
|
||||
// items of arguments as subsequent arguments, and callback **this** value set to null,
|
||||
// rethrowing any exceptions.
|
||||
callback.Call_(
|
||||
&this_object.handle(),
|
||||
input,
|
||||
arguments,
|
||||
ExceptionHandling::Rethrow,
|
||||
can_gc,
|
||||
)
|
||||
})
|
||||
},
|
||||
|data_string| TrustedScriptURL::new(data_string, &self.global(), can_gc),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ impl TrustedTypePolicyFactory {
|
|||
fn create_trusted_type_policy(
|
||||
&self,
|
||||
policy_name: String,
|
||||
_options: &TrustedTypePolicyOptions,
|
||||
options: &TrustedTypePolicyOptions,
|
||||
global: &GlobalScope,
|
||||
can_gc: CanGc,
|
||||
) -> Fallible<DomRoot<TrustedTypePolicy>> {
|
||||
|
@ -72,11 +72,10 @@ impl TrustedTypePolicyFactory {
|
|||
|
||||
// Step 4: Let policy be a new TrustedTypePolicy object.
|
||||
// Step 5: Set policy’s name property value to policyName.
|
||||
let policy = TrustedTypePolicy::new(policy_name.clone(), global, can_gc);
|
||||
// Step 6: Set policy’s options value to «[ "createHTML" ->
|
||||
// options["createHTML", "createScript" -> options["createScript",
|
||||
// "createScriptURL" -> options["createScriptURL" ]».
|
||||
// TODO(36258): implement step 6
|
||||
let policy = TrustedTypePolicy::new(policy_name.clone(), options, global, can_gc);
|
||||
// Step 7: If the policyName is default, set the factory’s default policy value to policy.
|
||||
if policy_name == "default" {
|
||||
self.default_policy.set(Some(&policy))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue