1use std::cell::Cell;
6use std::cmp::{Ord, Ordering};
7use std::collections::VecDeque;
8use std::default::Default;
9use std::rc::Rc;
10use std::time::{Duration, Instant};
11
12use base::id::PipelineId;
13use deny_public_fields::DenyPublicFields;
14use js::jsapi::Heap;
15use js::jsval::{JSVal, UndefinedValue};
16use js::rust::HandleValue;
17use rustc_hash::FxHashMap;
18use serde::{Deserialize, Serialize};
19use servo_config::pref;
20use timers::{BoxedTimerCallback, TimerEventRequest};
21
22use crate::dom::bindings::callback::ExceptionHandling::Report;
23use crate::dom::bindings::cell::DomRefCell;
24use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
25use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
26use crate::dom::bindings::error::Fallible;
27use crate::dom::bindings::inheritance::Castable;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, DomObject};
30use crate::dom::bindings::root::{AsHandleValue, Dom};
31use crate::dom::bindings::str::DOMString;
32use crate::dom::csp::CspReporting;
33use crate::dom::document::RefreshRedirectDue;
34use crate::dom::eventsource::EventSourceTimeoutCallback;
35use crate::dom::globalscope::GlobalScope;
36#[cfg(feature = "testbinding")]
37use crate::dom::testbinding::TestBindingCallback;
38use crate::dom::trustedscript::TrustedScript;
39use crate::dom::types::{Window, WorkerGlobalScope};
40use crate::dom::xmlhttprequest::XHRTimeoutCallback;
41use crate::script_module::ScriptFetchOptions;
42use crate::script_runtime::{CanGc, IntroductionType};
43use crate::script_thread::ScriptThread;
44use crate::task_source::SendableTaskSource;
45
46#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
47pub(crate) struct OneshotTimerHandle(i32);
48
49#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
50#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
51pub(crate) struct OneshotTimers {
52 global_scope: Dom<GlobalScope>,
53 js_timers: JsTimers,
54 next_timer_handle: Cell<OneshotTimerHandle>,
55 timers: DomRefCell<VecDeque<OneshotTimer>>,
56 suspended_since: Cell<Option<Instant>>,
57 suspension_offset: Cell<Duration>,
62 #[no_trace]
69 expected_event_id: Cell<TimerEventId>,
70}
71
72#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
73struct OneshotTimer {
74 handle: OneshotTimerHandle,
75 #[no_trace]
76 source: TimerSource,
77 callback: OneshotTimerCallback,
78 scheduled_for: Instant,
79}
80
81#[derive(JSTraceable, MallocSizeOf)]
85pub(crate) enum OneshotTimerCallback {
86 XhrTimeout(XHRTimeoutCallback),
87 EventSourceTimeout(EventSourceTimeoutCallback),
88 JsTimer(JsTimerTask),
89 #[cfg(feature = "testbinding")]
90 TestBindingCallback(TestBindingCallback),
91 RefreshRedirectDue(RefreshRedirectDue),
92}
93
94impl OneshotTimerCallback {
95 fn invoke<T: DomObject>(self, this: &T, js_timers: &JsTimers, can_gc: CanGc) {
96 match self {
97 OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(can_gc),
98 OneshotTimerCallback::EventSourceTimeout(callback) => callback.invoke(),
99 OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers, can_gc),
100 #[cfg(feature = "testbinding")]
101 OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
102 OneshotTimerCallback::RefreshRedirectDue(callback) => callback.invoke(can_gc),
103 }
104 }
105}
106
107impl Ord for OneshotTimer {
108 fn cmp(&self, other: &OneshotTimer) -> Ordering {
109 match self.scheduled_for.cmp(&other.scheduled_for).reverse() {
110 Ordering::Equal => self.handle.cmp(&other.handle).reverse(),
111 res => res,
112 }
113 }
114}
115
116impl PartialOrd for OneshotTimer {
117 fn partial_cmp(&self, other: &OneshotTimer) -> Option<Ordering> {
118 Some(self.cmp(other))
119 }
120}
121
122impl Eq for OneshotTimer {}
123impl PartialEq for OneshotTimer {
124 fn eq(&self, other: &OneshotTimer) -> bool {
125 std::ptr::eq(self, other)
126 }
127}
128
129impl OneshotTimers {
130 pub(crate) fn new(global_scope: &GlobalScope) -> OneshotTimers {
131 OneshotTimers {
132 global_scope: Dom::from_ref(global_scope),
133 js_timers: JsTimers::default(),
134 next_timer_handle: Cell::new(OneshotTimerHandle(1)),
135 timers: DomRefCell::new(VecDeque::new()),
136 suspended_since: Cell::new(None),
137 suspension_offset: Cell::new(Duration::ZERO),
138 expected_event_id: Cell::new(TimerEventId(0)),
139 }
140 }
141
142 pub(crate) fn schedule_callback(
143 &self,
144 callback: OneshotTimerCallback,
145 duration: Duration,
146 source: TimerSource,
147 ) -> OneshotTimerHandle {
148 let new_handle = self.next_timer_handle.get();
149 self.next_timer_handle
150 .set(OneshotTimerHandle(new_handle.0 + 1));
151
152 let timer = OneshotTimer {
153 handle: new_handle,
154 source,
155 callback,
156 scheduled_for: self.base_time() + duration,
157 };
158
159 {
160 let mut timers = self.timers.borrow_mut();
161 let insertion_index = timers.binary_search(&timer).err().unwrap();
162 timers.insert(insertion_index, timer);
163 }
164
165 if self.is_next_timer(new_handle) {
166 self.schedule_timer_call();
167 }
168
169 new_handle
170 }
171
172 pub(crate) fn unschedule_callback(&self, handle: OneshotTimerHandle) {
173 let was_next = self.is_next_timer(handle);
174
175 self.timers.borrow_mut().retain(|t| t.handle != handle);
176
177 if was_next {
178 self.invalidate_expected_event_id();
179 self.schedule_timer_call();
180 }
181 }
182
183 fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
184 match self.timers.borrow().back() {
185 None => false,
186 Some(max_timer) => max_timer.handle == handle,
187 }
188 }
189
190 pub(crate) fn fire_timer(&self, id: TimerEventId, global: &GlobalScope, can_gc: CanGc) {
192 let expected_id = self.expected_event_id.get();
194 if expected_id != id {
195 debug!(
196 "ignoring timer fire event {:?} (expected {:?})",
197 id, expected_id
198 );
199 return;
200 }
201
202 assert!(self.suspended_since.get().is_none());
203
204 let base_time = self.base_time();
205
206 if base_time < self.timers.borrow().back().unwrap().scheduled_for {
208 warn!("Unexpected timing!");
209 return;
210 }
211
212 let mut timers_to_run = Vec::new();
215
216 loop {
217 let mut timers = self.timers.borrow_mut();
218
219 if timers.is_empty() || timers.back().unwrap().scheduled_for > base_time {
220 break;
221 }
222
223 timers_to_run.push(timers.pop_back().unwrap());
224 }
225
226 for timer in timers_to_run {
227 if !global.can_continue_running() {
232 return;
233 }
234 let callback = timer.callback;
235 callback.invoke(global, &self.js_timers, can_gc);
236 }
237
238 self.schedule_timer_call();
239 }
240
241 fn base_time(&self) -> Instant {
242 let offset = self.suspension_offset.get();
243 match self.suspended_since.get() {
244 Some(suspend_time) => suspend_time - offset,
245 None => Instant::now() - offset,
246 }
247 }
248
249 pub(crate) fn slow_down(&self) {
250 let min_duration_ms = pref!(js_timers_minimum_duration) as u64;
251 self.js_timers
252 .set_min_duration(Duration::from_millis(min_duration_ms));
253 }
254
255 pub(crate) fn speed_up(&self) {
256 self.js_timers.remove_min_duration();
257 }
258
259 pub(crate) fn suspend(&self) {
260 if self.suspended_since.get().is_some() {
262 return warn!("Suspending an already suspended timer.");
263 }
264
265 debug!("Suspending timers.");
266 self.suspended_since.set(Some(Instant::now()));
267 self.invalidate_expected_event_id();
268 }
269
270 pub(crate) fn resume(&self) {
271 let additional_offset = match self.suspended_since.get() {
273 Some(suspended_since) => Instant::now() - suspended_since,
274 None => return warn!("Resuming an already resumed timer."),
275 };
276
277 debug!("Resuming timers.");
278 self.suspension_offset
279 .set(self.suspension_offset.get() + additional_offset);
280 self.suspended_since.set(None);
281
282 self.schedule_timer_call();
283 }
284
285 fn schedule_timer_call(&self) {
287 if self.suspended_since.get().is_some() {
288 return;
290 }
291
292 let timers = self.timers.borrow();
293 let Some(timer) = timers.back() else {
294 return;
295 };
296
297 let expected_event_id = self.invalidate_expected_event_id();
298 let callback = TimerListener {
301 context: Trusted::new(&*self.global_scope),
302 task_source: self
303 .global_scope
304 .task_manager()
305 .timer_task_source()
306 .to_sendable(),
307 source: timer.source,
308 id: expected_event_id,
309 }
310 .into_callback();
311
312 let event_request = TimerEventRequest {
313 callback,
314 duration: timer.scheduled_for - Instant::now(),
315 };
316
317 self.global_scope.schedule_timer(event_request);
318 }
319
320 fn invalidate_expected_event_id(&self) -> TimerEventId {
321 let TimerEventId(currently_expected) = self.expected_event_id.get();
322 let next_id = TimerEventId(currently_expected + 1);
323 debug!(
324 "invalidating expected timer (was {:?}, now {:?}",
325 currently_expected, next_id
326 );
327 self.expected_event_id.set(next_id);
328 next_id
329 }
330
331 #[allow(clippy::too_many_arguments)]
332 pub(crate) fn set_timeout_or_interval(
333 &self,
334 global: &GlobalScope,
335 callback: TimerCallback,
336 arguments: Vec<HandleValue>,
337 timeout: Duration,
338 is_interval: IsInterval,
339 source: TimerSource,
340 can_gc: CanGc,
341 ) -> Fallible<i32> {
342 self.js_timers.set_timeout_or_interval(
343 global,
344 callback,
345 arguments,
346 timeout,
347 is_interval,
348 source,
349 can_gc,
350 )
351 }
352
353 pub(crate) fn clear_timeout_or_interval(&self, global: &GlobalScope, handle: i32) {
354 self.js_timers.clear_timeout_or_interval(global, handle)
355 }
356}
357
358#[derive(Clone, Copy, Eq, Hash, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
359pub(crate) struct JsTimerHandle(i32);
360
361#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
362pub(crate) struct JsTimers {
363 next_timer_handle: Cell<JsTimerHandle>,
364 active_timers: DomRefCell<FxHashMap<JsTimerHandle, JsTimerEntry>>,
366 nesting_level: Cell<u32>,
368 min_duration: Cell<Option<Duration>>,
370}
371
372#[derive(JSTraceable, MallocSizeOf)]
373struct JsTimerEntry {
374 oneshot_handle: OneshotTimerHandle,
375}
376
377#[derive(JSTraceable, MallocSizeOf)]
382pub(crate) struct JsTimerTask {
383 handle: JsTimerHandle,
384 #[no_trace]
385 source: TimerSource,
386 callback: InternalTimerCallback,
387 is_interval: IsInterval,
388 nesting_level: u32,
389 duration: Duration,
390 is_user_interacting: bool,
391}
392
393#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
395pub(crate) enum IsInterval {
396 Interval,
397 NonInterval,
398}
399
400pub(crate) enum TimerCallback {
401 StringTimerCallback(TrustedScriptOrString),
402 FunctionTimerCallback(Rc<Function>),
403}
404
405#[derive(Clone, JSTraceable, MallocSizeOf)]
406#[cfg_attr(crown, allow(crown::unrooted_must_root))]
407enum InternalTimerCallback {
408 StringTimerCallback(DOMString),
409 FunctionTimerCallback(
410 #[conditional_malloc_size_of] Rc<Function>,
411 #[ignore_malloc_size_of = "Rc"] Rc<Box<[Heap<JSVal>]>>,
412 ),
413}
414
415impl Default for JsTimers {
416 fn default() -> Self {
417 JsTimers {
418 next_timer_handle: Cell::new(JsTimerHandle(1)),
419 active_timers: DomRefCell::new(FxHashMap::default()),
420 nesting_level: Cell::new(0),
421 min_duration: Cell::new(None),
422 }
423 }
424}
425
426impl JsTimers {
427 #[allow(clippy::too_many_arguments)]
429 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
430 pub(crate) fn set_timeout_or_interval(
431 &self,
432 global: &GlobalScope,
433 callback: TimerCallback,
434 arguments: Vec<HandleValue>,
435 timeout: Duration,
436 is_interval: IsInterval,
437 source: TimerSource,
438 can_gc: CanGc,
439 ) -> Fallible<i32> {
440 let callback = match callback {
441 TimerCallback::StringTimerCallback(trusted_script_or_string) => {
442 let global_name = if global.is::<Window>() {
444 "Window"
445 } else {
446 "WorkerGlobalScope"
447 };
448 let method_name = if is_interval == IsInterval::Interval {
450 "setInterval"
451 } else {
452 "setTimeout"
453 };
454 let sink = format!("{} {}", global_name, method_name);
456 let code_str = TrustedScript::get_trusted_script_compliant_string(
459 global,
460 trusted_script_or_string,
461 &sink,
462 can_gc,
463 )?;
464 if global
467 .get_csp_list()
468 .is_js_evaluation_allowed(global, code_str.as_ref())
469 {
470 InternalTimerCallback::StringTimerCallback(code_str)
472 } else {
473 return Ok(0);
474 }
475 },
476 TimerCallback::FunctionTimerCallback(function) => {
477 let mut args = Vec::with_capacity(arguments.len());
480 for _ in 0..arguments.len() {
481 args.push(Heap::default());
482 }
483 for (i, item) in arguments.iter().enumerate() {
484 args.get_mut(i).unwrap().set(item.get());
485 }
486 InternalTimerCallback::FunctionTimerCallback(
489 function,
490 Rc::new(args.into_boxed_slice()),
491 )
492 },
493 };
494
495 let JsTimerHandle(new_handle) = self.next_timer_handle.get();
499 self.next_timer_handle.set(JsTimerHandle(new_handle + 1));
500
501 let mut task = JsTimerTask {
505 handle: JsTimerHandle(new_handle),
506 source,
507 callback,
508 is_interval,
509 is_user_interacting: ScriptThread::is_user_interacting(),
510 nesting_level: 0,
511 duration: Duration::ZERO,
512 };
513
514 task.duration = timeout.max(Duration::ZERO);
516
517 self.initialize_and_schedule(global, task);
518
519 Ok(new_handle)
521 }
522
523 pub(crate) fn clear_timeout_or_interval(&self, global: &GlobalScope, handle: i32) {
524 let mut active_timers = self.active_timers.borrow_mut();
525
526 if let Some(entry) = active_timers.remove(&JsTimerHandle(handle)) {
527 global.unschedule_callback(entry.oneshot_handle);
528 }
529 }
530
531 pub(crate) fn set_min_duration(&self, duration: Duration) {
532 self.min_duration.set(Some(duration));
533 }
534
535 pub(crate) fn remove_min_duration(&self) {
536 self.min_duration.set(None);
537 }
538
539 fn user_agent_pad(&self, current_duration: Duration) -> Duration {
541 match self.min_duration.get() {
542 Some(min_duration) => min_duration.max(current_duration),
543 None => current_duration,
544 }
545 }
546
547 fn initialize_and_schedule(&self, global: &GlobalScope, mut task: JsTimerTask) {
549 let handle = task.handle;
550 let mut active_timers = self.active_timers.borrow_mut();
551
552 let nesting_level = self.nesting_level.get();
556
557 let duration = self.user_agent_pad(clamp_duration(nesting_level, task.duration));
558 task.nesting_level = nesting_level + 1;
561
562 let callback = OneshotTimerCallback::JsTimer(task);
565 let oneshot_handle = global.schedule_callback(callback, duration);
566
567 let entry = active_timers
569 .entry(handle)
570 .or_insert(JsTimerEntry { oneshot_handle });
571 entry.oneshot_handle = oneshot_handle;
572 }
573}
574
575fn clamp_duration(nesting_level: u32, unclamped: Duration) -> Duration {
577 let lower_bound_ms = if nesting_level > 5 { 4 } else { 0 };
579 let lower_bound = Duration::from_millis(lower_bound_ms);
580 lower_bound.max(unclamped)
581}
582
583impl JsTimerTask {
584 pub(crate) fn invoke<T: DomObject>(self, this: &T, timers: &JsTimers, can_gc: CanGc) {
586 timers.nesting_level.set(self.nesting_level);
591
592 let _guard = ScriptThread::user_interacting_guard();
593 match self.callback {
594 InternalTimerCallback::StringTimerCallback(ref code_str) => {
595 let global = this.global();
598 let cx = GlobalScope::get_cx();
600 rooted!(in(*cx) let mut rval = UndefinedValue());
602 _ = global.evaluate_js_on_global_with_result(
614 code_str,
615 rval.handle_mut(),
616 ScriptFetchOptions::default_classic_script(&global),
617 global.api_base_url(),
620 can_gc,
621 Some(IntroductionType::DOM_TIMER),
622 );
623 },
624 InternalTimerCallback::FunctionTimerCallback(ref function, ref arguments) => {
627 let arguments = self.collect_heap_args(arguments);
628 rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
629 let _ = function.Call_(this, arguments, value.handle_mut(), Report, can_gc);
630 },
631 };
632
633 timers.nesting_level.set(0);
635
636 if self.is_interval == IsInterval::Interval &&
642 timers.active_timers.borrow().contains_key(&self.handle)
643 {
644 timers.initialize_and_schedule(&this.global(), self);
645 }
646 }
647
648 fn collect_heap_args<'b>(&self, args: &'b [Heap<JSVal>]) -> Vec<HandleValue<'b>> {
649 args.iter().map(|arg| arg.as_handle_value()).collect()
650 }
651}
652
653#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
655pub enum TimerSource {
656 FromWindow(PipelineId),
658 FromWorker,
660}
661
662#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
664pub struct TimerEventId(pub u32);
665
666#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
670pub struct TimerEvent(pub TimerSource, pub TimerEventId);
671
672#[derive(Clone)]
674struct TimerListener {
675 task_source: SendableTaskSource,
676 context: Trusted<GlobalScope>,
677 source: TimerSource,
678 id: TimerEventId,
679}
680
681impl TimerListener {
682 fn handle(&self, event: TimerEvent) {
686 let context = self.context.clone();
687 self.task_source.queue(task!(timer_event: move || {
689 let global = context.root();
690 let TimerEvent(source, id) = event;
691 match source {
692 TimerSource::FromWorker => {
693 global.downcast::<WorkerGlobalScope>().expect("Window timer delivered to worker");
694 },
695 TimerSource::FromWindow(pipeline) => {
696 assert_eq!(pipeline, global.pipeline_id());
697 global.downcast::<Window>().expect("Worker timer delivered to window");
698 },
699 };
700 global.fire_timer(id, CanGc::note());
701 })
702 );
703 }
704
705 fn into_callback(self) -> BoxedTimerCallback {
706 let timer_event = TimerEvent(self.source, self.id);
707 Box::new(move || self.handle(timer_event))
708 }
709}