diff options
author | Gregory Terzian <gterzian@users.noreply.github.com> | 2019-11-17 14:37:45 +0800 |
---|---|---|
committer | Gregory Terzian <gterzian@users.noreply.github.com> | 2019-11-19 14:32:21 +0800 |
commit | 50a7111eb65155fc7a0bdb1646c4a2e0ebdd8f90 (patch) | |
tree | 503c793b58480822a9e74e064ec4876f3e74e9fe | |
parent | d553158e95cd5988eeb3e32310c11971c0450449 (diff) | |
download | servo-50a7111eb65155fc7a0bdb1646c4a2e0ebdd8f90.tar.gz servo-50a7111eb65155fc7a0bdb1646c4a2e0ebdd8f90.zip |
impl timer-task-source, dedicated time-out mechanism for service-worker
-rw-r--r-- | components/script/dom/abstractworkerglobalscope.rs | 21 | ||||
-rw-r--r-- | components/script/dom/dedicatedworkerglobalscope.rs | 45 | ||||
-rw-r--r-- | components/script/dom/dissimilaroriginwindow.rs | 4 | ||||
-rw-r--r-- | components/script/dom/globalscope.rs | 103 | ||||
-rw-r--r-- | components/script/dom/serviceworkerglobalscope.rs | 87 | ||||
-rw-r--r-- | components/script/dom/window.rs | 5 | ||||
-rw-r--r-- | components/script/dom/workerglobalscope.rs | 12 | ||||
-rw-r--r-- | components/script/dom/workletglobalscope.rs | 4 | ||||
-rw-r--r-- | components/script/script_thread.rs | 82 | ||||
-rw-r--r-- | components/script/task_manager.rs | 13 | ||||
-rw-r--r-- | components/script/task_source/mod.rs | 2 | ||||
-rw-r--r-- | components/script/task_source/timer.rs | 42 | ||||
-rw-r--r-- | components/script/timers.rs | 25 |
13 files changed, 241 insertions, 204 deletions
diff --git a/components/script/dom/abstractworkerglobalscope.rs b/components/script/dom/abstractworkerglobalscope.rs index e8e41375b70..d5186f91845 100644 --- a/components/script/dom/abstractworkerglobalscope.rs +++ b/components/script/dom/abstractworkerglobalscope.rs @@ -81,32 +81,27 @@ impl ScriptPort for Receiver<DedicatedWorkerScriptMsg> { } pub trait WorkerEventLoopMethods { - type TimerMsg: Send; type WorkerMsg: QueuedTaskConversion + Send; type Event; - fn timer_event_port(&self) -> &Receiver<Self::TimerMsg>; fn task_queue(&self) -> &TaskQueue<Self::WorkerMsg>; fn handle_event(&self, event: Self::Event); fn handle_worker_post_event(&self, worker: &TrustedWorkerAddress) -> Option<AutoWorkerReset>; fn from_worker_msg(&self, msg: Self::WorkerMsg) -> Self::Event; - fn from_timer_msg(&self, msg: Self::TimerMsg) -> Self::Event; fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> Self::Event; } // https://html.spec.whatwg.org/multipage/#worker-event-loop -pub fn run_worker_event_loop<T, TimerMsg, WorkerMsg, Event>( +pub fn run_worker_event_loop<T, WorkerMsg, Event>( worker_scope: &T, worker: Option<&TrustedWorkerAddress>, ) where - TimerMsg: Send, WorkerMsg: QueuedTaskConversion + Send, - T: WorkerEventLoopMethods<TimerMsg = TimerMsg, WorkerMsg = WorkerMsg, Event = Event> + T: WorkerEventLoopMethods<WorkerMsg = WorkerMsg, Event = Event> + DerivedFrom<WorkerGlobalScope> + DerivedFrom<GlobalScope> + DomObject, { let scope = worker_scope.upcast::<WorkerGlobalScope>(); - let timer_event_port = worker_scope.timer_event_port(); let devtools_port = match scope.from_devtools_sender() { Some(_) => Some(scope.from_devtools_receiver()), None => None, @@ -117,7 +112,6 @@ pub fn run_worker_event_loop<T, TimerMsg, WorkerMsg, Event>( task_queue.take_tasks(msg.unwrap()); worker_scope.from_worker_msg(task_queue.recv().unwrap()) }, - recv(timer_event_port) -> msg => worker_scope.from_timer_msg(msg.unwrap()), recv(devtools_port.unwrap_or(&crossbeam_channel::never())) -> msg => worker_scope.from_devtools_msg(msg.unwrap()), }; @@ -132,13 +126,10 @@ pub fn run_worker_event_loop<T, TimerMsg, WorkerMsg, Event>( // Batch all events that are ready. // The task queue will throttle non-priority tasks if necessary. match task_queue.try_recv() { - Err(_) => match timer_event_port.try_recv() { - Err(_) => match devtools_port.map(|port| port.try_recv()) { - None => {}, - Some(Err(_)) => break, - Some(Ok(ev)) => sequential.push(worker_scope.from_devtools_msg(ev)), - }, - Ok(ev) => sequential.push(worker_scope.from_timer_msg(ev)), + Err(_) => match devtools_port.map(|port| port.try_recv()) { + None => {}, + Some(Err(_)) => break, + Some(Ok(ev)) => sequential.push(worker_scope.from_devtools_msg(ev)), }, Ok(ev) => sequential.push(worker_scope.from_worker_msg(ev)), } diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index f5895379003..b8321526358 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -36,7 +36,7 @@ use crate::task_source::TaskSourceName; use crossbeam_channel::{unbounded, Receiver, Sender}; use devtools_traits::DevtoolScriptControlMsg; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::IpcReceiver; use ipc_channel::router::ROUTER; use js::jsapi::JS_AddInterruptCallback; use js::jsapi::{Heap, JSContext, JSObject}; @@ -47,7 +47,7 @@ use net_traits::image_cache::ImageCache; use net_traits::request::{CredentialsMode, Destination, ParserMetadata}; use net_traits::request::{Referrer, RequestBuilder, RequestMode}; use net_traits::IpcSend; -use script_traits::{TimerEvent, TimerSource, WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; +use script_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; use servo_rand::random; use servo_url::ServoUrl; use std::mem::replace; @@ -92,7 +92,6 @@ pub enum DedicatedWorkerScriptMsg { pub enum MixedMessage { FromWorker(DedicatedWorkerScriptMsg), - FromScheduler((TrustedWorkerAddress, TimerEvent)), FromDevtools(DevtoolScriptControlMsg), } @@ -173,8 +172,6 @@ pub struct DedicatedWorkerGlobalScope { task_queue: TaskQueue<DedicatedWorkerScriptMsg>, #[ignore_malloc_size_of = "Defined in std"] own_sender: Sender<DedicatedWorkerScriptMsg>, - #[ignore_malloc_size_of = "Defined in std"] - timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>, #[ignore_malloc_size_of = "Trusted<T> has unclear ownership like Dom<T>"] worker: DomRefCell<Option<TrustedWorkerAddress>>, #[ignore_malloc_size_of = "Can't measure trait objects"] @@ -185,14 +182,9 @@ pub struct DedicatedWorkerGlobalScope { } impl WorkerEventLoopMethods for DedicatedWorkerGlobalScope { - type TimerMsg = (TrustedWorkerAddress, TimerEvent); type WorkerMsg = DedicatedWorkerScriptMsg; type Event = MixedMessage; - fn timer_event_port(&self) -> &Receiver<(TrustedWorkerAddress, TimerEvent)> { - &self.timer_event_port - } - fn task_queue(&self) -> &TaskQueue<DedicatedWorkerScriptMsg> { &self.task_queue } @@ -210,10 +202,6 @@ impl WorkerEventLoopMethods for DedicatedWorkerGlobalScope { MixedMessage::FromWorker(msg) } - fn from_timer_msg(&self, msg: (TrustedWorkerAddress, TimerEvent)) -> MixedMessage { - MixedMessage::FromScheduler(msg) - } - fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> MixedMessage { MixedMessage::FromDevtools(msg) } @@ -230,8 +218,6 @@ impl DedicatedWorkerGlobalScope { parent_sender: Box<dyn ScriptChan + Send>, own_sender: Sender<DedicatedWorkerScriptMsg>, receiver: Receiver<DedicatedWorkerScriptMsg>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>, closing: Arc<AtomicBool>, image_cache: Arc<dyn ImageCache>, ) -> DedicatedWorkerGlobalScope { @@ -243,12 +229,10 @@ impl DedicatedWorkerGlobalScope { worker_url, runtime, from_devtools_receiver, - timer_event_chan, Some(closing), ), task_queue: TaskQueue::new(receiver, own_sender.clone()), own_sender: own_sender, - timer_event_port: timer_event_port, parent_sender: parent_sender, worker: DomRefCell::new(None), image_cache: image_cache, @@ -266,8 +250,6 @@ impl DedicatedWorkerGlobalScope { parent_sender: Box<dyn ScriptChan + Send>, own_sender: Sender<DedicatedWorkerScriptMsg>, receiver: Receiver<DedicatedWorkerScriptMsg>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<(TrustedWorkerAddress, TimerEvent)>, closing: Arc<AtomicBool>, image_cache: Arc<dyn ImageCache>, ) -> DomRoot<DedicatedWorkerGlobalScope> { @@ -282,8 +264,6 @@ impl DedicatedWorkerGlobalScope { parent_sender, own_sender, receiver, - timer_event_chan, - timer_event_port, closing, image_cache, )); @@ -352,17 +332,6 @@ impl DedicatedWorkerGlobalScope { devtools_mpsc_chan, ); - let (timer_tx, timer_rx) = unbounded(); - let (timer_ipc_chan, timer_ipc_port) = ipc::channel().unwrap(); - let worker_for_route = worker.clone(); - ROUTER.add_route( - timer_ipc_port.to_opaque(), - Box::new(move |message| { - let event = message.to().unwrap(); - timer_tx.send((worker_for_route.clone(), event)).unwrap(); - }), - ); - let global = DedicatedWorkerGlobalScope::new( init, DOMString::from_string(worker_name), @@ -373,8 +342,6 @@ impl DedicatedWorkerGlobalScope { parent_sender.clone(), own_sender, receiver, - timer_ipc_chan, - timer_rx, closing, image_cache, ); @@ -503,14 +470,6 @@ 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(DedicatedWorkerScriptMsg::CommonWorker( linked_worker, msg, diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs index ac596048717..ac866a90ff8 100644 --- a/components/script/dom/dissimilaroriginwindow.rs +++ b/components/script/dom/dissimilaroriginwindow.rs @@ -15,7 +15,6 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::windowproxy::WindowProxy; use crate::script_runtime::JSContext; use dom_struct::dom_struct; -use ipc_channel::ipc; use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, UndefinedValue}; use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue}; @@ -48,8 +47,6 @@ impl DissimilarOriginWindow { #[allow(unsafe_code)] pub fn new(global_to_clone_from: &GlobalScope, window_proxy: &WindowProxy) -> DomRoot<Self> { let cx = global_to_clone_from.get_cx(); - // Any timer events fired on this window are ignored. - let (timer_event_chan, _) = ipc::channel().unwrap(); let win = Box::new(Self { globalscope: GlobalScope::new_inherited( PipelineId::new(), @@ -59,7 +56,6 @@ impl DissimilarOriginWindow { global_to_clone_from.script_to_constellation_chan().clone(), global_to_clone_from.scheduler_chan().clone(), global_to_clone_from.resource_threads().clone(), - timer_event_chan, global_to_clone_from.origin().clone(), // FIXME(nox): The microtask queue is probably not important // here, but this whole DOM interface is a hack anyway. diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 1933d1e926e..4896c59e048 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -39,6 +39,7 @@ use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; +use crate::task_source::timer::TimerTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::task_source::TaskSource; use crate::task_source::TaskSourceName; @@ -134,8 +135,13 @@ pub struct GlobalScope { /// including resource_thread, filemanager_thread and storage_thread resource_threads: ResourceThreads, + /// The mechanism by which time-outs and intervals are scheduled. + /// <https://html.spec.whatwg.org/multipage/#timers> timers: OneshotTimers, + /// Have timers been initialized? + init_timers: Cell<bool>, + /// The origin of the globalscope origin: MutableOrigin, @@ -188,6 +194,13 @@ struct MessageListener { context: Trusted<GlobalScope>, } +/// A wrapper between timer events coming in over IPC, and the event-loop. +struct TimerListener { + canceller: TaskCanceller, + task_source: TimerTaskSource, + context: Trusted<GlobalScope>, +} + /// Data representing a message-port managed by this global. #[derive(JSTraceable, MallocSizeOf)] pub enum ManagedMessagePort { @@ -212,6 +225,34 @@ pub enum MessagePortState { UnManaged, } +impl TimerListener { + /// Handle a timer-event coming-in over IPC, + /// by queuing the appropriate task on the relevant event-loop. + fn handle(&self, event: TimerEvent) { + let context = self.context.clone(); + // Step 18, queue a task, + // https://html.spec.whatwg.org/multipage/#timer-initialisation-steps + let _ = self.task_source.queue_with_canceller( + task!(timer_event: move || { + let global = context.root(); + let TimerEvent(source, id) = event; + match source { + TimerSource::FromWorker => { + global.downcast::<WorkerGlobalScope>().expect("Window timer delivered to worker"); + }, + TimerSource::FromWindow(pipeline) => { + assert_eq!(pipeline, global.pipeline_id()); + global.downcast::<Window>().expect("Worker timer delivered to window"); + }, + }; + // Step 7, substeps run in a task. + global.fire_timer(id); + }), + &self.canceller, + ); + } +} + impl MessageListener { /// A new message came in, handle it via a task enqueued on the event-loop. /// A task is required, since we are using a trusted globalscope, @@ -297,7 +338,6 @@ impl GlobalScope { script_to_constellation_chan: ScriptToConstellationChan, scheduler_chan: IpcSender<TimerSchedulerMsg>, resource_threads: ResourceThreads, - timer_event_chan: IpcSender<TimerEvent>, origin: MutableOrigin, microtask_queue: Rc<MicrotaskQueue>, is_headless: bool, @@ -318,7 +358,8 @@ impl GlobalScope { scheduler_chan: scheduler_chan.clone(), in_error_reporting_mode: Default::default(), resource_threads, - timers: OneshotTimers::new(timer_event_chan, scheduler_chan), + timers: OneshotTimers::new(scheduler_chan), + init_timers: Default::default(), origin, microtask_queue, list_auto_close_worker: Default::default(), @@ -349,6 +390,36 @@ impl GlobalScope { false } + /// Setup the IPC-to-event-loop glue for timers to schedule themselves. + fn setup_timers(&self) { + if self.init_timers.get() { + return; + } + self.init_timers.set(true); + + let (timer_ipc_chan, timer_ipc_port) = ipc::channel().unwrap(); + self.timers.setup_scheduling(timer_ipc_chan); + + // Setup route from IPC to task-queue for the timer-task-source. + let context = Trusted::new(&*self); + let (task_source, canceller) = ( + self.timer_task_source(), + self.task_canceller(TaskSourceName::Timer), + ); + let timer_listener = TimerListener { + context, + task_source, + canceller, + }; + ROUTER.add_route( + timer_ipc_port.to_opaque(), + Box::new(move |message| { + let event = message.to().unwrap(); + timer_listener.handle(event); + }), + ); + } + /// Complete the transfer of a message-port. fn complete_port_transfer(&self, port_id: MessagePortId, tasks: VecDeque<PortMessageTask>) { let should_start = if let MessagePortState::Managed(_id, message_ports) = @@ -1063,6 +1134,18 @@ impl GlobalScope { unreachable!(); } + /// `TaskSource` to send messages to the timer queue of + /// this global scope. + pub fn timer_task_source(&self) -> TimerTaskSource { + if let Some(window) = self.downcast::<Window>() { + return window.task_manager().timer_task_source(); + } + if let Some(worker) = self.downcast::<WorkerGlobalScope>() { + return worker.timer_task_source(); + } + unreachable!(); + } + /// `TaskSource` to send messages to the remote-event task source of /// this global scope. pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { @@ -1145,11 +1228,13 @@ impl GlobalScope { ) } + /// <https://html.spec.whatwg.org/multipage/#timer-initialisation-steps> pub fn schedule_callback( &self, callback: OneshotTimerCallback, duration: MsDuration, ) -> OneshotTimerHandle { + self.setup_timers(); self.timers .schedule_callback(callback, duration, self.timer_source()) } @@ -1158,6 +1243,7 @@ impl GlobalScope { self.timers.unschedule_callback(handle); } + /// <https://html.spec.whatwg.org/multipage/#timer-initialisation-steps> pub fn set_timeout_or_interval( &self, callback: TimerCallback, @@ -1165,6 +1251,7 @@ impl GlobalScope { timeout: i32, is_interval: IsInterval, ) -> i32 { + self.setup_timers(); self.timers.set_timeout_or_interval( self, callback, @@ -1176,27 +1263,27 @@ impl GlobalScope { } pub fn clear_timeout_or_interval(&self, handle: i32) { - self.timers.clear_timeout_or_interval(self, handle) + self.timers.clear_timeout_or_interval(self, handle); } pub fn fire_timer(&self, handle: TimerEventId) { - self.timers.fire_timer(handle, self) + self.timers.fire_timer(handle, self); } pub fn resume(&self) { - self.timers.resume() + self.timers.resume(); } pub fn suspend(&self) { - self.timers.suspend() + self.timers.suspend(); } pub fn slow_down_timers(&self) { - self.timers.slow_down() + self.timers.slow_down(); } pub fn speed_up_timers(&self) { - self.timers.speed_up() + self.timers.speed_up(); } fn timer_source(&self) -> TimerSource { diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 657f43627ed..bdaf34e72cf 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -28,24 +28,22 @@ use crate::script_runtime::{ }; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use crate::task_source::TaskSourceName; -use crossbeam_channel::{unbounded, Receiver, Sender}; +use crossbeam_channel::{after, unbounded, Receiver, Sender}; use devtools_traits::DevtoolScriptControlMsg; use dom_struct::dom_struct; -use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::ipc::{IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use js::jsapi::{JSContext, JS_AddInterruptCallback}; use js::jsval::UndefinedValue; use msg::constellation_msg::PipelineId; use net_traits::request::{CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder}; use net_traits::{CustomResponseMediator, IpcSend}; -use script_traits::{ - ScopeThings, ServiceWorkerMsg, TimerEvent, WorkerGlobalScopeInit, WorkerScriptLoadOrigin, -}; +use script_traits::{ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin}; use servo_config::pref; use servo_rand::random; use servo_url::ServoUrl; use std::thread; -use std::time::Duration; +use std::time::{Duration, Instant}; use style::thread_state::{self, ThreadState}; /// Messages used to control service worker event loop @@ -118,7 +116,6 @@ impl QueuedTaskConversion for ServiceWorkerScriptMsg { pub enum MixedMessage { FromServiceWorker(ServiceWorkerScriptMsg), FromDevtools(DevtoolScriptControlMsg), - FromTimeoutThread(()), } #[derive(Clone, JSTraceable)] @@ -147,26 +144,30 @@ unsafe_no_jsmanaged_fields!(TaskQueue<ServiceWorkerScriptMsg>); #[dom_struct] pub struct ServiceWorkerGlobalScope { workerglobalscope: WorkerGlobalScope, + #[ignore_malloc_size_of = "Defined in std"] task_queue: TaskQueue<ServiceWorkerScriptMsg>, + #[ignore_malloc_size_of = "Defined in std"] own_sender: Sender<ServiceWorkerScriptMsg>, + + /// A port on which a single "time-out" message can be received, + /// indicating the sw should stop running, + /// while still draining the task-queue + // and running all enqueued, and not cancelled, tasks. #[ignore_malloc_size_of = "Defined in std"] - timer_event_port: Receiver<()>, + time_out_port: Receiver<Instant>, + #[ignore_malloc_size_of = "Defined in std"] swmanager_sender: IpcSender<ServiceWorkerMsg>, + scope_url: ServoUrl, } impl WorkerEventLoopMethods for ServiceWorkerGlobalScope { - type TimerMsg = (); type WorkerMsg = ServiceWorkerScriptMsg; type Event = MixedMessage; - fn timer_event_port(&self) -> &Receiver<()> { - &self.timer_event_port - } - fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> { &self.task_queue } @@ -183,10 +184,6 @@ impl WorkerEventLoopMethods for ServiceWorkerGlobalScope { MixedMessage::FromServiceWorker(msg) } - fn from_timer_msg(&self, msg: ()) -> MixedMessage { - MixedMessage::FromTimeoutThread(msg) - } - fn from_devtools_msg(&self, msg: DevtoolScriptControlMsg) -> MixedMessage { MixedMessage::FromDevtools(msg) } @@ -200,8 +197,7 @@ impl ServiceWorkerGlobalScope { runtime: Runtime, own_sender: Sender<ServiceWorkerScriptMsg>, receiver: Receiver<ServiceWorkerScriptMsg>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<()>, + time_out_port: Receiver<Instant>, swmanager_sender: IpcSender<ServiceWorkerMsg>, scope_url: ServoUrl, ) -> ServiceWorkerGlobalScope { @@ -213,12 +209,11 @@ impl ServiceWorkerGlobalScope { worker_url, runtime, from_devtools_receiver, - timer_event_chan, None, ), task_queue: TaskQueue::new(receiver, own_sender.clone()), - timer_event_port: timer_event_port, own_sender: own_sender, + time_out_port, swmanager_sender: swmanager_sender, scope_url: scope_url, } @@ -232,8 +227,7 @@ impl ServiceWorkerGlobalScope { runtime: Runtime, own_sender: Sender<ServiceWorkerScriptMsg>, receiver: Receiver<ServiceWorkerScriptMsg>, - timer_event_chan: IpcSender<TimerEvent>, - timer_event_port: Receiver<()>, + time_out_port: Receiver<Instant>, swmanager_sender: IpcSender<ServiceWorkerMsg>, scope_url: ServoUrl, ) -> DomRoot<ServiceWorkerGlobalScope> { @@ -245,8 +239,7 @@ impl ServiceWorkerGlobalScope { runtime, own_sender, receiver, - timer_event_chan, - timer_event_port, + time_out_port, swmanager_sender, scope_url, )); @@ -320,9 +313,12 @@ impl ServiceWorkerGlobalScope { let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); ROUTER .route_ipc_receiver_to_crossbeam_sender(devtools_receiver, devtools_mpsc_chan); - // TODO XXXcreativcoder use this timer_ipc_port, when we have a service worker instance here - let (timer_ipc_chan, _timer_ipc_port) = ipc::channel().unwrap(); - let (timer_chan, timer_port) = unbounded(); + + // Service workers are time limited + // https://w3c.github.io/ServiceWorker/#service-worker-lifetime + let sw_lifetime_timeout = pref!(dom.serviceworker.timeout_seconds) as u64; + let time_out_port = after(Duration::new(sw_lifetime_timeout, 0)); + let global = ServiceWorkerGlobalScope::new( init, url, @@ -330,8 +326,7 @@ impl ServiceWorkerGlobalScope { runtime, own_sender, receiver, - timer_ipc_chan, - timer_port, + time_out_port, swmanager_sender, scope_url, ); @@ -343,15 +338,6 @@ impl ServiceWorkerGlobalScope { } scope.execute_script(DOMString::from(source)); - // Service workers are time limited - thread::Builder::new() - .name("SWTimeoutThread".to_owned()) - .spawn(move || { - let sw_lifetime_timeout = pref!(dom.serviceworker.timeout_seconds) as u64; - thread::sleep(Duration::new(sw_lifetime_timeout, 0)); - let _ = timer_chan.send(()); - }) - .expect("Thread spawning failed"); global.dispatch_activate(); let reporter_name = format!("service-worker-reporter-{}", random::<u64>()); @@ -364,8 +350,9 @@ impl ServiceWorkerGlobalScope { // by inside settings until it is destroyed. // The worker processing model remains on this step // until the event loop is destroyed, - // which happens after the closing flag is set to true. - while !scope.is_closing() { + // which happens after the closing flag is set to true, + // or until the worker has run beyond its allocated time. + while !scope.is_closing() || !global.has_timed_out() { run_worker_event_loop(&*global, None); } }, @@ -398,15 +385,21 @@ impl ServiceWorkerGlobalScope { self.handle_script_event(msg); true }, - MixedMessage::FromTimeoutThread(_) => { - let _ = self - .swmanager_sender - .send(ServiceWorkerMsg::Timeout(self.scope_url.clone())); - false - }, } } + fn has_timed_out(&self) -> bool { + // Note: this should be included in the `select` inside `run_worker_event_loop`, + // otherwise a block on the select can prevent the timeout. + if self.time_out_port.try_recv().is_ok() { + let _ = self + .swmanager_sender + .send(ServiceWorkerMsg::Timeout(self.scope_url.clone())); + return true; + } + false + } + fn handle_script_event(&self, msg: ServiceWorkerScriptMsg) { use self::ServiceWorkerScriptMsg::*; diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 87c80ce62d0..bac74a21a15 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -106,8 +106,7 @@ use script_layout_interface::{PendingImageState, TrustedNodeAddress}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; use script_traits::{ConstellationControlMsg, DocumentState, HistoryEntryReplacement, LoadData}; use script_traits::{ - ScriptMsg, ScriptToConstellationChan, ScrollState, StructuredSerializedData, TimerEvent, - TimerEventId, + ScriptMsg, ScriptToConstellationChan, ScrollState, StructuredSerializedData, TimerEventId, }; use script_traits::{TimerSchedulerMsg, WindowSizeData, WindowSizeType}; use selectors::attr::CaseSensitivity; @@ -2186,7 +2185,6 @@ impl Window { constellation_chan: ScriptToConstellationChan, control_chan: IpcSender<ConstellationControlMsg>, scheduler_chan: IpcSender<TimerSchedulerMsg>, - timer_event_chan: IpcSender<TimerEvent>, layout_chan: Sender<Msg>, pipelineid: PipelineId, parent_info: Option<PipelineId>, @@ -2229,7 +2227,6 @@ impl Window { constellation_chan, scheduler_chan, resource_threads, - timer_event_chan, origin, microtask_queue, is_headless, diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index 7e55cbfc046..e943b6b0ef4 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -34,6 +34,7 @@ use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; +use crate::task_source::timer::TimerTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::timers::{IsInterval, TimerCallback}; use crossbeam_channel::Receiver; @@ -50,7 +51,6 @@ use net_traits::request::{ }; use net_traits::IpcSend; use script_traits::WorkerGlobalScopeInit; -use script_traits::{TimerEvent, TimerEventId}; use servo_url::{MutableOrigin, ServoUrl}; use std::cell::Ref; use std::default::Default; @@ -120,7 +120,6 @@ impl WorkerGlobalScope { worker_url: ServoUrl, runtime: Runtime, from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, - timer_event_chan: IpcSender<TimerEvent>, closing: Option<Arc<AtomicBool>>, ) -> Self { // Install a pipeline-namespace in the current thread. @@ -134,7 +133,6 @@ impl WorkerGlobalScope { init.script_to_constellation_chan, init.scheduler_chan, init.resource_threads, - timer_event_chan, MutableOrigin::new(init.origin), runtime.microtask_queue.clone(), init.is_headless, @@ -437,6 +435,10 @@ impl WorkerGlobalScope { PortMessageQueue(self.script_chan(), self.pipeline_id()) } + pub fn timer_task_source(&self) -> TimerTaskSource { + TimerTaskSource(self.script_chan(), self.pipeline_id()) + } + pub fn remote_event_task_source(&self) -> RemoteEventTaskSource { RemoteEventTaskSource(self.script_chan(), self.pipeline_id()) } @@ -466,10 +468,6 @@ impl WorkerGlobalScope { } } - pub fn handle_fire_timer(&self, timer_id: TimerEventId) { - self.upcast::<GlobalScope>().fire_timer(timer_id); - } - pub fn close(&self) { if let Some(ref closing) = self.closing { closing.store(true, Ordering::SeqCst); diff --git a/components/script/dom/workletglobalscope.rs b/components/script/dom/workletglobalscope.rs index 90fa5bd0115..995014cfc1a 100644 --- a/components/script/dom/workletglobalscope.rs +++ b/components/script/dom/workletglobalscope.rs @@ -15,7 +15,6 @@ use crate::script_thread::MainThreadScriptMsg; use crossbeam_channel::Sender; use devtools_traits::ScriptToDevtoolsControlMsg; use dom_struct::dom_struct; -use ipc_channel::ipc; use ipc_channel::ipc::IpcSender; use js::jsval::UndefinedValue; use js::rust::Runtime; @@ -55,8 +54,6 @@ impl WorkletGlobalScope { executor: WorkletExecutor, init: &WorkletGlobalScopeInit, ) -> Self { - // Any timer events fired on this global are ignored. - let (timer_event_chan, _) = ipc::channel().unwrap(); let script_to_constellation_chan = ScriptToConstellationChan { sender: init.to_constellation_sender.clone(), pipeline_id, @@ -70,7 +67,6 @@ impl WorkletGlobalScope { script_to_constellation_chan, init.scheduler_chan.clone(), init.resource_threads.clone(), - timer_event_chan, MutableOrigin::new(ImmutableOrigin::new_opaque()), Default::default(), init.is_headless, diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index dbc4f0fb444..46250debaac 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -82,6 +82,7 @@ use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; +use crate::task_source::timer::TimerTaskSource; use crate::task_source::user_interaction::UserInteractionTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::task_source::TaskSource; @@ -140,8 +141,8 @@ use script_traits::{ use script_traits::{InitialScriptState, JsEvalResult, LayoutMsg, LoadData, LoadOrigin}; use script_traits::{MouseButton, MouseEventType, NewLayoutInfo}; use script_traits::{Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory}; -use script_traits::{ScriptToConstellationChan, TimerEvent, TimerSchedulerMsg}; -use script_traits::{TimerSource, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta}; +use script_traits::{ScriptToConstellationChan, TimerSchedulerMsg}; +use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta}; use script_traits::{UpdatePipelineIdReason, WindowSizeData, WindowSizeType}; use servo_atoms::Atom; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; @@ -263,7 +264,6 @@ enum MixedMessage { FromScript(MainThreadScriptMsg), FromDevtools(DevtoolScriptControlMsg), FromImageCache((PipelineId, PendingImageResponse)), - FromScheduler(TimerEvent), } /// Messages used to control the script event loop. @@ -569,6 +569,8 @@ pub struct ScriptThread { port_message_sender: Box<dyn ScriptChan>, + timer_task_sender: Box<dyn ScriptChan>, + remote_event_task_sender: Box<dyn ScriptChan>, /// A channel to hand out to threads that need to respond to a message from the script thread. @@ -612,8 +614,6 @@ pub struct ScriptThread { closed_pipelines: DomRefCell<HashSet<PipelineId>>, scheduler_chan: IpcSender<TimerSchedulerMsg>, - timer_event_chan: Sender<TimerEvent>, - timer_event_port: Receiver<TimerEvent>, content_process_shutdown_chan: Sender<()>, @@ -1257,8 +1257,6 @@ impl ScriptThread { let devtools_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_devtools_receiver); - let (timer_event_chan, timer_event_port) = unbounded(); - // Ask the router to proxy IPC messages from the control port to us. let control_port = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(state.control_port); @@ -1302,6 +1300,7 @@ impl ScriptThread { port_message_sender: boxed_script_sender.clone(), file_reading_task_sender: boxed_script_sender.clone(), performance_timeline_task_sender: boxed_script_sender.clone(), + timer_task_sender: boxed_script_sender.clone(), remote_event_task_sender: boxed_script_sender.clone(), history_traversal_task_sender: chan.clone(), @@ -1323,8 +1322,6 @@ impl ScriptThread { closed_pipelines: DomRefCell::new(HashSet::new()), scheduler_chan: state.scheduler_chan, - timer_event_chan: timer_event_chan, - timer_event_port: timer_event_port, content_process_shutdown_chan: state.content_process_shutdown_chan, @@ -1386,8 +1383,8 @@ impl ScriptThread { /// Handle incoming control messages. fn handle_msgs(&self) -> bool { + use self::MixedMessage::FromScript; use self::MixedMessage::{FromConstellation, FromDevtools, FromImageCache}; - use self::MixedMessage::{FromScheduler, FromScript}; // Handle pending resize events. // Gather them first to avoid a double mut borrow on self. @@ -1422,7 +1419,6 @@ impl ScriptThread { FromScript(event) }, recv(self.control_port) -> msg => FromConstellation(msg.unwrap()), - recv(self.timer_event_port) -> msg => FromScheduler(msg.unwrap()), recv(self.devtools_chan.as_ref().map(|_| &self.devtools_port).unwrap_or(&crossbeam_channel::never())) -> msg => FromDevtools(msg.unwrap()), recv(self.image_cache_port) -> msg => FromImageCache(msg.unwrap()), @@ -1520,15 +1516,12 @@ impl ScriptThread { // on and execute the sequential non-resize events we've seen. match self.control_port.try_recv() { Err(_) => match self.task_queue.try_recv() { - 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 = FromImageCache(ev), - }, - Ok(ev) => event = FromDevtools(ev), + Err(_) => match self.devtools_port.try_recv() { + Err(_) => match self.image_cache_port.try_recv() { + Err(_) => break, + Ok(ev) => event = FromImageCache(ev), }, - Ok(ev) => event = FromScheduler(ev), + Ok(ev) => event = FromDevtools(ev), }, Ok(ev) => event = FromScript(ev), }, @@ -1552,7 +1545,6 @@ impl ScriptThread { }, FromConstellation(inner_msg) => self.handle_msg_from_constellation(inner_msg), FromScript(inner_msg) => self.handle_msg_from_script(inner_msg), - FromScheduler(inner_msg) => self.handle_timer_event(inner_msg), FromDevtools(inner_msg) => self.handle_msg_from_devtools(inner_msg), FromImageCache(inner_msg) => self.handle_msg_from_image_cache(inner_msg), } @@ -1625,7 +1617,6 @@ impl ScriptThread { }, _ => ScriptThreadEventCategory::ScriptEvent, }, - MixedMessage::FromScheduler(_) => ScriptThreadEventCategory::TimerEvent, } } @@ -1728,13 +1719,6 @@ impl ScriptThread { MainThreadScriptMsg::WakeUp => None, }, MixedMessage::FromImageCache((pipeline_id, _)) => Some(pipeline_id), - MixedMessage::FromScheduler(ref timer_event) => { - let TimerEvent(source, _) = *timer_event; - match source { - TimerSource::FromWindow(pipeline_id) => Some(pipeline_id), - _ => None, - } - }, } } @@ -1976,36 +1960,6 @@ impl ScriptThread { } } - 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 thread"), - }; - - let window = self.documents.borrow().find_window(pipeline_id); - let window = match window { - Some(w) => { - if w.Closed() { - return warn!( - "Received fire timer msg for a discarded browsing context whose pipeline is pending exit {}.", - pipeline_id - ); - } - w - }, - None => { - return warn!( - "Received fire timer msg for a closed pipeline {}.", - pipeline_id - ); - }, - }; - - window.handle_fire_timer(id); - } - fn handle_msg_from_devtools(&self, msg: DevtoolScriptControlMsg) { let documents = self.documents.borrow(); match msg { @@ -2812,6 +2766,10 @@ impl ScriptThread { RemoteEventTaskSource(self.remote_event_task_sender.clone(), pipeline_id) } + pub fn timer_task_source(&self, pipeline_id: PipelineId) -> TimerTaskSource { + TimerTaskSource(self.timer_task_sender.clone(), pipeline_id) + } + pub fn websocket_task_source(&self, pipeline_id: PipelineId) -> WebsocketTaskSource { WebsocketTaskSource(self.remote_event_task_sender.clone(), pipeline_id) } @@ -3176,12 +3134,6 @@ impl ScriptThread { let MainThreadScriptChan(ref sender) = self.chan; - let (ipc_timer_event_chan, ipc_timer_event_port) = ipc::channel().unwrap(); - ROUTER.route_ipc_receiver_to_crossbeam_sender( - ipc_timer_event_port, - self.timer_event_chan.clone(), - ); - let origin = if final_url.as_str() == "about:blank" || final_url.as_str() == "about:srcdoc" { incomplete.origin.clone() @@ -3205,6 +3157,7 @@ impl ScriptThread { self.port_message_queue(incomplete.pipeline_id), self.user_interaction_task_source(incomplete.pipeline_id), self.remote_event_task_source(incomplete.pipeline_id), + self.timer_task_source(incomplete.pipeline_id), self.websocket_task_source(incomplete.pipeline_id), ); // Create the window and document objects. @@ -3222,7 +3175,6 @@ impl ScriptThread { script_to_constellation_chan, self.control_chan.clone(), self.scheduler_chan.clone(), - ipc_timer_event_chan, incomplete.layout_chan, incomplete.pipeline_id, incomplete.parent_info, diff --git a/components/script/task_manager.rs b/components/script/task_manager.rs index a8908ea00a2..063b5700484 100644 --- a/components/script/task_manager.rs +++ b/components/script/task_manager.rs @@ -12,6 +12,7 @@ use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::performance_timeline::PerformanceTimelineTaskSource; use crate::task_source::port_message::PortMessageQueue; use crate::task_source::remote_event::RemoteEventTaskSource; +use crate::task_source::timer::TimerTaskSource; use crate::task_source::user_interaction::UserInteractionTaskSource; use crate::task_source::websocket::WebsocketTaskSource; use crate::task_source::TaskSourceName; @@ -54,6 +55,8 @@ pub struct TaskManager { #[ignore_malloc_size_of = "task sources are hard"] remote_event_task_source: RemoteEventTaskSource, #[ignore_malloc_size_of = "task sources are hard"] + timer_task_source: TimerTaskSource, + #[ignore_malloc_size_of = "task sources are hard"] websocket_task_source: WebsocketTaskSource, } @@ -68,6 +71,7 @@ impl TaskManager { port_message_queue: PortMessageQueue, user_interaction_task_source: UserInteractionTaskSource, remote_event_task_source: RemoteEventTaskSource, + timer_task_source: TimerTaskSource, websocket_task_source: WebsocketTaskSource, ) -> Self { TaskManager { @@ -80,6 +84,7 @@ impl TaskManager { port_message_queue, user_interaction_task_source, remote_event_task_source, + timer_task_source, websocket_task_source, task_cancellers: Default::default(), } @@ -159,6 +164,14 @@ impl TaskManager { task_source_functions!( self, + timer_task_source_with_canceller, + timer_task_source, + TimerTaskSource, + Timer + ); + + task_source_functions!( + self, websocket_task_source_with_canceller, websocket_task_source, WebsocketTaskSource, diff --git a/components/script/task_source/mod.rs b/components/script/task_source/mod.rs index d315b34fcae..7defd922779 100644 --- a/components/script/task_source/mod.rs +++ b/components/script/task_source/mod.rs @@ -10,6 +10,7 @@ pub mod networking; pub mod performance_timeline; pub mod port_message; pub mod remote_event; +pub mod timer; pub mod user_interaction; pub mod websocket; @@ -34,6 +35,7 @@ pub enum TaskSourceName { RemoteEvent, MediaElement, Websocket, + Timer, } impl TaskSourceName { diff --git a/components/script/task_source/timer.rs b/components/script/task_source/timer.rs new file mode 100644 index 00000000000..cd134fb12a2 --- /dev/null +++ b/components/script/task_source/timer.rs @@ -0,0 +1,42 @@ +/* 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 crate::script_runtime::{CommonScriptMsg, ScriptChan, ScriptThreadEventCategory}; +use crate::task::{TaskCanceller, TaskOnce}; +use crate::task_source::{TaskSource, TaskSourceName}; +use msg::constellation_msg::PipelineId; +use std::fmt; + +#[derive(JSTraceable)] +/// https://html.spec.whatwg.org/multipage/#timer-task-source +pub struct TimerTaskSource(pub Box<dyn ScriptChan + Send + 'static>, pub PipelineId); + +impl Clone for TimerTaskSource { + fn clone(&self) -> TimerTaskSource { + TimerTaskSource(self.0.clone(), self.1.clone()) + } +} + +impl fmt::Debug for TimerTaskSource { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "TimerTaskSource(...)") + } +} + +impl TaskSource for TimerTaskSource { + const NAME: TaskSourceName = TaskSourceName::Timer; + + fn queue_with_canceller<T>(&self, task: T, canceller: &TaskCanceller) -> Result<(), ()> + where + T: TaskOnce + 'static, + { + let msg = CommonScriptMsg::Task( + ScriptThreadEventCategory::TimerEvent, + Box::new(canceller.wrap_task(task)), + Some(self.1), + Self::NAME, + ); + self.0.send(msg).map_err(|_| ()) + } +} diff --git a/components/script/timers.rs b/components/script/timers.rs index f65d1013f48..4be6f822263 100644 --- a/components/script/timers.rs +++ b/components/script/timers.rs @@ -34,8 +34,12 @@ pub struct OneshotTimerHandle(i32); pub struct OneshotTimers { js_timers: JsTimers, #[ignore_malloc_size_of = "Defined in std"] - timer_event_chan: IpcSender<TimerEvent>, + /// The sender, to be cloned for each timer, + /// on which the timer scheduler in the constellation can send an event + /// when the timer is due. + timer_event_chan: DomRefCell<Option<IpcSender<TimerEvent>>>, #[ignore_malloc_size_of = "Defined in std"] + /// The sender to the timer scheduler in the constellation. scheduler_chan: IpcSender<TimerSchedulerMsg>, next_timer_handle: Cell<OneshotTimerHandle>, timers: DomRefCell<Vec<OneshotTimer>>, @@ -109,13 +113,10 @@ impl PartialEq for OneshotTimer { } impl OneshotTimers { - pub fn new( - timer_event_chan: IpcSender<TimerEvent>, - scheduler_chan: IpcSender<TimerSchedulerMsg>, - ) -> OneshotTimers { + pub fn new(scheduler_chan: IpcSender<TimerSchedulerMsg>) -> OneshotTimers { OneshotTimers { js_timers: JsTimers::new(), - timer_event_chan: timer_event_chan, + timer_event_chan: DomRefCell::new(None), scheduler_chan: scheduler_chan, next_timer_handle: Cell::new(OneshotTimerHandle(1)), timers: DomRefCell::new(Vec::new()), @@ -125,6 +126,12 @@ impl OneshotTimers { } } + pub fn setup_scheduling(&self, timer_event_chan: IpcSender<TimerEvent>) { + let mut chan = self.timer_event_chan.borrow_mut(); + assert!(chan.is_none()); + *chan = Some(timer_event_chan); + } + pub fn schedule_callback( &self, callback: OneshotTimerCallback, @@ -279,7 +286,10 @@ impl OneshotTimers { .saturating_sub(precise_time_ms().get()), ); let request = TimerEventRequest( - self.timer_event_chan.clone(), + self.timer_event_chan + .borrow() + .clone() + .expect("Timer event chan not setup to schedule timers."), timer.source, expected_event_id, delay, @@ -331,6 +341,7 @@ pub struct JsTimerHandle(i32); #[derive(DenyPublicFields, JSTraceable, MallocSizeOf)] pub struct JsTimers { next_timer_handle: Cell<JsTimerHandle>, + /// https://html.spec.whatwg.org/multipage/#list-of-active-timers active_timers: DomRefCell<HashMap<JsTimerHandle, JsTimerEntry>>, /// The nesting level of the currently executing timer task or 0. nesting_level: Cell<u32>, |