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::match_domstring_ascii;
14use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
15use script_bindings::str::DOMString;
16use servo_url::ServoUrl;
17
18use crate::dom::bindings::callback::ExceptionHandling;
19use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
20use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::{
21 Report, ReportList, ReportingObserverCallback, ReportingObserverMethods,
22 ReportingObserverOptions,
23};
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::num::Finite;
26use crate::dom::bindings::refcounted::Trusted;
27use crate::dom::bindings::reflector::DomGlobal;
28use crate::dom::bindings::root::DomRoot;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::window::Window;
31use crate::dom::workerglobalscope::WorkerGlobalScope;
32
33#[dom_struct]
34pub(crate) struct ReportingObserver {
35 reflector_: Reflector,
36
37 callback: TracedCallback<ReportingObserverCallback>,
38 buffered: Cell<bool>,
39 types: DomRefCell<Vec<DOMString>>,
40 report_queue: DomRefCell<Vec<Report>>,
41}
42
43impl ReportingObserver {
44 fn new_inherited(
45 callback: RootedCallback<ReportingObserverCallback>,
46 options: &ReportingObserverOptions,
47 ) -> Self {
48 Self {
49 reflector_: Reflector::new(),
50 callback: callback.to_traced(),
51 buffered: Cell::new(options.buffered),
52 types: DomRefCell::new(options.types.clone().unwrap_or_default()),
53 report_queue: Default::default(),
54 }
55 }
56
57 fn new_with_proto(
58 cx: &mut JSContext,
59 callback: RootedCallback<ReportingObserverCallback>,
60 options: &ReportingObserverOptions,
61 global: &GlobalScope,
62 proto: Option<HandleObject>,
63 ) -> DomRoot<Self> {
64 reflect_dom_object_with_proto(
65 cx,
66 Box::new(Self::new_inherited(callback, options)),
67 global,
68 proto,
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 |cx| {
112 Self::invoke_reporting_observers_with_notify_list(
113 cx,
114 observers_global.root().registered_reporting_observers()
115 );
116 }),
117 );
118 }
119 }
120
121 pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) {
123 for observer in global.registered_reporting_observers().iter() {
126 observer.add_report_to_observer(report);
127 }
128 global.append_report(report.clone());
130 }
136
137 fn invoke_reporting_observers_with_notify_list(
139 cx: &mut JSContext,
140 notify_list: Vec<DomRoot<ReportingObserver>>,
141 ) {
142 for observer in notify_list.iter() {
144 if observer.report_queue.borrow().is_empty() {
146 continue;
147 }
148 let reports = std::mem::take(&mut *observer.report_queue.borrow_mut());
151 let _ = observer.callback.Call_(
154 cx,
155 &**observer,
156 reports,
157 observer,
158 ExceptionHandling::Report,
159 );
160 }
161 }
162
163 fn generate_a_report(
165 global: &GlobalScope,
166 type_: DOMString,
167 url: Option<ServoUrl>,
168 body: Option<CSPViolationReportBody>,
169 destination: DOMString,
170 ) -> Report {
171 let url = url.unwrap_or(global.creation_url());
173 let url = Self::strip_url_for_reports(url).into();
177 Report {
180 type_,
181 url,
182 body,
183 destination,
184 timestamp: Finite::wrap(
185 SystemTime::now()
186 .duration_since(UNIX_EPOCH)
187 .unwrap_or_default()
188 .as_millis() as f64,
189 ),
190 attempts: 0,
191 }
192 }
193
194 pub(crate) fn generate_and_queue_a_report(
196 global: &GlobalScope,
197 type_: DOMString,
198 body: Option<CSPViolationReportBody>,
199 destination: DOMString,
200 ) {
201 let report = Self::generate_a_report(global, type_, None, body, destination);
204 Self::notify_reporting_observers_on_scope(global, &report);
209 global.append_report(report);
211 }
212
213 pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String {
215 let scheme = url.scheme();
216 if scheme != "https" && scheme != "http" && scheme != "ws" && scheme != "wss" {
221 return scheme.to_owned();
222 }
223 url.set_fragment(None);
225 let _ = url.set_username("");
227 let _ = url.set_password(None);
229 url.into_string()
231 }
232}
233
234impl ReportingObserverMethods<crate::DomTypeHolder> for ReportingObserver {
235 fn Constructor(
237 cx: &mut JSContext,
238 global: &GlobalScope,
239 proto: Option<HandleObject>,
240 callback: RootedCallback<ReportingObserverCallback>,
241 options: &ReportingObserverOptions,
242 ) -> DomRoot<ReportingObserver> {
243 ReportingObserver::new_with_proto(cx, callback, options, global, proto)
248 }
249
250 fn Observe(&self) {
252 let global = &self.global();
254 global.append_reporting_observer(self);
256 if !self.buffered.get() {
258 return;
259 }
260 self.buffered.set(false);
262 for report in global.buffered_reports() {
265 self.add_report_to_observer(&report);
267 }
268 }
269
270 fn Disconnect(&self) {
272 let global = &self.global();
277 global.remove_reporting_observer(self);
279 }
280
281 fn TakeRecords(&self) -> ReportList {
283 std::mem::take(&mut *self.report_queue.borrow_mut())
287 }
288}
289
290impl GlobalScope {
291 fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) {
292 if let Some(window) = self.downcast::<Window>() {
293 return window.append_reporting_observer(reporting_observer);
294 }
295 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
296 return worker.append_reporting_observer(reporting_observer);
297 }
298 unreachable!();
299 }
300
301 fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) {
302 if let Some(window) = self.downcast::<Window>() {
303 return window.remove_reporting_observer(reporting_observer);
304 }
305 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
306 return worker.remove_reporting_observer(reporting_observer);
307 }
308 unreachable!();
309 }
310
311 fn registered_reporting_observers(&self) -> Vec<DomRoot<ReportingObserver>> {
312 if let Some(window) = self.downcast::<Window>() {
313 return window.registered_reporting_observers();
314 }
315 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
316 return worker.registered_reporting_observers();
317 }
318 unreachable!();
319 }
320
321 fn append_report(&self, report: Report) {
322 if let Some(window) = self.downcast::<Window>() {
323 return window.append_report(report);
324 }
325 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
326 return worker.append_report(report);
327 }
328 unreachable!();
329 }
330
331 fn buffered_reports(&self) -> Vec<Report> {
332 if let Some(window) = self.downcast::<Window>() {
333 return window.buffered_reports();
334 }
335 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
336 return worker.buffered_reports();
337 }
338 unreachable!();
339 }
340}
341
342impl OwnerWindow<crate::DomTypeHolder> for ReportingObserver {}