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