1use std::cell::{self, Cell, RefCell, RefMut};
14use std::cmp::max;
15use std::collections::hash_map;
16use std::rc::Rc;
17use std::sync::Arc;
18use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
19use std::thread;
20
21use crossbeam_channel::{Receiver, SendError, Sender, unbounded};
22use dom_struct::dom_struct;
23use js::context::JSContext;
24use js::jsapi::{GCReason, JSGCParamKey, JSTracer};
25use js::realm::CurrentRealm;
26use js::rust::wrappers2::{JS_GC, JS_GetGCParameter};
27use malloc_size_of::malloc_size_of_is_0;
28use net_traits::policy_container::PolicyContainer;
29use net_traits::request::{Destination, Origin, PreloadedResources, RequestClient};
30use rustc_hash::FxHashMap;
31use script_bindings::reflector::{Reflector, reflect_dom_object};
32use servo_base::id::PipelineId;
33use servo_url::{ImmutableOrigin, ServoUrl};
34use style::thread_state::{self, ThreadState};
35use swapper::{Swapper, swapper};
36use uuid::Uuid;
37
38use crate::conversions::Convert;
39use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials;
40use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
41use crate::dom::bindings::codegen::Bindings::WorkletBinding::{WorkletMethods, WorkletOptions};
42use crate::dom::bindings::error::Error;
43use crate::dom::bindings::inheritance::Castable;
44use crate::dom::bindings::refcounted::TrustedPromise;
45use crate::dom::bindings::root::{Dom, DomRoot};
46use crate::dom::bindings::str::USVString;
47use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox};
48use crate::dom::globalscope::GlobalScope;
49use crate::dom::promise::Promise;
50use crate::dom::window::Window;
51use crate::dom::workletglobalscope::{
52 WorkletGlobalScope, WorkletGlobalScopeInit, WorkletGlobalScopeType,
53};
54use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg, ScriptEventLoopSender};
55use crate::modules::script_module::fetch_a_module_script_graph;
56use crate::realms::enter_auto_realm;
57use crate::runtime::microtask::MicrotaskQueue;
58use crate::runtime::script_runtime::{IntroductionType, Runtime, ScriptThreadEventCategory};
59use crate::tasks::task_source::TaskSourceName;
60use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
61
62const WORKLET_THREAD_POOL_SIZE: u32 = 3;
64const MIN_GC_THRESHOLD: u32 = 1_000_000;
65
66type LazyCellWithBoxedInitializer<T> = cell::LazyCell<T, Box<dyn FnOnce() -> T>>;
67
68#[derive(JSTraceable, MallocSizeOf)]
69struct DroppableField {
70 worklet_id: WorkletId,
71 #[ignore_malloc_size_of = "Difficult to measure memory usage of Rc<...> types"]
76 thread_pool: LazyCellWithBoxedInitializer<Rc<dyn WorkletThreadPool>>,
77
78 is_thread_pool_initialized: Cell<bool>,
82}
83
84impl Drop for DroppableField {
85 fn drop(&mut self) {
86 let worklet_id = self.worklet_id;
87 if self.is_thread_pool_initialized.get() {
88 self.thread_pool.exit_worklet(worklet_id);
89 }
90 }
91}
92
93#[dom_struct]
94pub(crate) struct Worklet {
96 reflector: Reflector,
97 window: Dom<Window>,
98 global_type: WorkletGlobalScopeType,
99 droppable_field: DroppableField,
100}
101
102impl Worklet {
103 fn new_inherited(
104 window: &Window,
105 global_type: WorkletGlobalScopeType,
106 thread_pool_constructor: Box<dyn FnOnce() -> Rc<dyn WorkletThreadPool>>,
107 ) -> Worklet {
108 Worklet {
109 reflector: Reflector::new(),
110 window: Dom::from_ref(window),
111 global_type,
112 droppable_field: DroppableField {
113 worklet_id: WorkletId::new(),
114 thread_pool: LazyCellWithBoxedInitializer::new(thread_pool_constructor),
115 is_thread_pool_initialized: Cell::new(false),
116 },
117 }
118 }
119
120 pub(crate) fn new(
121 cx: &mut JSContext,
122 window: &Window,
123 global_type: WorkletGlobalScopeType,
124 thread_pool_constructor: Box<dyn FnOnce() -> Rc<dyn WorkletThreadPool>>,
125 ) -> DomRoot<Worklet> {
126 debug!("Creating worklet {:?}.", global_type);
127 reflect_dom_object(
128 cx,
129 Box::new(Worklet::new_inherited(
130 window,
131 global_type,
132 thread_pool_constructor,
133 )),
134 window,
135 )
136 }
137
138 pub(crate) fn worklet_thread_pool(&self) -> Rc<dyn WorkletThreadPool> {
139 self.droppable_field.is_thread_pool_initialized.set(true);
140 self.droppable_field.thread_pool.clone()
141 }
142
143 #[cfg(feature = "testbinding")]
144 pub(crate) fn worklet_id(&self) -> WorkletId {
145 self.droppable_field.worklet_id
146 }
147
148 #[expect(dead_code)]
149 pub(crate) fn worklet_global_scope_type(&self) -> WorkletGlobalScopeType {
150 self.global_type
151 }
152}
153
154impl WorkletMethods<crate::DomTypeHolder> for Worklet {
155 fn AddModule(
157 &self,
158 realm: &mut CurrentRealm,
159 module_url: USVString,
160 options: &WorkletOptions,
161 ) -> Rc<Promise> {
162 let promise = Promise::new_in_realm(realm);
163
164 let module_url_record = match self.window.Document().base_url().join(&module_url.0) {
167 Ok(url) => url,
168 Err(err) => {
169 debug!("URL {:?} parse error {:?}.", module_url.0, err);
171 promise.reject_error(realm, Error::Syntax(None));
172
173 return promise;
174 },
175 };
176 debug!("Adding Worklet module {}.", module_url_record);
177
178 let global_scope = self.window.as_global_scope();
179
180 let pending_tasks_struct = PendingTasksStruct::new();
181
182 self.worklet_thread_pool()
196 .fetch_and_invoke_a_worklet_script(
197 self.window.pipeline_id(),
198 self.droppable_field.worklet_id,
199 self.global_type,
200 self.window.origin().immutable().clone(),
201 global_scope.api_base_url(),
202 module_url_record,
203 global_scope.policy_container(),
204 options.credentials,
205 pending_tasks_struct,
206 &promise,
207 global_scope.inherited_secure_context(),
208 );
209
210 debug!("Returning promise.");
212 promise
213 }
214}
215
216#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
218pub(crate) struct WorkletId(#[no_trace] Uuid);
219
220malloc_size_of_is_0!(WorkletId);
221
222impl WorkletId {
223 fn new() -> WorkletId {
224 WorkletId(Uuid::new_v4())
225 }
226}
227
228#[derive(Clone, Debug)]
230pub(crate) struct PendingTasksStruct(Arc<AtomicIsize>);
231
232impl PendingTasksStruct {
233 fn new() -> PendingTasksStruct {
234 PendingTasksStruct(Arc::new(AtomicIsize::new(
235 WORKLET_THREAD_POOL_SIZE as isize,
236 )))
237 }
238
239 fn set_counter_to(&self, value: isize) -> isize {
240 self.0.swap(value, Ordering::AcqRel)
241 }
242
243 fn decrement_counter_by(&self, offset: isize) -> isize {
244 self.0.fetch_sub(offset, Ordering::AcqRel)
245 }
246}
247
248pub trait WorkletThreadPool: JSTraceable {
249 #[allow(clippy::too_many_arguments)]
255 fn fetch_and_invoke_a_worklet_script(
256 &self,
257 pipeline_id: PipelineId,
258 worklet_id: WorkletId,
259 global_type: WorkletGlobalScopeType,
260 origin: ImmutableOrigin,
261 base_url: ServoUrl,
262 script_url: ServoUrl,
263 policy_container: PolicyContainer,
264 credentials: RequestCredentials,
265 pending_tasks_struct: PendingTasksStruct,
266 promise: &Rc<Promise>,
267 inherited_secure_context: Option<bool>,
268 );
269 fn exit_worklet(&self, worklet_id: WorkletId);
272 fn wake_threads(&self);
275 fn perform_a_worklet_task(&self, worklet_id: WorkletId, worklet_task: WorkletTask);
279}
280
281#[derive(Clone, JSTraceable)]
334pub(crate) struct StatelessWorkletThreadPool {
335 #[no_trace]
337 primary_sender: Sender<WorkletData>,
338 #[no_trace]
339 hot_backup_sender: Sender<WorkletData>,
340 #[no_trace]
341 cold_backup_sender: Sender<WorkletData>,
342 #[no_trace]
344 control_sender_0: Sender<WorkletControl>,
345 #[no_trace]
346 control_sender_1: Sender<WorkletControl>,
347 #[no_trace]
348 control_sender_2: Sender<WorkletControl>,
349}
350
351impl Drop for StatelessWorkletThreadPool {
352 fn drop(&mut self) {
353 let _ = self.cold_backup_sender.send(WorkletData::Quit);
354 let _ = self.hot_backup_sender.send(WorkletData::Quit);
355 let _ = self.primary_sender.send(WorkletData::Quit);
356 }
357}
358
359impl StatelessWorkletThreadPool {
360 pub(crate) fn spawn(global_init: WorkletGlobalScopeInit) -> StatelessWorkletThreadPool {
363 let primary_role = WorkletThreadRole::new(false, false);
364 let hot_backup_role = WorkletThreadRole::new(true, false);
365 let cold_backup_role = WorkletThreadRole::new(false, true);
366 let primary_sender = primary_role.sender.clone();
367 let hot_backup_sender = hot_backup_role.sender.clone();
368 let cold_backup_sender = cold_backup_role.sender.clone();
369 let init = WorkletThreadInit {
370 primary_sender: primary_sender.clone(),
371 hot_backup_sender: hot_backup_sender.clone(),
372 cold_backup_sender: cold_backup_sender.clone(),
373 global_init,
374 };
375 StatelessWorkletThreadPool {
376 primary_sender,
377 hot_backup_sender,
378 cold_backup_sender,
379 control_sender_0: WorkletThread::spawn(primary_role, init.clone(), 0),
380 control_sender_1: WorkletThread::spawn(hot_backup_role, init.clone(), 1),
381 control_sender_2: WorkletThread::spawn(cold_backup_role, init, 2),
382 }
383 }
384}
385
386impl WorkletThreadPool for StatelessWorkletThreadPool {
387 #[allow(clippy::too_many_arguments)]
388 fn fetch_and_invoke_a_worklet_script(
389 &self,
390 pipeline_id: PipelineId,
391 worklet_id: WorkletId,
392 global_type: WorkletGlobalScopeType,
393 origin: ImmutableOrigin,
394 base_url: ServoUrl,
395 script_url: ServoUrl,
396 policy_container: PolicyContainer,
397 credentials: RequestCredentials,
398 pending_tasks_struct: PendingTasksStruct,
399 promise: &Rc<Promise>,
400 inherited_secure_context: Option<bool>,
401 ) {
402 for sender in &[
404 &self.control_sender_0,
405 &self.control_sender_1,
406 &self.control_sender_2,
407 ] {
408 let _ = sender.send(WorkletControl::FetchAndInvokeAWorkletScript {
409 pipeline_id,
410 worklet_id,
411 global_type,
412 origin: origin.clone(),
413 base_url: base_url.clone(),
414 script_url: script_url.clone(),
415 policy_container: policy_container.clone(),
416 credentials,
417 pending_tasks_struct: pending_tasks_struct.clone(),
418 promise: TrustedPromise::new(promise.clone()),
419 inherited_secure_context,
420 });
421 }
422 self.wake_threads();
423 }
424
425 fn exit_worklet(&self, worklet_id: WorkletId) {
426 for sender in &[
427 &self.control_sender_0,
428 &self.control_sender_1,
429 &self.control_sender_2,
430 ] {
431 let _ = sender.send(WorkletControl::ExitWorklet(worklet_id));
432 }
433 self.wake_threads();
434 }
435
436 fn wake_threads(&self) {
437 let _ = self.cold_backup_sender.send(WorkletData::WakeUp);
439 let _ = self.hot_backup_sender.send(WorkletData::WakeUp);
440 let _ = self.primary_sender.send(WorkletData::WakeUp);
441 }
442
443 fn perform_a_worklet_task(&self, worklet_id: WorkletId, worklet_task: WorkletTask) {
445 let msg = WorkletData::Task(worklet_id, worklet_task);
446 let _ = self.primary_sender.send(msg);
447 }
448}
449
450type WorkletTask = Box<dyn FnOnce(&mut JSContext, &WorkletGlobalScope) + Send>;
452
453enum WorkletData {
455 Task(WorkletId, WorkletTask),
456 StartSwapRoles(Sender<WorkletData>),
457 FinishSwapRoles(Swapper<WorkletThreadRole>),
458 WakeUp,
459 Quit,
460}
461
462pub(crate) enum WorkletControl {
464 ExitWorklet(WorkletId),
465 FetchAndInvokeAWorkletScript {
466 pipeline_id: PipelineId,
467 worklet_id: WorkletId,
468 global_type: WorkletGlobalScopeType,
469 origin: ImmutableOrigin,
470 base_url: ServoUrl,
471 script_url: ServoUrl,
472 policy_container: PolicyContainer,
473 credentials: RequestCredentials,
474 pending_tasks_struct: PendingTasksStruct,
475 promise: TrustedPromise,
476 inherited_secure_context: Option<bool>,
477 },
478 Common(CommonScriptMsg),
479}
480
481struct WorkletThreadRole {
488 receiver: Receiver<WorkletData>,
489 sender: Sender<WorkletData>,
490 is_hot_backup: bool,
491 is_cold_backup: bool,
492}
493
494impl WorkletThreadRole {
495 fn new(is_hot_backup: bool, is_cold_backup: bool) -> WorkletThreadRole {
496 let (sender, receiver) = unbounded();
497 WorkletThreadRole {
498 sender,
499 receiver,
500 is_hot_backup,
501 is_cold_backup,
502 }
503 }
504}
505
506#[derive(Clone)]
508struct WorkletThreadInit {
509 primary_sender: Sender<WorkletData>,
511 hot_backup_sender: Sender<WorkletData>,
512 cold_backup_sender: Sender<WorkletData>,
513
514 global_init: WorkletGlobalScopeInit,
516}
517
518#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
520struct WorkletThread {
521 role: WorkletThreadRole,
523
524 control_receiver: Receiver<WorkletControl>,
526 control_sender: Sender<WorkletControl>,
528
529 primary_sender: Sender<WorkletData>,
531 hot_backup_sender: Sender<WorkletData>,
532 cold_backup_sender: Sender<WorkletData>,
533
534 global_init: WorkletGlobalScopeInit,
536
537 global_scopes: FxHashMap<WorkletId, Dom<WorkletGlobalScope>>,
539
540 control_buffer: Option<WorkletControl>,
542
543 closing: Arc<AtomicBool>,
545
546 runtime: Runtime,
548 should_gc: bool,
549 gc_threshold: u32,
550}
551
552#[expect(unsafe_code)]
553unsafe impl JSTraceable for WorkletThread {
554 unsafe fn trace(&self, trc: *mut JSTracer) {
555 debug!("Tracing worklet thread.");
556 unsafe { self.global_scopes.trace(trc) };
557 }
558}
559
560impl WorkletThread {
561 #[allow(unsafe_code)]
562 fn spawn(
564 role: WorkletThreadRole,
565 init: WorkletThreadInit,
566 thread_index: u8,
567 ) -> Sender<WorkletControl> {
568 let (control_sender, control_receiver) = unbounded();
569 let control_sender_clone = control_sender.clone();
570 let _ = thread::Builder::new()
571 .name(format!("Worklet#{thread_index}"))
572 .spawn(move || {
573 debug!("Initializing worklet thread.");
577 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
578 let runtime = Runtime::new(None);
579 let mut cx = unsafe { runtime.cx() };
580 let mut thread = RootedTraceableBox::new(WorkletThread {
581 role,
582 control_receiver,
583 control_sender: control_sender_clone,
584 primary_sender: init.primary_sender,
585 hot_backup_sender: init.hot_backup_sender,
586 cold_backup_sender: init.cold_backup_sender,
587 global_init: init.global_init,
588 global_scopes: FxHashMap::default(),
589 control_buffer: None,
590 runtime,
591 should_gc: false,
592 closing: Arc::new(AtomicBool::new(false)),
593 gc_threshold: MIN_GC_THRESHOLD,
594 });
595 thread.run(&mut cx);
596 })
597 .expect("Couldn't start worklet thread");
598 control_sender
599 }
600
601 fn run(&mut self, cx: &mut JSContext) {
603 loop {
604 let message = self.role.receiver.recv().unwrap();
606 match message {
607 WorkletData::Task(id, task) => {
609 self.perform_a_worklet_task(cx, id, task);
610 },
611 WorkletData::StartSwapRoles(sender) => {
618 let (our_swapper, their_swapper) = swapper();
619 match sender.send(WorkletData::FinishSwapRoles(their_swapper)) {
620 Ok(_) => {},
621 Err(_) => {
622 return;
625 },
626 };
627 let _ = our_swapper.swap(&mut self.role);
628 },
629 WorkletData::FinishSwapRoles(swapper) => {
632 let _ = swapper.swap(&mut self.role);
633 },
634 WorkletData::WakeUp => {},
636 WorkletData::Quit => {
638 return;
639 },
640 }
641
642 if self.role.is_cold_backup {
646 if let Some(control) = self.control_buffer.take() {
647 self.process_control(control, cx);
648 }
649 while let Ok(control) = self.control_receiver.try_recv() {
650 self.process_control(control, cx);
651 }
652
653 for worklet_global_scope in self.global_scopes.values() {
654 worklet_global_scope.perform_a_microtask_checkpoint(cx);
655 }
656
657 self.gc(cx);
658 } else if self.control_buffer.is_none() &&
659 let Ok(control) = self.control_receiver.try_recv()
660 {
661 self.control_buffer = Some(control);
662 let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
663 let _ = self.cold_backup_sender.send(msg);
664 }
665 if self.current_memory_usage() > self.gc_threshold {
669 if self.role.is_hot_backup || self.role.is_cold_backup {
670 self.should_gc = false;
671 self.gc(cx);
672 } else if !self.should_gc {
673 self.should_gc = true;
674 let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
675 let _ = self.hot_backup_sender.send(msg);
676 }
677 }
678 }
679 }
680
681 #[expect(unsafe_code)]
683 fn current_memory_usage(&self) -> u32 {
684 unsafe { JS_GetGCParameter(self.runtime.cx_no_gc(), JSGCParamKey::JSGC_BYTES) }
685 }
686
687 #[expect(unsafe_code)]
689 fn gc(&mut self, cx: &mut JSContext) {
690 debug!(
691 "BEGIN GC (usage = {}, threshold = {}).",
692 self.current_memory_usage(),
693 self.gc_threshold
694 );
695 unsafe { JS_GC(cx, GCReason::API) };
696 self.gc_threshold = max(MIN_GC_THRESHOLD, self.current_memory_usage() * 2);
697 debug!(
698 "END GC (usage = {}, threshold = {}).",
699 self.current_memory_usage(),
700 self.gc_threshold
701 );
702 }
703
704 #[expect(clippy::too_many_arguments)]
707 fn get_worklet_global_scope(
708 &mut self,
709 cx: &mut JSContext,
710 pipeline_id: PipelineId,
711 worklet_id: WorkletId,
712 inherited_secure_context: Option<bool>,
713 global_type: WorkletGlobalScopeType,
714 base_url: ServoUrl,
715 microtask_queue: Rc<MicrotaskQueue>,
716 ) -> DomRoot<WorkletGlobalScope> {
717 match self.global_scopes.entry(worklet_id) {
718 hash_map::Entry::Occupied(entry) => DomRoot::from_ref(entry.get()),
719
720 hash_map::Entry::Vacant(entry) => {
722 debug!("Creating new worklet global scope.");
723
724 let executor = WorkletExecutor {
726 worklet_id,
727 primary_sender: self.primary_sender.clone(),
728 hot_backup_sender: self.hot_backup_sender.clone(),
729 cold_backup_sender: self.cold_backup_sender.clone(),
730 control_sender: self.control_sender.clone(),
731 };
732
733 let result = WorkletGlobalScope::new(
734 global_type,
735 pipeline_id,
736 base_url,
737 inherited_secure_context,
738 executor,
739 &self.global_init,
740 cx,
741 self.closing.clone(),
742 microtask_queue,
743 );
744 entry.insert(Dom::from_ref(&*result));
745 result
746 },
747 }
748 }
749
750 #[allow(clippy::too_many_arguments)]
753 fn fetch_and_invoke_a_worklet_script(
754 &self,
755 global_scope: &WorkletGlobalScope,
756 pipeline_id: PipelineId,
757 origin: ImmutableOrigin,
758 script_url: ServoUrl,
759 policy_container: PolicyContainer,
760 credentials: RequestCredentials,
761 pending_tasks_struct: PendingTasksStruct,
762 promise: TrustedPromise,
763 cx: &mut JSContext,
764 ) {
765 debug!("Fetching from {}.", script_url);
766 let global = global_scope.upcast::<GlobalScope>();
771
772 let request_client = RequestClient {
774 preloaded_resources: PreloadedResources::default(),
775 policy_container,
776 origin: Origin::Origin(origin),
777 is_nested_browsing_context: global.is_nested_browsing_context(),
778 insecure_requests_policy: global.insecure_requests_policy(),
779 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_origin(),
780 };
781
782 let promise_task = Rc::new(RefCell::new(Some(promise)));
789 let script_thread_sender = self.global_init.to_script_thread_sender.clone();
790 let rooted_global = DomRoot::from_ref(global);
791 let script_url = ensure_blob_referenced_by_url_is_kept_alive(global, script_url);
792
793 fetch_a_module_script_graph(
800 cx,
801 global,
802 script_url,
803 request_client,
804 Destination::PaintWorklet,
805 global.get_referrer(),
806 credentials.convert(),
807 Some(IntroductionType::WORKLET),
808 move |cx, module_tree| {
809 match module_tree {
810 None => {
812 debug!("Failed to load script.");
813
814 reject_promise(
815 &pending_tasks_struct,
816 promise_task.borrow_mut(),
817 script_thread_sender.clone(),
818 );
819 },
820 Some(script) => {
821 let mut realm = enter_auto_realm(cx, &*rooted_global);
822 let cx = &mut realm.current_realm();
823
824 if script.get_rethrow_error().take().is_some() {
829 reject_promise(
831 &pending_tasks_struct,
832 promise_task.borrow_mut(),
833 script_thread_sender.clone(),
834 );
835
836 return;
838 }
839
840 rooted_global.run_a_module_script(cx, script, false);
842
843 let old_counter = pending_tasks_struct.decrement_counter_by(1);
847 if old_counter == 1 {
849 debug!("Resolving promise.");
850
851 let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id);
852 script_thread_sender
853 .send(msg)
854 .expect("Worklet thread outlived script thread.");
855
856 let task = promise_task
857 .borrow_mut()
858 .take()
859 .expect("promise_task must be consumed exactly once")
860 .resolve_task(());
861
862 let msg = CommonScriptMsg::Task(
863 ScriptThreadEventCategory::WorkletEvent,
864 Box::new(task),
865 None,
866 TaskSourceName::Networking,
867 );
868
869 let msg = MainThreadScriptMsg::Common(msg);
871 script_thread_sender
872 .send(msg)
873 .expect("Worklet thread outlived script thread.");
874 }
875 },
876 }
877 },
878 );
879 }
880
881 fn perform_a_worklet_task(
883 &self,
884 cx: &mut JSContext,
885 worklet_id: WorkletId,
886 worklet_task: WorkletTask,
887 ) {
888 match self.global_scopes.get(&worklet_id) {
889 Some(global) => worklet_task(cx, global),
890 None => warn!("No such worklet as {:?}.", worklet_id),
891 }
892 }
893
894 fn process_control(&mut self, control: WorkletControl, cx: &mut js::context::JSContext) {
896 match control {
897 WorkletControl::ExitWorklet(worklet_id) => {
898 self.global_scopes.remove(&worklet_id);
899 },
900 WorkletControl::FetchAndInvokeAWorkletScript {
901 pipeline_id,
902 worklet_id,
903 global_type,
904 origin,
905 base_url,
906 script_url,
907 policy_container,
908 credentials,
909 pending_tasks_struct,
910 promise,
911 inherited_secure_context,
912 } => {
913 let global = self.get_worklet_global_scope(
917 cx,
918 pipeline_id,
919 worklet_id,
920 inherited_secure_context,
921 global_type,
922 base_url,
923 self.runtime.microtask_queue.clone(),
924 );
925 self.fetch_and_invoke_a_worklet_script(
926 &global,
927 pipeline_id,
928 origin,
929 script_url,
930 policy_container,
931 credentials,
932 pending_tasks_struct,
933 promise,
934 cx,
935 )
936 },
937 WorkletControl::Common(script_msg) => {
938 if let CommonScriptMsg::Task(_, task, _, _) = script_msg {
939 task.run_box(cx);
940 }
941 },
942 }
943 }
944}
945
946pub(crate) fn reject_promise(
949 pending_tasks_struct: &PendingTasksStruct,
950 mut promise_task: RefMut<'_, Option<TrustedPromise>>,
951 script_thread_sender: Sender<MainThreadScriptMsg>,
952) {
953 let old_counter = pending_tasks_struct.set_counter_to(-1);
955
956 if old_counter > 0 {
958 let task = promise_task
960 .take()
961 .expect("promise_task must be consumed exactly once")
962 .reject_task(Error::Abort(None));
963
964 let msg = CommonScriptMsg::Task(
965 ScriptThreadEventCategory::WorkletEvent,
966 Box::new(task),
967 None,
968 TaskSourceName::Networking,
969 );
970
971 let msg = MainThreadScriptMsg::Common(msg);
973 script_thread_sender
974 .send(msg)
975 .expect("Worklet thread outlived script thread.");
976 }
977}
978
979#[derive(Clone, JSTraceable, MallocSizeOf)]
981pub(crate) struct WorkletExecutor {
982 worklet_id: WorkletId,
983 #[no_trace]
984 primary_sender: Sender<WorkletData>,
985 #[no_trace]
986 hot_backup_sender: Sender<WorkletData>,
987 #[no_trace]
988 cold_backup_sender: Sender<WorkletData>,
989 #[no_trace]
990 control_sender: Sender<WorkletControl>,
991}
992
993impl WorkletExecutor {
994 pub(crate) fn wake_threads(&self) -> Result<(), SendError<()>> {
996 self.cold_backup_sender
997 .send(WorkletData::WakeUp)
998 .map_err(|_| SendError(()))?;
999 self.hot_backup_sender
1000 .send(WorkletData::WakeUp)
1001 .map_err(|_| SendError(()))?;
1002 self.primary_sender
1003 .send(WorkletData::WakeUp)
1004 .map_err(|_| SendError(()))
1005 }
1006
1007 pub(crate) fn schedule_a_worklet_task(&self, task: WorkletTask) {
1009 let _ = self
1010 .primary_sender
1011 .send(WorkletData::Task(self.worklet_id, task));
1012 }
1013
1014 pub(crate) fn send_control_message(
1015 &self,
1016 control_message: WorkletControl,
1017 ) -> Result<(), SendError<()>> {
1018 self.control_sender
1019 .send(control_message)
1020 .map_err(|_| SendError(()))?;
1021 self.wake_threads()
1022 }
1023
1024 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
1025 ScriptEventLoopSender::Worklet(self.clone())
1026 }
1027}