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