script/dom/reportingobserver.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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::str::DOMString;
12use servo_url::ServoUrl;
13
14use crate::dom::bindings::callback::ExceptionHandling;
15use crate::dom::bindings::cell::DomRefCell;
16use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody;
17use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::{
18 Report, ReportList, ReportingObserverCallback, ReportingObserverMethods,
19 ReportingObserverOptions,
20};
21use crate::dom::bindings::num::Finite;
22use crate::dom::bindings::refcounted::Trusted;
23use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
24use crate::dom::bindings::root::DomRoot;
25use crate::dom::globalscope::GlobalScope;
26use crate::script_runtime::CanGc;
27
28#[dom_struct]
29pub(crate) struct ReportingObserver {
30 reflector_: Reflector,
31
32 #[ignore_malloc_size_of = "Rc has unclear ownership"]
33 callback: Rc<ReportingObserverCallback>,
34 buffered: RefCell<bool>,
35 types: DomRefCell<Vec<DOMString>>,
36 report_queue: DomRefCell<Vec<Report>>,
37}
38
39impl ReportingObserver {
40 fn new_inherited(
41 callback: Rc<ReportingObserverCallback>,
42 options: &ReportingObserverOptions,
43 ) -> Self {
44 Self {
45 reflector_: Reflector::new(),
46 callback,
47 buffered: RefCell::new(options.buffered),
48 types: DomRefCell::new(options.types.clone().unwrap_or_default()),
49 report_queue: Default::default(),
50 }
51 }
52
53 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
54 pub(crate) fn new_with_proto(
55 callback: Rc<ReportingObserverCallback>,
56 options: &ReportingObserverOptions,
57 global: &GlobalScope,
58 proto: Option<HandleObject>,
59 can_gc: CanGc,
60 ) -> DomRoot<Self> {
61 reflect_dom_object_with_proto(
62 Box::new(Self::new_inherited(callback, options)),
63 global,
64 proto,
65 can_gc,
66 )
67 }
68
69 fn report_is_visible_to_reporting_observers(report: &Report) -> bool {
70 match report.type_.str() {
71 // https://w3c.github.io/webappsec-csp/#reporting
72 "csp-violation" => true,
73 _ => false,
74 }
75 }
76
77 /// <https://w3c.github.io/reporting/#add-report>
78 fn add_report_to_observer(&self, report: &Report) {
79 // Step 1. If report’s type is not visible to ReportingObservers, return.
80 if !Self::report_is_visible_to_reporting_observers(report) {
81 return;
82 }
83 // Step 2. If observer’s options has a non-empty types member which does not contain report’s type, return.
84 let types = self.types.borrow();
85 if !types.is_empty() && !types.contains(&report.type_) {
86 return;
87 }
88 // Step 3. Create a new Report r with type initialized to report’s type,
89 // url initialized to report’s url, and body initialized to report’s body.
90 let report = Report {
91 type_: report.type_.clone(),
92 url: report.url.clone(),
93 body: report.body.clone(),
94 destination: report.destination.clone(),
95 attempts: report.attempts,
96 timestamp: report.timestamp,
97 };
98 // Step 4. Append r to observer’s report queue.
99 self.report_queue.borrow_mut().push(report);
100 // Step 5. If the size of observer’s report queue is 1:
101 if self.report_queue.borrow().len() == 1 {
102 // Step 5.1. Let global be observer’s relevant global object.
103 let global = self.global();
104 // Step 5.2. Queue a task to § 4.4 Invoke reporting observers with notify list
105 // with a copy of global’s registered reporting observer list.
106 let observers_global = Trusted::new(&*global);
107 global.task_manager().dom_manipulation_task_source().queue(
108 task!(notify_reporting_observers: move || {
109 Self::invoke_reporting_observers_with_notify_list(
110 observers_global.root().registered_reporting_observers()
111 );
112 }),
113 );
114 }
115 }
116
117 /// <https://w3c.github.io/reporting/#notify-observers>
118 pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) {
119 // Step 1. For each ReportingObserver observer registered with scope,
120 // execute § 4.3 Add report to observer on report and observer.
121 for observer in global.registered_reporting_observers().iter() {
122 observer.add_report_to_observer(report);
123 }
124 // Step 2. Append report to scope’s report buffer.
125 global.append_report(report.clone());
126 // Step 3. Let type be report’s type.
127 // TODO(37328)
128 // Step 4. If scope’s report buffer now contains more than 100 reports with
129 // type equal to type, remove the earliest item with type equal to type in the report buffer.
130 // TODO(37328)
131 }
132
133 /// <https://w3c.github.io/reporting/#invoke-observers>
134 fn invoke_reporting_observers_with_notify_list(notify_list: Vec<DomRoot<ReportingObserver>>) {
135 // Step 1. For each ReportingObserver observer in notify list:
136 for observer in notify_list.iter() {
137 // Step 1.1. If observer’s report queue is empty, then continue.
138 if observer.report_queue.borrow().is_empty() {
139 continue;
140 }
141 // Step 1.2. Let reports be a copy of observer’s report queue
142 // Step 1.3. Empty observer’s report queue
143 let reports = std::mem::take(&mut *observer.report_queue.borrow_mut());
144 // Step 1.4. Invoke observer’s callback with « reports, observer » and "report",
145 // and with observer as the callback this value.
146 let _ = observer.callback.Call_(
147 &**observer,
148 reports,
149 observer,
150 ExceptionHandling::Report,
151 CanGc::note(),
152 );
153 }
154 }
155
156 /// <https://w3c.github.io/reporting/#generate-a-report>
157 fn generate_a_report(
158 global: &GlobalScope,
159 type_: DOMString,
160 url: Option<ServoUrl>,
161 body: Option<CSPViolationReportBody>,
162 destination: DOMString,
163 ) -> Report {
164 // Step 2. If url was not provided by the caller, let url be settings’s creation URL.
165 let url = url.unwrap_or(global.creation_url().clone());
166 // Step 3. Set url’s username to the empty string, and its password to null.
167 // Step 4. Set report’s url to the result of executing the URL serializer
168 // on url with the exclude fragment flag set.
169 let url = Self::strip_url_for_reports(url).into();
170 // Step 1. Let report be a new report object with its values initialized as follows:
171 // Step 5. Return report.
172 Report {
173 type_,
174 url,
175 body,
176 destination,
177 timestamp: Finite::wrap(
178 SystemTime::now()
179 .duration_since(UNIX_EPOCH)
180 .unwrap_or_default()
181 .as_millis() as f64,
182 ),
183 attempts: 0,
184 }
185 }
186
187 /// <https://w3c.github.io/reporting/#generate-and-queue-a-report>
188 pub(crate) fn generate_and_queue_a_report(
189 global: &GlobalScope,
190 type_: DOMString,
191 body: Option<CSPViolationReportBody>,
192 destination: DOMString,
193 ) {
194 // Step 1. Let settings be context’s relevant settings object.
195 // Step 2. Let report be the result of running generate a report with data, type, destination and settings.
196 let report = Self::generate_a_report(global, type_, None, body, destination);
197 // Step 3. If settings is given, then
198 // Step 3.1. Let scope be settings’s global object.
199 // Step 3.2. If scope is an object implementing WindowOrWorkerGlobalScope, then
200 // execute § 4.2 Notify reporting observers on scope with report with scope and report.
201 Self::notify_reporting_observers_on_scope(global, &report);
202 // Step 4. Append report to context’s reports.
203 global.append_report(report);
204 }
205
206 /// <https://w3c.github.io/webappsec-csp/#strip-url-for-use-in-reports>
207 pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String {
208 let scheme = url.scheme();
209 // Step 1: If url’s scheme is not an HTTP(S) scheme, then return url’s scheme.
210 if scheme != "https" && scheme != "http" {
211 return scheme.to_owned();
212 }
213 // Step 2: Set url’s fragment to the empty string.
214 url.set_fragment(None);
215 // Step 3: Set url’s username to the empty string.
216 let _ = url.set_username("");
217 // Step 4: Set url’s password to the empty string.
218 let _ = url.set_password(None);
219 // Step 5: Return the result of executing the URL serializer on url.
220 url.into_string()
221 }
222}
223
224impl ReportingObserverMethods<crate::DomTypeHolder> for ReportingObserver {
225 /// <https://w3c.github.io/reporting/#dom-reportingobserver-reportingobserver>
226 fn Constructor(
227 global: &GlobalScope,
228 proto: Option<HandleObject>,
229 can_gc: CanGc,
230 callback: Rc<ReportingObserverCallback>,
231 options: &ReportingObserverOptions,
232 ) -> DomRoot<ReportingObserver> {
233 // Step 1. Create a new ReportingObserver object observer.
234 // Step 2. Set observer’s callback to callback.
235 // Step 3. Set observer’s options to options.
236 // Step 4. Return observer.
237 ReportingObserver::new_with_proto(callback, options, global, proto, can_gc)
238 }
239
240 /// <https://w3c.github.io/reporting/#dom-reportingobserver-observe>
241 fn Observe(&self) {
242 // Step 1. Let global be the be the relevant global object of this.
243 let global = &self.global();
244 // Step 2. Append this to the global’s registered reporting observer list.
245 global.append_reporting_observer(self);
246 // Step 3. If this’s buffered option is false, return.
247 if !*self.buffered.borrow() {
248 return;
249 }
250 // Step 4. Set this’s buffered option to false.
251 *self.buffered.borrow_mut() = false;
252 // Step 5.For each report in global’s report buffer, queue a task to
253 // execute § 4.3 Add report to observer with report and this.
254 for report in global.buffered_reports() {
255 // TODO(37328): Figure out how to put this in a task
256 self.add_report_to_observer(&report);
257 }
258 }
259
260 /// <https://w3c.github.io/reporting/#dom-reportingobserver-disconnect>
261 fn Disconnect(&self) {
262 // Step 1. If this is not registered, return.
263 // Skipped, as this is handled in `remove_reporting_observer`
264
265 // Step 2. Let global be the relevant global object of this.
266 let global = &self.global();
267 // Step 3. Remove this from global’s registered reporting observer list.
268 global.remove_reporting_observer(self);
269 }
270
271 /// <https://w3c.github.io/reporting/#dom-reportingobserver-takerecords>
272 fn TakeRecords(&self) -> ReportList {
273 // Step 1. Let reports be a copy of this’s report queue.
274 // Step 2. Empty this’s report queue.
275 // Step 3. Return reports.
276 std::mem::take(&mut *self.report_queue.borrow_mut())
277 }
278}