diff --git a/components/script/dom/bindings/global.rs b/components/script/dom/bindings/global.rs index e0e558e0f0f..c7ba5aaaf6c 100644 --- a/components/script/dom/bindings/global.rs +++ b/components/script/dom/bindings/global.rs @@ -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, + 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), diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 5e933ec6868..2082379f708 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -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>, #[ignore_heap_size_of = "channels are hard"] scheduler_chan: IpcSender, - timers: ActiveTimers, + timers: OneshotTimers, next_worker_id: Cell, @@ -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, timeout: i32, args: Vec) -> 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) -> 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, timeout: i32, args: Vec) -> 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) -> 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, 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, diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 360c78099dd..7ab29945961 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -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>, console: MutNullableHeap>, crypto: MutNullableHeap>, - 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, timer_event_chan: IpcSender) -> 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, 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, timeout: i32, args: Vec) -> 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) -> 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, timeout: i32, args: Vec) -> 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) -> 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, diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 634024f355b..0e53dfd869d 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -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, send_flag: Cell, - timeout_cancel: DOMRefCell>, + timeout_cancel: DOMRefCell>, fetch_time: Cell, generation_id: Cell, response_status: Cell>, @@ -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, - generation_id: GenerationId, - } - - impl ScheduledCallback for ScheduledXHRTimeout { - fn invoke(self: Box) { - 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 { - 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, + 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, Option); } diff --git a/components/script/timers.rs b/components/script/timers.rs index 20797d02cb0..7816106c9d7 100644 --- a/components/script/timers.rs +++ b/components/script/timers.rs @@ -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, #[ignore_heap_size_of = "Defined in std"] scheduler_chan: IpcSender, - next_timer_handle: Cell, - timers: DOMRefCell>, + next_timer_handle: Cell, + timers: DOMRefCell>, suspended_since: Cell>, /// 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, - /// The nesting level of the currently executing timer thread or 0. - nesting_level: Cell, } -// 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(self: Box, 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(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 { +impl PartialOrd for OneshotTimer { + fn partial_cmp(&self, other: &OneshotTimer) -> Option { 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), -} - -#[derive(JSTraceable, Clone)] -enum InternalTimerCallback { - StringTimerCallback(DOMString), - FunctionTimerCallback(Rc, Rc>>), - InternalCallback(Box), -} - -impl HeapSizeOf for InternalTimerCallback { - fn heap_size_of_children(&self) -> usize { - // FIXME: Rc isn't HeapSizeOf and we can't ignore it due to #6870 and #6871 - 0 - } -} - -pub trait ScheduledCallback: JSTraceable + HeapSizeOf { - fn invoke(self: Box); - - fn box_clone(&self) -> Box; -} - -impl Clone for Box { - fn clone(&self) -> Box { - self.box_clone() - } -} - -impl ActiveTimers { +impl OneshotTimers { pub fn new(timer_event_chan: IpcSender, scheduler_chan: IpcSender) - -> 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, - 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, + 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(&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 = 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 = 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, + 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, + active_timers: DOMRefCell>, + /// The nesting level of the currently executing timer task or 0. + nesting_level: Cell, +} + +#[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), +} + +#[derive(JSTraceable, Clone)] +enum InternalTimerCallback { + StringTimerCallback(DOMString), + FunctionTimerCallback(Rc, Rc>>), +} + +impl HeapSizeOf for InternalTimerCallback { + fn heap_size_of_children(&self) -> usize { + // FIXME: Rc 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, + 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(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 = 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); + } + } }