diff --git a/components/script/dom/csppolicyviolationreport.rs b/components/script/dom/csppolicyviolationreport.rs index 1ab99c99bb7..ef4f3fb7e43 100644 --- a/components/script/dom/csppolicyviolationreport.rs +++ b/components/script/dom/csppolicyviolationreport.rs @@ -7,11 +7,14 @@ use serde::Serialize; use servo_url::ServoUrl; use crate::conversions::Convert; +use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody; use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; +use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::ReportBody; use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding::{ SecurityPolicyViolationEventDisposition, SecurityPolicyViolationEventInit, }; use crate::dom::globalscope::GlobalScope; +use crate::dom::reportingobserver::ReportingObserver; #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -77,6 +80,28 @@ impl Convert for SecurityPolicyViolationReport } } +impl Convert for SecurityPolicyViolationReport { + fn convert(self) -> CSPViolationReportBody { + CSPViolationReportBody { + sample: self.sample.map(|s| s.into()), + blockedURL: Some(self.blocked_url.into()), + // TODO(37328): Why does /content-security-policy/reporting-api/ + // report-to-directive-allowed-in-meta.https.sub.html expect this to be + // empty, yet the spec expects us to copy referrer from SecurityPolicyViolationReport + referrer: Some("".to_owned().into()), + statusCode: self.status_code, + documentURL: self.document_url.into(), + sourceFile: Some(self.source_file.into()), + effectiveDirective: self.effective_directive.into(), + lineNumber: Some(self.line_number), + columnNumber: Some(self.column_number), + originalPolicy: self.original_policy.into(), + disposition: self.disposition, + parent: ReportBody::empty(), + } + } +} + /// impl From for CSPReportUriViolationReportBody { fn from(value: SecurityPolicyViolationReport) -> Self { @@ -101,7 +126,7 @@ impl From for CSPReportUriViolationReportBody { // Step 2.1. Set body["source-file'] to the result of // executing § 5.4 Strip URL for use in reports on violation’s source file. converted.source_file = ServoUrl::parse(&value.source_file) - .map(strip_url_for_reports) + .map(ReportingObserver::strip_url_for_reports) .ok(); // Step 2.2. Set body["line-number"] to violation’s line number. converted.line_number = Some(value.line_number); @@ -187,15 +212,15 @@ impl CSPViolationReportBuilder { SecurityPolicyViolationReport { violated_directive: self.effective_directive.clone(), effective_directive: self.effective_directive.clone(), - document_url: strip_url_for_reports(global.get_url()), + document_url: ReportingObserver::strip_url_for_reports(global.get_url()), disposition: match self.report_only { true => SecurityPolicyViolationEventDisposition::Report, false => SecurityPolicyViolationEventDisposition::Enforce, }, // https://w3c.github.io/webappsec-csp/#violation-referrer referrer: match global.get_referrer() { - Referrer::Client(url) => strip_url_for_reports(url), - Referrer::ReferrerUrl(url) => strip_url_for_reports(url), + Referrer::Client(url) => ReportingObserver::strip_url_for_reports(url), + Referrer::ReferrerUrl(url) => ReportingObserver::strip_url_for_reports(url), _ => "".to_owned(), }, sample: self.sample, @@ -218,20 +243,3 @@ fn serialize_disposition( SecurityPolicyViolationEventDisposition::Enforce => serializer.serialize_str("enforce"), } } - -/// -fn strip_url_for_reports(mut url: ServoUrl) -> String { - let scheme = url.scheme(); - // > Step 1: If url’s scheme is not an HTTP(S) scheme, then return url’s scheme. - if scheme != "https" && scheme != "http" { - return scheme.to_owned(); - } - // > Step 2: Set url’s fragment to the empty string. - url.set_fragment(None); - // > Step 3: Set url’s username to the empty string. - let _ = url.set_username(""); - // > Step 4: Set url’s password to the empty string. - let _ = url.set_password(None); - // > Step 5: Return the result of executing the URL serializer on url. - url.into_string() -} diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 0fe101a0d99..67dc70bf389 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -76,6 +76,7 @@ use crate::dom::bindings::codegen::Bindings::NotificationBinding::NotificationPe use crate::dom::bindings::codegen::Bindings::PermissionStatusBinding::{ PermissionName, PermissionState, }; +use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::Report; use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods; @@ -109,6 +110,7 @@ use crate::dom::performance::Performance; use crate::dom::performanceobserver::VALID_ENTRY_TYPES; use crate::dom::promise::Promise; use crate::dom::readablestream::{CrossRealmTransformReadable, ReadableStream}; +use crate::dom::reportingobserver::ReportingObserver; use crate::dom::serviceworker::ServiceWorker; use crate::dom::serviceworkerregistration::ServiceWorkerRegistration; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; @@ -3332,6 +3334,56 @@ impl GlobalScope { unreachable!(); } + pub(crate) fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) { + if let Some(window) = self.downcast::() { + return window.append_reporting_observer(DomRoot::from_ref(reporting_observer)); + } + if let Some(worker) = self.downcast::() { + return worker.append_reporting_observer(DomRoot::from_ref(reporting_observer)); + } + unreachable!(); + } + + pub(crate) fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) { + if let Some(window) = self.downcast::() { + return window.remove_reporting_observer(reporting_observer); + } + if let Some(worker) = self.downcast::() { + return worker.remove_reporting_observer(reporting_observer); + } + unreachable!(); + } + + pub(crate) fn registered_reporting_observers(&self) -> Vec> { + if let Some(window) = self.downcast::() { + return window.registered_reporting_observers(); + } + if let Some(worker) = self.downcast::() { + return worker.registered_reporting_observers(); + } + unreachable!(); + } + + pub(crate) fn append_report(&self, report: Report) { + if let Some(window) = self.downcast::() { + return window.append_report(report); + } + if let Some(worker) = self.downcast::() { + return worker.append_report(report); + } + unreachable!(); + } + + pub(crate) fn buffered_reports(&self) -> Vec { + if let Some(window) = self.downcast::() { + return window.buffered_reports(); + } + if let Some(worker) = self.downcast::() { + return worker.buffered_reports(); + } + unreachable!(); + } + pub(crate) fn import_map(&self) -> Ref<'_, ImportMap> { self.import_map.borrow() } diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 7fbb823c96a..cea5be27726 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -517,6 +517,7 @@ pub(crate) mod readablestreambyobrequest; pub(crate) mod readablestreamdefaultcontroller; pub(crate) mod readablestreamdefaultreader; pub(crate) mod readablestreamgenericreader; +pub(crate) mod reportingobserver; pub(crate) mod request; pub(crate) mod resizeobserver; pub(crate) mod resizeobserverentry; diff --git a/components/script/dom/reportingobserver.rs b/components/script/dom/reportingobserver.rs new file mode 100644 index 00000000000..c931f82ffd4 --- /dev/null +++ b/components/script/dom/reportingobserver.rs @@ -0,0 +1,278 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::cell::RefCell; +use std::rc::Rc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use dom_struct::dom_struct; +use js::rust::HandleObject; +use script_bindings::str::DOMString; +use servo_url::ServoUrl; + +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody; +use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::{ + Report, ReportList, ReportingObserverCallback, ReportingObserverMethods, + ReportingObserverOptions, +}; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::refcounted::Trusted; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub(crate) struct ReportingObserver { + reflector_: Reflector, + + #[ignore_malloc_size_of = "Rc has unclear ownership"] + callback: Rc, + buffered: RefCell, + types: DomRefCell>, + report_queue: DomRefCell>, +} + +impl ReportingObserver { + fn new_inherited( + callback: Rc, + options: &ReportingObserverOptions, + ) -> Self { + Self { + reflector_: Reflector::new(), + callback, + buffered: RefCell::new(options.buffered), + types: DomRefCell::new(options.types.clone().unwrap_or_default()), + report_queue: Default::default(), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new_with_proto( + callback: Rc, + options: &ReportingObserverOptions, + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + ) -> DomRoot { + reflect_dom_object_with_proto( + Box::new(Self::new_inherited(callback, options)), + global, + proto, + can_gc, + ) + } + + fn report_is_visible_to_reporting_observers(report: &Report) -> bool { + match report.type_.str() { + // https://w3c.github.io/webappsec-csp/#reporting + "csp-violation" => true, + _ => false, + } + } + + /// + fn add_report_to_observer(&self, report: &Report) { + // Step 1. If report’s type is not visible to ReportingObservers, return. + if !Self::report_is_visible_to_reporting_observers(report) { + return; + } + // Step 2. If observer’s options has a non-empty types member which does not contain report’s type, return. + let types = self.types.borrow(); + if !types.is_empty() && !types.contains(&report.type_) { + return; + } + // Step 3. Create a new Report r with type initialized to report’s type, + // url initialized to report’s url, and body initialized to report’s body. + let report = Report { + type_: report.type_.clone(), + url: report.url.clone(), + body: report.body.clone(), + destination: report.destination.clone(), + attempts: report.attempts, + timestamp: report.timestamp, + }; + // Step 4. Append r to observer’s report queue. + self.report_queue.borrow_mut().push(report); + // Step 5. If the size of observer’s report queue is 1: + if self.report_queue.borrow().len() == 1 { + // Step 5.1. Let global be observer’s relevant global object. + let global = self.global(); + // Step 5.2. Queue a task to § 4.4 Invoke reporting observers with notify list + // with a copy of global’s registered reporting observer list. + let observers_global = Trusted::new(&*global); + global.task_manager().dom_manipulation_task_source().queue( + task!(notify_reporting_observers: move || { + Self::invoke_reporting_observers_with_notify_list( + observers_global.root().registered_reporting_observers() + ); + }), + ); + } + } + + /// + pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) { + // Step 1. For each ReportingObserver observer registered with scope, + // execute § 4.3 Add report to observer on report and observer. + for observer in global.registered_reporting_observers().iter() { + observer.add_report_to_observer(report); + } + // Step 2. Append report to scope’s report buffer. + global.append_report(report.clone()); + // Step 3. Let type be report’s type. + // TODO(37328) + // Step 4. If scope’s report buffer now contains more than 100 reports with + // type equal to type, remove the earliest item with type equal to type in the report buffer. + // TODO(37328) + } + + /// + fn invoke_reporting_observers_with_notify_list(notify_list: Vec>) { + // Step 1. For each ReportingObserver observer in notify list: + for observer in notify_list.iter() { + // Step 1.1. If observer’s report queue is empty, then continue. + if observer.report_queue.borrow().is_empty() { + continue; + } + // Step 1.2. Let reports be a copy of observer’s report queue + // Step 1.3. Empty observer’s report queue + let reports = std::mem::take(&mut *observer.report_queue.borrow_mut()); + // Step 1.4. Invoke observer’s callback with « reports, observer » and "report", + // and with observer as the callback this value. + let _ = observer.callback.Call_( + &**observer, + reports, + observer, + ExceptionHandling::Report, + CanGc::note(), + ); + } + } + + /// + fn generate_a_report( + global: &GlobalScope, + type_: DOMString, + url: Option, + body: Option, + destination: DOMString, + ) -> Report { + // Step 2. If url was not provided by the caller, let url be settings’s creation URL. + let url = url.unwrap_or(global.creation_url().clone()); + // Step 3. Set url’s username to the empty string, and its password to null. + // Step 4. Set report’s url to the result of executing the URL serializer + // on url with the exclude fragment flag set. + let url = Self::strip_url_for_reports(url).into(); + // Step 1. Let report be a new report object with its values initialized as follows: + // Step 5. Return report. + Report { + type_, + url, + body, + destination, + timestamp: Finite::wrap( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as f64, + ), + attempts: 0, + } + } + + /// + pub(crate) fn generate_and_queue_a_report( + global: &GlobalScope, + type_: DOMString, + body: Option, + destination: DOMString, + ) { + // Step 1. Let settings be context’s relevant settings object. + // Step 2. Let report be the result of running generate a report with data, type, destination and settings. + let report = Self::generate_a_report(global, type_, None, body, destination); + // Step 3. If settings is given, then + // Step 3.1. Let scope be settings’s global object. + // Step 3.2. If scope is an object implementing WindowOrWorkerGlobalScope, then + // execute § 4.2 Notify reporting observers on scope with report with scope and report. + Self::notify_reporting_observers_on_scope(global, &report); + // Step 4. Append report to context’s reports. + global.append_report(report); + } + + /// + pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String { + let scheme = url.scheme(); + // Step 1: If url’s scheme is not an HTTP(S) scheme, then return url’s scheme. + if scheme != "https" && scheme != "http" { + return scheme.to_owned(); + } + // Step 2: Set url’s fragment to the empty string. + url.set_fragment(None); + // Step 3: Set url’s username to the empty string. + let _ = url.set_username(""); + // Step 4: Set url’s password to the empty string. + let _ = url.set_password(None); + // Step 5: Return the result of executing the URL serializer on url. + url.into_string() + } +} + +impl ReportingObserverMethods for ReportingObserver { + /// + fn Constructor( + global: &GlobalScope, + proto: Option, + can_gc: CanGc, + callback: Rc, + options: &ReportingObserverOptions, + ) -> DomRoot { + // Step 1. Create a new ReportingObserver object observer. + // Step 2. Set observer’s callback to callback. + // Step 3. Set observer’s options to options. + // Step 4. Return observer. + ReportingObserver::new_with_proto(callback, options, global, proto, can_gc) + } + + /// + fn Observe(&self) { + // Step 1. Let global be the be the relevant global object of this. + let global = &self.global(); + // Step 2. Append this to the global’s registered reporting observer list. + global.append_reporting_observer(self); + // Step 3. If this’s buffered option is false, return. + if !*self.buffered.borrow() { + return; + } + // Step 4. Set this’s buffered option to false. + *self.buffered.borrow_mut() = false; + // Step 5.For each report in global’s report buffer, queue a task to + // execute § 4.3 Add report to observer with report and this. + for report in global.buffered_reports() { + // TODO(37328): Figure out how to put this in a task + self.add_report_to_observer(&report); + } + } + + /// + fn Disconnect(&self) { + // Step 1. If this is not registered, return. + // Skipped, as this is handled in `remove_reporting_observer` + + // Step 2. Let global be the relevant global object of this. + let global = &self.global(); + // Step 3. Remove this from global’s registered reporting observer list. + global.remove_reporting_observer(self); + } + + /// + fn TakeRecords(&self) -> ReportList { + // Step 1. Let reports be a copy of this’s report queue. + // Step 2. Empty this’s report queue. + // Step 3. Return reports. + std::mem::take(&mut *self.report_queue.borrow_mut()) + } +} diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 86b3ee8d498..6be95e1a2dc 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -102,6 +102,7 @@ use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ ImageBitmapOptions, ImageBitmapSource, }; use crate::dom::bindings::codegen::Bindings::MediaQueryListBinding::MediaQueryList_Binding::MediaQueryListMethods; +use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::Report; use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ @@ -146,6 +147,7 @@ use crate::dom::navigator::Navigator; use crate::dom::node::{Node, NodeDamage, NodeTraits, from_untrusted_node_address}; use crate::dom::performance::Performance; use crate::dom::promise::Promise; +use crate::dom::reportingobserver::ReportingObserver; use crate::dom::screen::Screen; use crate::dom::selection::Selection; use crate::dom::shadowroot::ShadowRoot; @@ -398,6 +400,12 @@ pub(crate) struct Window { /// current_event: DomRefCell>>, + + /// + reporting_observer_list: DomRefCell>>, + + /// + report_list: DomRefCell>, } impl Window { @@ -502,6 +510,35 @@ impl Window { self.window_proxy.get().unwrap() } + pub(crate) fn append_reporting_observer(&self, reporting_observer: DomRoot) { + self.reporting_observer_list + .borrow_mut() + .push(reporting_observer); + } + + pub(crate) fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) { + if let Some(index) = self + .reporting_observer_list + .borrow() + .iter() + .position(|observer| &**observer == reporting_observer) + { + self.reporting_observer_list.borrow_mut().remove(index); + } + } + + pub(crate) fn registered_reporting_observers(&self) -> Vec> { + self.reporting_observer_list.borrow().clone() + } + + pub(crate) fn append_report(&self, report: Report) { + self.report_list.borrow_mut().push(report); + } + + pub(crate) fn buffered_reports(&self) -> Vec { + self.report_list.borrow().clone() + } + /// Returns the window proxy if it has not been discarded. /// pub(crate) fn undiscarded_window_proxy(&self) -> Option> { @@ -3106,6 +3143,8 @@ impl Window { current_event: DomRefCell::new(None), theme: Cell::new(theme), trusted_types: Default::default(), + reporting_observer_list: Default::default(), + report_list: Default::default(), }); unsafe { diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 4cc4f95e76f..0b3a80f45fe 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -36,6 +36,7 @@ use crate::dom::bindings::cell::{DomRefCell, Ref}; use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::{ ImageBitmapOptions, ImageBitmapSource, }; +use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::Report; use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestInit; use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction; use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType; @@ -56,6 +57,7 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::idbfactory::IDBFactory; use crate::dom::performance::Performance; use crate::dom::promise::Promise; +use crate::dom::reportingobserver::ReportingObserver; use crate::dom::trustedscripturl::TrustedScriptURL; use crate::dom::trustedtypepolicyfactory::TrustedTypePolicyFactory; use crate::dom::types::ImageBitmap; @@ -139,6 +141,12 @@ pub(crate) struct WorkerGlobalScope { #[no_trace] insecure_requests_policy: InsecureRequestsPolicy, + + /// + reporting_observer_list: DomRefCell>>, + + /// + report_list: DomRefCell>, } impl WorkerGlobalScope { @@ -196,6 +204,8 @@ impl WorkerGlobalScope { timer_scheduler: RefCell::default(), insecure_requests_policy, trusted_types: Default::default(), + reporting_observer_list: Default::default(), + report_list: Default::default(), } } @@ -259,6 +269,35 @@ impl WorkerGlobalScope { self.policy_container.borrow_mut().set_csp_list(csp_list); } + pub(crate) fn append_reporting_observer(&self, reporting_observer: DomRoot) { + self.reporting_observer_list + .borrow_mut() + .push(reporting_observer); + } + + pub(crate) fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) { + if let Some(index) = self + .reporting_observer_list + .borrow() + .iter() + .position(|observer| &**observer == reporting_observer) + { + self.reporting_observer_list.borrow_mut().remove(index); + } + } + + pub(crate) fn registered_reporting_observers(&self) -> Vec> { + self.reporting_observer_list.borrow().clone() + } + + pub(crate) fn append_report(&self, report: Report) { + self.report_list.borrow_mut().push(report); + } + + pub(crate) fn buffered_reports(&self) -> Vec { + self.report_list.borrow().clone() + } + /// Get a mutable reference to the [`TimerScheduler`] for this [`ServiceWorkerGlobalScope`]. pub(crate) fn timer_scheduler(&self) -> RefMut { self.timer_scheduler.borrow_mut() diff --git a/components/script/security_manager.rs b/components/script/security_manager.rs index 52e54349bea..57805aa5d34 100644 --- a/components/script/security_manager.rs +++ b/components/script/security_manager.rs @@ -26,6 +26,7 @@ use crate::dom::csppolicyviolationreport::{ use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::eventtarget::EventTarget; use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::reportingobserver::ReportingObserver; use crate::dom::securitypolicyviolationevent::SecurityPolicyViolationEvent; use crate::dom::types::GlobalScope; use crate::fetch::create_a_potential_cors_request; @@ -156,6 +157,24 @@ impl TaskOnce for CSPViolationReportTask { { self.post_csp_violation_to_report_uri(report_uri_directive); } + // Step 3.5. If violation’s policy’s directive set contains a directive named "report-to" directive: + if let Some(report_to_directive) = self + .violation_policy + .directive_set + .iter() + .find(|directive| directive.name == "report-to") + { + // Step 3.5.1. Let body be a new CSPViolationReportBody, initialized as follows: + let body = self.violation_report.clone().convert(); + // Step 3.5.2. Let settings object be violation’s global object’s relevant settings object. + // Step 3.5.3. Generate and queue a report with the following arguments: + ReportingObserver::generate_and_queue_a_report( + &self.global.root(), + "csp-violation".into(), + Some(body), + report_to_directive.value.join(" ").into(), + ) + } } } diff --git a/components/script_bindings/codegen/Bindings.conf b/components/script_bindings/codegen/Bindings.conf index bb686429931..97f4fb58f6f 100644 --- a/components/script_bindings/codegen/Bindings.conf +++ b/components/script_bindings/codegen/Bindings.conf @@ -799,6 +799,10 @@ Dictionaries = { 'derives': ['Clone', 'Copy'], }, +'CSPViolationReportBody': { + 'derives': ['Clone', 'MallocSizeOf'], +}, + 'FontFaceDescriptors': { 'derives': ['Clone', 'MallocSizeOf'] }, @@ -831,6 +835,14 @@ Dictionaries = { 'derives': ['Clone'], }, +'Report': { + 'derives': ['Clone', 'MallocSizeOf'], +}, + +'ReportBody': { + 'derives': ['Clone', 'MallocSizeOf'], +}, + 'StereoPannerOptions': { 'derives': ['Clone', 'Copy'], }, diff --git a/components/script_bindings/webidls/CSPViolationReportBody.webidl b/components/script_bindings/webidls/CSPViolationReportBody.webidl new file mode 100644 index 00000000000..a47b9c02297 --- /dev/null +++ b/components/script_bindings/webidls/CSPViolationReportBody.webidl @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// https://w3c.github.io/webappsec-csp/#dictdef-cspviolationreportbody + +dictionary CSPViolationReportBody : ReportBody { + required USVString documentURL; + USVString referrer; + USVString blockedURL; + required DOMString effectiveDirective; + required DOMString originalPolicy; + USVString sourceFile; + DOMString sample; + required SecurityPolicyViolationEventDisposition disposition; + required unsigned short statusCode; + unsigned long lineNumber; + unsigned long columnNumber; +}; diff --git a/components/script_bindings/webidls/ReportingObserver.webidl b/components/script_bindings/webidls/ReportingObserver.webidl new file mode 100644 index 00000000000..4b7fb5e93b9 --- /dev/null +++ b/components/script_bindings/webidls/ReportingObserver.webidl @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// https://w3c.github.io/reporting/#interface-reporting-observer + +dictionary ReportBody { +}; + +dictionary Report { + required DOMString type; + required DOMString url; + required DOMString destination; + required double timestamp; + required long attempts; + // TODO(37328): Change this to parent class ReportBody + CSPViolationReportBody body; +}; + +[Exposed=(Window,Worker)] +interface ReportingObserver { + constructor(ReportingObserverCallback callback, optional ReportingObserverOptions options = {}); + undefined observe(); + undefined disconnect(); + ReportList takeRecords(); +}; + +callback ReportingObserverCallback = undefined (sequence reports, ReportingObserver observer); + +dictionary ReportingObserverOptions { + sequence types; + boolean buffered = false; +}; + +typedef sequence ReportList; diff --git a/tests/wpt/meta/content-security-policy/reporting/post-redirect-stacktrace.https.html.ini b/tests/wpt/meta/content-security-policy/reporting/post-redirect-stacktrace.https.html.ini index 0623ac8d003..5b4c7d2c1e3 100644 --- a/tests/wpt/meta/content-security-policy/reporting/post-redirect-stacktrace.https.html.ini +++ b/tests/wpt/meta/content-security-policy/reporting/post-redirect-stacktrace.https.html.ini @@ -1,5 +1,5 @@ [post-redirect-stacktrace.https.html] - expected: ERROR + expected: TIMEOUT [StackTrace do not leak cross-origin post-redirect URL] expected: FAIL diff --git a/tests/wpt/meta/fetch/stale-while-revalidate/revalidate-not-blocked-by-csp.html.ini b/tests/wpt/meta/fetch/stale-while-revalidate/revalidate-not-blocked-by-csp.html.ini deleted file mode 100644 index 179507b09a7..00000000000 --- a/tests/wpt/meta/fetch/stale-while-revalidate/revalidate-not-blocked-by-csp.html.ini +++ /dev/null @@ -1,4 +0,0 @@ -[revalidate-not-blocked-by-csp.html] - [Request revalidation aren't blocked by CSP] - expected: FAIL - diff --git a/tests/wpt/meta/html/document-isolation-policy/reporting-subresource-corp.tentative.https.html.ini b/tests/wpt/meta/html/document-isolation-policy/reporting-subresource-corp.tentative.https.html.ini index 7ee7b7caaaf..2cbbeaabb1e 100644 --- a/tests/wpt/meta/html/document-isolation-policy/reporting-subresource-corp.tentative.https.html.ini +++ b/tests/wpt/meta/html/document-isolation-policy/reporting-subresource-corp.tentative.https.html.ini @@ -1,9 +1,10 @@ [reporting-subresource-corp.tentative.https.html] + expected: TIMEOUT [[document\] blocked due to DIP] - expected: FAIL + expected: TIMEOUT [[document\] blocked during redirect] - expected: FAIL + expected: NOTRUN [destination: script] expected: FAIL @@ -12,22 +13,22 @@ expected: FAIL [[dedicated worker\] blocked due to DIP] - expected: FAIL + expected: NOTRUN [[dedicated worker\] blocked during redirect] - expected: FAIL + expected: NOTRUN [[shared worker\] same-origin] - expected: FAIL + expected: NOTRUN [[shared worker\] blocked by CORP: same-origin] expected: FAIL [[shared worker\] blocked due to DIP] - expected: FAIL + expected: NOTRUN [[shared worker\] blocked during redirect] - expected: FAIL + expected: NOTRUN [[between service worker and page\] same-origin] expected: FAIL @@ -42,10 +43,13 @@ expected: FAIL [[document with service worker\] same-origin] - expected: FAIL + expected: NOTRUN [[document with service worker\] blocked due to DIP] - expected: FAIL + expected: NOTRUN [[document with service worker\] blocked during redirect] - expected: FAIL + expected: NOTRUN + + [[dedicated worker\] same-origin] + expected: NOTRUN diff --git a/tests/wpt/meta/reporting/document-reporting-bypass-report-to.https.sub.html.ini b/tests/wpt/meta/reporting/document-reporting-bypass-report-to.https.sub.html.ini index 26668188a63..5b71f3eb878 100644 --- a/tests/wpt/meta/reporting/document-reporting-bypass-report-to.https.sub.html.ini +++ b/tests/wpt/meta/reporting/document-reporting-bypass-report-to.https.sub.html.ini @@ -1,6 +1,7 @@ [document-reporting-bypass-report-to.https.sub.html] + expected: TIMEOUT [document policy violation observed] - expected: FAIL + expected: TIMEOUT [Only the Reporting-Endpoints configured endpoint received reports.] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/meta/reporting/document-reporting-named-endpoints.https.sub.html.ini b/tests/wpt/meta/reporting/document-reporting-named-endpoints.https.sub.html.ini index 599ece01563..c3bdf654eed 100644 --- a/tests/wpt/meta/reporting/document-reporting-named-endpoints.https.sub.html.ini +++ b/tests/wpt/meta/reporting/document-reporting-named-endpoints.https.sub.html.ini @@ -1,9 +1,7 @@ [document-reporting-named-endpoints.https.sub.html] - [csp violation report observed] - expected: FAIL - + expected: TIMEOUT [document policy violation observed] - expected: FAIL + expected: TIMEOUT [Reporting endpoints received reports.] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/meta/reporting/document-reporting-override-endpoint.https.sub.html.ini b/tests/wpt/meta/reporting/document-reporting-override-endpoint.https.sub.html.ini index 8f28f3de76e..1ee90c90bf5 100644 --- a/tests/wpt/meta/reporting/document-reporting-override-endpoint.https.sub.html.ini +++ b/tests/wpt/meta/reporting/document-reporting-override-endpoint.https.sub.html.ini @@ -1,6 +1,7 @@ [document-reporting-override-endpoint.https.sub.html] + expected: TIMEOUT [document policy violation observed] - expected: FAIL + expected: TIMEOUT [Only the second reporting endpoint received reports.] - expected: FAIL + expected: NOTRUN diff --git a/tests/wpt/meta/reporting/idlharness.any.js.ini b/tests/wpt/meta/reporting/idlharness.any.js.ini index 5a55d02e27f..0f79ee94867 100644 --- a/tests/wpt/meta/reporting/idlharness.any.js.ini +++ b/tests/wpt/meta/reporting/idlharness.any.js.ini @@ -50,33 +50,6 @@ [Report interface: attribute body] expected: FAIL - [ReportingObserver interface: existence and properties of interface object] - expected: FAIL - - [ReportingObserver interface object length] - expected: FAIL - - [ReportingObserver interface object name] - expected: FAIL - - [ReportingObserver interface: existence and properties of interface prototype object] - expected: FAIL - - [ReportingObserver interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [ReportingObserver interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [ReportingObserver interface: operation observe()] - expected: FAIL - - [ReportingObserver interface: operation disconnect()] - expected: FAIL - - [ReportingObserver interface: operation takeRecords()] - expected: FAIL - [idlharness.any.worker.html] [ReportBody interface: existence and properties of interface object] @@ -129,30 +102,3 @@ [Report interface: attribute body] expected: FAIL - - [ReportingObserver interface: existence and properties of interface object] - expected: FAIL - - [ReportingObserver interface object length] - expected: FAIL - - [ReportingObserver interface object name] - expected: FAIL - - [ReportingObserver interface: existence and properties of interface prototype object] - expected: FAIL - - [ReportingObserver interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [ReportingObserver interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [ReportingObserver interface: operation observe()] - expected: FAIL - - [ReportingObserver interface: operation disconnect()] - expected: FAIL - - [ReportingObserver interface: operation takeRecords()] - expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index 08837195da8..144b55e2fe0 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -13640,14 +13640,14 @@ ] ], "interfaces.https.html": [ - "eee8c799727b91e00b512795756b693a5f121f86", + "8a9345525360e7a7ce69e84e394b65a4cbc0ab34", [ null, {} ] ], "interfaces.worker.js": [ - "e86f34f261442aeaa7074c525fb4b1206219769d", + "f217fd8fb6b46144bc3576a081cc6ce5db3129d5", [ "mozilla/interfaces.worker.html", {} diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html index eee8c799727..8a934552536 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.https.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.https.html @@ -293,6 +293,7 @@ test_interfaces([ "ReadableStreamBYOBReader", "ReadableByteStreamController", "ReadableStreamBYOBRequest", + "ReportingObserver", "Request", "ResizeObserver", "ResizeObserverEntry", diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js index e86f34f2614..f217fd8fb6b 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.worker.js @@ -103,6 +103,7 @@ test_interfaces([ "ReadableStreamBYOBReader", "ReadableByteStreamController", "ReadableStreamBYOBRequest", + "ReportingObserver", "Request", "Response", "SecurityPolicyViolationEvent",