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