mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +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 profile_traits::mem;
|
||||||
use script_thread::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThread};
|
use script_thread::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThread};
|
||||||
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEventRequest};
|
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEventRequest};
|
||||||
use timers::{ScheduledCallback, TimerHandle};
|
use timers::{OneshotTimerCallback, OneshotTimerHandle};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
/// A freely-copyable reference to a rooted global object.
|
/// A freely-copyable reference to a rooted global object.
|
||||||
|
@ -224,9 +224,9 @@ impl<'a> GlobalRef<'a> {
|
||||||
/// Schedule the given `callback` to be invoked after at least `duration` milliseconds have
|
/// Schedule the given `callback` to be invoked after at least `duration` milliseconds have
|
||||||
/// passed.
|
/// passed.
|
||||||
pub fn schedule_callback(&self,
|
pub fn schedule_callback(&self,
|
||||||
callback: Box<ScheduledCallback>,
|
callback: OneshotTimerCallback,
|
||||||
duration: MsDuration)
|
duration: MsDuration)
|
||||||
-> TimerHandle {
|
-> OneshotTimerHandle {
|
||||||
match *self {
|
match *self {
|
||||||
GlobalRef::Window(window) => window.schedule_callback(callback, duration),
|
GlobalRef::Window(window) => window.schedule_callback(callback, duration),
|
||||||
GlobalRef::Worker(worker) => worker.schedule_callback(callback, duration),
|
GlobalRef::Worker(worker) => worker.schedule_callback(callback, duration),
|
||||||
|
@ -234,7 +234,7 @@ impl<'a> GlobalRef<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unschedule a previously-scheduled callback.
|
/// Unschedule a previously-scheduled callback.
|
||||||
pub fn unschedule_callback(&self, handle: TimerHandle) {
|
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
|
||||||
match *self {
|
match *self {
|
||||||
GlobalRef::Window(window) => window.unschedule_callback(handle),
|
GlobalRef::Window(window) => window.unschedule_callback(handle),
|
||||||
GlobalRef::Worker(worker) => worker.unschedule_callback(handle),
|
GlobalRef::Worker(worker) => worker.unschedule_callback(handle),
|
||||||
|
|
|
@ -76,7 +76,7 @@ use style::context::ReflowGoal;
|
||||||
use style::error_reporting::ParseErrorReporter;
|
use style::error_reporting::ParseErrorReporter;
|
||||||
use style::selector_impl::PseudoElement;
|
use style::selector_impl::PseudoElement;
|
||||||
use time;
|
use time;
|
||||||
use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle};
|
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::geometry::{self, MAX_RECT};
|
use util::geometry::{self, MAX_RECT};
|
||||||
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
|
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
|
||||||
|
@ -149,7 +149,7 @@ pub struct Window {
|
||||||
local_storage: MutNullableHeap<JS<Storage>>,
|
local_storage: MutNullableHeap<JS<Storage>>,
|
||||||
#[ignore_heap_size_of = "channels are hard"]
|
#[ignore_heap_size_of = "channels are hard"]
|
||||||
scheduler_chan: IpcSender<TimerEventRequest>,
|
scheduler_chan: IpcSender<TimerEventRequest>,
|
||||||
timers: ActiveTimers,
|
timers: OneshotTimers,
|
||||||
|
|
||||||
next_worker_id: Cell<WorkerId>,
|
next_worker_id: Cell<WorkerId>,
|
||||||
|
|
||||||
|
@ -466,7 +466,8 @@ impl WindowMethods for Window {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
|
||||||
fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Window(self),
|
||||||
|
TimerCallback::FunctionTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::NonInterval,
|
IsInterval::NonInterval,
|
||||||
|
@ -475,7 +476,8 @@ impl WindowMethods for Window {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
|
||||||
fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Window(self),
|
||||||
|
TimerCallback::StringTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::NonInterval,
|
IsInterval::NonInterval,
|
||||||
|
@ -484,12 +486,13 @@ impl WindowMethods for Window {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout
|
||||||
fn ClearTimeout(&self, handle: i32) {
|
fn ClearTimeout(&self, handle: i32) {
|
||||||
self.timers.clear_timeout_or_interval(handle);
|
self.timers.clear_timeout_or_interval(GlobalRef::Window(self), handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
||||||
fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Window(self),
|
||||||
|
TimerCallback::FunctionTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::Interval,
|
IsInterval::Interval,
|
||||||
|
@ -498,7 +501,8 @@ impl WindowMethods for Window {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
||||||
fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Window(self),
|
||||||
|
TimerCallback::StringTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::Interval,
|
IsInterval::Interval,
|
||||||
|
@ -1155,13 +1159,13 @@ impl Window {
|
||||||
self.scheduler_chan.clone()
|
self.scheduler_chan.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle {
|
pub fn schedule_callback(&self, callback: OneshotTimerCallback, duration: MsDuration) -> OneshotTimerHandle {
|
||||||
self.timers.schedule_callback(callback,
|
self.timers.schedule_callback(callback,
|
||||||
duration,
|
duration,
|
||||||
TimerSource::FromWindow(self.id.clone()))
|
TimerSource::FromWindow(self.id.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unschedule_callback(&self, handle: TimerHandle) {
|
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
|
||||||
self.timers.unschedule_callback(handle);
|
self.timers.unschedule_callback(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1349,7 +1353,7 @@ impl Window {
|
||||||
session_storage: Default::default(),
|
session_storage: Default::default(),
|
||||||
local_storage: Default::default(),
|
local_storage: Default::default(),
|
||||||
scheduler_chan: scheduler_chan.clone(),
|
scheduler_chan: scheduler_chan.clone(),
|
||||||
timers: ActiveTimers::new(timer_event_chan, scheduler_chan),
|
timers: OneshotTimers::new(timer_event_chan, scheduler_chan),
|
||||||
next_worker_id: Cell::new(WorkerId(0)),
|
next_worker_id: Cell::new(WorkerId(0)),
|
||||||
id: id,
|
id: id,
|
||||||
parent_info: parent_info,
|
parent_info: parent_info,
|
||||||
|
|
|
@ -30,7 +30,7 @@ use std::cell::Cell;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use timers::{ActiveTimers, IsInterval, ScheduledCallback, TimerCallback, TimerHandle};
|
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ pub struct WorkerGlobalScope {
|
||||||
navigator: MutNullableHeap<JS<WorkerNavigator>>,
|
navigator: MutNullableHeap<JS<WorkerNavigator>>,
|
||||||
console: MutNullableHeap<JS<Console>>,
|
console: MutNullableHeap<JS<Console>>,
|
||||||
crypto: MutNullableHeap<JS<Crypto>>,
|
crypto: MutNullableHeap<JS<Crypto>>,
|
||||||
timers: ActiveTimers,
|
timers: OneshotTimers,
|
||||||
#[ignore_heap_size_of = "Defined in std"]
|
#[ignore_heap_size_of = "Defined in std"]
|
||||||
mem_profiler_chan: mem::ProfilerChan,
|
mem_profiler_chan: mem::ProfilerChan,
|
||||||
#[ignore_heap_size_of = "Defined in ipc-channel"]
|
#[ignore_heap_size_of = "Defined in ipc-channel"]
|
||||||
|
@ -98,6 +98,7 @@ impl WorkerGlobalScope {
|
||||||
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
|
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
|
||||||
timer_event_chan: IpcSender<TimerEvent>)
|
timer_event_chan: IpcSender<TimerEvent>)
|
||||||
-> WorkerGlobalScope {
|
-> WorkerGlobalScope {
|
||||||
|
|
||||||
WorkerGlobalScope {
|
WorkerGlobalScope {
|
||||||
eventtarget: EventTarget::new_inherited(),
|
eventtarget: EventTarget::new_inherited(),
|
||||||
next_worker_id: Cell::new(WorkerId(0)),
|
next_worker_id: Cell::new(WorkerId(0)),
|
||||||
|
@ -109,7 +110,7 @@ impl WorkerGlobalScope {
|
||||||
navigator: Default::default(),
|
navigator: Default::default(),
|
||||||
console: Default::default(),
|
console: Default::default(),
|
||||||
crypto: Default::default(),
|
crypto: Default::default(),
|
||||||
timers: ActiveTimers::new(timer_event_chan, init.scheduler_chan.clone()),
|
timers: OneshotTimers::new(timer_event_chan, init.scheduler_chan.clone()),
|
||||||
mem_profiler_chan: init.mem_profiler_chan,
|
mem_profiler_chan: init.mem_profiler_chan,
|
||||||
to_devtools_sender: init.to_devtools_sender,
|
to_devtools_sender: init.to_devtools_sender,
|
||||||
from_devtools_sender: init.from_devtools_sender,
|
from_devtools_sender: init.from_devtools_sender,
|
||||||
|
@ -144,13 +145,13 @@ impl WorkerGlobalScope {
|
||||||
self.scheduler_chan.clone()
|
self.scheduler_chan.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schedule_callback(&self, callback: Box<ScheduledCallback>, duration: MsDuration) -> TimerHandle {
|
pub fn schedule_callback(&self, callback: OneshotTimerCallback, duration: MsDuration) -> OneshotTimerHandle {
|
||||||
self.timers.schedule_callback(callback,
|
self.timers.schedule_callback(callback,
|
||||||
duration,
|
duration,
|
||||||
TimerSource::FromWorker)
|
TimerSource::FromWorker)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unschedule_callback(&self, handle: TimerHandle) {
|
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
|
||||||
self.timers.unschedule_callback(handle);
|
self.timers.unschedule_callback(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +251,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
||||||
fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetTimeout(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
|
||||||
|
TimerCallback::FunctionTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::NonInterval,
|
IsInterval::NonInterval,
|
||||||
|
@ -259,7 +261,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
||||||
fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetTimeout_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
|
||||||
|
TimerCallback::StringTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::NonInterval,
|
IsInterval::NonInterval,
|
||||||
|
@ -268,12 +271,13 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
|
||||||
fn ClearTimeout(&self, handle: i32) {
|
fn ClearTimeout(&self, handle: i32) {
|
||||||
self.timers.clear_timeout_or_interval(handle);
|
self.timers.clear_timeout_or_interval(GlobalRef::Worker(self), handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
||||||
fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetInterval(&self, _cx: *mut JSContext, callback: Rc<Function>, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::FunctionTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
|
||||||
|
TimerCallback::FunctionTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::Interval,
|
IsInterval::Interval,
|
||||||
|
@ -282,7 +286,8 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
|
||||||
fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
fn SetInterval_(&self, _cx: *mut JSContext, callback: DOMString, timeout: i32, args: Vec<HandleValue>) -> i32 {
|
||||||
self.timers.set_timeout_or_interval(TimerCallback::StringTimerCallback(callback),
|
self.timers.set_timeout_or_interval(GlobalRef::Worker(self),
|
||||||
|
TimerCallback::StringTimerCallback(callback),
|
||||||
args,
|
args,
|
||||||
timeout,
|
timeout,
|
||||||
IsInterval::Interval,
|
IsInterval::Interval,
|
||||||
|
|
|
@ -59,7 +59,7 @@ use std::sync::mpsc::channel;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use string_cache::Atom;
|
use string_cache::Atom;
|
||||||
use time;
|
use time;
|
||||||
use timers::{ScheduledCallback, TimerHandle};
|
use timers::{OneshotTimerCallback, OneshotTimerHandle};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use url::percent_encoding::{utf8_percent_encode, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET};
|
use url::percent_encoding::{utf8_percent_encode, USERNAME_ENCODE_SET, PASSWORD_ENCODE_SET};
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
|
@ -146,7 +146,7 @@ pub struct XMLHttpRequest {
|
||||||
upload_events: Cell<bool>,
|
upload_events: Cell<bool>,
|
||||||
send_flag: Cell<bool>,
|
send_flag: Cell<bool>,
|
||||||
|
|
||||||
timeout_cancel: DOMRefCell<Option<TimerHandle>>,
|
timeout_cancel: DOMRefCell<Option<OneshotTimerHandle>>,
|
||||||
fetch_time: Cell<i64>,
|
fetch_time: Cell<i64>,
|
||||||
generation_id: Cell<GenerationId>,
|
generation_id: Cell<GenerationId>,
|
||||||
response_status: Cell<Result<(), ()>>,
|
response_status: Cell<Result<(), ()>>,
|
||||||
|
@ -1055,39 +1055,15 @@ impl XMLHttpRequest {
|
||||||
self.dispatch_progress_event(false, type_, len, total);
|
self.dispatch_progress_event(false, type_, len, total);
|
||||||
}
|
}
|
||||||
fn set_timeout(&self, duration_ms: u32) {
|
fn set_timeout(&self, duration_ms: u32) {
|
||||||
#[derive(JSTraceable, HeapSizeOf)]
|
|
||||||
struct ScheduledXHRTimeout {
|
|
||||||
#[ignore_heap_size_of = "Because it is non-owning"]
|
|
||||||
xhr: Trusted<XMLHttpRequest>,
|
|
||||||
generation_id: GenerationId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ScheduledCallback for ScheduledXHRTimeout {
|
|
||||||
fn invoke(self: Box<Self>) {
|
|
||||||
let this = *self;
|
|
||||||
let xhr = this.xhr.root();
|
|
||||||
if xhr.ready_state.get() != XMLHttpRequestState::Done {
|
|
||||||
xhr.process_partial_response(XHRProgress::Errored(this.generation_id, Error::Timeout));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn box_clone(&self) -> Box<ScheduledCallback> {
|
|
||||||
box ScheduledXHRTimeout {
|
|
||||||
xhr: self.xhr.clone(),
|
|
||||||
generation_id: self.generation_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets up the object to timeout in a given number of milliseconds
|
// Sets up the object to timeout in a given number of milliseconds
|
||||||
// This will cancel all previous timeouts
|
// This will cancel all previous timeouts
|
||||||
let global = self.global();
|
let global = self.global();
|
||||||
let callback = ScheduledXHRTimeout {
|
let callback = OneshotTimerCallback::XhrTimeout(XHRTimeoutCallback {
|
||||||
xhr: Trusted::new(self, global.r().networking_task_source()),
|
xhr: Trusted::new(self, global.r().networking_task_source()),
|
||||||
generation_id: self.generation_id.get(),
|
generation_id: self.generation_id.get(),
|
||||||
};
|
});
|
||||||
let duration = Length::new(duration_ms as u64);
|
let duration = Length::new(duration_ms as u64);
|
||||||
*self.timeout_cancel.borrow_mut() = Some(global.r().schedule_callback(box callback, duration));
|
*self.timeout_cancel.borrow_mut() = Some(global.r().schedule_callback(callback, duration));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_timeout(&self) {
|
fn cancel_timeout(&self) {
|
||||||
|
@ -1368,6 +1344,22 @@ impl XMLHttpRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
|
pub struct XHRTimeoutCallback {
|
||||||
|
#[ignore_heap_size_of = "Because it is non-owning"]
|
||||||
|
xhr: Trusted<XMLHttpRequest>,
|
||||||
|
generation_id: GenerationId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XHRTimeoutCallback {
|
||||||
|
pub fn invoke(self) {
|
||||||
|
let xhr = self.xhr.root();
|
||||||
|
if xhr.ready_state.get() != XMLHttpRequestState::Done {
|
||||||
|
xhr.process_partial_response(XHRProgress::Errored(self.generation_id, Error::Timeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
trait Extractable {
|
trait Extractable {
|
||||||
fn extract(&self) -> (Vec<u8>, Option<DOMString>);
|
fn extract(&self) -> (Vec<u8>, Option<DOMString>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
use dom::bindings::callback::ExceptionHandling::Report;
|
use dom::bindings::callback::ExceptionHandling::Report;
|
||||||
use dom::bindings::cell::DOMRefCell;
|
use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
||||||
|
use dom::bindings::global::GlobalRef;
|
||||||
use dom::bindings::reflector::Reflectable;
|
use dom::bindings::reflector::Reflectable;
|
||||||
use dom::bindings::trace::JSTraceable;
|
use dom::bindings::trace::JSTraceable;
|
||||||
use dom::window::ScriptHelpers;
|
use dom::window::ScriptHelpers;
|
||||||
|
use dom::xmlhttprequest::XHRTimeoutCallback;
|
||||||
use euclid::length::Length;
|
use euclid::length::Length;
|
||||||
use heapsize::HeapSizeOf;
|
use heapsize::HeapSizeOf;
|
||||||
use ipc_channel::ipc::IpcSender;
|
use ipc_channel::ipc::IpcSender;
|
||||||
|
@ -18,22 +20,24 @@ use script_traits::{MsDuration, precise_time_ms};
|
||||||
use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSource};
|
use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSource};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::cmp::{self, Ord, Ordering};
|
use std::cmp::{self, Ord, Ordering};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use util::str::DOMString;
|
use util::str::DOMString;
|
||||||
|
|
||||||
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord, Debug)]
|
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord, Debug)]
|
||||||
pub struct TimerHandle(i32);
|
pub struct OneshotTimerHandle(i32);
|
||||||
|
|
||||||
#[derive(JSTraceable, HeapSizeOf)]
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
#[privatize]
|
#[privatize]
|
||||||
pub struct ActiveTimers {
|
pub struct OneshotTimers {
|
||||||
|
js_timers: JsTimers,
|
||||||
#[ignore_heap_size_of = "Defined in std"]
|
#[ignore_heap_size_of = "Defined in std"]
|
||||||
timer_event_chan: IpcSender<TimerEvent>,
|
timer_event_chan: IpcSender<TimerEvent>,
|
||||||
#[ignore_heap_size_of = "Defined in std"]
|
#[ignore_heap_size_of = "Defined in std"]
|
||||||
scheduler_chan: IpcSender<TimerEventRequest>,
|
scheduler_chan: IpcSender<TimerEventRequest>,
|
||||||
next_timer_handle: Cell<TimerHandle>,
|
next_timer_handle: Cell<OneshotTimerHandle>,
|
||||||
timers: DOMRefCell<Vec<Timer>>,
|
timers: DOMRefCell<Vec<OneshotTimer>>,
|
||||||
suspended_since: Cell<Option<MsDuration>>,
|
suspended_since: Cell<Option<MsDuration>>,
|
||||||
/// Initially 0, increased whenever the associated document is reactivated
|
/// Initially 0, increased whenever the associated document is reactivated
|
||||||
/// by the amount of ms the document was inactive. The current time can be
|
/// by the amount of ms the document was inactive. The current time can be
|
||||||
|
@ -47,185 +51,104 @@ pub struct ActiveTimers {
|
||||||
/// - a timer was added with an earlier callback time. In this case the
|
/// - a timer was added with an earlier callback time. In this case the
|
||||||
/// original timer is rescheduled when it is the next one to get called.
|
/// original timer is rescheduled when it is the next one to get called.
|
||||||
expected_event_id: Cell<TimerEventId>,
|
expected_event_id: Cell<TimerEventId>,
|
||||||
/// The nesting level of the currently executing timer thread or 0.
|
|
||||||
nesting_level: Cell<u32>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Holder for the various JS values associated with setTimeout
|
|
||||||
// (ie. function value to invoke and all arguments to pass
|
|
||||||
// to the function when calling it)
|
|
||||||
// TODO: Handle rooting during fire_timer when movable GC is turned on
|
|
||||||
#[derive(JSTraceable, HeapSizeOf)]
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
#[privatize]
|
#[privatize]
|
||||||
struct Timer {
|
struct OneshotTimer {
|
||||||
handle: TimerHandle,
|
handle: OneshotTimerHandle,
|
||||||
source: TimerSource,
|
source: TimerSource,
|
||||||
callback: InternalTimerCallback,
|
callback: OneshotTimerCallback,
|
||||||
is_interval: IsInterval,
|
scheduled_for: MsDuration,
|
||||||
nesting_level: u32,
|
|
||||||
duration: MsDuration,
|
|
||||||
next_call: MsDuration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enum allowing more descriptive values for the is_interval field
|
// This enum is required to work around the fact that trait objects do not support generic methods.
|
||||||
#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)]
|
// A replacement trait would have a method such as
|
||||||
pub enum IsInterval {
|
// `invoke<T: Reflectable>(self: Box<Self>, this: &T, js_timers: &JsTimers);`.
|
||||||
Interval,
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
NonInterval,
|
pub enum OneshotTimerCallback {
|
||||||
|
XhrTimeout(XHRTimeoutCallback),
|
||||||
|
JsTimer(JsTimerTask),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for Timer {
|
impl OneshotTimerCallback {
|
||||||
fn cmp(&self, other: &Timer) -> Ordering {
|
fn invoke<T: Reflectable>(self, this: &T, js_timers: &JsTimers) {
|
||||||
match self.next_call.cmp(&other.next_call).reverse() {
|
match self {
|
||||||
|
OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(),
|
||||||
|
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for OneshotTimer {
|
||||||
|
fn cmp(&self, other: &OneshotTimer) -> Ordering {
|
||||||
|
match self.scheduled_for.cmp(&other.scheduled_for).reverse() {
|
||||||
Ordering::Equal => self.handle.cmp(&other.handle).reverse(),
|
Ordering::Equal => self.handle.cmp(&other.handle).reverse(),
|
||||||
res => res
|
res => res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for Timer {
|
impl PartialOrd for OneshotTimer {
|
||||||
fn partial_cmp(&self, other: &Timer) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &OneshotTimer) -> Option<Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Timer {}
|
impl Eq for OneshotTimer {}
|
||||||
impl PartialEq for Timer {
|
impl PartialEq for OneshotTimer {
|
||||||
fn eq(&self, other: &Timer) -> bool {
|
fn eq(&self, other: &OneshotTimer) -> bool {
|
||||||
self as *const Timer == other as *const Timer
|
self as *const OneshotTimer == other as *const OneshotTimer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl OneshotTimers {
|
||||||
pub enum TimerCallback {
|
|
||||||
StringTimerCallback(DOMString),
|
|
||||||
FunctionTimerCallback(Rc<Function>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(JSTraceable, Clone)]
|
|
||||||
enum InternalTimerCallback {
|
|
||||||
StringTimerCallback(DOMString),
|
|
||||||
FunctionTimerCallback(Rc<Function>, Rc<Vec<Heap<JSVal>>>),
|
|
||||||
InternalCallback(Box<ScheduledCallback>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HeapSizeOf for InternalTimerCallback {
|
|
||||||
fn heap_size_of_children(&self) -> usize {
|
|
||||||
// FIXME: Rc<T> isn't HeapSizeOf and we can't ignore it due to #6870 and #6871
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ScheduledCallback: JSTraceable + HeapSizeOf {
|
|
||||||
fn invoke(self: Box<Self>);
|
|
||||||
|
|
||||||
fn box_clone(&self) -> Box<ScheduledCallback>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Box<ScheduledCallback> {
|
|
||||||
fn clone(&self) -> Box<ScheduledCallback> {
|
|
||||||
self.box_clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActiveTimers {
|
|
||||||
pub fn new(timer_event_chan: IpcSender<TimerEvent>,
|
pub fn new(timer_event_chan: IpcSender<TimerEvent>,
|
||||||
scheduler_chan: IpcSender<TimerEventRequest>)
|
scheduler_chan: IpcSender<TimerEventRequest>)
|
||||||
-> ActiveTimers {
|
-> OneshotTimers {
|
||||||
ActiveTimers {
|
OneshotTimers {
|
||||||
|
js_timers: JsTimers::new(),
|
||||||
timer_event_chan: timer_event_chan,
|
timer_event_chan: timer_event_chan,
|
||||||
scheduler_chan: scheduler_chan,
|
scheduler_chan: scheduler_chan,
|
||||||
next_timer_handle: Cell::new(TimerHandle(1)),
|
next_timer_handle: Cell::new(OneshotTimerHandle(1)),
|
||||||
timers: DOMRefCell::new(Vec::new()),
|
timers: DOMRefCell::new(Vec::new()),
|
||||||
suspended_since: Cell::new(None),
|
suspended_since: Cell::new(None),
|
||||||
suspension_offset: Cell::new(Length::new(0)),
|
suspension_offset: Cell::new(Length::new(0)),
|
||||||
expected_event_id: Cell::new(TimerEventId(0)),
|
expected_event_id: Cell::new(TimerEventId(0)),
|
||||||
nesting_level: Cell::new(0),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
|
||||||
pub fn set_timeout_or_interval(&self,
|
|
||||||
callback: TimerCallback,
|
|
||||||
arguments: Vec<HandleValue>,
|
|
||||||
timeout: i32,
|
|
||||||
is_interval: IsInterval,
|
|
||||||
source: TimerSource)
|
|
||||||
-> i32 {
|
|
||||||
let callback = match callback {
|
|
||||||
TimerCallback::StringTimerCallback(code_str) =>
|
|
||||||
InternalTimerCallback::StringTimerCallback(code_str),
|
|
||||||
TimerCallback::FunctionTimerCallback(function) => {
|
|
||||||
// This is a bit complicated, but this ensures that the vector's
|
|
||||||
// buffer isn't reallocated (and moved) after setting the Heap values
|
|
||||||
let mut args = Vec::with_capacity(arguments.len());
|
|
||||||
for _ in 0..arguments.len() {
|
|
||||||
args.push(Heap::default());
|
|
||||||
}
|
|
||||||
for (i, item) in arguments.iter().enumerate() {
|
|
||||||
args.get_mut(i).unwrap().set(item.get());
|
|
||||||
}
|
|
||||||
InternalTimerCallback::FunctionTimerCallback(function, Rc::new(args))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let timeout = cmp::max(0, timeout);
|
|
||||||
// step 7
|
|
||||||
let duration = self.clamp_duration(Length::new(timeout as u64));
|
|
||||||
|
|
||||||
let TimerHandle(handle) = self.schedule_internal_callback(callback, duration, is_interval, source);
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn schedule_callback(&self,
|
pub fn schedule_callback(&self,
|
||||||
callback: Box<ScheduledCallback>,
|
callback: OneshotTimerCallback,
|
||||||
duration: MsDuration,
|
duration: MsDuration,
|
||||||
source: TimerSource) -> TimerHandle {
|
source: TimerSource)
|
||||||
self.schedule_internal_callback(InternalTimerCallback::InternalCallback(callback),
|
-> OneshotTimerHandle {
|
||||||
duration,
|
let new_handle = self.next_timer_handle.get();
|
||||||
IsInterval::NonInterval,
|
self.next_timer_handle.set(OneshotTimerHandle(new_handle.0 + 1));
|
||||||
source)
|
|
||||||
}
|
|
||||||
|
|
||||||
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
let scheduled_for = self.base_time() + duration;
|
||||||
fn schedule_internal_callback(&self,
|
|
||||||
callback: InternalTimerCallback,
|
|
||||||
duration: MsDuration,
|
|
||||||
is_interval: IsInterval,
|
|
||||||
source: TimerSource) -> TimerHandle {
|
|
||||||
// step 3
|
|
||||||
let TimerHandle(new_handle) = self.next_timer_handle.get();
|
|
||||||
self.next_timer_handle.set(TimerHandle(new_handle + 1));
|
|
||||||
|
|
||||||
let next_call = self.base_time() + duration;
|
let timer = OneshotTimer {
|
||||||
|
handle: new_handle,
|
||||||
let timer = Timer {
|
|
||||||
handle: TimerHandle(new_handle),
|
|
||||||
source: source,
|
source: source,
|
||||||
callback: callback,
|
callback: callback,
|
||||||
is_interval: is_interval,
|
scheduled_for: scheduled_for,
|
||||||
duration: duration,
|
|
||||||
// step 6
|
|
||||||
nesting_level: self.nesting_level.get() + 1,
|
|
||||||
next_call: next_call,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.insert_timer(timer);
|
{
|
||||||
|
let mut timers = self.timers.borrow_mut();
|
||||||
|
let insertion_index = timers.binary_search(&timer).err().unwrap();
|
||||||
|
timers.insert(insertion_index, timer);
|
||||||
|
}
|
||||||
|
|
||||||
let TimerHandle(max_handle) = self.timers.borrow().last().unwrap().handle;
|
if self.is_next_timer(new_handle) {
|
||||||
if max_handle == new_handle {
|
|
||||||
self.schedule_timer_call();
|
self.schedule_timer_call();
|
||||||
}
|
}
|
||||||
|
|
||||||
// step 10
|
new_handle
|
||||||
TimerHandle(new_handle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_timeout_or_interval(&self, handle: i32) {
|
pub fn unschedule_callback(&self, handle: OneshotTimerHandle) {
|
||||||
self.unschedule_callback(TimerHandle(handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unschedule_callback(&self, handle: TimerHandle) {
|
|
||||||
let was_next = self.is_next_timer(handle);
|
let was_next = self.is_next_timer(handle);
|
||||||
|
|
||||||
self.timers.borrow_mut().retain(|t| t.handle != handle);
|
self.timers.borrow_mut().retain(|t| t.handle != handle);
|
||||||
|
@ -236,12 +159,17 @@ impl ActiveTimers {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
|
||||||
#[allow(unsafe_code)]
|
match self.timers.borrow().last() {
|
||||||
|
None => false,
|
||||||
|
Some(ref max_timer) => max_timer.handle == handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fire_timer<T: Reflectable>(&self, id: TimerEventId, this: &T) {
|
pub fn fire_timer<T: Reflectable>(&self, id: TimerEventId, this: &T) {
|
||||||
let expected_id = self.expected_event_id.get();
|
let expected_id = self.expected_event_id.get();
|
||||||
if expected_id != id {
|
if expected_id != id {
|
||||||
debug!("ignoring timer fire event {:?} (expected {:?}", id, expected_id);
|
debug!("ignoring timer fire event {:?} (expected {:?})", id, expected_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,16 +178,16 @@ impl ActiveTimers {
|
||||||
let base_time = self.base_time();
|
let base_time = self.base_time();
|
||||||
|
|
||||||
// Since the event id was the expected one, at least one timer should be due.
|
// Since the event id was the expected one, at least one timer should be due.
|
||||||
assert!(base_time >= self.timers.borrow().last().unwrap().next_call);
|
assert!(base_time >= self.timers.borrow().last().unwrap().scheduled_for);
|
||||||
|
|
||||||
// select timers to run to prevent firing timers
|
// select timers to run to prevent firing timers
|
||||||
// that were installed during fire of another timer
|
// that were installed during fire of another timer
|
||||||
let mut timers_to_run: Vec<Timer> = Vec::new();
|
let mut timers_to_run = Vec::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut timers = self.timers.borrow_mut();
|
let mut timers = self.timers.borrow_mut();
|
||||||
|
|
||||||
if timers.is_empty() || timers.last().unwrap().next_call > base_time {
|
if timers.is_empty() || timers.last().unwrap().scheduled_for > base_time {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,79 +195,19 @@ impl ActiveTimers {
|
||||||
}
|
}
|
||||||
|
|
||||||
for timer in timers_to_run {
|
for timer in timers_to_run {
|
||||||
|
let callback = timer.callback;
|
||||||
let callback = timer.callback.clone();
|
callback.invoke(this, &self.js_timers);
|
||||||
|
|
||||||
// prep for step 6 in nested set_timeout_or_interval calls
|
|
||||||
self.nesting_level.set(timer.nesting_level);
|
|
||||||
|
|
||||||
// step 4.3
|
|
||||||
if timer.is_interval == IsInterval::Interval {
|
|
||||||
let mut timer = timer;
|
|
||||||
|
|
||||||
// step 7
|
|
||||||
timer.duration = self.clamp_duration(timer.duration);
|
|
||||||
// step 8, 9
|
|
||||||
timer.nesting_level += 1;
|
|
||||||
timer.next_call = base_time + timer.duration;
|
|
||||||
self.insert_timer(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// step 14
|
|
||||||
match callback {
|
|
||||||
InternalTimerCallback::StringTimerCallback(code_str) => {
|
|
||||||
let cx = this.global().r().get_cx();
|
|
||||||
let mut rval = RootedValue::new(cx, UndefinedValue());
|
|
||||||
|
|
||||||
this.evaluate_js_on_global_with_result(&code_str, rval.handle_mut());
|
|
||||||
},
|
|
||||||
InternalTimerCallback::FunctionTimerCallback(function, arguments) => {
|
|
||||||
let arguments: Vec<JSVal> = arguments.iter().map(|arg| arg.get()).collect();
|
|
||||||
let arguments = arguments.iter().by_ref().map(|arg| unsafe {
|
|
||||||
HandleValue::from_marked_location(arg)
|
|
||||||
}).collect();
|
|
||||||
|
|
||||||
let _ = function.Call_(this, arguments, Report);
|
|
||||||
},
|
|
||||||
InternalTimerCallback::InternalCallback(callback) => {
|
|
||||||
callback.invoke();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
self.nesting_level.set(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.schedule_timer_call();
|
self.schedule_timer_call();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_timer(&self, timer: Timer) {
|
fn base_time(&self) -> MsDuration {
|
||||||
let mut timers = self.timers.borrow_mut();
|
let offset = self.suspension_offset.get();
|
||||||
let insertion_index = timers.binary_search(&timer).err().unwrap();
|
|
||||||
timers.insert(insertion_index, timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_next_timer(&self, handle: TimerHandle) -> bool {
|
match self.suspended_since.get() {
|
||||||
match self.timers.borrow().last() {
|
Some(time) => time - offset,
|
||||||
None => false,
|
None => precise_time_ms() - offset,
|
||||||
Some(ref max_timer) => max_timer.handle == handle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn schedule_timer_call(&self) {
|
|
||||||
if self.suspended_since.get().is_some() {
|
|
||||||
// The timer will be scheduled when the pipeline is thawed.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timers = self.timers.borrow();
|
|
||||||
|
|
||||||
if let Some(timer) = timers.last() {
|
|
||||||
let expected_event_id = self.invalidate_expected_event_id();
|
|
||||||
|
|
||||||
let delay = Length::new(timer.next_call.get().saturating_sub(precise_time_ms().get()));
|
|
||||||
let request = TimerEventRequest(self.timer_event_chan.clone(), timer.source,
|
|
||||||
expected_event_id, delay);
|
|
||||||
self.scheduler_chan.send(request).unwrap();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,24 +232,22 @@ impl ActiveTimers {
|
||||||
self.schedule_timer_call();
|
self.schedule_timer_call();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn base_time(&self) -> MsDuration {
|
fn schedule_timer_call(&self) {
|
||||||
let offset = self.suspension_offset.get();
|
if self.suspended_since.get().is_some() {
|
||||||
|
// The timer will be scheduled when the pipeline is thawed.
|
||||||
match self.suspended_since.get() {
|
return;
|
||||||
Some(time) => time - offset,
|
|
||||||
None => precise_time_ms() - offset,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// see step 7 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
let timers = self.timers.borrow();
|
||||||
fn clamp_duration(&self, unclamped: MsDuration) -> MsDuration {
|
|
||||||
let ms = if self.nesting_level.get() > 5 {
|
|
||||||
4
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
cmp::max(Length::new(ms), unclamped)
|
if let Some(timer) = timers.last() {
|
||||||
|
let expected_event_id = self.invalidate_expected_event_id();
|
||||||
|
|
||||||
|
let delay = Length::new(timer.scheduled_for.get().saturating_sub(precise_time_ms().get()));
|
||||||
|
let request = TimerEventRequest(self.timer_event_chan.clone(), timer.source,
|
||||||
|
expected_event_id, delay);
|
||||||
|
self.scheduler_chan.send(request).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn invalidate_expected_event_id(&self) -> TimerEventId {
|
fn invalidate_expected_event_id(&self) -> TimerEventId {
|
||||||
|
@ -391,4 +257,231 @@ impl ActiveTimers {
|
||||||
self.expected_event_id.set(next_id);
|
self.expected_event_id.set(next_id);
|
||||||
next_id
|
next_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_timeout_or_interval(&self,
|
||||||
|
global: GlobalRef,
|
||||||
|
callback: TimerCallback,
|
||||||
|
arguments: Vec<HandleValue>,
|
||||||
|
timeout: i32,
|
||||||
|
is_interval: IsInterval,
|
||||||
|
source: TimerSource)
|
||||||
|
-> i32 {
|
||||||
|
|
||||||
|
self.js_timers.set_timeout_or_interval(global,
|
||||||
|
callback,
|
||||||
|
arguments,
|
||||||
|
timeout,
|
||||||
|
is_interval,
|
||||||
|
source)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_timeout_or_interval(&self, global: GlobalRef, handle: i32) {
|
||||||
|
self.js_timers.clear_timeout_or_interval(global, handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct JsTimerHandle(i32);
|
||||||
|
|
||||||
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
|
#[privatize]
|
||||||
|
pub struct JsTimers {
|
||||||
|
next_timer_handle: Cell<JsTimerHandle>,
|
||||||
|
active_timers: DOMRefCell<HashMap<JsTimerHandle, JsTimerEntry>>,
|
||||||
|
/// The nesting level of the currently executing timer task or 0.
|
||||||
|
nesting_level: Cell<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
|
struct JsTimerEntry {
|
||||||
|
oneshot_handle: OneshotTimerHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holder for the various JS values associated with setTimeout
|
||||||
|
// (ie. function value to invoke and all arguments to pass
|
||||||
|
// to the function when calling it)
|
||||||
|
// TODO: Handle rooting during invocation when movable GC is turned on
|
||||||
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
|
pub struct JsTimerTask {
|
||||||
|
#[ignore_heap_size_of = "Because it is non-owning"]
|
||||||
|
handle: JsTimerHandle,
|
||||||
|
source: TimerSource,
|
||||||
|
callback: InternalTimerCallback,
|
||||||
|
is_interval: IsInterval,
|
||||||
|
nesting_level: u32,
|
||||||
|
duration: MsDuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enum allowing more descriptive values for the is_interval field
|
||||||
|
#[derive(JSTraceable, PartialEq, Copy, Clone, HeapSizeOf)]
|
||||||
|
pub enum IsInterval {
|
||||||
|
Interval,
|
||||||
|
NonInterval,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum TimerCallback {
|
||||||
|
StringTimerCallback(DOMString),
|
||||||
|
FunctionTimerCallback(Rc<Function>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(JSTraceable, Clone)]
|
||||||
|
enum InternalTimerCallback {
|
||||||
|
StringTimerCallback(DOMString),
|
||||||
|
FunctionTimerCallback(Rc<Function>, Rc<Vec<Heap<JSVal>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HeapSizeOf for InternalTimerCallback {
|
||||||
|
fn heap_size_of_children(&self) -> usize {
|
||||||
|
// FIXME: Rc<T> isn't HeapSizeOf and we can't ignore it due to #6870 and #6871
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsTimers {
|
||||||
|
pub fn new() -> JsTimers {
|
||||||
|
JsTimers {
|
||||||
|
next_timer_handle: Cell::new(JsTimerHandle(1)),
|
||||||
|
active_timers: DOMRefCell::new(HashMap::new()),
|
||||||
|
nesting_level: Cell::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
||||||
|
pub fn set_timeout_or_interval(&self,
|
||||||
|
global: GlobalRef,
|
||||||
|
callback: TimerCallback,
|
||||||
|
arguments: Vec<HandleValue>,
|
||||||
|
timeout: i32,
|
||||||
|
is_interval: IsInterval,
|
||||||
|
source: TimerSource)
|
||||||
|
-> i32 {
|
||||||
|
let callback = match callback {
|
||||||
|
TimerCallback::StringTimerCallback(code_str) =>
|
||||||
|
InternalTimerCallback::StringTimerCallback(code_str),
|
||||||
|
TimerCallback::FunctionTimerCallback(function) => {
|
||||||
|
// This is a bit complicated, but this ensures that the vector's
|
||||||
|
// buffer isn't reallocated (and moved) after setting the Heap values
|
||||||
|
let mut args = Vec::with_capacity(arguments.len());
|
||||||
|
for _ in 0..arguments.len() {
|
||||||
|
args.push(Heap::default());
|
||||||
|
}
|
||||||
|
for (i, item) in arguments.iter().enumerate() {
|
||||||
|
args.get_mut(i).unwrap().set(item.get());
|
||||||
|
}
|
||||||
|
InternalTimerCallback::FunctionTimerCallback(function, Rc::new(args))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// step 2
|
||||||
|
let JsTimerHandle(new_handle) = self.next_timer_handle.get();
|
||||||
|
self.next_timer_handle.set(JsTimerHandle(new_handle + 1));
|
||||||
|
|
||||||
|
// step 3 as part of initialize_and_schedule below
|
||||||
|
|
||||||
|
// step 4
|
||||||
|
let mut task = JsTimerTask {
|
||||||
|
handle: JsTimerHandle(new_handle),
|
||||||
|
source: source,
|
||||||
|
callback: callback,
|
||||||
|
is_interval: is_interval,
|
||||||
|
nesting_level: 0,
|
||||||
|
duration: Length::new(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// step 5
|
||||||
|
task.duration = Length::new(cmp::max(0, timeout) as u64);
|
||||||
|
|
||||||
|
// step 3, 6-9, 11-14
|
||||||
|
self.initialize_and_schedule(global, task);
|
||||||
|
|
||||||
|
// step 10
|
||||||
|
new_handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_timeout_or_interval(&self, global: GlobalRef, handle: i32) {
|
||||||
|
let mut active_timers = self.active_timers.borrow_mut();
|
||||||
|
|
||||||
|
if let Some(entry) = active_timers.remove(&JsTimerHandle(handle)) {
|
||||||
|
global.unschedule_callback(entry.oneshot_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
||||||
|
fn initialize_and_schedule(&self, global: GlobalRef, mut task: JsTimerTask) {
|
||||||
|
let handle = task.handle;
|
||||||
|
let mut active_timers = self.active_timers.borrow_mut();
|
||||||
|
|
||||||
|
// step 6
|
||||||
|
let nesting_level = self.nesting_level.get();
|
||||||
|
|
||||||
|
// step 7
|
||||||
|
let duration = clamp_duration(nesting_level, task.duration);
|
||||||
|
|
||||||
|
// step 8, 9
|
||||||
|
task.nesting_level = nesting_level + 1;
|
||||||
|
|
||||||
|
// essentially step 11-14
|
||||||
|
let callback = OneshotTimerCallback::JsTimer(task);
|
||||||
|
let oneshot_handle = global.schedule_callback(callback, duration);
|
||||||
|
|
||||||
|
// step 3
|
||||||
|
let entry = active_timers.entry(handle).or_insert(JsTimerEntry {
|
||||||
|
oneshot_handle: oneshot_handle,
|
||||||
|
});
|
||||||
|
entry.oneshot_handle = oneshot_handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see step 7 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
||||||
|
fn clamp_duration(nesting_level: u32, unclamped: MsDuration) -> MsDuration {
|
||||||
|
let lower_bound = if nesting_level > 5 {
|
||||||
|
4
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
cmp::max(Length::new(lower_bound), unclamped)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsTimerTask {
|
||||||
|
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
|
||||||
|
#[allow(unsafe_code)]
|
||||||
|
pub fn invoke<T: Reflectable>(self, this: &T, timers: &JsTimers) {
|
||||||
|
// step 4.1 can be ignored, because we proactively prevent execution
|
||||||
|
// of this task when its scheduled execution is canceled.
|
||||||
|
|
||||||
|
// prep for step 6 in nested set_timeout_or_interval calls
|
||||||
|
timers.nesting_level.set(self.nesting_level);
|
||||||
|
|
||||||
|
// step 4.2
|
||||||
|
match *&self.callback {
|
||||||
|
InternalTimerCallback::StringTimerCallback(ref code_str) => {
|
||||||
|
let cx = this.global().r().get_cx();
|
||||||
|
let mut rval = RootedValue::new(cx, UndefinedValue());
|
||||||
|
|
||||||
|
this.evaluate_js_on_global_with_result(code_str, rval.handle_mut());
|
||||||
|
},
|
||||||
|
InternalTimerCallback::FunctionTimerCallback(ref function, ref arguments) => {
|
||||||
|
let arguments: Vec<JSVal> = arguments.iter().map(|arg| arg.get()).collect();
|
||||||
|
let arguments = arguments.iter().by_ref().map(|arg| unsafe {
|
||||||
|
HandleValue::from_marked_location(arg)
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let _ = function.Call_(this, arguments, Report);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// reset nesting level (see above)
|
||||||
|
timers.nesting_level.set(0);
|
||||||
|
|
||||||
|
// step 4.3
|
||||||
|
// Since we choose proactively prevent execution (see 4.1 above), we must only
|
||||||
|
// reschedule repeating timers when they were not canceled as part of step 4.2.
|
||||||
|
if self.is_interval == IsInterval::Interval &&
|
||||||
|
timers.active_timers.borrow().contains_key(&self.handle) {
|
||||||
|
|
||||||
|
timers.initialize_and_schedule(this.global().r(), self);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue