Skip to main content

script/dom/workers/
sharedworkerglobalscope.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::collections::VecDeque;
6use std::io;
7use std::sync::Arc;
8use std::sync::atomic::AtomicBool;
9use std::thread::{self, JoinHandle};
10
11use content_security_policy::Violation;
12use crossbeam_channel::{Receiver, Sender, unbounded};
13use devtools_traits::DevtoolScriptControlMsg;
14use dom_struct::dom_struct;
15use fonts::FontContext;
16use js::context::JSContext;
17use js::jsval::UndefinedValue;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::image_cache::ImageCache;
20use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
21use net_traits::request::{
22    CredentialsMode, Destination, InsecureRequestsPolicy, Origin, PreloadedResources, Referrer,
23    RequestClient,
24};
25use script_bindings::cell::DomRefCell;
26use script_bindings::conversions::SafeToJSValConvertible;
27use script_bindings::interfaces::HasOrigin;
28use servo_base::generic_channel::{GenericReceiver, RoutedReceiver};
29use servo_base::id::{BrowsingContextId, ScriptEventLoopId, WebViewId};
30use servo_constellation_traits::{MessagePortImpl, WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
31use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
32use style::thread_state::{self, ThreadState};
33use stylo_atoms::Atom;
34use uuid::Uuid;
35
36use crate::dom::abstractworker::{SimpleWorkerErrorHandler, WorkerScriptMsg};
37use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
38use crate::dom::bindings::codegen::Bindings::SharedWorkerGlobalScopeBinding;
39use crate::dom::bindings::codegen::Bindings::SharedWorkerGlobalScopeBinding::SharedWorkerGlobalScopeMethods;
40use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
41use crate::dom::bindings::codegen::UnionTypes::WindowProxyOrMessagePortOrServiceWorker;
42use crate::dom::bindings::inheritance::Castable;
43use crate::dom::bindings::refcounted::Trusted;
44use crate::dom::bindings::root::{Dom, DomRoot};
45use crate::dom::bindings::str::DOMString;
46use crate::dom::bindings::trace::CustomTraceable;
47use crate::dom::dedicatedworkerglobalscope::fetch_a_classic_worker_script;
48use crate::dom::event::Event;
49use crate::dom::event::messageevent::MessageEvent;
50use crate::dom::eventtarget::EventTarget;
51use crate::dom::globalscope::GlobalScope;
52use crate::dom::html::htmlscriptelement::Script;
53use crate::dom::messageport::MessagePort;
54use crate::dom::sharedworker::{SharedWorker, SharedWorkerStorageKey, TrustedSharedWorkerAddress};
55use crate::dom::types::DebuggerGlobalScope;
56#[cfg(feature = "webgpu")]
57use crate::dom::webgpu::identityhub::IdentityHub;
58use crate::dom::workerglobalscope::WorkerGlobalScope;
59use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
60use crate::script_module::{ModuleFetchClient, fetch_a_module_worker_script_graph};
61use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
62use crate::script_runtime::{CanGc, Runtime};
63use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
64use crate::task_source::TaskSourceName;
65
66pub(crate) enum SharedWorkerScriptMsg {
67    CommonWorker(WorkerScriptMsg),
68    Connect(MessagePortImpl),
69    WakeUp,
70}
71
72#[allow(dead_code)]
73pub(crate) enum SharedWorkerControlMsg {
74    Exit,
75}
76
77pub(crate) enum MixedMessage {
78    SharedWorker(SharedWorkerScriptMsg),
79    Devtools(DevtoolScriptControlMsg),
80    Control(SharedWorkerControlMsg),
81    Timer,
82}
83
84struct SharedWorkerRegistrationCleanup {
85    registration_id: Uuid,
86}
87
88impl Drop for SharedWorkerRegistrationCleanup {
89    fn drop(&mut self) {
90        SharedWorker::unregister_shared_worker(self.registration_id);
91    }
92}
93
94impl QueuedTaskConversion for SharedWorkerScriptMsg {
95    fn task_source_name(&self) -> Option<&TaskSourceName> {
96        let script_msg = match self {
97            SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
98            _ => return None,
99        };
100        match script_msg {
101            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
102                Some(task_source)
103            },
104            _ => None,
105        }
106    }
107
108    fn pipeline_id(&self) -> Option<servo_base::id::PipelineId> {
109        None
110    }
111
112    fn into_queued_task(self) -> Option<QueuedTask> {
113        let script_msg = match self {
114            SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
115            _ => return None,
116        };
117        let (event_category, task, pipeline_id, task_source) = match script_msg {
118            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
119                (category, boxed, pipeline_id, task_source)
120            },
121            _ => return None,
122        };
123        Some(QueuedTask {
124            worker: None,
125            event_category,
126            task,
127            pipeline_id,
128            task_source,
129        })
130    }
131
132    fn from_queued_task(queued_task: QueuedTask) -> Self {
133        let script_msg = CommonScriptMsg::Task(
134            queued_task.event_category,
135            queued_task.task,
136            queued_task.pipeline_id,
137            queued_task.task_source,
138        );
139        SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
140    }
141
142    fn inactive_msg() -> Self {
143        panic!("Workers should never receive messages marked as inactive");
144    }
145
146    fn wake_up_msg() -> Self {
147        SharedWorkerScriptMsg::WakeUp
148    }
149
150    fn is_wake_up(&self) -> bool {
151        matches!(self, SharedWorkerScriptMsg::WakeUp)
152    }
153}
154
155unsafe_no_jsmanaged_fields!(TaskQueue<SharedWorkerScriptMsg>);
156
157// https://html.spec.whatwg.org/multipage/#shared-workers-and-the-sharedworkerglobalscope-interface
158#[dom_struct]
159pub(crate) struct SharedWorkerGlobalScope {
160    workerglobalscope: WorkerGlobalScope,
161    /// The [`WebViewId`] of the `WebView` that this worker is associated with.
162    #[no_trace]
163    webview_id: WebViewId,
164    #[ignore_malloc_size_of = "Defined in std"]
165    task_queue: TaskQueue<SharedWorkerScriptMsg>,
166    own_sender: Sender<SharedWorkerScriptMsg>,
167    worker: DomRefCell<Option<TrustedSharedWorkerAddress>>,
168    parent_event_loop_sender: ScriptEventLoopSender,
169    #[ignore_malloc_size_of = "ImageCache"]
170    #[no_trace]
171    image_cache: Arc<dyn ImageCache>,
172    #[no_trace]
173    browsing_context: Option<BrowsingContextId>,
174    // Shared workers receive message ports through `connect` events on their `SharedWorkerGlobalScope` object for each connection.
175    pending_connect: DomRefCell<VecDeque<Dom<MessagePort>>>,
176    #[no_trace]
177    control_receiver: Receiver<SharedWorkerControlMsg>,
178    debugger_global: Dom<DebuggerGlobalScope>,
179    // A `SharedWorkerGlobalScope` object has associated constructor origin (an origin), constructor URL (a URL record), and credentials (a credentials mode), and extended lifetime (a boolean).
180    #[no_trace]
181    storage_key: SharedWorkerStorageKey,
182    #[no_trace]
183    constructor_origin: ImmutableOrigin,
184    #[no_trace]
185    constructor_url: ServoUrl,
186    #[no_trace]
187    credentials: CredentialsMode,
188    extended_lifetime: bool,
189    #[no_trace]
190    registration_id: Uuid,
191}
192
193impl WorkerEventLoopMethods for SharedWorkerGlobalScope {
194    type WorkerMsg = SharedWorkerScriptMsg;
195    type ControlMsg = SharedWorkerControlMsg;
196    type Event = MixedMessage;
197
198    fn task_queue(&self) -> &TaskQueue<SharedWorkerScriptMsg> {
199        &self.task_queue
200    }
201
202    fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
203        self.handle_mixed_message(event, cx)
204    }
205
206    fn handle_worker_post_event(
207        &self,
208        _worker: &crate::dom::worker::TrustedWorkerAddress,
209    ) -> Option<crate::dom::dedicatedworkerglobalscope::AutoWorkerReset<'_>> {
210        None
211    }
212
213    fn from_control_msg(msg: SharedWorkerControlMsg) -> MixedMessage {
214        MixedMessage::Control(msg)
215    }
216
217    fn from_worker_msg(msg: SharedWorkerScriptMsg) -> MixedMessage {
218        MixedMessage::SharedWorker(msg)
219    }
220
221    fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
222        MixedMessage::Devtools(msg)
223    }
224
225    fn from_timer_msg() -> MixedMessage {
226        MixedMessage::Timer
227    }
228
229    fn control_receiver(&self) -> &Receiver<SharedWorkerControlMsg> {
230        &self.control_receiver
231    }
232}
233
234impl SharedWorkerGlobalScope {
235    #[allow(clippy::too_many_arguments)]
236    fn new_inherited(
237        init: WorkerGlobalScopeInit,
238        webview_id: WebViewId,
239        worker_name: DOMString,
240        worker_type: WorkerType,
241        worker_url: ServoUrl,
242        worker: TrustedSharedWorkerAddress,
243        parent_event_loop_sender: ScriptEventLoopSender,
244        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
245        runtime: Runtime,
246        own_sender: Sender<SharedWorkerScriptMsg>,
247        receiver: Receiver<SharedWorkerScriptMsg>,
248        closing: Arc<AtomicBool>,
249        image_cache: Arc<dyn ImageCache>,
250        browsing_context: Option<BrowsingContextId>,
251        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
252        control_receiver: Receiver<SharedWorkerControlMsg>,
253        insecure_requests_policy: InsecureRequestsPolicy,
254        font_context: Option<Arc<FontContext>>,
255        debugger_global: &DebuggerGlobalScope,
256        storage_key: SharedWorkerStorageKey,
257        constructor_origin: ImmutableOrigin,
258        constructor_url: ServoUrl,
259        credentials: CredentialsMode,
260        extended_lifetime: bool,
261        registration_id: Uuid,
262    ) -> SharedWorkerGlobalScope {
263        SharedWorkerGlobalScope {
264            workerglobalscope: WorkerGlobalScope::new_inherited(
265                init,
266                worker_name,
267                worker_type,
268                worker_url,
269                runtime,
270                from_devtools_receiver,
271                closing,
272                #[cfg(feature = "webgpu")]
273                gpu_id_hub,
274                insecure_requests_policy,
275                font_context,
276                Some(ScriptEventLoopSender::SharedWorker(own_sender.clone())),
277            ),
278            webview_id,
279            task_queue: TaskQueue::new(receiver, own_sender.clone()),
280            own_sender,
281            worker: DomRefCell::new(Some(worker)),
282            parent_event_loop_sender,
283            image_cache,
284            browsing_context,
285            pending_connect: DomRefCell::new(VecDeque::new()),
286            control_receiver,
287            debugger_global: Dom::from_ref(debugger_global),
288            storage_key,
289            constructor_origin,
290            constructor_url,
291            credentials,
292            extended_lifetime,
293            registration_id,
294        }
295    }
296
297    #[allow(clippy::too_many_arguments)]
298    pub(crate) fn new(
299        init: WorkerGlobalScopeInit,
300        webview_id: WebViewId,
301        worker_name: DOMString,
302        worker_type: WorkerType,
303        worker_url: ServoUrl,
304        worker: TrustedSharedWorkerAddress,
305        parent_event_loop_sender: ScriptEventLoopSender,
306        from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
307        runtime: Runtime,
308        own_sender: Sender<SharedWorkerScriptMsg>,
309        receiver: Receiver<SharedWorkerScriptMsg>,
310        closing: Arc<AtomicBool>,
311        image_cache: Arc<dyn ImageCache>,
312        browsing_context: Option<BrowsingContextId>,
313        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
314        control_receiver: Receiver<SharedWorkerControlMsg>,
315        insecure_requests_policy: InsecureRequestsPolicy,
316        font_context: Option<Arc<FontContext>>,
317        debugger_global: &DebuggerGlobalScope,
318        storage_key: SharedWorkerStorageKey,
319        constructor_origin: ImmutableOrigin,
320        constructor_url: ServoUrl,
321        credentials: CredentialsMode,
322        extended_lifetime: bool,
323        registration_id: Uuid,
324        cx: &mut js::context::JSContext,
325    ) -> DomRoot<SharedWorkerGlobalScope> {
326        let scope = Box::new(SharedWorkerGlobalScope::new_inherited(
327            init,
328            webview_id,
329            worker_name,
330            worker_type,
331            worker_url,
332            worker,
333            parent_event_loop_sender,
334            from_devtools_receiver,
335            runtime,
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            debugger_global,
347            storage_key,
348            constructor_origin,
349            constructor_url,
350            credentials,
351            extended_lifetime,
352            registration_id,
353        ));
354        SharedWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, &scope.origin(), 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_shared_worker_scope(
361        mut init: WorkerGlobalScopeInit,
362        webview_id: WebViewId,
363        browsing_context: Option<BrowsingContextId>,
364        worker_name: DOMString,
365        worker_type: WorkerType,
366        worker_url: UrlWithBlobClaim,
367        worker: TrustedSharedWorkerAddress,
368        parent_event_loop_sender: ScriptEventLoopSender,
369        from_devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
370        own_sender: Sender<SharedWorkerScriptMsg>,
371        receiver: Receiver<SharedWorkerScriptMsg>,
372        worker_load_origin: WorkerScriptLoadOrigin,
373        closing: Arc<AtomicBool>,
374        image_cache: Arc<dyn ImageCache>,
375        #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
376        control_receiver: Receiver<SharedWorkerControlMsg>,
377        setup_sender: Sender<bool>,
378        registration_receiver: Receiver<()>,
379        registration_id: Uuid,
380        credentials: CredentialsMode,
381        extended_lifetime: bool,
382        constructor_origin: ImmutableOrigin,
383        constructor_url: ServoUrl,
384        storage_key: SharedWorkerStorageKey,
385        insecure_requests_policy: InsecureRequestsPolicy,
386        policy_container: PolicyContainer,
387        font_context: Option<Arc<FontContext>>,
388    ) -> io::Result<JoinHandle<()>> {
389        let event_loop_id = ScriptEventLoopId::installed()
390            .expect("Should always be in a ScriptThread or in a worker");
391        let current_global = GlobalScope::current().expect("No current global object");
392        let origin = current_global.origin().immutable().clone();
393        let referrer = current_global.get_referrer();
394        let is_secure_context = current_global.is_secure_context();
395        let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin();
396        let is_nested_browsing_context = current_global.is_nested_browsing_context();
397        let worker_name = worker_name.to_string();
398
399        thread::Builder::new()
400            .name(format!("SWW:{}", worker_url.debug_compact()))
401            .spawn(move || {
402                // Step 4. Let agent be the result of obtaining a dedicated/shared worker agent
403                // given outside settings and is shared. Run the rest of these steps in that
404                // agent.
405                thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
406                ScriptEventLoopId::install(event_loop_id);
407
408                let WorkerScriptLoadOrigin {
409                    referrer_url,
410                    pipeline_id,
411                    ..
412                } = worker_load_origin;
413
414                let referrer = referrer_url.map(Referrer::ReferrerUrl).unwrap_or(referrer);
415
416                let request_client = RequestClient {
417                    preloaded_resources: PreloadedResources::default(),
418                    policy_container: RequestPolicyContainer::PolicyContainer(
419                        policy_container.clone(),
420                    ),
421                    origin: Origin::Origin(origin.clone()),
422                    is_nested_browsing_context,
423                    insecure_requests_policy,
424                };
425
426                let event_loop_sender = ScriptEventLoopSender::SharedWorker(own_sender.clone());
427
428                // Shared workers currently run on a dedicated script worker thread but
429                // still create their own JS runtime in that thread; this avoids child
430                // runtime lifetime coupling with the embedding script thread.
431                let runtime = Runtime::new(Some(event_loop_sender.clone()));
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 SharedWorker::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 devtools_mpsc_port = from_devtools_receiver.route_preserving_errors();
456
457                let worker_id = init.worker_id;
458                let devtools_enabled = init.to_devtools_sender.is_some();
459                // Step 3. Let origin be a unique opaque origin if worker global scope's url's scheme is "data"; otherwise outside settings's origin.
460                if worker_url.scheme() == "data" {
461                    if is_secure_context {
462                        init.origin = ImmutableOrigin::new_opaque_data_url_worker();
463                    } else {
464                        init.origin = ImmutableOrigin::new_opaque();
465                    }
466                }
467                // Step 8. Set worker global scope's name to options["name"].
468                // Step 10.3. Set worker global scope's type to options["type"].
469                let global = SharedWorkerGlobalScope::new(
470                    init,
471                    webview_id,
472                    worker_name.into(),
473                    worker_type,
474                    worker_url.url(),
475                    worker,
476                    parent_event_loop_sender,
477                    devtools_mpsc_port,
478                    runtime,
479                    own_sender,
480                    receiver,
481                    closing,
482                    image_cache,
483                    browsing_context,
484                    #[cfg(feature = "webgpu")]
485                    gpu_id_hub,
486                    control_receiver,
487                    insecure_requests_policy,
488                    font_context,
489                    &debugger_global,
490                    storage_key,
491                    constructor_origin,
492                    constructor_url,
493                    credentials,
494                    extended_lifetime,
495                    registration_id,
496                    cx,
497                );
498                let scope = global.upcast::<WorkerGlobalScope>();
499                let global_scope = global.upcast::<GlobalScope>();
500                // Step 11.5.2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
501                let worker_is_secure_context = global_scope.is_secure_context();
502                if devtools_enabled {
503                    debugger_global.fire_add_debuggee(
504                        cx,
505                        global_scope,
506                        pipeline_id,
507                        Some(worker_id),
508                    );
509                }
510
511                if setup_sender.send(worker_is_secure_context).is_err() {
512                    scope.clear_js_runtime();
513                    return;
514                }
515
516                if registration_receiver.recv().is_err() {
517                    scope.clear_js_runtime();
518                    return;
519                }
520                // Keep cleanup guard alive for the remainder of worker execution.
521                // It is intentionally unused because its Drop unregisters the worker.
522                let _registration_cleanup = SharedWorkerRegistrationCleanup { registration_id };
523
524                let fetch_client = ModuleFetchClient {
525                    insecure_requests_policy,
526                    has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
527                    policy_container,
528                    client: request_client,
529                    pipeline_id,
530                    origin,
531                };
532
533                // Step 11. Let destination be "sharedworker" if is shared is true, and
534                // "worker" otherwise.
535                // Step 12. Obtain script by switching on options["type"]:
536                match worker_type {
537                    WorkerType::Classic => {
538                        fetch_a_classic_worker_script(
539                            scope,
540                            worker_url,
541                            fetch_client,
542                            Destination::SharedWorker,
543                            Some(webview_id),
544                            referrer,
545                        );
546                    },
547                    WorkerType::Module => {
548                        let worker_scope = DomRoot::from_ref(scope);
549                        fetch_a_module_worker_script_graph(
550                            cx,
551                            global_scope,
552                            worker_url.url(),
553                            fetch_client,
554                            Destination::SharedWorker,
555                            referrer,
556                            credentials,
557                            move |cx, module_tree| {
558                                worker_scope.on_complete(cx, module_tree.map(Script::Module));
559                            },
560                        );
561                    },
562                }
563
564                let reporter_name = format!("shared-worker-reporter-{}", worker_id);
565                scope
566                    .upcast::<GlobalScope>()
567                    .mem_profiler_chan()
568                    .run_with_memory_reporting(
569                        || {
570                            // Event loop: Run the responsible event loop specified by inside settings until it is destroyed.
571                            while !scope.is_closing() {
572                                run_worker_event_loop(&*global, None, cx);
573                            }
574                        },
575                        reporter_name,
576                        event_loop_sender,
577                        CommonScriptMsg::CollectReports,
578                    );
579
580                scope.clear_js_runtime();
581            })
582    }
583
584    pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
585        ScriptEventLoopSender::SharedWorker(self.own_sender.clone())
586    }
587
588    pub(crate) fn webview_id(&self) -> WebViewId {
589        self.webview_id
590    }
591
592    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
593        self.image_cache.clone()
594    }
595
596    pub(crate) fn browsing_context(&self) -> Option<BrowsingContextId> {
597        self.browsing_context
598    }
599
600    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
601        let (sender, receiver) = unbounded();
602        (
603            ScriptEventLoopSender::SharedWorker(sender),
604            ScriptEventLoopReceiver::SharedWorker(receiver),
605        )
606    }
607
608    /// Step 1.1 of onComplete of <https://html.spec.whatwg.org/multipage/#run-a-worker>
609    pub(crate) fn forward_simple_error_at_worker(&self) {
610        SharedWorker::unregister_shared_worker(self.registration_id);
611        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
612        let worker = self.worker.borrow().clone().expect("worker must be set");
613        // Step 1.1. Queue a global task on the DOM manipulation task source given worker's relevant global object to fire an event named error at worker.
614        self.parent_event_loop_sender
615            .send(CommonScriptMsg::Task(
616                WorkerEvent,
617                Box::new(SimpleWorkerErrorHandler::new(worker)),
618                Some(pipeline_id),
619                TaskSourceName::DOMManipulation,
620            ))
621            .expect("Sending to parent failed");
622    }
623
624    pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
625        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
626        self.parent_event_loop_sender
627            .send(CommonScriptMsg::ReportCspViolations(
628                pipeline_id,
629                violations,
630            ))
631            .unwrap_or_else(|error| {
632                log::warn!("Failed to send CSP violations to parent event loop: {error}");
633            });
634    }
635
636    /// Step 11 of onComplete of <https://html.spec.whatwg.org/multipage/#run-a-worker>
637    pub(crate) fn enable_outside_port_message_queue(&self) {
638        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
639        let worker = self.worker.borrow().clone().expect("worker must be set");
640
641        self.parent_event_loop_sender
642            .send(CommonScriptMsg::Task(
643                WorkerEvent,
644                Box::new(
645                    task!(sharedworker_enable_outside_port_message_queue: move |cx| {
646                        SharedWorker::enable_outside_port_message_queue(worker, cx);
647                    }),
648                ),
649                Some(pipeline_id),
650                TaskSourceName::DOMManipulation,
651            ))
652            .expect("Sending to parent failed");
653    }
654
655    fn handle_connect(
656        &self,
657        port_impl: MessagePortImpl,
658        cx: &mut JSContext,
659    ) -> DomRoot<MessagePort> {
660        // Let inside port be a new MessagePort object in inside settings's realm.
661        let inside_port = MessagePort::new_transferred(
662            self.upcast::<GlobalScope>(),
663            *port_impl.message_port_id(),
664            port_impl.entangled_port_id(),
665            CanGc::from_cx(cx),
666        );
667        self.upcast::<GlobalScope>()
668            .track_message_port(&inside_port, Some(port_impl));
669        inside_port
670    }
671
672    // Step 13. If is shared is true, then queue a global task on the DOM manipulation task source given worker global scope to fire an event named connect at worker global scope, using MessageEvent, with the data attribute initialized to the empty string, the ports attribute initialized to a new frozen array containing inside port, and the source attribute initialized to inside port.
673    fn dispatch_connect_event(&self, inside_port: &MessagePort) {
674        let worker_global = Trusted::new(self);
675        let inside_port = Trusted::new(inside_port);
676
677        self.upcast::<GlobalScope>()
678            .task_manager()
679            .dom_manipulation_task_source()
680            .queue(task!(sharedworker_connect_event: move |cx| {
681                let worker_global = worker_global.root();
682                let worker_global = &*worker_global;
683                let inside_port = inside_port.root();
684
685                rooted!(&in(cx) let mut data = UndefinedValue());
686                DOMString::from("").safe_to_jsval(cx,
687                    data.handle_mut(),
688                );
689
690                let source = WindowProxyOrMessagePortOrServiceWorker::MessagePort(
691                    inside_port.clone(),
692                );
693                let event = MessageEvent::new(
694                    cx,
695                    worker_global.upcast::<GlobalScope>(),
696                    Atom::from("connect"),
697                    false,
698                    false,
699                    data.handle(),
700                    DOMString::from(""),
701                    Some(&source),
702                    DOMString::new(),
703                    vec![inside_port],
704                );
705
706                event
707                    .upcast::<Event>()
708                    .fire(cx, worker_global.upcast::<EventTarget>());
709            }));
710    }
711
712    pub(crate) fn fire_pending_connect(&self, _cx: &mut JSContext) {
713        loop {
714            let inside_port = self
715                .pending_connect
716                .borrow_mut()
717                .pop_front()
718                .map(|inside_port| inside_port.as_rooted());
719            let Some(inside_port) = inside_port else {
720                break;
721            };
722            if self.upcast::<WorkerGlobalScope>().is_closing() {
723                return;
724            }
725            // Step 13. If is shared is true, then queue a global task on the DOM manipulation task source given worker global scope to fire an event named connect at worker global scope, using MessageEvent, with the data attribute initialized to the empty string, the ports attribute initialized to a new frozen array containing inside port, and the source attribute initialized to inside port.
726            self.dispatch_connect_event(&inside_port);
727        }
728    }
729
730    fn handle_script_event(&self, msg: SharedWorkerScriptMsg, cx: &mut JSContext) {
731        match msg {
732            SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg)) => {
733                self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
734            },
735            SharedWorkerScriptMsg::Connect(port_impl) => {
736                let inside_port = self.handle_connect(port_impl, cx);
737                if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
738                    // Step 13. If is shared is true, then queue a global task on the DOM manipulation task source given worker global scope to fire an event named connect at worker global scope, using MessageEvent, with the data attribute initialized to the empty string, the ports attribute initialized to a new frozen array containing inside port, and the source attribute initialized to inside port.
739                    self.dispatch_connect_event(&inside_port);
740                } else {
741                    // Step 13. If is shared is true, then queue a global task on the DOM manipulation task source given worker global scope to fire an event named connect at worker global scope, using MessageEvent, with the data attribute initialized to the empty string, the ports attribute initialized to a new frozen array containing inside port, and the source attribute initialized to inside port.
742                    self.pending_connect
743                        .borrow_mut()
744                        .push_back(Dom::from_ref(&*inside_port));
745                }
746            },
747            SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::DOMMessage(_)) => {
748                // SharedWorker messages arrive through the entangled MessagePort and are
749                // surfaced as connect/message events, not as direct WorkerScriptMsg::DOMMessage.
750                debug_assert!(
751                    false,
752                    "SharedWorkerGlobalScope does not support direct DOMMessage dispatch"
753                );
754            },
755            SharedWorkerScriptMsg::WakeUp => {},
756        }
757    }
758
759    fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
760        if self.upcast::<WorkerGlobalScope>().is_closing() {
761            return false;
762        }
763
764        match msg {
765            MixedMessage::Devtools(msg) => match msg {
766                DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, _bool_val) => {},
767                DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
768                    self.debugger_global.fire_eval(
769                        cx,
770                        code.into(),
771                        id,
772                        Some(self.upcast::<WorkerGlobalScope>().worker_id()),
773                        frame_actor_id,
774                        reply,
775                    );
776                },
777                _ => debug!("got an unusable devtools control message inside the worker!"),
778            },
779            MixedMessage::SharedWorker(msg) => {
780                self.handle_script_event(msg, cx);
781            },
782            MixedMessage::Control(SharedWorkerControlMsg::Exit) => {
783                return false;
784            },
785            MixedMessage::Timer => {},
786        }
787
788        true
789    }
790}
791
792impl SharedWorkerGlobalScopeMethods<crate::DomTypeHolder> for SharedWorkerGlobalScope {
793    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworkerglobalscope-name>
794    fn Name(&self) -> DOMString {
795        // The name getter steps are to return this's name.
796        // Its value represents the name that can be used to obtain a reference to the worker using the SharedWorker constructor.
797        self.workerglobalscope.worker_name()
798    }
799
800    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworkerglobalscope-close>
801    fn Close(&self) {
802        // The close() method steps are to close a worker given this.
803        self.upcast::<WorkerGlobalScope>().close()
804    }
805
806    // <https://html.spec.whatwg.org/multipage/#handler-sharedworkerglobalscope-onconnect>
807    event_handler!(connect, GetOnconnect, SetOnconnect);
808}
809
810impl HasOrigin for SharedWorkerGlobalScope {
811    fn origin(&self) -> MutableOrigin {
812        self.upcast::<WorkerGlobalScope>().origin()
813    }
814}