Clean up of script timer code.

The code was split into the following two abstractions.
 - OneshotTimers can be used to schedule arbitrary oneshot timers, such
   as XHR-Timeouts.
 - JsTimers (`setTimeout` and `setInterval`) which use OneshotTimers to
   schedule individual callbacks.

With this change the implementation (of JsTimers in particular) is in
much closer alignment with the specification.
This commit is contained in:
benshu 2015-11-19 07:57:04 +01:00
parent 581aa1a14f
commit f2d4a7bbca
5 changed files with 371 additions and 277 deletions

View file

@ -23,7 +23,7 @@ use net_traits::ResourceThread;
use profile_traits::mem; use profile_traits::mem;
use script_thread::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThread}; use script_thread::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThread};
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEventRequest}; use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEventRequest};
use timers::{ScheduledCallback, TimerHandle}; use timers::{OneshotTimerCallback, OneshotTimerHandle};
use url::Url; use url::Url;
/// A freely-copyable reference to a rooted global object. /// A freely-copyable reference to a rooted global object.
@ -224,9 +224,9 @@ impl<'a> GlobalRef<'a> {
/// Schedule the given `callback` to be invoked after at least `duration` milliseconds have /// Schedule the given `callback` to be invoked after at least `duration` milliseconds have
/// passed. /// passed.
pub fn schedule_callback(&self, pub fn schedule_callback(&self,
callback: Box<ScheduledCallback>, callback: OneshotTimerCallback,
duration: MsDuration) duration: MsDuration)
-> TimerHandle { -> OneshotTimerHandle {
match *self { match *self {
GlobalRef::Window(window) => window.schedule_callback(callback, duration), GlobalRef::Window(window) => window.schedule_callback(callback, duration),
GlobalRef::Worker(worker) => worker.schedule_callback(callback, duration), GlobalRef::Worker(worker) => worker.schedule_callback(callback, duration),
@ -234,7 +234,7 @@ impl<'a> GlobalRef<'a> {
} }
/// Unschedule a previously-scheduled callback. /// Unschedule a previously-scheduled callback.
pub fn unschedule_callback(&self, handle: TimerHandle) { pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
match *self { match *self {
GlobalRef::Window(window) => window.unschedule_callback(handle), GlobalRef::Window(window) => window.unschedule_callback(handle),
GlobalRef::Worker(worker) => worker.unschedule_callback(handle), GlobalRef::Worker(worker) => worker.unschedule_callback(handle),

View file

@ -76,7 +76,7 @@ use style::context::ReflowGoal;
use style::error_reporting::ParseErrorReporter; use style::error_reporting::ParseErrorReporter;
use style::selector_impl::PseudoElement; use style::selector_impl::PseudoElement;
use time; use time;
use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle}; use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
use url::Url; use url::Url;
use util::geometry::{self, MAX_RECT}; use util::geometry::{self, MAX_RECT};
use util::str::{DOMString, HTML_SPACE_CHARACTERS}; use util::str::{DOMString, HTML_SPACE_CHARACTERS};
@ -149,7 +149,7 @@ pub struct Window {
local_storage: MutNullableHeap<JS<Storage>>, local_storage: MutNullableHeap<JS<Storage>>,
#[ignore_heap_size_of = "channels are hard"] #[ignore_heap_size_of = "channels are hard"]
scheduler_chan: IpcSender<TimerEventRequest>, scheduler_chan: IpcSender<TimerEventRequest>,
timers: ActiveTimers, timers: OneshotTimers,
next_worker_id: Cell<WorkerId>, next_worker_id: Cell<WorkerId>,
@ -466,7 +466,8 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Window(self),
TimerCallback::FunctionTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::NonInterval, IsInterval::NonInterval,
@ -475,7 +476,8 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout // https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Window(self),
TimerCallback::StringTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::NonInterval, IsInterval::NonInterval,
@ -484,12 +486,13 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout // https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout
fn ClearTimeout(&self, handle: i32) { fn ClearTimeout(&self, handle: i32) {
self.timers.clear_timeout_or_interval(handle); self.timers.clear_timeout_or_interval(GlobalRef::Window(self), handle);
} }
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Window(self),
TimerCallback::FunctionTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::Interval, IsInterval::Interval,
@ -498,7 +501,8 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Window(self),
TimerCallback::StringTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::Interval, IsInterval::Interval,
@ -1155,13 +1159,13 @@ impl Window {
self.scheduler_chan.clone() self.scheduler_chan.clone()
} }
pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle { pub fn schedule_callback(&self, callback: OneshotTimerCallback, duration: MsDuration) -> OneshotTimerHandle {
self.timers.schedule_callback(callback, self.timers.schedule_callback(callback,
duration, duration,
TimerSource::FromWindow(self.id.clone())) TimerSource::FromWindow(self.id.clone()))
} }
pub fn unschedule_callback(&self, handle: TimerHandle) { pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
self.timers.unschedule_callback(handle); self.timers.unschedule_callback(handle);
} }
@ -1349,7 +1353,7 @@ impl Window {
session_storage: Default::default(), session_storage: Default::default(),
local_storage: Default::default(), local_storage: Default::default(),
scheduler_chan: scheduler_chan.clone(), scheduler_chan: scheduler_chan.clone(),
timers: ActiveTimers::new(timer_event_chan, scheduler_chan), timers: OneshotTimers::new(timer_event_chan, scheduler_chan),
next_worker_id: Cell::new(WorkerId(0)), next_worker_id: Cell::new(WorkerId(0)),
id: id, id: id,
parent_info: parent_info, parent_info: parent_info,

View file

@ -30,7 +30,7 @@ use std::cell::Cell;
use std::default::Default; use std::default::Default;
use std::rc::Rc; use std::rc::Rc;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle}; use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
use url::Url; use url::Url;
use util::str::DOMString; use util::str::DOMString;
@ -64,7 +64,7 @@ pub struct WorkerGlobalScope {
navigator: MutNullableHeap<JS<WorkerNavigator>>, navigator: MutNullableHeap<JS<WorkerNavigator>>,
console: MutNullableHeap<JS<Console>>, console: MutNullableHeap<JS<Console>>,
crypto: MutNullableHeap<JS<Crypto>>, crypto: MutNullableHeap<JS<Crypto>>,
timers: ActiveTimers, timers: OneshotTimers,
#[ignore_heap_size_of = "Defined in std"] #[ignore_heap_size_of = "Defined in std"]
mem_profiler_chan: mem::ProfilerChan, mem_profiler_chan: mem::ProfilerChan,
#[ignore_heap_size_of = "Defined in ipc-channel"] #[ignore_heap_size_of = "Defined in ipc-channel"]
@ -98,6 +98,7 @@ impl WorkerGlobalScope {
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
timer_event_chan: IpcSender<TimerEvent>) timer_event_chan: IpcSender<TimerEvent>)
-> WorkerGlobalScope { -> WorkerGlobalScope {
WorkerGlobalScope { WorkerGlobalScope {
eventtarget: EventTarget::new_inherited(), eventtarget: EventTarget::new_inherited(),
next_worker_id: Cell::new(WorkerId(0)), next_worker_id: Cell::new(WorkerId(0)),
@ -109,7 +110,7 @@ impl WorkerGlobalScope {
navigator: Default::default(), navigator: Default::default(),
console: Default::default(), console: Default::default(),
crypto: Default::default(), crypto: Default::default(),
timers: ActiveTimers::new(timer_event_chan, init.scheduler_chan.clone()), timers: OneshotTimers::new(timer_event_chan, init.scheduler_chan.clone()),
mem_profiler_chan: init.mem_profiler_chan, mem_profiler_chan: init.mem_profiler_chan,
to_devtools_sender: init.to_devtools_sender, to_devtools_sender: init.to_devtools_sender,
from_devtools_sender: init.from_devtools_sender, from_devtools_sender: init.from_devtools_sender,
@ -144,13 +145,13 @@ impl WorkerGlobalScope {
self.scheduler_chan.clone() self.scheduler_chan.clone()
} }
pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle { pub fn schedule_callback(&self, callback: OneshotTimerCallback, duration: MsDuration) -> OneshotTimerHandle {
self.timers.schedule_callback(callback, self.timers.schedule_callback(callback,
duration, duration,
TimerSource::FromWorker) TimerSource::FromWorker)
} }
pub fn unschedule_callback(&self, handle: TimerHandle) { pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
self.timers.unschedule_callback(handle); self.timers.unschedule_callback(handle);
} }
@ -250,7 +251,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
TimerCallback::FunctionTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::NonInterval, IsInterval::NonInterval,
@ -259,7 +261,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
TimerCallback::StringTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::NonInterval, IsInterval::NonInterval,
@ -268,12 +271,13 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
fn ClearTimeout(&self, handle: i32) { fn ClearTimeout(&self, handle: i32) {
self.timers.clear_timeout_or_interval(handle); self.timers.clear_timeout_or_interval(GlobalRef::Worker(self), handle);
} }
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
TimerCallback::FunctionTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::Interval, IsInterval::Interval,
@ -282,7 +286,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval // https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 { fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback), self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
TimerCallback::StringTimerCallback(callback),
args, args,
timeout, timeout,
IsInterval::Interval, IsInterval::Interval,

View file

@ -59,7 +59,7 @@ use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use string_cache::Atom; use string_cache::Atom;
use time; use time;
use timers::{ScheduledCallback, TimerHandle}; use timers::{OneshotTimerCallback, OneshotTimerHandle};
use url::Url; use url::Url;
use url::percent_encoding::{utf8_percent_encode, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET}; use url::percent_encoding::{utf8_percent_encode, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET};
use util::str::DOMString; use util::str::DOMString;
@ -146,7 +146,7 @@ pub struct XMLHttpRequest {
upload_events: Cell<bool>, upload_events: Cell<bool>,
send_flag: Cell<bool>, send_flag: Cell<bool>,
timeout_cancel: DOMRefCell<Option<TimerHandle>>, timeout_cancel: DOMRefCell<Option<OneshotTimerHandle>>,
fetch_time: Cell<i64>, fetch_time: Cell<i64>,
generation_id: Cell<GenerationId>, generation_id: Cell<GenerationId>,
response_status: Cell<Result<(), ()>>, response_status: Cell<Result<(), ()>>,
@ -1055,39 +1055,15 @@ impl XMLHttpRequest {
self.dispatch_progress_event(false, type_, len, total); self.dispatch_progress_event(false, type_, len, total);
} }
fn set_timeout(&self, duration_ms: u32) { fn set_timeout(&self, duration_ms: u32) {
#[derive(JSTraceable, HeapSizeOf)]
struct ScheduledXHRTimeout {
#[ignore_heap_size_of = "Because it is non-owning"]
xhr: Trusted<XMLHttpRequest>,
generation_id: GenerationId,
}
impl ScheduledCallback for ScheduledXHRTimeout {
fn invoke(self: Box<Self>) {
let this = *self;
let xhr = this.xhr.root();
if xhr.ready_state.get() != XMLHttpRequestState::Done {
xhr.process_partial_response(XHRProgress::Errored(this.generation_id, Error::Timeout));
}
}
fn box_clone(&self) -> Box<ScheduledCallback> {
box ScheduledXHRTimeout {
xhr: self.xhr.clone(),
generation_id: self.generation_id,
}
}
}
// Sets up the object to timeout in a given number of milliseconds // Sets up the object to timeout in a given number of milliseconds
// This will cancel all previous timeouts // This will cancel all previous timeouts
let global = self.global(); let global = self.global();
let callback = ScheduledXHRTimeout { let callback = OneshotTimerCallback::XhrTimeout(XHRTimeoutCallback {
xhr: Trusted::new(self, global.r().networking_task_source()), xhr: Trusted::new(self, global.r().networking_task_source()),
generation_id: self.generation_id.get(), generation_id: self.generation_id.get(),
}; });
let duration = Length::new(duration_ms as u64); let duration = Length::new(duration_ms as u64);
*self.timeout_cancel.borrow_mut() = Some(global.r().schedule_callback(box callback, duration)); *self.timeout_cancel.borrow_mut() = Some(global.r().schedule_callback(callback, duration));
} }
fn cancel_timeout(&self) { fn cancel_timeout(&self) {
@ -1368,6 +1344,22 @@ impl XMLHttpRequest {
} }
} }
#[derive(JSTraceable, HeapSizeOf)]
pub struct XHRTimeoutCallback {
#[ignore_heap_size_of = "Because it is non-owning"]
xhr: Trusted<XMLHttpRequest>,
generation_id: GenerationId,
}
impl XHRTimeoutCallback {
pub fn invoke(self) {
let xhr = self.xhr.root();
if xhr.ready_state.get() != XMLHttpRequestState::Done {
xhr.process_partial_response(XHRProgress::Errored(self.generation_id, Error::Timeout));
}
}
}
trait Extractable { trait Extractable {
fn extract(&self) -> (Vec<u8>, Option<DOMString>); fn extract(&self) -> (Vec<u8>, Option<DOMString>);
} }

View file

@ -5,9 +5,11 @@
use dom::bindings::callback::ExceptionHandling::Report; use dom::bindings::callback::ExceptionHandling::Report;
use dom::bindings::cell::DOMRefCell; use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::FunctionBinding::Function; use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::global::GlobalRef;
use dom::bindings::reflector::Reflectable; use dom::bindings::reflector::Reflectable;
use dom::bindings::trace::JSTraceable; use dom::bindings::trace::JSTraceable;
use dom::window::ScriptHelpers; use dom::window::ScriptHelpers;
use dom::xmlhttprequest::XHRTimeoutCallback;
use euclid::length::Length; use euclid::length::Length;
use heapsize::HeapSizeOf; use heapsize::HeapSizeOf;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
@ -18,22 +20,24 @@ use script_traits::{MsDuration, precise_time_ms};
use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSource}; use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSource};
use std::cell::Cell; use std::cell::Cell;
use std::cmp::{self, Ord, Ordering}; use std::cmp::{self, Ord, Ordering};
use std::collections::HashMap;
use std::default::Default; use std::default::Default;
use std::rc::Rc; use std::rc::Rc;
use util::str::DOMString; use util::str::DOMString;
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord, Debug)] #[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord, Debug)]
pub struct TimerHandle(i32); pub struct OneshotTimerHandle(i32);
#[derive(JSTraceable, HeapSizeOf)] #[derive(JSTraceable, HeapSizeOf)]
#[privatize] #[privatize]
pub struct ActiveTimers { pub struct OneshotTimers {
js_timers: JsTimers,
#[ignore_heap_size_of = "Defined in std"] #[ignore_heap_size_of = "Defined in std"]
timer_event_chan: IpcSender<TimerEvent>, timer_event_chan: IpcSender<TimerEvent>,
#[ignore_heap_size_of = "Defined in std"] #[ignore_heap_size_of = "Defined in std"]
scheduler_chan: IpcSender<TimerEventRequest>, scheduler_chan: IpcSender<TimerEventRequest>,
next_timer_handle: Cell<TimerHandle>, next_timer_handle: Cell<OneshotTimerHandle>,
timers: DOMRefCell<Vec<Timer>>, timers: DOMRefCell<Vec<OneshotTimer>>,
suspended_since: Cell<Option<MsDuration>>, suspended_since: Cell<Option<MsDuration>>,
/// Initially 0, increased whenever the associated document is reactivated /// Initially 0, increased whenever the associated document is reactivated
/// by the amount of ms the document was inactive. The current time can be /// by the amount of ms the document was inactive. The current time can be
@ -47,185 +51,104 @@ pub struct ActiveTimers {
/// - a timer was added with an earlier callback time. In this case the /// - a timer was added with an earlier callback time. In this case the
/// original timer is rescheduled when it is the next one to get called. /// original timer is rescheduled when it is the next one to get called.
expected_event_id: Cell<TimerEventId>, expected_event_id: Cell<TimerEventId>,
/// The nesting level of the currently executing timer thread or 0.
nesting_level: Cell<u32>,
} }
// Holder for the various JS values associated with setTimeout
// (ie. function value to invoke and all arguments to pass
// to the function when calling it)
// TODO: Handle rooting during fire_timer when movable GC is turned on
#[derive(JSTraceable, HeapSizeOf)] #[derive(JSTraceable, HeapSizeOf)]
#[privatize] #[privatize]
struct Timer { struct OneshotTimer {
handle: TimerHandle, handle: OneshotTimerHandle,
source: TimerSource, source: TimerSource,
callback: InternalTimerCallback, callback: OneshotTimerCallback,
is_interval: IsInterval, scheduled_for: MsDuration,
nesting_level: u32,
duration: MsDuration,
next_call: MsDuration,
} }
// Enum allowing more descriptive values for the is_interval field // This enum is required to work around the fact that trait objects do not support generic methods.
#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)] // A replacement trait would have a method such as
pub enum IsInterval { // `invoke<T: Reflectable>(self: Box<Self>, this: &T, js_timers: &JsTimers);`.
Interval, #[derive(JSTraceable, HeapSizeOf)]
NonInterval, pub enum OneshotTimerCallback {
XhrTimeout(XHRTimeoutCallback),
JsTimer(JsTimerTask),
} }
impl Ord for Timer { impl OneshotTimerCallback {
fn cmp(&self, other: &Timer) -> Ordering { fn invoke<T: Reflectable>(self, this: &T, js_timers: &JsTimers) {
match self.next_call.cmp(&other.next_call).reverse() { match self {
OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(),
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers),
}
}
}
impl Ord for OneshotTimer {
fn cmp(&self, other: &OneshotTimer) -> Ordering {
match self.scheduled_for.cmp(&other.scheduled_for).reverse() {
Ordering::Equal => self.handle.cmp(&other.handle).reverse(), Ordering::Equal => self.handle.cmp(&other.handle).reverse(),
res => res res => res
} }
} }
} }
impl PartialOrd for Timer { impl PartialOrd for OneshotTimer {
fn partial_cmp(&self, other: &Timer) -> Option<Ordering> { fn partial_cmp(&self, other: &OneshotTimer) -> Option<Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
} }
} }
impl Eq for Timer {} impl Eq for OneshotTimer {}
impl PartialEq for Timer { impl PartialEq for OneshotTimer {
fn eq(&self, other: &Timer) -> bool { fn eq(&self, other: &OneshotTimer) -> bool {
self as *const Timer == other as *const Timer self as *const OneshotTimer == other as *const OneshotTimer
} }
} }
#[derive(Clone)] impl OneshotTimers {
pub enum TimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(Rc<Function>),
}
#[derive(JSTraceable, Clone)]
enum InternalTimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(Rc<Function>, Rc<Vec<Heap<JSVal>>>),
InternalCallback(Box<ScheduledCallback>),
}
impl HeapSizeOf for InternalTimerCallback {
fn heap_size_of_children(&self) -> usize {
// FIXME: Rc<T> isn't HeapSizeOf and we can't ignore it due to #6870 and #6871
0
}
}
pub trait ScheduledCallback: JSTraceable + HeapSizeOf {
fn invoke(self: Box<Self>);
fn box_clone(&self) -> Box<ScheduledCallback>;
}
impl Clone for Box<ScheduledCallback> {
fn clone(&self) -> Box<ScheduledCallback> {
self.box_clone()
}
}
impl ActiveTimers {
pub fn new(timer_event_chan: IpcSender<TimerEvent>, pub fn new(timer_event_chan: IpcSender<TimerEvent>,
scheduler_chan: IpcSender<TimerEventRequest>) scheduler_chan: IpcSender<TimerEventRequest>)
-> ActiveTimers { -> OneshotTimers {
ActiveTimers { OneshotTimers {
js_timers: JsTimers::new(),
timer_event_chan: timer_event_chan, timer_event_chan: timer_event_chan,
scheduler_chan: scheduler_chan, scheduler_chan: scheduler_chan,
next_timer_handle: Cell::new(TimerHandle(1)), next_timer_handle: Cell::new(OneshotTimerHandle(1)),
timers: DOMRefCell::new(Vec::new()), timers: DOMRefCell::new(Vec::new()),
suspended_since: Cell::new(None), suspended_since: Cell::new(None),
suspension_offset: Cell::new(Length::new(0)), suspension_offset: Cell::new(Length::new(0)),
expected_event_id: Cell::new(TimerEventId(0)), expected_event_id: Cell::new(TimerEventId(0)),
nesting_level: Cell::new(0),
} }
} }
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
pub fn set_timeout_or_interval(&self,
callback: TimerCallback,
arguments: Vec<HandleValue>,
timeout: i32,
is_interval: IsInterval,
source: TimerSource)
-> i32 {
let callback = match callback {
TimerCallback::StringTimerCallback(code_str) =>
InternalTimerCallback::StringTimerCallback(code_str),
TimerCallback::FunctionTimerCallback(function) => {
// This is a bit complicated, but this ensures that the vector's
// buffer isn't reallocated (and moved) after setting the Heap values
let mut args = Vec::with_capacity(arguments.len());
for _ in 0..arguments.len() {
args.push(Heap::default());
}
for (i, item) in arguments.iter().enumerate() {
args.get_mut(i).unwrap().set(item.get());
}
InternalTimerCallback::FunctionTimerCallback(function, Rc::new(args))
}
};
let timeout = cmp::max(0, timeout);
// step 7
let duration = self.clamp_duration(Length::new(timeout as u64));
let TimerHandle(handle) = self.schedule_internal_callback(callback, duration, is_interval, source);
handle
}
pub fn schedule_callback(&self, pub fn schedule_callback(&self,
callback: Box<ScheduledCallback>, callback: OneshotTimerCallback,
duration: MsDuration, duration: MsDuration,
source: TimerSource) -> TimerHandle { source: TimerSource)
self.schedule_internal_callback(InternalTimerCallback::InternalCallback(callback), -> OneshotTimerHandle {
duration, let new_handle = self.next_timer_handle.get();
IsInterval::NonInterval, self.next_timer_handle.set(OneshotTimerHandle(new_handle.0 + 1));
source)
}
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps let scheduled_for = self.base_time() + duration;
fn schedule_internal_callback(&self,
callback: InternalTimerCallback,
duration: MsDuration,
is_interval: IsInterval,
source: TimerSource) -> TimerHandle {
// step 3
let TimerHandle(new_handle) = self.next_timer_handle.get();
self.next_timer_handle.set(TimerHandle(new_handle + 1));
let next_call = self.base_time() + duration; let timer = OneshotTimer {
handle: new_handle,
let timer = Timer {
handle: TimerHandle(new_handle),
source: source, source: source,
callback: callback, callback: callback,
is_interval: is_interval, scheduled_for: scheduled_for,
duration: duration,
// step 6
nesting_level: self.nesting_level.get() + 1,
next_call: next_call,
}; };
self.insert_timer(timer); {
let mut timers = self.timers.borrow_mut();
let insertion_index = timers.binary_search(&timer).err().unwrap();
timers.insert(insertion_index, timer);
}
let TimerHandle(max_handle) = self.timers.borrow().last().unwrap().handle; if self.is_next_timer(new_handle) {
if max_handle == new_handle {
self.schedule_timer_call(); self.schedule_timer_call();
} }
// step 10 new_handle
TimerHandle(new_handle)
} }
pub fn clear_timeout_or_interval(&self, handle: i32) { pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
self.unschedule_callback(TimerHandle(handle));
}
pub fn unschedule_callback(&self, handle: TimerHandle) {
let was_next = self.is_next_timer(handle); let was_next = self.is_next_timer(handle);
self.timers.borrow_mut().retain(|t| t.handle != handle); self.timers.borrow_mut().retain(|t| t.handle != handle);
@ -236,12 +159,17 @@ impl ActiveTimers {
} }
} }
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
#[allow(unsafe_code)] match self.timers.borrow().last() {
None => false,
Some(ref max_timer) => max_timer.handle == handle
}
}
pub fn fire_timer<T: Reflectable>(&self, id: TimerEventId, this: &T) { pub fn fire_timer<T: Reflectable>(&self, id: TimerEventId, this: &T) {
let expected_id = self.expected_event_id.get(); let expected_id = self.expected_event_id.get();
if expected_id != id { if expected_id != id {
debug!("ignoring timer fire event {:?} (expected {:?}", id, expected_id); debug!("ignoring timer fire event {:?} (expected {:?})", id, expected_id);
return; return;
} }
@ -250,16 +178,16 @@ impl ActiveTimers {
let base_time = self.base_time(); let base_time = self.base_time();
// Since the event id was the expected one, at least one timer should be due. // Since the event id was the expected one, at least one timer should be due.
assert!(base_time >= self.timers.borrow().last().unwrap().next_call); assert!(base_time >= self.timers.borrow().last().unwrap().scheduled_for);
// select timers to run to prevent firing timers // select timers to run to prevent firing timers
// that were installed during fire of another timer // that were installed during fire of another timer
let mut timers_to_run: Vec<Timer> = Vec::new(); let mut timers_to_run = Vec::new();
loop { loop {
let mut timers = self.timers.borrow_mut(); let mut timers = self.timers.borrow_mut();
if timers.is_empty() || timers.last().unwrap().next_call > base_time { if timers.is_empty() || timers.last().unwrap().scheduled_for > base_time {
break; break;
} }
@ -267,79 +195,19 @@ impl ActiveTimers {
} }
for timer in timers_to_run { for timer in timers_to_run {
let callback = timer.callback;
let callback = timer.callback.clone(); callback.invoke(this, &self.js_timers);
// prep for step 6 in nested set_timeout_or_interval calls
self.nesting_level.set(timer.nesting_level);
// step 4.3
if timer.is_interval == IsInterval::Interval {
let mut timer = timer;
// step 7
timer.duration = self.clamp_duration(timer.duration);
// step 8, 9
timer.nesting_level += 1;
timer.next_call = base_time + timer.duration;
self.insert_timer(timer);
}
// step 14
match callback {
InternalTimerCallback::StringTimerCallback(code_str) => {
let cx = this.global().r().get_cx();
let mut rval = RootedValue::new(cx, UndefinedValue());
this.evaluate_js_on_global_with_result(&code_str, rval.handle_mut());
},
InternalTimerCallback::FunctionTimerCallback(function, arguments) => {
let arguments: Vec<JSVal> = arguments.iter().map(|arg| arg.get()).collect();
let arguments = arguments.iter().by_ref().map(|arg| unsafe {
HandleValue::from_marked_location(arg)
}).collect();
let _ = function.Call_(this, arguments, Report);
},
InternalTimerCallback::InternalCallback(callback) => {
callback.invoke();
},
};
self.nesting_level.set(0);
} }
self.schedule_timer_call(); self.schedule_timer_call();
} }
fn insert_timer(&self, timer: Timer) { fn base_time(&self) -> MsDuration {
let mut timers = self.timers.borrow_mut(); let offset = self.suspension_offset.get();
let insertion_index = timers.binary_search(&timer).err().unwrap();
timers.insert(insertion_index, timer);
}
fn is_next_timer(&self, handle: TimerHandle) -> bool { match self.suspended_since.get() {
match self.timers.borrow().last() { Some(time) => time - offset,
None => false, None => precise_time_ms() - offset,
Some(ref max_timer) => max_timer.handle == handle
}
}
fn schedule_timer_call(&self) {
if self.suspended_since.get().is_some() {
// The timer will be scheduled when the pipeline is thawed.
return;
}
let timers = self.timers.borrow();
if let Some(timer) = timers.last() {
let expected_event_id = self.invalidate_expected_event_id();
let delay = Length::new(timer.next_call.get().saturating_sub(precise_time_ms().get()));
let request = TimerEventRequest(self.timer_event_chan.clone(), timer.source,
expected_event_id, delay);
self.scheduler_chan.send(request).unwrap();
} }
} }
@ -364,24 +232,22 @@ impl ActiveTimers {
self.schedule_timer_call(); self.schedule_timer_call();
} }
fn base_time(&self) -> MsDuration { fn schedule_timer_call(&self) {
let offset = self.suspension_offset.get(); if self.suspended_since.get().is_some() {
// The timer will be scheduled when the pipeline is thawed.
match self.suspended_since.get() { return;
Some(time) => time - offset,
None => precise_time_ms() - offset,
}
} }
// see step 7 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps let timers = self.timers.borrow();
fn clamp_duration(&self, unclamped: MsDuration) -> MsDuration {
let ms = if self.nesting_level.get() > 5 {
4
} else {
0
};
cmp::max(Length::new(ms), unclamped) if let Some(timer) = timers.last() {
let expected_event_id = self.invalidate_expected_event_id();
let delay = Length::new(timer.scheduled_for.get().saturating_sub(precise_time_ms().get()));
let request = TimerEventRequest(self.timer_event_chan.clone(), timer.source,
expected_event_id, delay);
self.scheduler_chan.send(request).unwrap();
}
} }
fn invalidate_expected_event_id(&self) -> TimerEventId { fn invalidate_expected_event_id(&self) -> TimerEventId {
@ -391,4 +257,231 @@ impl ActiveTimers {
self.expected_event_id.set(next_id); self.expected_event_id.set(next_id);
next_id next_id
} }
pub fn set_timeout_or_interval(&self,
global: GlobalRef,
callback: TimerCallback,
arguments: Vec<HandleValue>,
timeout: i32,
is_interval: IsInterval,
source: TimerSource)
-> i32 {
self.js_timers.set_timeout_or_interval(global,
callback,
arguments,
timeout,
is_interval,
source)
}
pub fn clear_timeout_or_interval(&self, global: GlobalRef, handle: i32) {
self.js_timers.clear_timeout_or_interval(global, handle)
}
}
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord)]
pub struct JsTimerHandle(i32);
#[derive(JSTraceable, HeapSizeOf)]
#[privatize]
pub struct JsTimers {
next_timer_handle: Cell<JsTimerHandle>,
active_timers: DOMRefCell<HashMap<JsTimerHandle, JsTimerEntry>>,
/// The nesting level of the currently executing timer task or 0.
nesting_level: Cell<u32>,
}
#[derive(JSTraceable, HeapSizeOf)]
struct JsTimerEntry {
oneshot_handle: OneshotTimerHandle,
}
// Holder for the various JS values associated with setTimeout
// (ie. function value to invoke and all arguments to pass
// to the function when calling it)
// TODO: Handle rooting during invocation when movable GC is turned on
#[derive(JSTraceable, HeapSizeOf)]
pub struct JsTimerTask {
#[ignore_heap_size_of = "Because it is non-owning"]
handle: JsTimerHandle,
source: TimerSource,
callback: InternalTimerCallback,
is_interval: IsInterval,
nesting_level: u32,
duration: MsDuration,
}
// Enum allowing more descriptive values for the is_interval field
#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)]
pub enum IsInterval {
Interval,
NonInterval,
}
#[derive(Clone)]
pub enum TimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(Rc<Function>),
}
#[derive(JSTraceable, Clone)]
enum InternalTimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(Rc<Function>, Rc<Vec<Heap<JSVal>>>),
}
impl HeapSizeOf for InternalTimerCallback {
fn heap_size_of_children(&self) -> usize {
// FIXME: Rc<T> isn't HeapSizeOf and we can't ignore it due to #6870 and #6871
0
}
}
impl JsTimers {
pub fn new() -> JsTimers {
JsTimers {
next_timer_handle: Cell::new(JsTimerHandle(1)),
active_timers: DOMRefCell::new(HashMap::new()),
nesting_level: Cell::new(0),
}
}
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
pub fn set_timeout_or_interval(&self,
global: GlobalRef,
callback: TimerCallback,
arguments: Vec<HandleValue>,
timeout: i32,
is_interval: IsInterval,
source: TimerSource)
-> i32 {
let callback = match callback {
TimerCallback::StringTimerCallback(code_str) =>
InternalTimerCallback::StringTimerCallback(code_str),
TimerCallback::FunctionTimerCallback(function) => {
// This is a bit complicated, but this ensures that the vector's
// buffer isn't reallocated (and moved) after setting the Heap values
let mut args = Vec::with_capacity(arguments.len());
for _ in 0..arguments.len() {
args.push(Heap::default());
}
for (i, item) in arguments.iter().enumerate() {
args.get_mut(i).unwrap().set(item.get());
}
InternalTimerCallback::FunctionTimerCallback(function, Rc::new(args))
}
};
// step 2
let JsTimerHandle(new_handle) = self.next_timer_handle.get();
self.next_timer_handle.set(JsTimerHandle(new_handle + 1));
// step 3 as part of initialize_and_schedule below
// step 4
let mut task = JsTimerTask {
handle: JsTimerHandle(new_handle),
source: source,
callback: callback,
is_interval: is_interval,
nesting_level: 0,
duration: Length::new(0),
};
// step 5
task.duration = Length::new(cmp::max(0, timeout) as u64);
// step 3, 6-9, 11-14
self.initialize_and_schedule(global, task);
// step 10
new_handle
}
pub fn clear_timeout_or_interval(&self, global: GlobalRef, handle: i32) {
let mut active_timers = self.active_timers.borrow_mut();
if let Some(entry) = active_timers.remove(&JsTimerHandle(handle)) {
global.unschedule_callback(entry.oneshot_handle);
}
}
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
fn initialize_and_schedule(&self, global: GlobalRef, mut task: JsTimerTask) {
let handle = task.handle;
let mut active_timers = self.active_timers.borrow_mut();
// step 6
let nesting_level = self.nesting_level.get();
// step 7
let duration = clamp_duration(nesting_level, task.duration);
// step 8, 9
task.nesting_level = nesting_level + 1;
// essentially step 11-14
let callback = OneshotTimerCallback::JsTimer(task);
let oneshot_handle = global.schedule_callback(callback, duration);
// step 3
let entry = active_timers.entry(handle).or_insert(JsTimerEntry {
oneshot_handle: oneshot_handle,
});
entry.oneshot_handle = oneshot_handle;
}
}
// see step 7 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
fn clamp_duration(nesting_level: u32, unclamped: MsDuration) -> MsDuration {
let lower_bound = if nesting_level > 5 {
4
} else {
0
};
cmp::max(Length::new(lower_bound), unclamped)
}
impl JsTimerTask {
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
#[allow(unsafe_code)]
pub fn invoke<T: Reflectable>(self, this: &T, timers: &JsTimers) {
// step 4.1 can be ignored, because we proactively prevent execution
// of this task when its scheduled execution is canceled.
// prep for step 6 in nested set_timeout_or_interval calls
timers.nesting_level.set(self.nesting_level);
// step 4.2
match *&self.callback {
InternalTimerCallback::StringTimerCallback(ref code_str) => {
let cx = this.global().r().get_cx();
let mut rval = RootedValue::new(cx, UndefinedValue());
this.evaluate_js_on_global_with_result(code_str, rval.handle_mut());
},
InternalTimerCallback::FunctionTimerCallback(ref function, ref arguments) => {
let arguments: Vec<JSVal> = arguments.iter().map(|arg| arg.get()).collect();
let arguments = arguments.iter().by_ref().map(|arg| unsafe {
HandleValue::from_marked_location(arg)
}).collect();
let _ = function.Call_(this, arguments, Report);
},
};
// reset nesting level (see above)
timers.nesting_level.set(0);
// step 4.3
// Since we choose proactively prevent execution (see 4.1 above), we must only
// reschedule repeating timers when they were not canceled as part of step 4.2.
if self.is_interval == IsInterval::Interval &&
timers.active_timers.borrow().contains_key(&self.handle) {
timers.initialize_and_schedule(this.global().r(), self);
}
}
} }