fix(csp): preserve Document response status in violations

This commit is contained in:
ldm0
2026-11-03 18:08:18 +08:00
parent ab40e47b39
commit d2db161d33
33 changed files with 359 additions and 28 deletions
@@ -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(),
"<!doctype html><body>CSP response metadata</body>".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('<!doctype html><body>replaced</body>');
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 = '<!doctype html><body>srcdoc</body>';
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(())
}
+4
View File
@@ -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())
+1
View File
@@ -140,6 +140,7 @@ impl ContentSecurityPolicyReportingEndpoints {
}
}
#[derive(Clone, Copy)]
pub struct ContentSecurityPolicyViolationEventFields<'a> {
pub document_uri: &'a str,
pub referrer: &'a str,
+9
View File
@@ -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<u16>,
// 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<u16> {
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<u16>) {
self.arena_state.source_response_status = status;
}
pub fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
self.metadata.quirks_mode = quirks_mode;
}
+22
View File
@@ -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";
+27
View File
@@ -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<u16> {
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<String> {
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<u16>,
) -> 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<DomHandle> {
let target = self
.node(document_handle)
@@ -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<u16>) {
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)
}
@@ -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<DomHandle>,
@@ -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<v8::Local<'s, v8::Object>> {
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(),
@@ -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
@@ -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,
@@ -106,6 +106,7 @@ impl JsContextHost {
content_type,
character_set,
)
.with_response_status(Some(head.status))
.with_policy_container(policy_container),
))
}
@@ -55,6 +55,7 @@ pub(crate) struct ChildBrowsingContextSnapshot {
pub(crate) resource_was_cached: bool,
pub(crate) content_type: Option<String>,
pub(crate) character_set: String,
pub(crate) response_status: Option<u16>,
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<u16>) -> Self {
self.response_status = status;
self
}
pub(crate) fn with_policy_container(
mut self,
policy_container: DocumentPolicyContainer,
@@ -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.
@@ -289,6 +289,7 @@ enum LightweightPopupClassicScriptAdvance {
}
struct LightweightPopupDocumentProjectionSource<'a> {
response_status: Option<u16>,
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,
@@ -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(),
@@ -1791,6 +1791,23 @@ impl JsContextHost {
}
}
pub(crate) fn content_security_policy_response_status(
&self,
owner: OwnerDispatchScope,
) -> Option<u16> {
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(|| {
@@ -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<String>,
document_url: url::Url,
response_status: u16,
request_origin: moli_url::WebOrigin,
network_partition_key: Option<String>,
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,
@@ -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(),
@@ -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,
@@ -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,
@@ -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()
+8 -1
View File
@@ -1100,6 +1100,7 @@ pub(crate) struct PageVmEnvConfig {
pub(crate) document_policy_container: crate::document_runtime::DocumentPolicyContainer,
pub(crate) document_default_language: Option<String>,
pub(crate) document_last_modified: Option<f64>,
pub(crate) document_response_status: Option<u16>,
pub(crate) document_settings: moli_page_types::DocumentSettings,
pub(crate) network_offline: bool,
pub(crate) blocked_url_patterns: Vec<String>,
@@ -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<u8>)],
) {
// 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);
@@ -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(),
@@ -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(),
@@ -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(),
@@ -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(),
@@ -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(),
@@ -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(),
@@ -277,7 +277,7 @@ impl ConcurrentParseTimeRuntime {
) -> Result<StreamingNavigationPageCreationResult> {
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(),
@@ -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(),
@@ -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(),
@@ -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(),
+2
View File
@@ -1759,6 +1759,7 @@ impl ChildModulepreloadFetchCompletion {
#[derive(Debug)]
pub(super) struct LoadedChildDocument {
pub(super) response_status: Option<u16>,
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,