servo/components/script/dom/performance.rs
ddh 26007fddd3 refactored performance timing to align with updated spec
refactoring with ResourceFetchMetadata

implemented deprecated window.timing functionality

created ResourceTimingListener trait

fixed w3c links in navigation timing

updated include.ini to run resource timing tests on ci
2018-11-20 16:21:32 +00:00

408 lines
14 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 crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::PerformanceBinding;
use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceEntryList as DOMPerformanceEntryList;
use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{
DOMHighResTimeStamp, PerformanceMethods,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::performanceentry::PerformanceEntry;
use crate::dom::performancemark::PerformanceMark;
use crate::dom::performancemeasure::PerformanceMeasure;
use crate::dom::performancenavigationtiming::PerformanceNavigationTiming;
use crate::dom::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
use crate::dom::window::Window;
use dom_struct::dom_struct;
use metrics::ToMs;
use std::cell::Cell;
use std::cmp::Ordering;
const INVALID_ENTRY_NAMES: &'static [&'static str] = &[
"navigationStart",
"unloadEventStart",
"unloadEventEnd",
"redirectStart",
"redirectEnd",
"fetchStart",
"domainLookupStart",
"domainLookupEnd",
"connectStart",
"connectEnd",
"secureConnectionStart",
"requestStart",
"responseStart",
"responseEnd",
"domLoading",
"domInteractive",
"domContentLoadedEventStart",
"domContentLoadedEventEnd",
"domComplete",
"loadEventStart",
"loadEventEnd",
];
/// Implementation of a list of PerformanceEntry items shared by the
/// Performance and PerformanceObserverEntryList interfaces implementations.
#[derive(JSTraceable, MallocSizeOf)]
pub struct PerformanceEntryList {
entries: DOMPerformanceEntryList,
}
impl PerformanceEntryList {
pub fn new(entries: DOMPerformanceEntryList) -> Self {
PerformanceEntryList { entries }
}
pub fn get_entries_by_name_and_type(
&self,
name: Option<DOMString>,
entry_type: Option<DOMString>,
) -> Vec<DomRoot<PerformanceEntry>> {
let mut res = self
.entries
.iter()
.filter(|e| {
name.as_ref().map_or(true, |name_| *e.name() == *name_) && entry_type
.as_ref()
.map_or(true, |type_| *e.entry_type() == *type_)
})
.map(|e| e.clone())
.collect::<Vec<DomRoot<PerformanceEntry>>>();
res.sort_by(|a, b| {
a.start_time()
.partial_cmp(&b.start_time())
.unwrap_or(Ordering::Equal)
});
res
}
pub fn clear_entries_by_name_and_type(
&mut self,
name: Option<DOMString>,
entry_type: Option<DOMString>,
) {
self.entries.retain(|e| {
name.as_ref().map_or(true, |name_| *e.name() == *name_) && entry_type
.as_ref()
.map_or(true, |type_| *e.entry_type() == *type_)
});
}
fn get_last_entry_start_time_with_name_and_type(
&self,
name: DOMString,
entry_type: DOMString,
) -> f64 {
match self
.entries
.iter()
.rev()
.find(|e| *e.entry_type() == *entry_type && *e.name() == *name)
{
Some(entry) => entry.start_time(),
None => 0.,
}
}
}
impl IntoIterator for PerformanceEntryList {
type Item = DomRoot<PerformanceEntry>;
type IntoIter = ::std::vec::IntoIter<DomRoot<PerformanceEntry>>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
#[derive(JSTraceable, MallocSizeOf)]
struct PerformanceObserver {
observer: DomRoot<DOMPerformanceObserver>,
entry_types: Vec<DOMString>,
}
#[dom_struct]
pub struct Performance {
eventtarget: EventTarget,
entries: DomRefCell<PerformanceEntryList>,
observers: DomRefCell<Vec<PerformanceObserver>>,
pending_notification_observers_task: Cell<bool>,
navigation_start_precise: u64,
}
impl Performance {
fn new_inherited(navigation_start_precise: u64) -> Performance {
Performance {
eventtarget: EventTarget::new_inherited(),
entries: DomRefCell::new(PerformanceEntryList::new(Vec::new())),
observers: DomRefCell::new(Vec::new()),
pending_notification_observers_task: Cell::new(false),
navigation_start_precise,
}
}
pub fn new(global: &GlobalScope, navigation_start_precise: u64) -> DomRoot<Performance> {
reflect_dom_object(
Box::new(Performance::new_inherited(navigation_start_precise)),
global,
PerformanceBinding::Wrap,
)
}
/// Add a PerformanceObserver to the list of observers with a set of
/// observed entry types.
pub fn add_observer(
&self,
observer: &DOMPerformanceObserver,
entry_types: Vec<DOMString>,
buffered: bool,
) {
if buffered {
let entries = self.entries.borrow();
let mut new_entries = entry_types
.iter()
.flat_map(|e| entries.get_entries_by_name_and_type(None, Some(e.clone())))
.collect::<DOMPerformanceEntryList>();
let mut obs_entries = observer.entries();
obs_entries.append(&mut new_entries);
observer.set_entries(obs_entries);
}
let mut observers = self.observers.borrow_mut();
match observers.iter().position(|o| *o.observer == *observer) {
// If the observer is already in the list, we only update the observed
// entry types.
Some(p) => observers[p].entry_types = entry_types,
// Otherwise, we create and insert the new PerformanceObserver.
None => observers.push(PerformanceObserver {
observer: DomRoot::from_ref(observer),
entry_types,
}),
};
}
/// Remove a PerformanceObserver from the list of observers.
pub fn remove_observer(&self, observer: &DOMPerformanceObserver) {
let mut observers = self.observers.borrow_mut();
let index = match observers.iter().position(|o| &(*o.observer) == observer) {
Some(p) => p,
None => return,
};
observers.remove(index);
}
/// Queue a notification for each performance observer interested in
/// this type of performance entry and queue a low priority task to
/// notify the observers if no other notification task is already queued.
///
/// Algorithm spec:
/// <https://w3c.github.io/performance-timeline/#queue-a-performanceentry>
pub fn queue_entry(&self, entry: &PerformanceEntry, add_to_performance_entries_buffer: bool) {
// Steps 1-3.
// Add the performance entry to the list of performance entries that have not
// been notified to each performance observer owner, filtering the ones it's
// interested in.
for o in self
.observers
.borrow()
.iter()
.filter(|o| o.entry_types.contains(entry.entry_type()))
{
o.observer.queue_entry(entry);
}
// Step 4.
// If the "add to performance entry buffer flag" is set, add the
// new entry to the buffer.
if add_to_performance_entries_buffer {
self.entries
.borrow_mut()
.entries
.push(DomRoot::from_ref(entry));
}
// Step 5.
// If there is already a queued notification task, we just bail out.
if self.pending_notification_observers_task.get() {
return;
}
// Step 6.
// Queue a new notification task.
self.pending_notification_observers_task.set(true);
let task_source = self.global().performance_timeline_task_source();
task_source.queue_notification(&self.global());
}
/// Observers notifications task.
///
/// Algorithm spec (step 7):
/// <https://w3c.github.io/performance-timeline/#queue-a-performanceentry>
pub fn notify_observers(&self) {
// Step 7.1.
self.pending_notification_observers_task.set(false);
// Step 7.2.
// We have to operate over a copy of the performance observers to avoid
// the risk of an observer's callback modifying the list of registered
// observers.
let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
.observers
.borrow()
.iter()
.map(|o| {
DOMPerformanceObserver::new(
&self.global(),
o.observer.callback(),
o.observer.entries(),
)
})
.collect();
// Step 7.3.
for o in observers.iter() {
o.notify();
}
}
fn now(&self) -> f64 {
(time::precise_time_ns() - self.navigation_start_precise).to_ms()
}
}
impl PerformanceMethods for Performance {
// FIXME(avada): this should be deprecated in the future, but some sites still use it
// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#performance-timing-attribute
fn Timing(&self) -> DomRoot<PerformanceNavigationTiming> {
let entries = self.GetEntriesByType(DOMString::from("navigation"));
if entries.len() > 0 {
return DomRoot::from_ref(
entries[0]
.downcast::<PerformanceNavigationTiming>()
.unwrap(),
);
}
unreachable!("Are we trying to expose Performance.timing in workers?");
}
// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HighResolutionTime/Overview.html#dom-performance-now
fn Now(&self) -> DOMHighResTimeStamp {
Finite::wrap(self.now())
}
// https://www.w3.org/TR/hr-time-2/#dom-performance-timeorigin
fn TimeOrigin(&self) -> DOMHighResTimeStamp {
Finite::wrap(self.navigation_start_precise as f64)
}
// https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentries
fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
self.entries
.borrow()
.get_entries_by_name_and_type(None, None)
}
// https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentriesbytype
fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
self.entries
.borrow()
.get_entries_by_name_and_type(None, Some(entry_type))
}
// https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentriesbyname
fn GetEntriesByName(
&self,
name: DOMString,
entry_type: Option<DOMString>,
) -> Vec<DomRoot<PerformanceEntry>> {
self.entries
.borrow()
.get_entries_by_name_and_type(Some(name), entry_type)
}
// https://w3c.github.io/user-timing/#dom-performance-mark
fn Mark(&self, mark_name: DOMString) -> Fallible<()> {
let global = self.global();
// Step 1.
if global.is::<Window>() && INVALID_ENTRY_NAMES.contains(&mark_name.as_ref()) {
return Err(Error::Syntax);
}
// Steps 2 to 6.
let entry = PerformanceMark::new(&global, mark_name, self.now(), 0.);
// Steps 7 and 8.
self.queue_entry(
&entry.upcast::<PerformanceEntry>(),
true, /* buffer performance entry */
);
// Step 9.
Ok(())
}
// https://w3c.github.io/user-timing/#dom-performance-clearmarks
fn ClearMarks(&self, mark_name: Option<DOMString>) {
self.entries
.borrow_mut()
.clear_entries_by_name_and_type(mark_name, Some(DOMString::from("mark")));
}
// https://w3c.github.io/user-timing/#dom-performance-measure
fn Measure(
&self,
measure_name: DOMString,
start_mark: Option<DOMString>,
end_mark: Option<DOMString>,
) -> Fallible<()> {
// Steps 1 and 2.
let end_time = match end_mark {
Some(name) => self
.entries
.borrow()
.get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name),
None => self.now(),
};
// Step 3.
let start_time = match start_mark {
Some(name) => self
.entries
.borrow()
.get_last_entry_start_time_with_name_and_type(DOMString::from("mark"), name),
None => 0.,
};
// Steps 4 to 8.
let entry = PerformanceMeasure::new(
&self.global(),
measure_name,
start_time,
end_time - start_time,
);
// Step 9 and 10.
self.queue_entry(
&entry.upcast::<PerformanceEntry>(),
true, /* buffer performance entry */
);
// Step 11.
Ok(())
}
// https://w3c.github.io/user-timing/#dom-performance-clearmeasures
fn ClearMeasures(&self, measure_name: Option<DOMString>) {
self.entries
.borrow_mut()
.clear_entries_by_name_and_type(measure_name, Some(DOMString::from("measure")));
}
}