Skip to main content

script/dom/workers/
dedicatedworkerglobalscope.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::Cell;
8use std::collections::VecDeque;
9use std::rc::Rc;
10use std::sync::Arc;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::thread::{self, JoinHandle};
13
14use crossbeam_channel::{Receiver, Sender, unbounded};
15use devtools_traits::DevtoolScriptControlMsg;
16use dom_struct::dom_struct;
17use fonts::FontContext;
18use js::context::JSContext;
19use js::jsapi::{Heap, JSObject};
20use js::jsval::UndefinedValue;
21use js::rust::{CustomAutoRooterGuard, HandleValue};
22use net_traits::blob_url_store::UrlWithBlobClaim;
23use net_traits::image_cache::ImageCache;
24use net_traits::policy_container::PolicyContainer;
25use net_traits::request::{
26    CredentialsMode, Destination, InsecureRequestsPolicy, Origin, ParserMetadata,
27    PreloadedResources, Referrer, RequestBuilder, RequestClient, RequestMode,
28};
29use script_bindings::cell::DomRefCell;
30use script_bindings::interfaces::HasOrigin;
31use servo_base::generic_channel::{self, GenericReceiver, GenericSender, RoutedReceiver};
32use servo_base::id::{BrowsingContextId, PipelineId, ScriptEventLoopId, WebViewId};
33use servo_constellation_traits::{
34    ScriptToConstellationMessage, WorkerAnimationFrameTick, WorkerGlobalScopeInit,
35    WorkerScriptLoadOrigin,
36};
37use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
38use style::thread_state::{self, ThreadState};
39
40use crate::conversions::Convert;
41use crate::dom::abstractworker::{MessageData, SimpleWorkerErrorHandler, WorkerScriptMsg};
42use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
43use crate::dom::bindings::callback::ExceptionHandling;
44use crate::dom::bindings::codegen::Bindings::AnimationFrameProviderBinding::FrameRequestCallback;
45use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
46use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
47use crate::dom::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
48use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceMethods;
49use crate::dom::bindings::codegen::Bindings::WorkerBinding::{WorkerOptions, WorkerType};
50use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
51use crate::dom::bindings::inheritance::Castable;
52use crate::dom::bindings::num::Finite;
53use crate::dom::bindings::refcounted::Trusted;
54use crate::dom::bindings::reflector::DomGlobal;
55use crate::dom::bindings::root::DomRoot;
56use crate::dom::bindings::str::DOMString;
57use crate::dom::bindings::structuredclone;
58use crate::dom::bindings::trace::{CustomTraceable, RootedTraceableBox};
59use crate::dom::csp::Violation;
60use crate::dom::errorevent::ErrorEvent;
61use crate::dom::event::{Event, EventBubbles, EventCancelable};
62use crate::dom::eventtarget::EventTarget;
63use crate::dom::globalscope::GlobalScope;
64use crate::dom::html::htmlscriptelement::Script;
65use crate::dom::messageevent::MessageEvent;
66use crate::dom::types::DebuggerGlobalScope;
67#[cfg(feature = "webgpu")]
68use crate::dom::webgpu::identityhub::IdentityHub;
69use crate::dom::worker::{TrustedWorkerAddress, Worker};
70use crate::dom::workerglobalscope::{ScriptFetchContext, WorkerGlobalScope};
71use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
72use crate::modules::script_module::fetch_a_module_script_graph;
73use crate::realms::enter_auto_realm;
74use crate::runtime::script_runtime::ScriptThreadEventCategory::WorkerEvent;
75use crate::runtime::script_runtime::{IntroductionType, Runtime, ThreadSafeJSContext};
76use crate::tasks::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
77use crate::tasks::task_source::TaskSourceName;
78
79/// Set the `worker` field of a related DedicatedWorkerGlobalScope object to a particular
80/// value for the duration of this object's lifetime. This ensures that the related Worker
81/// object only lives as long as necessary (ie. while events are being executed), while
82/// providing a reference that can be cloned freely.
83pub(crate) struct AutoWorkerReset<'a> {
84    workerscope: &'a DedicatedWorkerGlobalScope,
85    old_worker: Option<TrustedWorkerAddress>,
86}
87
88impl<'a> AutoWorkerReset<'a> {
89    pub(crate) fn new(
90        workerscope: &'a DedicatedWorkerGlobalScope,
91        worker: TrustedWorkerAddress,
92    ) -> AutoWorkerReset<'a> {
93        let old_worker = workerscope.replace_worker(Some(worker));
94        AutoWorkerReset {
95            workerscope,
96            old_worker,
97        }
98    }
99}
100
101impl Drop for AutoWorkerReset<'_> {
102    fn drop(&mut self) {
103        self.workerscope
104            .replace_worker(std::mem::take(&mut self.old_worker));
105    }
106}
107
108/// Messages sent from the owning global.
109pub(crate) enum DedicatedWorkerControlMsg {
110    /// Shutdown the worker.
111    Exit,
112    AnimationFrameProviderUnsupported,
113}
114
115pub(crate) enum DedicatedWorkerScriptMsg {
116    /// Standard message from a worker.
117    CommonWorker(TrustedWorkerAddress, WorkerScriptMsg),
118    /// Wake-up call from the task queue.
119    WakeUp,
120}
121
122pub(crate) enum MixedMessage {
123    Worker(DedicatedWorkerScriptMsg),
124    Devtools(DevtoolScriptControlMsg),
125    Control(DedicatedWorkerControlMsg),
126    AnimationFrameTick(WorkerAnimationFrameTick),
127    Timer,
128}
129
130impl QueuedTaskConversion for DedicatedWorkerScriptMsg {
131    fn task_source_name(&self) -> Option<&TaskSourceName> {
132        let common_worker_msg = match self {
133            DedicatedWorkerScriptMsg::CommonWorker(_, common_worker_msg) => common_worker_msg,
134            _ => return None,
135        };
136        let script_msg = match common_worker_msg {
137            WorkerScriptMsg::Common(script_msg) => script_msg,
138            _ => return None,
139        };
140        match script_msg {
141            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, source_name) => {
142                Some(source_name)
143            },
144            _ => None,
145        }
146    }
147
148    fn pipeline_id(&self) -> Option<PipelineId> {
149        // Workers always return None, since the pipeline_id is only used to check for document activity,
150        // and this check does not apply to worker event-loops.
151        None
152    }
153
154    fn into_queued_task(self) -> Option<QueuedTask> {
155        let (worker, common_worker_msg) = match self {
156            DedicatedWorkerScriptMsg::CommonWorker(worker, common_worker_msg) => {
157                (worker, common_worker_msg)
158            },
159            _ => return None,
160        };
161        let script_msg = match common_worker_msg {
162            WorkerScriptMsg::Common(script_msg) => script_msg,
163            _ => return None,
164        };
165        let (event_category, task, pipeline_id, task_source) = match script_msg {
166            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
167                (category, boxed, pipeline_id, task_source)
168            },
169            _ => return None,
170        };
171        Some(QueuedTask {
172            worker: Some(worker),
173            event_category,
174            task,
175            pipeline_id,
176            task_source,
177        })
178    }
179
180    fn from_queued_task(queued_task: QueuedTask) -> Self {
181        let script_msg = CommonScriptMsg::Task(
182            queued_task.event_category,
183            queued_task.task,
184            queued_task.pipeline_id,
185            queued_task.task_source,
186        );
187        DedicatedWorkerScriptMsg::CommonWorker(
188            queued_task.worker.unwrap(),
189            WorkerScriptMsg::Common(script_msg),
190        )
191    }
192
193    fn inactive_msg() -> Self {
194        // Inactive is only relevant in the context of a browsing-context event-loop.
195        panic!("Workers should never receive messages marked as inactive");
196    }
197
198    fn wake_up_msg() -> Self {
199        DedicatedWorkerScriptMsg::WakeUp
200    }
201
202    fn is_wake_up(&self) -> bool {
203        matches!(self, DedicatedWorkerScriptMsg::WakeUp)
204    }
205}
206
207unsafe_no_jsmanaged_fields!(TaskQueue<DedicatedWorkerScriptMsg>);
208
209// https://html.spec.whatwg.org/multipage/#dedicatedworkerglobalscope
210#[dom_struct]
211pub(crate) struct DedicatedWorkerGlobalScope {
212    workerglobalscope: WorkerGlobalScope,
213    /// The [`WebViewId`] of the `WebView` that this worker is associated with.
214    #[no_trace]
215    webview_id: WebViewId,
216    #[ignore_malloc_size_of = "Defined in std"]
217    task_queue: TaskQueue<DedicatedWorkerScriptMsg>,
218    own_sender: Sender<DedicatedWorkerScriptMsg>,
219    worker: DomRefCell<Option<TrustedWorkerAddress>>,
220    /// Sender to the parent thread.
221    parent_event_loop_sender: ScriptEventLoopSender,
222    #[ignore_malloc_size_of = "ImageCache"]
223    #[no_trace]
224    image_cache: Arc<dyn ImageCache>,
225    #[no_trace]
226    browsing_context: Option<BrowsingContextId>,
227    #[conditional_malloc_size_of]
228    animation_frame_provider_supported: Arc<AtomicBool>,
229    animation_frame_provider_registered: Cell<bool>,
230    /// <https://html.spec.whatwg.org/multipage/#animation-frame-callback-identifier>
231    animation_frame_ident: Cell<u32>,
232    /// Pending animation frame callbacks for a later worker rendering update.
233    #[ignore_malloc_size_of = "closures are hard"]
234    animation_frame_list: DomRefCell<VecDeque<(u32, Rc<FrameRequestCallback>)>>,
235    /// Callbacks snapshotted for the current worker rendering update.
236    #[ignore_malloc_size_of = "closures are hard"]
237    current_animation_frame_list: DomRefCell<VecDeque<(u32, Rc<FrameRequestCallback>)>>,
238    /// Whether we're in the process of running animation callbacks.
239    running_animation_callbacks: Cell<bool>,
240    /// Whether Constellation currently treats this worker as having callbacks.
241    animation_frame_callbacks_active: Cell<bool>,
242    /// A sender for animation frame ticks.
243    #[no_trace]
244    #[ignore_malloc_size_of = "channels are hard"]
245    animation_frame_tick_sender: Option<GenericSender<WorkerAnimationFrameTick>>,
246    /// A receiver for animation frame ticks.
247    #[no_trace]
248    #[ignore_malloc_size_of = "channels are hard"]
249    animation_frame_tick_receiver: Option<RoutedReceiver<WorkerAnimationFrameTick>>,
250    /// A receiver of control messages.
251    #[no_trace]
252    control_receiver: Receiver<DedicatedWorkerControlMsg>,
253    #[no_trace]
254    queued_worker_tasks: DomRefCell<Vec<MessageData>>,
255}
256
257impl WorkerEventLoopMethods for DedicatedWorkerGlobalScope {
258    type WorkerMsg = DedicatedWorkerScriptMsg;
259    type ControlMsg = DedicatedWorkerControlMsg;
260    type Event = MixedMessage;
261
262    fn task_queue(&self) -> &TaskQueue<DedicatedWorkerScriptMsg> {
263        &self.task_queue
264    }
265
266    fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
267        self.handle_mixed_message(event, cx)
268    }
269
270    fn handle_worker_post_event(
271        &self,
272        worker: &TrustedWorkerAddress,
273    ) -> Option<AutoWorkerReset<'_>> {
274        let ar = AutoWorkerReset::new(self, worker.clone());
275        Some(ar)
276    }
277
278    fn from_control_msg(msg: DedicatedWorkerControlMsg) -> MixedMessage {
279        MixedMessage::Control(msg)
280    }
281
282    fn from_worker_msg(msg: DedicatedWorkerScriptMsg) -> MixedMessage {
283        MixedMessage::Worker(msg)
284    }
285
286    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
287        MixedMessage::Devtools(msg)
288    }
289
290    fn from_timer_msg() -> MixedMessage {
291        MixedMessage::Timer
292    }
293
294    fn from_animation_frame_tick_msg(msg: WorkerAnimationFrameTick) -> Option<MixedMessage> {
295        Some(MixedMessage::AnimationFrameTick(msg))
296    }
297
298    fn animation_frame_tick_receiver(&self) -> Option<&RoutedReceiver<WorkerAnimationFrameTick>> {
299        self.animation_frame_tick_receiver.as_ref()
300    }
301
302    fn control_receiver(&self) -> &Receiver<DedicatedWorkerControlMsg> {
303        &self.control_receiver
304    }
305}
306
307impl DedicatedWorkerGlobalScope {
308    #[allow(clippy::too_many_arguments)]
309    fn new_inherited(
310        init: WorkerGlobalScopeInit,
311        webview_id: WebViewId,
312        worker_name: DOMString,
313        worker_type: WorkerType,
314        worker_url: ServoUrl,
315        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
316        runtime: Runtime,
317        parent_event_loop_sender: ScriptEventLoopSender,
318        own_sender: Sender<DedicatedWorkerScriptMsg>,
319        receiver: Receiver<DedicatedWorkerScriptMsg>,
320        closing: Arc<AtomicBool>,
321        animation_frame_provider_supported: Arc<AtomicBool>,
322        image_cache: Arc<dyn ImageCache>,
323        browsing_context: Option<BrowsingContextId>,
324        animation_frame_tick_sender: Option<GenericSender<WorkerAnimationFrameTick>>,
325        animation_frame_tick_receiver: Option<RoutedReceiver<WorkerAnimationFrameTick>>,
326        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
327        control_receiver: Receiver<DedicatedWorkerControlMsg>,
328        insecure_requests_policy: InsecureRequestsPolicy,
329        font_context: Arc<FontContext>,
330    ) -> DedicatedWorkerGlobalScope {
331        DedicatedWorkerGlobalScope {
332            workerglobalscope: WorkerGlobalScope::new_inherited(
333                init,
334                worker_name,
335                worker_type,
336                worker_url,
337                runtime,
338                from_devtools_receiver,
339                closing,
340                #[cfg(feature = "webgpu")]
341                gpu_id_hub,
342                insecure_requests_policy,
343                font_context,
344                None,
345            ),
346            webview_id,
347            task_queue: TaskQueue::new(receiver, own_sender.clone()),
348            own_sender,
349            parent_event_loop_sender,
350            worker: DomRefCell::new(None),
351            image_cache,
352            browsing_context,
353            animation_frame_provider_supported,
354            animation_frame_provider_registered: Cell::new(false),
355            animation_frame_ident: Cell::new(0),
356            animation_frame_list: DomRefCell::new(VecDeque::new()),
357            current_animation_frame_list: DomRefCell::new(VecDeque::new()),
358            running_animation_callbacks: Cell::new(false),
359            animation_frame_callbacks_active: Cell::new(false),
360            animation_frame_tick_sender,
361            animation_frame_tick_receiver,
362            control_receiver,
363            queued_worker_tasks: Default::default(),
364        }
365    }
366
367    #[expect(clippy::too_many_arguments)]
368    pub(crate) fn new(
369        init: WorkerGlobalScopeInit,
370        webview_id: WebViewId,
371        worker_name: DOMString,
372        worker_type: WorkerType,
373        worker_url: ServoUrl,
374        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
375        runtime: Runtime,
376        parent_event_loop_sender: ScriptEventLoopSender,
377        own_sender: Sender<DedicatedWorkerScriptMsg>,
378        receiver: Receiver<DedicatedWorkerScriptMsg>,
379        closing: Arc<AtomicBool>,
380        animation_frame_provider_supported: Arc<AtomicBool>,
381        image_cache: Arc<dyn ImageCache>,
382        browsing_context: Option<BrowsingContextId>,
383        animation_frame_tick_sender: Option<GenericSender<WorkerAnimationFrameTick>>,
384        animation_frame_tick_receiver: Option<RoutedReceiver<WorkerAnimationFrameTick>>,
385        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
386        control_receiver: Receiver<DedicatedWorkerControlMsg>,
387        insecure_requests_policy: InsecureRequestsPolicy,
388        font_context: Arc<FontContext>,
389        debugger_global: &DebuggerGlobalScope,
390        cx: &mut js::context::JSContext,
391    ) -> DomRoot<DedicatedWorkerGlobalScope> {
392        let scope = Box::new(DedicatedWorkerGlobalScope::new_inherited(
393            init,
394            webview_id,
395            worker_name,
396            worker_type,
397            worker_url,
398            from_devtools_receiver,
399            runtime,
400            parent_event_loop_sender,
401            own_sender,
402            receiver,
403            closing,
404            animation_frame_provider_supported,
405            image_cache,
406            browsing_context,
407            animation_frame_tick_sender,
408            animation_frame_tick_receiver,
409            #[cfg(feature = "webgpu")]
410            gpu_id_hub,
411            control_receiver,
412            insecure_requests_policy,
413            font_context,
414        ));
415        let scope = DedicatedWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
416            cx,
417            &scope.origin(),
418            scope,
419        );
420        scope
421            .upcast::<WorkerGlobalScope>()
422            .init_debugger_global(debugger_global, cx);
423
424        scope
425    }
426
427    /// <https://html.spec.whatwg.org/multipage/#run-a-worker>
428    #[expect(unsafe_code)]
429    #[allow(clippy::too_many_arguments)]
430    pub(crate) fn run_worker_scope(
431        mut init: WorkerGlobalScopeInit,
432        webview_id: WebViewId,
433        worker_url: UrlWithBlobClaim,
434        from_devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
435        worker: TrustedWorkerAddress,
436        parent_event_loop_sender: ScriptEventLoopSender,
437        own_sender: Sender<DedicatedWorkerScriptMsg>,
438        receiver: Receiver<DedicatedWorkerScriptMsg>,
439        worker_load_origin: WorkerScriptLoadOrigin,
440        worker_options: &WorkerOptions,
441        closing: Arc<AtomicBool>,
442        animation_frame_provider_supported: Arc<AtomicBool>,
443        image_cache: Arc<dyn ImageCache>,
444        browsing_context: Option<BrowsingContextId>,
445        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
446        control_receiver: Receiver<DedicatedWorkerControlMsg>,
447        context_sender: Sender<ThreadSafeJSContext>,
448        insecure_requests_policy: InsecureRequestsPolicy,
449        policy_container: PolicyContainer,
450        font_context: Arc<FontContext>,
451    ) -> JoinHandle<()> {
452        let event_loop_id = ScriptEventLoopId::installed()
453            .expect("Should always be in a ScriptThread or in a dedicated worker");
454        let current_global = GlobalScope::current().expect("No current global object");
455        let origin = current_global.origin().immutable().clone();
456        let referrer = current_global.get_referrer();
457        let parent = current_global.runtime_handle();
458        let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin();
459        let is_secure_context = current_global.is_secure_context();
460        let is_nested_browsing_context = current_global.is_nested_browsing_context();
461
462        let worker_type = worker_options.type_;
463        let worker_name = worker_options.name.to_string();
464        let credentials = worker_options.credentials.convert();
465
466        thread::Builder::new()
467            .name(format!("WW:{}", worker_url.debug_compact()))
468            .spawn(move || {
469                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
470                ScriptEventLoopId::install(event_loop_id);
471
472                let WorkerScriptLoadOrigin {
473                    referrer_url,
474                    pipeline_id,
475                    ..
476                } = worker_load_origin;
477
478                let referrer = referrer_url.map(Referrer::ReferrerUrl).unwrap_or(referrer);
479
480                let request_client = RequestClient {
481                    preloaded_resources: PreloadedResources::default(),
482                    policy_container,
483                    origin: Origin::Origin(origin.clone()),
484                    is_nested_browsing_context,
485                    insecure_requests_policy,
486                    has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
487                };
488
489                let event_loop_sender = ScriptEventLoopSender::DedicatedWorker {
490                    sender: own_sender.clone(),
491                    main_thread_worker: worker.clone(),
492                };
493
494                let runtime = unsafe {
495                    Runtime::new_with_parent(Some(parent), Some(event_loop_sender.clone()))
496                };
497                // SAFETY: We are in a new thread, so this first cx.
498                // It is OK to have it separated of runtime here,
499                // because it will never outlive it (runtime destruction happens at the end of this function)
500                let mut cx = unsafe { runtime.cx() };
501                let cx = &mut cx;
502                let debugger_global = DebuggerGlobalScope::new(
503                    pipeline_id,
504                    init.to_devtools_sender.clone(),
505                    init.from_devtools_sender
506                        .clone()
507                        .expect("Guaranteed by Worker::Constructor"),
508                    init.mem_profiler_chan.clone(),
509                    init.time_profiler_chan.clone(),
510                    init.script_to_constellation_chan.clone(),
511                    init.script_to_embedder_chan.clone(),
512                    init.resource_threads.clone(),
513                    init.storage_threads.clone(),
514                    #[cfg(feature = "webgpu")]
515                    gpu_id_hub.clone(),
516                    cx,
517                );
518                debugger_global.execute(cx);
519
520                let context_for_interrupt = runtime.thread_safe_js_context();
521                let _ = context_sender.send(context_for_interrupt);
522
523                let devtools_mpsc_port = from_devtools_receiver.route_preserving_errors();
524                let animation_frame_channel = init
525                    .animation_frame_provider_supported
526                    .then(|| generic_channel::channel().expect("Failed to create generic channel"));
527                let (animation_frame_tick_sender, animation_frame_tick_receiver) =
528                    match animation_frame_channel {
529                        Some((sender, receiver)) => {
530                            (Some(sender), Some(receiver.route_preserving_errors()))
531                        },
532                        None => (None, None),
533                    };
534
535                // Step 8 "Set up a worker environment settings object [...]"
536                //
537                // <https://html.spec.whatwg.org/multipage/#script-settings-for-workers>
538                //
539                // > The origin: Return a unique opaque origin if `worker global
540                // > scope`'s url's scheme is "data", and `inherited origin`
541                // > otherwise.
542                if worker_url.scheme() == "data" {
543                    // Workers created from a data: url are secure if they were created from secure contexts
544                    if is_secure_context {
545                        init.origin = ImmutableOrigin::new_opaque_data_url_worker();
546                    } else {
547                        init.origin = ImmutableOrigin::new_opaque();
548                    }
549                }
550
551                let worker_id = init.worker_id;
552                let devtools_enabled = init.to_devtools_sender.is_some();
553                let global = DedicatedWorkerGlobalScope::new(
554                    init,
555                    webview_id,
556                    worker_name.into(),
557                    worker_type,
558                    worker_url.url(),
559                    devtools_mpsc_port,
560                    runtime,
561                    parent_event_loop_sender,
562                    own_sender,
563                    receiver,
564                    closing,
565                    animation_frame_provider_supported,
566                    image_cache,
567                    browsing_context,
568                    animation_frame_tick_sender,
569                    animation_frame_tick_receiver,
570                    #[cfg(feature = "webgpu")]
571                    gpu_id_hub,
572                    control_receiver,
573                    insecure_requests_policy,
574                    font_context,
575                    &debugger_global,
576                    cx,
577                );
578
579                if devtools_enabled {
580                    debugger_global.fire_add_debuggee(
581                        cx,
582                        global.upcast(),
583                        pipeline_id,
584                        Some(worker_id),
585                    );
586                }
587                let scope = global.upcast::<WorkerGlobalScope>();
588                let global_scope = global.upcast::<GlobalScope>();
589
590                // Step 12. Obtain script by switching on options["type"]:
591                {
592                    let _ar = AutoWorkerReset::new(&global, worker.clone());
593                    match worker_type {
594                        WorkerType::Classic => {
595                            fetch_a_classic_worker_script(
596                                scope,
597                                worker_url,
598                                request_client,
599                                Destination::Worker,
600                                Some(webview_id),
601                                referrer,
602                            );
603                        },
604                        WorkerType::Module => {
605                            let worker_scope = DomRoot::from_ref(scope);
606                            fetch_a_module_script_graph(
607                                cx,
608                                global_scope,
609                                worker_url,
610                                request_client,
611                                Destination::Worker,
612                                referrer,
613                                credentials,
614                                Some(IntroductionType::WORKER),
615                                move |cx, module_tree| {
616                                    worker_scope.on_complete(cx, module_tree.map(Script::Module));
617                                },
618                            );
619                        },
620                    }
621
622                    let reporter_name = format!("dedicated-worker-reporter-{}", worker_id);
623                    scope
624                        .upcast::<GlobalScope>()
625                        .mem_profiler_chan()
626                        .run_with_memory_reporting(
627                            || {
628                                // Step 27, Run the responsible event loop specified
629                                // by inside settings until it is destroyed.
630                                // The worker processing model remains on this step
631                                // until the event loop is destroyed,
632                                // which happens after the closing flag is set to true.
633                                while !scope.is_closing() {
634                                    run_worker_event_loop(&*global, Some(&worker), cx);
635                                }
636                            },
637                            reporter_name,
638                            event_loop_sender,
639                            CommonScriptMsg::CollectReports,
640                        );
641                }
642
643                // Drop worker rAF state before destroying the JS runtime.
644                global.clear_animation_frame_callbacks_and_unregister();
645                scope.clear_js_runtime();
646            })
647            .expect("Thread spawning failed")
648    }
649
650    pub(crate) fn webview_id(&self) -> WebViewId {
651        self.webview_id
652    }
653
654    pub(crate) fn animation_frame_provider_supported(&self) -> bool {
655        self.animation_frame_provider_supported
656            .load(Ordering::SeqCst)
657    }
658
659    pub(crate) fn animation_frame_provider_supported_flag(&self) -> Arc<AtomicBool> {
660        self.animation_frame_provider_supported.clone()
661    }
662
663    fn register_animation_frame_provider(&self) {
664        if !self.animation_frame_provider_supported() {
665            return;
666        }
667
668        let Some(sender) = self.animation_frame_tick_sender.clone() else {
669            return;
670        };
671
672        if self.animation_frame_provider_registered.replace(true) {
673            return;
674        }
675
676        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
677        log::debug!(
678            "Registering dedicated worker animation frame provider: worker={worker_id:?} ---->"
679        );
680        let _ = self
681            .upcast::<GlobalScope>()
682            .script_to_constellation_chan()
683            .send(
684                ScriptToConstellationMessage::RegisterWorkerAnimationFrameProvider(
685                    worker_id, sender,
686                ),
687            );
688    }
689
690    fn unregister_animation_frame_provider(&self) {
691        if !self.animation_frame_provider_registered.replace(false) {
692            return;
693        }
694
695        self.animation_frame_callbacks_active.set(false);
696        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
697        log::debug!(
698            "Unregistering dedicated worker animation frame provider: worker={worker_id:?} ---->"
699        );
700        let _ = self
701            .upcast::<GlobalScope>()
702            .script_to_constellation_chan()
703            .send(ScriptToConstellationMessage::UnregisterWorkerAnimationFrameProvider(worker_id));
704    }
705
706    fn send_animation_frame_callbacks_state(&self, active: bool) {
707        if !self.animation_frame_provider_supported() {
708            return;
709        }
710
711        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
712        log::debug!(
713            "Sending dedicated worker animation frame state: worker={worker_id:?}, active={active} ---->",
714        );
715        let _ = self
716            .upcast::<GlobalScope>()
717            .script_to_constellation_chan()
718            .send(
719                ScriptToConstellationMessage::ChangeWorkerAnimationFrameProviderState(
720                    worker_id, active,
721                ),
722            );
723    }
724
725    fn set_animation_frame_callbacks_active(&self, active: bool) {
726        if self.animation_frame_callbacks_active.replace(active) == active {
727            return;
728        }
729
730        self.send_animation_frame_callbacks_state(active);
731    }
732
733    fn remove_animation_frame_callback_from(
734        list: &DomRefCell<VecDeque<(u32, Rc<FrameRequestCallback>)>>,
735        ident: u32,
736    ) {
737        let mut list = list.borrow_mut();
738        if let Some(position) = list.iter().position(|(handle, _)| *handle == ident) {
739            list.remove(position);
740        }
741    }
742
743    fn has_animation_frame_callbacks(&self) -> bool {
744        !self.animation_frame_list.borrow().is_empty() ||
745            !self.current_animation_frame_list.borrow().is_empty()
746    }
747
748    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-requestanimationframe>
749    pub(crate) fn request_animation_frame(
750        &self,
751        callback: Rc<FrameRequestCallback>,
752    ) -> Fallible<u32> {
753        // Step 1. If this is not supported, then throw a "NotSupportedError" DOMException.
754        if !self.animation_frame_provider_supported() {
755            return Err(Error::NotSupported(Some(
756                "Animation frame callbacks are not supported for this worker".to_owned(),
757            )));
758        }
759
760        self.register_animation_frame_provider();
761
762        // Step 2. Let target be this's target object.
763        // Step 3. Increment target's animation frame callback identifier by one,
764        // and let handle be the result.
765        let ident = self.animation_frame_ident.get() + 1;
766        self.animation_frame_ident.set(ident);
767
768        // Step 4. Let callbacks be target's map of animation frame callbacks.
769        // Step 5. Set callbacks[handle] to callback.
770        self.animation_frame_list
771            .borrow_mut()
772            .push_back((ident, callback));
773        log::debug!("Queued dedicated worker animation frame callback: handle={ident} ---->");
774        self.set_animation_frame_callbacks_active(true);
775
776        // Step 6. Return handle.
777        Ok(ident)
778    }
779
780    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-cancelanimationframe>
781    pub(crate) fn cancel_animation_frame(&self, ident: u32) -> ErrorResult {
782        // Step 1. If this is not supported, then throw a "NotSupportedError" DOMException.
783        if !self.animation_frame_provider_supported() {
784            return Err(Error::NotSupported(Some(
785                "Animation frame callbacks are not supported for this worker".to_owned(),
786            )));
787        }
788
789        // Step 2. Let callbacks be this's target object's map of animation frame callbacks.
790        // Step 3. Remove callbacks[handle].
791        Self::remove_animation_frame_callback_from(&self.animation_frame_list, ident);
792
793        Self::remove_animation_frame_callback_from(&self.current_animation_frame_list, ident);
794        log::debug!("Cancelled dedicated worker animation frame callback: handle={ident} ---->");
795
796        if !self.running_animation_callbacks.get() && !self.has_animation_frame_callbacks() {
797            self.set_animation_frame_callbacks_active(false);
798        }
799
800        Ok(())
801    }
802
803    /// <https://html.spec.whatwg.org/multipage/#run-the-animation-frame-callbacks>
804    pub(crate) fn run_the_animation_frame_callbacks(&self, cx: &mut JSContext) {
805        if !self.animation_frame_provider_supported() ||
806            self.upcast::<WorkerGlobalScope>().is_closing()
807        {
808            return;
809        }
810
811        // Step 1. Let callbacks be target's map of animation frame callbacks.
812        // Step 2. Let callbackHandles be the result of getting the keys of callbacks.
813        let callback_count = self.animation_frame_list.borrow().len();
814        log::debug!("Running dedicated worker animation frame callbacks: count={callback_count}");
815        {
816            let mut pending = self.animation_frame_list.borrow_mut();
817            let mut current = self.current_animation_frame_list.borrow_mut();
818            for _ in 0..callback_count {
819                if let Some(callback) = pending.pop_front() {
820                    current.push_back(callback);
821                }
822            }
823        }
824
825        self.running_animation_callbacks.set(true);
826        let timing = self.upcast::<GlobalScope>().performance(cx).Now();
827
828        // Step 3. For each handle in callbackHandles, if handle exists in callbacks:
829        for _ in 0..callback_count {
830            // Step 3.1. Let callback be callbacks[handle].
831            // Step 3.2. Remove callbacks[handle].
832            let callback = self
833                .current_animation_frame_list
834                .borrow_mut()
835                .pop_front()
836                .map(|(_, callback)| callback);
837
838            if let Some(callback) = callback {
839                // Step 3.3. Invoke callback with « now » and "`report`".
840                let _ = callback.Call__(cx, Finite::wrap(*timing), ExceptionHandling::Report);
841            }
842        }
843
844        self.current_animation_frame_list.borrow_mut().clear();
845        self.running_animation_callbacks.set(false);
846
847        if !self.has_animation_frame_callbacks() {
848            self.set_animation_frame_callbacks_active(false);
849        } else {
850            // Acknowledge the consumed worker tick while staying active; Paint
851            // will drive the next refresh tick.
852            self.send_animation_frame_callbacks_state(true);
853        }
854    }
855
856    pub(crate) fn clear_animation_frame_callbacks_and_unregister(&self) {
857        // Worker shutdown drops pending rAF callbacks and unregisters the tick target.
858        let worker_id = self.upcast::<WorkerGlobalScope>().worker_id();
859        log::debug!(
860            "Clearing dedicated worker animation frame callbacks: worker={worker_id:?} ---->"
861        );
862        self.animation_frame_list.borrow_mut().clear();
863        self.current_animation_frame_list.borrow_mut().clear();
864        self.running_animation_callbacks.set(false);
865        self.animation_frame_callbacks_active.set(false);
866        self.unregister_animation_frame_provider();
867    }
868
869    /// The non-None value of the `worker` field can contain a rooted [`TrustedWorkerAddress`]
870    /// version of the main thread's worker object. This is set while handling messages and then
871    /// unset otherwise, ensuring that the main thread object can be garbage collected. See
872    /// [`AutoWorkerReset`].
873    fn replace_worker(
874        &self,
875        new_worker: Option<TrustedWorkerAddress>,
876    ) -> Option<TrustedWorkerAddress> {
877        let old_worker = std::mem::replace(&mut *self.worker.borrow_mut(), new_worker);
878
879        // The `TaskManager` maintains a handle to this `DedicatedWorkerGlobalScope`'s
880        // event_loop_sender, which might in turn have a `TrustedWorkerAddress` rooting of the main
881        // thread's worker, which prevents garbage collection. Resetting it here ensures that
882        // garbage collection of the main thread object can happen again (assuming the new `worker`
883        // is `None`).
884        self.upcast::<GlobalScope>()
885            .task_manager()
886            .set_sender(self.event_loop_sender());
887
888        old_worker
889    }
890
891    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
892        self.image_cache.clone()
893    }
894
895    pub(crate) fn event_loop_sender(&self) -> Option<ScriptEventLoopSender> {
896        Some(ScriptEventLoopSender::DedicatedWorker {
897            sender: self.own_sender.clone(),
898            main_thread_worker: self.worker.borrow().clone()?,
899        })
900    }
901
902    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
903        let (sender, receiver) = unbounded();
904        let main_thread_worker = self.worker.borrow().as_ref().unwrap().clone();
905        (
906            ScriptEventLoopSender::DedicatedWorker {
907                sender,
908                main_thread_worker,
909            },
910            ScriptEventLoopReceiver::DedicatedWorker(receiver),
911        )
912    }
913
914    pub(crate) fn fire_queued_messages(&self, cx: &mut JSContext) {
915        let queue: Vec<_> = self.queued_worker_tasks.borrow_mut().drain(..).collect();
916        for msg in queue {
917            if self.upcast::<WorkerGlobalScope>().is_closing() {
918                return;
919            }
920            self.dispatch_message_event(cx, msg);
921        }
922    }
923
924    fn dispatch_message_event(&self, cx: &mut JSContext, msg: MessageData) {
925        let scope = self.upcast::<WorkerGlobalScope>();
926        let target = self.upcast();
927        let mut realm = enter_auto_realm(cx, self);
928        let cx = &mut realm;
929        rooted!(&in(cx) let mut message = UndefinedValue());
930        if let Ok(ports) =
931            structuredclone::read(cx, scope.upcast(), *msg.data, message.handle_mut())
932        {
933            MessageEvent::dispatch_jsval(
934                cx,
935                target,
936                scope.upcast(),
937                message.handle(),
938                Some(msg.origin.ascii_serialization().as_ref()),
939                None,
940                ports,
941            );
942        } else {
943            MessageEvent::dispatch_error(cx, target, scope.upcast());
944        }
945    }
946
947    fn handle_script_event(&self, msg: WorkerScriptMsg, cx: &mut JSContext) {
948        match msg {
949            WorkerScriptMsg::DOMMessage(message_data) => {
950                if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
951                    self.dispatch_message_event(cx, message_data);
952                } else {
953                    self.queued_worker_tasks.borrow_mut().push(message_data);
954                }
955            },
956            WorkerScriptMsg::Common(msg) => {
957                self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
958            },
959        }
960    }
961
962    /// <https://html.spec.whatwg.org/multipage/#event-loop-processing-model>
963    fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
964        if self.upcast::<WorkerGlobalScope>().is_closing() {
965            return false;
966        }
967        // FIXME(#26324): `self.worker` is None in devtools messages.
968        match msg {
969            MixedMessage::Devtools(msg) => self
970                .upcast::<WorkerGlobalScope>()
971                .handle_devtools_message(msg, cx),
972            MixedMessage::Worker(DedicatedWorkerScriptMsg::CommonWorker(linked_worker, msg)) => {
973                let _ar = AutoWorkerReset::new(self, linked_worker);
974                self.handle_script_event(msg, cx);
975            },
976            MixedMessage::Worker(DedicatedWorkerScriptMsg::WakeUp) => {},
977            MixedMessage::Control(DedicatedWorkerControlMsg::Exit) => {
978                return false;
979            },
980            MixedMessage::Control(DedicatedWorkerControlMsg::AnimationFrameProviderUnsupported) => {
981                self.clear_animation_frame_callbacks_and_unregister();
982                self.upcast::<GlobalScope>()
983                    .disable_owned_worker_animation_frame_providers();
984            },
985            MixedMessage::AnimationFrameTick(_) => {
986                // Step 6.1.2. Run the animation frame callbacks for that
987                // DedicatedWorkerGlobalScope, passing in now as the timestamp.
988                self.run_the_animation_frame_callbacks(cx);
989            },
990            MixedMessage::Timer => {},
991        }
992        true
993    }
994
995    /// Step 7.2 of <https://html.spec.whatwg.org/multipage/#report-an-exception>
996    pub(crate) fn forward_error_to_worker_object(&self, error_info: ErrorInfo) {
997        // Step 7.2.1. Let workerObject be the Worker object associated with global.
998        let worker = self.worker.borrow().as_ref().unwrap().clone();
999        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
1000        let task = Box::new(task!(forward_error_to_worker_object: move |cx| {
1001            let worker = worker.root();
1002            let global = worker.global();
1003
1004            // Step 7.2.2. Set notHandled to the result of firing an event named error at workerObject, using ErrorEvent,
1005            // with the cancelable attribute initialized to true, and additional attributes initialized according to errorInfo.
1006            let event = ErrorEvent::new(
1007                cx,
1008                &global,
1009                atom!("error"),
1010                EventBubbles::DoesNotBubble,
1011                EventCancelable::Cancelable,
1012                error_info.message.as_str().into(),
1013                error_info.filename.as_str().into(),
1014                error_info.lineno,
1015                error_info.column,
1016                HandleValue::null(),
1017            );
1018
1019            // Step 7.2.3. If notHandled is true, then report exception for workerObject's relevant global object with omitError set to true.
1020            if event.upcast::<Event>().fire(cx, worker.upcast::<EventTarget>()) {
1021                global.report_an_error(cx, error_info, HandleValue::null());
1022            }
1023        }));
1024        self.parent_event_loop_sender
1025            .send(CommonScriptMsg::Task(
1026                WorkerEvent,
1027                task,
1028                Some(pipeline_id),
1029                TaskSourceName::DOMManipulation,
1030            ))
1031            .unwrap();
1032    }
1033
1034    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
1035    fn post_message_impl(
1036        &self,
1037        cx: &mut JSContext,
1038        message: HandleValue,
1039        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
1040    ) -> ErrorResult {
1041        let data = structuredclone::write(cx, message, Some(transfer))?;
1042        let worker = self.worker.borrow().as_ref().unwrap().clone();
1043        let global_scope = self.upcast::<GlobalScope>();
1044        let pipeline_id = global_scope.pipeline_id();
1045        let task = Box::new(task!(post_worker_message: move |cx| {
1046            Worker::handle_message(worker, data, cx);
1047        }));
1048        self.parent_event_loop_sender
1049            .send(CommonScriptMsg::Task(
1050                WorkerEvent,
1051                task,
1052                Some(pipeline_id),
1053                TaskSourceName::DOMManipulation,
1054            ))
1055            .expect("Sending to parent failed");
1056        Ok(())
1057    }
1058
1059    pub(crate) fn browsing_context(&self) -> Option<BrowsingContextId> {
1060        self.browsing_context
1061    }
1062
1063    pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
1064        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
1065        self.parent_event_loop_sender
1066            .send(CommonScriptMsg::ReportCspViolations(
1067                pipeline_id,
1068                violations,
1069            ))
1070            .unwrap_or_else(|error| {
1071                log::warn!("Failed to send CSP violations to parent event loop: {error}");
1072            });
1073    }
1074
1075    pub(crate) fn forward_simple_error_at_worker(&self) {
1076        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
1077        let worker = self.worker.borrow().clone().expect("worker must be set");
1078        self.parent_event_loop_sender
1079            .send(CommonScriptMsg::Task(
1080                WorkerEvent,
1081                Box::new(SimpleWorkerErrorHandler::new(worker)),
1082                Some(pipeline_id),
1083                TaskSourceName::DOMManipulation,
1084            ))
1085            .expect("Sending to parent failed");
1086    }
1087}
1088
1089/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-script>
1090pub(crate) fn fetch_a_classic_worker_script(
1091    workerscope: &WorkerGlobalScope,
1092    url_with_blob_lock: UrlWithBlobClaim,
1093    fetch_client: RequestClient,
1094    destination: Destination,
1095    webview_id: Option<WebViewId>,
1096    referrer: Referrer,
1097) {
1098    let policy_container = fetch_client.policy_container.clone();
1099
1100    // Step 1. Let request be a new request whose URL is url,
1101    let request = RequestBuilder::new(webview_id, url_with_blob_lock.clone(), referrer)
1102        // client is fetchClient,
1103        .client(fetch_client)
1104        .pipeline_id(Some(workerscope.pipeline_id()))
1105        // destination is destination,
1106        .destination(destination)
1107        // TODO initiator type is "other",
1108        // mode is "same-origin",
1109        .mode(RequestMode::SameOrigin)
1110        // credentials mode is "same-origin",
1111        .credentials_mode(CredentialsMode::CredentialsSameOrigin)
1112        // parser metadata is "not parser-inserted",
1113        .parser_metadata(ParserMetadata::NotParserInserted)
1114        // and whose use-URL-credentials flag is set.
1115        .use_url_credentials(true);
1116
1117    let context = ScriptFetchContext::new(
1118        Trusted::new(workerscope),
1119        url_with_blob_lock.url(),
1120        policy_container,
1121    );
1122    let global = workerscope.upcast::<GlobalScope>();
1123    let task_source = global.task_manager().networking_task_source().to_sendable();
1124    global.fetch(request, context, task_source);
1125}
1126
1127impl DedicatedWorkerGlobalScopeMethods<crate::DomTypeHolder> for DedicatedWorkerGlobalScope {
1128    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-name>
1129    fn Name(&self) -> DOMString {
1130        self.workerglobalscope.worker_name()
1131    }
1132
1133    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
1134    fn PostMessage(
1135        &self,
1136        cx: &mut JSContext,
1137        message: HandleValue,
1138        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
1139    ) -> ErrorResult {
1140        self.post_message_impl(cx, message, transfer)
1141    }
1142
1143    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
1144    fn PostMessage_(
1145        &self,
1146        cx: &mut JSContext,
1147        message: HandleValue,
1148        options: RootedTraceableBox<StructuredSerializeOptions>,
1149    ) -> ErrorResult {
1150        auto_root!(&in(cx) let guard =
1151            options
1152                .transfer
1153                .iter()
1154                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
1155                .collect::<Vec<_>>());
1156        self.post_message_impl(cx, message, guard)
1157    }
1158
1159    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-close>
1160    fn Close(&self) {
1161        // Step 2
1162        self.upcast::<WorkerGlobalScope>().close();
1163    }
1164
1165    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-requestanimationframe>
1166    fn RequestAnimationFrame(&self, callback: Rc<FrameRequestCallback>) -> Fallible<u32> {
1167        self.request_animation_frame(callback)
1168    }
1169
1170    /// <https://html.spec.whatwg.org/multipage/#dom-animationframeprovider-cancelanimationframe>
1171    fn CancelAnimationFrame(&self, ident: u32) -> ErrorResult {
1172        self.cancel_animation_frame(ident)
1173    }
1174
1175    // https://html.spec.whatwg.org/multipage/#handler-dedicatedworkerglobalscope-onmessage
1176    event_handler!(message, GetOnmessage, SetOnmessage);
1177
1178    // https://html.spec.whatwg.org/multipage/#handler-dedicatedworkerglobalscope-onmessageerror
1179    event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
1180}
1181
1182impl HasOrigin for DedicatedWorkerGlobalScope {
1183    fn origin(&self) -> MutableOrigin {
1184        self.upcast::<WorkerGlobalScope>().origin()
1185    }
1186}