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