mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
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:
commit
8f278109ce
5 changed files with 371 additions and 277 deletions
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue