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;
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::fetch_a_module_worker_script_graph;
61use crate::script_runtime::Runtime;
62use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
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: policy_container.clone(),
419                    origin: Origin::Origin(origin.clone()),
420                    is_nested_browsing_context,
421                    insecure_requests_policy,
422                    has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
423                };
424
425                let event_loop_sender = ScriptEventLoopSender::SharedWorker(own_sender.clone());
426
427                // Shared workers currently run on a dedicated script worker thread but
428                // still create their own JS runtime in that thread; this avoids child
429                // runtime lifetime coupling with the embedding script thread.
430                let runtime = Runtime::new(Some(event_loop_sender.clone()));
431                // SAFETY: We are in a new thread, so this first cx.
432                // It is OK to have it separated of runtime here,
433                // because it will never outlive it (runtime destruction happens at the end of this function)
434                let mut cx = unsafe { runtime.cx() };
435                let cx = &mut cx;
436                let debugger_global = DebuggerGlobalScope::new(
437                    pipeline_id,
438                    init.to_devtools_sender.clone(),
439                    init.from_devtools_sender
440                        .clone()
441                        .expect("Guaranteed by SharedWorker::Constructor"),
442                    init.mem_profiler_chan.clone(),
443                    init.time_profiler_chan.clone(),
444                    init.script_to_constellation_chan.clone(),
445                    init.script_to_embedder_chan.clone(),
446                    init.resource_threads.clone(),
447                    init.storage_threads.clone(),
448                    #[cfg(feature = "webgpu")]
449                    gpu_id_hub.clone(),
450                    cx,
451                );
452                debugger_global.execute(cx);
453
454                let devtools_mpsc_port = from_devtools_receiver.route_preserving_errors();
455
456                let worker_id = init.worker_id;
457                let devtools_enabled = init.to_devtools_sender.is_some();
458                // Step 3. Let origin be a unique opaque origin if worker global scope's url's scheme is "data"; otherwise outside settings's origin.
459                if worker_url.scheme() == "data" {
460                    if is_secure_context {
461                        init.origin = ImmutableOrigin::new_opaque_data_url_worker();
462                    } else {
463                        init.origin = ImmutableOrigin::new_opaque();
464                    }
465                }
466                // Step 8. Set worker global scope's name to options["name"].
467                // Step 10.3. Set worker global scope's type to options["type"].
468                let global = SharedWorkerGlobalScope::new(
469                    init,
470                    webview_id,
471                    worker_name.into(),
472                    worker_type,
473                    worker_url.url(),
474                    worker,
475                    parent_event_loop_sender,
476                    devtools_mpsc_port,
477                    runtime,
478                    own_sender,
479                    receiver,
480                    closing,
481                    image_cache,
482                    browsing_context,
483                    #[cfg(feature = "webgpu")]
484                    gpu_id_hub,
485                    control_receiver,
486                    insecure_requests_policy,
487                    font_context,
488                    &debugger_global,
489                    storage_key,
490                    constructor_origin,
491                    constructor_url,
492                    credentials,
493                    extended_lifetime,
494                    registration_id,
495                    cx,
496                );
497                let scope = global.upcast::<WorkerGlobalScope>();
498                let global_scope = global.upcast::<GlobalScope>();
499                // Step 11.5.2. Let workerIsSecureContext be true if insideSettings is a secure context; otherwise, false.
500                let worker_is_secure_context = global_scope.is_secure_context();
501                if devtools_enabled {
502                    debugger_global.fire_add_debuggee(
503                        cx,
504                        global_scope,
505                        pipeline_id,
506                        Some(worker_id),
507                    );
508                }
509
510                if setup_sender.send(worker_is_secure_context).is_err() {
511                    scope.clear_js_runtime();
512                    return;
513                }
514
515                if registration_receiver.recv().is_err() {
516                    scope.clear_js_runtime();
517                    return;
518                }
519                // Keep cleanup guard alive for the remainder of worker execution.
520                // It is intentionally unused because its Drop unregisters the worker.
521                let _registration_cleanup = SharedWorkerRegistrationCleanup { registration_id };
522
523                // Step 11. Let destination be "sharedworker" if is shared is true, and
524                // "worker" otherwise.
525                // Step 12. Obtain script by switching on options["type"]:
526                match worker_type {
527                    WorkerType::Classic => {
528                        fetch_a_classic_worker_script(
529                            scope,
530                            worker_url,
531                            request_client,
532                            Destination::SharedWorker,
533                            Some(webview_id),
534                            referrer,
535                        );
536                    },
537                    WorkerType::Module => {
538                        let worker_scope = DomRoot::from_ref(scope);
539                        fetch_a_module_worker_script_graph(
540                            cx,
541                            global_scope,
542                            worker_url.url(),
543                            request_client,
544                            Destination::SharedWorker,
545                            referrer,
546                            credentials,
547                            move |cx, module_tree| {
548                                worker_scope.on_complete(cx, module_tree.map(Script::Module));
549                            },
550                        );
551                    },
552                }
553
554                let reporter_name = format!("shared-worker-reporter-{}", worker_id);
555                scope
556                    .upcast::<GlobalScope>()
557                    .mem_profiler_chan()
558                    .run_with_memory_reporting(
559                        || {
560                            // Event loop: Run the responsible event loop specified by inside settings until it is destroyed.
561                            while !scope.is_closing() {
562                                run_worker_event_loop(&*global, None, cx);
563                            }
564                        },
565                        reporter_name,
566                        event_loop_sender,
567                        CommonScriptMsg::CollectReports,
568                    );
569
570                scope.clear_js_runtime();
571            })
572    }
573
574    pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
575        ScriptEventLoopSender::SharedWorker(self.own_sender.clone())
576    }
577
578    pub(crate) fn webview_id(&self) -> WebViewId {
579        self.webview_id
580    }
581
582    pub(crate) fn image_cache(&self) -> Arc<dyn ImageCache> {
583        self.image_cache.clone()
584    }
585
586    pub(crate) fn browsing_context(&self) -> Option<BrowsingContextId> {
587        self.browsing_context
588    }
589
590    pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
591        let (sender, receiver) = unbounded();
592        (
593            ScriptEventLoopSender::SharedWorker(sender),
594            ScriptEventLoopReceiver::SharedWorker(receiver),
595        )
596    }
597
598    /// Step 1.1 of onComplete of <https://html.spec.whatwg.org/multipage/#run-a-worker>
599    pub(crate) fn forward_simple_error_at_worker(&self) {
600        SharedWorker::unregister_shared_worker(self.registration_id);
601        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
602        let worker = self.worker.borrow().clone().expect("worker must be set");
603        // 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.
604        self.parent_event_loop_sender
605            .send(CommonScriptMsg::Task(
606                WorkerEvent,
607                Box::new(SimpleWorkerErrorHandler::new(worker)),
608                Some(pipeline_id),
609                TaskSourceName::DOMManipulation,
610            ))
611            .expect("Sending to parent failed");
612    }
613
614    pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) {
615        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
616        self.parent_event_loop_sender
617            .send(CommonScriptMsg::ReportCspViolations(
618                pipeline_id,
619                violations,
620            ))
621            .unwrap_or_else(|error| {
622                log::warn!("Failed to send CSP violations to parent event loop: {error}");
623            });
624    }
625
626    /// Step 11 of onComplete of <https://html.spec.whatwg.org/multipage/#run-a-worker>
627    pub(crate) fn enable_outside_port_message_queue(&self) {
628        let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
629        let worker = self.worker.borrow().clone().expect("worker must be set");
630
631        self.parent_event_loop_sender
632            .send(CommonScriptMsg::Task(
633                WorkerEvent,
634                Box::new(
635                    task!(sharedworker_enable_outside_port_message_queue: move |cx| {
636                        SharedWorker::enable_outside_port_message_queue(worker, cx);
637                    }),
638                ),
639                Some(pipeline_id),
640                TaskSourceName::DOMManipulation,
641            ))
642            .expect("Sending to parent failed");
643    }
644
645    fn handle_connect(
646        &self,
647        port_impl: MessagePortImpl,
648        cx: &mut JSContext,
649    ) -> DomRoot<MessagePort> {
650        // Let inside port be a new MessagePort object in inside settings's realm.
651        let inside_port = MessagePort::new_transferred(
652            cx,
653            self.upcast::<GlobalScope>(),
654            *port_impl.message_port_id(),
655            port_impl.entangled_port_id(),
656        );
657        self.upcast::<GlobalScope>()
658            .track_message_port(&inside_port, Some(port_impl));
659        inside_port
660    }
661
662    // 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.
663    fn dispatch_connect_event(&self, inside_port: &MessagePort) {
664        let worker_global = Trusted::new(self);
665        let inside_port = Trusted::new(inside_port);
666
667        self.upcast::<GlobalScope>()
668            .task_manager()
669            .dom_manipulation_task_source()
670            .queue(task!(sharedworker_connect_event: move |cx| {
671                let worker_global = worker_global.root();
672                let worker_global = &*worker_global;
673                let inside_port = inside_port.root();
674
675                rooted!(&in(cx) let mut data = UndefinedValue());
676                DOMString::from("").safe_to_jsval(cx,
677                    data.handle_mut(),
678                );
679
680                let source = WindowProxyOrMessagePortOrServiceWorker::MessagePort(
681                    inside_port.clone(),
682                );
683                let event = MessageEvent::new(
684                    cx,
685                    worker_global.upcast::<GlobalScope>(),
686                    Atom::from("connect"),
687                    false,
688                    false,
689                    data.handle(),
690                    DOMString::from(""),
691                    Some(&source),
692                    DOMString::new(),
693                    vec![inside_port],
694                );
695
696                event
697                    .upcast::<Event>()
698                    .fire(cx, worker_global.upcast::<EventTarget>());
699            }));
700    }
701
702    pub(crate) fn fire_pending_connect(&self, _cx: &mut JSContext) {
703        loop {
704            let inside_port = self
705                .pending_connect
706                .borrow_mut()
707                .pop_front()
708                .map(|inside_port| inside_port.as_rooted());
709            let Some(inside_port) = inside_port else {
710                break;
711            };
712            if self.upcast::<WorkerGlobalScope>().is_closing() {
713                return;
714            }
715            // 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.
716            self.dispatch_connect_event(&inside_port);
717        }
718    }
719
720    fn handle_script_event(&self, msg: SharedWorkerScriptMsg, cx: &mut JSContext) {
721        match msg {
722            SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg)) => {
723                self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
724            },
725            SharedWorkerScriptMsg::Connect(port_impl) => {
726                let inside_port = self.handle_connect(port_impl, cx);
727                if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
728                    // 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.
729                    self.dispatch_connect_event(&inside_port);
730                } else {
731                    // 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.
732                    self.pending_connect
733                        .borrow_mut()
734                        .push_back(Dom::from_ref(&*inside_port));
735                }
736            },
737            SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::DOMMessage(_)) => {
738                // SharedWorker messages arrive through the entangled MessagePort and are
739                // surfaced as connect/message events, not as direct WorkerScriptMsg::DOMMessage.
740                debug_assert!(
741                    false,
742                    "SharedWorkerGlobalScope does not support direct DOMMessage dispatch"
743                );
744            },
745            SharedWorkerScriptMsg::WakeUp => {},
746        }
747    }
748
749    fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
750        if self.upcast::<WorkerGlobalScope>().is_closing() {
751            return false;
752        }
753
754        match msg {
755            MixedMessage::Devtools(msg) => match msg {
756                DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, _bool_val) => {},
757                DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
758                    self.debugger_global.fire_eval(
759                        cx,
760                        code.into(),
761                        id,
762                        Some(self.upcast::<WorkerGlobalScope>().worker_id()),
763                        frame_actor_id,
764                        reply,
765                    );
766                },
767                _ => debug!("got an unusable devtools control message inside the worker!"),
768            },
769            MixedMessage::SharedWorker(msg) => {
770                self.handle_script_event(msg, cx);
771            },
772            MixedMessage::Control(SharedWorkerControlMsg::Exit) => {
773                return false;
774            },
775            MixedMessage::Timer => {},
776        }
777
778        true
779    }
780}
781
782impl SharedWorkerGlobalScopeMethods<crate::DomTypeHolder> for SharedWorkerGlobalScope {
783    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworkerglobalscope-name>
784    fn Name(&self) -> DOMString {
785        // The name getter steps are to return this's name.
786        // Its value represents the name that can be used to obtain a reference to the worker using the SharedWorker constructor.
787        self.workerglobalscope.worker_name()
788    }
789
790    /// <https://html.spec.whatwg.org/multipage/#dom-sharedworkerglobalscope-close>
791    fn Close(&self) {
792        // The close() method steps are to close a worker given this.
793        self.upcast::<WorkerGlobalScope>().close()
794    }
795
796    // <https://html.spec.whatwg.org/multipage/#handler-sharedworkerglobalscope-onconnect>
797    event_handler!(connect, GetOnconnect, SetOnconnect);
798}
799
800impl HasOrigin for SharedWorkerGlobalScope {
801    fn origin(&self) -> MutableOrigin {
802        self.upcast::<WorkerGlobalScope>().origin()
803    }
804}