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
5use std::sync::Arc;
6use std::sync::atomic::AtomicBool;
7use std::thread::{self, JoinHandle};
8
9use crossbeam_channel::{Receiver, Sender, unbounded};
10use devtools_traits::DevtoolScriptControlMsg;
11use dom_struct::dom_struct;
12use fonts::FontContext;
13use js::context::JSContext;
14use js::jsapi::{Heap, JSObject};
15use js::jsval::UndefinedValue;
16use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
17use net_traits::blob_url_store::UrlWithBlobClaim;
18use net_traits::image_cache::ImageCache;
19use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
20use net_traits::request::{
21    CredentialsMode, Destination, InsecureRequestsPolicy, Origin, ParserMetadata,
22    PreloadedResources, Referrer, RequestBuilder, RequestClient, RequestMode,
23};
24use script_bindings::cell::DomRefCell;
25use script_bindings::interfaces::HasOrigin;
26use servo_base::generic_channel::{GenericReceiver, RoutedReceiver};
27use servo_base::id::{BrowsingContextId, PipelineId, ScriptEventLoopId, WebViewId};
28use servo_constellation_traits::{WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
29use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
30use style::thread_state::{self, ThreadState};
31
32use crate::conversions::Convert;
33use crate::dom::abstractworker::{MessageData, SimpleWorkerErrorHandler, WorkerScriptMsg};
34use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
35use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
36use crate::dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
37use crate::dom::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
38use crate::dom::bindings::codegen::Bindings::WorkerBinding::{WorkerOptions, WorkerType};
39use crate::dom::bindings::error::{ErrorInfo, ErrorResult};
40use crate::dom::bindings::inheritance::Castable;
41use crate::dom::bindings::refcounted::Trusted;
42use crate::dom::bindings::reflector::DomGlobal;
43use crate::dom::bindings::root::DomRoot;
44use crate::dom::bindings::str::DOMString;
45use crate::dom::bindings::structuredclone;
46use crate::dom::bindings::trace::{CustomTraceable, RootedTraceableBox};
47use crate::dom::csp::Violation;
48use crate::dom::errorevent::ErrorEvent;
49use crate::dom::event::{Event, EventBubbles, EventCancelable};
50use crate::dom::eventtarget::EventTarget;
51use crate::dom::globalscope::GlobalScope;
52use crate::dom::html::htmlscriptelement::Script;
53use crate::dom::messageevent::MessageEvent;
54use crate::dom::types::DebuggerGlobalScope;
55#[cfg(feature = "webgpu")]
56use crate::dom::webgpu::identityhub::IdentityHub;
57use crate::dom::worker::{TrustedWorkerAddress, Worker};
58use crate::dom::workerglobalscope::{ScriptFetchContext, WorkerGlobalScope};
59use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
60use crate::realms::enter_auto_realm;
61use crate::script_module::{ModuleFetchClient, fetch_a_module_worker_script_graph};
62use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
63use crate::script_runtime::{Runtime, ThreadSafeJSContext};
64use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
65use crate::task_source::TaskSourceName;
66
67/// Set the `worker` field of a related DedicatedWorkerGlobalScope object to a particular
68/// value for the duration of this object's lifetime. This ensures that the related Worker
69/// object only lives as long as necessary (ie. while events are being executed), while
70/// providing a reference that can be cloned freely.
71pub(crate) struct AutoWorkerReset<'a> {
72    workerscope: &'a DedicatedWorkerGlobalScope,
73    old_worker: Option<TrustedWorkerAddress>,
74}
75
76impl<'a> AutoWorkerReset<'a> {
77    pub(crate) fn new(
78        workerscope: &'a DedicatedWorkerGlobalScope,
79        worker: TrustedWorkerAddress,
80    ) -> AutoWorkerReset<'a> {
81        let old_worker = workerscope.replace_worker(Some(worker));
82        AutoWorkerReset {
83            workerscope,
84            old_worker,
85        }
86    }
87}
88
89impl Drop for AutoWorkerReset<'_> {
90    fn drop(&mut self) {
91        self.workerscope
92            .replace_worker(std::mem::take(&mut self.old_worker));
93    }
94}
95
96/// Messages sent from the owning global.
97pub(crate) enum DedicatedWorkerControlMsg {
98    /// Shutdown the worker.
99    Exit,
100}
101
102pub(crate) enum DedicatedWorkerScriptMsg {
103    /// Standard message from a worker.
104    CommonWorker(TrustedWorkerAddress, WorkerScriptMsg),
105    /// Wake-up call from the task queue.
106    WakeUp,
107}
108
109pub(crate) enum MixedMessage {
110    Worker(DedicatedWorkerScriptMsg),
111    Devtools(DevtoolScriptControlMsg),
112    Control(DedicatedWorkerControlMsg),
113    Timer,
114}
115
116impl QueuedTaskConversion for DedicatedWorkerScriptMsg {
117    fn task_source_name(&self) -> Option<&TaskSourceName> {
118        let common_worker_msg = match self {
119            DedicatedWorkerScriptMsg::CommonWorker(_, common_worker_msg) => common_worker_msg,
120            _ => return None,
121        };
122        let script_msg = match common_worker_msg {
123            WorkerScriptMsg::Common(script_msg) => script_msg,
124            _ => return None,
125        };
126        match script_msg {
127            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, source_name) => {
128                Some(source_name)
129            },
130            _ => None,
131        }
132    }
133
134    fn pipeline_id(&self) -> Option<PipelineId> {
135        // Workers always return None, since the pipeline_id is only used to check for document activity,
136        // and this check does not apply to worker event-loops.
137        None
138    }
139
140    fn into_queued_task(self) -> Option<QueuedTask> {
141        let (worker, common_worker_msg) = match self {
142            DedicatedWorkerScriptMsg::CommonWorker(worker, common_worker_msg) => {
143                (worker, common_worker_msg)
144            },
145            _ => return None,
146        };
147        let script_msg = match common_worker_msg {
148            WorkerScriptMsg::Common(script_msg) => script_msg,
149            _ => return None,
150        };
151        let (event_category, task, pipeline_id, task_source) = match script_msg {
152            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
153                (category, boxed, pipeline_id, task_source)
154            },
155            _ => return None,
156        };
157        Some(QueuedTask {
158            worker: Some(worker),
159            event_category,
160            task,
161            pipeline_id,
162            task_source,
163        })
164    }
165
166    fn from_queued_task(queued_task: QueuedTask) -> Self {
167        let script_msg = CommonScriptMsg::Task(
168            queued_task.event_category,
169            queued_task.task,
170            queued_task.pipeline_id,
171            queued_task.task_source,
172        );
173        DedicatedWorkerScriptMsg::CommonWorker(
174            queued_task.worker.unwrap(),
175            WorkerScriptMsg::Common(script_msg),
176        )
177    }
178
179    fn inactive_msg() -> Self {
180        // Inactive is only relevant in the context of a browsing-context event-loop.
181        panic!("Workers should never receive messages marked as inactive");
182    }
183
184    fn wake_up_msg() -> Self {
185        DedicatedWorkerScriptMsg::WakeUp
186    }
187
188    fn is_wake_up(&self) -> bool {
189        matches!(self, DedicatedWorkerScriptMsg::WakeUp)
190    }
191}
192
193unsafe_no_jsmanaged_fields!(TaskQueue<DedicatedWorkerScriptMsg>);
194
195// https://html.spec.whatwg.org/multipage/#dedicatedworkerglobalscope
196#[dom_struct]
197pub(crate) struct DedicatedWorkerGlobalScope {
198    workerglobalscope: WorkerGlobalScope,
199    /// The [`WebViewId`] of the `WebView` that this worker is associated with.
200    #[no_trace]
201    webview_id: WebViewId,
202    #[ignore_malloc_size_of = "Defined in std"]
203    task_queue: TaskQueue<DedicatedWorkerScriptMsg>,
204    own_sender: Sender<DedicatedWorkerScriptMsg>,
205    worker: DomRefCell<Option<TrustedWorkerAddress>>,
206    /// Sender to the parent thread.
207    parent_event_loop_sender: ScriptEventLoopSender,
208    #[ignore_malloc_size_of = "ImageCache"]
209    #[no_trace]
210    image_cache: Arc<dyn ImageCache>,
211    #[no_trace]
212    browsing_context: Option<BrowsingContextId>,
213    /// A receiver of control messages,
214    /// currently only used to signal shutdown.
215    #[no_trace]
216    control_receiver: Receiver<DedicatedWorkerControlMsg>,
217    #[no_trace]
218    queued_worker_tasks: DomRefCell<Vec<MessageData>>,
219}
220
221impl WorkerEventLoopMethods for DedicatedWorkerGlobalScope {
222    type WorkerMsg = DedicatedWorkerScriptMsg;
223    type ControlMsg = DedicatedWorkerControlMsg;
224    type Event = MixedMessage;
225
226    fn task_queue(&self) -> &TaskQueue<DedicatedWorkerScriptMsg> {
227        &self.task_queue
228    }
229
230    fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
231        self.handle_mixed_message(event, cx)
232    }
233
234    fn handle_worker_post_event(
235        &self,
236        worker: &TrustedWorkerAddress,
237    ) -> Option<AutoWorkerReset<'_>> {
238        let ar = AutoWorkerReset::new(self, worker.clone());
239        Some(ar)
240    }
241
242    fn from_control_msg(msg: DedicatedWorkerControlMsg) -> MixedMessage {
243        MixedMessage::Control(msg)
244    }
245
246    fn from_worker_msg(msg: DedicatedWorkerScriptMsg) -> MixedMessage {
247        MixedMessage::Worker(msg)
248    }
249
250    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
251        MixedMessage::Devtools(msg)
252    }
253
254    fn from_timer_msg() -> MixedMessage {
255        MixedMessage::Timer
256    }
257
258    fn control_receiver(&self) -> &Receiver<DedicatedWorkerControlMsg> {
259        &self.control_receiver
260    }
261}
262
263impl DedicatedWorkerGlobalScope {
264    #[allow(clippy::too_many_arguments)]
265    fn new_inherited(
266        init: WorkerGlobalScopeInit,
267        webview_id: WebViewId,
268        worker_name: DOMString,
269        worker_type: WorkerType,
270        worker_url: ServoUrl,
271        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
272        runtime: Runtime,
273        parent_event_loop_sender: ScriptEventLoopSender,
274        own_sender: Sender<DedicatedWorkerScriptMsg>,
275        receiver: Receiver<DedicatedWorkerScriptMsg>,
276        closing: Arc<AtomicBool>,
277        image_cache: Arc<dyn ImageCache>,
278        browsing_context: Option<BrowsingContextId>,
279        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
280        control_receiver: Receiver<DedicatedWorkerControlMsg>,
281        insecure_requests_policy: InsecureRequestsPolicy,
282        font_context: Option<Arc<FontContext>>,
283    ) -> DedicatedWorkerGlobalScope {
284        DedicatedWorkerGlobalScope {
285            workerglobalscope: WorkerGlobalScope::new_inherited(
286                init,
287                worker_name,
288                worker_type,
289                worker_url,
290                runtime,
291                from_devtools_receiver,
292                closing,
293                #[cfg(feature = "webgpu")]
294                gpu_id_hub,
295                insecure_requests_policy,
296                font_context,
297                None,
298            ),
299            webview_id,
300            task_queue: TaskQueue::new(receiver, own_sender.clone()),
301            own_sender,
302            parent_event_loop_sender,
303            worker: DomRefCell::new(None),
304            image_cache,
305            browsing_context,
306            control_receiver,
307            queued_worker_tasks: Default::default(),
308        }
309    }
310
311    #[expect(clippy::too_many_arguments)]
312    pub(crate) fn new(
313        init: WorkerGlobalScopeInit,
314        webview_id: WebViewId,
315        worker_name: DOMString,
316        worker_type: WorkerType,
317        worker_url: ServoUrl,
318        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
319        runtime: Runtime,
320        parent_event_loop_sender: ScriptEventLoopSender,
321        own_sender: Sender<DedicatedWorkerScriptMsg>,
322        receiver: Receiver<DedicatedWorkerScriptMsg>,
323        closing: Arc<AtomicBool>,
324        image_cache: Arc<dyn ImageCache>,
325        browsing_context: Option<BrowsingContextId>,
326        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
327        control_receiver: Receiver<DedicatedWorkerControlMsg>,
328        insecure_requests_policy: InsecureRequestsPolicy,
329        font_context: Option<Arc<FontContext>>,
330        debugger_global: &DebuggerGlobalScope,
331        cx: &mut js::context::JSContext,
332    ) -> DomRoot<DedicatedWorkerGlobalScope> {
333        let scope = Box::new(DedicatedWorkerGlobalScope::new_inherited(
334            init,
335            webview_id,
336            worker_name,
337            worker_type,
338            worker_url,
339            from_devtools_receiver,
340            runtime,
341            parent_event_loop_sender,
342            own_sender,
343            receiver,
344            closing,
345            image_cache,
346            browsing_context,
347            #[cfg(feature = "webgpu")]
348            gpu_id_hub,
349            control_receiver,
350            insecure_requests_policy,
351            font_context,
352        ));
353        let scope = DedicatedWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
354            cx,
355            &scope.origin(),
356            scope,
357        );
358        scope
359            .upcast::<WorkerGlobalScope>()
360            .init_debugger_global(debugger_global, cx);
361
362        scope
363    }
364
365    /// <https://html.spec.whatwg.org/multipage/#run-a-worker>
366    #[expect(unsafe_code)]
367    #[allow(clippy::too_many_arguments)]
368    pub(crate) fn run_worker_scope(
369        mut init: WorkerGlobalScopeInit,
370        webview_id: WebViewId,
371        worker_url: UrlWithBlobClaim,
372        from_devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
373        worker: TrustedWorkerAddress,
374        parent_event_loop_sender: ScriptEventLoopSender,
375        own_sender: Sender<DedicatedWorkerScriptMsg>,
376        receiver: Receiver<DedicatedWorkerScriptMsg>,
377        worker_load_origin: WorkerScriptLoadOrigin,
378        worker_options: &WorkerOptions,
379        closing: Arc<AtomicBool>,
380        image_cache: Arc<dyn ImageCache>,
381        browsing_context: Option<BrowsingContextId>,
382        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
383        control_receiver: Receiver<DedicatedWorkerControlMsg>,
384        context_sender: Sender<ThreadSafeJSContext>,
385        insecure_requests_policy: InsecureRequestsPolicy,
386        policy_container: PolicyContainer,
387        font_context: Option<Arc<FontContext>>,
388    ) -> JoinHandle<()> {
389        let event_loop_id = ScriptEventLoopId::installed()
390            .expect("Should always be in a ScriptThread or in a dedicated worker");
391        let current_global = GlobalScope::current().expect("No current global object");
392        let origin = current_global.origin().immutable().clone();
393        let referrer = current_global.get_referrer();
394        let parent = current_global.runtime_handle();
395        let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin();
396        let is_secure_context = current_global.is_secure_context();
397        let is_nested_browsing_context = current_global.is_nested_browsing_context();
398
399        let worker_type = worker_options.type_;
400        let worker_name = worker_options.name.to_string();
401        let credentials = worker_options.credentials.convert();
402
403        thread::Builder::new()
404            .name(format!("WW:{}", worker_url.debug_compact()))
405            .spawn(move || {
406                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
407                ScriptEventLoopId::install(event_loop_id);
408
409                let WorkerScriptLoadOrigin {
410                    referrer_url,
411                    pipeline_id,
412                    ..
413                } = worker_load_origin;
414
415                let referrer = referrer_url.map(Referrer::ReferrerUrl).unwrap_or(referrer);
416
417                let request_client = RequestClient {
418                    preloaded_resources: PreloadedResources::default(),
419                    policy_container: RequestPolicyContainer::PolicyContainer(
420                        policy_container.clone(),
421                    ),
422                    origin: Origin::Origin(origin.clone()),
423                    is_nested_browsing_context,
424                    insecure_requests_policy,
425                };
426
427                let event_loop_sender = ScriptEventLoopSender::DedicatedWorker {
428                    sender: own_sender.clone(),
429                    main_thread_worker: worker.clone(),
430                };
431
432                let runtime = unsafe {
433                    Runtime::new_with_parent(Some(parent), Some(event_loop_sender.clone()))
434                };
435                // SAFETY: We are in a new thread, so this first cx.
436                // It is OK to have it separated of runtime here,
437                // because it will never outlive it (runtime destruction happens at the end of this function)
438                let mut cx = unsafe { runtime.cx() };
439                let cx = &mut cx;
440                let debugger_global = DebuggerGlobalScope::new(
441                    pipeline_id,
442                    init.to_devtools_sender.clone(),
443                    init.from_devtools_sender
444                        .clone()
445                        .expect("Guaranteed by Worker::Constructor"),
446                    init.mem_profiler_chan.clone(),
447                    init.time_profiler_chan.clone(),
448                    init.script_to_constellation_chan.clone(),
449                    init.script_to_embedder_chan.clone(),
450                    init.resource_threads.clone(),
451                    init.storage_threads.clone(),
452                    #[cfg(feature = "webgpu")]
453                    gpu_id_hub.clone(),
454                    cx,
455                );
456                debugger_global.execute(cx);
457
458                let context_for_interrupt = runtime.thread_safe_js_context();
459                let _ = context_sender.send(context_for_interrupt);
460
461                let devtools_mpsc_port = from_devtools_receiver.route_preserving_errors();
462
463                // Step 8 "Set up a worker environment settings object [...]"
464                //
465                // <https://html.spec.whatwg.org/multipage/#script-settings-for-workers>
466                //
467                // > The origin: Return a unique opaque origin if `worker global
468                // > scope`'s url's scheme is "data", and `inherited origin`
469                // > otherwise.
470                if worker_url.scheme() == "data" {
471                    // Workers created from a data: url are secure if they were created from secure contexts
472                    if is_secure_context {
473                        init.origin = ImmutableOrigin::new_opaque_data_url_worker();
474                    } else {
475                        init.origin = ImmutableOrigin::new_opaque();
476                    }
477                }
478
479                let worker_id = init.worker_id;
480                let devtools_enabled = init.to_devtools_sender.is_some();
481                let global = DedicatedWorkerGlobalScope::new(
482                    init,
483                    webview_id,
484                    worker_name.into(),
485                    worker_type,
486                    worker_url.url(),
487                    devtools_mpsc_port,
488                    runtime,
489                    parent_event_loop_sender,
490                    own_sender,
491                    receiver,
492                    closing,
493                    image_cache,
494                    browsing_context,
495                    #[cfg(feature = "webgpu")]
496                    gpu_id_hub,
497                    control_receiver,
498                    insecure_requests_policy,
499                    font_context,
500                    &debugger_global,
501                    cx,
502                );
503                if devtools_enabled {
504                    debugger_global.fire_add_debuggee(
505                        cx,
506                        global.upcast(),
507                        pipeline_id,
508                        Some(worker_id),
509                    );
510                }
511                let scope = global.upcast::<WorkerGlobalScope>();
512                let global_scope = global.upcast::<GlobalScope>();
513
514                let fetch_client = ModuleFetchClient {
515                    insecure_requests_policy,
516                    has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
517                    policy_container,
518                    client: request_client,
519                    pipeline_id,
520                    origin,
521                };
522
523                // Step 12. Obtain script by switching on options["type"]:
524                {
525                    let _ar = AutoWorkerReset::new(&global, worker.clone());
526                    match worker_type {
527                        WorkerType::Classic => {
528                            fetch_a_classic_worker_script(
529                                scope,
530                                worker_url,
531                                fetch_client,
532                                Destination::Worker,
533                                Some(webview_id),
534                                referrer,
535                            );
536                        },
537                        WorkerType::Module => {
538                            let worker_scope = DomRoot::from_ref(scope);
539                            fetch_a_module_worker_script_graph(
540                                cx,
541                                global_scope,
542                                worker_url.url(),
543                                fetch_client,
544                                Destination::Worker,
545                                referrer,
546                                credentials,
547                                move |cx, module_tree| {
548                                    worker_scope.on_complete(cx, module_tree.map(Script::Module));
549                                },
550                            );
551                        },
552                    }
553
554                    let reporter_name = format!("dedicated-worker-reporter-{}", worker_id);
555                    scope
556                        .upcast::<GlobalScope>()
557                        .mem_profiler_chan()
558                        .run_with_memory_reporting(
559                            || {
560                                // Step 27, Run the responsible event loop specified
561                                // by inside settings until it is destroyed.
562                                // The worker processing model remains on this step
563                                // until the event loop is destroyed,
564                                // which happens after the closing flag is set to true.
565                                while !scope.is_closing() {
566                                    run_worker_event_loop(&*global, Some(&worker), cx);
567                                }
568                            },
569                            reporter_name,
570                            event_loop_sender,
571                            CommonScriptMsg::CollectReports,
572                        );
573                }
574
575                scope.clear_js_runtime();
576            })
577            .expect("Thread spawning failed")
578    }
579
580    pub(crate) fn webview_id(&self) -> WebViewId {
581        self.webview_id
582    }
583
584    /// The non-None value of the `worker` field can contain a rooted [`TrustedWorkerAddress`]
585    /// version of the main thread's worker object. This is set while handling messages and then
586    /// unset otherwise, ensuring that the main thread object can be garbage collected. See
587    /// [`AutoWorkerReset`].
588    fn replace_worker(
589        &self,
590        new_worker: Option<TrustedWorkerAddress>,
591    ) -> Option<TrustedWorkerAddress> {
592        let old_worker = std::mem::replace(&mut *self.worker.borrow_mut(), new_worker);
593
594        // The `TaskManager` maintains a handle to this `DedicatedWorkerGlobalScope`'s
595        // event_loop_sender, which might in turn have a `TrustedWorkerAddress` rooting of the main
596        // thread's worker, which prevents garbage collection. Resetting it here ensures that
597        // garbage collection of the main thread object can happen again (assuming the new `worker`
598        // is `None`).
599        self.upcast::<GlobalScope>()
600            .task_manager()
601            .set_sender(self.event_loop_sender());
602
603        old_worker
604    }
605
606    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
607        self.image_cache.clone()
608    }
609
610    pub(crate) fn event_loop_sender(&self) -> Option<ScriptEventLoopSender> {
611        Some(ScriptEventLoopSender::DedicatedWorker {
612            sender: self.own_sender.clone(),
613            main_thread_worker: self.worker.borrow().clone()?,
614        })
615    }
616
617    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
618        let (sender, receiver) = unbounded();
619        let main_thread_worker = self.worker.borrow().as_ref().unwrap().clone();
620        (
621            ScriptEventLoopSender::DedicatedWorker {
622                sender,
623                main_thread_worker,
624            },
625            ScriptEventLoopReceiver::DedicatedWorker(receiver),
626        )
627    }
628
629    pub(crate) fn fire_queued_messages(&self, cx: &mut JSContext) {
630        let queue: Vec<_> = self.queued_worker_tasks.borrow_mut().drain(..).collect();
631        for msg in queue {
632            if self.upcast::<WorkerGlobalScope>().is_closing() {
633                return;
634            }
635            self.dispatch_message_event(cx, msg);
636        }
637    }
638
639    fn dispatch_message_event(&self, cx: &mut JSContext, msg: MessageData) {
640        let scope = self.upcast::<WorkerGlobalScope>();
641        let target = self.upcast();
642        let mut realm = enter_auto_realm(cx, self);
643        let cx = &mut realm;
644        rooted!(&in(cx) let mut message = UndefinedValue());
645        if let Ok(ports) =
646            structuredclone::read(cx, scope.upcast(), *msg.data, message.handle_mut())
647        {
648            MessageEvent::dispatch_jsval(
649                cx,
650                target,
651                scope.upcast(),
652                message.handle(),
653                Some(&msg.origin.ascii_serialization()),
654                None,
655                ports,
656            );
657        } else {
658            MessageEvent::dispatch_error(cx, target, scope.upcast());
659        }
660    }
661
662    fn handle_script_event(&self, msg: WorkerScriptMsg, cx: &mut JSContext) {
663        match msg {
664            WorkerScriptMsg::DOMMessage(message_data) => {
665                if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
666                    self.dispatch_message_event(cx, message_data);
667                } else {
668                    self.queued_worker_tasks.borrow_mut().push(message_data);
669                }
670            },
671            WorkerScriptMsg::Common(msg) => {
672                self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
673            },
674        }
675    }
676
677    fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
678        if self.upcast::<WorkerGlobalScope>().is_closing() {
679            return false;
680        }
681        // FIXME(#26324): `self.worker` is None in devtools messages.
682        match msg {
683            MixedMessage::Devtools(msg) => self
684                .upcast::<WorkerGlobalScope>()
685                .handle_devtools_message(msg, cx),
686            MixedMessage::Worker(DedicatedWorkerScriptMsg::CommonWorker(linked_worker, msg)) => {
687                let _ar = AutoWorkerReset::new(self, linked_worker);
688                self.handle_script_event(msg, cx);
689            },
690            MixedMessage::Worker(DedicatedWorkerScriptMsg::WakeUp) => {},
691            MixedMessage::Control(DedicatedWorkerControlMsg::Exit) => {
692                return false;
693            },
694            MixedMessage::Timer => {},
695        }
696        true
697    }
698
699    /// Step 7.2 of <https://html.spec.whatwg.org/multipage/#report-an-exception>
700    pub(crate) fn forward_error_to_worker_object(&self, error_info: ErrorInfo) {
701        // Step 7.2.1. Let workerObject be the Worker object associated with global.
702        let worker = self.worker.borrow().as_ref().unwrap().clone();
703        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
704        let task = Box::new(task!(forward_error_to_worker_object: move |cx| {
705            let worker = worker.root();
706            let global = worker.global();
707
708            // Step 7.2.2. Set notHandled to the result of firing an event named error at workerObject, using ErrorEvent,
709            // with the cancelable attribute initialized to true, and additional attributes initialized according to errorInfo.
710            let event = ErrorEvent::new(
711                cx,
712                &global,
713                atom!("error"),
714                EventBubbles::DoesNotBubble,
715                EventCancelable::Cancelable,
716                error_info.message.as_str().into(),
717                error_info.filename.as_str().into(),
718                error_info.lineno,
719                error_info.column,
720                HandleValue::null(),
721            );
722
723            // Step 7.2.3. If notHandled is true, then report exception for workerObject's relevant global object with omitError set to true.
724            if event.upcast::<Event>().fire(cx, worker.upcast::<EventTarget>()) {
725                global.report_an_error(cx, error_info, HandleValue::null());
726            }
727        }));
728        self.parent_event_loop_sender
729            .send(CommonScriptMsg::Task(
730                WorkerEvent,
731                task,
732                Some(pipeline_id),
733                TaskSourceName::DOMManipulation,
734            ))
735            .unwrap();
736    }
737
738    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
739    fn post_message_impl(
740        &self,
741        cx: &mut JSContext,
742        message: HandleValue,
743        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
744    ) -> ErrorResult {
745        let data = structuredclone::write(cx, message, Some(transfer))?;
746        let worker = self.worker.borrow().as_ref().unwrap().clone();
747        let global_scope = self.upcast::<GlobalScope>();
748        let pipeline_id = global_scope.pipeline_id();
749        let task = Box::new(task!(post_worker_message: move |cx| {
750            Worker::handle_message(worker, data, cx);
751        }));
752        self.parent_event_loop_sender
753            .send(CommonScriptMsg::Task(
754                WorkerEvent,
755                task,
756                Some(pipeline_id),
757                TaskSourceName::DOMManipulation,
758            ))
759            .expect("Sending to parent failed");
760        Ok(())
761    }
762
763    pub(crate) fn browsing_context(&self) -> Option<BrowsingContextId> {
764        self.browsing_context
765    }
766
767    pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
768        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
769        self.parent_event_loop_sender
770            .send(CommonScriptMsg::ReportCspViolations(
771                pipeline_id,
772                violations,
773            ))
774            .unwrap_or_else(|error| {
775                log::warn!("Failed to send CSP violations to parent event loop: {error}");
776            });
777    }
778
779    pub(crate) fn forward_simple_error_at_worker(&self) {
780        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
781        let worker = self.worker.borrow().clone().expect("worker must be set");
782        self.parent_event_loop_sender
783            .send(CommonScriptMsg::Task(
784                WorkerEvent,
785                Box::new(SimpleWorkerErrorHandler::new(worker)),
786                Some(pipeline_id),
787                TaskSourceName::DOMManipulation,
788            ))
789            .expect("Sending to parent failed");
790    }
791}
792
793/// <https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-script>
794pub(crate) fn fetch_a_classic_worker_script(
795    workerscope: &WorkerGlobalScope,
796    url_with_blob_lock: UrlWithBlobClaim,
797    fetch_client: ModuleFetchClient,
798    destination: Destination,
799    webview_id: Option<WebViewId>,
800    referrer: Referrer,
801) {
802    // Step 1. Let request be a new request whose URL is url,
803    let request = RequestBuilder::new(webview_id, url_with_blob_lock.clone(), referrer)
804        // client is fetchClient,
805        .insecure_requests_policy(fetch_client.insecure_requests_policy)
806        .has_trustworthy_ancestor_origin(fetch_client.has_trustworthy_ancestor_origin)
807        .policy_container(fetch_client.policy_container.clone())
808        .client(fetch_client.client)
809        .pipeline_id(Some(fetch_client.pipeline_id))
810        .origin(fetch_client.origin)
811        // destination is destination,
812        .destination(destination)
813        // TODO initiator type is "other",
814        // mode is "same-origin",
815        .mode(RequestMode::SameOrigin)
816        // credentials mode is "same-origin",
817        .credentials_mode(CredentialsMode::CredentialsSameOrigin)
818        // parser metadata is "not parser-inserted",
819        .parser_metadata(ParserMetadata::NotParserInserted)
820        // and whose use-URL-credentials flag is set.
821        .use_url_credentials(true);
822
823    let context = ScriptFetchContext::new(
824        Trusted::new(workerscope),
825        url_with_blob_lock.url(),
826        fetch_client.policy_container,
827    );
828    let global = workerscope.upcast::<GlobalScope>();
829    let task_source = global.task_manager().networking_task_source().to_sendable();
830    global.fetch(request, context, task_source);
831}
832
833impl DedicatedWorkerGlobalScopeMethods<crate::DomTypeHolder> for DedicatedWorkerGlobalScope {
834    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-name>
835    fn Name(&self) -> DOMString {
836        self.workerglobalscope.worker_name()
837    }
838
839    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
840    fn PostMessage(
841        &self,
842        cx: &mut JSContext,
843        message: HandleValue,
844        transfer: CustomAutoRooterGuard<Vec<*mut JSObject>>,
845    ) -> ErrorResult {
846        self.post_message_impl(cx, message, transfer)
847    }
848
849    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage>
850    fn PostMessage_(
851        &self,
852        cx: &mut JSContext,
853        message: HandleValue,
854        options: RootedTraceableBox<StructuredSerializeOptions>,
855    ) -> ErrorResult {
856        let mut rooted = CustomAutoRooter::new(
857            options
858                .transfer
859                .iter()
860                .map(|js: &RootedTraceableBox<Heap<*mut JSObject>>| js.get())
861                .collect(),
862        );
863        #[expect(unsafe_code)]
864        let guard = unsafe { CustomAutoRooterGuard::new(cx.raw_cx(), &mut rooted) };
865        self.post_message_impl(cx, message, guard)
866    }
867
868    /// <https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-close>
869    fn Close(&self) {
870        // Step 2
871        self.upcast::<WorkerGlobalScope>().close();
872    }
873
874    // https://html.spec.whatwg.org/multipage/#handler-dedicatedworkerglobalscope-onmessage
875    event_handler!(message, GetOnmessage, SetOnmessage);
876
877    // https://html.spec.whatwg.org/multipage/#handler-dedicatedworkerglobalscope-onmessageerror
878    event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
879}
880
881impl HasOrigin for DedicatedWorkerGlobalScope {
882    fn origin(&self) -> MutableOrigin {
883        self.upcast::<WorkerGlobalScope>().origin()
884    }
885}