Skip to content

Commit

Permalink
Auto merge of #18028 - ferjm:performance.timeline, r=jdm
Browse files Browse the repository at this point in the history
Performance Timeline API

[Performance Timeline API](https://www.w3.org/TR/performance-timeline-2/) implementation.

This API is required to allow DOM access to the [Paint Timing API](https://wicg.github.io/paint-timing/#example) metrics implemented in #17256. Unfortunately, I couldn't test it properly, as its usage depends on other APIs like the Paint Timing, User Timing, Resource Timing or Server Timing APIs. I'll work in the integration with the Paint Timing API next.

- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [ ] There are [WPTs](https://github.com/servo/servo/tree/master/tests/wpt/web-platform-tests/performance-timeline) for this API, however they depend on the implementation of the User Timing and the Resource Timing APIs, which I'll hopefully be implementing soon.

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/18028)
<!-- Reviewable:end -->
  • Loading branch information
bors-servo committed Aug 17, 2017
2 parents cc86ca2 + 52348f1 commit 6988c74
Show file tree
Hide file tree
Showing 18 changed files with 599 additions and 7 deletions.
2 changes: 1 addition & 1 deletion components/script/dom/bindings/str.rs
Expand Up @@ -122,7 +122,7 @@ pub fn is_token(s: &[u8]) -> bool {
///
/// [idl]: https://heycam.github.io/webidl/#idl-DOMString
///
/// Cenceptually, a DOMString has the same value space as a JavaScript String,
/// Conceptually, a DOMString has the same value space as a JavaScript String,
/// i.e., an array of 16-bit *code units* representing UTF-16, potentially with
/// unpaired surrogates present (also sometimes called WTF-16).
///
Expand Down
3 changes: 3 additions & 0 deletions components/script/dom/mod.rs
Expand Up @@ -397,6 +397,9 @@ pub mod paintrenderingcontext2d;
pub mod paintsize;
pub mod paintworkletglobalscope;
pub mod performance;
pub mod performanceentry;
pub mod performanceobserver;
pub mod performanceobserverentrylist;
pub mod performancetiming;
pub mod permissions;
pub mod permissionstatus;
Expand Down
197 changes: 194 additions & 3 deletions components/script/dom/performance.rs
Expand Up @@ -2,22 +2,79 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::PerformanceBinding;
use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
use dom::bindings::codegen::Bindings::PerformanceBinding::{DOMHighResTimeStamp, PerformanceMethods};
use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceEntryList as DOMPerformanceEntryList;
use dom::bindings::js::{JS, Root};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::performanceentry::PerformanceEntry;
use dom::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
use dom::performancetiming::PerformanceTiming;
use dom::window::Window;
use dom_struct::dom_struct;
use script_thread::{Runnable, ScriptThread};
use std::cell::Cell;
use time;

pub type DOMHighResTimeStamp = Finite<f64>;
/// Implementation of a list of PerformanceEntry items shared by the
/// Performance and PerformanceObserverEntryList interfaces implementations.
#[derive(HeapSizeOf, JSTraceable)]
pub struct PerformanceEntryList {
entries: DOMPerformanceEntryList,
}

impl PerformanceEntryList {
pub fn new(entries: DOMPerformanceEntryList) -> Self {
PerformanceEntryList {
entries,
}
}

pub fn get_entries(&self) -> Vec<Root<PerformanceEntry>> {
self.entries.clone()
}

pub fn get_entries_by_type(&self, entry_type: DOMString) -> Vec<Root<PerformanceEntry>> {
self.entries.iter().filter(|e| *e.entry_type() == entry_type)
.map(|e| e.clone())
.collect()
}

pub fn get_entries_by_name(&self, name: DOMString, entry_type: Option<DOMString>)
-> Vec<Root<PerformanceEntry>> {
self.entries.iter().filter(|e|
*e.name() == name &&
entry_type.as_ref().map_or(true, |type_| *e.entry_type() == *type_)
).map(|e| e.clone()).collect()
}
}

impl IntoIterator for PerformanceEntryList {
type Item = Root<PerformanceEntry>;
type IntoIter = ::std::vec::IntoIter<Root<PerformanceEntry>>;

fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}

#[derive(HeapSizeOf, JSTraceable)]
struct PerformanceObserver {
observer: Root<DOMPerformanceObserver>,
entry_types: Vec<DOMString>,
}

#[dom_struct]
pub struct Performance {
reflector_: Reflector,
timing: JS<PerformanceTiming>,
entries: DOMRefCell<PerformanceEntryList>,
observers: DOMRefCell<Vec<PerformanceObserver>>,
pending_notification_observers_task: Cell<bool>,
}

impl Performance {
Expand All @@ -29,6 +86,9 @@ impl Performance {
timing: JS::from_ref(&*PerformanceTiming::new(window,
navigation_start,
navigation_start_precise)),
entries: DOMRefCell::new(PerformanceEntryList::new(Vec::new())),
observers: DOMRefCell::new(Vec::new()),
pending_notification_observers_task: Cell::new(false),
}
}

Expand All @@ -41,6 +101,121 @@ impl Performance {
window,
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>) {
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: Root::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
///
/// XXX This should be called at some point by the User Timing, Resource
/// Timing, Server Timing and Paint Timing APIs.
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(Root::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 global = self.global();
let window = global.as_window();
let task_source = window.performance_timeline_task_source();
task_source.queue_notification(self, window);
}

/// Observers notifications task.
///
/// Algorithm spec (step 7):
/// https://w3c.github.io/performance-timeline/#queue-a-performanceentry
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<Root<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();
}
}
}

pub struct NotifyPerformanceObserverRunnable {
owner: Trusted<Performance>,
}

impl NotifyPerformanceObserverRunnable {
pub fn new(owner: Trusted<Performance>) -> Self {
NotifyPerformanceObserverRunnable {
owner,
}
}
}

impl Runnable for NotifyPerformanceObserverRunnable {
fn name(&self) -> &'static str { "NotifyPerformanceObserverRunnable" }

fn main_thread_handler(self: Box<NotifyPerformanceObserverRunnable>,
_: &ScriptThread) {
self.owner.root().notify_observers();
}
}

impl PerformanceMethods for Performance {
Expand All @@ -55,4 +230,20 @@ impl PerformanceMethods for Performance {
let now = (time::precise_time_ns() as f64 - nav_start) / 1000000 as f64;
Finite::wrap(now)
}

// https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentries
fn GetEntries(&self) -> Vec<Root<PerformanceEntry>> {
self.entries.borrow().get_entries()
}

// https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentriesbytype
fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<Root<PerformanceEntry>> {
self.entries.borrow().get_entries_by_type(entry_type)
}

// https://www.w3.org/TR/performance-timeline-2/#dom-performance-getentriesbyname
fn GetEntriesByName(&self, name: DOMString, entry_type: Option<DOMString>)
-> Vec<Root<PerformanceEntry>> {
self.entries.borrow().get_entries_by_name(name, entry_type)
}
}
76 changes: 76 additions & 0 deletions components/script/dom/performanceentry.rs
@@ -0,0 +1,76 @@
/* 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 http://mozilla.org/MPL/2.0/. */

use dom::bindings::codegen::Bindings::PerformanceEntryBinding;
use dom::bindings::codegen::Bindings::PerformanceEntryBinding::PerformanceEntryMethods;
use dom::bindings::js::Root;
use dom::bindings::num::Finite;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;

#[dom_struct]
pub struct PerformanceEntry {
reflector_: Reflector,
name: DOMString,
entry_type: DOMString,
start_time: f64,
duration: f64,
}

impl PerformanceEntry {
fn new_inherited(name: DOMString,
entry_type: DOMString,
start_time: f64,
duration: f64) -> PerformanceEntry {
PerformanceEntry {
reflector_: Reflector::new(),
name,
entry_type,
start_time,
duration,
}
}

#[allow(unrooted_must_root)]
pub fn new(global: &GlobalScope,
name: DOMString,
entry_type: DOMString,
start_time: f64,
duration: f64) -> Root<PerformanceEntry> {
let entry = PerformanceEntry::new_inherited(name, entry_type, start_time, duration);
reflect_dom_object(box entry, global, PerformanceEntryBinding::Wrap)
}

pub fn entry_type(&self) -> &DOMString {
&self.entry_type
}

pub fn name(&self) -> &DOMString {
&self.name
}
}

impl PerformanceEntryMethods for PerformanceEntry {
// https://w3c.github.io/performance-timeline/#dom-performanceentry-name
fn Name(&self) -> DOMString {
DOMString::from(self.name.clone())
}

// https://w3c.github.io/performance-timeline/#dom-performanceentry-entrytype
fn EntryType(&self) -> DOMString {
DOMString::from(self.entry_type.clone())
}

// https://w3c.github.io/performance-timeline/#dom-performanceentry-starttime
fn StartTime(&self) -> Finite<f64> {
Finite::wrap(self.start_time)
}

// https://w3c.github.io/performance-timeline/#dom-performanceentry-duration
fn Duration(&self) -> Finite<f64> {
Finite::wrap(self.duration)
}
}

0 comments on commit 6988c74

Please sign in to comment.