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_with_cx};
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;
50#[cfg(feature = "testbinding")]
51use crate::dom::testworkletglobalscope::TestWorkletTask;
52use crate::dom::window::Window;
53use crate::dom::workletglobalscope::{
54 WorkletGlobalScope, WorkletGlobalScopeInit, WorkletGlobalScopeType, WorkletTask,
55};
56use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg, ScriptEventLoopSender};
57use crate::microtask::MicrotaskQueue;
58use crate::modules::script_module::fetch_a_module_script_graph;
59use crate::realms::enter_auto_realm;
60use crate::script_runtime::{IntroductionType, Runtime, ScriptThreadEventCategory};
61use crate::tasks::task_source::TaskSourceName;
62use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
63
64const WORKLET_THREAD_POOL_SIZE: u32 = 3;
66const MIN_GC_THRESHOLD: u32 = 1_000_000;
67
68type LazyCellWithBoxedInitializer<T> = cell::LazyCell<T, Box<dyn FnOnce() -> T>>;
69
70#[derive(JSTraceable, MallocSizeOf)]
71struct DroppableField {
72 worklet_id: WorkletId,
73 #[ignore_malloc_size_of = "Difficult to measure memory usage of Rc<...> types"]
78 thread_pool: LazyCellWithBoxedInitializer<Rc<WorkletThreadPool>>,
79
80 is_thread_pool_initialized: Cell<bool>,
84}
85
86impl Drop for DroppableField {
87 fn drop(&mut self) {
88 let worklet_id = self.worklet_id;
89 if self.is_thread_pool_initialized.get() {
90 self.thread_pool.exit_worklet(worklet_id);
91 }
92 }
93}
94
95#[dom_struct]
96pub(crate) struct Worklet {
98 reflector: Reflector,
99 window: Dom<Window>,
100 global_type: WorkletGlobalScopeType,
101 droppable_field: DroppableField,
102}
103
104impl Worklet {
105 fn new_inherited(
106 window: &Window,
107 global_type: WorkletGlobalScopeType,
108 thread_pool_constructor: Box<dyn FnOnce() -> Rc<WorkletThreadPool>>,
109 ) -> Worklet {
110 Worklet {
111 reflector: Reflector::new(),
112 window: Dom::from_ref(window),
113 global_type,
114 droppable_field: DroppableField {
115 worklet_id: WorkletId::new(),
116 thread_pool: LazyCellWithBoxedInitializer::new(thread_pool_constructor),
117 is_thread_pool_initialized: Cell::new(false),
118 },
119 }
120 }
121
122 pub(crate) fn new(
123 cx: &mut JSContext,
124 window: &Window,
125 global_type: WorkletGlobalScopeType,
126 thread_pool_constructor: Box<dyn FnOnce() -> Rc<WorkletThreadPool>>,
127 ) -> DomRoot<Worklet> {
128 debug!("Creating worklet {:?}.", global_type);
129 reflect_dom_object_with_cx(
130 Box::new(Worklet::new_inherited(
131 window,
132 global_type,
133 thread_pool_constructor,
134 )),
135 window,
136 cx,
137 )
138 }
139
140 pub(crate) fn worklet_thread_pool(&self) -> &WorkletThreadPool {
141 self.droppable_field.is_thread_pool_initialized.set(true);
142 &self.droppable_field.thread_pool
143 }
144
145 #[cfg(feature = "testbinding")]
146 pub(crate) fn worklet_id(&self) -> WorkletId {
147 self.droppable_field.worklet_id
148 }
149
150 #[expect(dead_code)]
151 pub(crate) fn worklet_global_scope_type(&self) -> WorkletGlobalScopeType {
152 self.global_type
153 }
154}
155
156impl WorkletMethods<crate::DomTypeHolder> for Worklet {
157 fn AddModule(
159 &self,
160 realm: &mut CurrentRealm,
161 module_url: USVString,
162 options: &WorkletOptions,
163 ) -> Rc<Promise> {
164 let promise = Promise::new_in_realm(realm);
165
166 let module_url_record = match self.window.Document().base_url().join(&module_url.0) {
169 Ok(url) => url,
170 Err(err) => {
171 debug!("URL {:?} parse error {:?}.", module_url.0, err);
173 promise.reject_error(realm, Error::Syntax(None));
174
175 return promise;
176 },
177 };
178 debug!("Adding Worklet module {}.", module_url_record);
179
180 let global_scope = self.window.as_global_scope();
181
182 let pending_tasks_struct = PendingTasksStruct::new();
183
184 self.worklet_thread_pool()
198 .fetch_and_invoke_a_worklet_script(
199 self.window.pipeline_id(),
200 self.droppable_field.worklet_id,
201 self.global_type,
202 self.window.origin().immutable().clone(),
203 global_scope.api_base_url(),
204 module_url_record,
205 global_scope.policy_container(),
206 options.credentials,
207 pending_tasks_struct,
208 &promise,
209 global_scope.inherited_secure_context(),
210 );
211
212 debug!("Returning promise.");
214 promise
215 }
216}
217
218#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
220pub(crate) struct WorkletId(#[no_trace] Uuid);
221
222malloc_size_of_is_0!(WorkletId);
223
224impl WorkletId {
225 fn new() -> WorkletId {
226 WorkletId(Uuid::new_v4())
227 }
228}
229
230#[derive(Clone, Debug)]
232pub(crate) struct PendingTasksStruct(Arc<AtomicIsize>);
233
234impl PendingTasksStruct {
235 fn new() -> PendingTasksStruct {
236 PendingTasksStruct(Arc::new(AtomicIsize::new(
237 WORKLET_THREAD_POOL_SIZE as isize,
238 )))
239 }
240
241 fn set_counter_to(&self, value: isize) -> isize {
242 self.0.swap(value, Ordering::AcqRel)
243 }
244
245 fn decrement_counter_by(&self, offset: isize) -> isize {
246 self.0.fetch_sub(offset, Ordering::AcqRel)
247 }
248}
249
250#[derive(Clone, JSTraceable)]
300pub(crate) struct WorkletThreadPool {
301 #[no_trace]
303 primary_sender: Sender<WorkletData>,
304 #[no_trace]
305 hot_backup_sender: Sender<WorkletData>,
306 #[no_trace]
307 cold_backup_sender: Sender<WorkletData>,
308 #[no_trace]
310 control_sender_0: Sender<WorkletControl>,
311 #[no_trace]
312 control_sender_1: Sender<WorkletControl>,
313 #[no_trace]
314 control_sender_2: Sender<WorkletControl>,
315}
316
317impl Drop for WorkletThreadPool {
318 fn drop(&mut self) {
319 let _ = self.cold_backup_sender.send(WorkletData::Quit);
320 let _ = self.hot_backup_sender.send(WorkletData::Quit);
321 let _ = self.primary_sender.send(WorkletData::Quit);
322 }
323}
324
325impl WorkletThreadPool {
326 pub(crate) fn spawn(global_init: WorkletGlobalScopeInit) -> WorkletThreadPool {
329 let primary_role = WorkletThreadRole::new(false, false);
330 let hot_backup_role = WorkletThreadRole::new(true, false);
331 let cold_backup_role = WorkletThreadRole::new(false, true);
332 let primary_sender = primary_role.sender.clone();
333 let hot_backup_sender = hot_backup_role.sender.clone();
334 let cold_backup_sender = cold_backup_role.sender.clone();
335 let init = WorkletThreadInit {
336 primary_sender: primary_sender.clone(),
337 hot_backup_sender: hot_backup_sender.clone(),
338 cold_backup_sender: cold_backup_sender.clone(),
339 global_init,
340 };
341 WorkletThreadPool {
342 primary_sender,
343 hot_backup_sender,
344 cold_backup_sender,
345 control_sender_0: WorkletThread::spawn(primary_role, init.clone(), 0),
346 control_sender_1: WorkletThread::spawn(hot_backup_role, init.clone(), 1),
347 control_sender_2: WorkletThread::spawn(cold_backup_role, init, 2),
348 }
349 }
350
351 #[allow(clippy::too_many_arguments)]
356 fn fetch_and_invoke_a_worklet_script(
357 &self,
358 pipeline_id: PipelineId,
359 worklet_id: WorkletId,
360 global_type: WorkletGlobalScopeType,
361 origin: ImmutableOrigin,
362 base_url: ServoUrl,
363 script_url: ServoUrl,
364 policy_container: PolicyContainer,
365 credentials: RequestCredentials,
366 pending_tasks_struct: PendingTasksStruct,
367 promise: &Rc<Promise>,
368 inherited_secure_context: Option<bool>,
369 ) {
370 for sender in &[
372 &self.control_sender_0,
373 &self.control_sender_1,
374 &self.control_sender_2,
375 ] {
376 let _ = sender.send(WorkletControl::FetchAndInvokeAWorkletScript {
377 pipeline_id,
378 worklet_id,
379 global_type,
380 origin: origin.clone(),
381 base_url: base_url.clone(),
382 script_url: script_url.clone(),
383 policy_container: policy_container.clone(),
384 credentials,
385 pending_tasks_struct: pending_tasks_struct.clone(),
386 promise: TrustedPromise::new(promise.clone()),
387 inherited_secure_context,
388 });
389 }
390 self.wake_threads();
391 }
392
393 pub(crate) fn exit_worklet(&self, worklet_id: WorkletId) {
394 for sender in &[
395 &self.control_sender_0,
396 &self.control_sender_1,
397 &self.control_sender_2,
398 ] {
399 let _ = sender.send(WorkletControl::ExitWorklet(worklet_id));
400 }
401 self.wake_threads();
402 }
403
404 #[cfg(feature = "testbinding")]
406 pub(crate) fn test_worklet_lookup(&self, id: WorkletId, key: String) -> Option<String> {
407 let (sender, receiver) = unbounded();
408 let msg = WorkletData::Task(id, WorkletTask::Test(TestWorkletTask::Lookup(key, sender)));
409 let _ = self.primary_sender.send(msg);
410 receiver.recv().expect("Test worklet has died?")
411 }
412
413 fn wake_threads(&self) {
414 let _ = self.cold_backup_sender.send(WorkletData::WakeUp);
416 let _ = self.hot_backup_sender.send(WorkletData::WakeUp);
417 let _ = self.primary_sender.send(WorkletData::WakeUp);
418 }
419}
420
421enum WorkletData {
423 Task(WorkletId, WorkletTask),
424 StartSwapRoles(Sender<WorkletData>),
425 FinishSwapRoles(Swapper<WorkletThreadRole>),
426 WakeUp,
427 Quit,
428}
429
430pub(crate) enum WorkletControl {
432 ExitWorklet(WorkletId),
433 FetchAndInvokeAWorkletScript {
434 pipeline_id: PipelineId,
435 worklet_id: WorkletId,
436 global_type: WorkletGlobalScopeType,
437 origin: ImmutableOrigin,
438 base_url: ServoUrl,
439 script_url: ServoUrl,
440 policy_container: PolicyContainer,
441 credentials: RequestCredentials,
442 pending_tasks_struct: PendingTasksStruct,
443 promise: TrustedPromise,
444 inherited_secure_context: Option<bool>,
445 },
446 Common(CommonScriptMsg),
447}
448
449struct WorkletThreadRole {
456 receiver: Receiver<WorkletData>,
457 sender: Sender<WorkletData>,
458 is_hot_backup: bool,
459 is_cold_backup: bool,
460}
461
462impl WorkletThreadRole {
463 fn new(is_hot_backup: bool, is_cold_backup: bool) -> WorkletThreadRole {
464 let (sender, receiver) = unbounded();
465 WorkletThreadRole {
466 sender,
467 receiver,
468 is_hot_backup,
469 is_cold_backup,
470 }
471 }
472}
473
474#[derive(Clone)]
476struct WorkletThreadInit {
477 primary_sender: Sender<WorkletData>,
479 hot_backup_sender: Sender<WorkletData>,
480 cold_backup_sender: Sender<WorkletData>,
481
482 global_init: WorkletGlobalScopeInit,
484}
485
486#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
488struct WorkletThread {
489 role: WorkletThreadRole,
491
492 control_receiver: Receiver<WorkletControl>,
494 control_sender: Sender<WorkletControl>,
496
497 primary_sender: Sender<WorkletData>,
499 hot_backup_sender: Sender<WorkletData>,
500 cold_backup_sender: Sender<WorkletData>,
501
502 global_init: WorkletGlobalScopeInit,
504
505 global_scopes: FxHashMap<WorkletId, Dom<WorkletGlobalScope>>,
507
508 control_buffer: Option<WorkletControl>,
510
511 closing: Arc<AtomicBool>,
513
514 runtime: Runtime,
516 should_gc: bool,
517 gc_threshold: u32,
518}
519
520#[expect(unsafe_code)]
521unsafe impl JSTraceable for WorkletThread {
522 unsafe fn trace(&self, trc: *mut JSTracer) {
523 debug!("Tracing worklet thread.");
524 unsafe { self.global_scopes.trace(trc) };
525 }
526}
527
528impl WorkletThread {
529 #[allow(unsafe_code)]
530 fn spawn(
532 role: WorkletThreadRole,
533 init: WorkletThreadInit,
534 thread_index: u8,
535 ) -> Sender<WorkletControl> {
536 let (control_sender, control_receiver) = unbounded();
537 let control_sender_clone = control_sender.clone();
538 let _ = thread::Builder::new()
539 .name(format!("Worklet#{thread_index}"))
540 .spawn(move || {
541 debug!("Initializing worklet thread.");
545 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
546 let runtime = Runtime::new(None);
547 let mut cx = unsafe { runtime.cx() };
548 let mut thread = RootedTraceableBox::new(WorkletThread {
549 role,
550 control_receiver,
551 control_sender: control_sender_clone,
552 primary_sender: init.primary_sender,
553 hot_backup_sender: init.hot_backup_sender,
554 cold_backup_sender: init.cold_backup_sender,
555 global_init: init.global_init,
556 global_scopes: FxHashMap::default(),
557 control_buffer: None,
558 runtime,
559 should_gc: false,
560 closing: Arc::new(AtomicBool::new(false)),
561 gc_threshold: MIN_GC_THRESHOLD,
562 });
563 thread.run(&mut cx);
564 })
565 .expect("Couldn't start worklet thread");
566 control_sender
567 }
568
569 fn run(&mut self, cx: &mut JSContext) {
571 loop {
572 let message = self.role.receiver.recv().unwrap();
574 match message {
575 WorkletData::Task(id, task) => {
577 self.perform_a_worklet_task(cx, id, task);
578 },
579 WorkletData::StartSwapRoles(sender) => {
586 let (our_swapper, their_swapper) = swapper();
587 match sender.send(WorkletData::FinishSwapRoles(their_swapper)) {
588 Ok(_) => {},
589 Err(_) => {
590 return;
593 },
594 };
595 let _ = our_swapper.swap(&mut self.role);
596 },
597 WorkletData::FinishSwapRoles(swapper) => {
600 let _ = swapper.swap(&mut self.role);
601 },
602 WorkletData::WakeUp => {},
604 WorkletData::Quit => {
606 return;
607 },
608 }
609
610 if self.role.is_cold_backup {
614 if let Some(control) = self.control_buffer.take() {
615 self.process_control(control, cx);
616 }
617 while let Ok(control) = self.control_receiver.try_recv() {
618 self.process_control(control, cx);
619 }
620
621 for worklet_global_scope in self.global_scopes.values() {
622 worklet_global_scope.perform_a_microtask_checkpoint(cx);
623 }
624
625 self.gc(cx);
626 } else if self.control_buffer.is_none() &&
627 let Ok(control) = self.control_receiver.try_recv()
628 {
629 self.control_buffer = Some(control);
630 let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
631 let _ = self.cold_backup_sender.send(msg);
632 }
633 if self.current_memory_usage() > self.gc_threshold {
637 if self.role.is_hot_backup || self.role.is_cold_backup {
638 self.should_gc = false;
639 self.gc(cx);
640 } else if !self.should_gc {
641 self.should_gc = true;
642 let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
643 let _ = self.hot_backup_sender.send(msg);
644 }
645 }
646 }
647 }
648
649 #[expect(unsafe_code)]
651 fn current_memory_usage(&self) -> u32 {
652 unsafe { JS_GetGCParameter(self.runtime.cx_no_gc(), JSGCParamKey::JSGC_BYTES) }
653 }
654
655 #[expect(unsafe_code)]
657 fn gc(&mut self, cx: &mut JSContext) {
658 debug!(
659 "BEGIN GC (usage = {}, threshold = {}).",
660 self.current_memory_usage(),
661 self.gc_threshold
662 );
663 unsafe { JS_GC(cx, GCReason::API) };
664 self.gc_threshold = max(MIN_GC_THRESHOLD, self.current_memory_usage() * 2);
665 debug!(
666 "END GC (usage = {}, threshold = {}).",
667 self.current_memory_usage(),
668 self.gc_threshold
669 );
670 }
671
672 #[expect(clippy::too_many_arguments)]
675 fn get_worklet_global_scope(
676 &mut self,
677 cx: &mut JSContext,
678 pipeline_id: PipelineId,
679 worklet_id: WorkletId,
680 inherited_secure_context: Option<bool>,
681 global_type: WorkletGlobalScopeType,
682 base_url: ServoUrl,
683 microtask_queue: Rc<MicrotaskQueue>,
684 ) -> DomRoot<WorkletGlobalScope> {
685 match self.global_scopes.entry(worklet_id) {
686 hash_map::Entry::Occupied(entry) => DomRoot::from_ref(entry.get()),
687
688 hash_map::Entry::Vacant(entry) => {
690 debug!("Creating new worklet global scope.");
691
692 let executor = WorkletExecutor {
694 worklet_id,
695 primary_sender: self.primary_sender.clone(),
696 hot_backup_sender: self.hot_backup_sender.clone(),
697 cold_backup_sender: self.cold_backup_sender.clone(),
698 control_sender: self.control_sender.clone(),
699 };
700
701 let result = WorkletGlobalScope::new(
702 global_type,
703 pipeline_id,
704 base_url,
705 inherited_secure_context,
706 executor,
707 &self.global_init,
708 cx,
709 self.closing.clone(),
710 microtask_queue,
711 );
712 entry.insert(Dom::from_ref(&*result));
713 result
714 },
715 }
716 }
717
718 #[allow(clippy::too_many_arguments)]
721 fn fetch_and_invoke_a_worklet_script(
722 &self,
723 global_scope: &WorkletGlobalScope,
724 pipeline_id: PipelineId,
725 origin: ImmutableOrigin,
726 script_url: ServoUrl,
727 policy_container: PolicyContainer,
728 credentials: RequestCredentials,
729 pending_tasks_struct: PendingTasksStruct,
730 promise: TrustedPromise,
731 cx: &mut JSContext,
732 ) {
733 debug!("Fetching from {}.", script_url);
734 let global = global_scope.upcast::<GlobalScope>();
739
740 let request_client = RequestClient {
742 preloaded_resources: PreloadedResources::default(),
743 policy_container,
744 origin: Origin::Origin(origin),
745 is_nested_browsing_context: global.is_nested_browsing_context(),
746 insecure_requests_policy: global.insecure_requests_policy(),
747 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_origin(),
748 };
749
750 let promise_task = Rc::new(RefCell::new(Some(promise)));
757 let script_thread_sender = self.global_init.to_script_thread_sender.clone();
758 let rooted_global = DomRoot::from_ref(global);
759 let script_url = ensure_blob_referenced_by_url_is_kept_alive(global, script_url);
760
761 fetch_a_module_script_graph(
768 cx,
769 global,
770 script_url,
771 request_client,
772 Destination::PaintWorklet,
773 global.get_referrer(),
774 credentials.convert(),
775 Some(IntroductionType::WORKLET),
776 move |cx, module_tree| {
777 match module_tree {
778 None => {
780 debug!("Failed to load script.");
781
782 reject_promise(
783 &pending_tasks_struct,
784 promise_task.borrow_mut(),
785 script_thread_sender.clone(),
786 );
787 },
788 Some(script) => {
789 let mut realm = enter_auto_realm(cx, &*rooted_global);
790 let cx = &mut realm.current_realm();
791
792 if script.get_rethrow_error().take().is_some() {
797 reject_promise(
799 &pending_tasks_struct,
800 promise_task.borrow_mut(),
801 script_thread_sender.clone(),
802 );
803
804 return;
806 }
807
808 rooted_global.run_a_module_script(cx, script, false);
810
811 let old_counter = pending_tasks_struct.decrement_counter_by(1);
815 if old_counter == 1 {
817 debug!("Resolving promise.");
818
819 let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id);
820 script_thread_sender
821 .send(msg)
822 .expect("Worklet thread outlived script thread.");
823
824 let task = promise_task
825 .borrow_mut()
826 .take()
827 .expect("promise_task must be consumed exactly once")
828 .resolve_task(());
829
830 let msg = CommonScriptMsg::Task(
831 ScriptThreadEventCategory::WorkletEvent,
832 Box::new(task),
833 None,
834 TaskSourceName::Networking,
835 );
836
837 let msg = MainThreadScriptMsg::Common(msg);
839 script_thread_sender
840 .send(msg)
841 .expect("Worklet thread outlived script thread.");
842 }
843 },
844 }
845 },
846 );
847 }
848
849 fn perform_a_worklet_task(&self, cx: &mut JSContext, worklet_id: WorkletId, task: WorkletTask) {
851 match self.global_scopes.get(&worklet_id) {
852 Some(global) => global.perform_a_worklet_task(cx, task),
853 None => warn!("No such worklet as {:?}.", worklet_id),
854 }
855 }
856
857 fn process_control(&mut self, control: WorkletControl, cx: &mut js::context::JSContext) {
859 match control {
860 WorkletControl::ExitWorklet(worklet_id) => {
861 self.global_scopes.remove(&worklet_id);
862 },
863 WorkletControl::FetchAndInvokeAWorkletScript {
864 pipeline_id,
865 worklet_id,
866 global_type,
867 origin,
868 base_url,
869 script_url,
870 policy_container,
871 credentials,
872 pending_tasks_struct,
873 promise,
874 inherited_secure_context,
875 } => {
876 let global = self.get_worklet_global_scope(
880 cx,
881 pipeline_id,
882 worklet_id,
883 inherited_secure_context,
884 global_type,
885 base_url,
886 self.runtime.microtask_queue.clone(),
887 );
888 self.fetch_and_invoke_a_worklet_script(
889 &global,
890 pipeline_id,
891 origin,
892 script_url,
893 policy_container,
894 credentials,
895 pending_tasks_struct,
896 promise,
897 cx,
898 )
899 },
900 WorkletControl::Common(script_msg) => {
901 if let CommonScriptMsg::Task(_, task, _, _) = script_msg {
902 task.run_box(cx);
903 }
904 },
905 }
906 }
907}
908
909pub(crate) fn reject_promise(
912 pending_tasks_struct: &PendingTasksStruct,
913 mut promise_task: RefMut<'_, Option<TrustedPromise>>,
914 script_thread_sender: Sender<MainThreadScriptMsg>,
915) {
916 let old_counter = pending_tasks_struct.set_counter_to(-1);
918
919 if old_counter > 0 {
921 let task = promise_task
923 .take()
924 .expect("promise_task must be consumed exactly once")
925 .reject_task(Error::Abort(None));
926
927 let msg = CommonScriptMsg::Task(
928 ScriptThreadEventCategory::WorkletEvent,
929 Box::new(task),
930 None,
931 TaskSourceName::Networking,
932 );
933
934 let msg = MainThreadScriptMsg::Common(msg);
936 script_thread_sender
937 .send(msg)
938 .expect("Worklet thread outlived script thread.");
939 }
940}
941
942#[derive(Clone, JSTraceable, MallocSizeOf)]
944pub(crate) struct WorkletExecutor {
945 worklet_id: WorkletId,
946 #[no_trace]
947 primary_sender: Sender<WorkletData>,
948 #[no_trace]
949 hot_backup_sender: Sender<WorkletData>,
950 #[no_trace]
951 cold_backup_sender: Sender<WorkletData>,
952 #[no_trace]
953 control_sender: Sender<WorkletControl>,
954}
955
956impl WorkletExecutor {
957 pub(crate) fn wake_threads(&self) -> Result<(), SendError<()>> {
959 self.cold_backup_sender
960 .send(WorkletData::WakeUp)
961 .map_err(|_| SendError(()))?;
962 self.hot_backup_sender
963 .send(WorkletData::WakeUp)
964 .map_err(|_| SendError(()))?;
965 self.primary_sender
966 .send(WorkletData::WakeUp)
967 .map_err(|_| SendError(()))
968 }
969
970 pub(crate) fn schedule_a_worklet_task(&self, task: WorkletTask) {
972 let _ = self
973 .primary_sender
974 .send(WorkletData::Task(self.worklet_id, task));
975 }
976
977 pub(crate) fn send_control_message(
978 &self,
979 control_message: WorkletControl,
980 ) -> Result<(), SendError<()>> {
981 self.control_sender
982 .send(control_message)
983 .map_err(|_| SendError(()))?;
984 self.wake_threads()
985 }
986
987 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
988 ScriptEventLoopSender::Worklet(self.clone())
989 }
990}