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}