bao-servo-script 0.5.1

A component of the servo web-engine.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! An implementation of Houdini worklets.
//!
//! The goal of this implementation is to maximize responsiveness of worklets,
//! and in particular to ensure that the thread performing worklet tasks
//! is never busy GCing or loading worklet code. We do this by providing a custom
//! thread pool implementation, which only performs GC or code loading on
//! a backup thread, not on the primary worklet thread.

use std::cell::{self, Cell, RefCell, RefMut};
use std::cmp::max;
use std::collections::hash_map;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
use std::thread;

use crossbeam_channel::{Receiver, SendError, Sender, unbounded};
use dom_struct::dom_struct;
use js::context::JSContext;
use js::jsapi::{GCReason, JSGCParamKey, JSTracer};
use js::realm::CurrentRealm;
use js::rust::wrappers2::{JS_GC, JS_GetGCParameter};
use malloc_size_of::malloc_size_of_is_0;
use net_traits::policy_container::PolicyContainer;
use net_traits::request::{Destination, Origin, PreloadedResources, RequestClient};
use rustc_hash::FxHashMap;
use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
use servo_base::id::PipelineId;
use servo_url::{ImmutableOrigin, ServoUrl};
use style::thread_state::{self, ThreadState};
use swapper::{Swapper, swapper};
use uuid::Uuid;

use crate::conversions::Convert;
use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCredentials;
use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::WorkletBinding::{WorkletMethods, WorkletOptions};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::TrustedPromise;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::USVString;
use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
#[cfg(feature = "testbinding")]
use crate::dom::testworkletglobalscope::TestWorkletTask;
use crate::dom::window::Window;
use crate::dom::workletglobalscope::{
    WorkletGlobalScope, WorkletGlobalScopeInit, WorkletGlobalScopeType, WorkletTask,
};
use crate::messaging::{CommonScriptMsg, MainThreadScriptMsg, ScriptEventLoopSender};
use crate::microtask::MicrotaskQueue;
use crate::modules::script_module::fetch_a_module_script_graph;
use crate::realms::enter_auto_realm;
use crate::script_runtime::{IntroductionType, Runtime, ScriptThreadEventCategory};
use crate::tasks::task_source::TaskSourceName;
use crate::url::ensure_blob_referenced_by_url_is_kept_alive;

// Magic numbers
const WORKLET_THREAD_POOL_SIZE: u32 = 3;
const MIN_GC_THRESHOLD: u32 = 1_000_000;

type LazyCellWithBoxedInitializer<T> = cell::LazyCell<T, Box<dyn FnOnce() -> T>>;

#[derive(JSTraceable, MallocSizeOf)]
struct DroppableField {
    worklet_id: WorkletId,
    /// The cached version of the script thread's WorkletThreadPool. We keep this cached
    /// because we may need to access it after the script thread has terminated.
    /// NOTE: Do not access the `thread_pool` field directly, instead use the
    /// `Worklet::worklet_thread_pool` method to access the Thread Pool.
    #[ignore_malloc_size_of = "Difficult to measure memory usage of Rc<...> types"]
    thread_pool: LazyCellWithBoxedInitializer<Rc<WorkletThreadPool>>,

    /// NOTE: The `is_thread_pool_initialized` field is a temporary workaround because
    /// using the `LazyCell::get()` method requires Rust version >1.94.0 and is not
    /// supported by the current MSRV (Minimum Supported Rust Version).
    is_thread_pool_initialized: Cell<bool>,
}

impl Drop for DroppableField {
    fn drop(&mut self) {
        let worklet_id = self.worklet_id;
        if self.is_thread_pool_initialized.get() {
            self.thread_pool.exit_worklet(worklet_id);
        }
    }
}

#[dom_struct]
/// <https://drafts.css-houdini.org/worklets/#worklet>
pub(crate) struct Worklet {
    reflector: Reflector,
    window: Dom<Window>,
    global_type: WorkletGlobalScopeType,
    droppable_field: DroppableField,
}

impl Worklet {
    fn new_inherited(
        window: &Window,
        global_type: WorkletGlobalScopeType,
        thread_pool_constructor: Box<dyn FnOnce() -> Rc<WorkletThreadPool>>,
    ) -> Worklet {
        Worklet {
            reflector: Reflector::new(),
            window: Dom::from_ref(window),
            global_type,
            droppable_field: DroppableField {
                worklet_id: WorkletId::new(),
                thread_pool: LazyCellWithBoxedInitializer::new(thread_pool_constructor),
                is_thread_pool_initialized: Cell::new(false),
            },
        }
    }

    pub(crate) fn new(
        cx: &mut JSContext,
        window: &Window,
        global_type: WorkletGlobalScopeType,
        thread_pool_constructor: Box<dyn FnOnce() -> Rc<WorkletThreadPool>>,
    ) -> DomRoot<Worklet> {
        debug!("Creating worklet {:?}.", global_type);
        reflect_dom_object_with_cx(
            Box::new(Worklet::new_inherited(
                window,
                global_type,
                thread_pool_constructor,
            )),
            window,
            cx,
        )
    }

    pub(crate) fn worklet_thread_pool(&self) -> &WorkletThreadPool {
        self.droppable_field.is_thread_pool_initialized.set(true);
        &self.droppable_field.thread_pool
    }

    #[cfg(feature = "testbinding")]
    pub(crate) fn worklet_id(&self) -> WorkletId {
        self.droppable_field.worklet_id
    }

    #[expect(dead_code)]
    pub(crate) fn worklet_global_scope_type(&self) -> WorkletGlobalScopeType {
        self.global_type
    }
}

impl WorkletMethods<crate::DomTypeHolder> for Worklet {
    /// <https://html.spec.whatwg.org/multipage/#dom-worklet-addmodule>
    fn AddModule(
        &self,
        realm: &mut CurrentRealm,
        module_url: USVString,
        options: &WorkletOptions,
    ) -> Rc<Promise> {
        let promise = Promise::new_in_realm(realm);

        // Step 1. Let outsideSettings be the relevant settings object of this.
        // Step 2. Let moduleURLRecord be the result of encoding-parsing a URL given moduleURL, relative to outsideSettings.
        let module_url_record = match self.window.Document().base_url().join(&module_url.0) {
            Ok(url) => url,
            Err(err) => {
                // Step 3. If moduleURLRecord is failure, then return a promise rejected with a "SyntaxError" DOMException.
                debug!("URL {:?} parse error {:?}.", module_url.0, err);
                promise.reject_error(realm, Error::Syntax(None));

                return promise;
            },
        };
        debug!("Adding Worklet module {}.", module_url_record);

        let global_scope = self.window.as_global_scope();

        let pending_tasks_struct = PendingTasksStruct::new();

        // NOTE: The following steps are split between `WorkletThread::get_worklet_global_scope` and `WorkledThread::fetch_and_invoke_a_worklet_script` methods:
        // Step 5. Let workletInstance be this.
        // Step 6. Run the following steps in parallel:
        // Step 6.1. If workletInstance's global scopes is empty:
        // Step 6.1.1. Create a worklet global scope given workletInstance.
        // Step 6.1.2. Optionally, create additional global scope instances given workletInstance, depending on the specific worklet in question and its specification.
        // Step 6.1.3. Wait for all steps of the creation process(es) — including those taking place within the worklet agents — to complete, before moving on.
        // Step 6.2. Let pendingTasks be workletInstance's global scopes's size.

        // Step 6.3. Let addedSuccessfully be false.
        // NOTE: We skip step 6.3 because we do not implement the `added modules list` yet
        // <https://html.spec.whatwg.org/multipage/#concept-worklet-added-modules-list>

        self.worklet_thread_pool()
            .fetch_and_invoke_a_worklet_script(
                self.window.pipeline_id(),
                self.droppable_field.worklet_id,
                self.global_type,
                self.window.origin().immutable().clone(),
                global_scope.api_base_url(),
                module_url_record,
                global_scope.policy_container(),
                options.credentials,
                pending_tasks_struct,
                &promise,
                global_scope.inherited_secure_context(),
            );

        // Step 7. Return promise
        debug!("Returning promise.");
        promise
    }
}

/// A guid for worklets.
#[derive(Clone, Copy, Debug, Eq, Hash, JSTraceable, PartialEq)]
pub(crate) struct WorkletId(#[no_trace] Uuid);

malloc_size_of_is_0!(WorkletId);

impl WorkletId {
    fn new() -> WorkletId {
        WorkletId(Uuid::new_v4())
    }
}

/// <https://drafts.css-houdini.org/worklets/#pending-tasks-struct>
#[derive(Clone, Debug)]
pub(crate) struct PendingTasksStruct(Arc<AtomicIsize>);

impl PendingTasksStruct {
    fn new() -> PendingTasksStruct {
        PendingTasksStruct(Arc::new(AtomicIsize::new(
            WORKLET_THREAD_POOL_SIZE as isize,
        )))
    }

    fn set_counter_to(&self, value: isize) -> isize {
        self.0.swap(value, Ordering::AcqRel)
    }

    fn decrement_counter_by(&self, offset: isize) -> isize {
        self.0.fetch_sub(offset, Ordering::AcqRel)
    }
}

/// Worklets execute in a dedicated thread pool.
///
/// The goal is to ensure that there is a primary worklet thread,
/// which is able to responsively execute worklet code. In particular,
/// worklet execution should not be delayed by GC, or by script
/// loading.
///
/// To achieve this, we implement a three-thread pool, with the
/// threads cycling between three thread roles:
///
///  * The primary worklet thread is the one available to execute
///    worklet code.
///
///  * The hot backup thread may peform GC, but otherwise is expected
///    to take over the primary role.
///
///  * The cold backup thread may peform script loading and other
///    long-running tasks.
///
/// In the implementation, we use two kinds of messages:
///
///  * Data messages are expected to be processed quickly, and include
///    the worklet tasks to be performed by the primary thread, as
///    well as requests to change role or quit execution.
///
///  * Control messages are expected to be processed more slowly, and
///    include script loading.
///
/// Data messages are targeted at a role, for example, task execution
/// is expected to be performed by whichever thread is currently
/// primary. Control messages are targeted at a thread, for example
/// adding a module is performed in every thread, even if they change roles
/// in the middle of module loading.
///
/// The thread pool lives in the script thread, and is initialized
/// when a worklet adds a module. It is dropped when the script thread
/// is dropped, and asks each of the worklet threads to quit.
///
/// Layout can end up blocking on the primary worklet thread
/// (e.g. when invoking a paint callback), so it is important to avoid
/// deadlock by making sure the primary worklet thread doesn't end up
/// blocking waiting on layout. In particular, since the constellation
/// can block waiting on layout, this means the primary worklet thread
/// can't block waiting on the constellation. In general, the primary
/// worklet thread shouldn't perform any blocking operations. If a worklet
/// thread needs to do anything blocking, it should send a control
/// message, to make sure that the blocking operation is performed
/// by a backup thread, not by the primary thread.

#[derive(Clone, JSTraceable)]
pub(crate) struct WorkletThreadPool {
    // Channels to send data messages to the three roles.
    #[no_trace]
    primary_sender: Sender<WorkletData>,
    #[no_trace]
    hot_backup_sender: Sender<WorkletData>,
    #[no_trace]
    cold_backup_sender: Sender<WorkletData>,
    // Channels to send control messages to the three threads.
    #[no_trace]
    control_sender_0: Sender<WorkletControl>,
    #[no_trace]
    control_sender_1: Sender<WorkletControl>,
    #[no_trace]
    control_sender_2: Sender<WorkletControl>,
}

impl Drop for WorkletThreadPool {
    fn drop(&mut self) {
        let _ = self.cold_backup_sender.send(WorkletData::Quit);
        let _ = self.hot_backup_sender.send(WorkletData::Quit);
        let _ = self.primary_sender.send(WorkletData::Quit);
    }
}

impl WorkletThreadPool {
    /// Create a new thread pool and spawn the threads.
    /// When the thread pool is dropped, the threads will be asked to quit.
    pub(crate) fn spawn(global_init: WorkletGlobalScopeInit) -> WorkletThreadPool {
        let primary_role = WorkletThreadRole::new(false, false);
        let hot_backup_role = WorkletThreadRole::new(true, false);
        let cold_backup_role = WorkletThreadRole::new(false, true);
        let primary_sender = primary_role.sender.clone();
        let hot_backup_sender = hot_backup_role.sender.clone();
        let cold_backup_sender = cold_backup_role.sender.clone();
        let init = WorkletThreadInit {
            primary_sender: primary_sender.clone(),
            hot_backup_sender: hot_backup_sender.clone(),
            cold_backup_sender: cold_backup_sender.clone(),
            global_init,
        };
        WorkletThreadPool {
            primary_sender,
            hot_backup_sender,
            cold_backup_sender,
            control_sender_0: WorkletThread::spawn(primary_role, init.clone(), 0),
            control_sender_1: WorkletThread::spawn(hot_backup_role, init.clone(), 1),
            control_sender_2: WorkletThread::spawn(cold_backup_role, init, 2),
        }
    }

    /// Loads a worklet module into every worklet thread.
    /// If all of the threads load successfully, the promise is resolved.
    /// If any of the threads fails to load, the promise is rejected.
    /// <https://drafts.css-houdini.org/worklets/#fetch-and-invoke-a-worklet-script>
    #[allow(clippy::too_many_arguments)]
    fn fetch_and_invoke_a_worklet_script(
        &self,
        pipeline_id: PipelineId,
        worklet_id: WorkletId,
        global_type: WorkletGlobalScopeType,
        origin: ImmutableOrigin,
        base_url: ServoUrl,
        script_url: ServoUrl,
        policy_container: PolicyContainer,
        credentials: RequestCredentials,
        pending_tasks_struct: PendingTasksStruct,
        promise: &Rc<Promise>,
        inherited_secure_context: Option<bool>,
    ) {
        // Send each thread a control message asking it to load the script.
        for sender in &[
            &self.control_sender_0,
            &self.control_sender_1,
            &self.control_sender_2,
        ] {
            let _ = sender.send(WorkletControl::FetchAndInvokeAWorkletScript {
                pipeline_id,
                worklet_id,
                global_type,
                origin: origin.clone(),
                base_url: base_url.clone(),
                script_url: script_url.clone(),
                policy_container: policy_container.clone(),
                credentials,
                pending_tasks_struct: pending_tasks_struct.clone(),
                promise: TrustedPromise::new(promise.clone()),
                inherited_secure_context,
            });
        }
        self.wake_threads();
    }

    pub(crate) fn exit_worklet(&self, worklet_id: WorkletId) {
        for sender in &[
            &self.control_sender_0,
            &self.control_sender_1,
            &self.control_sender_2,
        ] {
            let _ = sender.send(WorkletControl::ExitWorklet(worklet_id));
        }
        self.wake_threads();
    }

    /// For testing.
    #[cfg(feature = "testbinding")]
    pub(crate) fn test_worklet_lookup(&self, id: WorkletId, key: String) -> Option<String> {
        let (sender, receiver) = unbounded();
        let msg = WorkletData::Task(id, WorkletTask::Test(TestWorkletTask::Lookup(key, sender)));
        let _ = self.primary_sender.send(msg);
        receiver.recv().expect("Test worklet has died?")
    }

    fn wake_threads(&self) {
        // If any of the threads are blocked waiting on data, wake them up.
        let _ = self.cold_backup_sender.send(WorkletData::WakeUp);
        let _ = self.hot_backup_sender.send(WorkletData::WakeUp);
        let _ = self.primary_sender.send(WorkletData::WakeUp);
    }
}

/// The data messages sent to worklet threads
enum WorkletData {
    Task(WorkletId, WorkletTask),
    StartSwapRoles(Sender<WorkletData>),
    FinishSwapRoles(Swapper<WorkletThreadRole>),
    WakeUp,
    Quit,
}

/// The control message sent to worklet threads
pub(crate) enum WorkletControl {
    ExitWorklet(WorkletId),
    FetchAndInvokeAWorkletScript {
        pipeline_id: PipelineId,
        worklet_id: WorkletId,
        global_type: WorkletGlobalScopeType,
        origin: ImmutableOrigin,
        base_url: ServoUrl,
        script_url: ServoUrl,
        policy_container: PolicyContainer,
        credentials: RequestCredentials,
        pending_tasks_struct: PendingTasksStruct,
        promise: TrustedPromise,
        inherited_secure_context: Option<bool>,
    },
    Common(CommonScriptMsg),
}

/// A role that a worklet thread can be playing.
///
/// These roles are used as tokens or capabilities, we track unique
/// ownership using Rust's types, and use atomic swapping to exchange
/// them between worklet threads. This ensures that each thread pool has
/// exactly one primary, one hot backup and one cold backup.
struct WorkletThreadRole {
    receiver: Receiver<WorkletData>,
    sender: Sender<WorkletData>,
    is_hot_backup: bool,
    is_cold_backup: bool,
}

impl WorkletThreadRole {
    fn new(is_hot_backup: bool, is_cold_backup: bool) -> WorkletThreadRole {
        let (sender, receiver) = unbounded();
        WorkletThreadRole {
            sender,
            receiver,
            is_hot_backup,
            is_cold_backup,
        }
    }
}

/// Data to initialize a worklet thread.
#[derive(Clone)]
struct WorkletThreadInit {
    /// Senders
    primary_sender: Sender<WorkletData>,
    hot_backup_sender: Sender<WorkletData>,
    cold_backup_sender: Sender<WorkletData>,

    /// Data for initializing new worklet global scopes
    global_init: WorkletGlobalScopeInit,
}

/// A thread for executing worklets.
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
struct WorkletThread {
    /// Which role the thread is currently playing
    role: WorkletThreadRole,

    /// The thread's receiver for control messages
    control_receiver: Receiver<WorkletControl>,
    /// The sender for sending control messages to this thread's event loop
    control_sender: Sender<WorkletControl>,

    /// Senders
    primary_sender: Sender<WorkletData>,
    hot_backup_sender: Sender<WorkletData>,
    cold_backup_sender: Sender<WorkletData>,

    /// Data for initializing new worklet global scopes
    global_init: WorkletGlobalScopeInit,

    /// The global scopes created by this thread
    global_scopes: FxHashMap<WorkletId, Dom<WorkletGlobalScope>>,

    /// A one-place buffer for control messages
    control_buffer: Option<WorkletControl>,

    /// A flag that is set when a `WorkletThread` begins shutting down.
    closing: Arc<AtomicBool>,

    /// The JS runtime
    runtime: Runtime,
    should_gc: bool,
    gc_threshold: u32,
}

#[expect(unsafe_code)]
unsafe impl JSTraceable for WorkletThread {
    unsafe fn trace(&self, trc: *mut JSTracer) {
        debug!("Tracing worklet thread.");
        unsafe { self.global_scopes.trace(trc) };
    }
}

impl WorkletThread {
    #[allow(unsafe_code)]
    /// Spawn a new worklet thread, returning the channel to send it control messages.
    fn spawn(
        role: WorkletThreadRole,
        init: WorkletThreadInit,
        thread_index: u8,
    ) -> Sender<WorkletControl> {
        let (control_sender, control_receiver) = unbounded();
        let control_sender_clone = control_sender.clone();
        let _ = thread::Builder::new()
            .name(format!("Worklet#{thread_index}"))
            .spawn(move || {
                // TODO: add a new IN_WORKLET thread state?
                // TODO: set interrupt handler?
                // TODO: configure the JS runtime (e.g. discourage GC, encourage agressive JIT)
                debug!("Initializing worklet thread.");
                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
                let runtime = Runtime::new(None);
                let mut cx = unsafe { runtime.cx() };
                let mut thread = RootedTraceableBox::new(WorkletThread {
                    role,
                    control_receiver,
                    control_sender: control_sender_clone,
                    primary_sender: init.primary_sender,
                    hot_backup_sender: init.hot_backup_sender,
                    cold_backup_sender: init.cold_backup_sender,
                    global_init: init.global_init,
                    global_scopes: FxHashMap::default(),
                    control_buffer: None,
                    runtime,
                    should_gc: false,
                    closing: Arc::new(AtomicBool::new(false)),
                    gc_threshold: MIN_GC_THRESHOLD,
                });
                thread.run(&mut cx);
            })
            .expect("Couldn't start worklet thread");
        control_sender
    }

    /// The main event loop for a worklet thread
    fn run(&mut self, cx: &mut JSContext) {
        loop {
            // The handler for data messages
            let message = self.role.receiver.recv().unwrap();
            match message {
                // The whole point of this thread pool is to perform tasks!
                WorkletData::Task(id, task) => {
                    self.perform_a_worklet_task(cx, id, task);
                },
                // To start swapping roles, get ready to perform an atomic swap,
                // and block waiting for the other end to finish it.
                // NOTE: the cold backup can block on the primary or the hot backup;
                //       the hot backup can block on the primary;
                //       the primary can block on nothing;
                //       this total ordering on thread roles is what guarantees deadlock-freedom.
                WorkletData::StartSwapRoles(sender) => {
                    let (our_swapper, their_swapper) = swapper();
                    match sender.send(WorkletData::FinishSwapRoles(their_swapper)) {
                        Ok(_) => {},
                        Err(_) => {
                            // This might happen if the script thread shuts down while
                            // waiting for the worklet to finish.
                            return;
                        },
                    };
                    let _ = our_swapper.swap(&mut self.role);
                },
                // To finish swapping roles, perform the atomic swap.
                // The other end should have already started the swap, so this shouldn't block.
                WorkletData::FinishSwapRoles(swapper) => {
                    let _ = swapper.swap(&mut self.role);
                },
                // Wake up! There may be control messages to process.
                WorkletData::WakeUp => {},
                // Quit!
                WorkletData::Quit => {
                    return;
                },
            }

            // Only process control messages if we're the cold backup,
            // otherwise if there are outstanding control messages,
            // try to become the cold backup.
            if self.role.is_cold_backup {
                if let Some(control) = self.control_buffer.take() {
                    self.process_control(control, cx);
                }
                while let Ok(control) = self.control_receiver.try_recv() {
                    self.process_control(control, cx);
                }

                for worklet_global_scope in self.global_scopes.values() {
                    worklet_global_scope.perform_a_microtask_checkpoint(cx);
                }

                self.gc(cx);
            } else if self.control_buffer.is_none() &&
                let Ok(control) = self.control_receiver.try_recv()
            {
                self.control_buffer = Some(control);
                let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
                let _ = self.cold_backup_sender.send(msg);
            }
            // If we are tight on memory, and we're a backup then perform a gc.
            // If we are tight on memory, and we're the primary then try to become the hot backup.
            // Hopefully this happens soon!
            if self.current_memory_usage() > self.gc_threshold {
                if self.role.is_hot_backup || self.role.is_cold_backup {
                    self.should_gc = false;
                    self.gc(cx);
                } else if !self.should_gc {
                    self.should_gc = true;
                    let msg = WorkletData::StartSwapRoles(self.role.sender.clone());
                    let _ = self.hot_backup_sender.send(msg);
                }
            }
        }
    }

    /// The current memory usage of the thread
    #[expect(unsafe_code)]
    fn current_memory_usage(&self) -> u32 {
        unsafe { JS_GetGCParameter(self.runtime.cx_no_gc(), JSGCParamKey::JSGC_BYTES) }
    }

    /// Perform a GC.
    #[expect(unsafe_code)]
    fn gc(&mut self, cx: &mut JSContext) {
        debug!(
            "BEGIN GC (usage = {}, threshold = {}).",
            self.current_memory_usage(),
            self.gc_threshold
        );
        unsafe { JS_GC(cx, GCReason::API) };
        self.gc_threshold = max(MIN_GC_THRESHOLD, self.current_memory_usage() * 2);
        debug!(
            "END GC (usage = {}, threshold = {}).",
            self.current_memory_usage(),
            self.gc_threshold
        );
    }

    /// Get the worklet global scope for a given worklet.
    /// Creates the worklet global scope if it doesn't exist.
    #[expect(clippy::too_many_arguments)]
    fn get_worklet_global_scope(
        &mut self,
        cx: &mut JSContext,
        pipeline_id: PipelineId,
        worklet_id: WorkletId,
        inherited_secure_context: Option<bool>,
        global_type: WorkletGlobalScopeType,
        base_url: ServoUrl,
        microtask_queue: Rc<MicrotaskQueue>,
    ) -> DomRoot<WorkletGlobalScope> {
        match self.global_scopes.entry(worklet_id) {
            hash_map::Entry::Occupied(entry) => DomRoot::from_ref(entry.get()),

            // Step 6.1. If workletInstance's global scopes is empty:
            hash_map::Entry::Vacant(entry) => {
                debug!("Creating new worklet global scope.");

                // Step 6.1.1. Create a worklet global scope given workletInstance.
                let executor = WorkletExecutor {
                    worklet_id,
                    primary_sender: self.primary_sender.clone(),
                    hot_backup_sender: self.hot_backup_sender.clone(),
                    cold_backup_sender: self.cold_backup_sender.clone(),
                    control_sender: self.control_sender.clone(),
                };

                let result = WorkletGlobalScope::new(
                    global_type,
                    pipeline_id,
                    base_url,
                    inherited_secure_context,
                    executor,
                    &self.global_init,
                    cx,
                    self.closing.clone(),
                    microtask_queue,
                );
                entry.insert(Dom::from_ref(&*result));
                result
            },
        }
    }

    /// Fetch and invoke a worklet script.
    /// <https://html.spec.whatwg.org/multipage/#fetch-a-worklet-script-graph>
    #[allow(clippy::too_many_arguments)]
    fn fetch_and_invoke_a_worklet_script(
        &self,
        global_scope: &WorkletGlobalScope,
        pipeline_id: PipelineId,
        origin: ImmutableOrigin,
        script_url: ServoUrl,
        policy_container: PolicyContainer,
        credentials: RequestCredentials,
        pending_tasks_struct: PendingTasksStruct,
        promise: TrustedPromise,
        cx: &mut JSContext,
    ) {
        debug!("Fetching from {}.", script_url);
        // TODO: Settings object?

        // TODO: Fetch the script asynchronously?
        // TODO: Caching.
        let global = global_scope.upcast::<GlobalScope>();

        // Step 1. Let requestURL be request's URL.
        let request_client = RequestClient {
            preloaded_resources: PreloadedResources::default(),
            policy_container,
            origin: Origin::Origin(origin),
            is_nested_browsing_context: global.is_nested_browsing_context(),
            insecure_requests_policy: global.insecure_requests_policy(),
            has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_origin(),
        };

        // Step 2. If moduleResponsesMap[requestURL] is "fetching", wait in parallel until that entry's value changes, then queue a task on the networking task source to proceed with running the following steps.
        // NOTE: We do not perform the Step 2 because Worklet currently does not implement a `module responses map`
        // <https://html.spec.whatwg.org/multipage/#concept-worklet-module-responses-map>

        // `fetch_a_module_script_graph` requires the `on_complete` closure to be cloneable
        // therefore, we wrap the TrustedPromise in an Rc to make it cloneable and RefCell allows calling `reject_task` and `resolve_task`
        let promise_task = Rc::new(RefCell::new(Some(promise)));
        let script_thread_sender = self.global_init.to_script_thread_sender.clone();
        let rooted_global = DomRoot::from_ref(global);
        let script_url = ensure_blob_referenced_by_url_is_kept_alive(global, script_url);

        // NOTE: We implement the rest of the steps in AddModule here
        // <https://html.spec.whatwg.org/multipage/#dom-worklet-addmodule>
        // Step 6.4. For each workletGlobalScope of workletInstance's global scopes,
        // queue a global task on the networking task source given workletGlobalScope to fetch a worklet script graph given moduleURLRecord,
        // outsideSettings, workletInstance's worklet destination type, options["credentials"], workletGlobalScope's relevant settings object,
        // workletInstance's module responses map, and the following steps given script:
        fetch_a_module_script_graph(
            cx,
            global,
            script_url,
            request_client,
            Destination::PaintWorklet,
            global.get_referrer(),
            credentials.convert(),
            Some(IntroductionType::WORKLET),
            move |cx, module_tree| {
                match module_tree {
                    // Step 6.4.1. If script is null:
                    None => {
                        debug!("Failed to load script.");

                        reject_promise(
                            &pending_tasks_struct,
                            promise_task.borrow_mut(),
                            script_thread_sender.clone(),
                        );
                    },
                    Some(script) => {
                        let mut realm = enter_auto_realm(cx, &*rooted_global);
                        let cx = &mut realm.current_realm();

                        // Step 6.4.2. If script's error to rethrow is not null:
                        // NOTE: The `AddModule` specification in the Step 6.4.2.1.1.2. requires the promise to be rejected with the script's "rethrow error".
                        // However, the `JSVal` from `get_rethrow_error` cannot be used with the `promise_task` here because they are from different runtimes.
                        // So we throw an AbortError instead.
                        if script.get_rethrow_error().take().is_some() {
                            // Step 6.4.2.1. and its substeps are handled by `reject_promise` function
                            reject_promise(
                                &pending_tasks_struct,
                                promise_task.borrow_mut(),
                                script_thread_sender.clone(),
                            );

                            // Step 6.4.2.2. Abort these steps.
                            return;
                        }

                        // Step 6.4.4. Run a module script given script.
                        rooted_global.run_a_module_script(cx, script, false);

                        // NOTE: we are treating all negative values as -1
                        // Step 6.4.5.1. If pendingTasks is not −1:
                        // Step 6.4.5.1.1. Set pendingTasks to pendingTasks − 1.
                        let old_counter = pending_tasks_struct.decrement_counter_by(1);
                        // Step Step 6.4.5.1.2. If pendingTasks is 0 then, resolve promise.
                        if old_counter == 1 {
                            debug!("Resolving promise.");

                            let msg = MainThreadScriptMsg::WorkletLoaded(pipeline_id);
                            script_thread_sender
                                .send(msg)
                                .expect("Worklet thread outlived script thread.");

                            let task = promise_task
                                .borrow_mut()
                                .take()
                                .expect("promise_task must be consumed exactly once")
                                .resolve_task(());

                            let msg = CommonScriptMsg::Task(
                                ScriptThreadEventCategory::WorkletEvent,
                                Box::new(task),
                                None,
                                TaskSourceName::Networking,
                            );

                            // Step 6.4.5. Queue a global task on the networking task source given workletInstance's relevant global object to perform the following steps:
                            let msg = MainThreadScriptMsg::Common(msg);
                            script_thread_sender
                                .send(msg)
                                .expect("Worklet thread outlived script thread.");
                        }
                    },
                }
            },
        );
    }

    /// Perform a task.
    fn perform_a_worklet_task(&self, cx: &mut JSContext, worklet_id: WorkletId, task: WorkletTask) {
        match self.global_scopes.get(&worklet_id) {
            Some(global) => global.perform_a_worklet_task(cx, task),
            None => warn!("No such worklet as {:?}.", worklet_id),
        }
    }

    /// Process a control message.
    fn process_control(&mut self, control: WorkletControl, cx: &mut js::context::JSContext) {
        match control {
            WorkletControl::ExitWorklet(worklet_id) => {
                self.global_scopes.remove(&worklet_id);
            },
            WorkletControl::FetchAndInvokeAWorkletScript {
                pipeline_id,
                worklet_id,
                global_type,
                origin,
                base_url,
                script_url,
                policy_container,
                credentials,
                pending_tasks_struct,
                promise,
                inherited_secure_context,
            } => {
                // A worklet global scope is created here as part of the AddModule specs.
                // <https://html.spec.whatwg.org/multipage/#dom-worklet-addmodule>
                // 6.1.3. Wait for all steps of the creation process(es) — including those taking place within the worklet agents — to complete, before moving on.
                let global = self.get_worklet_global_scope(
                    cx,
                    pipeline_id,
                    worklet_id,
                    inherited_secure_context,
                    global_type,
                    base_url,
                    self.runtime.microtask_queue.clone(),
                );
                self.fetch_and_invoke_a_worklet_script(
                    &global,
                    pipeline_id,
                    origin,
                    script_url,
                    policy_container,
                    credentials,
                    pending_tasks_struct,
                    promise,
                    cx,
                )
            },
            WorkletControl::Common(script_msg) => {
                if let CommonScriptMsg::Task(_, task, _, _) = script_msg {
                    task.run_box(cx);
                }
            },
        }
    }
}

/// This function is an abstraction of steps 6.4.1.1 and 6.4.2.1 of the `AddModule` spec
/// <https://html.spec.whatwg.org/multipage/#dom-worklet-addmodule>
pub(crate) fn reject_promise(
    pending_tasks_struct: &PendingTasksStruct,
    mut promise_task: RefMut<'_, Option<TrustedPromise>>,
    script_thread_sender: Sender<MainThreadScriptMsg>,
) {
    // Step 6.4.1.1.1.1. Set pendingTasks to −1
    let old_counter = pending_tasks_struct.set_counter_to(-1);

    // 6.4.1.1.1. If pendingTasks is not −1:
    if old_counter > 0 {
        // 6.4.1.1.1.2. Reject promise with an "AbortError" DOMException
        let task = promise_task
            .take()
            .expect("promise_task must be consumed exactly once")
            .reject_task(Error::Abort(None));

        let msg = CommonScriptMsg::Task(
            ScriptThreadEventCategory::WorkletEvent,
            Box::new(task),
            None,
            TaskSourceName::Networking,
        );

        // Step 6.4.1.1. Queue a global task on the networking task source given workletInstance's relevant global object to perform the following steps:
        let msg = MainThreadScriptMsg::Common(msg);
        script_thread_sender
            .send(msg)
            .expect("Worklet thread outlived script thread.");
    }
}

/// An executor of worklet tasks
#[derive(Clone, JSTraceable, MallocSizeOf)]
pub(crate) struct WorkletExecutor {
    worklet_id: WorkletId,
    #[no_trace]
    primary_sender: Sender<WorkletData>,
    #[no_trace]
    hot_backup_sender: Sender<WorkletData>,
    #[no_trace]
    cold_backup_sender: Sender<WorkletData>,
    #[no_trace]
    control_sender: Sender<WorkletControl>,
}

impl WorkletExecutor {
    /// If any of the threads are blocked waiting on data, wake them up.
    pub(crate) fn wake_threads(&self) -> Result<(), SendError<()>> {
        self.cold_backup_sender
            .send(WorkletData::WakeUp)
            .map_err(|_| SendError(()))?;
        self.hot_backup_sender
            .send(WorkletData::WakeUp)
            .map_err(|_| SendError(()))?;
        self.primary_sender
            .send(WorkletData::WakeUp)
            .map_err(|_| SendError(()))
    }

    /// Schedule a worklet task to be peformed by the worklet thread pool.
    pub(crate) fn schedule_a_worklet_task(&self, task: WorkletTask) {
        let _ = self
            .primary_sender
            .send(WorkletData::Task(self.worklet_id, task));
    }

    pub(crate) fn send_control_message(
        &self,
        control_message: WorkletControl,
    ) -> Result<(), SendError<()>> {
        self.control_sender
            .send(control_message)
            .map_err(|_| SendError(()))?;
        self.wake_threads()
    }

    pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
        ScriptEventLoopSender::Worklet(self.clone())
    }
}