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