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