1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::Cell;
8use std::cmp::{Ord, Ordering};
9use std::collections::VecDeque;
10use std::default::Default;
11use std::rc::Rc;
12use std::time::{Duration, Instant};
13
14use deny_public_fields::DenyPublicFields;
15use js::context::JSContext;
16use js::jsapi::Heap;
17use js::jsval::{JSVal, UndefinedValue};
18use js::rust::HandleValue;
19use js::rust::wrappers2::JS_GetScriptedCallerPrivate;
20use net_traits::request::ParserMetadata;
21use rustc_hash::FxHashMap;
22use script_bindings::callback::{RootedCallback, TracedCallback};
23use script_bindings::cell::DomRefCell;
24use script_bindings::domstring::TracedDOMString;
25use script_bindings::trace::RootedTraceableBox;
26use serde::{Deserialize, Serialize};
27use servo_base::id::PipelineId;
28use servo_config::pref;
29use servo_url::ServoUrl;
30use timers::{BoxedTimerCallback, TimerEventRequest};
31
32use crate::dom::bindings::callback::ExceptionHandling::Report;
33use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
34use crate::dom::bindings::codegen::UnionTypes::TrustedScriptOrString;
35use crate::dom::bindings::error::Fallible;
36use crate::dom::bindings::inheritance::Castable;
37use crate::dom::bindings::refcounted::Trusted;
38use crate::dom::bindings::root::{AsHandleValue, Dom, DomRoot};
39use crate::dom::bindings::str::DOMString;
40use crate::dom::csp::CspReporting;
41use crate::dom::document::RefreshRedirectDue;
42use crate::dom::eventsource::EventSourceTimeoutCallback;
43use crate::dom::geolocation::geolocation::GeolocationTimeoutCallback;
44use crate::dom::globalscope::GlobalScope;
45#[cfg(feature = "testbinding")]
46use crate::dom::testbinding::TestBindingCallback;
47use crate::dom::trustedtypes::trustedscript::TrustedScript;
48use crate::dom::types::Window;
49use crate::dom::xmlhttprequest::XHRTimeoutCallback;
50use crate::event_loop::script_thread::ScriptThread;
51use crate::runtime::script_runtime::IntroductionType;
52use crate::scripting::fetching_options::ScriptFetchOptions;
53use crate::scripting::script_execution::{RethrowErrors, ScriptOptions};
54use crate::scripting::script_module::module_script_from_reference_private;
55use crate::tasks::task_source::SendableTaskSource;
56
57type TimerKey = i32;
58type RunStepsDeadline = Instant;
59type CompletionStep = Box<dyn FnOnce(&mut JSContext, &GlobalScope) + 'static>;
60
61type OrderingIdentifier = DOMString;
64
65#[derive(JSTraceable, MallocSizeOf)]
66struct OrderingEntry {
67 milliseconds: u64,
68 start_seq: u64,
69 handle: OneshotTimerHandle,
70}
71
72type OrderingQueues = FxHashMap<OrderingIdentifier, Vec<OrderingEntry>>;
74
75type RunStepsActiveMap = FxHashMap<TimerKey, RunStepsDeadline>;
77
78#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
79pub(crate) struct OneshotTimerHandle(i32);
80
81#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
82#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
83pub(crate) struct OneshotTimers {
84 global_scope: Dom<GlobalScope>,
85 js_timers: JsTimers,
86 next_timer_handle: Cell<OneshotTimerHandle>,
87 timers: DomRefCell<VecDeque<OneshotTimer>>,
88 suspended_since: Cell<Option<Instant>>,
89 suspension_offset: Cell<Duration>,
94 #[no_trace]
101 expected_event_id: Cell<TimerEventId>,
102 map_of_active_timers: DomRefCell<RunStepsActiveMap>,
106
107 runsteps_queues: DomRefCell<OrderingQueues>,
111
112 next_runsteps_key: Cell<TimerKey>,
114
115 runsteps_start_seq: Cell<u64>,
118}
119
120#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
121struct OneshotTimerData {
122 handle: OneshotTimerHandle,
123 #[no_trace]
124 source: TimerSource,
125 scheduled_for: Instant,
126}
127
128#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
129#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
130struct OneshotTimer {
131 data: OneshotTimerData,
132 callback: OneshotTimerOrJsCallback,
133}
134
135impl js::gc::Rootable for OneshotTimer {}
136
137impl OneshotTimer {
138 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
139 fn root(self, cx: &JSContext) -> RootedOneshotTimer {
140 RootedOneshotTimer {
141 data: self.data,
142 callback: match self.callback {
143 OneshotTimerOrJsCallback::NonJs(callback) => {
144 RootedOneshotTimerOrJsCallback::NonJs(callback)
145 },
146 OneshotTimerOrJsCallback::Js(task) => {
147 let callback = match task.callback {
148 InternalTimerCallback::StringTimerCallback(string, fetch_info) => {
149 RootedInternalTimerCallback::StringTimerCallback(
150 string.root(),
151 fetch_info,
152 )
153 },
154 InternalTimerCallback::FunctionTimerCallback(callback, args) => {
155 RootedInternalTimerCallback::FunctionTimerCallback(
156 callback.root(cx),
157 RootedTraceableBox::new(args),
158 )
159 },
160 };
161 RootedOneshotTimerOrJsCallback::Js(task.data, callback)
162 },
163 },
164 }
165 }
166}
167
168struct RootedOneshotTimer {
169 data: OneshotTimerData,
170 callback: RootedOneshotTimerOrJsCallback,
171}
172
173#[derive(JSTraceable, MallocSizeOf)]
174#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
175pub(crate) enum OneshotTimerOrJsCallback {
176 NonJs(OneshotTimerCallback),
177 Js(JsTimerTask),
178}
179
180pub(crate) enum RootedOneshotTimerOrJsCallback {
181 NonJs(OneshotTimerCallback),
182 Js(JsTimerTaskData, RootedInternalTimerCallback),
183}
184
185impl RootedOneshotTimerOrJsCallback {
186 fn into_traced(self) -> OneshotTimerOrJsCallback {
187 match self {
188 Self::NonJs(callback) => OneshotTimerOrJsCallback::NonJs(callback),
189 Self::Js(data, callback) => OneshotTimerOrJsCallback::Js(JsTimerTask {
190 data,
191 callback: callback.into_traced(),
192 }),
193 }
194 }
195
196 fn invoke(self, cx: &mut JSContext, global: &GlobalScope, js_timers: &JsTimers) {
197 match self {
198 Self::NonJs(callback) => callback.invoke(cx, global),
199 Self::Js(data, callback) => invoke_js_timer(cx, data, callback, global, js_timers),
200 }
201 }
202}
203
204#[derive(JSTraceable, MallocSizeOf)]
208pub(crate) enum OneshotTimerCallback {
209 XhrTimeout(XHRTimeoutCallback),
210 EventSourceTimeout(EventSourceTimeoutCallback),
211 GeolocationTimeout(GeolocationTimeoutCallback),
213 #[cfg(feature = "testbinding")]
214 TestBindingCallback(TestBindingCallback),
215 RefreshRedirectDue(RefreshRedirectDue),
216 RunStepsAfterTimeout {
218 timer_key: i32,
220 ordering_id: DOMString,
222 milliseconds: u64,
224 #[no_trace]
226 #[ignore_malloc_size_of = "Closure"]
227 completion: CompletionStep,
228 },
229}
230
231impl OneshotTimerCallback {
232 fn invoke(self, cx: &mut JSContext, global: &GlobalScope) {
233 match self {
234 OneshotTimerCallback::XhrTimeout(callback) => callback.invoke(cx),
235 OneshotTimerCallback::EventSourceTimeout(callback) => callback.invoke(),
236 OneshotTimerCallback::GeolocationTimeout(callback) => callback.invoke(),
237 #[cfg(feature = "testbinding")]
238 OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(cx),
239 OneshotTimerCallback::RefreshRedirectDue(callback) => callback.invoke(cx, global),
240 OneshotTimerCallback::RunStepsAfterTimeout { completion, .. } => {
241 completion(cx, global);
244 },
245 }
246 }
247}
248
249impl Ord for OneshotTimer {
250 fn cmp(&self, other: &OneshotTimer) -> Ordering {
251 match self
252 .data
253 .scheduled_for
254 .cmp(&other.data.scheduled_for)
255 .reverse()
256 {
257 Ordering::Equal => self.data.handle.cmp(&other.data.handle).reverse(),
258 res => res,
259 }
260 }
261}
262
263impl PartialOrd for OneshotTimer {
264 fn partial_cmp(&self, other: &OneshotTimer) -> Option<Ordering> {
265 Some(self.cmp(other))
266 }
267}
268
269impl Eq for OneshotTimer {}
270impl PartialEq for OneshotTimer {
271 fn eq(&self, other: &OneshotTimer) -> bool {
272 std::ptr::eq(self, other)
273 }
274}
275
276impl OneshotTimers {
277 pub(crate) fn new(global_scope: &GlobalScope) -> OneshotTimers {
278 OneshotTimers {
279 global_scope: Dom::from_ref(global_scope),
280 js_timers: JsTimers::default(),
281 next_timer_handle: Cell::new(OneshotTimerHandle(1)),
282 timers: DomRefCell::new(VecDeque::new()),
283 suspended_since: Cell::new(None),
284 suspension_offset: Cell::new(Duration::ZERO),
285 expected_event_id: Cell::new(TimerEventId(0)),
286 map_of_active_timers: Default::default(),
287 runsteps_queues: Default::default(),
288 next_runsteps_key: Cell::new(1),
289 runsteps_start_seq: Cell::new(0),
290 }
291 }
292
293 #[inline]
295 pub(crate) fn now_for_runsteps(&self) -> Instant {
296 self.base_time()
298 }
299
300 pub(crate) fn fresh_runsteps_key(&self) -> TimerKey {
303 let k = self.next_runsteps_key.get();
304 self.next_runsteps_key.set(k + 1);
305 k
306 }
307
308 pub(crate) fn runsteps_set_active(&self, timer_key: TimerKey, deadline: RunStepsDeadline) {
311 self.map_of_active_timers
312 .borrow_mut()
313 .insert(timer_key, deadline);
314 }
315
316 fn runsteps_enqueue_sorted(
319 &self,
320 ordering_id: &DOMString,
321 handle: OneshotTimerHandle,
322 milliseconds: u64,
323 ) {
324 let mut map = self.runsteps_queues.borrow_mut();
325 let q = map.entry(ordering_id.clone()).or_default();
326
327 let seq = {
328 let cur = self.runsteps_start_seq.get();
329 self.runsteps_start_seq.set(cur + 1);
330 cur
331 };
332
333 let key = OrderingEntry {
334 milliseconds,
335 start_seq: seq,
336 handle,
337 };
338
339 let idx = q
340 .binary_search_by(|ordering_entry| {
341 match ordering_entry.milliseconds.cmp(&milliseconds) {
342 Ordering::Less => Ordering::Less,
343 Ordering::Greater => Ordering::Greater,
344 Ordering::Equal => ordering_entry.start_seq.cmp(&seq),
345 }
346 })
347 .unwrap_or_else(|i| i);
348
349 q.insert(idx, key);
350 }
351
352 pub(crate) fn schedule_js_callback(
353 &self,
354 callback: RootedInternalTimerCallback,
355 data: JsTimerTaskData,
356 duration: Duration,
357 source: TimerSource,
358 ) -> OneshotTimerHandle {
359 let new_handle = self.next_timer_handle.get();
360 self.next_timer_handle
361 .set(OneshotTimerHandle(new_handle.0 + 1));
362
363 self.schedule_generic_callback(
364 new_handle,
365 OneshotTimerOrJsCallback::Js(JsTimerTask {
366 callback: callback.into_traced(),
367 data,
368 }),
369 duration,
370 source,
371 );
372
373 new_handle
374 }
375
376 pub(crate) fn schedule_callback(
377 &self,
378 callback: OneshotTimerCallback,
379 duration: Duration,
380 source: TimerSource,
381 ) -> OneshotTimerHandle {
382 let new_handle = self.next_timer_handle.get();
383 self.next_timer_handle
384 .set(OneshotTimerHandle(new_handle.0 + 1));
385
386 if let OneshotTimerCallback::RunStepsAfterTimeout {
389 ordering_id,
390 milliseconds,
391 ..
392 } = &callback
393 {
394 self.runsteps_enqueue_sorted(ordering_id, new_handle, *milliseconds);
395 }
396
397 self.schedule_generic_callback(
398 new_handle,
399 OneshotTimerOrJsCallback::NonJs(callback),
400 duration,
401 source,
402 );
403
404 new_handle
405 }
406
407 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
408 fn schedule_generic_callback(
409 &self,
410 new_handle: OneshotTimerHandle,
411 callback: OneshotTimerOrJsCallback,
412 duration: Duration,
413 source: TimerSource,
414 ) {
415 let timer = OneshotTimer {
416 data: OneshotTimerData {
417 handle: new_handle,
418 source,
419 scheduled_for: self.base_time() + duration,
420 },
421 callback,
422 };
423 {
424 let mut timers = self.timers.borrow_mut();
425 let insertion_index = timers.binary_search(&timer).err().unwrap();
426 timers.insert(insertion_index, timer);
427 }
428
429 if self.is_next_timer(new_handle) {
430 self.schedule_timer_call();
431 }
432 }
433
434 pub(crate) fn unschedule_callback(&self, handle: OneshotTimerHandle) {
435 let was_next = self.is_next_timer(handle);
436
437 self.timers.borrow_mut().retain(|t| t.data.handle != handle);
438
439 if was_next {
440 self.invalidate_expected_event_id();
441 self.schedule_timer_call();
442 }
443 }
444
445 fn is_next_timer(&self, handle: OneshotTimerHandle) -> bool {
446 match self.timers.borrow().back() {
447 None => false,
448 Some(max_timer) => max_timer.data.handle == handle,
449 }
450 }
451
452 pub(crate) fn fire_timer(&self, cx: &mut JSContext, id: TimerEventId) {
454 let expected_id = self.expected_event_id.get();
456 if expected_id != id {
457 debug!(
458 "ignoring timer fire event {:?} (expected {:?})",
459 id, expected_id
460 );
461 return;
462 }
463
464 assert!(self.suspended_since.get().is_none());
465
466 let base_time = self.base_time();
467
468 if base_time < self.timers.borrow().back().unwrap().data.scheduled_for {
470 warn!("Unexpected timing!");
471 return;
472 }
473
474 let timers_to_run = {
477 let mut timers = self.timers.borrow_mut();
478 let mut timers_to_run = Vec::with_capacity(timers.len());
479 loop {
480 if timers.is_empty() || timers.back().unwrap().data.scheduled_for > base_time {
481 break;
482 }
483
484 timers_to_run.push(timers.pop_back().unwrap().root(cx));
485 }
486 timers_to_run
487 };
488
489 for timer in timers_to_run {
490 if !self.global_scope.can_continue_running() {
495 return;
496 }
497 match &timer.callback {
498 RootedOneshotTimerOrJsCallback::NonJs(
500 OneshotTimerCallback::RunStepsAfterTimeout { ordering_id, .. },
501 ) => {
502 let head_handle_opt = {
505 let queues_ref = self.runsteps_queues.borrow();
506 queues_ref
507 .get(ordering_id)
508 .and_then(|v| v.first().map(|t| t.handle))
509 };
510 let is_head = head_handle_opt.is_none_or(|head| head == timer.data.handle);
511
512 if !is_head {
513 rooted!(&in(cx) let mut rein = Some(OneshotTimer {
515 data: OneshotTimerData {
516 handle: timer.data.handle,
517 source: timer.data.source,
518 scheduled_for: self.base_time(),
519 },
520 callback: timer.callback.into_traced(),
521 }));
522 let mut timers = self.timers.borrow_mut();
523 let idx = timers
524 .binary_search(rein.as_ref(cx.no_gc()).as_ref().unwrap())
525 .err()
526 .unwrap();
527 timers.insert(idx, rein.take().unwrap());
528 continue;
529 }
530
531 let (timer_key, ordering_id_owned, completion) = match timer.callback {
532 RootedOneshotTimerOrJsCallback::NonJs(
533 OneshotTimerCallback::RunStepsAfterTimeout {
534 timer_key,
535 ordering_id,
536 milliseconds: _,
537 completion,
538 },
539 ) => (timer_key, ordering_id, completion),
540 _ => unreachable!(),
541 };
542
543 (completion)(cx, &self.global_scope);
548
549 self.map_of_active_timers.borrow_mut().remove(&timer_key);
551
552 {
553 let mut queues_mut = self.runsteps_queues.borrow_mut();
554 if let Some(q) = queues_mut.get_mut(&ordering_id_owned) {
555 if !q.is_empty() {
556 q.remove(0);
557 }
558 if q.is_empty() {
559 queues_mut.remove(&ordering_id_owned);
560 }
561 }
562 }
563 },
564 _ => {
565 let cb = timer.callback;
566 cb.invoke(cx, &self.global_scope, &self.js_timers);
567 },
568 }
569 }
570
571 self.schedule_timer_call();
572 }
573
574 fn base_time(&self) -> Instant {
575 let offset = self.suspension_offset.get();
576 match self.suspended_since.get() {
577 Some(suspend_time) => suspend_time - offset,
578 None => Instant::now() - offset,
579 }
580 }
581
582 pub(crate) fn slow_down(&self) {
583 let min_duration_ms = pref!(js_timers_minimum_duration) as u64;
584 self.js_timers
585 .set_min_duration(Duration::from_millis(min_duration_ms));
586 }
587
588 pub(crate) fn speed_up(&self) {
589 self.js_timers.remove_min_duration();
590 }
591
592 pub(crate) fn suspend(&self) {
593 if self.suspended_since.get().is_some() {
595 return warn!("Suspending an already suspended timer.");
596 }
597
598 debug!("Suspending timers.");
599 self.suspended_since.set(Some(Instant::now()));
600 self.invalidate_expected_event_id();
601 }
602
603 pub(crate) fn resume(&self) {
604 let additional_offset = match self.suspended_since.get() {
606 Some(suspended_since) => Instant::now() - suspended_since,
607 None => return warn!("Resuming an already resumed timer."),
608 };
609
610 debug!("Resuming timers.");
611 self.suspension_offset
612 .set(self.suspension_offset.get() + additional_offset);
613 self.suspended_since.set(None);
614
615 self.schedule_timer_call();
616 }
617
618 fn schedule_timer_call(&self) {
620 if self.suspended_since.get().is_some() {
621 return;
623 }
624
625 let timers = self.timers.borrow();
626 let Some(timer) = timers.back() else {
627 return;
628 };
629
630 let expected_event_id = self.invalidate_expected_event_id();
631 let context = match timer.data.source {
632 TimerSource::FromWorker => {
633 TimerListenerContext::Worker(Trusted::new(&*self.global_scope))
634 },
635 TimerSource::FromWindow(pipelineid) => TimerListenerContext::Window(pipelineid),
636 };
637
638 let callback = TimerListener {
641 context,
642 task_source: self
643 .global_scope
644 .task_manager()
645 .timer_task_source()
646 .to_sendable(),
647 source: timer.data.source,
648 id: expected_event_id,
649 }
650 .into_callback();
651
652 let event_request = TimerEventRequest {
653 callback,
654 duration: timer.data.scheduled_for - self.base_time(),
655 };
656
657 self.global_scope.schedule_timer(event_request);
658 }
659
660 fn invalidate_expected_event_id(&self) -> TimerEventId {
661 let TimerEventId(currently_expected) = self.expected_event_id.get();
662 let next_id = TimerEventId(currently_expected + 1);
663 debug!(
664 "invalidating expected timer (was {:?}, now {:?}",
665 currently_expected, next_id
666 );
667 self.expected_event_id.set(next_id);
668 next_id
669 }
670
671 #[allow(clippy::too_many_arguments)]
672 pub(crate) fn set_timeout_or_interval(
673 &self,
674 cx: &mut JSContext,
675 global: &GlobalScope,
676 callback: TimerCallback,
677 arguments: Vec<HandleValue>,
678 timeout: Duration,
679 is_interval: IsInterval,
680 source: TimerSource,
681 ) -> Fallible<i32> {
682 self.js_timers.set_timeout_or_interval(
683 cx,
684 global,
685 callback,
686 arguments,
687 timeout,
688 is_interval,
689 source,
690 )
691 }
692
693 pub(crate) fn clear_timeout_or_interval(&self, global: &GlobalScope, handle: i32) {
694 self.js_timers.clear_timeout_or_interval(global, handle)
695 }
696
697 pub(crate) fn clear(&self) {
698 self.timers.borrow_mut().clear();
699 self.js_timers.clear();
700 }
701}
702
703#[derive(Clone, Copy, Eq, Hash, JSTraceable, MallocSizeOf, Ord, PartialEq, PartialOrd)]
704pub(crate) struct JsTimerHandle(i32);
705
706#[derive(DenyPublicFields, JSTraceable, MallocSizeOf)]
707pub(crate) struct JsTimers {
708 next_timer_handle: Cell<JsTimerHandle>,
709 active_timers: DomRefCell<FxHashMap<JsTimerHandle, JsTimerEntry>>,
711 nesting_level: Cell<u32>,
713 min_duration: Cell<Option<Duration>>,
715}
716
717impl JsTimers {
718 fn clear(&self) {
719 self.active_timers.borrow_mut().clear();
720 }
721}
722
723#[derive(JSTraceable, MallocSizeOf)]
724struct JsTimerEntry {
725 oneshot_handle: OneshotTimerHandle,
726}
727
728#[derive(JSTraceable, MallocSizeOf)]
729pub(crate) struct JsTimerTaskData {
730 handle: JsTimerHandle,
731 #[no_trace]
732 source: TimerSource,
733 is_interval: IsInterval,
734 nesting_level: u32,
735 duration: Duration,
736 is_user_interacting: bool,
737}
738
739#[derive(JSTraceable, MallocSizeOf)]
743#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
744pub(crate) struct JsTimerTask {
745 data: JsTimerTaskData,
746 callback: InternalTimerCallback,
747}
748
749#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
751pub(crate) enum IsInterval {
752 Interval,
753 NonInterval,
754}
755
756pub(crate) enum TimerCallback {
757 StringTimerCallback(TrustedScriptOrString),
758 FunctionTimerCallback(RootedCallback<Function>),
759}
760
761#[derive(Clone, JSTraceable, MallocSizeOf)]
762#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
763enum InternalTimerCallback {
764 StringTimerCallback(TracedDOMString, InitiatingScriptFetchInfo),
765 FunctionTimerCallback(
766 TracedCallback<Function>,
767 #[ignore_malloc_size_of = "mozjs"] Rc<Box<[Heap<JSVal>]>>,
768 ),
769}
770
771pub(crate) enum RootedInternalTimerCallback {
772 StringTimerCallback(DOMString, InitiatingScriptFetchInfo),
773 FunctionTimerCallback(
774 RootedCallback<Function>,
775 RootedTraceableBox<Rc<Box<[Heap<JSVal>]>>>,
776 ),
777}
778
779impl RootedInternalTimerCallback {
780 fn into_traced(self) -> InternalTimerCallback {
781 match self {
782 Self::StringTimerCallback(string, fetch_info) => {
783 InternalTimerCallback::StringTimerCallback(string.traced(), fetch_info)
784 },
785 Self::FunctionTimerCallback(callback, args) => {
786 InternalTimerCallback::FunctionTimerCallback(callback.to_traced(), *args.into_box())
787 },
788 }
789 }
790}
791
792impl Default for JsTimers {
793 fn default() -> Self {
794 JsTimers {
795 next_timer_handle: Cell::new(JsTimerHandle(1)),
796 active_timers: DomRefCell::new(FxHashMap::default()),
797 nesting_level: Cell::new(0),
798 min_duration: Cell::new(None),
799 }
800 }
801}
802
803impl JsTimers {
804 #[allow(clippy::too_many_arguments)]
806 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
807 pub(crate) fn set_timeout_or_interval(
808 &self,
809 cx: &mut JSContext,
810 global: &GlobalScope,
811 callback: TimerCallback,
812 arguments: Vec<HandleValue>,
813 timeout: Duration,
814 is_interval: IsInterval,
815 source: TimerSource,
816 ) -> Fallible<i32> {
817 let callback = match callback {
818 TimerCallback::StringTimerCallback(trusted_script_or_string) => {
819 let global_name = if global.is::<Window>() {
821 "Window"
822 } else {
823 "WorkerGlobalScope"
824 };
825 let method_name = if is_interval == IsInterval::Interval {
827 "setInterval"
828 } else {
829 "setTimeout"
830 };
831 let sink = format!("{} {}", global_name, method_name);
833 let code_str = TrustedScript::get_trusted_type_compliant_string(
836 cx,
837 global,
838 trusted_script_or_string,
839 &sink,
840 )?;
841
842 let initiating_script_fetch_info = active_script_fetch_info(cx, global);
843
844 if global
847 .get_csp_list()
848 .is_js_evaluation_allowed(cx, global, &code_str.str())
849 {
850 RootedInternalTimerCallback::StringTimerCallback(
852 code_str,
853 initiating_script_fetch_info,
854 )
855 } else {
856 return Ok(0);
857 }
858 },
859 TimerCallback::FunctionTimerCallback(function) => {
860 let mut args = Vec::with_capacity(arguments.len());
863 for _ in 0..arguments.len() {
864 args.push(Heap::default());
865 }
866 for (i, item) in arguments.iter().enumerate() {
867 args.get_mut(i).unwrap().set(item.get());
868 }
869 RootedInternalTimerCallback::FunctionTimerCallback(
872 function,
873 RootedTraceableBox::new(Rc::new(args.into_boxed_slice())),
874 )
875 },
876 };
877
878 let JsTimerHandle(new_handle) = self.next_timer_handle.get();
882 self.next_timer_handle.set(JsTimerHandle(new_handle + 1));
883
884 let mut task = JsTimerTaskData {
888 handle: JsTimerHandle(new_handle),
889 source,
890 is_interval,
891 is_user_interacting: ScriptThread::is_user_interacting(),
892 nesting_level: 0,
893 duration: Duration::ZERO,
894 };
895
896 task.duration = timeout.max(Duration::ZERO);
898
899 self.initialize_and_schedule(global, callback, task);
900
901 Ok(new_handle)
903 }
904
905 pub(crate) fn clear_timeout_or_interval(&self, global: &GlobalScope, handle: i32) {
906 let mut active_timers = self.active_timers.borrow_mut();
907
908 if let Some(entry) = active_timers.remove(&JsTimerHandle(handle)) {
909 global.unschedule_callback(entry.oneshot_handle);
910 }
911 }
912
913 pub(crate) fn set_min_duration(&self, duration: Duration) {
914 self.min_duration.set(Some(duration));
915 }
916
917 pub(crate) fn remove_min_duration(&self) {
918 self.min_duration.set(None);
919 }
920
921 fn user_agent_pad(&self, current_duration: Duration) -> Duration {
923 match self.min_duration.get() {
924 Some(min_duration) => min_duration.max(current_duration),
925 None => current_duration,
926 }
927 }
928
929 fn initialize_and_schedule(
931 &self,
932 global: &GlobalScope,
933 callback: RootedInternalTimerCallback,
934 mut task: JsTimerTaskData,
935 ) {
936 let handle = task.handle;
937 let mut active_timers = self.active_timers.borrow_mut();
938
939 let nesting_level = self.nesting_level.get();
943
944 let duration = self.user_agent_pad(clamp_duration(nesting_level, task.duration));
945 task.nesting_level = nesting_level + 1;
948
949 let oneshot_handle = global.schedule_js_callback(callback, task, duration);
952
953 let entry = active_timers
955 .entry(handle)
956 .or_insert(JsTimerEntry { oneshot_handle });
957 entry.oneshot_handle = oneshot_handle;
958 }
959}
960
961fn clamp_duration(nesting_level: u32, unclamped: Duration) -> Duration {
963 let lower_bound_ms = if nesting_level > 5 { 4 } else { 0 };
965 let lower_bound = Duration::from_millis(lower_bound_ms);
966 lower_bound.max(unclamped)
967}
968
969fn invoke_js_timer(
971 cx: &mut JSContext,
972 data: JsTimerTaskData,
973 callback: RootedInternalTimerCallback,
974 global: &GlobalScope,
975 timers: &JsTimers,
976) {
977 timers.nesting_level.set(data.nesting_level);
982
983 let _guard = ScriptThread::user_interacting_guard();
984 match callback {
985 RootedInternalTimerCallback::StringTimerCallback(ref code_str, ref fetch_info) => {
986 let InitiatingScriptFetchInfo {
992 fetch_options,
993 base_url,
994 } = fetch_info.clone();
995
996 let script = global.create_a_classic_script(
999 cx,
1000 (*code_str.str()).into(),
1001 base_url,
1002 ScriptOptions::empty(),
1003 fetch_options,
1004 Some(IntroductionType::DOM_TIMER),
1005 1,
1006 );
1007
1008 _ = global.run_a_classic_script(
1010 cx,
1011 script,
1012 RethrowErrors::No,
1013 None, );
1015 },
1016 RootedInternalTimerCallback::FunctionTimerCallback(ref function, ref arguments) => {
1019 let arguments = collect_heap_args(arguments);
1020 rooted!(&in(cx) let mut value: JSVal);
1021 let _ = function.Call_(cx, global, arguments, value.handle_mut(), Report);
1022 },
1023 };
1024
1025 timers.nesting_level.set(0);
1027
1028 if data.is_interval == IsInterval::Interval &&
1034 timers.active_timers.borrow().contains_key(&data.handle)
1035 {
1036 timers.initialize_and_schedule(global, callback, data);
1037 }
1038}
1039
1040fn collect_heap_args<'b>(args: &'b [Heap<JSVal>]) -> Vec<HandleValue<'b>> {
1041 args.iter().map(|arg| arg.as_handle_value()).collect()
1042}
1043
1044#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
1046pub enum TimerSource {
1047 FromWindow(PipelineId),
1049 FromWorker,
1051}
1052
1053#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1055pub struct TimerEventId(pub u32);
1056
1057#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
1061pub struct TimerEvent(pub TimerSource, pub TimerEventId);
1062
1063#[derive(Clone)]
1068enum TimerListenerContext {
1069 Worker(Trusted<GlobalScope>),
1070 Window(PipelineId),
1071}
1072
1073#[derive(Clone)]
1075struct TimerListener {
1076 task_source: SendableTaskSource,
1077 context: TimerListenerContext,
1078 source: TimerSource,
1079 id: TimerEventId,
1080}
1081
1082impl TimerListener {
1083 fn handle(&self, event: TimerEvent) {
1087 let context = self.context.clone();
1088 self.task_source.queue(task!(timer_event: move |cx| {
1090 let TimerEvent(_source, id) = event;
1091 let global = match context {
1092 TimerListenerContext::Worker(global) => {
1093 global.root()
1094 },
1095 TimerListenerContext::Window(pipeline) => match ScriptThread::find_window(pipeline) {
1096 Some(window) => DomRoot::upcast::<GlobalScope>(window),
1097 None => return,
1098 },
1099 };
1100 global.fire_timer(cx, id);
1101 }));
1102 }
1103
1104 fn into_callback(self) -> BoxedTimerCallback {
1105 let timer_event = TimerEvent(self.source, self.id);
1106 Box::new(move || self.handle(timer_event))
1107 }
1108}
1109
1110#[derive(Clone, JSTraceable, MallocSizeOf)]
1111pub(crate) struct InitiatingScriptFetchInfo {
1112 #[no_trace]
1113 fetch_options: ScriptFetchOptions,
1114 #[no_trace]
1115 base_url: ServoUrl,
1116}
1117
1118#[expect(unsafe_code)]
1119fn active_script_fetch_info(cx: &mut JSContext, global: &GlobalScope) -> InitiatingScriptFetchInfo {
1121 rooted!(&in(cx) let mut value = UndefinedValue());
1122 unsafe { JS_GetScriptedCallerPrivate(cx, value.handle_mut()) };
1123
1124 let reference_private = value.handle();
1125
1126 let initiating_script = unsafe { module_script_from_reference_private(reference_private) };
1128
1129 let (fetch_options, base_url) = match initiating_script {
1130 Some(script) => (
1132 ScriptFetchOptions {
1134 cryptographic_nonce: script.options.cryptographic_nonce.clone(),
1136 integrity_metadata: String::new(),
1138 parser_metadata: ParserMetadata::NotParserInserted,
1140 credentials_mode: script.options.credentials_mode,
1142 referrer_policy: script.options.referrer_policy,
1144 render_blocking: false,
1146 },
1147 script.base_url.clone(),
1149 ),
1150 None => (
1151 ScriptFetchOptions::default_classic_script(),
1153 global.api_base_url(),
1155 ),
1156 };
1157
1158 InitiatingScriptFetchInfo {
1159 fetch_options,
1160 base_url,
1161 }
1162}