fix(csp): retain all violations in document policy checks

This commit is contained in:
ldm0
2026-09-23 09:04:55 +08:00
parent a429d38070
commit afefceb546
14 changed files with 672 additions and 377 deletions
+1
View File
@@ -672,6 +672,7 @@ pub(crate) struct DocumentPolicyContainer {
pub(crate) document_content_security_policies: Vec<String>,
pub(crate) response_content_security_policies: Vec<String>,
pub(crate) response_content_security_report_only_policies: Vec<String>,
pub(crate) inherited_meta_content_security_policies: Vec<String>,
pub(crate) content_security_reporting_endpoints:
crate::content_security_policy::ContentSecurityPolicyReportingEndpoints,
pub(crate) credentialless: bool,
File diff suppressed because it is too large Load Diff
@@ -324,6 +324,10 @@ impl JsContextHost {
expected_current_owner,
)?;
debug_assert_eq!(owner_transition.retired_owner(), expected_current_owner);
unsafe { &*self.runtime }.initialize_inherited_meta_content_security_policies(
document_handle,
&document_policy_container.inherited_meta_content_security_policies,
);
let ancestor_origins_refreshed =
self.refresh_current_child_document_ancestor_origins(handle);
debug_assert!(
@@ -41,10 +41,16 @@ impl JsContextHost {
.dom_host()
.node(handle)
.and_then(crate::dom::native::Node::owner_document)
&& let Some(permissions_policy) =
self.document_permissions_policy_for_document_handle(parent_document)
{
policy_container.permissions_policy = permissions_policy;
if let Some(permissions_policy) =
self.document_permissions_policy_for_document_handle(parent_document)
{
policy_container.permissions_policy = permissions_policy;
}
// Inheritance clones the creator's policy list at navigation time.
// Later parent meta mutations must not alter the child's policies.
policy_container.inherited_meta_content_security_policies = unsafe { &*self.runtime }
.meta_content_security_policy_strings_for_document(parent_document);
}
policy_container.document_referrer =
self.document_url_for_child_context(handle).to_string();
@@ -101,6 +107,12 @@ impl JsContextHost {
let document_url = init.document_url.clone();
let document_handle =
self.create_empty_live_child_html_document(document_url.clone(), Some("text/html"));
unsafe { &*self.runtime }.initialize_inherited_meta_content_security_policies(
document_handle,
&init
.policy_container
.inherited_meta_content_security_policies,
);
self.dom_host_mut()
.set_document_fallback_base_url_for_handle(
document_handle,
@@ -419,16 +419,16 @@ impl JsContextHost {
.clear_content_security_policy_for_bypass();
}
let ancestor_origins = self.child_document_frame_ancestor_origins(handle);
let (report_only_violation, enforced_violation) = loaded
let (report_only_violations, enforced_violations) = loaded
.policy_container
.navigation_response_frame_ancestors_check(
&loaded.final_url,
DocumentNavigationEmbeddingContext::Nested(&ancestor_origins),
)
.into_violations();
for violation in report_only_violation
for violation in report_only_violations
.iter()
.chain(enforced_violation.iter())
.chain(enforced_violations.iter())
{
// The protected response never receives a Document when enforcement blocks
// it, so there is no target on which to dispatch a DOM event. Keep the
@@ -446,7 +446,7 @@ impl JsContextHost {
&violation.report_to_endpoints,
);
}
if let Some(violation) = enforced_violation {
if let Some(violation) = enforced_violations.into_iter().next() {
Err(format!(
"child document response blocked by Content Security Policy `{}` for `{}`",
violation.effective_directive, violation.blocked_uri
@@ -28,9 +28,12 @@ impl JsContextHost {
handle: DomHandle,
url: &Url,
) -> Option<ChildBrowsingContextSnapshot> {
let snapshot = self.apply_page_csp_bypass_to_child_snapshot(
self.materialize_local_child_snapshot_for_url(url)?,
);
let mut snapshot = self.materialize_local_child_snapshot_for_url(url)?;
if ChildBrowsingContextBootstrap::Url(url.clone()).content_security_policy_inherited() {
snapshot.policy_container =
self.initial_child_about_blank_policy_container_from_parent(handle);
}
let snapshot = self.apply_page_csp_bypass_to_child_snapshot(snapshot);
if moli_url::is_about_blank(&snapshot.url) {
Some(snapshot.with_fallback_base_url(self.document_base_url_for_child_context(handle)))
} else {
@@ -151,31 +154,29 @@ impl JsContextHost {
handle: DomHandle,
bootstrap: &ChildBrowsingContextBootstrap,
) -> Option<ChildBrowsingContextSnapshot> {
match bootstrap {
let mut snapshot = match bootstrap {
ChildBrowsingContextBootstrap::AboutBlank => {
let policy_container =
self.initial_child_about_blank_policy_container_from_parent(handle);
Some(
self.apply_page_csp_bypass_to_child_snapshot(
ChildBrowsingContextSnapshot::about_blank(
self.document_base_url_for_child_context(handle),
)
.with_policy_container(policy_container),
),
)
Some(ChildBrowsingContextSnapshot::about_blank(
self.document_base_url_for_child_context(handle),
))
}
ChildBrowsingContextBootstrap::Srcdoc { base_url, markup } => Some(
self.apply_page_csp_bypass_to_child_snapshot(ChildBrowsingContextSnapshot::srcdoc(
ChildBrowsingContextBootstrap::Srcdoc { base_url, markup } => {
Some(ChildBrowsingContextSnapshot::srcdoc(
base_url.clone(),
markup.clone(),
self.document_character_set().to_owned(),
)),
),
))
}
ChildBrowsingContextBootstrap::Url(url) => {
self.materialize_local_child_snapshot_for_navigation_url(handle, url)
return self.materialize_local_child_snapshot_for_navigation_url(handle, url);
}
ChildBrowsingContextBootstrap::Request(_) => None,
}?;
if bootstrap.content_security_policy_inherited() {
snapshot.policy_container =
self.initial_child_about_blank_policy_container_from_parent(handle);
}
Some(self.apply_page_csp_bypass_to_child_snapshot(snapshot))
}
pub(in crate::native_bridge::context_host) fn child_document_fallback_character_set(
@@ -230,10 +230,6 @@ impl ChildBrowsingContextEntry {
self.live_bootstrap.security_origin_inherited()
}
pub(super) fn content_security_policy_inherited(&self) -> bool {
self.live_bootstrap.content_security_policy_inherited()
}
pub(super) fn document_policy_container_snapshot(&self) -> ChildDocumentPolicyContainer {
self.document_policy_container.clone()
}
@@ -338,10 +334,6 @@ impl ChildBrowsingContextEntry {
.as_slice()
}
pub(super) fn has_response_content_security_policies(&self) -> bool {
!self.response_content_security_policies().is_empty()
}
pub(super) fn content_security_reporting_endpoints(
&self,
) -> crate::content_security_policy::ContentSecurityPolicyReportingEndpoints {
@@ -367,6 +359,10 @@ impl ChildBrowsingContextEntry {
self.document_policy_container
.document_content_security_policies =
policy_container.document_content_security_policies.clone();
self.document_policy_container
.inherited_meta_content_security_policies = policy_container
.inherited_meta_content_security_policies
.clone();
self.document_policy_container
.response_content_security_policies =
policy_container.response_content_security_policies.clone();
@@ -405,6 +401,11 @@ impl ChildBrowsingContextEntry {
snapshot.policy_container.document_isolation_policy;
self.document_policy_container.cross_origin_isolated =
snapshot.policy_container.cross_origin_isolated;
self.document_policy_container
.inherited_meta_content_security_policies = snapshot
.policy_container
.inherited_meta_content_security_policies
.clone();
self.document_policy_container
.response_content_security_policies = snapshot
.policy_container
@@ -363,6 +363,9 @@ impl JsContextHost {
content_security_reporting_endpoints: refresh_policy_source
.map(|policy| policy.content_security_reporting_endpoints.clone())
.unwrap_or_default(),
inherited_meta_content_security_policies: refresh_policy_source
.map(|policy| policy.inherited_meta_content_security_policies.clone())
.unwrap_or_default(),
permissions_policy: if is_new {
// The synchronous initial about:blank Document is
// already subject to the iframe's container policy.
@@ -508,6 +508,9 @@ impl LightweightPopupDocumentState {
self.policy_container.cross_origin_isolated = response.cross_origin_isolated;
self.policy_container.document_content_security_policies =
response.document_content_security_policies;
self.policy_container
.inherited_meta_content_security_policies =
response.inherited_meta_content_security_policies;
self.policy_container.response_content_security_policies =
response.response_content_security_policies;
self.policy_container
@@ -99,14 +99,8 @@ impl JsContextHost {
})
}
OwnerDispatchScope::Child(handle) => {
let mut policy_container =
let policy_container =
self.child_browsing_context_policy_container_snapshot(handle)?;
policy_container.response_content_security_policies =
self.child_effective_enforced_content_security_policies(handle);
policy_container.response_content_security_report_only_policies =
self.child_effective_response_content_security_report_only_policies(handle);
policy_container.content_security_reporting_endpoints =
self.child_effective_content_security_reporting_endpoints(handle);
Some(OwnerDocumentPolicySnapshot {
document_handle: self.child_browsing_context_document_handle(handle),
document_url: self.child_browsing_context_current_url(handle)?,
@@ -300,22 +294,24 @@ impl JsContextHost {
if policy_owner_dispatch_scope(scope) != OwnerDispatchScope::Top {
return DocumentCspOutcome::SkippedNonTopContext;
}
let (report_only_violation, enforced_violation) = self
let (report_only_violations, enforced_violations) = self
.document_subresource_csp_check(request_url, kind)
.into_violations();
let host_ptr: *mut JsContextHost = self;
if let Some(violation) = report_only_violation {
for violation in report_only_violations {
self.dispatch_content_security_policy_violation_event_best_effort(
scope, host_ptr, &violation,
);
}
let Some(violation) = enforced_violation else {
return DocumentCspOutcome::Allowed;
};
self.dispatch_content_security_policy_violation_event_best_effort(
scope, host_ptr, &violation,
);
DocumentCspOutcome::Blocked(violation)
for violation in &enforced_violations {
self.dispatch_content_security_policy_violation_event_best_effort(
scope, host_ptr, violation,
);
}
match enforced_violations.into_iter().next() {
Some(violation) => DocumentCspOutcome::Blocked(violation),
None => DocumentCspOutcome::Allowed,
}
}
pub(crate) fn check_element_subresource_csp<'s>(
@@ -334,7 +330,7 @@ impl JsContextHost {
// The element's Document owns this request even when a different
// Window's script changes its source or queues the update microtask.
// SAFETY: this host and its DocumentRuntime are owned by the same ScriptVm.
let (report_only_violation, enforced_violation) = unsafe { &*self.runtime }
let (report_only_violations, enforced_violations) = unsafe { &*self.runtime }
.document_subresource_csp_check_for_document(
snapshot.document_handle,
&snapshot.document_url,
@@ -344,18 +340,20 @@ impl JsContextHost {
)
.into_violations();
let host_ptr: *mut JsContextHost = self;
if let Some(violation) = report_only_violation {
for violation in report_only_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
}
let Some(violation) = enforced_violation else {
return DocumentCspOutcome::Allowed;
};
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
DocumentCspOutcome::Blocked(violation)
for violation in &enforced_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, violation,
);
}
match enforced_violations.into_iter().next() {
Some(violation) => DocumentCspOutcome::Blocked(violation),
None => DocumentCspOutcome::Allowed,
}
}
pub(crate) fn owner_dispatch_scope_for_node(
@@ -404,34 +402,33 @@ impl JsContextHost {
else {
return true;
};
let (mut report_only_violation, mut enforced_violation) = check.into_violations();
let (mut report_only_violations, mut enforced_violations) = check.into_violations();
if owner == OwnerDispatchScope::Top
&& let Some(position) = unsafe { &*self.runtime }.parser_script_start_position(script)
{
let line = i32::try_from(position.line).unwrap_or(i32::MAX);
let column = i32::try_from(position.column).unwrap_or(i32::MAX);
if let Some(violation) = report_only_violation.as_mut() {
for violation in &mut report_only_violations {
violation.line_number = line;
violation.column_number = column;
}
if let Some(violation) = enforced_violation.as_mut() {
for violation in &mut enforced_violations {
violation.line_number = line;
violation.column_number = column;
}
}
let host_ptr: *mut JsContextHost = self;
if let Some(violation) = report_only_violation {
for violation in report_only_violations {
self.dispatch_content_security_policy_violation_event_for_element_owner_best_effort(
scope, host_ptr, owner, script, &violation,
);
}
let Some(violation) = enforced_violation else {
return true;
};
self.dispatch_content_security_policy_violation_event_for_element_owner_best_effort(
scope, host_ptr, owner, script, &violation,
);
false
for violation in &enforced_violations {
self.dispatch_content_security_policy_violation_event_for_element_owner_best_effort(
scope, host_ptr, owner, script, violation,
);
}
enforced_violations.is_empty()
}
pub(crate) fn allows_inline_javascript_navigation_by_csp<'s>(
@@ -463,20 +460,19 @@ impl JsContextHost {
// owner. Committed documents must always produce a check.
return true;
};
let (report_only_violation, enforced_violation) = check.into_violations();
let (report_only_violations, enforced_violations) = check.into_violations();
let host_ptr: *mut JsContextHost = self;
if let Some(violation) = report_only_violation {
for violation in report_only_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
}
let Some(violation) = enforced_violation else {
return true;
};
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
false
for violation in &enforced_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, violation,
);
}
enforced_violations.is_empty()
}
fn inline_source_csp_check_for_owner(
@@ -574,20 +570,22 @@ impl JsContextHost {
) else {
return DocumentCspOutcome::Allowed;
};
let (report_only_violation, enforced_violation) = check.into_violations();
let (report_only_violations, enforced_violations) = check.into_violations();
let host_ptr: *mut JsContextHost = self;
if let Some(violation) = report_only_violation {
for violation in report_only_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
}
let Some(violation) = enforced_violation else {
return DocumentCspOutcome::Allowed;
};
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
DocumentCspOutcome::Blocked(violation)
for violation in &enforced_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, violation,
);
}
match enforced_violations.into_iter().next() {
Some(violation) => DocumentCspOutcome::Blocked(violation),
None => DocumentCspOutcome::Allowed,
}
}
fn document_connect_csp_check_for_owner_with_redirect_status(
@@ -637,7 +635,7 @@ impl JsContextHost {
request_url,
ContentSecurityPolicyRedirectStatus::NoRedirect,
)
.map(|check| check.into_violations().1.is_none())
.map(|check| check.into_violations().1.is_empty())
.unwrap_or(true)
}
@@ -726,133 +724,16 @@ impl JsContextHost {
)
}
fn child_effective_enforced_content_security_policies(
&self,
child_handle: DomHandle,
) -> Vec<String> {
let mut policies = Vec::new();
if self
.child_browsing_contexts
.get(&child_handle)
.is_some_and(|entry| entry.content_security_policy_inherited())
{
policies.extend(self.parent_effective_enforced_content_security_policies(child_handle));
}
policies.extend(
self.child_browsing_contexts
.get(&child_handle)
.map(|entry| entry.response_content_security_policies().to_vec())
.unwrap_or_default(),
);
policies
}
fn child_effective_response_content_security_report_only_policies(
&self,
child_handle: DomHandle,
) -> Vec<String> {
let mut policies = Vec::new();
if self
.child_browsing_contexts
.get(&child_handle)
.is_some_and(|entry| entry.content_security_policy_inherited())
{
policies.extend(
self.parent_effective_response_content_security_report_only_policies(child_handle),
);
}
policies.extend(
self.child_browsing_contexts
.get(&child_handle)
.map(|entry| {
entry
.response_content_security_report_only_policies()
.to_vec()
})
.unwrap_or_default(),
);
policies
}
fn parent_effective_enforced_content_security_policies(
&self,
child_handle: DomHandle,
) -> Vec<String> {
match self.child_browsing_context_parent_handle(child_handle) {
Some(parent) => {
let mut policies = self.child_effective_enforced_content_security_policies(parent);
if let Some(document) = self.child_browsing_context_document_handle(parent) {
// SAFETY: JsContextHost is owned by the ScriptVm that owns this
// DocumentRuntime.
policies.extend(
unsafe { &*self.runtime }
.meta_content_security_policy_strings_for_document(document),
);
}
policies
}
None => {
// SAFETY: JsContextHost is owned by the ScriptVm that owns this
// DocumentRuntime.
let runtime = unsafe { &*self.runtime };
let mut policies = runtime.response_content_security_policies().to_vec();
policies.extend(
runtime.meta_content_security_policy_strings_for_document(
runtime.document_handle(),
),
);
policies
}
}
}
fn parent_effective_response_content_security_report_only_policies(
&self,
child_handle: DomHandle,
) -> Vec<String> {
match self.child_browsing_context_parent_handle(child_handle) {
Some(parent) => {
self.child_effective_response_content_security_report_only_policies(parent)
}
None => unsafe { &*self.runtime }
.response_content_security_report_only_policies()
.to_vec(),
}
}
fn child_effective_content_security_reporting_endpoints(
&self,
child_handle: DomHandle,
) -> ContentSecurityPolicyReportingEndpoints {
let inherits_parent = self
.child_browsing_contexts
.get(&child_handle)
.is_some_and(|entry| entry.content_security_policy_inherited());
let has_own_response_policies = self
.child_browsing_contexts
.get(&child_handle)
.is_some_and(|entry| entry.has_response_content_security_policies());
if inherits_parent && !has_own_response_policies {
return self.parent_effective_content_security_reporting_endpoints(child_handle);
}
self.child_browsing_contexts
.get(&child_handle)
.map(|entry| entry.content_security_reporting_endpoints())
.unwrap_or_default()
}
fn parent_effective_content_security_reporting_endpoints(
&self,
child_handle: DomHandle,
) -> ContentSecurityPolicyReportingEndpoints {
match self.child_browsing_context_parent_handle(child_handle) {
Some(parent) => self.child_effective_content_security_reporting_endpoints(parent),
None => unsafe { &*self.runtime }
.content_security_reporting_endpoints()
.clone(),
}
}
pub(crate) fn allows_eval_code_generation_by_csp<'s>(
&mut self,
scope: &mut v8::PinScope<'s, '_>,
@@ -921,15 +802,15 @@ impl JsContextHost {
check: DocumentContentSecurityPolicyCheck,
include_call_location: bool,
) -> bool {
let (mut report_only_violation, mut enforced_violation) = check.into_violations();
if report_only_violation.is_none() && enforced_violation.is_none() {
let (mut report_only_violations, mut enforced_violations) = check.into_violations();
if report_only_violations.is_empty() && enforced_violations.is_empty() {
return true;
}
if include_call_location
&& let Some((source_file, line_number, column_number)) =
current_script_violation_location(scope)
{
for violation in [&mut report_only_violation, &mut enforced_violation]
for violation in [&mut report_only_violations, &mut enforced_violations]
.into_iter()
.flatten()
{
@@ -939,18 +820,17 @@ impl JsContextHost {
}
}
let host_ptr: *mut JsContextHost = self;
if let Some(violation) = report_only_violation {
for violation in report_only_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
}
let Some(violation) = enforced_violation else {
return true;
};
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, &violation,
);
false
for violation in &enforced_violations {
self.dispatch_content_security_policy_violation_event_for_owner_best_effort(
scope, host_ptr, owner, violation,
);
}
enforced_violations.is_empty()
}
pub(crate) fn child_wasm_eval_csp_violation(
@@ -964,6 +844,8 @@ impl JsContextHost {
)?
.into_violations()
.1
.into_iter()
.next()
}
pub(crate) fn allows_trusted_type_policy_name_by_csp<'s>(
+4 -4
View File
@@ -4530,7 +4530,7 @@ impl PageVm {
);
}
let (_, enforced_violation) = self
let (_, enforced_violations) = self
.vm()
.document_runtime
.style_element_request_csp_check(
@@ -4540,7 +4540,7 @@ impl PageVm {
},
)
.into_violations();
if enforced_violation.is_some() {
if !enforced_violations.is_empty() {
return ScannedStylesheetAdmission::DeferredToParser(
ScannedStylesheetDeferral::ContentSecurityPolicy,
);
@@ -4577,7 +4577,7 @@ impl PageVm {
ScannedImageDeferral::FetchInterception,
);
}
let (_, enforced_violation) = self
let (_, enforced_violations) = self
.vm()
.document_runtime
.document_subresource_csp_check(
@@ -4585,7 +4585,7 @@ impl PageVm {
crate::document_runtime::DocumentSubresourceCspKind::Image,
)
.into_violations();
if enforced_violation.is_some() {
if !enforced_violations.is_empty() {
return ScannedImageAdmission::DeferredToParser(
ScannedImageDeferral::ContentSecurityPolicy,
);
@@ -45,11 +45,11 @@ impl ScriptVm {
return complete_default_app_manifest(&document_url, Some(&manifest_url));
}
let (_report_only_violation, enforced_violation) = self
let (_report_only_violation, enforced_violations) = self
.document_runtime
.document_subresource_csp_check(&manifest_url, DocumentSubresourceCspKind::Manifest)
.into_violations();
if enforced_violation.is_some() {
if !enforced_violations.is_empty() {
return complete_default_app_manifest(&document_url, Some(&manifest_url));
}
@@ -36,6 +36,72 @@ fn inline_script_and_handler_csp_accept_base64url_hashes() {
}
}
#[test]
fn inline_script_reports_every_policy_and_only_enforced_policies_block() {
let policies = [
"script-src 'nonce-allowed' 'report-sample'".to_owned(),
"script-src 'nonce-allowed'".to_owned(),
];
for enforce in [false, true] {
let mut vm = new_storage_test_vm("https://multiple-inline-csp.test/page.html");
if enforce {
vm.set_response_content_security_policies(&policies);
}
vm.set_response_content_security_report_only_policies(&policies);
vm.eval(
r#"
globalThis.multipleCspEvents = [];
document.addEventListener('securitypolicyviolation', event => {
multipleCspEvents.push({
policy: event.originalPolicy,
disposition: event.disposition,
directive: event.effectiveDirective,
blockedURI: event.blockedURI,
sample: event.sample,
target: event.target.id,
});
});
const root = document.documentElement || document.appendChild(document.createElement('html'));
const blocked = document.createElement('script');
blocked.id = 'reported';
blocked.text = 'globalThis.untrustedRan = true';
root.appendChild(blocked);
const allowed = document.createElement('script');
allowed.nonce = 'allowed';
allowed.text = 'globalThis.trustedRan = true';
root.appendChild(allowed);
"#,
)
.expect("inline script probe");
drain_pre_domcontentloaded_non_script_page_tasks_for_test(&mut vm);
assert_eq!(
vm.eval("globalThis.untrustedRan === true").unwrap(),
(!enforce).to_string(),
);
assert_eq!(vm.eval("globalThis.trustedRan === true").unwrap(), "true");
let events: serde_json::Value =
serde_json::from_str(&vm.eval("JSON.stringify(multipleCspEvents)").unwrap()).unwrap();
let mut expected = Vec::new();
for disposition in if enforce {
vec!["report", "enforce"]
} else {
vec!["report"]
} {
for (index, policy) in policies.iter().enumerate() {
expected.push(serde_json::json!({
"policy": policy,
"disposition": disposition,
"directive": "script-src-elem",
"blockedURI": "inline",
"sample": if index == 0 { "globalThis.untrustedRan = true" } else { "" },
"target": "reported",
}));
}
}
assert_eq!(events, serde_json::json!(expected));
}
}
#[test]
fn module_fetch_csp_uses_captured_parser_metadata_and_nonce() {
let mut vm = new_storage_test_vm("https://module-csp-provenance.test/page.html");
@@ -43,7 +43,7 @@ impl DocumentRuntime {
request_resource_type: moli_fetch::RequestResourceType,
link_preload: bool,
) -> Result<StylesheetFetch, OwnerlessStylesheetAdmissionError> {
let (_, enforced_violation) = self
let (_, enforced_violations) = self
.response_style_element_request_csp_check(
&request_url,
crate::content_security_policy::ContentSecurityPolicyStyleElementRequest {
@@ -51,7 +51,7 @@ impl DocumentRuntime {
},
)
.into_violations();
if enforced_violation.is_some() {
if !enforced_violations.is_empty() {
// The eventual DOM client owns violation reporting and its
// load/error event. Speculation only decides whether a physical
// resource may start.