fune/servo/components/script/timers.rs
Josh Matthews 3df7a65e2a servo: Merge #4057 - Enable refcounting arbitrary DOM types (from jdm:refcountdom); r=Ms2ger
This replaces the specialized TrustedXHRAddress and TrustedWorkerAddress code that was used for the same purpose. A non-zero refcount pins the given DOM object's reflector and prevents it from being GCed even when there are no other outstanding references visible to SpiderMonkey. This will enable us to implement asynchronous operations that refer to particular DOM objects (such as "queue a task to fire a simple event named load at the iframe element" from the spec) safely and conveniently, and paves the way for things like asynchronous network responses.

Some concerns about the resulting size of XHR progress messages have been expressed, but I believe optimizations to reduce that can be implemented in subsequent PRs.

r? @Ms2ger - note in particular the changes to the worker lifetime code. I couldn't figure out how to achieve an identical lifetime to the previous addref/release pairing, and I also was having trouble figuring out why the existing setup was safe. The new implementation now holds the main script task Worker object alive via the TrustedWorkerAddress field in the dedicated worker global scope, which is a significant difference.

Source-Repo: https://github.com/servo/servo
Source-Revision: 2c259f477c41331e66beab8bda865971982a1ff4
2014-12-29 11:57:45 -07:00

185 lines
5.7 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use dom::bindings::cell::DOMRefCell;
use dom::bindings::callback::ExceptionHandling::ReportExceptions;
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::js::JSRef;
use dom::bindings::utils::Reflectable;
use script_task::{ScriptChan, ScriptMsg, TimerSource};
use servo_util::task::spawn_named;
use js::jsval::JSVal;
use std::cell::Cell;
use std::cmp;
use std::collections::HashMap;
use std::comm::{channel, Sender};
use std::comm::Select;
use std::hash::{Hash, sip};
use std::io::timer::Timer;
use std::time::duration::Duration;
#[deriving(PartialEq, Eq)]
#[jstraceable]
pub struct TimerId(i32);
#[jstraceable]
#[privatize]
struct TimerHandle {
handle: TimerId,
data: TimerData,
cancel_chan: Option<Sender<()>>,
}
impl Hash for TimerId {
fn hash(&self, state: &mut sip::SipState) {
let TimerId(id) = *self;
id.hash(state);
}
}
impl TimerHandle {
fn cancel(&mut self) {
self.cancel_chan.as_ref().map(|chan| chan.send_opt(()).ok());
}
}
#[jstraceable]
#[privatize]
pub struct TimerManager {
active_timers: DOMRefCell<HashMap<TimerId, TimerHandle>>,
next_timer_handle: Cell<i32>,
}
#[unsafe_destructor]
impl Drop for TimerManager {
fn drop(&mut self) {
for (_, timer_handle) in self.active_timers.borrow_mut().iter_mut() {
timer_handle.cancel();
}
}
}
// Enum allowing more descriptive values for the is_interval field
#[jstraceable]
#[deriving(PartialEq, Clone)]
pub enum IsInterval {
Interval,
NonInterval,
}
// 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
#[jstraceable]
#[privatize]
#[deriving(Clone)]
struct TimerData {
is_interval: IsInterval,
funval: Function,
args: Vec<JSVal>
}
impl TimerManager {
pub fn new() -> TimerManager {
TimerManager {
active_timers: DOMRefCell::new(HashMap::new()),
next_timer_handle: Cell::new(0)
}
}
pub fn set_timeout_or_interval(&self,
callback: Function,
arguments: Vec<JSVal>,
timeout: i32,
is_interval: IsInterval,
source: TimerSource,
script_chan: Box<ScriptChan+Send>)
-> i32 {
let timeout = cmp::max(0, timeout) as u64;
let handle = self.next_timer_handle.get();
self.next_timer_handle.set(handle + 1);
// Spawn a new timer task; it will dispatch the `ScriptMsg::FireTimer`
// to the relevant script handler that will deal with it.
let tm = Timer::new().unwrap();
let (cancel_chan, cancel_port) = channel();
let spawn_name = match source {
TimerSource::FromWindow(_) if is_interval == IsInterval::Interval => "Window:SetInterval",
TimerSource::FromWorker if is_interval == IsInterval::Interval => "Worker:SetInterval",
TimerSource::FromWindow(_) => "Window:SetTimeout",
TimerSource::FromWorker => "Worker:SetTimeout",
};
spawn_named(spawn_name, proc() {
let mut tm = tm;
let duration = Duration::milliseconds(timeout as i64);
let timeout_port = if is_interval == IsInterval::Interval {
tm.periodic(duration)
} else {
tm.oneshot(duration)
};
let cancel_port = cancel_port;
let select = Select::new();
let mut timeout_handle = select.handle(&timeout_port);
unsafe { timeout_handle.add() };
let mut cancel_handle = select.handle(&cancel_port);
unsafe { cancel_handle.add() };
loop {
let id = select.wait();
if id == timeout_handle.id() {
timeout_port.recv();
script_chan.send(ScriptMsg::FireTimer(source, TimerId(handle)));
if is_interval == IsInterval::NonInterval {
break;
}
} else if id == cancel_handle.id() {
break;
}
}
});
let timer_id = TimerId(handle);
let timer = TimerHandle {
handle: timer_id,
cancel_chan: Some(cancel_chan),
data: TimerData {
is_interval: is_interval,
funval: callback,
args: arguments
}
};
self.active_timers.borrow_mut().insert(timer_id, timer);
handle
}
pub fn clear_timeout_or_interval(&self, handle: i32) {
let mut timer_handle = self.active_timers.borrow_mut().remove(&TimerId(handle));
match timer_handle {
Some(ref mut handle) => handle.cancel(),
None => { }
}
}
pub fn fire_timer<T: Reflectable>(&self, timer_id: TimerId, this: JSRef<T>) {
let data = match self.active_timers.borrow().get(&timer_id) {
None => return,
Some(timer_handle) => timer_handle.data.clone(),
};
// TODO: Must handle rooting of funval and args when movable GC is turned on
let _ = data.funval.Call_(this, data.args, ReportExceptions);
if data.is_interval == IsInterval::NonInterval {
self.active_timers.borrow_mut().remove(&timer_id);
}
}
}