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::context::JSContext;
11use js::rust::HandleObject;
12use script_bindings::cell::DomRefCell;
13use script_bindings::match_domstring_ascii;
14use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
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 #[conditional_malloc_size_of]
38 callback: Rc<ReportingObserverCallback>,
39 buffered: RefCell<bool>,
40 types: DomRefCell<Vec<DOMString>>,
41 report_queue: DomRefCell<Vec<Report>>,
42}
43
44impl ReportingObserver {
45 fn new_inherited(
46 callback: Rc<ReportingObserverCallback>,
47 options: &ReportingObserverOptions,
48 ) -> Self {
49 Self {
50 reflector_: Reflector::new(),
51 callback,
52 buffered: RefCell::new(options.buffered),
53 types: DomRefCell::new(options.types.clone().unwrap_or_default()),
54 report_queue: Default::default(),
55 }
56 }
57
58 fn new_with_proto(
59 cx: &mut JSContext,
60 callback: Rc<ReportingObserverCallback>,
61 options: &ReportingObserverOptions,
62 global: &GlobalScope,
63 proto: Option<HandleObject>,
64 ) -> DomRoot<Self> {
65 reflect_dom_object_with_proto_and_cx(
66 Box::new(Self::new_inherited(callback, options)),
67 global,
68 proto,
69 cx,
70 )
71 }
72
73 fn report_is_visible_to_reporting_observers(report: &Report) -> bool {
74 match_domstring_ascii!(report.type_,
75 "csp-violation" => true,
77 _ => false,
78 )
79 }
80
81 fn add_report_to_observer(&self, report: &Report) {
83 if !Self::report_is_visible_to_reporting_observers(report) {
85 return;
86 }
87 let types = self.types.borrow();
89 if !types.is_empty() && !types.contains(&report.type_) {
90 return;
91 }
92 let report = Report {
95 type_: report.type_.clone(),
96 url: report.url.clone(),
97 body: report.body.clone(),
98 destination: report.destination.clone(),
99 attempts: report.attempts,
100 timestamp: report.timestamp,
101 };
102 self.report_queue.borrow_mut().push(report);
104 if self.report_queue.borrow().len() == 1 {
106 let global = self.global();
108 let observers_global = Trusted::new(&*global);
111 global.task_manager().dom_manipulation_task_source().queue(
112 task!(notify_reporting_observers: move |cx| {
113 Self::invoke_reporting_observers_with_notify_list(
114 cx,
115 observers_global.root().registered_reporting_observers()
116 );
117 }),
118 );
119 }
120 }
121
122 pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) {
124 for observer in global.registered_reporting_observers().iter() {
127 observer.add_report_to_observer(report);
128 }
129 global.append_report(report.clone());
131 }
137
138 fn invoke_reporting_observers_with_notify_list(
140 cx: &mut JSContext,
141 notify_list: Vec<DomRoot<ReportingObserver>>,
142 ) {
143 for observer in notify_list.iter() {
145 if observer.report_queue.borrow().is_empty() {
147 continue;
148 }
149 let reports = std::mem::take(&mut *observer.report_queue.borrow_mut());
152 let _ = observer.callback.Call_(
155 cx,
156 &**observer,
157 reports,
158 observer,
159 ExceptionHandling::Report,
160 );
161 }
162 }
163
164 fn generate_a_report(
166 global: &GlobalScope,
167 type_: DOMString,
168 url: Option<ServoUrl>,
169 body: Option<CSPViolationReportBody>,
170 destination: DOMString,
171 ) -> Report {
172 let url = url.unwrap_or(global.creation_url());
174 let url = Self::strip_url_for_reports(url).into();
178 Report {
181 type_,
182 url,
183 body,
184 destination,
185 timestamp: Finite::wrap(
186 SystemTime::now()
187 .duration_since(UNIX_EPOCH)
188 .unwrap_or_default()
189 .as_millis() as f64,
190 ),
191 attempts: 0,
192 }
193 }
194
195 pub(crate) fn generate_and_queue_a_report(
197 global: &GlobalScope,
198 type_: DOMString,
199 body: Option<CSPViolationReportBody>,
200 destination: DOMString,
201 ) {
202 let report = Self::generate_a_report(global, type_, None, body, destination);
205 Self::notify_reporting_observers_on_scope(global, &report);
210 global.append_report(report);
212 }
213
214 pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String {
216 let scheme = url.scheme();
217 if scheme != "https" && scheme != "http" && scheme != "ws" && scheme != "wss" {
222 return scheme.to_owned();
223 }
224 url.set_fragment(None);
226 let _ = url.set_username("");
228 let _ = url.set_password(None);
230 url.into_string()
232 }
233}
234
235impl ReportingObserverMethods<crate::DomTypeHolder> for ReportingObserver {
236 fn Constructor(
238 cx: &mut JSContext,
239 global: &GlobalScope,
240 proto: Option<HandleObject>,
241 callback: Rc<ReportingObserverCallback>,
242 options: &ReportingObserverOptions,
243 ) -> DomRoot<ReportingObserver> {
244 ReportingObserver::new_with_proto(cx, callback, options, global, proto)
249 }
250
251 fn Observe(&self) {
253 let global = &self.global();
255 global.append_reporting_observer(self);
257 if !*self.buffered.borrow() {
259 return;
260 }
261 *self.buffered.borrow_mut() = false;
263 for report in global.buffered_reports() {
266 self.add_report_to_observer(&report);
268 }
269 }
270
271 fn Disconnect(&self) {
273 let global = &self.global();
278 global.remove_reporting_observer(self);
280 }
281
282 fn TakeRecords(&self) -> ReportList {
284 std::mem::take(&mut *self.report_queue.borrow_mut())
288 }
289}
290
291impl GlobalScope {
292 fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) {
293 if let Some(window) = self.downcast::<Window>() {
294 return window.append_reporting_observer(DomRoot::from_ref(reporting_observer));
295 }
296 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
297 return worker.append_reporting_observer(DomRoot::from_ref(reporting_observer));
298 }
299 unreachable!();
300 }
301
302 fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) {
303 if let Some(window) = self.downcast::<Window>() {
304 return window.remove_reporting_observer(reporting_observer);
305 }
306 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
307 return worker.remove_reporting_observer(reporting_observer);
308 }
309 unreachable!();
310 }
311
312 fn registered_reporting_observers(&self) -> Vec<DomRoot<ReportingObserver>> {
313 if let Some(window) = self.downcast::<Window>() {
314 return window.registered_reporting_observers();
315 }
316 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
317 return worker.registered_reporting_observers();
318 }
319 unreachable!();
320 }
321
322 fn append_report(&self, report: Report) {
323 if let Some(window) = self.downcast::<Window>() {
324 return window.append_report(report);
325 }
326 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
327 return worker.append_report(report);
328 }
329 unreachable!();
330 }
331
332 fn buffered_reports(&self) -> Vec<Report> {
333 if let Some(window) = self.downcast::<Window>() {
334 return window.buffered_reports();
335 }
336 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
337 return worker.buffered_reports();
338 }
339 unreachable!();
340 }
341}