servo: Merge #7450 - Ordering guarantees for timers (from benschulz:constellation-timer); r=jdm

This is an rough solution to the issue described in #3396. XHRs still do their own thing and an overall clean up is in order. Before I do that, though, I'd really like someone to sign off on the overall idea.

There's one major difference to what jdm layed out #3396: The timers remain with the window/worker and only the earliest expiring one is coordinated with the dedicated timer thread.
That means both the timer thread and the window/worker have to keep track of which timer expires next, which feels a bit wonky. However, the upshot is that there's no need for communication with the timer thread when a pipeline is frozen, thawed or dropped.

Most relvant parts are
 - the [`TimerScheduler`](6f5f661958 (diff-74137a6f50ab38e7a1e4d16920a66ce7R73)), which is the new per-constellation timer task and
 - the [`ActiveTimers`](6f5f661958 (diff-86707d952414a2860b78bcf6c1db8e2eR34)) which is what's left on the window/worker side.

Source-Repo: https://github.com/servo/servo
Source-Revision: 2de5407cdabef67ed03b2ad4edf4a22541d77875
This commit is contained in:
benshu 2015-10-21 09:07:30 -06:00
Родитель c830d87e38
Коммит 7eac66ba86
21 изменённых файлов: 786 добавлений и 334 удалений

Просмотреть файл

@ -41,6 +41,7 @@ use profile_traits::mem;
use profile_traits::time;
use script_traits::{CompositorEvent, ConstellationControlMsg, LayoutControlMsg};
use script_traits::{ScriptState, ScriptTaskFactory};
use script_traits::{TimerEventRequest};
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::io::{self, Write};
@ -49,6 +50,7 @@ use std::mem::replace;
use std::process;
use std::sync::mpsc::{Receiver, Sender, channel};
use style_traits::viewport::ViewportConstraints;
use timer_scheduler::TimerScheduler;
use url::Url;
use util::cursor::Cursor;
use util::geometry::PagePx;
@ -135,6 +137,8 @@ pub struct Constellation<LTF, STF> {
/// A list of in-process senders to `WebGLPaintTask`s.
webgl_paint_tasks: Vec<Sender<CanvasMsg>>,
scheduler_chan: Sender<TimerEventRequest>,
}
/// State needed to construct a constellation.
@ -280,6 +284,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
webdriver: WebDriverData::new(),
canvas_paint_tasks: Vec::new(),
webgl_paint_tasks: Vec::new(),
scheduler_chan: TimerScheduler::start(),
};
let namespace_id = constellation.next_pipeline_namespace_id();
PipelineNamespace::install(namespace_id);
@ -317,6 +322,7 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> {
id: pipeline_id,
parent_info: parent_info,
constellation_chan: self.chan.clone(),
scheduler_chan: self.scheduler_chan.clone(),
compositor_proxy: self.compositor_proxy.clone_compositor_proxy(),
devtools_chan: self.devtools_chan.clone(),
image_cache_task: self.image_cache_task.clone(),

Просмотреть файл

@ -6,6 +6,7 @@
#![feature(iter_cmp)]
#![feature(slice_bytes)]
#![feature(vec_push_all)]
#![feature(mpsc_select)]
#![feature(plugin)]
#![plugin(plugins)]
@ -54,6 +55,7 @@ mod compositor_layer;
mod headless;
mod scrolling;
mod surface_map;
mod timer_scheduler;
pub mod compositor_task;
pub mod constellation;
pub mod pipeline;

Просмотреть файл

@ -24,6 +24,7 @@ use profile_traits::mem as profile_mem;
use profile_traits::time;
use script_traits::{ConstellationControlMsg, InitialScriptState};
use script_traits::{LayoutControlMsg, NewLayoutInfo, ScriptTaskFactory};
use script_traits::{TimerEventRequest};
use std::any::Any;
use std::mem;
use std::sync::mpsc::{Receiver, Sender, channel};
@ -75,6 +76,8 @@ pub struct InitialPipelineState {
pub parent_info: Option<(PipelineId, SubpageId)>,
/// A channel to the associated constellation.
pub constellation_chan: ConstellationChan,
/// A channel to schedule timer events.
pub scheduler_chan: Sender<TimerEventRequest>,
/// A channel to the compositor.
pub compositor_proxy: Box<CompositorProxy + 'static + Send>,
/// A channel to the developer tools, if applicable.
@ -181,6 +184,7 @@ impl Pipeline {
id: state.id,
parent_info: state.parent_info,
constellation_chan: state.constellation_chan,
scheduler_chan: state.scheduler_chan,
compositor_proxy: state.compositor_proxy,
devtools_chan: script_to_devtools_chan,
image_cache_task: state.image_cache_task,
@ -316,6 +320,7 @@ pub struct PipelineContent {
id: PipelineId,
parent_info: Option<(PipelineId, SubpageId)>,
constellation_chan: ConstellationChan,
scheduler_chan: Sender<TimerEventRequest>,
compositor_proxy: Box<CompositorProxy + Send + 'static>,
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
image_cache_task: ImageCacheTask,
@ -361,6 +366,7 @@ impl PipelineContent {
control_chan: self.script_chan.clone(),
control_port: mem::replace(&mut self.script_port, None).unwrap(),
constellation_chan: self.constellation_chan.clone(),
scheduler_chan: self.scheduler_chan.clone(),
failure_info: self.failure.clone(),
resource_task: self.resource_task,
storage_task: self.storage_task.clone(),

Просмотреть файл

@ -0,0 +1,221 @@
/* 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 euclid::length::Length;
use num::traits::Saturating;
use script_traits::{MsDuration, NsDuration, precise_time_ms, precise_time_ns};
use script_traits::{TimerEvent, TimerEventRequest};
use std::cell::RefCell;
use std::cmp::{self, Ord};
use std::collections::BinaryHeap;
use std::sync::Arc;
use std::sync::atomic::{self, AtomicBool};
use std::sync::mpsc::{channel, Receiver, Select, Sender};
use std::thread::{self, spawn, Thread};
use util::task::spawn_named;
/// A quick hack to work around the removal of [`std::old_io::timer::Timer`](
/// http://doc.rust-lang.org/1.0.0-beta/std/old_io/timer/struct.Timer.html )
struct CancelableOneshotTimer {
thread: Thread,
canceled: Arc<AtomicBool>,
port: Receiver<()>,
}
impl CancelableOneshotTimer {
fn new(duration: MsDuration) -> CancelableOneshotTimer {
let (tx, rx) = channel();
let canceled = Arc::new(AtomicBool::new(false));
let canceled_clone = canceled.clone();
let thread = spawn(move || {
let due_time = precise_time_ms() + duration;
let mut park_time = duration;
loop {
thread::park_timeout_ms(park_time.get() as u32);
if canceled_clone.load(atomic::Ordering::Relaxed) {
return;
}
// park_timeout_ms does not guarantee parking for the
// given amout. We might have woken up early.
let current_time = precise_time_ms();
if current_time >= due_time {
let _ = tx.send(());
return;
}
park_time = due_time - current_time;
}
}).thread().clone();
CancelableOneshotTimer {
thread: thread,
canceled: canceled,
port: rx,
}
}
fn port(&self) -> &Receiver<()> {
&self.port
}
fn cancel(&self) {
self.canceled.store(true, atomic::Ordering::Relaxed);
self.thread.unpark();
}
}
pub struct TimerScheduler {
port: Receiver<TimerEventRequest>,
scheduled_events: RefCell<BinaryHeap<ScheduledEvent>>,
timer: RefCell<Option<CancelableOneshotTimer>>,
}
struct ScheduledEvent {
request: TimerEventRequest,
for_time: NsDuration,
}
impl Ord for ScheduledEvent {
fn cmp(&self, other: &ScheduledEvent) -> cmp::Ordering {
self.for_time.cmp(&other.for_time).reverse()
}
}
impl PartialOrd for ScheduledEvent {
fn partial_cmp(&self, other: &ScheduledEvent) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Eq for ScheduledEvent {}
impl PartialEq for ScheduledEvent {
fn eq(&self, other: &ScheduledEvent) -> bool {
self as *const ScheduledEvent == other as *const ScheduledEvent
}
}
enum Task {
HandleRequest(TimerEventRequest),
DispatchDueEvents,
}
impl TimerScheduler {
pub fn start() -> Sender<TimerEventRequest> {
let (chan, port) = channel();
let timer_scheduler = TimerScheduler {
port: port,
scheduled_events: RefCell::new(BinaryHeap::new()),
timer: RefCell::new(None),
};
spawn_named("TimerScheduler".to_owned(), move || {
timer_scheduler.run_event_loop();
});
chan
}
fn run_event_loop(&self) {
loop {
match self.receive_next_task() {
Some(Task::HandleRequest(request)) => self.handle_request(request),
Some(Task::DispatchDueEvents) => self.dispatch_due_events(),
None => break,
}
}
}
#[allow(unsafe_code)]
fn receive_next_task(&self) -> Option<Task> {
let port = &self.port;
let timer = self.timer.borrow();
let timer_port = timer.as_ref().map(|timer| timer.port());
if let Some(ref timer_port) = timer_port {
let sel = Select::new();
let mut scheduler_handle = sel.handle(port);
let mut timer_handle = sel.handle(timer_port);
unsafe {
scheduler_handle.add();
timer_handle.add();
}
let ret = sel.wait();
if ret == scheduler_handle.id() {
port.recv().ok().map(|req| Task::HandleRequest(req))
} else if ret == timer_handle.id() {
timer_port.recv().ok().map(|_| Task::DispatchDueEvents)
} else {
panic!("unexpected select result!")
}
} else {
port.recv().ok().map(|req| Task::HandleRequest(req))
}
}
fn handle_request(&self, request: TimerEventRequest) {
let TimerEventRequest(_, _, _, duration_ms) = request;
let duration_ns = Length::new(duration_ms.get() * 1000 * 1000);
let schedule_for = precise_time_ns() + duration_ns;
let previously_earliest = self.scheduled_events.borrow().peek()
.map(|scheduled| scheduled.for_time)
.unwrap_or(Length::new(u64::max_value()));
self.scheduled_events.borrow_mut().push(ScheduledEvent {
request: request,
for_time: schedule_for,
});
if schedule_for < previously_earliest {
self.start_timer_for_next_event();
}
}
fn dispatch_due_events(&self) {
let now = precise_time_ns();
{
let mut events = self.scheduled_events.borrow_mut();
while !events.is_empty() && events.peek().as_ref().unwrap().for_time <= now {
let event = events.pop().unwrap();
let TimerEventRequest(chan, source, id, _) = event.request;
let _ = chan.send(TimerEvent(source, id));
}
}
self.start_timer_for_next_event();
}
fn start_timer_for_next_event(&self) {
let events = self.scheduled_events.borrow();
let next_event = events.peek();
let mut timer = self.timer.borrow_mut();
if let Some(ref mut timer) = *timer {
timer.cancel();
}
*timer = next_event.map(|next_event| {
let delay_ns = next_event.for_time.get().saturating_sub(precise_time_ns().get());
// Round up, we'd rather be late than early…
let delay_ms = Length::new(delay_ns.saturating_add(999999) / (1000 * 1000));
CancelableOneshotTimer::new(delay_ms)
});
}
}

Просмотреть файл

@ -92,6 +92,7 @@ impl Formattable for ProfilerCategory {
ProfilerCategory::ScriptEvent => "Script Event",
ProfilerCategory::ScriptUpdateReplacedElement => "Script Update Replaced Element",
ProfilerCategory::ScriptSetViewport => "Script Set Viewport",
ProfilerCategory::ScriptTimerEvent => "Script Timer Event",
ProfilerCategory::ScriptWebSocketEvent => "Script Web Socket Event",
ProfilerCategory::ScriptWorkerEvent => "Script Worker Event",
ProfilerCategory::ScriptXhrEvent => "Script Xhr Event",

Просмотреть файл

@ -70,6 +70,7 @@ pub enum ProfilerCategory {
ScriptEvent,
ScriptUpdateReplacedElement,
ScriptSetViewport,
ScriptTimerEvent,
ScriptWebSocketEvent,
ScriptWorkerEvent,
ScriptXhrEvent,

Просмотреть файл

@ -22,6 +22,8 @@ use msg::constellation_msg::{ConstellationChan, PipelineId, WorkerId};
use net_traits::ResourceTask;
use profile_traits::mem;
use script_task::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptTask};
use script_traits::TimerEventRequest;
use std::sync::mpsc::Sender;
use url::Url;
use util::mem::HeapSizeOf;
@ -96,6 +98,14 @@ impl<'a> GlobalRef<'a> {
}
}
/// Get the scheduler channel to request timer events.
pub fn scheduler_chan(&self) -> Sender<TimerEventRequest> {
match *self {
GlobalRef::Window(window) => window.scheduler_chan(),
GlobalRef::Worker(worker) => worker.scheduler_chan(),
}
}
/// Get an `IpcSender<ScriptToDevtoolsControlMsg>` to send messages to Devtools
/// task when available.
pub fn devtools_chan(&self) -> Option<IpcSender<ScriptToDevtoolsControlMsg>> {

Просмотреть файл

@ -37,6 +37,7 @@ use dom::bindings::js::{JS, Root};
use dom::bindings::refcounted::Trusted;
use dom::bindings::utils::{Reflectable, Reflector, WindowProxyHandler};
use encoding::types::EncodingRef;
use euclid::length::Length as EuclidLength;
use euclid::matrix2d::Matrix2D;
use euclid::rect::Rect;
use euclid::size::Size2D;
@ -58,7 +59,7 @@ use net_traits::storage_task::StorageType;
use profile_traits::mem::ProfilerChan as MemProfilerChan;
use profile_traits::time::ProfilerChan as TimeProfilerChan;
use script_task::ScriptChan;
use script_traits::UntrustedNodeAddress;
use script_traits::{TimerEventChan, TimerEventId, TimerSource, UntrustedNodeAddress};
use selectors::parser::PseudoElement;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
@ -283,6 +284,7 @@ no_jsmanaged_fields!(HashSet<T>);
// These three are interdependent, if you plan to put jsmanaged data
// in one of these make sure it is propagated properly to containing structs
no_jsmanaged_fields!(SubpageId, WindowSizeData, PipelineId);
no_jsmanaged_fields!(TimerEventId, TimerSource);
no_jsmanaged_fields!(WorkerId);
no_jsmanaged_fields!(QuirksMode);
no_jsmanaged_fields!(Runtime);
@ -293,6 +295,7 @@ no_jsmanaged_fields!(WindowProxyHandler);
no_jsmanaged_fields!(UntrustedNodeAddress);
no_jsmanaged_fields!(LengthOrPercentageOrAuto);
no_jsmanaged_fields!(RGBA);
no_jsmanaged_fields!(EuclidLength<Unit, T>);
no_jsmanaged_fields!(Matrix2D<T>);
no_jsmanaged_fields!(StorageType);
no_jsmanaged_fields!(CanvasGradientStop, LinearGradientStyle, RadialGradientStyle);
@ -311,6 +314,13 @@ impl JSTraceable for Box<ScriptChan + Send> {
}
}
impl JSTraceable for Box<TimerEventChan + Send> {
#[inline]
fn trace(&self, _trc: *mut JSTracer) {
// Do nothing
}
}
impl JSTraceable for Box<FnBox(f64, )> {
#[inline]
fn trace(&self, _trc: *mut JSTracer) {

Просмотреть файл

@ -29,7 +29,8 @@ use msg::constellation_msg::PipelineId;
use net_traits::load_whole_resource;
use rand::random;
use script_task::ScriptTaskEventCategory::WorkerEvent;
use script_task::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptTask, StackRootTLS, TimerSource};
use script_task::{ScriptTask, ScriptChan, ScriptPort, StackRootTLS, CommonScriptMsg};
use script_traits::{TimerEvent, TimerEventChan, TimerSource};
use std::mem::replace;
use std::rc::Rc;
use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel};
@ -101,6 +102,29 @@ impl ScriptPort for Receiver<(TrustedWorkerAddress, WorkerScriptMsg)> {
}
}
/// A TimerEventChan that can be cloned freely and will silently send a TrustedWorkerAddress
/// with timer events. While this SendableWorkerScriptChan is alive, the associated Worker
/// object will remain alive.
struct WorkerThreadTimerEventChan {
sender: Sender<(TrustedWorkerAddress, TimerEvent)>,
worker: TrustedWorkerAddress,
}
impl TimerEventChan for WorkerThreadTimerEventChan {
fn send(&self, event: TimerEvent) -> Result<(), ()> {
self.sender
.send((self.worker.clone(), event))
.map_err(|_| ())
}
fn clone(&self) -> Box<TimerEventChan + Send> {
box WorkerThreadTimerEventChan {
sender: self.sender.clone(),
worker: self.worker.clone(),
}
}
}
/// Set the `worker` field of a related DedicatedWorkerGlobalScope object to a particular
/// value for the duration of this object's lifetime. This ensures that the related Worker
/// object only lives as long as necessary (ie. while events are being executed), while
@ -127,6 +151,7 @@ impl<'a> Drop for AutoWorkerReset<'a> {
enum MixedMessage {
FromWorker((TrustedWorkerAddress, WorkerScriptMsg)),
FromScheduler((TrustedWorkerAddress, TimerEvent)),
FromDevtools(DevtoolScriptControlMsg),
}
@ -139,6 +164,8 @@ pub struct DedicatedWorkerGlobalScope {
receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>,
#[ignore_heap_size_of = "Defined in std"]
own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
#[ignore_heap_size_of = "Defined in std"]
timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>,
#[ignore_heap_size_of = "Trusted<T> has unclear ownership like JS<T>"]
worker: DOMRefCell<Option<TrustedWorkerAddress>>,
#[ignore_heap_size_of = "Can't measure trait objects"]
@ -154,14 +181,18 @@ impl DedicatedWorkerGlobalScope {
runtime: Rc<Runtime>,
parent_sender: Box<ScriptChan + Send>,
own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>)
receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>,
timer_event_chan: Box<TimerEventChan + Send>,
timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>)
-> DedicatedWorkerGlobalScope {
DedicatedWorkerGlobalScope {
workerglobalscope: WorkerGlobalScope::new_inherited(
init, worker_url, runtime, from_devtools_receiver),
init, worker_url, runtime, from_devtools_receiver, timer_event_chan),
id: id,
receiver: receiver,
own_sender: own_sender,
timer_event_port: timer_event_port,
parent_sender: parent_sender,
worker: DOMRefCell::new(None),
}
@ -174,11 +205,13 @@ impl DedicatedWorkerGlobalScope {
runtime: Rc<Runtime>,
parent_sender: Box<ScriptChan + Send>,
own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>)
receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>,
timer_event_chan: Box<TimerEventChan + Send>,
timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>)
-> Root<DedicatedWorkerGlobalScope> {
let scope = box DedicatedWorkerGlobalScope::new_inherited(
init, worker_url, id, from_devtools_receiver, runtime.clone(), parent_sender,
own_sender, receiver);
own_sender, receiver, timer_event_chan, timer_event_port);
DedicatedWorkerGlobalScopeBinding::Wrap(runtime.cx(), scope)
}
@ -214,9 +247,16 @@ impl DedicatedWorkerGlobalScope {
let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
ROUTER.route_ipc_receiver_to_mpsc_sender(from_devtools_receiver, devtools_mpsc_chan);
let (timer_tx, timer_rx) = channel();
let timer_event_chan = box WorkerThreadTimerEventChan {
sender: timer_tx,
worker: worker.clone(),
};
let global = DedicatedWorkerGlobalScope::new(
init, url, id, devtools_mpsc_port, runtime.clone(),
parent_sender.clone(), own_sender, receiver);
parent_sender.clone(), own_sender, receiver,
timer_event_chan, timer_rx);
// FIXME(njn): workers currently don't have a unique ID suitable for using in reporter
// registration (#6631), so we instead use a random number and cross our fingers.
let scope = global.upcast::<WorkerGlobalScope>();
@ -263,13 +303,16 @@ impl DedicatedWorkerGlobalScope {
fn receive_event(&self) -> Result<MixedMessage, RecvError> {
let scope = self.upcast::<WorkerGlobalScope>();
let worker_port = &self.receiver;
let timer_event_port = &self.timer_event_port;
let devtools_port = scope.from_devtools_receiver();
let sel = Select::new();
let mut worker_handle = sel.handle(worker_port);
let mut timer_event_handle = sel.handle(timer_event_port);
let mut devtools_handle = sel.handle(devtools_port);
unsafe {
worker_handle.add();
timer_event_handle.add();
if scope.from_devtools_sender().is_some() {
devtools_handle.add();
}
@ -277,6 +320,8 @@ impl DedicatedWorkerGlobalScope {
let ret = sel.wait();
if ret == worker_handle.id() {
Ok(MixedMessage::FromWorker(try!(worker_port.recv())))
} else if ret == timer_event_handle.id() {
Ok(MixedMessage::FromScheduler(try!(timer_event_port.recv())))
} else if ret == devtools_handle.id() {
Ok(MixedMessage::FromDevtools(try!(devtools_port.recv())))
} else {
@ -301,11 +346,6 @@ impl DedicatedWorkerGlobalScope {
WorkerScriptMsg::Common(CommonScriptMsg::RefcountCleanup(addr)) => {
LiveDOMReferences::cleanup(addr);
},
WorkerScriptMsg::Common(
CommonScriptMsg::FireTimer(TimerSource::FromWorker, timer_id)) => {
let scope = self.upcast::<WorkerGlobalScope>();
scope.handle_fire_timer(timer_id);
},
WorkerScriptMsg::Common(CommonScriptMsg::CollectReports(reports_chan)) => {
let scope = self.upcast::<WorkerGlobalScope>();
let cx = scope.get_cx();
@ -313,9 +353,6 @@ impl DedicatedWorkerGlobalScope {
let reports = ScriptTask::get_reports(cx, path_seg);
reports_chan.send(reports);
},
WorkerScriptMsg::Common(CommonScriptMsg::FireTimer(_, _)) => {
panic!("obtained a fire timeout from window for the worker!")
},
}
}
@ -333,6 +370,18 @@ impl DedicatedWorkerGlobalScope {
_ => debug!("got an unusable devtools control message inside the worker!"),
}
},
MixedMessage::FromScheduler((linked_worker, timer_event)) => {
match timer_event {
TimerEvent(TimerSource::FromWorker, id) => {
let _ar = AutoWorkerReset::new(self, linked_worker);
let scope = self.upcast::<WorkerGlobalScope>();
scope.handle_fire_timer(id);
},
TimerEvent(_, _) => {
panic!("A worker received a TimerEvent from a window.")
}
}
}
MixedMessage::FromWorker((linked_worker, msg)) => {
let _ar = AutoWorkerReset::new(self, linked_worker);
self.handle_script_event(msg);

Просмотреть файл

@ -52,9 +52,9 @@ use num::traits::ToPrimitive;
use page::Page;
use profile_traits::mem;
use rustc_serialize::base64::{FromBase64, STANDARD, ToBase64};
use script_task::{MainThreadScriptChan, SendableMainThreadScriptChan};
use script_task::{MainThreadScriptMsg, ScriptChan, ScriptPort, TimerSource};
use script_traits::ConstellationControlMsg;
use script_task::{ScriptChan, ScriptPort, MainThreadScriptMsg};
use script_task::{SendableMainThreadScriptChan, MainThreadScriptChan, MainThreadTimerEventChan};
use script_traits::{ConstellationControlMsg, TimerEventChan, TimerEventId, TimerEventRequest, TimerSource};
use selectors::parser::PseudoElement;
use std::ascii::AsciiExt;
use std::borrow::ToOwned;
@ -70,7 +70,7 @@ use std::sync::mpsc::TryRecvError::{Disconnected, Empty};
use std::sync::mpsc::{Receiver, Sender, channel};
use string_cache::Atom;
use time;
use timers::{IsInterval, TimerCallback, TimerId, TimerManager};
use timers::{ActiveTimers, IsInterval, TimerCallback};
use url::Url;
use util::geometry::{self, MAX_RECT};
use util::str::{DOMString, HTML_SPACE_CHARACTERS};
@ -129,7 +129,9 @@ pub struct Window {
screen: MutNullableHeap<JS<Screen>>,
session_storage: MutNullableHeap<JS<Storage>>,
local_storage: MutNullableHeap<JS<Storage>>,
timers: TimerManager,
#[ignore_heap_size_of = "channels are hard"]
scheduler_chan: Sender<TimerEventRequest>,
timers: ActiveTimers,
next_worker_id: Cell<WorkerId>,
@ -425,8 +427,7 @@ impl WindowMethods for Window {
args,
timeout,
IsInterval::NonInterval,
TimerSource::FromWindow(self.id.clone()),
self.script_chan.clone())
TimerSource::FromWindow(self.id.clone()))
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-settimeout
@ -435,8 +436,7 @@ impl WindowMethods for Window {
args,
timeout,
IsInterval::NonInterval,
TimerSource::FromWindow(self.id.clone()),
self.script_chan.clone())
TimerSource::FromWindow(self.id.clone()))
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-cleartimeout
@ -450,8 +450,7 @@ impl WindowMethods for Window {
args,
timeout,
IsInterval::Interval,
TimerSource::FromWindow(self.id.clone()),
self.script_chan.clone())
TimerSource::FromWindow(self.id.clone()))
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
@ -460,8 +459,7 @@ impl WindowMethods for Window {
args,
timeout,
IsInterval::Interval,
TimerSource::FromWindow(self.id.clone()),
self.script_chan.clone())
TimerSource::FromWindow(self.id.clone()))
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
@ -1076,7 +1074,7 @@ impl Window {
MainThreadScriptMsg::Navigate(self.id, LoadData::new(url))).unwrap();
}
pub fn handle_fire_timer(&self, timer_id: TimerId) {
pub fn handle_fire_timer(&self, timer_id: TimerEventId) {
self.timers.fire_timer(timer_id, self);
self.reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::Timer);
}
@ -1122,6 +1120,10 @@ impl Window {
self.constellation_chan.clone()
}
pub fn scheduler_chan(&self) -> Sender<TimerEventRequest> {
self.scheduler_chan.clone()
}
pub fn windowproxy_handler(&self) -> WindowProxyHandler {
WindowProxyHandler(self.dom_static.windowproxy_handler.0)
}
@ -1267,6 +1269,8 @@ impl Window {
mem_profiler_chan: mem::ProfilerChan,
devtools_chan: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
constellation_chan: ConstellationChan,
scheduler_chan: Sender<TimerEventRequest>,
timer_event_chan: MainThreadTimerEventChan,
layout_chan: LayoutChan,
id: PipelineId,
parent_info: Option<(PipelineId, SubpageId)>,
@ -1299,7 +1303,8 @@ impl Window {
screen: Default::default(),
session_storage: Default::default(),
local_storage: Default::default(),
timers: TimerManager::new(),
scheduler_chan: scheduler_chan.clone(),
timers: ActiveTimers::new(box timer_event_chan, scheduler_chan),
next_worker_id: Cell::new(WorkerId(0)),
id: id,
parent_info: parent_info,

Просмотреть файл

@ -72,6 +72,7 @@ impl Worker {
let resource_task = global.resource_task();
let constellation_chan = global.constellation_chan();
let scheduler_chan = global.scheduler_chan();
let (sender, receiver) = channel();
let worker = Worker::new(global, sender.clone());
@ -101,6 +102,7 @@ impl Worker {
to_devtools_sender: global.devtools_chan(),
from_devtools_sender: optional_sender,
constellation_chan: constellation_chan,
scheduler_chan: scheduler_chan,
worker_id: worker_id,
};
DedicatedWorkerGlobalScope::run_worker_scope(

Просмотреть файл

@ -23,12 +23,13 @@ use js::rust::Runtime;
use msg::constellation_msg::{ConstellationChan, PipelineId, WorkerId};
use net_traits::{ResourceTask, load_whole_resource};
use profile_traits::mem;
use script_task::{CommonScriptMsg, ScriptChan, ScriptPort, TimerSource};
use script_task::{CommonScriptMsg, ScriptChan, ScriptPort};
use script_traits::{TimerEventChan, TimerEventId, TimerEventRequest, TimerSource};
use std::cell::Cell;
use std::default::Default;
use std::rc::Rc;
use std::sync::mpsc::Receiver;
use timers::{IsInterval, TimerCallback, TimerId, TimerManager};
use std::sync::mpsc::{Receiver, Sender};
use timers::{ActiveTimers, IsInterval, TimerCallback};
use url::{Url, UrlParser};
use util::str::DOMString;
@ -43,6 +44,7 @@ pub struct WorkerGlobalScopeInit {
pub to_devtools_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
pub from_devtools_sender: Option<IpcSender<DevtoolScriptControlMsg>>,
pub constellation_chan: ConstellationChan,
pub scheduler_chan: Sender<TimerEventRequest>,
pub worker_id: WorkerId,
}
@ -61,7 +63,7 @@ pub struct WorkerGlobalScope {
navigator: MutNullableHeap<JS<WorkerNavigator>>,
console: MutNullableHeap<JS<Console>>,
crypto: MutNullableHeap<JS<Crypto>>,
timers: TimerManager,
timers: ActiveTimers,
#[ignore_heap_size_of = "Defined in std"]
mem_profiler_chan: mem::ProfilerChan,
#[ignore_heap_size_of = "Defined in ipc-channel"]
@ -83,13 +85,17 @@ pub struct WorkerGlobalScope {
#[ignore_heap_size_of = "Defined in std"]
constellation_chan: ConstellationChan,
#[ignore_heap_size_of = "Defined in std"]
scheduler_chan: Sender<TimerEventRequest>,
}
impl WorkerGlobalScope {
pub fn new_inherited(init: WorkerGlobalScopeInit,
worker_url: Url,
runtime: Rc<Runtime>,
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>)
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
timer_event_chan: Box<TimerEventChan + Send>)
-> WorkerGlobalScope {
WorkerGlobalScope {
eventtarget: EventTarget::new_inherited(),
@ -102,13 +108,14 @@ impl WorkerGlobalScope {
navigator: Default::default(),
console: Default::default(),
crypto: Default::default(),
timers: TimerManager::new(),
timers: ActiveTimers::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,
from_devtools_receiver: from_devtools_receiver,
devtools_wants_updates: Cell::new(false),
constellation_chan: init.constellation_chan,
scheduler_chan: init.scheduler_chan,
}
}
@ -132,6 +139,10 @@ impl WorkerGlobalScope {
self.constellation_chan.clone()
}
pub fn scheduler_chan(&self) -> Sender<TimerEventRequest> {
self.scheduler_chan.clone()
}
pub fn get_cx(&self) -> *mut JSContext {
self.runtime.cx()
}
@ -233,8 +244,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
args,
timeout,
IsInterval::NonInterval,
TimerSource::FromWorker,
self.script_chan())
TimerSource::FromWorker)
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
@ -243,8 +253,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
args,
timeout,
IsInterval::NonInterval,
TimerSource::FromWorker,
self.script_chan())
TimerSource::FromWorker)
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
@ -258,8 +267,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
args,
timeout,
IsInterval::Interval,
TimerSource::FromWorker,
self.script_chan())
TimerSource::FromWorker)
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-setinterval
@ -268,8 +276,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
args,
timeout,
IsInterval::Interval,
TimerSource::FromWorker,
self.script_chan())
TimerSource::FromWorker)
}
// https://html.spec.whatwg.org/multipage/#dom-windowtimers-clearinterval
@ -330,7 +337,7 @@ impl WorkerGlobalScope {
}
}
pub fn handle_fire_timer(&self, timer_id: TimerId) {
pub fn handle_fire_timer(&self, timer_id: TimerEventId) {
self.timers.fire_timer(timer_id, self);
}

Просмотреть файл

@ -1,31 +0,0 @@
/* 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/. */
/// A quick hack to work around the removal of [`std::old_io::timer::Timer`](
/// http://doc.rust-lang.org/1.0.0-beta/std/old_io/timer/struct.Timer.html )
use std::sync::mpsc::{Receiver, channel};
use std::thread::{sleep_ms, spawn};
pub fn oneshot(duration_ms: u32) -> Receiver<()> {
let (tx, rx) = channel();
spawn(move || {
sleep_ms(duration_ms);
let _ = tx.send(());
});
rx
}
pub fn periodic(duration_ms: u32) -> Receiver<()> {
let (tx, rx) = channel();
spawn(move || {
loop {
sleep_ms(duration_ms);
if tx.send(()).is_err() {
break
}
}
});
rx
}

Просмотреть файл

@ -86,7 +86,6 @@ mod devtools;
pub mod document_loader;
#[macro_use]
pub mod dom;
mod horribly_inefficient_timers;
pub mod layout_interface;
mod mem;
mod network_listener;

Просмотреть файл

@ -82,6 +82,7 @@ use script_traits::CompositorEvent::{MouseDownEvent, MouseUpEvent};
use script_traits::{CompositorEvent, ConstellationControlMsg};
use script_traits::{InitialScriptState, MouseButton, NewLayoutInfo};
use script_traits::{OpaqueScriptLayoutChannel, ScriptState, ScriptTaskFactory};
use script_traits::{TimerEvent, TimerEventChan, TimerEventRequest, TimerSource};
use std::any::Any;
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
@ -96,7 +97,6 @@ use std::sync::mpsc::{Receiver, Select, Sender, channel};
use std::sync::{Arc, Mutex};
use string_cache::Atom;
use time::{Tm, now};
use timers::TimerId;
use url::{Url, UrlParser};
use util::opts;
use util::str::DOMString;
@ -156,12 +156,6 @@ impl InProgressLoad {
}
}
#[derive(Copy, Clone)]
pub enum TimerSource {
FromWindow(PipelineId),
FromWorker
}
pub trait Runnable {
fn handler(self: Box<Self>);
}
@ -175,6 +169,7 @@ enum MixedMessage {
FromScript(MainThreadScriptMsg),
FromDevtools(DevtoolScriptControlMsg),
FromImageCache(ImageCacheResult),
FromScheduler(TimerEvent),
}
/// Common messages used to control the event loops in both the script and the worker
@ -182,10 +177,6 @@ pub enum CommonScriptMsg {
/// Requests that the script task measure its memory usage. The results are sent back via the
/// supplied channel.
CollectReports(ReportsChan),
/// Fires a JavaScript timeout
/// TimerSource must be FromWindow when dispatched to ScriptTask and
/// must be FromWorker when dispatched to a DedicatedGlobalWorkerScope
FireTimer(TimerSource, TimerId),
/// A DOM object's last pinned reference was removed (dispatched to all tasks).
RefcountCleanup(TrustedReference),
/// Generic message that encapsulates event handling.
@ -205,6 +196,7 @@ pub enum ScriptTaskEventCategory {
NetworkEvent,
Resize,
ScriptEvent,
TimerEvent,
UpdateReplacedElement,
SetViewport,
WebSocketEvent,
@ -327,6 +319,20 @@ impl MainThreadScriptChan {
}
}
pub struct MainThreadTimerEventChan(Sender<TimerEvent>);
impl TimerEventChan for MainThreadTimerEventChan {
fn send(&self, event: TimerEvent) -> Result<(), ()> {
let MainThreadTimerEventChan(ref chan) = *self;
chan.send(event).map_err(|_| ())
}
fn clone(&self) -> Box<TimerEventChan + Send> {
let MainThreadTimerEventChan(ref chan) = *self;
box MainThreadTimerEventChan((*chan).clone())
}
}
pub struct StackRootTLS;
impl StackRootTLS {
@ -408,6 +414,10 @@ pub struct ScriptTask {
/// List of pipelines that have been owned and closed by this script task.
closed_pipelines: RefCell<HashSet<PipelineId>>,
scheduler_chan: Sender<TimerEventRequest>,
timer_event_chan: Sender<TimerEvent>,
timer_event_port: Receiver<TimerEvent>,
}
/// In the event of task failure, all data on the stack runs its destructor. However, there
@ -604,6 +614,8 @@ impl ScriptTask {
let image_cache_port =
ROUTER.route_ipc_receiver_to_new_mpsc_receiver(ipc_image_cache_port);
let (timer_event_chan, timer_event_port) = channel();
ScriptTask {
page: DOMRefCell::new(None),
incomplete_loads: DOMRefCell::new(vec!()),
@ -631,6 +643,10 @@ impl ScriptTask {
js_runtime: Rc::new(runtime),
mouse_over_targets: DOMRefCell::new(vec!()),
closed_pipelines: RefCell::new(HashSet::new()),
scheduler_chan: state.scheduler_chan,
timer_event_chan: timer_event_chan,
timer_event_port: timer_event_port,
}
}
@ -717,26 +733,30 @@ impl ScriptTask {
// Receive at least one message so we don't spinloop.
let mut event = {
let sel = Select::new();
let mut port1 = sel.handle(&self.port);
let mut port2 = sel.handle(&self.control_port);
let mut port3 = sel.handle(&self.devtools_port);
let mut port4 = sel.handle(&self.image_cache_port);
let mut script_port = sel.handle(&self.port);
let mut control_port = sel.handle(&self.control_port);
let mut timer_event_port = sel.handle(&self.timer_event_port);
let mut devtools_port = sel.handle(&self.devtools_port);
let mut image_cache_port = sel.handle(&self.image_cache_port);
unsafe {
port1.add();
port2.add();
script_port.add();
control_port.add();
timer_event_port.add();
if self.devtools_chan.is_some() {
port3.add();
devtools_port.add();
}
port4.add();
image_cache_port.add();
}
let ret = sel.wait();
if ret == port1.id() {
if ret == script_port.id() {
MixedMessage::FromScript(self.port.recv().unwrap())
} else if ret == port2.id() {
} else if ret == control_port.id() {
MixedMessage::FromConstellation(self.control_port.recv().unwrap())
} else if ret == port3.id() {
} else if ret == timer_event_port.id() {
MixedMessage::FromScheduler(self.timer_event_port.recv().unwrap())
} else if ret == devtools_port.id() {
MixedMessage::FromDevtools(self.devtools_port.recv().unwrap())
} else if ret == port4.id() {
} else if ret == image_cache_port.id() {
MixedMessage::FromImageCache(self.image_cache_port.recv().unwrap())
} else {
panic!("unexpected select result")
@ -797,12 +817,15 @@ impl ScriptTask {
// on and execute the sequential non-resize events we've seen.
match self.control_port.try_recv() {
Err(_) => match self.port.try_recv() {
Err(_) => match self.devtools_port.try_recv() {
Err(_) => match self.image_cache_port.try_recv() {
Err(_) => break,
Ok(ev) => event = MixedMessage::FromImageCache(ev),
Err(_) => match self.timer_event_port.try_recv() {
Err(_) => match self.devtools_port.try_recv() {
Err(_) => match self.image_cache_port.try_recv() {
Err(_) => break,
Ok(ev) => event = MixedMessage::FromImageCache(ev),
},
Ok(ev) => event = MixedMessage::FromDevtools(ev),
},
Ok(ev) => event = MixedMessage::FromDevtools(ev),
Ok(ev) => event = MixedMessage::FromScheduler(ev),
},
Ok(ev) => event = MixedMessage::FromScript(ev),
},
@ -823,6 +846,7 @@ impl ScriptTask {
},
MixedMessage::FromConstellation(inner_msg) => self.handle_msg_from_constellation(inner_msg),
MixedMessage::FromScript(inner_msg) => self.handle_msg_from_script(inner_msg),
MixedMessage::FromScheduler(inner_msg) => self.handle_timer_event(inner_msg),
MixedMessage::FromDevtools(inner_msg) => self.handle_msg_from_devtools(inner_msg),
MixedMessage::FromImageCache(inner_msg) => self.handle_msg_from_image_cache(inner_msg),
}
@ -871,7 +895,8 @@ impl ScriptTask {
*category,
_ => ScriptTaskEventCategory::ScriptEvent
}
}
},
MixedMessage::FromScheduler(_) => ScriptTaskEventCategory::TimerEvent,
}
}
@ -893,6 +918,7 @@ impl ScriptTask {
ScriptTaskEventCategory::ScriptEvent => ProfilerCategory::ScriptEvent,
ScriptTaskEventCategory::UpdateReplacedElement => ProfilerCategory::ScriptUpdateReplacedElement,
ScriptTaskEventCategory::SetViewport => ProfilerCategory::ScriptSetViewport,
ScriptTaskEventCategory::TimerEvent => ProfilerCategory::ScriptTimerEvent,
ScriptTaskEventCategory::WebSocketEvent => ProfilerCategory::ScriptWebSocketEvent,
ScriptTaskEventCategory::WorkerEvent => ProfilerCategory::ScriptWorkerEvent,
ScriptTaskEventCategory::XhrEvent => ProfilerCategory::ScriptXhrEvent,
@ -966,12 +992,6 @@ impl ScriptTask {
runnable.handler(self),
MainThreadScriptMsg::DocumentLoadsComplete(id) =>
self.handle_loads_complete(id),
MainThreadScriptMsg::Common(
CommonScriptMsg::FireTimer(TimerSource::FromWindow(id), timer_id)) =>
self.handle_fire_timer_msg(id, timer_id),
MainThreadScriptMsg::Common(
CommonScriptMsg::FireTimer(TimerSource::FromWorker, _)) =>
panic!("Worker timeouts must not be sent to script task"),
MainThreadScriptMsg::Common(CommonScriptMsg::RunnableMsg(_, runnable)) =>
// The category of the runnable is ignored by the pattern, however
// it is still respected by profiling (see categorize_msg).
@ -983,6 +1003,22 @@ impl ScriptTask {
}
}
fn handle_timer_event(&self, timer_event: TimerEvent) {
let TimerEvent(source, id) = timer_event;
let pipeline_id = match source {
TimerSource::FromWindow(pipeline_id) => pipeline_id,
TimerSource::FromWorker => panic!("Worker timeouts must not be sent to script task"),
};
let page = self.root_page();
let page = page.find(pipeline_id).expect("ScriptTask: received fire timer msg for a
pipeline ID not associated with this script task. This is a bug.");
let window = page.window();
window.r().handle_fire_timer(id);
}
fn handle_msg_from_devtools(&self, msg: DevtoolScriptControlMsg) {
let page = self.root_page();
match msg {
@ -1272,15 +1308,6 @@ impl ScriptTask {
reports_chan.send(reports);
}
/// Handles a timer that fired.
fn handle_fire_timer_msg(&self, id: PipelineId, timer_id: TimerId) {
let page = self.root_page();
let page = page.find(id).expect("ScriptTask: received fire timer msg for a
pipeline ID not associated with this script task. This is a bug.");
let window = page.window();
window.r().handle_fire_timer(timer_id);
}
/// Handles freeze message
fn handle_freeze_msg(&self, id: PipelineId) {
// Workaround for a race condition when navigating before the initial page has
@ -1587,6 +1614,8 @@ impl ScriptTask {
self.mem_profiler_chan.clone(),
self.devtools_chan.clone(),
self.constellation_chan.clone(),
self.scheduler_chan.clone(),
MainThreadTimerEventChan(self.timer_event_chan.clone()),
incomplete.layout_chan,
incomplete.pipeline_id,
incomplete.parent_info,

Просмотреть файл

@ -8,81 +8,63 @@ use dom::bindings::codegen::Bindings::FunctionBinding::Function;
use dom::bindings::global::global_object_for_js_object;
use dom::bindings::utils::Reflectable;
use dom::window::ScriptHelpers;
use horribly_inefficient_timers;
use euclid::length::Length;
use js::jsapi::{HandleValue, Heap, RootedValue};
use js::jsval::{JSVal, UndefinedValue};
use script_task::{CommonScriptMsg, ScriptChan, TimerSource};
use std::borrow::ToOwned;
use num::traits::Saturating;
use script_traits::{MsDuration, precise_time_ms};
use script_traits::{TimerEventChan, TimerEventId, TimerEventRequest, TimerSource};
use std::cell::Cell;
use std::cmp;
use std::collections::HashMap;
use std::cmp::{self, Ord, Ordering};
use std::default::Default;
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use std::sync::mpsc::Select;
use std::sync::mpsc::{Sender, channel};
use std::sync::mpsc::Sender;
use util::mem::HeapSizeOf;
use util::str::DOMString;
use util::task::spawn_named;
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf)]
pub struct TimerId(i32);
#[derive(JSTraceable, PartialEq, Eq, Copy, Clone, HeapSizeOf, Hash, PartialOrd, Ord)]
pub struct TimerHandle(i32);
#[derive(JSTraceable, HeapSizeOf)]
#[privatize]
struct TimerHandle {
handle: TimerId,
data: TimerData,
#[ignore_heap_size_of = "channels are hard"]
control_chan: Option<Sender<TimerControlMsg>>,
}
#[derive(JSTraceable, Clone)]
pub enum TimerCallback {
StringTimerCallback(DOMString),
FunctionTimerCallback(Rc<Function>)
}
impl HeapSizeOf for TimerCallback {
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 Hash for TimerId {
fn hash<H: Hasher>(&self, state: &mut H) {
let TimerId(id) = *self;
id.hash(state);
}
}
impl TimerHandle {
fn cancel(&mut self) {
self.control_chan.as_ref().map(|chan| chan.send(TimerControlMsg::Cancel).ok());
}
fn suspend(&mut self) {
self.control_chan.as_ref().map(|chan| chan.send(TimerControlMsg::Suspend).ok());
}
fn resume(&mut self) {
self.control_chan.as_ref().map(|chan| chan.send(TimerControlMsg::Resume).ok());
}
pub struct ActiveTimers {
#[ignore_heap_size_of = "Defined in std"]
timer_event_chan: Box<TimerEventChan + Send>,
#[ignore_heap_size_of = "Defined in std"]
scheduler_chan: Sender<TimerEventRequest>,
next_timer_handle: Cell<TimerHandle>,
timers: DOMRefCell<Vec<Timer>>,
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
/// offset back by this amount for a coherent time across document
/// activations.
suspension_offset: Cell<MsDuration>,
/// Calls to `fire_timer` with a different argument than this get ignored.
/// They were previously scheduled and got invalidated when
/// - timers were suspended,
/// - the timer it was scheduled for got canceled or
/// - 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 task 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]
pub struct TimerManager {
active_timers: DOMRefCell<HashMap<TimerId, TimerHandle>>,
next_timer_handle: Cell<i32>,
}
impl Drop for TimerManager {
fn drop(&mut self) {
for (_, timer_handle) in &mut *self.active_timers.borrow_mut() {
timer_handle.cancel();
}
}
struct Timer {
handle: TimerHandle,
source: TimerSource,
callback: InternalTimerCallback,
is_interval: IsInterval,
nesting_level: u32,
duration: MsDuration,
next_call: MsDuration,
}
// Enum allowing more descriptive values for the is_interval field
@ -92,175 +74,269 @@ pub enum IsInterval {
NonInterval,
}
// Messages sent control timers from script task
#[derive(JSTraceable, PartialEq, Copy, Clone, Debug)]
pub enum TimerControlMsg {
Cancel,
Suspend,
Resume
impl Ord for Timer {
fn cmp(&self, other: &Timer) -> Ordering {
match self.next_call.cmp(&other.next_call).reverse() {
Ordering::Equal => self.handle.cmp(&other.handle).reverse(),
res @ _ => res
}
}
}
// 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 TimerData {
is_interval: IsInterval,
callback: TimerCallback,
args: Vec<Heap<JSVal>>
impl PartialOrd for Timer {
fn partial_cmp(&self, other: &Timer) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl TimerManager {
pub fn new() -> TimerManager {
TimerManager {
active_timers: DOMRefCell::new(HashMap::new()),
next_timer_handle: Cell::new(0)
impl Eq for Timer {}
impl PartialEq for Timer {
fn eq(&self, other: &Timer) -> bool {
self as *const Timer == other as *const Timer
}
}
#[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 ActiveTimers {
pub fn new(timer_event_chan: Box<TimerEventChan + Send>,
scheduler_chan: Sender<TimerEventRequest>)
-> ActiveTimers {
ActiveTimers {
timer_event_chan: timer_event_chan,
scheduler_chan: scheduler_chan,
next_timer_handle: Cell::new(TimerHandle(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 {
assert!(self.suspended_since.get().is_none());
// step 3
let TimerHandle(new_handle) = self.next_timer_handle.get();
self.next_timer_handle.set(TimerHandle(new_handle + 1));
let timeout = cmp::max(0, timeout);
// step 7
let duration = self.clamp_duration(Length::new(timeout as u64));
let next_call = self.base_time() + duration;
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 timer = Timer {
handle: TimerHandle(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,
};
self.insert_timer(timer);
let TimerHandle(max_handle) = self.timers.borrow().last().unwrap().handle;
if max_handle == new_handle {
self.schedule_timer_call();
}
// step 10
new_handle
}
pub fn clear_timeout_or_interval(&self, handle: i32) {
let handle = TimerHandle(handle);
let was_next = self.is_next_timer(handle);
self.timers.borrow_mut().retain(|t| t.handle != handle);
if was_next {
self.invalidate_expected_event_id();
self.schedule_timer_call();
}
}
// see https://html.spec.whatwg.org/multipage/#timer-initialisation-steps
#[allow(unsafe_code)]
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);
return;
}
assert!(self.suspended_since.get().is_none());
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);
loop {
let timer = {
let mut timers = self.timers.borrow_mut();
if timers.is_empty() || timers.last().unwrap().next_call > base_time {
break;
}
timers.pop().unwrap()
};
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 proxy = this.reflector().get_jsobject();
let cx = global_object_for_js_object(proxy.get()).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);
}
};
self.nesting_level.set(0);
}
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 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) {
assert!(self.suspended_since.get().is_none());
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();
}
}
pub fn suspend(&self) {
for (_, timer_handle) in &mut *self.active_timers.borrow_mut() {
timer_handle.suspend();
}
assert!(self.suspended_since.get().is_none());
self.suspended_since.set(Some(precise_time_ms()));
self.invalidate_expected_event_id();
}
pub fn resume(&self) {
for (_, timer_handle) in &mut *self.active_timers.borrow_mut() {
timer_handle.resume();
}
}
assert!(self.suspended_since.get().is_some());
#[allow(unsafe_code)]
pub fn set_timeout_or_interval(&self,
callback: TimerCallback,
arguments: Vec<HandleValue>,
timeout: i32,
is_interval: IsInterval,
source: TimerSource,
script_chan: Box<ScriptChan + Send>)
-> i32 {
let duration_ms = cmp::max(0, timeout) as u32;
let handle = self.next_timer_handle.get();
self.next_timer_handle.set(handle + 1);
// Spawn a new timer task; it will dispatch the `CommonScriptMsg::FireTimer`
// to the relevant script handler that will deal with it.
let (control_chan, control_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",
}.to_owned();
spawn_named(spawn_name, move || {
let timeout_port = if is_interval == IsInterval::Interval {
horribly_inefficient_timers::periodic(duration_ms)
} else {
horribly_inefficient_timers::oneshot(duration_ms)
};
let control_port = control_port;
let select = Select::new();
let mut timeout_handle = select.handle(&timeout_port);
unsafe { timeout_handle.add() };
let mut control_handle = select.handle(&control_port);
unsafe { control_handle.add() };
loop {
let id = select.wait();
if id == timeout_handle.id() {
timeout_port.recv().unwrap();
if script_chan.send(CommonScriptMsg::FireTimer(source, TimerId(handle))).is_err() {
break;
}
if is_interval == IsInterval::NonInterval {
break;
}
} else if id == control_handle.id() {
match control_port.recv().unwrap() {
TimerControlMsg::Suspend => {
let msg = control_port.recv().unwrap();
match msg {
TimerControlMsg::Suspend => panic!("Nothing to suspend!"),
TimerControlMsg::Resume => {},
TimerControlMsg::Cancel => {
break;
},
}
},
TimerControlMsg::Resume => panic!("Nothing to resume!"),
TimerControlMsg::Cancel => {
break;
}
}
}
}
});
let timer_id = TimerId(handle);
let timer = TimerHandle {
handle: timer_id,
control_chan: Some(control_chan),
data: TimerData {
is_interval: is_interval,
callback: callback,
args: Vec::with_capacity(arguments.len())
}
let additional_offset = match self.suspended_since.get() {
Some(suspended_since) => precise_time_ms() - suspended_since,
None => panic!("Timers are not suspended.")
};
self.active_timers.borrow_mut().insert(timer_id, timer);
// 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 timers = self.active_timers.borrow_mut();
let mut timer = timers.get_mut(&timer_id).unwrap();
for _ in 0..arguments.len() {
timer.data.args.push(Heap::default());
}
for (i, item) in arguments.iter().enumerate() {
timer.data.args.get_mut(i).unwrap().set(item.get());
}
handle
self.suspension_offset.set(self.suspension_offset.get() + additional_offset);
self.schedule_timer_call();
}
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 => {}
}
fn base_time(&self) -> MsDuration {
precise_time_ms() - self.suspension_offset.get()
}
#[allow(unsafe_code)]
pub fn fire_timer<T: Reflectable>(&self, timer_id: TimerId, this: &T) {
// 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 (is_interval, callback, args): (IsInterval, TimerCallback, Vec<JSVal>) =
match self.active_timers.borrow().get(&timer_id) {
Some(timer_handle) =>
(timer_handle.data.is_interval,
timer_handle.data.callback.clone(),
timer_handle.data.args.iter().map(|arg| arg.get()).collect()),
None => return,
};
cmp::max(Length::new(ms), unclamped)
}
match callback {
TimerCallback::FunctionTimerCallback(function) => {
let arg_handles = args.iter().by_ref().map(|arg| unsafe {
HandleValue::from_marked_location(arg)
}).collect();
let _ = function.Call_(this, arg_handles, Report);
}
TimerCallback::StringTimerCallback(code_str) => {
let proxy = this.reflector().get_jsobject();
let cx = global_object_for_js_object(proxy.get()).r().get_cx();
let mut rval = RootedValue::new(cx, UndefinedValue());
this.evaluate_js_on_global_with_result(&code_str, rval.handle_mut());
}
}
if is_interval == IsInterval::NonInterval {
self.active_timers.borrow_mut().remove(&timer_id);
}
fn invalidate_expected_event_id(&self) -> TimerEventId {
let TimerEventId(currently_expected) = self.expected_event_id.get();
let next_id = TimerEventId(currently_expected + 1);
debug!("invalidating expected timer (was {:?}, now {:?}", currently_expected, next_id);
self.expected_event_id.set(next_id);
next_id
}
}

Просмотреть файл

@ -38,3 +38,4 @@ libc = "0.1"
euclid = "0.2"
serde = "0.6"
serde_macros = "0.5"
time = "0.1.12"

Просмотреть файл

@ -19,11 +19,13 @@ extern crate msg;
extern crate net_traits;
extern crate profile_traits;
extern crate serde;
extern crate time;
extern crate url;
extern crate util;
use app_units::Au;
use devtools_traits::ScriptToDevtoolsControlMsg;
use euclid::length::Length;
use euclid::point::Point2D;
use euclid::rect::Rect;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
@ -36,10 +38,11 @@ use msg::webdriver_msg::WebDriverScriptCommand;
use net_traits::ResourceTask;
use net_traits::image_cache_task::ImageCacheTask;
use net_traits::storage_task::StorageTask;
use profile_traits::{mem, time};
use profile_traits::mem;
use std::any::Any;
use std::sync::mpsc::{Receiver, Sender};
use url::Url;
use util::mem::HeapSizeOf;
/// The address of a node. Layout sends these back. They must be validated via
/// `from_untrusted_node_address` before they can be used, because we do not trust layout.
@ -177,6 +180,56 @@ pub enum CompositorEvent {
/// crates that don't need to know about them.
pub struct OpaqueScriptLayoutChannel(pub (Box<Any + Send>, Box<Any + Send>));
/// Requests a TimerEvent-Message be sent after the given duration.
pub struct TimerEventRequest(pub Box<TimerEventChan + Send>, pub TimerSource, pub TimerEventId, pub MsDuration);
/// Notifies the script task to fire due timers.
/// TimerSource must be FromWindow when dispatched to ScriptTask and
/// must be FromWorker when dispatched to a DedicatedGlobalWorkerScope
pub struct TimerEvent(pub TimerSource, pub TimerEventId);
/// A cloneable interface for sending timer events.
pub trait TimerEventChan {
/// Send a timer event to the associated event loop.
fn send(&self, msg: TimerEvent) -> Result<(), ()>;
/// Clone this handle.
fn clone(&self) -> Box<TimerEventChan + Send>;
}
/// Describes the task that requested the TimerEvent.
#[derive(Copy, Clone, HeapSizeOf)]
pub enum TimerSource {
/// The event was requested from a window (ScriptTask).
FromWindow(PipelineId),
/// The event was requested from a worker (DedicatedGlobalWorkerScope).
FromWorker
}
/// The id to be used for a TimerEvent is defined by the corresponding TimerEventRequest.
#[derive(PartialEq, Eq, Copy, Clone, Debug, HeapSizeOf)]
pub struct TimerEventId(pub u32);
/// Unit of measurement.
#[derive(Clone, Copy, HeapSizeOf)]
pub enum Milliseconds {}
/// Unit of measurement.
#[derive(Clone, Copy, HeapSizeOf)]
pub enum Nanoseconds {}
/// Amount of milliseconds.
pub type MsDuration = Length<Milliseconds, u64>;
/// Amount of nanoseconds.
pub type NsDuration = Length<Nanoseconds, u64>;
/// Returns the duration since an unspecified epoch measured in ms.
pub fn precise_time_ms() -> MsDuration {
Length::new(time::precise_time_ns() / (1000 * 1000))
}
/// Returns the duration since an unspecified epoch measured in ns.
pub fn precise_time_ns() -> NsDuration {
Length::new(time::precise_time_ns())
}
/// Data needed to construct a script thread.
pub struct InitialScriptState {
/// The ID of the pipeline with which this script thread is associated.
@ -192,6 +245,8 @@ pub struct InitialScriptState {
pub control_port: Receiver<ConstellationControlMsg>,
/// A channel on which messages can be sent to the constellation from script.
pub constellation_chan: ConstellationChan,
/// A channel to schedule timer events.
pub scheduler_chan: Sender<TimerEventRequest>,
/// Information that script sends out when it panics.
pub failure_info: Failure,
/// A channel to the resource manager task.
@ -201,7 +256,7 @@ pub struct InitialScriptState {
/// A channel to the image cache task.
pub image_cache_task: ImageCacheTask,
/// A channel to the time profiler thread.
pub time_profiler_chan: time::ProfilerChan,
pub time_profiler_chan: profile_traits::time::ProfilerChan,
/// A channel to the memory profiler thread.
pub mem_profiler_chan: mem::ProfilerChan,
/// A channel to the developer tools, if applicable.

1
servo/components/servo/Cargo.lock сгенерированный
Просмотреть файл

@ -1567,6 +1567,7 @@ dependencies = [
"profile_traits 0.0.1",
"serde 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
]

1
servo/ports/cef/Cargo.lock сгенерированный
Просмотреть файл

@ -1491,6 +1491,7 @@ dependencies = [
"profile_traits 0.0.1",
"serde 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
]

1
servo/ports/gonk/Cargo.lock сгенерированный
Просмотреть файл

@ -1353,6 +1353,7 @@ dependencies = [
"profile_traits 0.0.1",
"serde 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_macros 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)",
"util 0.0.1",
]