script/dom/reporting/
reportingobserver.rs1use std::cell::RefCell;
6use std::rc::Rc;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use dom_struct::dom_struct;
10use js::rust::HandleObject;
11use script_bindings::match_domstring_ascii;
12use script_bindings::str::DOMString;
13use servo_url::ServoUrl;
14
15use crate::dom::bindings::callback::ExceptionHandling;
16use crate::dom::bindings::cell::DomRefCell;
17use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
18use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::{
19 Report, ReportList, ReportingObserverCallback, ReportingObserverMethods,
20 ReportingObserverOptions,
21};
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::num::Finite;
24use crate::dom::bindings::refcounted::Trusted;
25use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
26use crate::dom::bindings::root::DomRoot;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::window::Window;
29use crate::dom::workerglobalscope::WorkerGlobalScope;
30use crate::script_runtime::CanGc;
31
32#[dom_struct]
33pub(crate) struct ReportingObserver {
34 reflector_: Reflector,
35
36 #[conditional_malloc_size_of]
37 callback: Rc<ReportingObserverCallback>,
38 buffered: RefCell<bool>,
39 types: DomRefCell<Vec<DOMString>>,
40 report_queue: DomRefCell<Vec<Report>>,
41}
42
43impl ReportingObserver {
44 fn new_inherited(
45 callback: Rc<ReportingObserverCallback>,
46 options: &ReportingObserverOptions,
47 ) -> Self {
48 Self {
49 reflector_: Reflector::new(),
50 callback,
51 buffered: RefCell::new(options.buffered),
52 types: DomRefCell::new(options.types.clone().unwrap_or_default()),
53 report_queue: Default::default(),
54 }
55 }
56
57 pub(crate) fn new_with_proto(
58 callback: Rc<ReportingObserverCallback>,
59 options: &ReportingObserverOptions,
60 global: &GlobalScope,
61 proto: Option<HandleObject>,
62 can_gc: CanGc,
63 ) -> DomRoot<Self> {
64 reflect_dom_object_with_proto(
65 Box::new(Self::new_inherited(callback, options)),
66 global,
67 proto,
68 can_gc,
69 )
70 }
71
72 fn report_is_visible_to_reporting_observers(report: &Report) -> bool {
73 match_domstring_ascii!(report.type_,
74 "csp-violation" => true,
76 _ => false,
77 )
78 }
79
80 fn add_report_to_observer(&self, report: &Report) {
82 if !Self::report_is_visible_to_reporting_observers(report) {
84 return;
85 }
86 let types = self.types.borrow();
88 if !types.is_empty() && !types.contains(&report.type_) {
89 return;
90 }
91 let report = Report {
94 type_: report.type_.clone(),
95 url: report.url.clone(),
96 body: report.body.clone(),
97 destination: report.destination.clone(),
98 attempts: report.attempts,
99 timestamp: report.timestamp,
100 };
101 self.report_queue.borrow_mut().push(report);
103 if self.report_queue.borrow().len() == 1 {
105 let global = self.global();
107 let observers_global = Trusted::new(&*global);
110 global.task_manager().dom_manipulation_task_source().queue(
111 task!(notify_reporting_observers: move || {
112 Self::invoke_reporting_observers_with_notify_list(
113 observers_global.root().registered_reporting_observers()
114 );
115 }),
116 );
117 }
118 }
119
120 pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) {
122 for observer in global.registered_reporting_observers().iter() {
125 observer.add_report_to_observer(report);
126 }
127 global.append_report(report.clone());
129 }
135
136 fn invoke_reporting_observers_with_notify_list(notify_list: Vec<DomRoot<ReportingObserver>>) {
138 for observer in notify_list.iter() {
140 if observer.report_queue.borrow().is_empty() {
142 continue;
143 }
144 let reports = std::mem::take(&mut *observer.report_queue.borrow_mut());
147 let _ = observer.callback.Call_(
150 &**observer,
151 reports,
152 observer,
153 ExceptionHandling::Report,
154 CanGc::note(),
155 );
156 }
157 }
158
159 fn generate_a_report(
161 global: &GlobalScope,
162 type_: DOMString,
163 url: Option<ServoUrl>,
164 body: Option<CSPViolationReportBody>,
165 destination: DOMString,
166 ) -> Report {
167 let url = url.unwrap_or(global.creation_url());
169 let url = Self::strip_url_for_reports(url).into();
173 Report {
176 type_,
177 url,
178 body,
179 destination,
180 timestamp: Finite::wrap(
181 SystemTime::now()
182 .duration_since(UNIX_EPOCH)
183 .unwrap_or_default()
184 .as_millis() as f64,
185 ),
186 attempts: 0,
187 }
188 }
189
190 pub(crate) fn generate_and_queue_a_report(
192 global: &GlobalScope,
193 type_: DOMString,
194 body: Option<CSPViolationReportBody>,
195 destination: DOMString,
196 ) {
197 let report = Self::generate_a_report(global, type_, None, body, destination);
200 Self::notify_reporting_observers_on_scope(global, &report);
205 global.append_report(report);
207 }
208
209 pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String {
211 let scheme = url.scheme();
212 if scheme != "https" && scheme != "http" {
214 return scheme.to_owned();
215 }
216 url.set_fragment(None);
218 let _ = url.set_username("");
220 let _ = url.set_password(None);
222 url.into_string()
224 }
225}
226
227impl ReportingObserverMethods<crate::DomTypeHolder> for ReportingObserver {
228 fn Constructor(
230 global: &GlobalScope,
231 proto: Option<HandleObject>,
232 can_gc: CanGc,
233 callback: Rc<ReportingObserverCallback>,
234 options: &ReportingObserverOptions,
235 ) -> DomRoot<ReportingObserver> {
236 ReportingObserver::new_with_proto(callback, options, global, proto, can_gc)
241 }
242
243 fn Observe(&self) {
245 let global = &self.global();
247 global.append_reporting_observer(self);
249 if !*self.buffered.borrow() {
251 return;
252 }
253 *self.buffered.borrow_mut() = false;
255 for report in global.buffered_reports() {
258 self.add_report_to_observer(&report);
260 }
261 }
262
263 fn Disconnect(&self) {
265 let global = &self.global();
270 global.remove_reporting_observer(self);
272 }
273
274 fn TakeRecords(&self) -> ReportList {
276 std::mem::take(&mut *self.report_queue.borrow_mut())
280 }
281}
282
283impl GlobalScope {
284 fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) {
285 if let Some(window) = self.downcast::<Window>() {
286 return window.append_reporting_observer(DomRoot::from_ref(reporting_observer));
287 }
288 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
289 return worker.append_reporting_observer(DomRoot::from_ref(reporting_observer));
290 }
291 unreachable!();
292 }
293
294 fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) {
295 if let Some(window) = self.downcast::<Window>() {
296 return window.remove_reporting_observer(reporting_observer);
297 }
298 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
299 return worker.remove_reporting_observer(reporting_observer);
300 }
301 unreachable!();
302 }
303
304 fn registered_reporting_observers(&self) -> Vec<DomRoot<ReportingObserver>> {
305 if let Some(window) = self.downcast::<Window>() {
306 return window.registered_reporting_observers();
307 }
308 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
309 return worker.registered_reporting_observers();
310 }
311 unreachable!();
312 }
313
314 fn append_report(&self, report: Report) {
315 if let Some(window) = self.downcast::<Window>() {
316 return window.append_report(report);
317 }
318 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
319 return worker.append_report(report);
320 }
321 unreachable!();
322 }
323
324 fn buffered_reports(&self) -> Vec<Report> {
325 if let Some(window) = self.downcast::<Window>() {
326 return window.buffered_reports();
327 }
328 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
329 return worker.buffered_reports();
330 }
331 unreachable!();
332 }
333}