Correct event_target for CSP violations (#36887)

All logic is implemented in `report_csp_violations` to avoid
pulling in various element-logic into SecurityManager.

Update the `icon-blocked.sub.html` WPT test to ensure that
the document is the correct target (verified in Firefox and Chrome).

Fixes #36806

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
This commit is contained in:
Tim van der Lippe 2025-05-08 12:46:31 +02:00 committed by GitHub
parent f3f4cc5500
commit b6b80d4f6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 167 additions and 193 deletions

View file

@ -4321,7 +4321,7 @@ impl Document {
},
};
self.global().report_csp_violations(violations);
self.global().report_csp_violations(violations, Some(el));
result
}

View file

@ -1040,14 +1040,39 @@ impl From<bool> for EventCancelable {
}
impl From<EventCancelable> for bool {
fn from(bubbles: EventCancelable) -> Self {
match bubbles {
fn from(cancelable: EventCancelable) -> Self {
match cancelable {
EventCancelable::Cancelable => true,
EventCancelable::NotCancelable => false,
}
}
}
#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
pub(crate) enum EventComposed {
Composed,
NotComposed,
}
impl From<bool> for EventComposed {
fn from(boolean: bool) -> Self {
if boolean {
EventComposed::Composed
} else {
EventComposed::NotComposed
}
}
}
impl From<EventComposed> for bool {
fn from(composed: EventComposed) -> Self {
match composed {
EventComposed::Composed => true,
EventComposed::NotComposed => false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)]
#[repr(u16)]
#[derive(MallocSizeOf)]

View file

@ -448,7 +448,7 @@ impl FetchResponseListener for EventSourceContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -106,6 +106,7 @@ use crate::dom::crypto::Crypto;
use crate::dom::dedicatedworkerglobalscope::{
DedicatedWorkerControlMsg, DedicatedWorkerGlobalScope,
};
use crate::dom::element::Element;
use crate::dom::errorevent::ErrorEvent;
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
use crate::dom::eventsource::EventSource;
@ -115,6 +116,7 @@ use crate::dom::htmlscriptelement::{ScriptId, SourceCode};
use crate::dom::imagebitmap::ImageBitmap;
use crate::dom::messageevent::MessageEvent;
use crate::dom::messageport::MessagePort;
use crate::dom::node::Node;
use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
use crate::dom::performance::Performance;
use crate::dom::performanceobserver::VALID_ENTRY_TYPES;
@ -2930,7 +2932,7 @@ impl GlobalScope {
let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source);
self.report_csp_violations(violations);
self.report_csp_violations(violations, None);
is_js_evaluation_allowed == CheckResult::Allowed
}
@ -2957,7 +2959,7 @@ impl GlobalScope {
let (result, violations) =
csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other);
self.report_csp_violations(violations);
self.report_csp_violations(violations, None);
result == CheckResult::Blocked
}
@ -3444,8 +3446,13 @@ impl GlobalScope {
unreachable!();
}
/// <https://www.w3.org/TR/CSP/#report-violation>
#[allow(unsafe_code)]
pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
pub(crate) fn report_csp_violations(
&self,
violations: Vec<Violation>,
element: Option<&Element>,
) {
let scripted_caller =
unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default();
for violation in violations {
@ -3471,7 +3478,38 @@ impl GlobalScope {
.line_number(scripted_caller.line)
.column_number(scripted_caller.col + 1)
.build(self);
let task = CSPViolationReportTask::new(self, report);
// Step 1: Let global be violations global object.
// We use `self` as `global`;
// Step 2: Let target be violations element.
let target = element.and_then(|event_target| {
// Step 3.1: If target is not null, and global is a Window,
// and targets shadow-including root is not globals associated Document, set target to null.
if let Some(window) = self.downcast::<Window>() {
if !window
.Document()
.upcast::<Node>()
.is_shadow_including_inclusive_ancestor_of(event_target.upcast())
{
return None;
}
}
Some(event_target)
});
let target = match target {
// Step 3.2: If target is null:
None => {
// Step 3.2.2: If target is a Window, set target to targets associated Document.
if let Some(window) = self.downcast::<Window>() {
Trusted::new(window.Document().upcast())
} else {
// Step 3.2.1: Set target to violations global object.
Trusted::new(self.upcast())
}
},
Some(event_target) => Trusted::new(event_target.upcast()),
};
// Step 3: Queue a task to run the following steps:
let task = CSPViolationReportTask::new(Trusted::new(self), target, report);
self.task_manager()
.dom_manipulation_task_source()
.queue(task);

View file

@ -298,7 +298,7 @@ impl FetchResponseListener for ImageContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -773,7 +773,7 @@ impl FetchResponseListener for PrefetchContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -2951,7 +2951,7 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -546,7 +546,8 @@ impl FetchResponseListener for ClassicContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
let elem = self.elem.root();
global.report_csp_violations(violations, Some(elem.upcast::<Element>()));
}
}

View file

@ -422,7 +422,7 @@ impl FetchResponseListener for PosterFrameFetchContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -795,7 +795,7 @@ impl FetchResponseListener for ResourceFetchListener {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -15,7 +15,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
@ -70,12 +70,14 @@ impl SecurityPolicyViolationEvent {
)
}
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
composed: EventComposed,
init: &SecurityPolicyViolationEventInit,
can_gc: CanGc,
) -> DomRoot<Self> {
@ -83,6 +85,7 @@ impl SecurityPolicyViolationEvent {
{
let event = ev.upcast::<Event>();
event.init_event(type_, bool::from(bubbles), bool::from(cancelable));
event.set_composed(bool::from(composed));
}
ev
}
@ -92,10 +95,13 @@ impl SecurityPolicyViolationEvent {
type_: Atom,
bubbles: EventBubbles,
cancelable: EventCancelable,
composed: EventComposed,
init: &SecurityPolicyViolationEventInit,
can_gc: CanGc,
) -> DomRoot<Self> {
Self::new_with_proto(global, None, type_, bubbles, cancelable, init, can_gc)
Self::new_with_proto(
global, None, type_, bubbles, cancelable, composed, init, can_gc,
)
}
}
@ -115,6 +121,7 @@ impl SecurityPolicyViolationEventMethods<crate::DomTypeHolder> for SecurityPolic
Atom::from(type_),
EventBubbles::from(init.parent.bubbles),
EventCancelable::from(init.parent.cancelable),
EventComposed::from(init.parent.composed),
init,
can_gc,
)

View file

@ -1075,7 +1075,8 @@ impl FetchResponseListener for ParserContext {
};
let document = &parser.document;
let global = &document.global();
global.report_csp_violations(violations);
// TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved
global.report_csp_violations(violations, None);
}
}

View file

@ -66,7 +66,7 @@ impl TrustedTypePolicyFactory {
(CheckResult::Allowed, Vec::new())
};
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
// Step 2: If allowedByCSP is "Blocked", throw a TypeError and abort further steps.
if allowed_by_csp == CheckResult::Blocked {
@ -230,7 +230,7 @@ impl TrustedTypePolicyFactory {
.should_sink_type_mismatch_violation_be_blocked_by_csp(
sink, sink_group, &input,
);
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
// Step 6.2: If disposition is “Allowed”, return stringified input and abort further steps.
if disposition == CheckResult::Allowed {
Ok(input)

View file

@ -471,7 +471,7 @@ struct ReportCSPViolationTask {
impl TaskOnce for ReportCSPViolationTask {
fn run_once(self) {
let global = self.websocket.root().global();
global.report_csp_violations(self.violations);
global.report_csp_violations(self.violations, None);
}
}

View file

@ -148,7 +148,7 @@ impl FetchResponseListener for XHRContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -313,7 +313,7 @@ impl FetchResponseListener for FetchContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -81,7 +81,7 @@ impl FetchResponseListener for LayoutImageContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -1277,7 +1277,7 @@ impl FetchResponseListener for ModuleContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}

View file

@ -390,7 +390,7 @@ unsafe extern "C" fn content_security_policy_allows(
RuntimeCode::WASM => csp_list.is_wasm_evaluation_allowed(),
};
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
allowed = is_evaluation_allowed == CheckResult::Allowed;
});
allowed

View file

@ -3606,7 +3606,8 @@ impl ScriptThread {
fn handle_csp_violations(&self, id: PipelineId, _: RequestId, violations: Vec<csp::Violation>) {
if let Some(global) = self.documents.borrow().find_global(id) {
global.report_csp_violations(violations);
// TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved
global.report_csp_violations(violations, None);
}
}

View file

@ -14,8 +14,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed};
use crate::dom::eventtarget::EventTarget;
use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent;
use crate::dom::types::GlobalScope;
@ -23,6 +22,7 @@ use crate::script_runtime::CanGc;
use crate::task::TaskOnce;
pub(crate) struct CSPViolationReportTask {
global: Trusted<GlobalScope>,
event_target: Trusted<EventTarget>,
violation_report: SecurityPolicyViolationReport,
}
@ -159,28 +159,31 @@ impl CSPViolationReportBuilder {
impl CSPViolationReportTask {
pub fn new(
global: &GlobalScope,
report: SecurityPolicyViolationReport,
global: Trusted<GlobalScope>,
event_target: Trusted<EventTarget>,
violation_report: SecurityPolicyViolationReport,
) -> CSPViolationReportTask {
CSPViolationReportTask {
violation_report: report,
event_target: Trusted::new(global.upcast::<EventTarget>()),
global,
event_target,
violation_report,
}
}
fn fire_violation_event(self, can_gc: CanGc) {
let target = self.event_target.root();
let global = &target.global();
let event = SecurityPolicyViolationEvent::new(
global,
&self.global.root(),
Atom::from("securitypolicyviolation"),
EventBubbles::Bubbles,
EventCancelable::Cancelable,
EventComposed::Composed,
&self.violation_report.convert(),
can_gc,
);
event.upcast::<Event>().fire(&target, can_gc);
event
.upcast::<Event>()
.fire(&self.event_target.root(), can_gc);
}
}

View file

@ -286,7 +286,7 @@ impl FetchResponseListener for StylesheetContext {
fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) {
let global = &self.resource_timing_global();
global.report_csp_violations(violations);
global.report_csp_violations(violations, None);
}
}