diff --git a/moli-core/tests/scripts/csp_response_status.rs b/moli-core/tests/scripts/csp_response_status.rs
new file mode 100644
index 0000000000..135bf0c6e1
--- /dev/null
+++ b/moli-core/tests/scripts/csp_response_status.rs
@@ -0,0 +1,127 @@
+use super::*;
+
+pub(super) fn fixture_response(query: &str) -> (&'static str, String, String) {
+ let status = match query {
+ "201" => "201 Created",
+ "404" => "404 Not Found",
+ _ => "200 OK",
+ };
+ (
+ status,
+ "Content-Type: text/html\r\nContent-Security-Policy: connect-src 'none'\r\nCache-Control: no-store\r\n".into(),
+ "
CSP response metadata".into(),
+ )
+}
+
+const PROBE: &str = r#"async function probe(view) {
+ const reported = new Promise(resolve => {
+ view.document.addEventListener('securitypolicyviolation', resolve, {once:true});
+ });
+ await view.fetch('/blocked-csp-response-status').then(
+ () => {throw new Error('CSP must prevent transport');},
+ error => {if (!(error instanceof view.TypeError)) throw error;}
+ );
+ const event = await reported;
+ return [event.statusCode, event instanceof view.SecurityPolicyViolationEvent,
+ event.isTrusted, event.target === view.document, event.effectiveDirective];
+}"#;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn document_csp_response_status_retains_navigation_metadata_through_open() -> Result<()> {
+ let servers = IntegrityServers::spawn().await?;
+ let browser = Browser::new(AppConfig::default())?;
+ for status in [201, 404] {
+ let mut page = browser
+ .fetch_allow_http_error(&format!(
+ "{}/csp-response-status.html?{status}",
+ servers.origin
+ ))
+ .await?;
+ let expression = format!(
+ r#"(async () => {{
+ const probe = {PROBE};
+ const before = await probe(window);
+ document.open();
+ document.write('replaced');
+ document.close();
+ return [before, await probe(window)];
+ }})().then(JSON.stringify)"#
+ );
+ let result = tokio::time::timeout(
+ Duration::from_secs(10),
+ page.evaluate_runtime_expression_with_await_async(&expression, true),
+ )
+ .await??;
+ let observed: serde_json::Value =
+ serde_json::from_str(result["value"].as_str().expect("CSP observations"))?;
+ assert_eq!(
+ observed,
+ serde_json::json!([
+ [status, true, true, true, "connect-src"],
+ [status, true, true, true, "connect-src"]
+ ])
+ );
+ }
+ assert!(
+ servers
+ .requests
+ .lock()
+ .iter()
+ .all(|request| { !request.path.starts_with("/blocked-csp-response-status") })
+ );
+ Ok(())
+}
+
+#[tokio::test(flavor = "multi_thread")]
+async fn child_csp_response_status_uses_its_response_without_inheriting_parent_status() -> Result<()>
+{
+ let servers = IntegrityServers::spawn().await?;
+ let browser = Browser::new(AppConfig::default())?;
+ let mut page = browser
+ .fetch(&format!("{}/csp-response-status.html?201", servers.origin))
+ .await?;
+ let expression = format!(
+ r#"(async () => {{
+ const probe = {PROBE};
+ const rows = [];
+ for (const source of ['/csp-response-status.html?404', 'about:blank', 'srcdoc']) {{
+ const frame = document.createElement('iframe');
+ if (source === 'srcdoc') frame.srcdoc = 'srcdoc';
+ else frame.src = source;
+ await new Promise(resolve => {{frame.onload = resolve; document.body.append(frame);}});
+ try {{rows.push(await probe(frame.contentWindow));}}
+ finally {{frame.remove();}}
+ }}
+ const popup = open();
+ try {{rows.push(await probe(popup));}}
+ finally {{popup.close();}}
+ rows.push(await probe(window));
+ return rows;
+ }})().then(JSON.stringify)"#
+ );
+ let result = tokio::time::timeout(
+ Duration::from_secs(10),
+ page.evaluate_runtime_expression_with_await_async(&expression, true),
+ )
+ .await??;
+ let observed: serde_json::Value =
+ serde_json::from_str(result["value"].as_str().expect("child CSP observations"))?;
+ assert_eq!(
+ observed,
+ serde_json::json!([
+ [404, true, true, true, "connect-src"],
+ [0, true, true, true, "connect-src"],
+ [0, true, true, true, "connect-src"],
+ [0, true, true, true, "connect-src"],
+ [201, true, true, true, "connect-src"]
+ ])
+ );
+ assert!(
+ servers
+ .requests
+ .lock()
+ .iter()
+ .all(|request| { !request.path.starts_with("/blocked-csp-response-status") })
+ );
+ Ok(())
+}
diff --git a/moli-core/tests/scripts/integrity.rs b/moli-core/tests/scripts/integrity.rs
index 6ce4e2279f..6cf2cb9f35 100644
--- a/moli-core/tests/scripts/integrity.rs
+++ b/moli-core/tests/scripts/integrity.rs
@@ -56,6 +56,9 @@ mod local_worker_referrer;
#[path = "service_worker_referrer.rs"]
mod service_worker_referrer;
+#[path = "csp_response_status.rs"]
+mod csp_response_status;
+
struct IntegrityServers {
origin: String,
cross_origin: String,
@@ -216,6 +219,7 @@ fn fixture_response(
"/document-referrer.html" | "/document-referrer.js" => {
module_document_referrer::fixture_response(path, query, origin, cross)
}
+ "/csp-response-status.html" => csp_response_status::fixture_response(query),
"/import-referrer.js" => dynamic_import_referrer::fixture_response(query),
"/local-referrer-worker.js" | "/local-referrer-page.html" => {
local_worker_referrer::fixture_response(path, query, request.referer.as_deref())
diff --git a/moli-csp/src/types.rs b/moli-csp/src/types.rs
index d6536eb5d6..19ffd54a49 100644
--- a/moli-csp/src/types.rs
+++ b/moli-csp/src/types.rs
@@ -140,6 +140,7 @@ impl ContentSecurityPolicyReportingEndpoints {
}
}
+#[derive(Clone, Copy)]
pub struct ContentSecurityPolicyViolationEventFields<'a> {
pub document_uri: &'a str,
pub referrer: &'a str,
diff --git a/moli-dom/src/native/document.rs b/moli-dom/src/native/document.rs
index b6743191d7..ce56537108 100644
--- a/moli-dom/src/native/document.rs
+++ b/moli-dom/src/native/document.rs
@@ -89,6 +89,7 @@ struct DocumentMetadata {
// Document identity. Only a whole-arena snapshot may copy them unchanged.
#[derive(Debug, Clone, Default)]
struct DocumentArenaState {
+ source_response_status: Option,
// Meta delivery updates policy in time order; removing the element does
// not undo the policy it delivered to this Document.
meta_referrer_policy: MetaReferrerPolicy,
@@ -263,6 +264,10 @@ impl Document {
self.metadata.source_last_modified_ms
}
+ pub fn source_response_status(&self) -> Option {
+ self.arena_state.source_response_status
+ }
+
pub fn quirks_mode(&self) -> QuirksMode {
self.metadata.quirks_mode
}
@@ -374,6 +379,10 @@ impl Document {
self.metadata.source_last_modified_ms = timestamp_ms;
}
+ pub fn set_source_response_status(&mut self, status: Option) {
+ self.arena_state.source_response_status = status;
+ }
+
pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
self.metadata.quirks_mode = quirks_mode;
}
diff --git a/moli-dom/src/native/host/clone.rs b/moli-dom/src/native/host/clone.rs
index 0a4973df7d..b1a63d0f0a 100644
--- a/moli-dom/src/native/host/clone.rs
+++ b/moli-dom/src/native/host/clone.rs
@@ -642,6 +642,28 @@ mod tests {
assert_eq!(host.owner_document_handle(template), Some(destination));
}
+ #[test]
+ fn document_response_metadata_survives_stream_replacement_but_not_cloning() {
+ let mut host = test_host();
+ let document = host.document_handle();
+ assert_eq!(
+ host.document_source_response_status_for_handle(document),
+ None
+ );
+ assert!(host.set_document_source_response_status_for_handle(document, Some(404)));
+ host.reset_html_document_shell();
+ assert_eq!(
+ host.document_source_response_status_for_handle(document),
+ Some(404)
+ );
+ let clone = host.clone_node(document, true).unwrap();
+ assert_eq!(host.document_source_response_status_for_handle(clone), None);
+ assert_eq!(
+ host.document_source_response_status_for_handle(document),
+ Some(404)
+ );
+ }
+
#[test]
fn element_copies_preserve_native_identity_and_target_template_document() {
const HTML: &str = "http://www.w3.org/1999/xhtml";
diff --git a/moli-dom/src/native/host/document.rs b/moli-dom/src/native/host/document.rs
index 4ddd4493d0..c225c35287 100644
--- a/moli-dom/src/native/host/document.rs
+++ b/moli-dom/src/native/host/document.rs
@@ -1229,6 +1229,15 @@ impl DomHost {
.and_then(Document::source_last_modified_ms)
}
+ pub fn document_source_response_status_for_handle(
+ &self,
+ document_handle: DomHandle,
+ ) -> Option {
+ self.node(document_handle)
+ .and_then(Node::as_document)
+ .and_then(Document::source_response_status)
+ }
+
pub fn document_default_language_for_node(&self, handle: DomHandle) -> Option {
let document_handle = self.owner_document_handle(handle)?;
self.document_meta_default_language_for_handle(document_handle)
@@ -1420,6 +1429,24 @@ impl DomHost {
true
}
+ pub fn set_document_source_response_status_for_handle(
+ &mut self,
+ document_handle: DomHandle,
+ status: Option,
+ ) -> bool {
+ let Some(document) = self
+ .node_mut(document_handle)
+ .and_then(|node| node.data_mut().as_document_mut())
+ else {
+ return false;
+ };
+ if document.source_response_status() == status {
+ return false;
+ }
+ document.set_source_response_status(status);
+ true
+ }
+
pub fn document_target_element(&self, document_handle: DomHandle) -> Option {
let target = self
.node(document_handle)
diff --git a/moli-renderer-v8/src/document_runtime/runtime_core.rs b/moli-renderer-v8/src/document_runtime/runtime_core.rs
index d053eb9512..02cdc4db50 100644
--- a/moli-renderer-v8/src/document_runtime/runtime_core.rs
+++ b/moli-renderer-v8/src/document_runtime/runtime_core.rs
@@ -343,6 +343,13 @@ impl DocumentRuntime {
.set_document_source_last_modified_for_handle(document_handle, timestamp_ms);
}
+ pub(crate) fn set_document_source_response_status(&mut self, status: Option) {
+ let document_handle = self.dom_host.document_handle();
+ let _ = self
+ .dom_host
+ .set_document_source_response_status_for_handle(document_handle, status);
+ }
+
pub(crate) fn take_style_source_document_sync_pending(&mut self) -> bool {
std::mem::take(&mut self.style_source_document_sync_pending)
}
diff --git a/moli-renderer-v8/src/document_runtime/security_policy.rs b/moli-renderer-v8/src/document_runtime/security_policy.rs
index 956c94693b..e7d691687e 100644
--- a/moli-renderer-v8/src/document_runtime/security_policy.rs
+++ b/moli-renderer-v8/src/document_runtime/security_policy.rs
@@ -2280,7 +2280,12 @@ impl DocumentRuntime {
.current_main_document_task_owner()
.ok_or_else(|| anyhow::anyhow!("main CSP violation document owner is unavailable"))?;
if send_report {
- let fields = ContentSecurityPolicyViolationEventFields::from(violation);
+ let fields = self.content_security_policy_violation_event_fields(
+ target
+ .and_then(|target| self.dom_host.owner_document_handle(target))
+ .unwrap_or_else(|| self.document_handle()),
+ violation,
+ );
let report_owner = event_owner.unwrap_or(document_owner);
let child_handle = event_owner.and(target).and_then(|document| {
host.child_browsing_context_host_for_document_handle(document)
@@ -2361,11 +2366,19 @@ impl DocumentRuntime {
})
.unwrap_or_else(|| scope.get_current_context());
let scope = &mut v8::ContextScope::new(scope, context);
+ let document = match event_target {
+ EventTargetHandle::Node(handle) => self
+ .dom_host
+ .owner_document_handle(handle)
+ .unwrap_or_else(|| self.document_handle()),
+ _ => self.document_handle(),
+ };
+ let fields = self.content_security_policy_violation_event_fields(document, violation);
let event = create_content_security_policy_violation_event(
scope,
event_target_value,
event_target_value,
- violation,
+ &fields,
)
.map_err(anyhow::Error::msg)?;
self.dispatch_public_event_best_effort(
@@ -2379,6 +2392,21 @@ impl DocumentRuntime {
Ok(())
}
+ pub(crate) fn content_security_policy_violation_event_fields<'a>(
+ &self,
+ document: DomHandle,
+ violation: &'a DocumentContentSecurityPolicyViolation,
+ ) -> ContentSecurityPolicyViolationEventFields<'a> {
+ ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(
+ self.dom_host
+ .document_source_response_status_for_handle(document)
+ .unwrap_or(0),
+ ),
+ ..ContentSecurityPolicyViolationEventFields::from(violation)
+ }
+ }
+
fn document_content_security_policies_for_optional_document(
&self,
document_handle: Option,
@@ -3038,13 +3066,10 @@ pub(crate) fn create_content_security_policy_violation_event<'s>(
scope: &mut v8::PinScope<'s, '_>,
current_target: v8::Local<'s, v8::Value>,
target: v8::Local<'s, v8::Value>,
- violation: &DocumentContentSecurityPolicyViolation,
+ fields: &ContentSecurityPolicyViolationEventFields<'_>,
) -> anyhow::Result> {
- let event = create_security_policy_violation_event(
- scope,
- &ContentSecurityPolicyViolationEventFields::from(violation),
- )
- .ok_or_else(|| anyhow::anyhow!("failed to create CSP violation event"))?;
+ let event = create_security_policy_violation_event(scope, fields)
+ .ok_or_else(|| anyhow::anyhow!("failed to create CSP violation event"))?;
let _ = crate::context_bootstrap::event_backing(scope, event).set(
scope,
v8str(scope, "target").into(),
diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_documents/commit.rs b/moli-renderer-v8/src/native_bridge/context_host/child_documents/commit.rs
index 59d15d3c25..4a8ff43e8d 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/child_documents/commit.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/child_documents/commit.rs
@@ -203,6 +203,11 @@ impl JsContextHost {
};
self.dom_host_mut()
.set_document_character_set_for_handle(document_handle, &snapshot.character_set);
+ self.dom_host_mut()
+ .set_document_source_response_status_for_handle(
+ document_handle,
+ snapshot.response_status,
+ );
self.dom_host_mut()
.set_document_allow_declarative_shadow_roots_for_handle(document_handle, true);
// Navigation-created Documents start loading before parser scripts can
diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs b/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs
index 7d05929b50..d74a1c56ee 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs
@@ -501,8 +501,10 @@ impl JsContextHost {
// SAFETY: JsContextHost is owned by the ScriptVm that owns this DocumentRuntime.
unsafe { &mut *self.runtime }
.record_content_security_policy_inspector_issue(Some(handle), violation);
- let fields =
- ContentSecurityPolicyViolationEventFields::from_url_violation(violation);
+ let fields = ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(loaded.response_status.unwrap_or(0)),
+ ..ContentSecurityPolicyViolationEventFields::from_url_violation(violation)
+ };
send_content_security_policy_reports(
&reports,
pending.resource_loader.request_client(),
@@ -710,6 +712,7 @@ impl JsContextHost {
loaded.content_type,
loaded.character_set,
)
+ .with_response_status(loaded.response_status)
.with_resource_was_cached(resource_was_cached);
self.cache_child_snapshot_with_current_document_policy(handle, snapshot)
}
@@ -1024,6 +1027,7 @@ fn child_document_load_outcome_from_response(
};
Ok(ChildDocumentLoadOutcome::Loaded(Box::new(
LoadedChildDocument {
+ response_status: Some(head.status),
response_links: crate::response_links::DocumentResponseLinks::parse(
&head.final_url,
&head.headers,
diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_documents/snapshots.rs b/moli-renderer-v8/src/native_bridge/context_host/child_documents/snapshots.rs
index a6ee47706c..eb7c1e30e6 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/child_documents/snapshots.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/child_documents/snapshots.rs
@@ -106,6 +106,7 @@ impl JsContextHost {
content_type,
character_set,
)
+ .with_response_status(Some(head.status))
.with_policy_container(policy_container),
))
}
diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_frame_snapshots.rs b/moli-renderer-v8/src/native_bridge/context_host/child_frame_snapshots.rs
index f4469d18e3..ec31cc3aed 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/child_frame_snapshots.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/child_frame_snapshots.rs
@@ -55,6 +55,7 @@ pub(crate) struct ChildBrowsingContextSnapshot {
pub(crate) resource_was_cached: bool,
pub(crate) content_type: Option,
pub(crate) character_set: String,
+ pub(crate) response_status: Option,
pub(crate) policy_container: DocumentPolicyContainer,
}
@@ -89,6 +90,7 @@ impl ChildBrowsingContextSnapshot {
resource_was_cached: false,
content_type,
character_set: character_set.into(),
+ response_status: None,
policy_container: DocumentPolicyContainer::default(),
}
}
@@ -126,6 +128,11 @@ impl ChildBrowsingContextSnapshot {
self
}
+ pub(crate) fn with_response_status(mut self, status: Option) -> Self {
+ self.response_status = status;
+ self
+ }
+
pub(crate) fn with_policy_container(
mut self,
policy_container: DocumentPolicyContainer,
diff --git a/moli-renderer-v8/src/native_bridge/context_host/misc_platform_api_tasks.rs b/moli-renderer-v8/src/native_bridge/context_host/misc_platform_api_tasks.rs
index 05cfc92cfc..c3794b849e 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/misc_platform_api_tasks.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/misc_platform_api_tasks.rs
@@ -265,13 +265,15 @@ impl JsContextHost {
})
.unwrap_or(resolved.document_handle);
let target = EventTargetHandle::Node(event_target);
+ let fields = self.content_security_policy_violation_event_fields(
+ resolved.document_handle,
+ &violation,
+ );
crate::host::event_target_value(scope, host_ptr, target)
.ok()
.and_then(|value| {
- create_content_security_policy_violation_event(
- scope, value, value, &violation,
- )
- .ok()
+ create_content_security_policy_violation_event(scope, value, value, &fields)
+ .ok()
})
.is_some_and(|event| {
// SAFETY: the Host and its DocumentRuntime share one ScriptVm.
diff --git a/moli-renderer-v8/src/native_bridge/context_host/popups.rs b/moli-renderer-v8/src/native_bridge/context_host/popups.rs
index 660579eeb4..767fd19f59 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/popups.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/popups.rs
@@ -289,6 +289,7 @@ enum LightweightPopupClassicScriptAdvance {
}
struct LightweightPopupDocumentProjectionSource<'a> {
+ response_status: Option,
response_links: &'a crate::response_links::DocumentResponseLinks,
task: LightweightPopupNavigationTaskToken,
document_url: &'a Url,
@@ -2997,6 +2998,7 @@ impl JsContextHost {
policy_container.response_content_security_policies.clone();
}
let loaded = LoadedChildDocument {
+ response_status: snapshot.response_status,
response_links: Default::default(),
final_url: snapshot.url,
policy_container,
@@ -3089,6 +3091,7 @@ impl JsContextHost {
);
Ok(PopupDocumentLoadOutcome::Loaded(Box::new(
LoadedChildDocument {
+ response_status: Some(head.status),
response_links: crate::response_links::DocumentResponseLinks::parse(
&head.final_url,
&head.headers,
@@ -3333,6 +3336,7 @@ impl JsContextHost {
scope,
window,
LightweightPopupDocumentProjectionSource {
+ response_status: loaded.response_status,
response_links: &loaded.response_links,
task,
document_url: &final_url,
@@ -3795,11 +3799,18 @@ impl JsContextHost {
let Some(window) = self.lightweight_popup_window(scope, popup_id) else {
return;
};
+ let fields = crate::content_security_policy::ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(
+ self.content_security_policy_response_status(
+ super::OwnerDispatchScope::LightweightPopup(popup_id),
+ )
+ .unwrap_or(0),
+ ),
+ ..crate::content_security_policy::ContentSecurityPolicyViolationEventFields::from(
+ violation,
+ )
+ };
if send_report {
- let fields =
- crate::content_security_policy::ContentSecurityPolicyViolationEventFields::from(
- violation,
- );
let Some(document_owner) = self.current_lightweight_popup_document_owner(popup_id)
else {
tracing::debug!(popup_id, "discarded CSP report for retired popup document");
@@ -3819,7 +3830,7 @@ impl JsContextHost {
scope,
window.into(),
window.into(),
- violation,
+ &fields,
) {
Ok(event) => {
self.dispatch_lightweight_popup_window_event(
@@ -3985,6 +3996,11 @@ impl JsContextHost {
let document_handle =
self.remember_lightweight_popup_document_handle(scope, popup_id, document);
if let Some(document_handle) = document_handle {
+ self.dom_host_mut()
+ .set_document_source_response_status_for_handle(
+ document_handle,
+ source.response_status,
+ );
let _ = crate::context_bootstrap::install_css_runtime_state_for_document(
scope,
window,
@@ -5130,6 +5146,7 @@ impl JsContextHost {
scope,
window,
LightweightPopupDocumentProjectionSource {
+ response_status: None,
response_links: &Default::default(),
task,
document_url: &document_url,
diff --git a/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs b/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs
index 8c57c85a59..bedffb4967 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/popups/renderer_owned.rs
@@ -292,6 +292,7 @@ impl JsContextHost {
document_policy_container: policy.clone(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: moli_page_types::DocumentSettings {
script_execution_disabled: !policy.sandbox.allows_scripts,
debugger_enabled: self.dom_debugger_state.debugger_pauses_enabled(),
diff --git a/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs b/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs
index 52bd1ac4fe..4d54c2c7b6 100644
--- a/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs
+++ b/moli-renderer-v8/src/native_bridge/context_host/security_policy.rs
@@ -1791,6 +1791,23 @@ impl JsContextHost {
}
}
+ pub(crate) fn content_security_policy_response_status(
+ &self,
+ owner: OwnerDispatchScope,
+ ) -> Option {
+ let document = match owner {
+ OwnerDispatchScope::Top => Some(self.document_handle()),
+ OwnerDispatchScope::Child(handle) => {
+ self.child_browsing_context_document_handle(handle)
+ }
+ OwnerDispatchScope::LightweightPopup(popup_id) => {
+ self.lightweight_popup_document_handle(popup_id)
+ }
+ }?;
+ self.dom_host()
+ .document_source_response_status_for_handle(document)
+ }
+
fn dispatch_child_content_security_policy_violation_event<'s>(
&mut self,
scope: &mut v8::PinScope<'s, '_>,
@@ -1801,14 +1818,20 @@ impl JsContextHost {
let window = self
.child_browsing_context_window_wrapper(scope, handle)
.ok_or_else(|| anyhow::anyhow!("child window wrapper is unavailable"))?;
+ let fields = ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(
+ self.content_security_policy_response_status(OwnerDispatchScope::Child(handle))
+ .unwrap_or(0),
+ ),
+ ..ContentSecurityPolicyViolationEventFields::from(violation)
+ };
let event = create_content_security_policy_violation_event(
scope,
window.into(),
window.into(),
- violation,
+ &fields,
)?;
if send_report {
- let fields = ContentSecurityPolicyViolationEventFields::from(violation);
let document_owner =
self.current_child_document_task_owner(handle)
.ok_or_else(|| {
diff --git a/moli-renderer-v8/src/network_host/csp_reports.rs b/moli-renderer-v8/src/network_host/csp_reports.rs
index 8320d8e180..0e15b078f2 100644
--- a/moli-renderer-v8/src/network_host/csp_reports.rs
+++ b/moli-renderer-v8/src/network_host/csp_reports.rs
@@ -31,19 +31,23 @@ pub(crate) fn send_content_security_policy_reports_for_window(
else {
return;
};
+ let fields = ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(request_context.response_status),
+ ..*fields
+ };
if let Some(execution_owner) = host.current_window_execution_context_owner(owner.dispatch_scope)
&& let Some((_, context)) =
host.window_execution_context(scope, execution_owner, owner.dispatch_scope)
{
let scope = &mut v8::ContextScope::new(scope, context);
crate::content_security_policy::notify_content_security_policy_reporting_observers(
- scope, fields,
+ scope, &fields,
);
}
send_content_security_policy_reports_from_window_context(
host,
&request_context,
- fields,
+ &fields,
report_uri_endpoints,
report_to_endpoints,
);
@@ -67,19 +71,23 @@ pub(crate) fn send_content_security_policy_reports_for_lightweight_popup(
else {
return;
};
+ let fields = ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(request_context.response_status),
+ ..*fields
+ };
if let Some(execution_owner) = host.current_window_execution_context_owner(owner.dispatch_scope)
&& let Some((_, context)) =
host.window_execution_context(scope, execution_owner, owner.dispatch_scope)
{
let scope = &mut v8::ContextScope::new(scope, context);
crate::content_security_policy::notify_content_security_policy_reporting_observers(
- scope, fields,
+ scope, &fields,
);
}
send_content_security_policy_reports_from_window_context(
host,
&request_context,
- fields,
+ &fields,
report_uri_endpoints,
report_to_endpoints,
);
@@ -118,6 +126,7 @@ pub(crate) struct WindowCspReportRequestContext {
request_client: ResourceRequestClient,
frame_id: Option,
document_url: url::Url,
+ response_status: u16,
request_origin: moli_url::WebOrigin,
network_partition_key: Option,
policy_context: crate::types::SubresourcePolicyContext,
@@ -208,7 +217,10 @@ pub(crate) fn send_content_security_policy_violation_report_from_window_context(
request_context: &WindowCspReportRequestContext,
violation: &crate::document_runtime::DocumentContentSecurityPolicyViolation,
) {
- let fields = ContentSecurityPolicyViolationEventFields::from(violation);
+ let fields = ContentSecurityPolicyViolationEventFields {
+ status_code: i32::from(request_context.response_status),
+ ..ContentSecurityPolicyViolationEventFields::from(violation)
+ };
send_content_security_policy_reports_from_window_context(
host,
request_context,
@@ -552,6 +564,9 @@ fn window_csp_report_request_context_for_identity(
resource_loader,
frame_id,
document_url,
+ response_status: host
+ .content_security_policy_response_status(identity.dispatch_scope())
+ .unwrap_or(0),
request_origin,
network_partition_key: active_subresource_network_partition_key(
host,
diff --git a/moli-renderer-v8/src/page_task_queue/resource_completions.rs b/moli-renderer-v8/src/page_task_queue/resource_completions.rs
index e1e467ffa5..ef45001f9c 100644
--- a/moli-renderer-v8/src/page_task_queue/resource_completions.rs
+++ b/moli-renderer-v8/src/page_task_queue/resource_completions.rs
@@ -1186,6 +1186,7 @@ mod tests {
moli_dom::NodeId::new(7),
Ok(ChildDocumentLoadOutcome::Loaded(Box::new(
LoadedChildDocument {
+ response_status: None,
response_links: Default::default(),
final_url: Url::parse("https://example.test/child").unwrap(),
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
@@ -1212,6 +1213,7 @@ mod tests {
),
Ok(PopupDocumentLoadOutcome::Loaded(Box::new(
LoadedChildDocument {
+ response_status: None,
response_links: Default::default(),
final_url: Url::parse("https://example.test/popup").unwrap(),
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
diff --git a/moli-renderer-v8/src/runtime/owner/document_response.rs b/moli-renderer-v8/src/runtime/owner/document_response.rs
index 28ea649591..0522db8070 100644
--- a/moli-renderer-v8/src/runtime/owner/document_response.rs
+++ b/moli-renderer-v8/src/runtime/owner/document_response.rs
@@ -121,6 +121,8 @@ impl RendererCreateStreamingRawPageRequest {
history_policy_container,
document_default_language,
document_last_modified,
+ document_response_status: (!moli_url::is_about_blank(&final_url))
+ .then_some(response_status),
document_settings,
network_offline,
blocked_url_patterns,
diff --git a/moli-renderer-v8/src/runtime/owner/page_creation.rs b/moli-renderer-v8/src/runtime/owner/page_creation.rs
index 263bd41ff4..49f8e7dd23 100644
--- a/moli-renderer-v8/src/runtime/owner/page_creation.rs
+++ b/moli-renderer-v8/src/runtime/owner/page_creation.rs
@@ -937,6 +937,8 @@ impl RendererOwnerHandle {
history_policy_container,
document_default_language,
document_last_modified,
+ document_response_status: (!moli_url::is_about_blank(&final_url))
+ .then_some(response_status),
document_settings,
network_offline,
blocked_url_patterns,
diff --git a/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs b/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs
index 42ad0fa3c9..37cb40497c 100644
--- a/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs
@@ -1497,6 +1497,7 @@ impl PageVm {
},
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: moli_page_types::DocumentSettings {
script_execution_disabled: self.script_execution_disabled(),
..self.document_settings.clone()
diff --git a/moli-renderer-v8/src/runtime/page_vm/mod.rs b/moli-renderer-v8/src/runtime/page_vm/mod.rs
index 166d9728e4..be66727803 100644
--- a/moli-renderer-v8/src/runtime/page_vm/mod.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/mod.rs
@@ -1100,6 +1100,7 @@ pub(crate) struct PageVmEnvConfig {
pub(crate) document_policy_container: crate::document_runtime::DocumentPolicyContainer,
pub(crate) document_default_language: Option,
pub(crate) document_last_modified: Option,
+ pub(crate) document_response_status: Option,
pub(crate) document_settings: moli_page_types::DocumentSettings,
pub(crate) network_offline: bool,
pub(crate) blocked_url_patterns: Vec,
@@ -1138,11 +1139,14 @@ impl PageVmEnvConfig {
.collect()
}
- pub(crate) fn apply_navigation_response_headers(
+ pub(crate) fn apply_navigation_response_metadata(
&mut self,
final_url: &Url,
+ status: u16,
headers: &[(String, Vec)],
) {
+ // Blank Documents inherit policy, but have no fetched response.
+ self.document_response_status = (!moli_url::is_about_blank(final_url)).then_some(status);
self.about_document_state = self
.about_document_state
.take()
@@ -4672,6 +4676,9 @@ impl PageVm {
self.vm_mut()
.document_runtime
.set_document_source_last_modified(env.document_last_modified);
+ self.vm_mut()
+ .document_runtime
+ .set_document_source_response_status(env.document_response_status);
}
self.vm_mut()
.set_stored_document_start_scripts(&env.document_start_scripts);
diff --git a/moli-renderer-v8/src/runtime/page_vm/test_support.rs b/moli-renderer-v8/src/runtime/page_vm/test_support.rs
index f49843a4bd..38c457582f 100644
--- a/moli-renderer-v8/src/runtime/page_vm/test_support.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/test_support.rs
@@ -729,6 +729,7 @@ fn minimal_test_page_vm_env_config() -> PageVmEnvConfig {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs b/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs
index 606386c424..4c4f758792 100644
--- a/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs
@@ -988,6 +988,7 @@ pub(super) fn stale_loaded_completion(
ChildDocumentLoadNetworkAttribution::new(frame_id.to_owned(), None, loader_id),
Ok(ChildDocumentLoadOutcome::Loaded(Box::new(
LoadedChildDocument {
+ response_status: None,
response_links: Default::default(),
final_url: Url::parse(request_url).expect("final URL"),
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/extracted/page_task_dispatch.rs b/moli-renderer-v8/src/runtime/page_vm/tests/extracted/page_task_dispatch.rs
index cdec1b0b52..18c6449dfd 100644
--- a/moli-renderer-v8/src/runtime/page_vm/tests/extracted/page_task_dispatch.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/tests/extracted/page_task_dispatch.rs
@@ -25,6 +25,7 @@ fn default_runtime_hooks_reject_direct_no_owner_page_vm_construction() {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/mod.rs b/moli-renderer-v8/src/runtime/page_vm/tests/mod.rs
index ee5e79b316..44c752da39 100644
--- a/moli-renderer-v8/src/runtime/page_vm/tests/mod.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/tests/mod.rs
@@ -709,6 +709,7 @@ fn test_page_vm_with_loader_dom_host_hooks_and_response_referrer_policy(
},
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/popup_document_completion.rs b/moli-renderer-v8/src/runtime/page_vm/tests/popup_document_completion.rs
index fd76136652..c31e60a47a 100644
--- a/moli-renderer-v8/src/runtime/page_vm/tests/popup_document_completion.rs
+++ b/moli-renderer-v8/src/runtime/page_vm/tests/popup_document_completion.rs
@@ -77,6 +77,7 @@ fn loaded_popup_completion(
target,
Ok(PopupDocumentLoadOutcome::Loaded(Box::new(
LoadedChildDocument {
+ response_status: None,
response_links: Default::default(),
final_url: Url::parse(final_url).expect("popup completion final URL"),
policy_container: crate::document_runtime::DocumentPolicyContainer::default(),
diff --git a/moli-renderer-v8/src/runtime/phase_one/mod.rs b/moli-renderer-v8/src/runtime/phase_one/mod.rs
index 15e9ead0f3..5b6928f0fb 100644
--- a/moli-renderer-v8/src/runtime/phase_one/mod.rs
+++ b/moli-renderer-v8/src/runtime/phase_one/mod.rs
@@ -695,6 +695,7 @@ mod tests {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/phase_one/streaming.rs b/moli-renderer-v8/src/runtime/phase_one/streaming.rs
index c15d4dc8f2..17dc86ea83 100644
--- a/moli-renderer-v8/src/runtime/phase_one/streaming.rs
+++ b/moli-renderer-v8/src/runtime/phase_one/streaming.rs
@@ -277,7 +277,7 @@ impl ConcurrentParseTimeRuntime {
) -> Result {
debug_assert!(!response_headers_indicate_download(&response_headers));
let mut env = env.clone();
- env.apply_navigation_response_headers(&final_url, &response_headers);
+ env.apply_navigation_response_metadata(&final_url, response_status, &response_headers);
let content_type = content_type.or_else(|| {
moli_web_mime::navigation_response_mime_type_from_headers(&response_headers)
@@ -1313,6 +1313,7 @@ mod tests {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_blocking_scripts.rs b/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_blocking_scripts.rs
index bb299d6c99..96c5aa5b71 100644
--- a/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_blocking_scripts.rs
+++ b/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_blocking_scripts.rs
@@ -657,6 +657,7 @@ fn parser_owner_boundary_with_live_backend_queues_document_turn_before_runtime_w
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -951,6 +952,7 @@ fn parser_connected_head_script_does_not_push_later_head_tokens_into_body() {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -1132,6 +1134,7 @@ fn parser_connected_external_head_script_with_live_head_and_body_mutation_keeps_
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -1737,6 +1740,7 @@ fn parser_owner_body_stylesheet_pause_preserves_unconsumed_tail_on_live_page_vm(
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_script_handoffs.rs b/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_script_handoffs.rs
index cdf461f2ea..37fe7d621d 100644
--- a/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_script_handoffs.rs
+++ b/moli-renderer-v8/src/runtime/phase_one/tests/extracted/parser_script_handoffs.rs
@@ -354,6 +354,7 @@ fn external_async_handoff_marks_parser_stream_already_started_on_live_backend()
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -481,6 +482,7 @@ fn blocking_classic_handoff_registers_parser_owned_handle_on_live_backend() {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -622,6 +624,7 @@ fn non_async_post_parse_handoff_registers_pending_before_source_and_seals_withou
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -797,6 +800,7 @@ fn parser_owned_external_module_handoff_starts_pending_script_tree_root_fetch()
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -1117,6 +1121,7 @@ fn parser_owned_inline_importmap_handoff_registers_parser_owned_handle_on_live_b
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
@@ -1285,6 +1290,7 @@ fn parser_owner_style_import_handoff_is_stylesheet_gated_on_live_page_vm() {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/runtime/phase_one/tests/extracted/tree_mutations_and_document_write.rs b/moli-renderer-v8/src/runtime/phase_one/tests/extracted/tree_mutations_and_document_write.rs
index b6b351ae42..df96f3eada 100644
--- a/moli-renderer-v8/src/runtime/phase_one/tests/extracted/tree_mutations_and_document_write.rs
+++ b/moli-renderer-v8/src/runtime/phase_one/tests/extracted/tree_mutations_and_document_write.rs
@@ -1142,6 +1142,7 @@ fn parser_connected_head_document_write_keeps_later_head_tokens_in_head() {
document_policy_container: Default::default(),
document_default_language: None,
document_last_modified: None,
+ document_response_status: None,
document_settings: Default::default(),
network_offline: false,
blocked_url_patterns: Vec::new(),
diff --git a/moli-renderer-v8/src/types.rs b/moli-renderer-v8/src/types.rs
index 6097d94a05..21aae0a86b 100644
--- a/moli-renderer-v8/src/types.rs
+++ b/moli-renderer-v8/src/types.rs
@@ -1759,6 +1759,7 @@ impl ChildModulepreloadFetchCompletion {
#[derive(Debug)]
pub(super) struct LoadedChildDocument {
+ pub(super) response_status: Option,
pub(super) response_links: crate::response_links::DocumentResponseLinks,
pub(super) final_url: Url,
pub(super) policy_container: DocumentPolicyContainer,
@@ -1787,6 +1788,7 @@ impl ChildDocumentLoadOutcome {
>,
) -> Self {
Self::BlockedResponse(Box::new(LoadedChildDocument {
+ response_status: None,
final_url,
policy_container: DocumentPolicyContainer {
required_content_security_policy,