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