Auto merge of #8603 - benschulz:timers-clean-up, r=jdm

Timers clean up

This PR splits the `ActiveTimers` abstraction into

 - `OneshotTimers` for scheduling "arbitrary" oneshot timers, such as XHR timeouts, and
 - `JsTimers`, based on `OneshotTimers`, for scheduling JS timers (`setTimeout`/`setInterval`).

The result is mich cleaner and the timer initialization steps now closely resemble the specification.

**Notes**
 - The second and third commit are strictly renames and code rearrangements.
 - I'm not particularily happy with the `OneshotTimerCallback` enum and its circular dependency with `XHRTimeoutCallback`, but I couldn't come up with anything better.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/8603)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2016-02-21 21:43:28 +05:30
commit 8f278109ce
5 changed files with 371 additions and 277 deletions

View file

@ -23,7 +23,7 @@ use net_traits::ResourceThread;
use profile_traits::mem;
use script_thread::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThread};
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEventRequest};
use timers::{ScheduledCallback, TimerHandle};
use timers::{OneshotTimerCallback, OneshotTimerHandle};
use url::Url;
/// 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
/// passed.
pub fn schedule_callback(&self,
callback: Box<ScheduledCallback>,
callback: OneshotTimerCallback,
duration: MsDuration)
-> TimerHandle {
-> OneshotTimerHandle {
match *self {
GlobalRef::Window(window) => window.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.
pub fn unschedule_callback(&self, handle: TimerHandle) {
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
match *self {
GlobalRef::Window(window) => window.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::selector_impl::PseudoElement;
use time;
use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle};
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
use url::Url;
use util::geometry::{self, MAX_RECT};
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
@ -149,7 +149,7 @@ pub struct Window {
local_storage: MutNullableHeap<JS<Storage>>,
#[ignore_heap_size_of = "channels are hard"]
scheduler_chan: IpcSender<TimerEventRequest>,
timers: ActiveTimers,
timers: OneshotTimers,
next_worker_id: Cell<WorkerId>,
@ -466,7 +466,8 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
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,
timeout,
IsInterval::NonInterval,
@ -475,7 +476,8 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
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,
timeout,
IsInterval::NonInterval,
@ -484,12 +486,13 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout
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
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,
timeout,
IsInterval::Interval,
@ -498,7 +501,8 @@ impl WindowMethods for Window {
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
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,
timeout,
IsInterval::Interval,
@ -1155,13 +1159,13 @@ impl Window {
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,
duration,
TimerSource::FromWindow(self.id.clone()))
}
pub fn unschedule_callback(&self, handle: TimerHandle) {
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
self.timers.unschedule_callback(handle);
}
@ -1349,7 +1353,7 @@ impl Window {
session_storage: Default::default(),
local_storage: Default::default(),
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)),
id: id,
parent_info: parent_info,

View file

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

View file

@ -59,7 +59,7 @@ use std::sync::mpsc::channel;
use std::sync::{Arc, Mutex};
use string_cache::Atom;
use time;
use timers::{ScheduledCallback, TimerHandle};
use timers::{OneshotTimerCallback, OneshotTimerHandle};
use url::Url;
use url::percent_encoding::{utf8_percent_encode, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET};
use util::str::DOMString;
@ -146,7 +146,7 @@ pub struct XMLHttpRequest {
upload_events: Cell<bool>,
send_flag: Cell<bool>,
timeout_cancel: DOMRefCell<Option<TimerHandle>>,
timeout_cancel: DOMRefCell<Option<OneshotTimerHandle>>,
fetch_time: Cell<i64>,
generation_id: Cell<GenerationId>,
response_status: Cell<Result<(), ()>>,
@ -1055,39 +1055,15 @@ impl XMLHttpRequest {
self.dispatch_progress_event(false, type_, len, total);
}
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
// This will cancel all previous timeouts
let global = self.global();
let callback = ScheduledXHRTimeout {
let callback = OneshotTimerCallback::XhrTimeout(XHRTimeoutCallback {
xhr: Trusted::new(self, global.r().networking_task_source()),
generation_id: self.generation_id.get(),
};
});
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) {
@ -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 {
fn extract(&self) -> (Vec<u8>, Option<DOMString>);
}

View file

@ -5,9 +5,11 @@
use dom::bindings::callback::ExceptionHandling::Report;
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::global::GlobalRef;
use dom::bindings::reflector::Reflectable;
use dom::bindings::trace::JSTraceable;
use dom::window::ScriptHelpers;
use dom::xmlhttprequest::XHRTimeoutCallback;
use euclid::length::Length;
use heapsize::HeapSizeOf;
use ipc_channel::ipc::IpcSender;
@ -18,22 +20,24 @@ use script_traits::{MsDuration, precise_time_ms};
use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSource};
use std::cell::Cell;
use std::cmp::{self, Ord, Ordering};
use std::collections::HashMap;
use std::default::Default;
use std::rc::Rc;
use util::str::DOMString;
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord, Debug)]
pub struct TimerHandle(i32);
pub struct OneshotTimerHandle(i32);
#[derive(JSTraceable, HeapSizeOf)]
#[privatize]
pub struct ActiveTimers {
pub struct OneshotTimers {
js_timers: JsTimers,
#[ignore_heap_size_of = "Defined in std"]
timer_event_chan: IpcSender<TimerEvent>,
#[ignore_heap_size_of = "Defined in std"]
scheduler_chan: IpcSender<TimerEventRequest>,
next_timer_handle: Cell<TimerHandle>,
timers: DOMRefCell<Vec<Timer>>,
next_timer_handle: Cell<OneshotTimerHandle>,
timers: DOMRefCell<Vec<OneshotTimer>>,
suspended_since: Cell<Option<MsDuration>>,
/// Initially 0, increased whenever the associated document is reactivated
/// 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
/// original timer is rescheduled when it is the next one to get called.
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)]
#[privatize]
struct Timer {
handle: TimerHandle,
struct OneshotTimer {
handle: OneshotTimerHandle,
source: TimerSource,
callback: InternalTimerCallback,
is_interval: IsInterval,
nesting_level: u32,
duration: MsDuration,
next_call: MsDuration,
callback: OneshotTimerCallback,
scheduled_for: MsDuration,
}
// Enum allowing more descriptive values for the is_interval field
#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)]
pub enum IsInterval {
Interval,
NonInterval,
// This enum is required to work around the fact that trait objects do not support generic methods.
// A replacement trait would have a method such as
// `invoke<T: Reflectable>(self: Box<Self>, this: &T, js_timers: &JsTimers);`.
#[derive(JSTraceable, HeapSizeOf)]
pub enum OneshotTimerCallback {
XhrTimeout(XHRTimeoutCallback),
JsTimer(JsTimerTask),
}
impl Ord for Timer {
fn cmp(&self, other: &Timer) -> Ordering {
match self.next_call.cmp(&other.next_call).reverse() {
impl OneshotTimerCallback {
fn invoke<T: Reflectable>(self, this: &T, js_timers: &JsTimers) {
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(),
res => res
}
}
}
impl PartialOrd for Timer {
fn partial_cmp(&self, other: &Timer) -> Option<Ordering> {
impl PartialOrd for OneshotTimer {
fn partial_cmp(&self, other: &OneshotTimer) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for Timer {}
impl PartialEq for Timer {
fn eq(&self, other: &Timer) -> bool {
self as *const Timer == other as *const Timer
impl Eq for OneshotTimer {}
impl PartialEq for OneshotTimer {
fn eq(&self, other: &OneshotTimer) -> bool {
self as *const OneshotTimer == other as *const OneshotTimer
}
}
#[derive(Clone)]
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 {
impl OneshotTimers {
pub fn new(timer_event_chan: IpcSender<TimerEvent>,
scheduler_chan: IpcSender<TimerEventRequest>)
-> ActiveTimers {
ActiveTimers {
-> OneshotTimers {
OneshotTimers {
js_timers: JsTimers::new(),
timer_event_chan: timer_event_chan,
scheduler_chan: scheduler_chan,
next_timer_handle: Cell::new(TimerHandle(1)),
next_timer_handle: Cell::new(OneshotTimerHandle(1)),
timers: DOMRefCell::new(Vec::new()),
suspended_since: Cell::new(None),
suspension_offset: Cell::new(Length::new(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,
callback: Box<ScheduledCallback>,
callback: OneshotTimerCallback,
duration: MsDuration,
source: TimerSource) -> TimerHandle {
self.schedule_internal_callback(InternalTimerCallback::InternalCallback(callback),
duration,
IsInterval::NonInterval,
source)
}
source: TimerSource)
-> OneshotTimerHandle {
let new_handle = self.next_timer_handle.get();
self.next_timer_handle.set(OneshotTimerHandle(new_handle.0 + 1));
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
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 scheduled_for = self.base_time() + duration;
let next_call = self.base_time() + duration;
let timer = Timer {
handle: TimerHandle(new_handle),
let timer = OneshotTimer {
handle: new_handle,
source: source,
callback: callback,
is_interval: is_interval,
duration: duration,
// step 6
nesting_level: self.nesting_level.get() + 1,
next_call: next_call,
scheduled_for: scheduled_for,
};
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 max_handle == new_handle {
if self.is_next_timer(new_handle) {
self.schedule_timer_call();
}
// step 10
TimerHandle(new_handle)
new_handle
}
pub fn clear_timeout_or_interval(&self, handle: i32) {
self.unschedule_callback(TimerHandle(handle));
}
pub fn unschedule_callback(&self, handle: TimerHandle) {
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
let was_next = self.is_next_timer(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
#[allow(unsafe_code)]
fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
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) {
let expected_id = self.expected_event_id.get();
if expected_id != id {
debug!("ignoring timer fire event {:?} (expected {:?}", id, expected_id);
debug!("ignoring timer fire event {:?} (expected {:?})", id, expected_id);
return;
}
@ -250,16 +178,16 @@ impl ActiveTimers {
let base_time = self.base_time();
// 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
// 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 {
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;
}
@ -267,79 +195,19 @@ impl ActiveTimers {
}
for timer in timers_to_run {
let callback = timer.callback.clone();
// 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);
let callback = timer.callback;
callback.invoke(this, &self.js_timers);
}
self.schedule_timer_call();
}
fn insert_timer(&self, timer: Timer) {
let mut timers = self.timers.borrow_mut();
let insertion_index = timers.binary_search(&timer).err().unwrap();
timers.insert(insertion_index, timer);
}
fn base_time(&self) -> MsDuration {
let offset = self.suspension_offset.get();
fn is_next_timer(&self, handle: TimerHandle) -> bool {
match self.timers.borrow().last() {
None => false,
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();
match self.suspended_since.get() {
Some(time) => time - offset,
None => precise_time_ms() - offset,
}
}
@ -364,24 +232,22 @@ impl ActiveTimers {
self.schedule_timer_call();
}
fn base_time(&self) -> MsDuration {
let offset = self.suspension_offset.get();
match self.suspended_since.get() {
Some(time) => time - offset,
None => precise_time_ms() - offset,
fn schedule_timer_call(&self) {
if self.suspended_since.get().is_some() {
// The timer will be scheduled when the pipeline is thawed.
return;
}
}
// see step 7 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
fn clamp_duration(&self, unclamped: MsDuration) -> MsDuration {
let ms = if self.nesting_level.get() > 5 {
4
} else {
0
};
let timers = self.timers.borrow();
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 {
@ -391,4 +257,231 @@ impl ActiveTimers {
self.expected_event_id.set(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);
}
}
}