use std::cell::Cell;
use std::cmp::{Ord, Ordering};
use std::collections::HashMap;
use std::default::Default;
use std::rc::Rc;
use std::time::{Duration, Instant};
use deny_public_fields::DenyPublicFields;
use ipc_channel::ipc::IpcSender;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use js::rust::HandleValue;
use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSchedulerMsg, TimerSource};
use servo_config::pref;
use crate::dom::bindings::callback::ExceptionHandling::Report;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::str::DOMString;
use crate::dom::document::FakeRequestAnimationFrameCallback;
use crate::dom::eventsource::EventSourceTimeoutCallback;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlmetaelement::RefreshRedirectDue;
use crate::dom::testbinding::TestBindingCallback;
use crate::dom::xmlhttprequest::XHRTimeoutCallback;
use crate::script_module::ScriptFetchOptions;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
pub struct OneshotTimerHandle(i32);
#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
pub struct OneshotTimers {
js_timers: JsTimers,
#[ignore_malloc_size_of = "Defined in std"]
#[no_trace]
timer_event_chan: DomRefCell<Option<IpcSender<TimerEvent>>>,
#[ignore_malloc_size_of = "Defined in std"]
#[no_trace]
scheduler_chan: IpcSender<TimerSchedulerMsg>,
next_timer_handle: Cell<OneshotTimerHandle>,
timers: DomRefCell<Vec<OneshotTimer>>,
suspended_since: Cell<Option<Instant>>,
suspension_offset: Cell<Duration>,
#[no_trace]
expected_event_id: Cell<TimerEventId>,
}
#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
struct OneshotTimer {
handle: OneshotTimerHandle,
#[no_trace]
source: TimerSource,
callback: OneshotTimerCallback,
scheduled_for: Instant,
}
#[derive(JSTraceable, MallocSizeOf)]
pub enum OneshotTimerCallback {
XhrTimeout(XHRTimeoutCallback),
EventSourceTimeout(EventSourceTimeoutCallback),
JsTimer(JsTimerTask),
TestBindingCallback(TestBindingCallback),
FakeRequestAnimationFrame(FakeRequestAnimationFrameCallback),
RefreshRedirectDue(RefreshRedirectDue),
}
impl OneshotTimerCallback {
fn invoke<T: DomObject>(self, this: &T, js_timers: &JsTimers, can_gc: CanGc) {
match self {
OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(can_gc),
OneshotTimerCallback::EventSourceTimeout(callback) => callback.invoke(),
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers, can_gc),
OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
OneshotTimerCallback::FakeRequestAnimationFrame(callback) => callback.invoke(can_gc),
OneshotTimerCallback::RefreshRedirectDue(callback) => callback.invoke(can_gc),
}
}
}
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 OneshotTimer {
fn partial_cmp(&self, other: &OneshotTimer) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Eq for OneshotTimer {}
impl PartialEq for OneshotTimer {
fn eq(&self, other: &OneshotTimer) -> bool {
std::ptr::eq(self, other)
}
}
impl OneshotTimers {
pub fn new(scheduler_chan: IpcSender<TimerSchedulerMsg>) -> OneshotTimers {
OneshotTimers {
js_timers: JsTimers::default(),
timer_event_chan: DomRefCell::new(None),
scheduler_chan,
next_timer_handle: Cell::new(OneshotTimerHandle(1)),
timers: DomRefCell::new(Vec::new()),
suspended_since: Cell::new(None),
suspension_offset: Cell::new(Duration::ZERO),
expected_event_id: Cell::new(TimerEventId(0)),
}
}
pub fn setup_scheduling(&self, timer_event_chan: IpcSender<TimerEvent>) {
let mut chan = self.timer_event_chan.borrow_mut();
assert!(chan.is_none());
*chan = Some(timer_event_chan);
}
pub fn schedule_callback(
&self,
callback: OneshotTimerCallback,
duration: Duration,
source: TimerSource,
) -> OneshotTimerHandle {
let new_handle = self.next_timer_handle.get();
self.next_timer_handle
.set(OneshotTimerHandle(new_handle.0 + 1));
let timer = OneshotTimer {
handle: new_handle,
source,
callback,
scheduled_for: self.base_time() + duration,
};
{
let mut timers = self.timers.borrow_mut();
let insertion_index = timers.binary_search(&timer).err().unwrap();
timers.insert(insertion_index, timer);
}
if self.is_next_timer(new_handle) {
self.schedule_timer_call();
}
new_handle
}
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
let was_next = self.is_next_timer(handle);
self.timers.borrow_mut().retain(|t| t.handle != handle);
if was_next {
self.invalidate_expected_event_id();
self.schedule_timer_call();
}
}
fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
match self.timers.borrow().last() {
None => false,
Some(max_timer) => max_timer.handle == handle,
}
}
pub fn fire_timer(&self, id: TimerEventId, global: &GlobalScope, can_gc: CanGc) {
let expected_id = self.expected_event_id.get();
if expected_id != id {
debug!(
"ignoring timer fire event {:?} (expected {:?})",
id, expected_id
);
return;
}
assert!(self.suspended_since.get().is_none());
let base_time = self.base_time();
if base_time < self.timers.borrow().last().unwrap().scheduled_for {
warn!("Unexpected timing!");
return;
}
let mut timers_to_run = Vec::new();
loop {
let mut timers = self.timers.borrow_mut();
if timers.is_empty() || timers.last().unwrap().scheduled_for > base_time {
break;
}
timers_to_run.push(timers.pop().unwrap());
}
for timer in timers_to_run {
if !global.can_continue_running() {
return;
}
let callback = timer.callback;
callback.invoke(global, &self.js_timers, can_gc);
}
self.schedule_timer_call();
}
fn base_time(&self) -> Instant {
let offset = self.suspension_offset.get();
match self.suspended_since.get() {
Some(suspend_time) => suspend_time - offset,
None => Instant::now() - offset,
}
}
pub fn slow_down(&self) {
let min_duration_ms = pref!(js.timers.minimum_duration) as u64;
self.js_timers
.set_min_duration(Duration::from_millis(min_duration_ms));
}
pub fn speed_up(&self) {
self.js_timers.remove_min_duration();
}
pub fn suspend(&self) {
if self.suspended_since.get().is_some() {
return warn!("Suspending an already suspended timer.");
}
debug!("Suspending timers.");
self.suspended_since.set(Some(Instant::now()));
self.invalidate_expected_event_id();
}
pub fn resume(&self) {
let additional_offset = match self.suspended_since.get() {
Some(suspended_since) => Instant::now() - suspended_since,
None => return warn!("Resuming an already resumed timer."),
};
debug!("Resuming timers.");
self.suspension_offset
.set(self.suspension_offset.get() + additional_offset);
self.suspended_since.set(None);
self.schedule_timer_call();
}
fn schedule_timer_call(&self) {
if self.suspended_since.get().is_some() {
return;
}
let timers = self.timers.borrow();
if let Some(timer) = timers.last() {
let expected_event_id = self.invalidate_expected_event_id();
let delay = timer.scheduled_for - Instant::now();
let request = TimerEventRequest(
self.timer_event_chan
.borrow()
.clone()
.expect("Timer event chan not setup to schedule timers."),
timer.source,
expected_event_id,
delay,
);
self.scheduler_chan
.send(TimerSchedulerMsg(request))
.unwrap();
}
}
fn invalidate_expected_event_id(&self) -> TimerEventId {
let TimerEventId(currently_expected) = self.expected_event_id.get();
let next_id = TimerEventId(currently_expected + 1);
debug!(
"invalidating expected timer (was {:?}, now {:?}",
currently_expected, next_id
);
self.expected_event_id.set(next_id);
next_id
}
pub fn set_timeout_or_interval(
&self,
global: &GlobalScope,
callback: TimerCallback,
arguments: Vec<HandleValue>,
timeout: Duration,
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: &GlobalScope, handle: i32) {
self.js_timers.clear_timeout_or_interval(global, handle)
}
}
#[derive(Clone, Copy, Eq, Hash, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
pub struct JsTimerHandle(i32);
#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
pub struct JsTimers {
next_timer_handle: Cell<JsTimerHandle>,
active_timers: DomRefCell<HashMap<JsTimerHandle, JsTimerEntry>>,
nesting_level: Cell<u32>,
min_duration: Cell<Option<Duration>>,
}
#[derive(JSTraceable, MallocSizeOf)]
struct JsTimerEntry {
oneshot_handle: OneshotTimerHandle,
}
#[derive(JSTraceable, MallocSizeOf)]
pub struct JsTimerTask {
#[ignore_malloc_size_of = "Because it is non-owning"]
handle: JsTimerHandle,
#[no_trace]
source: TimerSource,
callback: InternalTimerCallback,
is_interval: IsInterval,
nesting_level: u32,
duration: Duration,
is_user_interacting: bool,
}
#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
pub enum IsInterval {
Interval,
NonInterval,
}
#[derive(Clone)]
pub enum TimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(Rc<Function>),
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
enum InternalTimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(
#[ignore_malloc_size_of = "Rc"] Rc<Function>,
#[ignore_malloc_size_of = "Rc"] Rc<Box<[Heap<JSVal>]>>,
),
}
impl Default for JsTimers {
fn default() -> Self {
JsTimers {
next_timer_handle: Cell::new(JsTimerHandle(1)),
active_timers: DomRefCell::new(HashMap::new()),
nesting_level: Cell::new(0),
min_duration: Cell::new(None),
}
}
}
impl JsTimers {
pub fn set_timeout_or_interval(
&self,
global: &GlobalScope,
callback: TimerCallback,
arguments: Vec<HandleValue>,
timeout: Duration,
is_interval: IsInterval,
source: TimerSource,
) -> i32 {
let callback = match callback {
TimerCallback::StringTimerCallback(code_str) => {
let cx = GlobalScope::get_cx();
if global.is_js_evaluation_allowed(cx) {
InternalTimerCallback::StringTimerCallback(code_str)
} else {
return 0;
}
},
TimerCallback::FunctionTimerCallback(function) => {
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.into_boxed_slice()),
)
},
};
let JsTimerHandle(new_handle) = self.next_timer_handle.get();
self.next_timer_handle.set(JsTimerHandle(new_handle + 1));
let mut task = JsTimerTask {
handle: JsTimerHandle(new_handle),
source,
callback,
is_interval,
is_user_interacting: ScriptThread::is_user_interacting(),
nesting_level: 0,
duration: Duration::ZERO,
};
task.duration = timeout.max(Duration::ZERO);
self.initialize_and_schedule(global, task);
new_handle
}
pub fn clear_timeout_or_interval(&self, global: &GlobalScope, 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);
}
}
pub fn set_min_duration(&self, duration: Duration) {
self.min_duration.set(Some(duration));
}
pub fn remove_min_duration(&self) {
self.min_duration.set(None);
}
fn user_agent_pad(&self, current_duration: Duration) -> Duration {
match self.min_duration.get() {
Some(min_duration) => min_duration.max(current_duration),
None => current_duration,
}
}
fn initialize_and_schedule(&self, global: &GlobalScope, mut task: JsTimerTask) {
let handle = task.handle;
let mut active_timers = self.active_timers.borrow_mut();
let nesting_level = self.nesting_level.get();
let duration = self.user_agent_pad(clamp_duration(nesting_level, task.duration));
task.nesting_level = nesting_level + 1;
let callback = OneshotTimerCallback::JsTimer(task);
let oneshot_handle = global.schedule_callback(callback, duration);
let entry = active_timers
.entry(handle)
.or_insert(JsTimerEntry { oneshot_handle });
entry.oneshot_handle = oneshot_handle;
}
}
fn clamp_duration(nesting_level: u32, unclamped: Duration) -> Duration {
let lower_bound_ms = if nesting_level > 5 { 4 } else { 0 };
let lower_bound = Duration::from_millis(lower_bound_ms);
lower_bound.max(unclamped)
}
impl JsTimerTask {
pub fn invoke<T: DomObject>(self, this: &T, timers: &JsTimers, can_gc: CanGc) {
timers.nesting_level.set(self.nesting_level);
let was_user_interacting = ScriptThread::is_user_interacting();
ScriptThread::set_user_interacting(self.is_user_interacting);
match self.callback {
InternalTimerCallback::StringTimerCallback(ref code_str) => {
let global = this.global();
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut rval = UndefinedValue());
global.evaluate_js_on_global_with_result(
code_str,
rval.handle_mut(),
ScriptFetchOptions::default_classic_script(&global),
global.api_base_url(),
can_gc,
);
},
InternalTimerCallback::FunctionTimerCallback(ref function, ref arguments) => {
let arguments = self.collect_heap_args(arguments);
rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
let _ = function.Call_(this, arguments, value.handle_mut(), Report);
},
};
ScriptThread::set_user_interacting(was_user_interacting);
timers.nesting_level.set(0);
if self.is_interval == IsInterval::Interval &&
timers.active_timers.borrow().contains_key(&self.handle)
{
timers.initialize_and_schedule(&this.global(), self);
}
}
#[allow(unsafe_code)]
fn collect_heap_args<'b>(&self, args: &'b [Heap<JSVal>]) -> Vec<HandleValue<'b>> {
args.iter()
.map(|arg| unsafe { HandleValue::from_raw(arg.handle()) })
.collect()
}
}