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