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