/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::RefCell; use std::rc::Rc; use std::time::{SystemTime, UNIX_EPOCH}; use dom_struct::dom_struct; use js::rust::HandleObject; use script_bindings::str::DOMString; use servo_url::ServoUrl; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::CSPViolationReportBodyBinding::CSPViolationReportBody; use crate::dom::bindings::codegen::Bindings::ReportingObserverBinding::{ Report, ReportList, ReportingObserverCallback, ReportingObserverMethods, ReportingObserverOptions, }; use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; use crate::dom::bindings::root::DomRoot; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; #[dom_struct] pub(crate) struct ReportingObserver { reflector_: Reflector, #[ignore_malloc_size_of = "Rc has unclear ownership"] callback: Rc, buffered: RefCell, types: DomRefCell>, report_queue: DomRefCell>, } impl ReportingObserver { fn new_inherited( callback: Rc, options: &ReportingObserverOptions, ) -> Self { Self { reflector_: Reflector::new(), callback, buffered: RefCell::new(options.buffered), types: DomRefCell::new(options.types.clone().unwrap_or_default()), report_queue: Default::default(), } } #[cfg_attr(crown, allow(crown::unrooted_must_root))] pub(crate) fn new_with_proto( callback: Rc, options: &ReportingObserverOptions, global: &GlobalScope, proto: Option, can_gc: CanGc, ) -> DomRoot { reflect_dom_object_with_proto( Box::new(Self::new_inherited(callback, options)), global, proto, can_gc, ) } fn report_is_visible_to_reporting_observers(report: &Report) -> bool { match report.type_.str() { // https://w3c.github.io/webappsec-csp/#reporting "csp-violation" => true, _ => false, } } /// fn add_report_to_observer(&self, report: &Report) { // Step 1. If report’s type is not visible to ReportingObservers, return. if !Self::report_is_visible_to_reporting_observers(report) { return; } // Step 2. If observer’s options has a non-empty types member which does not contain report’s type, return. let types = self.types.borrow(); if !types.is_empty() && !types.contains(&report.type_) { return; } // Step 3. Create a new Report r with type initialized to report’s type, // url initialized to report’s url, and body initialized to report’s body. let report = Report { type_: report.type_.clone(), url: report.url.clone(), body: report.body.clone(), destination: report.destination.clone(), attempts: report.attempts, timestamp: report.timestamp, }; // Step 4. Append r to observer’s report queue. self.report_queue.borrow_mut().push(report); // Step 5. If the size of observer’s report queue is 1: if self.report_queue.borrow().len() == 1 { // Step 5.1. Let global be observer’s relevant global object. let global = self.global(); // Step 5.2. Queue a task to § 4.4 Invoke reporting observers with notify list // with a copy of global’s registered reporting observer list. let observers_global = Trusted::new(&*global); global.task_manager().dom_manipulation_task_source().queue( task!(notify_reporting_observers: move || { Self::invoke_reporting_observers_with_notify_list( observers_global.root().registered_reporting_observers() ); }), ); } } /// pub(crate) fn notify_reporting_observers_on_scope(global: &GlobalScope, report: &Report) { // Step 1. For each ReportingObserver observer registered with scope, // execute § 4.3 Add report to observer on report and observer. for observer in global.registered_reporting_observers().iter() { observer.add_report_to_observer(report); } // Step 2. Append report to scope’s report buffer. global.append_report(report.clone()); // Step 3. Let type be report’s type. // TODO(37328) // Step 4. If scope’s report buffer now contains more than 100 reports with // type equal to type, remove the earliest item with type equal to type in the report buffer. // TODO(37328) } /// fn invoke_reporting_observers_with_notify_list(notify_list: Vec>) { // Step 1. For each ReportingObserver observer in notify list: for observer in notify_list.iter() { // Step 1.1. If observer’s report queue is empty, then continue. if observer.report_queue.borrow().is_empty() { continue; } // Step 1.2. Let reports be a copy of observer’s report queue // Step 1.3. Empty observer’s report queue let reports = std::mem::take(&mut *observer.report_queue.borrow_mut()); // Step 1.4. Invoke observer’s callback with « reports, observer » and "report", // and with observer as the callback this value. let _ = observer.callback.Call_( &**observer, reports, observer, ExceptionHandling::Report, CanGc::note(), ); } } /// fn generate_a_report( global: &GlobalScope, type_: DOMString, url: Option, body: Option, destination: DOMString, ) -> Report { // Step 2. If url was not provided by the caller, let url be settings’s creation URL. let url = url.unwrap_or(global.creation_url().clone()); // Step 3. Set url’s username to the empty string, and its password to null. // Step 4. Set report’s url to the result of executing the URL serializer // on url with the exclude fragment flag set. let url = Self::strip_url_for_reports(url).into(); // Step 1. Let report be a new report object with its values initialized as follows: // Step 5. Return report. Report { type_, url, body, destination, timestamp: Finite::wrap( SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_millis() as f64, ), attempts: 0, } } /// pub(crate) fn generate_and_queue_a_report( global: &GlobalScope, type_: DOMString, body: Option, destination: DOMString, ) { // Step 1. Let settings be context’s relevant settings object. // Step 2. Let report be the result of running generate a report with data, type, destination and settings. let report = Self::generate_a_report(global, type_, None, body, destination); // Step 3. If settings is given, then // Step 3.1. Let scope be settings’s global object. // Step 3.2. If scope is an object implementing WindowOrWorkerGlobalScope, then // execute § 4.2 Notify reporting observers on scope with report with scope and report. Self::notify_reporting_observers_on_scope(global, &report); // Step 4. Append report to context’s reports. global.append_report(report); } /// pub(crate) fn strip_url_for_reports(mut url: ServoUrl) -> String { let scheme = url.scheme(); // Step 1: If url’s scheme is not an HTTP(S) scheme, then return url’s scheme. if scheme != "https" && scheme != "http" { return scheme.to_owned(); } // Step 2: Set url’s fragment to the empty string. url.set_fragment(None); // Step 3: Set url’s username to the empty string. let _ = url.set_username(""); // Step 4: Set url’s password to the empty string. let _ = url.set_password(None); // Step 5: Return the result of executing the URL serializer on url. url.into_string() } } impl ReportingObserverMethods for ReportingObserver { /// fn Constructor( global: &GlobalScope, proto: Option, can_gc: CanGc, callback: Rc, options: &ReportingObserverOptions, ) -> DomRoot { // Step 1. Create a new ReportingObserver object observer. // Step 2. Set observer’s callback to callback. // Step 3. Set observer’s options to options. // Step 4. Return observer. ReportingObserver::new_with_proto(callback, options, global, proto, can_gc) } /// fn Observe(&self) { // Step 1. Let global be the be the relevant global object of this. let global = &self.global(); // Step 2. Append this to the global’s registered reporting observer list. global.append_reporting_observer(self); // Step 3. If this’s buffered option is false, return. if !*self.buffered.borrow() { return; } // Step 4. Set this’s buffered option to false. *self.buffered.borrow_mut() = false; // Step 5.For each report in global’s report buffer, queue a task to // execute § 4.3 Add report to observer with report and this. for report in global.buffered_reports() { // TODO(37328): Figure out how to put this in a task self.add_report_to_observer(&report); } } /// fn Disconnect(&self) { // Step 1. If this is not registered, return. // Skipped, as this is handled in `remove_reporting_observer` // Step 2. Let global be the relevant global object of this. let global = &self.global(); // Step 3. Remove this from global’s registered reporting observer list. global.remove_reporting_observer(self); } /// fn TakeRecords(&self) -> ReportList { // Step 1. Let reports be a copy of this’s report queue. // Step 2. Empty this’s report queue. // Step 3. Return reports. std::mem::take(&mut *self.report_queue.borrow_mut()) } }