Make Performance Timeline API work in Workers

This commit is contained in:
Fernando Jiménez Moreno 2017-08-30 17:52:18 +02:00
parent 449ffb23ac
commit fb843c251a
20 changed files with 146 additions and 128 deletions

View file

@ -154,6 +154,7 @@ impl Formattable for ProfilerCategory {
ProfilerCategory::ScriptExitFullscreen => "Script Exit Fullscreen",
ProfilerCategory::ScriptWebVREvent => "Script WebVR Event",
ProfilerCategory::ScriptWorkletEvent => "Script Worklet Event",
ProfilerCategory::ScriptPerformanceEvent => "Script Performance Event",
ProfilerCategory::TimeToFirstPaint => "Time To First Paint",
ProfilerCategory::TimeToFirstContentfulPaint => "Time To First Contentful Paint",
ProfilerCategory::ApplicationHeartbeat => "Application Heartbeat",

View file

@ -90,6 +90,7 @@ pub enum ProfilerCategory {
ScriptExitFullscreen = 0x78,
ScriptWebVREvent = 0x79,
ScriptWorkletEvent = 0x7a,
ScriptPerformanceEvent = 0x7b,
TimeToFirstPaint = 0x80,
TimeToFirstContentfulPaint = 0x81,
ApplicationHeartbeat = 0x90,

View file

@ -1586,7 +1586,7 @@ impl Document {
self.running_animation_callbacks.set(true);
let was_faking_animation_frames = self.is_faking_animation_frames();
let timing = self.window.Performance().Now();
let timing = self.global().performance().Now();
for (_, callback) in animation_frame_list.drain(..) {
if let Some(callback) = callback {

View file

@ -5,6 +5,7 @@
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
use dom::bindings::conversions::root_from_object;
use dom::bindings::error::{ErrorInfo, report_pending_exception};
use dom::bindings::inheritance::Castable;
@ -17,6 +18,7 @@ use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use dom::errorevent::ErrorEvent;
use dom::event::{Event, EventBubbles, EventCancelable, EventStatus};
use dom::eventtarget::EventTarget;
use dom::performance::Performance;
use dom::window::Window;
use dom::workerglobalscope::WorkerGlobalScope;
use dom::workletglobalscope::WorkletGlobalScope;
@ -46,6 +48,7 @@ use std::collections::hash_map::Entry;
use std::ffi::CString;
use task_source::file_reading::FileReadingTaskSource;
use task_source::networking::NetworkingTaskSource;
use task_source::performance_timeline::PerformanceTimelineTaskSource;
use time::{Timespec, get_time};
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle};
use timers::{OneshotTimers, TimerCallback};
@ -569,6 +572,29 @@ impl GlobalScope {
pub fn incumbent() -> Option<Root<Self>> {
incumbent_global()
}
pub fn performance(&self) -> Root<Performance> {
if let Some(window) = self.downcast::<Window>() {
return window.Performance();
}
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
return worker.Performance();
}
unreachable!();
}
/// Channel to send messages to the performance timeline task source
/// of this global scope.
pub fn performance_timeline_task_source(&self) -> PerformanceTimelineTaskSource {
if let Some(window) = self.downcast::<Window>() {
return window.performance_timeline_task_source();
}
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
return worker.performance_timeline_task_source();
}
unreachable!();
}
}
fn timestamp_in_ms(time: Timespec) -> u64 {

View file

@ -6,17 +6,17 @@ use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::PerformanceBinding;
use dom::bindings::codegen::Bindings::PerformanceBinding::{DOMHighResTimeStamp, PerformanceMethods};
use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceEntryList as DOMPerformanceEntryList;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::num::Finite;
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::globalscope::GlobalScope;
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 std::cmp::Ordering;
use time;
@ -64,34 +64,40 @@ struct PerformanceObserver {
#[dom_struct]
pub struct Performance {
reflector_: Reflector,
timing: JS<PerformanceTiming>,
timing: Option<JS<PerformanceTiming>>,
entries: DOMRefCell<PerformanceEntryList>,
observers: DOMRefCell<Vec<PerformanceObserver>>,
pending_notification_observers_task: Cell<bool>,
navigation_start_precise: f64,
}
impl Performance {
fn new_inherited(window: &Window,
fn new_inherited(global: &GlobalScope,
navigation_start: u64,
navigation_start_precise: f64) -> Performance {
Performance {
reflector_: Reflector::new(),
timing: JS::from_ref(&*PerformanceTiming::new(window,
navigation_start,
navigation_start_precise)),
timing: if global.is::<Window>() {
Some(JS::from_ref(&*PerformanceTiming::new(global.as_window(),
navigation_start,
navigation_start_precise)))
} else {
None
},
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(window: &Window,
pub fn new(global: &GlobalScope,
navigation_start: u64,
navigation_start_precise: f64) -> Root<Performance> {
reflect_dom_object(box Performance::new_inherited(window,
reflect_dom_object(box Performance::new_inherited(global,
navigation_start,
navigation_start_precise),
window,
global,
PerformanceBinding::Wrap)
}
@ -168,17 +174,15 @@ impl Performance {
// 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);
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
fn notify_observers(&self) {
pub fn notify_observers(&self) {
// Step 7.1.
self.pending_notification_observers_task.set(false);
@ -200,34 +204,21 @@ impl Performance {
}
}
pub struct NotifyPerformanceObserverRunnable {
owner: Trusted<Performance>,
}
impl NotifyPerformanceObserverRunnable {
pub fn new(owner: Trusted<Performance>) -> Self {
NotifyPerformanceObserverRunnable {
owner,
}
}
}
impl Runnable for NotifyPerformanceObserverRunnable {
fn main_thread_handler(self: Box<NotifyPerformanceObserverRunnable>,
_: &ScriptThread) {
self.owner.root().notify_observers();
}
}
impl PerformanceMethods for Performance {
// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#performance-timing-attribute
fn Timing(&self) -> Root<PerformanceTiming> {
Root::from_ref(&*self.timing)
match self.timing {
Some(ref timing) => Root::from_ref(&*timing),
None => 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 {
let nav_start = self.timing.navigation_start_precise();
let nav_start = match self.timing {
Some(ref timing) => timing.navigation_start_precise(),
None => self.navigation_start_precise,
};
let now = (time::precise_time_ns() as f64 - nav_start) / 1000000 as f64;
Finite::wrap(now)
}

View file

@ -9,7 +9,6 @@ use dom::bindings::codegen::Bindings::PerformanceObserverBinding;
use dom::bindings::codegen::Bindings::PerformanceObserverBinding::PerformanceObserverCallback;
use dom::bindings::codegen::Bindings::PerformanceObserverBinding::PerformanceObserverInit;
use dom::bindings::codegen::Bindings::PerformanceObserverBinding::PerformanceObserverMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
use dom::bindings::error::{Error, Fallible};
use dom::bindings::js::Root;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
@ -51,8 +50,8 @@ impl PerformanceObserver {
#[allow(unrooted_must_root)]
pub fn new(global: &GlobalScope,
callback: Rc<PerformanceObserverCallback>,
entries: DOMPerformanceEntryList)
callback: Rc<PerformanceObserverCallback>,
entries: DOMPerformanceEntryList)
-> Root<PerformanceObserver> {
let observer = PerformanceObserver::new_inherited(callback, DOMRefCell::new(entries));
reflect_dom_object(box observer, global, PerformanceObserverBinding::Wrap)
@ -110,15 +109,15 @@ impl PerformanceObserverMethods for PerformanceObserver {
return Err((Error::Type("entryTypes cannot be empty".to_string())));
}
let performance = self.global().as_window().Performance();
// step 3-4-5
performance.add_observer(self, entry_types, options.buffered);
self.global().performance().add_observer(self, entry_types, options.buffered);
Ok(())
}
// https://w3c.github.io/performance-timeline/#dom-performanceobserver-disconnect()
fn Disconnect(&self) {
self.global().as_window().Performance().remove_observer(self);
self.global().performance().remove_observer(self);
self.entries.borrow_mut().clear();
}
}

View file

@ -9,17 +9,19 @@
typedef double DOMHighResTimeStamp;
typedef sequence<PerformanceEntry> PerformanceEntryList;
[Exposed=(Window,Worker)]
[Exposed=(Window, Worker)]
interface Performance {
DOMHighResTimeStamp now();
};
[Exposed=(Window)]
partial interface Performance {
readonly attribute PerformanceTiming timing;
/* readonly attribute PerformanceNavigation navigation; */
};
partial interface Performance {
DOMHighResTimeStamp now();
};
// https://w3c.github.io/performance-timeline/#extensions-to-the-performance-interface
[Exposed=(Window, Worker)]
partial interface Performance {
PerformanceEntryList getEntries();
PerformanceEntryList getEntriesByType(DOMString type);

View file

@ -6,7 +6,7 @@
* https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-navigation-timing-interface
*/
[Exposed=(Window,Worker)]
[Exposed=(Window)]
interface PerformanceTiming {
readonly attribute unsigned long long navigationStart;
/* readonly attribute unsigned long long unloadEventStart;

View file

@ -98,11 +98,6 @@ interface WindowBase64 {
};
Window implements WindowBase64;
// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html#sec-window.performance-attribute
partial interface Window {
[Replaceable] readonly attribute Performance performance;
};
// https://html.spec.whatwg.org/multipage/#Window-partial
partial interface Window {
void captureEvents();

View file

@ -26,5 +26,11 @@ interface WindowOrWorkerGlobalScope {
// ImageBitmapSource image, long sx, long sy, long sw, long sh, optional ImageBitmapOptions options);
};
// https://w3c.github.io/hr-time/#the-performance-attribute
partial interface WindowOrWorkerGlobalScope {
[Replaceable]
readonly attribute Performance performance;
};
Window implements WindowOrWorkerGlobalScope;
WorkerGlobalScope implements WindowOrWorkerGlobalScope;

View file

@ -710,7 +710,8 @@ impl WindowMethods for Window {
// NavigationTiming/Overview.html#sec-window.performance-attribute
fn Performance(&self) -> Root<Performance> {
self.performance.or_init(|| {
Performance::new(self, self.navigation_start.get(),
let global_scope = self.upcast::<GlobalScope>();
Performance::new(global_scope, self.navigation_start.get(),
self.navigation_start_precise.get())
})
}

View file

@ -17,6 +17,7 @@ use dom::bindings::trace::RootedTraceableBox;
use dom::crypto::Crypto;
use dom::dedicatedworkerglobalscope::DedicatedWorkerGlobalScope;
use dom::globalscope::GlobalScope;
use dom::performance::Performance;
use dom::promise::Promise;
use dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use dom::window::{base64_atob, base64_btoa};
@ -44,6 +45,8 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Receiver;
use task_source::file_reading::FileReadingTaskSource;
use task_source::networking::NetworkingTaskSource;
use task_source::performance_timeline::PerformanceTimelineTaskSource;
use time::precise_time_ns;
use timers::{IsInterval, TimerCallback};
pub fn prepare_workerscope_init(global: &GlobalScope,
@ -89,6 +92,9 @@ pub struct WorkerGlobalScope {
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
microtask_queue: MicrotaskQueue,
navigation_start_precise: f64,
performance: MutNullableJS<Performance>,
}
impl WorkerGlobalScope {
@ -120,6 +126,8 @@ impl WorkerGlobalScope {
from_devtools_sender: init.from_devtools_sender,
from_devtools_receiver: from_devtools_receiver,
microtask_queue: MicrotaskQueue::default(),
navigation_start_precise: precise_time_ns() as f64,
performance: Default::default(),
}
}
@ -320,6 +328,16 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
fn Fetch(&self, input: RequestOrUSVString, init: RootedTraceableBox<RequestInit>) -> Rc<Promise> {
fetch::Fetch(self.upcast(), input, init)
}
// https://w3c.github.io/hr-time/#the-performance-attribute
fn Performance(&self) -> Root<Performance> {
self.performance.or_init(|| {
let global_scope = self.upcast::<GlobalScope>();
Performance::new(global_scope,
0 /* navigation start is not used in workers */,
self.navigation_start_precise)
})
}
}
@ -368,6 +386,10 @@ impl WorkerGlobalScope {
NetworkingTaskSource(self.script_chan())
}
pub fn performance_timeline_task_source(&self) -> PerformanceTimelineTaskSource {
PerformanceTimelineTaskSource(self.script_chan())
}
pub fn new_script_pair(&self) -> (Box<ScriptChan + Send>, Box<ScriptPort + Send>) {
let dedicated = self.downcast::<DedicatedWorkerGlobalScope>();
if let Some(dedicated) = dedicated {

View file

@ -88,7 +88,8 @@ pub enum ScriptThreadEventCategory {
ServiceWorkerEvent,
EnterFullscreen,
ExitFullscreen,
WebVREvent
WebVREvent,
PerformanceTimelineTask,
}
/// An interface for receiving ScriptMsg values in an event loop. Used for synchronous DOM

View file

@ -118,7 +118,7 @@ use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSour
use task_source::file_reading::FileReadingTaskSource;
use task_source::history_traversal::HistoryTraversalTaskSource;
use task_source::networking::NetworkingTaskSource;
use task_source::performance_timeline::{PerformanceTimelineTask, PerformanceTimelineTaskSource};
use task_source::performance_timeline::PerformanceTimelineTaskSource;
use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSource};
use time::{get_time, precise_time_ns, Tm};
use url::Position;
@ -272,8 +272,6 @@ pub enum MainThreadScriptMsg {
/// Notifies the script thread that a new worklet has been loaded, and thus the page should be
/// reflowed.
WorkletLoaded(PipelineId),
/// Tasks that originate from the performance timeline task source.
PerformanceTimeline(PerformanceTimelineTask),
}
impl OpaqueSender<CommonScriptMsg> for Box<ScriptChan + Send> {
@ -860,9 +858,9 @@ impl ScriptThread {
dom_manipulation_task_source: DOMManipulationTaskSource(chan.clone()),
user_interaction_task_source: UserInteractionTaskSource(chan.clone()),
networking_task_source: NetworkingTaskSource(boxed_script_sender.clone()),
history_traversal_task_source: HistoryTraversalTaskSource(chan.clone()),
file_reading_task_source: FileReadingTaskSource(boxed_script_sender),
performance_timeline_task_source: PerformanceTimelineTaskSource(chan),
history_traversal_task_source: HistoryTraversalTaskSource(chan),
file_reading_task_source: FileReadingTaskSource(boxed_script_sender.clone()),
performance_timeline_task_source: PerformanceTimelineTaskSource(boxed_script_sender),
control_chan: state.control_chan,
control_port: control_port,
@ -1192,6 +1190,7 @@ impl ScriptThread {
ScriptThreadEventCategory::ServiceWorkerEvent => ProfilerCategory::ScriptServiceWorkerEvent,
ScriptThreadEventCategory::EnterFullscreen => ProfilerCategory::ScriptEnterFullscreen,
ScriptThreadEventCategory::ExitFullscreen => ProfilerCategory::ScriptExitFullscreen,
ScriptThreadEventCategory::PerformanceTimelineTask => ProfilerCategory::ScriptPerformanceEvent,
};
profile(profiler_cat, None, self.time_profiler_chan.clone(), f)
} else {
@ -1295,8 +1294,6 @@ impl ScriptThread {
self.handle_worklet_loaded(pipeline_id),
MainThreadScriptMsg::DOMManipulation(task) =>
task.handle_task(self),
MainThreadScriptMsg::PerformanceTimeline(task) =>
task.handle_task(self),
MainThreadScriptMsg::UserInteraction(task) =>
task.handle_task(self),
}
@ -2009,7 +2006,6 @@ impl ScriptThread {
let DOMManipulationTaskSource(ref dom_sender) = self.dom_manipulation_task_source;
let UserInteractionTaskSource(ref user_sender) = self.user_interaction_task_source;
let HistoryTraversalTaskSource(ref history_sender) = self.history_traversal_task_source;
let PerformanceTimelineTaskSource(ref performance_sender) = self.performance_timeline_task_source;
let (ipc_timer_event_chan, ipc_timer_event_port) = ipc::channel().unwrap();
ROUTER.route_ipc_receiver_to_mpsc_sender(ipc_timer_event_port,
@ -2034,7 +2030,7 @@ impl ScriptThread {
self.networking_task_source.clone(),
HistoryTraversalTaskSource(history_sender.clone()),
self.file_reading_task_source.clone(),
PerformanceTimelineTaskSource(performance_sender.clone()),
self.performance_timeline_task_source.clone(),
self.image_cache_channel.clone(),
self.image_cache.clone(),
self.resource_threads.clone(),

View file

@ -6,18 +6,41 @@
// a low priority task and it should be processed during idle periods.
// We are currently treating this task queue as a normal priority queue.
use dom::bindings::inheritance::Castable;
use dom::bindings::refcounted::Trusted;
use dom::performance::{NotifyPerformanceObserverRunnable, Performance};
use dom::window::Window;
use script_thread::{MainThreadScriptMsg, Runnable, RunnableWrapper, ScriptThread};
use dom::globalscope::GlobalScope;
use dom::performance::Performance;
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory};
use script_thread::{Runnable, RunnableWrapper};
use std::fmt;
use std::result::Result;
use std::sync::mpsc::Sender;
use task_source::TaskSource;
#[derive(Clone, JSTraceable)]
pub struct PerformanceTimelineTaskSource(pub Sender<MainThreadScriptMsg>);
pub struct NotifyPerformanceObserverRunnable {
owner: Trusted<Performance>,
}
impl NotifyPerformanceObserverRunnable {
pub fn new(owner: Trusted<Performance>) -> Self {
NotifyPerformanceObserverRunnable {
owner,
}
}
}
impl Runnable for NotifyPerformanceObserverRunnable {
fn handler(self: Box<NotifyPerformanceObserverRunnable>) {
self.owner.root().notify_observers();
}
}
#[derive(JSTraceable)]
pub struct PerformanceTimelineTaskSource(pub Box<ScriptChan + Send + 'static>);
impl Clone for PerformanceTimelineTaskSource {
fn clone(&self) -> PerformanceTimelineTaskSource {
PerformanceTimelineTaskSource(self.0.clone())
}
}
impl fmt::Debug for PerformanceTimelineTaskSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -30,29 +53,18 @@ impl TaskSource for PerformanceTimelineTaskSource {
msg: Box<T>,
wrapper: &RunnableWrapper) -> Result<(), ()>
where T: Runnable + Send + 'static {
let msg = PerformanceTimelineTask(wrapper.wrap_runnable(msg));
self.0.send(MainThreadScriptMsg::PerformanceTimeline(msg)).map_err(|_| ())
let msg = CommonScriptMsg::RunnableMsg(
ScriptThreadEventCategory::PerformanceTimelineTask,
wrapper.wrap_runnable(msg)
);
self.0.send(msg).map_err(|_| ())
}
}
impl PerformanceTimelineTaskSource {
pub fn queue_notification(&self, owner: &Performance, window: &Window) {
let owner = Trusted::new(owner);
pub fn queue_notification(&self, global: &GlobalScope) {
let owner = Trusted::new(&*global.performance());
let runnable = box NotifyPerformanceObserverRunnable::new(owner);
let _ = self.queue(runnable, window.upcast());
}
}
pub struct PerformanceTimelineTask(pub Box<Runnable + Send>);
impl fmt::Debug for PerformanceTimelineTask {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PerformanceTimelineTask(...)")
}
}
impl PerformanceTimelineTask {
pub fn handle_task(self, script_thread: &ScriptThread) {
self.0.main_thread_handler(script_thread);
let _ = self.queue(runnable, global);
}
}

View file

@ -1,17 +0,0 @@
[basic.any.worker.html]
type: testharness
[self.performance.now() is a function that returns a number]
expected: FAIL
[self.performance.now() returns a positive number]
expected: FAIL
[self.performance.now() difference is not negative]
expected: FAIL
[High resolution time has approximately the right relative magnitude]
expected: FAIL
[basic.any.html]
type: testharness

View file

@ -1,11 +0,0 @@
[monotonic-clock.any.html]
type: testharness
[monotonic-clock.any.worker.html]
type: testharness
[self.performance.now() returns a positive number]
expected: FAIL
[self.performance.now() difference is not negative]
expected: FAIL

View file

@ -1,6 +0,0 @@
[window-worker-time-origin.html]
type: testharness
expected: ERROR
[Worker time origin is approximately its creation time]
expected: TIMEOUT

View file

@ -27306,7 +27306,7 @@
"support"
],
"mozilla/interfaces.worker.js": [
"5fb0da8a22a5afe00d1232c700720c080f5dff44",
"fb5537ec0753e3a1c56dff957bcc1c0660dcb7b9",
"testharness"
],
"mozilla/iterable.html": [

View file

@ -37,7 +37,6 @@ test_interfaces([
"PerformanceObserver",
"PerformanceObserverEntryList",
"PerformancePaintTiming",
"PerformanceTiming",
"ProgressEvent",
"Request",
"Response",