script/dom/reporting/
reportingobserver.rs1use std::cell::Cell;
6use std::time::{SystemTime, UNIX_EPOCH};
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::rust::HandleObject;
11use script_bindings::callback::{OwnerWindow, RootedCallback, TracedCallback};
12use script_bindings::cell::DomRefCell;
13use script_bindings::domstring::TracedDOMString;
14use script_bindings::match_domstring_ascii;
15use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
16use script_bindings::str::DOMString;
17use servo_url::ServoUrl;
18
19use crate::dom::bindings::callback::ExceptionHandling;
20use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
21use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::{
22 Report, ReportList, ReportingObserverCallback, ReportingObserverMethods,
23 ReportingObserverOptions,
24};
25use crate::dom::bindings::inheritance::Castable;
26use crate::dom::bindings::num::Finite;
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::DomRoot;
30use crate::dom::globalscope::GlobalScope;
31use crate::dom::window::Window;
32use crate::dom::workerglobalscope::WorkerGlobalScope;
33
34#[dom_struct]
35pub(crate) struct ReportingObserver {
36 reflector_: Reflector,
37
38 callback: TracedCallback<ReportingObserverCallback>,
39 buffered: Cell<bool>,
40 types: DomRefCell<Vec<TracedDOMString>>,
41 report_queue: DomRefCell<Vec<Report>>,
42}
43
44impl ReportingObserver {
45 fn new_inherited(
46 callback: RootedCallback<ReportingObserverCallback>,
47 options: &ReportingObserverOptions,
48 ) -> Self {
49 Self {
50 reflector_: Reflector::new(),
51 callback: callback.to_traced(),
52 buffered: Cell::new(options.buffered),
53 types: DomRefCell::new(
54 options
55 .types
56 .clone()
57 .unwrap_or_default()
58 .into_iter()
59 .map(|domstring| domstring.traced())
60 .collect(),
61 ),
62 report_queue: Default::default(),
63 }
64 }
65
66 fn new_with_proto(
67 cx: &mut JSContext,
68 callback: RootedCallback<ReportingObserverCallback>,
69 options: &ReportingObserverOptions,
70 global: &GlobalScope,
71 proto: Option<HandleObject>,
72 ) -> DomRoot<Self> {
73 reflect_dom_object_with_proto(
74 cx,
75 Box::new(Self::new_inherited(callback, options)),
76 global,
77 proto,
78 )
79 }
80
81 fn report_is_visible_to_reporting_observers(report: &Report) -> bool {
82 match_domstring_ascii!(report.type_,
83 "csp-violation" => true,
85 _ => false,
86 )
87 }
88
89 fn add_report_to_observer(&self, report: &Report) {
91 if !Self::report_is_visible_to_reporting_observers(report) {
93 return;
94 }
95 let types = self.types.borrow();
97 if !types.is_empty() && !types.contains(&report.type_) {
98 return;
99 }
100 let report = Report {
103 type_: report.type_.clone(),
104 url: report.url.clone(),
105 body: report.body.clone(),
106 destination: report.destination.clone(),
107 attempts: report.attempts,
108 timestamp: report.timestamp,
109 };
110 self.report_queue.borrow_mut().push(report);
112 if self.report_queue.borrow().len() == 1 {
114 let global = self.global();
116 let observers_global = Trusted::new(&*global);
119 global.task_manager().dom_manipulation_task_source().queue(
120 task!(notify_reporting_observers: move |cx| {
121 Self::invoke_reporting_observers_with_notify_list(
122 cx,
123 observers_global.root().registered_reporting_observers()
124 );
125 }),
126 );
127 }
128 }
129
130 pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) {
132 for observer in global.registered_reporting_observers().iter() {
135 observer.add_report_to_observer(report);
136 }
137 global.append_report(report.clone());
139 }
145
146 fn invoke_reporting_observers_with_notify_list(
148 cx: &mut JSContext,
149 notify_list: Vec<DomRoot<ReportingObserver>>,
150 ) {
151 for observer in notify_list.iter() {
153 if observer.report_queue.borrow().is_empty() {
155 continue;
156 }
157 let reports = std::mem::take(&mut *observer.report_queue.borrow_mut());
160 let _ = observer.callback.Call_(
163 cx,
164 &**observer,
165 reports,
166 observer,
167 ExceptionHandling::Report,
168 );
169 }
170 }
171
172 fn generate_a_report(
174 global: &GlobalScope,
175 type_: DOMString,
176 url: Option<ServoUrl>,
177 body: Option<CSPViolationReportBody>,
178 destination: DOMString,
179 ) -> Report {
180 let url = url.unwrap_or(global.creation_url());
182 let url = Self::strip_url_for_reports(url).into();
186 Report {
189 type_,
190 url,
191 body,
192 destination,
193 timestamp: Finite::wrap(
194 SystemTime::now()
195 .duration_since(UNIX_EPOCH)
196 .unwrap_or_default()
197 .as_millis() as f64,
198 ),
199 attempts: 0,
200 }
201 }
202
203 pub(crate) fn generate_and_queue_a_report(
205 global: &GlobalScope,
206 type_: DOMString,
207 body: Option<CSPViolationReportBody>,
208 destination: DOMString,
209 ) {
210 let report = Self::generate_a_report(global, type_, None, body, destination);
213 Self::notify_reporting_observers_on_scope(global, &report);
218 global.append_report(report);
220 }
221
222 pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String {
224 let scheme = url.scheme();
225 if scheme != "https" && scheme != "http" && scheme != "ws" && scheme != "wss" {
230 return scheme.to_owned();
231 }
232 url.set_fragment(None);
234 let _ = url.set_username("");
236 let _ = url.set_password(None);
238 url.into_string()
240 }
241}
242
243impl ReportingObserverMethods<crate::DomTypeHolder> for ReportingObserver {
244 fn Constructor(
246 cx: &mut JSContext,
247 global: &GlobalScope,
248 proto: Option<HandleObject>,
249 callback: RootedCallback<ReportingObserverCallback>,
250 options: &ReportingObserverOptions,
251 ) -> DomRoot<ReportingObserver> {
252 ReportingObserver::new_with_proto(cx, callback, options, global, proto)
257 }
258
259 fn Observe(&self) {
261 let global = &self.global();
263 global.append_reporting_observer(self);
265 if !self.buffered.get() {
267 return;
268 }
269 self.buffered.set(false);
271 for report in global.buffered_reports() {
274 self.add_report_to_observer(&report);
276 }
277 }
278
279 fn Disconnect(&self) {
281 let global = &self.global();
286 global.remove_reporting_observer(self);
288 }
289
290 fn TakeRecords(&self) -> ReportList {
292 std::mem::take(&mut *self.report_queue.borrow_mut())
296 }
297}
298
299impl GlobalScope {
300 fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) {
301 if let Some(window) = self.downcast::<Window>() {
302 return window.append_reporting_observer(reporting_observer);
303 }
304 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
305 return worker.append_reporting_observer(reporting_observer);
306 }
307 unreachable!();
308 }
309
310 fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) {
311 if let Some(window) = self.downcast::<Window>() {
312 return window.remove_reporting_observer(reporting_observer);
313 }
314 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
315 return worker.remove_reporting_observer(reporting_observer);
316 }
317 unreachable!();
318 }
319
320 fn registered_reporting_observers(&self) -> Vec<DomRoot<ReportingObserver>> {
321 if let Some(window) = self.downcast::<Window>() {
322 return window.registered_reporting_observers();
323 }
324 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
325 return worker.registered_reporting_observers();
326 }
327 unreachable!();
328 }
329
330 fn append_report(&self, report: Report) {
331 if let Some(window) = self.downcast::<Window>() {
332 return window.append_report(report);
333 }
334 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
335 return worker.append_report(report);
336 }
337 unreachable!();
338 }
339
340 fn buffered_reports(&self) -> Vec<Report> {
341 if let Some(window) = self.downcast::<Window>() {
342 return window.buffered_reports();
343 }
344 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
345 return worker.buffered_reports();
346 }
347 unreachable!();
348 }
349}
350
351impl OwnerWindow<crate::DomTypeHolder> for ReportingObserver {}