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