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::modules::script_module::fetch_a_module_script_graph;
59use crate::realms::enter_auto_realm;
60use crate::runtime::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<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_rooted(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: 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: 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: PolicyContainer,
475 credentials: RequestCredentials,
476 pending_tasks_struct: PendingTasksStruct,
477 promise: TrustedPromise,
478 inherited_secure_context: Option<bool>,
479 },
480 Common(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: 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 };
780
781 let promise_task = Rc::new(RefCell::new(Some(promise)));
788 let script_thread_sender = self.global_init.to_script_thread_sender.clone();
789 let rooted_global = DomRoot::from_ref(global);
790 let script_url = ensure_blob_referenced_by_url_is_kept_alive(global, script_url);
791
792 fetch_a_module_script_graph(
799 cx,
800 global,
801 script_url,
802 request_client,
803 Destination::PaintWorklet,
804 global.get_referrer(),
805 credentials.convert(),
806 Some(IntroductionType::WORKLET),
807 move |cx, module_tree| {
808 match module_tree {
809 None => {
811 debug!("Failed to load script.");
812
813 reject_promise(
814 &pending_tasks_struct,
815 promise_task.borrow_mut(),
816 script_thread_sender.clone(),
817 );
818 },
819 Some(script) => {
820 let mut realm = enter_auto_realm(cx, &*rooted_global);
821 let cx = &mut realm.current_realm();
822
823 if script.get_rethrow_error().take().is_some() {
828 reject_promise(
830 &pending_tasks_struct,
831 promise_task.borrow_mut(),
832 script_thread_sender.clone(),
833 );
834
835 return;
837 }
838
839 rooted_global.run_a_module_script(cx, script, false);
841
842 let old_counter = pending_tasks_struct.decrement_counter_by(1);
846 if old_counter == 1 {
848 debug!("Resolving promise.");
849
850 let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id);
851 script_thread_sender
852 .send(msg)
853 .expect("Worklet thread outlived script thread.");
854
855 let task = promise_task
856 .borrow_mut()
857 .take()
858 .expect("promise_task must be consumed exactly once")
859 .resolve_task(());
860
861 let msg = CommonScriptMsg::Task(
862 ScriptThreadEventCategory::WorkletEvent,
863 Box::new(task),
864 None,
865 TaskSourceName::Networking,
866 );
867
868 let msg = MainThreadScriptMsg::Common(msg);
870 script_thread_sender
871 .send(msg)
872 .expect("Worklet thread outlived script thread.");
873 }
874 },
875 }
876 },
877 );
878 }
879
880 fn perform_a_worklet_task(
882 &self,
883 cx: &mut JSContext,
884 worklet_id: WorkletId,
885 worklet_task: WorkletTask,
886 ) {
887 match self.global_scopes.get(&worklet_id) {
888 Some(global) => worklet_task(cx, global),
889 None => warn!("No such worklet as {:?}.", worklet_id),
890 }
891 }
892
893 fn process_control(&mut self, control: WorkletControl, cx: &mut js::context::JSContext) {
895 match control {
896 WorkletControl::ExitWorklet(worklet_id) => {
897 self.global_scopes.remove(&worklet_id);
898 },
899 WorkletControl::FetchAndInvokeAWorkletScript {
900 pipeline_id,
901 worklet_id,
902 global_type,
903 origin,
904 base_url,
905 script_url,
906 policy_container,
907 credentials,
908 pending_tasks_struct,
909 promise,
910 inherited_secure_context,
911 } => {
912 let global = self.get_worklet_global_scope(
916 cx,
917 pipeline_id,
918 worklet_id,
919 inherited_secure_context,
920 global_type,
921 base_url,
922 );
923 self.fetch_and_invoke_a_worklet_script(
924 &global,
925 pipeline_id,
926 origin,
927 script_url,
928 policy_container,
929 credentials,
930 pending_tasks_struct,
931 promise,
932 cx,
933 )
934 },
935 WorkletControl::Common(script_msg) => {
936 if let CommonScriptMsg::Task(_, task, _, _) = script_msg {
937 task.run_box(cx);
938 }
939 },
940 }
941 }
942}
943
944pub(crate) fn reject_promise(
947 pending_tasks_struct: &PendingTasksStruct,
948 mut promise_task: RefMut<'_, Option<TrustedPromise>>,
949 script_thread_sender: Sender<MainThreadScriptMsg>,
950) {
951 let old_counter = pending_tasks_struct.set_counter_to(-1);
953
954 if old_counter > 0 {
956 let task = promise_task
958 .take()
959 .expect("promise_task must be consumed exactly once")
960 .reject_task(Error::Abort(None));
961
962 let msg = CommonScriptMsg::Task(
963 ScriptThreadEventCategory::WorkletEvent,
964 Box::new(task),
965 None,
966 TaskSourceName::Networking,
967 );
968
969 let msg = MainThreadScriptMsg::Common(msg);
971 script_thread_sender
972 .send(msg)
973 .expect("Worklet thread outlived script thread.");
974 }
975}
976
977#[derive(Clone, JSTraceable, MallocSizeOf)]
979pub(crate) struct WorkletExecutor {
980 worklet_id: WorkletId,
981 #[no_trace]
982 primary_sender: Sender<WorkletData>,
983 #[no_trace]
984 hot_backup_sender: Sender<WorkletData>,
985 #[no_trace]
986 cold_backup_sender: Sender<WorkletData>,
987 #[no_trace]
988 control_sender: Sender<WorkletControl>,
989}
990
991impl WorkletExecutor {
992 pub(crate) fn wake_threads(&self) -> Result<(), SendError<()>> {
994 self.cold_backup_sender
995 .send(WorkletData::WakeUp)
996 .map_err(|_| SendError(()))?;
997 self.hot_backup_sender
998 .send(WorkletData::WakeUp)
999 .map_err(|_| SendError(()))?;
1000 self.primary_sender
1001 .send(WorkletData::WakeUp)
1002 .map_err(|_| SendError(()))
1003 }
1004
1005 pub(crate) fn schedule_a_worklet_task(&self, task: WorkletTask) {
1007 let _ = self
1008 .primary_sender
1009 .send(WorkletData::Task(self.worklet_id, task));
1010 }
1011
1012 pub(crate) fn send_control_message(
1013 &self,
1014 control_message: WorkletControl,
1015 ) -> Result<(), SendError<()>> {
1016 self.control_sender
1017 .send(control_message)
1018 .map_err(|_| SendError(()))?;
1019 self.wake_threads()
1020 }
1021
1022 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
1023 ScriptEventLoopSender::Worklet(self.clone())
1024 }
1025}