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}