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};
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;
32use crate::script_runtime::CanGc;
33
34#[dom_struct]
35pub(crate) struct ReportingObserver {
36 reflector_: Reflector,
37
38 #[conditional_malloc_size_of]
39 callback: Rc<ReportingObserverCallback>,
40 buffered: RefCell<bool>,
41 types: DomRefCell<Vec<DOMString>>,
42 report_queue: DomRefCell<Vec<Report>>,
43}
44
45impl ReportingObserver {
46 fn new_inherited(
47 callback: Rc<ReportingObserverCallback>,
48 options: &ReportingObserverOptions,
49 ) -> Self {
50 Self {
51 reflector_: Reflector::new(),
52 callback,
53 buffered: RefCell::new(options.buffered),
54 types: DomRefCell::new(options.types.clone().unwrap_or_default()),
55 report_queue: Default::default(),
56 }
57 }
58
59 pub(crate) fn new_with_proto(
60 callback: Rc<ReportingObserverCallback>,
61 options: &ReportingObserverOptions,
62 global: &GlobalScope,
63 proto: Option<HandleObject>,
64 can_gc: CanGc,
65 ) -> DomRoot<Self> {
66 reflect_dom_object_with_proto(
67 Box::new(Self::new_inherited(callback, options)),
68 global,
69 proto,
70 can_gc,
71 )
72 }
73
74 fn report_is_visible_to_reporting_observers(report: &Report) -> bool {
75 match_domstring_ascii!(report.type_,
76 "csp-violation" => true,
78 _ => false,
79 )
80 }
81
82 fn add_report_to_observer(&self, report: &Report) {
84 if !Self::report_is_visible_to_reporting_observers(report) {
86 return;
87 }
88 let types = self.types.borrow();
90 if !types.is_empty() && !types.contains(&report.type_) {
91 return;
92 }
93 let report = Report {
96 type_: report.type_.clone(),
97 url: report.url.clone(),
98 body: report.body.clone(),
99 destination: report.destination.clone(),
100 attempts: report.attempts,
101 timestamp: report.timestamp,
102 };
103 self.report_queue.borrow_mut().push(report);
105 if self.report_queue.borrow().len() == 1 {
107 let global = self.global();
109 let observers_global = Trusted::new(&*global);
112 global.task_manager().dom_manipulation_task_source().queue(
113 task!(notify_reporting_observers: move |cx| {
114 Self::invoke_reporting_observers_with_notify_list(
115 cx,
116 observers_global.root().registered_reporting_observers()
117 );
118 }),
119 );
120 }
121 }
122
123 pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) {
125 for observer in global.registered_reporting_observers().iter() {
128 observer.add_report_to_observer(report);
129 }
130 global.append_report(report.clone());
132 }
138
139 fn invoke_reporting_observers_with_notify_list(
141 cx: &mut JSContext,
142 notify_list: Vec<DomRoot<ReportingObserver>>,
143 ) {
144 for observer in notify_list.iter() {
146 if observer.report_queue.borrow().is_empty() {
148 continue;
149 }
150 let reports = std::mem::take(&mut *observer.report_queue.borrow_mut());
153 let _ = observer.callback.Call_(
156 cx,
157 &**observer,
158 reports,
159 observer,
160 ExceptionHandling::Report,
161 );
162 }
163 }
164
165 fn generate_a_report(
167 global: &GlobalScope,
168 type_: DOMString,
169 url: Option<ServoUrl>,
170 body: Option<CSPViolationReportBody>,
171 destination: DOMString,
172 ) -> Report {
173 let url = url.unwrap_or(global.creation_url());
175 let url = Self::strip_url_for_reports(url).into();
179 Report {
182 type_,
183 url,
184 body,
185 destination,
186 timestamp: Finite::wrap(
187 SystemTime::now()
188 .duration_since(UNIX_EPOCH)
189 .unwrap_or_default()
190 .as_millis() as f64,
191 ),
192 attempts: 0,
193 }
194 }
195
196 pub(crate) fn generate_and_queue_a_report(
198 global: &GlobalScope,
199 type_: DOMString,
200 body: Option<CSPViolationReportBody>,
201 destination: DOMString,
202 ) {
203 let report = Self::generate_a_report(global, type_, None, body, destination);
206 Self::notify_reporting_observers_on_scope(global, &report);
211 global.append_report(report);
213 }
214
215 pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String {
217 let scheme = url.scheme();
218 if scheme != "https" && scheme != "http" && scheme != "ws" && scheme != "wss" {
223 return scheme.to_owned();
224 }
225 url.set_fragment(None);
227 let _ = url.set_username("");
229 let _ = url.set_password(None);
231 url.into_string()
233 }
234}
235
236impl ReportingObserverMethods<crate::DomTypeHolder> for ReportingObserver {
237 fn Constructor(
239 global: &GlobalScope,
240 proto: Option<HandleObject>,
241 can_gc: CanGc,
242 callback: Rc<ReportingObserverCallback>,
243 options: &ReportingObserverOptions,
244 ) -> DomRoot<ReportingObserver> {
245 ReportingObserver::new_with_proto(callback, options, global, proto, can_gc)
250 }
251
252 fn Observe(&self) {
254 let global = &self.global();
256 global.append_reporting_observer(self);
258 if !*self.buffered.borrow() {
260 return;
261 }
262 *self.buffered.borrow_mut() = false;
264 for report in global.buffered_reports() {
267 self.add_report_to_observer(&report);
269 }
270 }
271
272 fn Disconnect(&self) {
274 let global = &self.global();
279 global.remove_reporting_observer(self);
281 }
282
283 fn TakeRecords(&self) -> ReportList {
285 std::mem::take(&mut *self.report_queue.borrow_mut())
289 }
290}
291
292impl GlobalScope {
293 fn append_reporting_observer(&self, reporting_observer: &ReportingObserver) {
294 if let Some(window) = self.downcast::<Window>() {
295 return window.append_reporting_observer(DomRoot::from_ref(reporting_observer));
296 }
297 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
298 return worker.append_reporting_observer(DomRoot::from_ref(reporting_observer));
299 }
300 unreachable!();
301 }
302
303 fn remove_reporting_observer(&self, reporting_observer: &ReportingObserver) {
304 if let Some(window) = self.downcast::<Window>() {
305 return window.remove_reporting_observer(reporting_observer);
306 }
307 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
308 return worker.remove_reporting_observer(reporting_observer);
309 }
310 unreachable!();
311 }
312
313 fn registered_reporting_observers(&self) -> Vec<DomRoot<ReportingObserver>> {
314 if let Some(window) = self.downcast::<Window>() {
315 return window.registered_reporting_observers();
316 }
317 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
318 return worker.registered_reporting_observers();
319 }
320 unreachable!();
321 }
322
323 fn append_report(&self, report: Report) {
324 if let Some(window) = self.downcast::<Window>() {
325 return window.append_report(report);
326 }
327 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
328 return worker.append_report(report);
329 }
330 unreachable!();
331 }
332
333 fn buffered_reports(&self) -> Vec<Report> {
334 if let Some(window) = self.downcast::<Window>() {
335 return window.buffered_reports();
336 }
337 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
338 return worker.buffered_reports();
339 }
340 unreachable!();
341 }
342}