1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
14
15use std::cell::{self, Cell, RefCell, RefMut};
16use std::cmp::max;
17use std::collections::hash_map;
18use std::rc::Rc;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
21use std::thread;
22
23use crossbeam_channel::{Receiver, SendError, Sender, unbounded};
24use dom_struct::dom_struct;
25use js::context::JSContext;
26use js::jsapi::{GCReason, JSGCParamKey, JSTracer};
27use js::realm::CurrentRealm;
28use js::rust::wrappers2::{JS_GC, JS_GetGCParameter};
29use malloc_size_of::malloc_size_of_is_0;
30use net_traits::policy_container::PolicyContainer;
31use net_traits::request::{Destination, Origin, PreloadedResources, RequestClient};
32use rustc_hash::FxHashMap;
33use script_bindings::reflector::{Reflector, reflect_dom_object};
34use servo_base::id::PipelineId;
35use servo_url::{ImmutableOrigin, ServoUrl};
36use style::thread_state::{self, ThreadState};
37use swapper::{Swapper, swapper};
38use uuid::Uuid;
39
40use crate::conversions::Convert;
41use crate::dom::RootedPromise;
42use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials;
43use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
44use crate::dom::bindings::codegen::Bindings::WorkletBinding::{WorkletMethods, WorkletOptions};
45use crate::dom::bindings::error::Error;
46use crate::dom::bindings::inheritance::Castable;
47use crate::dom::bindings::refcounted::TrustedPromise;
48use crate::dom::bindings::root::{Dom, DomRoot};
49use crate::dom::bindings::str::USVString;
50use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox};
51use crate::dom::globalscope::GlobalScope;
52use crate::dom::promise::Promise;
53use crate::dom::window::Window;
54use crate::dom::workletglobalscope::{
55 WorkletGlobalScope, WorkletGlobalScopeInit, WorkletGlobalScopeType,
56};
57use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg, ScriptEventLoopSender};
58use crate::realms::enter_auto_realm;
59use crate::runtime::script_runtime::{IntroductionType, Runtime, ScriptThreadEventCategory};
60use crate::scripting::script_module::fetch_a_module_script_graph;
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<dyn 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<dyn 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<dyn WorkletThreadPool>>,
127 ) -> DomRoot<Worklet> {
128 debug!("Creating worklet {:?}.", global_type);
129 reflect_dom_object(
130 cx,
131 Box::new(Worklet::new_inherited(
132 window,
133 global_type,
134 thread_pool_constructor,
135 )),
136 window,
137 )
138 }
139
140 pub(crate) fn worklet_thread_pool(&self) -> Rc<dyn WorkletThreadPool> {
141 self.droppable_field.is_thread_pool_initialized.set(true);
142 self.droppable_field.thread_pool.clone()
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 ) -> RootedPromise {
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
250pub trait WorkletThreadPool: JSTraceable {
251 #[allow(clippy::too_many_arguments)]
257 fn fetch_and_invoke_a_worklet_script(
258 &self,
259 pipeline_id: PipelineId,
260 worklet_id: WorkletId,
261 global_type: WorkletGlobalScopeType,
262 origin: ImmutableOrigin,
263 base_url: ServoUrl,
264 script_url: ServoUrl,
265 policy_container: Arc<PolicyContainer>,
266 credentials: RequestCredentials,
267 pending_tasks_struct: PendingTasksStruct,
268 promise: &RootedPromise,
269 inherited_secure_context: Option<bool>,
270 );
271 fn exit_worklet(&self, worklet_id: WorkletId);
274 fn wake_threads(&self);
277 fn perform_a_worklet_task(&self, worklet_id: WorkletId, worklet_task: WorkletTask);
281}
282
283#[derive(Clone, JSTraceable)]
336pub(crate) struct StatelessWorkletThreadPool {
337 #[no_trace]
339 primary_sender: Sender<WorkletData>,
340 #[no_trace]
341 hot_backup_sender: Sender<WorkletData>,
342 #[no_trace]
343 cold_backup_sender: Sender<WorkletData>,
344 #[no_trace]
346 control_sender_0: Sender<WorkletControl>,
347 #[no_trace]
348 control_sender_1: Sender<WorkletControl>,
349 #[no_trace]
350 control_sender_2: Sender<WorkletControl>,
351}
352
353impl Drop for StatelessWorkletThreadPool {
354 fn drop(&mut self) {
355 let _ = self.cold_backup_sender.send(WorkletData::Quit);
356 let _ = self.hot_backup_sender.send(WorkletData::Quit);
357 let _ = self.primary_sender.send(WorkletData::Quit);
358 }
359}
360
361impl StatelessWorkletThreadPool {
362 pub(crate) fn spawn(global_init: WorkletGlobalScopeInit) -> StatelessWorkletThreadPool {
365 let primary_role = WorkletThreadRole::new(false, false);
366 let hot_backup_role = WorkletThreadRole::new(true, false);
367 let cold_backup_role = WorkletThreadRole::new(false, true);
368 let primary_sender = primary_role.sender.clone();
369 let hot_backup_sender = hot_backup_role.sender.clone();
370 let cold_backup_sender = cold_backup_role.sender.clone();
371 let init = WorkletThreadInit {
372 primary_sender: primary_sender.clone(),
373 hot_backup_sender: hot_backup_sender.clone(),
374 cold_backup_sender: cold_backup_sender.clone(),
375 global_init,
376 };
377 StatelessWorkletThreadPool {
378 primary_sender,
379 hot_backup_sender,
380 cold_backup_sender,
381 control_sender_0: WorkletThread::spawn(primary_role, init.clone(), 0),
382 control_sender_1: WorkletThread::spawn(hot_backup_role, init.clone(), 1),
383 control_sender_2: WorkletThread::spawn(cold_backup_role, init, 2),
384 }
385 }
386}
387
388impl WorkletThreadPool for StatelessWorkletThreadPool {
389 #[allow(clippy::too_many_arguments)]
390 fn fetch_and_invoke_a_worklet_script(
391 &self,
392 pipeline_id: PipelineId,
393 worklet_id: WorkletId,
394 global_type: WorkletGlobalScopeType,
395 origin: ImmutableOrigin,
396 base_url: ServoUrl,
397 script_url: ServoUrl,
398 policy_container: Arc<PolicyContainer>,
399 credentials: RequestCredentials,
400 pending_tasks_struct: PendingTasksStruct,
401 promise: &RootedPromise,
402 inherited_secure_context: Option<bool>,
403 ) {
404 for sender in &[
406 &self.control_sender_0,
407 &self.control_sender_1,
408 &self.control_sender_2,
409 ] {
410 let _ = sender.send(WorkletControl::FetchAndInvokeAWorkletScript {
411 pipeline_id,
412 worklet_id,
413 global_type,
414 origin: origin.clone(),
415 base_url: base_url.clone(),
416 script_url: script_url.clone(),
417 policy_container: policy_container.clone(),
418 credentials,
419 pending_tasks_struct: pending_tasks_struct.clone(),
420 promise: TrustedPromise::from(promise),
421 inherited_secure_context,
422 });
423 }
424 self.wake_threads();
425 }
426
427 fn exit_worklet(&self, worklet_id: WorkletId) {
428 for sender in &[
429 &self.control_sender_0,
430 &self.control_sender_1,
431 &self.control_sender_2,
432 ] {
433 let _ = sender.send(WorkletControl::ExitWorklet(worklet_id));
434 }
435 self.wake_threads();
436 }
437
438 fn wake_threads(&self) {
439 let _ = self.cold_backup_sender.send(WorkletData::WakeUp);
441 let _ = self.hot_backup_sender.send(WorkletData::WakeUp);
442 let _ = self.primary_sender.send(WorkletData::WakeUp);
443 }
444
445 fn perform_a_worklet_task(&self, worklet_id: WorkletId, worklet_task: WorkletTask) {
447 let msg = WorkletData::Task(worklet_id, worklet_task);
448 let _ = self.primary_sender.send(msg);
449 }
450}
451
452type WorkletTask = Box<dyn FnOnce(&mut JSContext, &WorkletGlobalScope) + Send>;
454
455enum WorkletData {
457 Task(WorkletId, WorkletTask),
458 StartSwapRoles(Sender<WorkletData>),
459 FinishSwapRoles(Swapper<WorkletThreadRole>),
460 WakeUp,
461 Quit,
462}
463
464pub(crate) enum WorkletControl {
466 ExitWorklet(WorkletId),
467 FetchAndInvokeAWorkletScript {
468 pipeline_id: PipelineId,
469 worklet_id: WorkletId,
470 global_type: WorkletGlobalScopeType,
471 origin: ImmutableOrigin,
472 base_url: ServoUrl,
473 script_url: ServoUrl,
474 policy_container: Arc<PolicyContainer>,
475 credentials: RequestCredentials,
476 pending_tasks_struct: PendingTasksStruct,
477 promise: TrustedPromise,
478 inherited_secure_context: Option<bool>,
479 },
480 Common(WorkletId, CommonScriptMsg),
481}
482
483struct WorkletThreadRole {
490 receiver: Receiver<WorkletData>,
491 sender: Sender<WorkletData>,
492 is_hot_backup: bool,
493 is_cold_backup: bool,
494}
495
496impl WorkletThreadRole {
497 fn new(is_hot_backup: bool, is_cold_backup: bool) -> WorkletThreadRole {
498 let (sender, receiver) = unbounded();
499 WorkletThreadRole {
500 sender,
501 receiver,
502 is_hot_backup,
503 is_cold_backup,
504 }
505 }
506}
507
508#[derive(Clone)]
510struct WorkletThreadInit {
511 primary_sender: Sender<WorkletData>,
513 hot_backup_sender: Sender<WorkletData>,
514 cold_backup_sender: Sender<WorkletData>,
515
516 global_init: WorkletGlobalScopeInit,
518}
519
520#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
522struct WorkletThread {
523 role: WorkletThreadRole,
525
526 control_receiver: Receiver<WorkletControl>,
528 control_sender: Sender<WorkletControl>,
530
531 primary_sender: Sender<WorkletData>,
533 hot_backup_sender: Sender<WorkletData>,
534 cold_backup_sender: Sender<WorkletData>,
535
536 global_init: WorkletGlobalScopeInit,
538
539 global_scopes: FxHashMap<WorkletId, Dom<WorkletGlobalScope>>,
541
542 control_buffer: Option<WorkletControl>,
544
545 closing: Arc<AtomicBool>,
547
548 runtime: Runtime,
550 should_gc: bool,
551 gc_threshold: u32,
552}
553
554#[expect(unsafe_code)]
555unsafe impl JSTraceable for WorkletThread {
556 unsafe fn trace(&self, trc: *mut JSTracer) {
557 debug!("Tracing worklet thread.");
558 unsafe { self.global_scopes.trace(trc) };
559 }
560}
561
562impl WorkletThread {
563 #[allow(unsafe_code)]
564 fn spawn(
566 role: WorkletThreadRole,
567 init: WorkletThreadInit,
568 thread_index: u8,
569 ) -> Sender<WorkletControl> {
570 let (control_sender, control_receiver) = unbounded();
571 let control_sender_clone = control_sender.clone();
572 let _ = thread::Builder::new()
573 .name(format!("Worklet#{thread_index}"))
574 .spawn(move || {
575 debug!("Initializing worklet thread.");
579 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
580 let runtime = Runtime::new(None);
581 let mut cx = unsafe { runtime.cx() };
582 let mut thread = RootedTraceableBox::new(WorkletThread {
583 role,
584 control_receiver,
585 control_sender: control_sender_clone,
586 primary_sender: init.primary_sender,
587 hot_backup_sender: init.hot_backup_sender,
588 cold_backup_sender: init.cold_backup_sender,
589 global_init: init.global_init,
590 global_scopes: FxHashMap::default(),
591 control_buffer: None,
592 runtime,
593 should_gc: false,
594 closing: Arc::new(AtomicBool::new(false)),
595 gc_threshold: MIN_GC_THRESHOLD,
596 });
597 thread.run(&mut cx);
598 })
599 .expect("Couldn't start worklet thread");
600 control_sender
601 }
602
603 fn run(&mut self, cx: &mut JSContext) {
605 loop {
606 let message = self.role.receiver.recv().unwrap();
608 match message {
609 WorkletData::Task(id, task) => {
611 self.perform_a_worklet_task(cx, id, task);
612 },
613 WorkletData::StartSwapRoles(sender) => {
620 let (our_swapper, their_swapper) = swapper();
621 match sender.send(WorkletData::FinishSwapRoles(their_swapper)) {
622 Ok(_) => {},
623 Err(_) => {
624 return;
627 },
628 };
629 let _ = our_swapper.swap(&mut self.role);
630 },
631 WorkletData::FinishSwapRoles(swapper) => {
634 let _ = swapper.swap(&mut self.role);
635 },
636 WorkletData::WakeUp => {},
638 WorkletData::Quit => {
640 return;
641 },
642 }
643
644 if self.role.is_cold_backup {
648 if let Some(control) = self.control_buffer.take() {
649 self.process_control(control, cx);
650 }
651 while let Ok(control) = self.control_receiver.try_recv() {
652 self.process_control(control, cx);
653 }
654
655 for worklet_global_scope in self.global_scopes.values() {
656 worklet_global_scope.perform_a_microtask_checkpoint(cx);
657 }
658
659 self.gc(cx);
660 } else if self.control_buffer.is_none() &&
661 let Ok(control) = self.control_receiver.try_recv()
662 {
663 self.control_buffer = Some(control);
664 let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
665 let _ = self.cold_backup_sender.send(msg);
666 }
667 if self.current_memory_usage() > self.gc_threshold {
671 if self.role.is_hot_backup || self.role.is_cold_backup {
672 self.should_gc = false;
673 self.gc(cx);
674 } else if !self.should_gc {
675 self.should_gc = true;
676 let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
677 let _ = self.hot_backup_sender.send(msg);
678 }
679 }
680 }
681 }
682
683 #[expect(unsafe_code)]
685 fn current_memory_usage(&self) -> u32 {
686 unsafe { JS_GetGCParameter(self.runtime.cx_no_gc(), JSGCParamKey::JSGC_BYTES) }
687 }
688
689 #[expect(unsafe_code)]
691 fn gc(&mut self, cx: &mut JSContext) {
692 debug!(
693 "BEGIN GC (usage = {}, threshold = {}).",
694 self.current_memory_usage(),
695 self.gc_threshold
696 );
697 unsafe { JS_GC(cx, GCReason::API) };
698 self.gc_threshold = max(MIN_GC_THRESHOLD, self.current_memory_usage() * 2);
699 debug!(
700 "END GC (usage = {}, threshold = {}).",
701 self.current_memory_usage(),
702 self.gc_threshold
703 );
704 }
705
706 fn get_worklet_global_scope(
709 &mut self,
710 cx: &mut JSContext,
711 pipeline_id: PipelineId,
712 worklet_id: WorkletId,
713 inherited_secure_context: Option<bool>,
714 global_type: WorkletGlobalScopeType,
715 base_url: ServoUrl,
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 );
743 entry.insert(Dom::from_ref(&*result));
744 result
745 },
746 }
747 }
748
749 #[allow(clippy::too_many_arguments)]
752 fn fetch_and_invoke_a_worklet_script(
753 &self,
754 global_scope: &WorkletGlobalScope,
755 pipeline_id: PipelineId,
756 origin: ImmutableOrigin,
757 script_url: ServoUrl,
758 policy_container: Arc<PolicyContainer>,
759 credentials: RequestCredentials,
760 pending_tasks_struct: PendingTasksStruct,
761 promise: TrustedPromise,
762 cx: &mut JSContext,
763 ) {
764 debug!("Fetching from {}.", script_url);
765 let global = global_scope.upcast::<GlobalScope>();
770
771 let request_client = RequestClient {
773 preloaded_resources: PreloadedResources::default(),
774 policy_container,
775 origin: Origin::Origin(origin),
776 is_nested_browsing_context: global.is_nested_browsing_context(),
777 insecure_requests_policy: global.insecure_requests_policy(),
778 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_origin(),
779 network_partition_key: None,
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.has_rethrow_error() {
829 reject_promise(
831 &pending_tasks_struct,
832 promise_task.borrow_mut(),
833 script_thread_sender.clone(),
834 );
835
836 return;
838 }
839
840 let module = RootedTraceableBox::new(script);
842 rooted_global.run_a_module_script(cx, module, false);
843
844 let old_counter = pending_tasks_struct.decrement_counter_by(1);
848 if old_counter == 1 {
850 debug!("Resolving promise.");
851
852 let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id);
853 script_thread_sender
854 .send(msg)
855 .expect("Worklet thread outlived script thread.");
856
857 let task = promise_task
858 .borrow_mut()
859 .take()
860 .expect("promise_task must be consumed exactly once")
861 .resolve_task(());
862
863 let msg = CommonScriptMsg::Task(
864 ScriptThreadEventCategory::WorkletEvent,
865 Box::new(task),
866 None,
867 TaskSourceName::Networking,
868 );
869
870 let msg = MainThreadScriptMsg::Common(msg);
872 script_thread_sender
873 .send(msg)
874 .expect("Worklet thread outlived script thread.");
875 }
876 },
877 }
878 },
879 );
880 }
881
882 fn perform_a_worklet_task(
884 &self,
885 cx: &mut JSContext,
886 worklet_id: WorkletId,
887 worklet_task: WorkletTask,
888 ) {
889 match self.global_scopes.get(&worklet_id) {
890 Some(global) => worklet_task(cx, global),
891 None => warn!("No such worklet as {:?}.", worklet_id),
892 }
893 }
894
895 fn process_control(&mut self, control: WorkletControl, cx: &mut js::context::JSContext) {
897 match control {
898 WorkletControl::ExitWorklet(worklet_id) => {
899 if let Some(global_scope) = self
900 .global_scopes
901 .remove(&worklet_id)
902 .map(|global_scope| global_scope.as_rooted())
903 {
904 global_scope.upcast::<GlobalScope>().tear_down();
905 }
906 },
907 WorkletControl::FetchAndInvokeAWorkletScript {
908 pipeline_id,
909 worklet_id,
910 global_type,
911 origin,
912 base_url,
913 script_url,
914 policy_container,
915 credentials,
916 pending_tasks_struct,
917 promise,
918 inherited_secure_context,
919 } => {
920 let global = self.get_worklet_global_scope(
924 cx,
925 pipeline_id,
926 worklet_id,
927 inherited_secure_context,
928 global_type,
929 base_url,
930 );
931 self.fetch_and_invoke_a_worklet_script(
932 &global,
933 pipeline_id,
934 origin,
935 script_url,
936 policy_container,
937 credentials,
938 pending_tasks_struct,
939 promise,
940 cx,
941 )
942 },
943 WorkletControl::Common(worklet_id, script_msg) => {
944 if let CommonScriptMsg::Task(_, task, _, _) = script_msg &&
945 let Some(global) = self.global_scopes.get(&worklet_id)
946 {
947 let mut realm = enter_auto_realm(cx, global.upcast::<GlobalScope>());
948 let cx = &mut realm;
949 task.run_box(cx);
950 }
951 },
952 }
953 }
954}
955
956pub(crate) fn reject_promise(
959 pending_tasks_struct: &PendingTasksStruct,
960 mut promise_task: RefMut<'_, Option<TrustedPromise>>,
961 script_thread_sender: Sender<MainThreadScriptMsg>,
962) {
963 let old_counter = pending_tasks_struct.set_counter_to(-1);
965
966 if old_counter > 0 {
968 let task = promise_task
970 .take()
971 .expect("promise_task must be consumed exactly once")
972 .reject_task(Error::Abort(None));
973
974 let msg = CommonScriptMsg::Task(
975 ScriptThreadEventCategory::WorkletEvent,
976 Box::new(task),
977 None,
978 TaskSourceName::Networking,
979 );
980
981 let msg = MainThreadScriptMsg::Common(msg);
983 script_thread_sender
984 .send(msg)
985 .expect("Worklet thread outlived script thread.");
986 }
987}
988
989#[derive(Clone, JSTraceable, MallocSizeOf)]
991pub(crate) struct WorkletExecutor {
992 worklet_id: WorkletId,
993 #[no_trace]
994 primary_sender: Sender<WorkletData>,
995 #[no_trace]
996 hot_backup_sender: Sender<WorkletData>,
997 #[no_trace]
998 cold_backup_sender: Sender<WorkletData>,
999 #[no_trace]
1000 control_sender: Sender<WorkletControl>,
1001}
1002
1003impl WorkletExecutor {
1004 pub(crate) fn wake_threads(&self) -> Result<(), SendError<()>> {
1006 self.cold_backup_sender
1007 .send(WorkletData::WakeUp)
1008 .map_err(|_| SendError(()))?;
1009 self.hot_backup_sender
1010 .send(WorkletData::WakeUp)
1011 .map_err(|_| SendError(()))?;
1012 self.primary_sender
1013 .send(WorkletData::WakeUp)
1014 .map_err(|_| SendError(()))
1015 }
1016
1017 pub(crate) fn schedule_a_worklet_task(&self, task: WorkletTask) {
1019 let _ = self
1020 .primary_sender
1021 .send(WorkletData::Task(self.worklet_id, task));
1022 }
1023
1024 pub(crate) fn send_common_message(
1026 &self,
1027 common_message: CommonScriptMsg,
1028 ) -> Result<(), SendError<()>> {
1029 self.control_sender
1030 .send(WorkletControl::Common(self.worklet_id, common_message))
1031 .map_err(|_| SendError(()))?;
1032 self.wake_threads()
1033 }
1034
1035 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
1036 ScriptEventLoopSender::Worklet(self.clone())
1037 }
1038}