diff --git a/Cargo.lock b/Cargo.lock index 10ab7b05da4..c352127bea1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5744,7 +5744,6 @@ dependencies = [ "servo_url", "smallvec", "style_traits", - "time 0.1.45", "uuid", "webdriver", "webgpu", diff --git a/components/constellation/timer_scheduler.rs b/components/constellation/timer_scheduler.rs index 5e6071c8301..6481696fc73 100644 --- a/components/constellation/timer_scheduler.rs +++ b/components/constellation/timer_scheduler.rs @@ -64,10 +64,9 @@ impl TimerScheduler { /// Handle an incoming timer request. pub fn handle_timer_request(&mut self, request: TimerSchedulerMsg) { let TimerEventRequest(_, _, _, delay) = request.0; - let schedule = Instant::now() + Duration::from_millis(delay.get()); let event = ScheduledEvent { request: request.0, - for_time: schedule, + for_time: Instant::now() + delay, }; self.0.push(event); } diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 2f42a3cfe60..0b616866245 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -47,8 +47,7 @@ use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataR use script_layout_interface::{PendingRestyle, ReflowGoal, TrustedNodeAddress}; use script_traits::{ AnimationState, AnimationTickType, CompositorEvent, DocumentActivity, MouseButton, - MouseEventType, MsDuration, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress, - WheelDelta, + MouseEventType, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta, }; use servo_arc::Arc; use servo_atoms::Atom; @@ -1993,7 +1992,7 @@ impl Document { }; self.global().schedule_callback( OneshotTimerCallback::FakeRequestAnimationFrame(callback), - MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY), + Duration::from_millis(FAKE_REQUEST_ANIMATION_FRAME_DELAY), ); } else if !self.running_animation_callbacks.get() { // No need to send a `ChangeRunningAnimationsState` if we're running animation callbacks: @@ -2443,7 +2442,7 @@ impl Document { window: window_from_node(&*document), url: url.clone(), }), - MsDuration::new(time.saturating_mul(1000)), + Duration::from_secs(*time), ); } // Note: this will, among others, result in the "iframe-load-event-steps" being run. diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index af0f1c08d89..ee9a5019f0d 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -6,9 +6,9 @@ use std::cell::Cell; use std::mem; use std::str::{Chars, FromStr}; use std::sync::{Arc, Mutex}; +use std::time::Duration; use dom_struct::dom_struct; -use euclid::Length; use headers::ContentType; use http::header::{self, HeaderName, HeaderValue}; use ipc_channel::ipc; @@ -48,7 +48,7 @@ use crate::script_runtime::CanGc; use crate::task_source::{TaskSource, TaskSourceName}; use crate::timers::OneshotTimerCallback; -const DEFAULT_RECONNECTION_TIME: u64 = 5000; +const DEFAULT_RECONNECTION_TIME: Duration = Duration::from_millis(5000); #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] struct GenerationId(u32); @@ -69,7 +69,7 @@ pub struct EventSource { #[no_trace] request: DomRefCell>, last_event_id: DomRefCell, - reconnection_time: Cell, + reconnection_time: Cell, generation_id: Cell, ready_state: Cell, @@ -162,7 +162,7 @@ impl EventSourceContext { event_source.upcast::().fire_event(atom!("error")); // Step 2. - let duration = Length::new(event_source.reconnection_time.get()); + let duration = event_source.reconnection_time.get(); // Step 3. // TODO: Optionally wait some more. @@ -192,7 +192,10 @@ impl EventSourceContext { "id" => mem::swap(&mut self.last_event_id, &mut self.value), "retry" => { if let Ok(time) = u64::from_str(&self.value) { - self.event_source.root().reconnection_time.set(time); + self.event_source + .root() + .reconnection_time + .set(Duration::from_millis(time)); } }, _ => (), diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 981870c33fe..43ad1764c49 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -11,7 +11,7 @@ use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::thread::JoinHandle; -use std::time::{Instant, SystemTime, UNIX_EPOCH}; +use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; use std::{mem, ptr}; use base::id::{ @@ -53,7 +53,7 @@ use script_traits::serializable::{BlobData, BlobImpl, FileBlob}; use script_traits::transferable::MessagePortImpl; use script_traits::{ BroadcastMsg, GamepadEvent, GamepadSupportedHapticEffects, GamepadUpdateType, MessagePortMsg, - MsDuration, PortMessageTask, ScriptMsg, ScriptToConstellationChan, TimerEvent, TimerEventId, + PortMessageTask, ScriptMsg, ScriptToConstellationChan, TimerEvent, TimerEventId, TimerSchedulerMsg, TimerSource, }; use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl}; @@ -2741,7 +2741,7 @@ impl GlobalScope { pub fn schedule_callback( &self, callback: OneshotTimerCallback, - duration: MsDuration, + duration: Duration, ) -> OneshotTimerHandle { self.setup_timers(); self.timers @@ -2757,7 +2757,7 @@ impl GlobalScope { &self, callback: TimerCallback, arguments: Vec, - timeout: i32, + timeout: Duration, is_interval: IsInterval, ) -> i32 { self.setup_timers(); diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs index 45673c55652..eb46e4856c4 100644 --- a/components/script/dom/htmlmetaelement.rs +++ b/components/script/dom/htmlmetaelement.rs @@ -4,12 +4,13 @@ use std::str::FromStr; use std::sync::LazyLock; +use std::time::Duration; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; use js::rust::HandleObject; use regex::bytes::Regex; -use script_traits::{HistoryEntryReplacement, MsDuration}; +use script_traits::HistoryEntryReplacement; use servo_url::ServoUrl; use style::str::HTML_SPACE_CHARACTERS; @@ -207,7 +208,7 @@ impl HTMLMetaElement { window: window.clone(), url: url_record, }), - MsDuration::new(time.saturating_mul(1000)), + Duration::from_secs(time), ); document.set_declarative_refresh(DeclarativeRefresh::CreatedAfterLoad); } else { diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs index a1d260f6a98..41813330e38 100644 --- a/components/script/dom/testbinding.rs +++ b/components/script/dom/testbinding.rs @@ -7,6 +7,7 @@ use std::borrow::ToOwned; use std::ptr::{self, NonNull}; use std::rc::Rc; +use std::time::Duration; use dom_struct::dom_struct; use js::jsapi::{Heap, JSObject, JS_NewPlainObject}; @@ -14,7 +15,6 @@ use js::jsval::{JSVal, NullValue}; use js::rust::{CustomAutoRooterGuard, HandleObject, HandleValue}; use js::typedarray::{self, Uint8ClampedArray}; use script_traits::serializable::BlobImpl; -use script_traits::MsDuration; use servo_config::prefs; use crate::dom::bindings::buffer_source::create_buffer_source; @@ -999,7 +999,7 @@ impl TestBindingMethods for TestBinding { }; let _ = self.global().schedule_callback( OneshotTimerCallback::TestBindingCallback(cb), - MsDuration::new(delay), + Duration::from_millis(delay), ); } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 6f20bb64ace..ddc3079d4a1 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -12,6 +12,7 @@ use std::ptr::NonNull; use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::{Arc, Mutex}; +use std::time::Duration; use std::{cmp, env, mem}; use app_units::Au; @@ -882,7 +883,7 @@ impl WindowMethods for Window { self.upcast::().set_timeout_or_interval( callback, args, - timeout, + Duration::from_millis(timeout.max(0) as u64), IsInterval::NonInterval, ) } @@ -908,7 +909,7 @@ impl WindowMethods for Window { self.upcast::().set_timeout_or_interval( callback, args, - timeout, + Duration::from_millis(timeout.max(0) as u64), IsInterval::Interval, ) } diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs index af622d7b90d..fb1baed40f7 100644 --- a/components/script/dom/workerglobalscope.rs +++ b/components/script/dom/workerglobalscope.rs @@ -6,6 +6,7 @@ use std::default::Default; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::time::Duration; use base::id::{PipelineId, PipelineNamespace}; use crossbeam_channel::Receiver; @@ -345,7 +346,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { self.upcast::().set_timeout_or_interval( callback, args, - timeout, + Duration::from_millis(timeout.max(0) as u64), IsInterval::NonInterval, ) } @@ -371,7 +372,7 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope { self.upcast::().set_timeout_or_interval( callback, args, - timeout, + Duration::from_millis(timeout.max(0) as u64), IsInterval::Interval, ) } diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index daac1204c46..05f14a9b380 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -7,11 +7,11 @@ use std::cell::Cell; use std::default::Default; use std::str::{self, FromStr}; use std::sync::{Arc, Mutex}; +use std::time::{Duration, Instant}; use std::{cmp, slice}; use dom_struct::dom_struct; use encoding_rs::{Encoding, UTF_8}; -use euclid::Length; use headers::{ContentLength, ContentType, HeaderMapExt}; use html5ever::serialize; use html5ever::serialize::SerializeOpts; @@ -125,7 +125,7 @@ impl XHRProgress { pub struct XMLHttpRequest { eventtarget: XMLHttpRequestEventTarget, ready_state: Cell, - timeout: Cell, + timeout: Cell, with_credentials: Cell, upload: Dom, response_url: DomRefCell, @@ -162,7 +162,7 @@ pub struct XMLHttpRequest { send_flag: Cell, timeout_cancel: DomRefCell>, - fetch_time: Cell, + fetch_time: Cell, generation_id: Cell, response_status: Cell>, #[no_trace] @@ -185,7 +185,7 @@ impl XMLHttpRequest { XMLHttpRequest { eventtarget: XMLHttpRequestEventTarget::new_inherited(), ready_state: Cell::new(XMLHttpRequestState::Unsent), - timeout: Cell::new(0u32), + timeout: Cell::new(Duration::ZERO), with_credentials: Cell::new(false), upload: Dom::from_ref(&*XMLHttpRequestUpload::new(global)), response_url: DomRefCell::new(String::new()), @@ -210,7 +210,7 @@ impl XMLHttpRequest { send_flag: Cell::new(false), timeout_cancel: DomRefCell::new(None), - fetch_time: Cell::new(0), + fetch_time: Cell::new(Instant::now()), generation_id: Cell::new(GenerationId(0)), response_status: Cell::new(Ok(())), referrer: global.get_referrer(), @@ -420,7 +420,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { // Step 10 if !asynch { // FIXME: This should only happen if the global environment is a document environment - if self.timeout.get() != 0 || + if !self.timeout.get().is_zero() || self.response_type.get() != XMLHttpRequestResponseType::_empty { return Err(Error::InvalidAccess); @@ -514,7 +514,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { /// fn Timeout(&self) -> u32 { - self.timeout.get() + self.timeout.get().as_millis() as u32 } /// @@ -523,20 +523,22 @@ impl XMLHttpRequestMethods for XMLHttpRequest { if self.sync_in_window() { return Err(Error::InvalidAccess); } + // Step 2 + let timeout = Duration::from_millis(timeout as u64); self.timeout.set(timeout); if self.send_flag.get() { - if timeout == 0 { + if timeout.is_zero() { self.cancel_timeout(); return Ok(()); } - let progress = time::now().to_timespec().sec - self.fetch_time.get(); - if timeout > (progress * 1000) as u32 { - self.set_timeout(timeout - (progress * 1000) as u32); + let progress = Instant::now() - self.fetch_time.get(); + if timeout > progress { + self.set_timeout(timeout - progress); } else { // Immediately execute the timeout steps - self.set_timeout(0); + self.set_timeout(Duration::ZERO); } } Ok(()) @@ -788,7 +790,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { } } - self.fetch_time.set(time::now().to_timespec().sec); + self.fetch_time.set(Instant::now()); let rv = self.fetch(request, &self.global()); // Step 10 @@ -797,7 +799,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest { } let timeout = self.timeout.get(); - if timeout > 0 { + if timeout > Duration::ZERO { self.set_timeout(timeout); } Ok(()) @@ -1298,14 +1300,13 @@ impl XMLHttpRequest { self.dispatch_progress_event(false, type_, len, total); } - fn set_timeout(&self, duration_ms: u32) { + fn set_timeout(&self, duration: Duration) { // Sets up the object to timeout in a given number of milliseconds // This will cancel all previous timeouts let callback = OneshotTimerCallback::XhrTimeout(XHRTimeoutCallback { xhr: Trusted::new(self), generation_id: self.generation_id.get(), }); - let duration = Length::new(duration_ms as u64); *self.timeout_cancel.borrow_mut() = Some(self.global().schedule_callback(callback, duration)); } diff --git a/components/script/timers.rs b/components/script/timers.rs index 98547726c96..9022ea4bc2d 100644 --- a/components/script/timers.rs +++ b/components/script/timers.rs @@ -3,21 +3,18 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::cell::Cell; -use std::cmp::{self, Ord, Ordering}; +use std::cmp::{Ord, Ordering}; use std::collections::HashMap; use std::default::Default; use std::rc::Rc; +use std::time::{Duration, Instant}; use deny_public_fields::DenyPublicFields; -use euclid::Length; use ipc_channel::ipc::IpcSender; use js::jsapi::Heap; use js::jsval::{JSVal, UndefinedValue}; use js::rust::HandleValue; -use script_traits::{ - precise_time_ms, MsDuration, TimerEvent, TimerEventId, TimerEventRequest, TimerSchedulerMsg, - TimerSource, -}; +use script_traits::{TimerEvent, TimerEventId, TimerEventRequest, TimerSchedulerMsg, TimerSource}; use servo_config::pref; use crate::dom::bindings::callback::ExceptionHandling::Report; @@ -52,14 +49,12 @@ pub struct OneshotTimers { scheduler_chan: IpcSender, next_timer_handle: Cell, timers: DomRefCell>, - #[no_trace] - suspended_since: Cell>, + suspended_since: Cell>, /// 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. - #[no_trace] - suspension_offset: Cell, + suspension_offset: Cell, /// Calls to `fire_timer` with a different argument than this get ignored. /// They were previously scheduled and got invalidated when /// - timers were suspended, @@ -76,8 +71,7 @@ struct OneshotTimer { #[no_trace] source: TimerSource, callback: OneshotTimerCallback, - #[no_trace] - scheduled_for: MsDuration, + scheduled_for: Instant, } // This enum is required to work around the fact that trait objects do not support generic methods. @@ -137,7 +131,7 @@ impl OneshotTimers { next_timer_handle: Cell::new(OneshotTimerHandle(1)), timers: DomRefCell::new(Vec::new()), suspended_since: Cell::new(None), - suspension_offset: Cell::new(Length::new(0)), + suspension_offset: Cell::new(Duration::ZERO), expected_event_id: Cell::new(TimerEventId(0)), } } @@ -151,20 +145,18 @@ impl OneshotTimers { pub fn schedule_callback( &self, callback: OneshotTimerCallback, - duration: MsDuration, + duration: Duration, source: TimerSource, ) -> OneshotTimerHandle { let new_handle = self.next_timer_handle.get(); self.next_timer_handle .set(OneshotTimerHandle(new_handle.0 + 1)); - let scheduled_for = self.base_time() + duration; - let timer = OneshotTimer { handle: new_handle, source, callback, - scheduled_for, + scheduled_for: self.base_time() + duration, }; { @@ -247,18 +239,18 @@ impl OneshotTimers { self.schedule_timer_call(); } - fn base_time(&self) -> MsDuration { + fn base_time(&self) -> Instant { let offset = self.suspension_offset.get(); - match self.suspended_since.get() { - Some(time) => time - offset, - None => precise_time_ms() - offset, + Some(suspend_time) => suspend_time - offset, + None => Instant::now() - offset, } } pub fn slow_down(&self) { - let duration = pref!(js.timers.minimum_duration) as u64; - self.js_timers.set_min_duration(MsDuration::new(duration)); + let min_duration_ms = pref!(js.timers.minimum_duration) as u64; + self.js_timers + .set_min_duration(Duration::from_millis(min_duration_ms)); } pub fn speed_up(&self) { @@ -272,14 +264,14 @@ impl OneshotTimers { } debug!("Suspending timers."); - self.suspended_since.set(Some(precise_time_ms())); + self.suspended_since.set(Some(Instant::now())); self.invalidate_expected_event_id(); } pub fn resume(&self) { // Resume is idempotent: do nothing if the timers are already resumed. let additional_offset = match self.suspended_since.get() { - Some(suspended_since) => precise_time_ms() - suspended_since, + Some(suspended_since) => Instant::now() - suspended_since, None => return warn!("Resuming an already resumed timer."), }; @@ -302,12 +294,7 @@ impl OneshotTimers { if let Some(timer) = timers.last() { let expected_event_id = self.invalidate_expected_event_id(); - let delay = Length::new( - timer - .scheduled_for - .get() - .saturating_sub(precise_time_ms().get()), - ); + let delay = timer.scheduled_for - Instant::now(); let request = TimerEventRequest( self.timer_event_chan .borrow() @@ -339,7 +326,7 @@ impl OneshotTimers { global: &GlobalScope, callback: TimerCallback, arguments: Vec, - timeout: i32, + timeout: Duration, is_interval: IsInterval, source: TimerSource, ) -> i32 { @@ -369,8 +356,7 @@ pub struct JsTimers { /// The nesting level of the currently executing timer task or 0. nesting_level: Cell, /// Used to introduce a minimum delay in event intervals - #[no_trace] - min_duration: Cell>, + min_duration: Cell>, } #[derive(JSTraceable, MallocSizeOf)] @@ -391,8 +377,7 @@ pub struct JsTimerTask { callback: InternalTimerCallback, is_interval: IsInterval, nesting_level: u32, - #[no_trace] - duration: MsDuration, + duration: Duration, is_user_interacting: bool, } @@ -436,7 +421,7 @@ impl JsTimers { global: &GlobalScope, callback: TimerCallback, arguments: Vec, - timeout: i32, + timeout: Duration, is_interval: IsInterval, source: TimerSource, ) -> i32 { @@ -480,11 +465,11 @@ impl JsTimers { is_interval, is_user_interacting: ScriptThread::is_user_interacting(), nesting_level: 0, - duration: Length::new(0), + duration: Duration::ZERO, }; // step 5 - task.duration = Length::new(cmp::max(0, timeout) as u64); + task.duration = timeout.max(Duration::ZERO); // step 3, 6-9, 11-14 self.initialize_and_schedule(global, task); @@ -501,7 +486,7 @@ impl JsTimers { } } - pub fn set_min_duration(&self, duration: MsDuration) { + pub fn set_min_duration(&self, duration: Duration) { self.min_duration.set(Some(duration)); } @@ -510,9 +495,9 @@ impl JsTimers { } // see step 13 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps - fn user_agent_pad(&self, current_duration: MsDuration) -> MsDuration { + fn user_agent_pad(&self, current_duration: Duration) -> Duration { match self.min_duration.get() { - Some(min_duration) => cmp::max(min_duration, current_duration), + Some(min_duration) => min_duration.max(current_duration), None => current_duration, } } @@ -543,10 +528,10 @@ impl JsTimers { } // see step 7 of https://html.spec.whatwg.org/multipage/#timer-initialisation-steps -fn clamp_duration(nesting_level: u32, unclamped: MsDuration) -> MsDuration { - let lower_bound = if nesting_level > 5 { 4 } else { 0 }; - - cmp::max(Length::new(lower_bound), unclamped) +fn clamp_duration(nesting_level: u32, unclamped: Duration) -> Duration { + let lower_bound_ms = if nesting_level > 5 { 4 } else { 0 }; + let lower_bound = Duration::from_millis(lower_bound_ms); + lower_bound.max(unclamped) } impl JsTimerTask { diff --git a/components/shared/script/Cargo.toml b/components/shared/script/Cargo.toml index c8303658efa..3d1972fe973 100644 --- a/components/shared/script/Cargo.toml +++ b/components/shared/script/Cargo.toml @@ -39,7 +39,6 @@ servo_atoms = { workspace = true } servo_url = { path = "../../url" } smallvec = { workspace = true } style_traits = { workspace = true } -time = { workspace = true } uuid = { workspace = true } webdriver = { workspace = true } webgpu = { path = "../../webgpu" } diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 895923930cd..d008f572048 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -18,6 +18,7 @@ use std::borrow::Cow; use std::collections::{HashMap, VecDeque}; use std::fmt; use std::sync::Arc; +use std::time::Duration; use background_hang_monitor_api::BackgroundHangMonitorRegister; use base::id::{ @@ -32,7 +33,7 @@ use crossbeam_channel::{RecvTimeoutError, Sender}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::CompositorEventVariant; use euclid::default::Point2D; -use euclid::{Length, Rect, Scale, Size2D, UnknownUnit, Vector2D}; +use euclid::{Rect, Scale, Size2D, UnknownUnit, Vector2D}; use http::{HeaderMap, Method}; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use ipc_channel::Error as IpcError; @@ -577,7 +578,7 @@ pub struct TimerEventRequest( pub IpcSender, pub TimerSource, pub TimerEventId, - pub MsDuration, + pub Duration, ); /// The message used to send a request to the timer scheduler. @@ -603,23 +604,6 @@ pub enum TimerSource { #[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)] pub struct TimerEventId(pub u32); -/// Unit of measurement. -#[derive(Clone, Copy, MallocSizeOf)] -pub enum Milliseconds {} -/// Unit of measurement. -#[derive(Clone, Copy, MallocSizeOf)] -pub enum Nanoseconds {} - -/// Amount of milliseconds. -pub type MsDuration = Length; -/// Amount of nanoseconds. -pub type NsDuration = Length; - -/// Returns the duration since an unspecified epoch measured in ms. -pub fn precise_time_ms() -> MsDuration { - Length::new(time::precise_time_ns() / (1000 * 1000)) -} - /// Data needed to construct a script thread. /// /// NB: *DO NOT* add any Senders or Receivers here! pcwalton will have to rewrite your code if you