mirror of
https://github.com/servo/servo.git
synced 2025-07-15 11:23:39 +01:00
The specification moved around lately with how it defines its reports and report bodies. They became dictionaries, but are currently missing some fields [1]. Most tests won't be passing yet, since the `Reporting-Endpoints` header isn't used yet. In fact, the specification leaves it up to the browser to figure out when to run this task [2]. I am not sure if there some background scheduling we can do here. Confirmed with content-security-policy/reporting-api/ report-to-directive-allowed-in-meta.https.sub.html that the callback is invoked. The test doesn't pass, since the `describe_scripted_caller` is empty for HTML elements. Thus the `source_file` is empty, whereas it should be equivalent to the current document URL. Part of #37328 Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com> [1]: https://github.com/w3c/reporting/issues/286 [2]: https://w3c.github.io/reporting/#report-delivery
278 lines
12 KiB
Rust
278 lines
12 KiB
Rust
/* 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<ReportingObserverCallback>,
|
||
buffered: RefCell<bool>,
|
||
types: DomRefCell<Vec<DOMString>>,
|
||
report_queue: DomRefCell<Vec<Report>>,
|
||
}
|
||
|
||
impl ReportingObserver {
|
||
fn new_inherited(
|
||
callback: Rc<ReportingObserverCallback>,
|
||
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<ReportingObserverCallback>,
|
||
options: &ReportingObserverOptions,
|
||
global: &GlobalScope,
|
||
proto: Option<HandleObject>,
|
||
can_gc: CanGc,
|
||
) -> DomRoot<Self> {
|
||
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,
|
||
}
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#add-report>
|
||
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()
|
||
);
|
||
}),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#notify-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)
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#invoke-observers>
|
||
fn invoke_reporting_observers_with_notify_list(notify_list: Vec<DomRoot<ReportingObserver>>) {
|
||
// 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(),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#generate-a-report>
|
||
fn generate_a_report(
|
||
global: &GlobalScope,
|
||
type_: DOMString,
|
||
url: Option<ServoUrl>,
|
||
body: Option<CSPViolationReportBody>,
|
||
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,
|
||
}
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#generate-and-queue-a-report>
|
||
pub(crate) fn generate_and_queue_a_report(
|
||
global: &GlobalScope,
|
||
type_: DOMString,
|
||
body: Option<CSPViolationReportBody>,
|
||
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);
|
||
}
|
||
|
||
/// <https://w3c.github.io/webappsec-csp/#strip-url-for-use-in-reports>
|
||
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<crate::DomTypeHolder> for ReportingObserver {
|
||
/// <https://w3c.github.io/reporting/#dom-reportingobserver-reportingobserver>
|
||
fn Constructor(
|
||
global: &GlobalScope,
|
||
proto: Option<HandleObject>,
|
||
can_gc: CanGc,
|
||
callback: Rc<ReportingObserverCallback>,
|
||
options: &ReportingObserverOptions,
|
||
) -> DomRoot<ReportingObserver> {
|
||
// 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)
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#dom-reportingobserver-observe>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#dom-reportingobserver-disconnect>
|
||
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);
|
||
}
|
||
|
||
/// <https://w3c.github.io/reporting/#dom-reportingobserver-takerecords>
|
||
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())
|
||
}
|
||
}
|