Implement trusted types processing for JavaScript URL (#38623)

We pass in the new trait implementation to process the value,
which the CSP crate calls in its implementation. Additionally,
since the request url can change, we need to propagate that
to load_data as well.

This also avoids a crash when a discarded browsing context is
accessed while navigating the iframes in the WPT tests. This
is a known issue, but hampers investigation into actual
Trusted Types support.

All tests using iframes don't work, as they don't have the
correct browsing context. The other tests do work, but some
fail on header ascii parsing (#36801) or error while handling
errors. That last one I don't understand based on the current
code and I would need to do a deep-dive in the existing code
to understand better what's going on.

Part of #36258
Part of #37920

---------

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-08-27 23:53:18 +02:00 committed by GitHub
parent c4bd955a69
commit 84f478a47a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 66 additions and 45 deletions

View file

@ -19,13 +19,16 @@ use js::rust::describe_scripted_caller;
use log::warn;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::csppolicyviolationreport::CSPViolationReportBuilder;
use crate::dom::element::Element;
use crate::dom::globalscope::GlobalScope;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::trustedscript::TrustedScript;
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
use crate::security_manager::CSPViolationReportTask;
pub(crate) trait CspReporting {
@ -34,8 +37,9 @@ pub(crate) trait CspReporting {
fn should_navigation_request_be_blocked(
&self,
global: &GlobalScope,
load_data: &LoadData,
load_data: &mut LoadData,
element: Option<&Element>,
can_gc: CanGc,
) -> bool;
fn should_elements_inline_type_behavior_be_blocked(
&self,
@ -96,13 +100,14 @@ impl CspReporting for Option<CspList> {
fn should_navigation_request_be_blocked(
&self,
global: &GlobalScope,
load_data: &LoadData,
load_data: &mut LoadData,
element: Option<&Element>,
can_gc: CanGc,
) -> bool {
let Some(csp_list) = self else {
return false;
};
let request = Request {
let mut request = Request {
url: load_data.url.clone().into_url(),
origin: match &load_data.load_origin {
LoadOrigin::Script(immutable_origin) => immutable_origin.clone().into_url_origin(),
@ -117,8 +122,25 @@ impl CspReporting for Option<CspList> {
parser_metadata: ParserMetadata::None,
};
// TODO: set correct navigation check type for form submission if applicable
let (result, violations) =
csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other);
let (result, violations) = csp_list.should_navigation_request_be_blocked(
&mut request,
NavigationCheckType::Other,
|script_source| {
// Step 4. Let convertedScriptSource be the result of executing
// Process value with a default policy algorithm, with the following arguments:
TrustedScript::get_trusted_script_compliant_string(
global,
TrustedScriptOrString::String(script_source.into()),
"Location href",
can_gc,
)
.ok()
.map(|s| s.into())
},
);
// In case trusted types processing has changed the Javascript contents
load_data.url = request.url.into();
global.report_csp_violations(violations, element, None);

View file

@ -34,7 +34,6 @@ use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::csp::CspReporting;
use crate::dom::document::{Document, determine_policy_for_token};
use crate::dom::domtokenlist::DOMTokenList;
use crate::dom::element::{
@ -167,19 +166,15 @@ impl HTMLIFrameElement {
if load_data.url.scheme() == "javascript" {
let window_proxy = self.GetContentWindow();
if let Some(window_proxy) = window_proxy {
let global = &document.global();
if global.get_csp_list().should_navigation_request_be_blocked(
global,
&load_data,
if !ScriptThread::navigate_to_javascript_url(
&document.global(),
&window_proxy.global(),
&mut load_data,
Some(self.upcast()),
can_gc,
) {
return;
}
// Important re security. See https://github.com/servo/servo/issues/23373
if ScriptThread::check_load_origin(&load_data.load_origin, &document.url().origin())
{
ScriptThread::eval_js_url(&window_proxy.global(), &mut load_data, can_gc);
}
}
}

View file

@ -630,15 +630,9 @@ impl ScriptThread {
.clone();
let task = task!(navigate_javascript: move || {
// Important re security. See https://github.com/servo/servo/issues/23373
if let Some(window) = trusted_global.root().downcast::<Window>() {
if trusted_global.root().is::<Window>() {
let global = &trusted_global.root();
// Step 5: If the result of should navigation request of type be blocked by
// Content Security Policy? given request and cspNavigationType is "Blocked", then return. [CSP]
if global.get_csp_list().should_navigation_request_be_blocked(global, &load_data, None) {
return;
}
if ScriptThread::check_load_origin(&load_data.load_origin, &window.get_url().origin()) {
ScriptThread::eval_js_url(&trusted_global.root(), &mut load_data, CanGc::note());
if Self::navigate_to_javascript_url(global, global, &mut load_data, None, CanGc::note()) {
sender
.send((pipeline_id, ScriptToConstellationMessage::LoadUrl(load_data, history_handling)))
.unwrap();
@ -663,6 +657,36 @@ impl ScriptThread {
});
}
/// <https://html.spec.whatwg.org/multipage/#navigate-to-a-javascript:-url>
pub(crate) fn navigate_to_javascript_url(
global: &GlobalScope,
containing_global: &GlobalScope,
load_data: &mut LoadData,
container: Option<&Element>,
can_gc: CanGc,
) -> bool {
// Step 3. If initiatorOrigin is not same origin-domain with targetNavigable's active document's origin, then return.
//
// Important re security. See https://github.com/servo/servo/issues/23373
if !Self::check_load_origin(&load_data.load_origin, &global.get_url().origin()) {
return false;
}
// Step 5: If the result of should navigation request of type be blocked by
// Content Security Policy? given request and cspNavigationType is "Blocked", then return. [CSP]
if global
.get_csp_list()
.should_navigation_request_be_blocked(global, load_data, container, can_gc)
{
return false;
}
// Step 6. Let newDocument be the result of evaluating a javascript: URL given targetNavigable,
// url, initiatorOrigin, and userInvolvement.
Self::eval_js_url(containing_global, load_data, can_gc);
true
}
pub(crate) fn process_attach_layout(new_layout_info: NewLayoutInfo, origin: MutableOrigin) {
with_script_thread(|script_thread| {
let pipeline_id = Some(new_layout_info.new_pipeline_id);