1use std::sync::Arc;
6use std::sync::atomic::AtomicBool;
7use std::thread::{self, JoinHandle};
8
9use crossbeam_channel::{Receiver, Sender, unbounded};
10use devtools_traits::DevtoolScriptControlMsg;
11use dom_struct::dom_struct;
12use fonts::FontContext;
13use js::context::JSContext;
14use js::jsval::UndefinedValue;
15use net_traits::blob_url_store::UrlWithBlobClaim;
16use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
17use net_traits::request::{
18 CredentialsMode, Destination, InsecureRequestsPolicy, Origin, PreloadedResources, Referrer,
19 RequestClient,
20};
21use script_bindings::cell::DomRefCell;
22use script_bindings::conversions::SafeToJSValConvertible;
23use servo_base::generic_channel::{GenericReceiver, RoutedReceiver};
24use servo_base::id::ScriptEventLoopId;
25use servo_constellation_traits::{MessagePortImpl, WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
26use servo_url::ServoUrl;
27use style::thread_state::{self, ThreadState};
28use stylo_atoms::Atom;
29
30use crate::dom::abstractworker::{SimpleWorkerErrorHandler, WorkerScriptMsg};
31use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
32use crate::dom::bindings::codegen::Bindings::SharedWorkerGlobalScopeBinding;
33use crate::dom::bindings::codegen::Bindings::SharedWorkerGlobalScopeBinding::SharedWorkerGlobalScopeMethods;
34use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
35use crate::dom::bindings::codegen::UnionTypes::WindowProxyOrMessagePortOrServiceWorker;
36use crate::dom::bindings::inheritance::Castable;
37use crate::dom::bindings::refcounted::Trusted;
38use crate::dom::bindings::root::{Dom, DomRoot};
39use crate::dom::bindings::str::DOMString;
40use crate::dom::bindings::trace::CustomTraceable;
41use crate::dom::dedicatedworkerglobalscope::fetch_a_classic_worker_script;
42use crate::dom::event::Event;
43use crate::dom::event::messageevent::MessageEvent;
44use crate::dom::eventtarget::EventTarget;
45use crate::dom::globalscope::GlobalScope;
46use crate::dom::html::htmlscriptelement::Script;
47use crate::dom::messageport::MessagePort;
48use crate::dom::sharedworker::{SharedWorker, TrustedSharedWorkerAddress};
49use crate::dom::types::DebuggerGlobalScope;
50#[cfg(feature = "webgpu")]
51use crate::dom::webgpu::identityhub::IdentityHub;
52use crate::dom::workerglobalscope::WorkerGlobalScope;
53use crate::messaging::{CommonScriptMsg, ScriptEventLoopReceiver, ScriptEventLoopSender};
54use crate::script_module::{ModuleFetchClient, fetch_a_module_worker_script_graph};
55use crate::script_runtime::ScriptThreadEventCategory::WorkerEvent;
56use crate::script_runtime::{CanGc, Runtime, ThreadSafeJSContext};
57use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
58use crate::task_source::TaskSourceName;
59
60pub(crate) enum SharedWorkerScriptMsg {
61 CommonWorker(WorkerScriptMsg),
62 #[allow(dead_code)]
63 Connect(MessagePortImpl),
64 WakeUp,
65}
66
67#[allow(dead_code)]
68pub(crate) enum SharedWorkerControlMsg {
69 Exit,
70}
71
72pub(crate) enum MixedMessage {
73 SharedWorker(SharedWorkerScriptMsg),
74 Devtools(DevtoolScriptControlMsg),
75 Control(SharedWorkerControlMsg),
76 Timer,
77}
78
79impl QueuedTaskConversion for SharedWorkerScriptMsg {
80 fn task_source_name(&self) -> Option<&TaskSourceName> {
81 let script_msg = match self {
82 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
83 _ => return None,
84 };
85 match script_msg {
86 CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
87 Some(task_source)
88 },
89 _ => None,
90 }
91 }
92
93 fn pipeline_id(&self) -> Option<servo_base::id::PipelineId> {
94 None
95 }
96
97 fn into_queued_task(self) -> Option<QueuedTask> {
98 let script_msg = match self {
99 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
100 _ => return None,
101 };
102 let (event_category, task, pipeline_id, task_source) = match script_msg {
103 CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
104 (category, boxed, pipeline_id, task_source)
105 },
106 _ => return None,
107 };
108 Some(QueuedTask {
109 worker: None,
110 event_category,
111 task,
112 pipeline_id,
113 task_source,
114 })
115 }
116
117 fn from_queued_task(queued_task: QueuedTask) -> Self {
118 let script_msg = CommonScriptMsg::Task(
119 queued_task.event_category,
120 queued_task.task,
121 queued_task.pipeline_id,
122 queued_task.task_source,
123 );
124 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
125 }
126
127 fn inactive_msg() -> Self {
128 panic!("Workers should never receive messages marked as inactive");
129 }
130
131 fn wake_up_msg() -> Self {
132 SharedWorkerScriptMsg::WakeUp
133 }
134
135 fn is_wake_up(&self) -> bool {
136 matches!(self, SharedWorkerScriptMsg::WakeUp)
137 }
138}
139
140unsafe_no_jsmanaged_fields!(TaskQueue<SharedWorkerScriptMsg>);
141
142#[dom_struct]
144pub(crate) struct SharedWorkerGlobalScope {
145 workerglobalscope: WorkerGlobalScope,
146 #[ignore_malloc_size_of = "Defined in std"]
147 task_queue: TaskQueue<SharedWorkerScriptMsg>,
148 own_sender: Sender<SharedWorkerScriptMsg>,
149 worker: DomRefCell<Option<TrustedSharedWorkerAddress>>,
150 parent_event_loop_sender: ScriptEventLoopSender,
151 pending_connect: DomRefCell<Option<Dom<MessagePort>>>,
152 #[no_trace]
153 control_receiver: Receiver<SharedWorkerControlMsg>,
154 debugger_global: Dom<DebuggerGlobalScope>,
155}
156
157impl WorkerEventLoopMethods for SharedWorkerGlobalScope {
158 type WorkerMsg = SharedWorkerScriptMsg;
159 type ControlMsg = SharedWorkerControlMsg;
160 type Event = MixedMessage;
161
162 fn task_queue(&self) -> &TaskQueue<SharedWorkerScriptMsg> {
163 &self.task_queue
164 }
165
166 fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
167 self.handle_mixed_message(event, cx)
168 }
169
170 fn handle_worker_post_event(
171 &self,
172 _worker: &crate::dom::worker::TrustedWorkerAddress,
173 ) -> Option<crate::dom::dedicatedworkerglobalscope::AutoWorkerReset<'_>> {
174 None
175 }
176
177 fn from_control_msg(msg: SharedWorkerControlMsg) -> MixedMessage {
178 MixedMessage::Control(msg)
179 }
180
181 fn from_worker_msg(msg: SharedWorkerScriptMsg) -> MixedMessage {
182 MixedMessage::SharedWorker(msg)
183 }
184
185 fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
186 MixedMessage::Devtools(msg)
187 }
188
189 fn from_timer_msg() -> MixedMessage {
190 MixedMessage::Timer
191 }
192
193 fn control_receiver(&self) -> &Receiver<SharedWorkerControlMsg> {
194 &self.control_receiver
195 }
196}
197
198impl SharedWorkerGlobalScope {
199 #[allow(clippy::too_many_arguments)]
200 #[allow(dead_code)]
201 fn new_inherited(
202 init: WorkerGlobalScopeInit,
203 worker_name: DOMString,
204 worker_type: WorkerType,
205 worker_url: ServoUrl,
206 worker: TrustedSharedWorkerAddress,
207 parent_event_loop_sender: ScriptEventLoopSender,
208 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
209 runtime: Runtime,
210 own_sender: Sender<SharedWorkerScriptMsg>,
211 receiver: Receiver<SharedWorkerScriptMsg>,
212 closing: Arc<AtomicBool>,
213 #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
214 control_receiver: Receiver<SharedWorkerControlMsg>,
215 insecure_requests_policy: InsecureRequestsPolicy,
216 font_context: Option<Arc<FontContext>>,
217 debugger_global: &DebuggerGlobalScope,
218 ) -> SharedWorkerGlobalScope {
219 SharedWorkerGlobalScope {
220 workerglobalscope: WorkerGlobalScope::new_inherited(
221 init,
222 worker_name,
223 worker_type,
224 worker_url,
225 runtime,
226 from_devtools_receiver,
227 closing,
228 #[cfg(feature = "webgpu")]
229 gpu_id_hub,
230 insecure_requests_policy,
231 font_context,
232 ),
233 task_queue: TaskQueue::new(receiver, own_sender.clone()),
234 own_sender,
235 worker: DomRefCell::new(Some(worker)),
236 parent_event_loop_sender,
237 pending_connect: DomRefCell::new(None),
238 control_receiver,
239 debugger_global: Dom::from_ref(debugger_global),
240 }
241 }
242
243 #[allow(clippy::too_many_arguments)]
244 #[allow(dead_code)]
245 pub(crate) fn new(
246 init: WorkerGlobalScopeInit,
247 worker_name: DOMString,
248 worker_type: WorkerType,
249 worker_url: ServoUrl,
250 worker: TrustedSharedWorkerAddress,
251 parent_event_loop_sender: ScriptEventLoopSender,
252 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
253 runtime: Runtime,
254 own_sender: Sender<SharedWorkerScriptMsg>,
255 receiver: Receiver<SharedWorkerScriptMsg>,
256 closing: Arc<AtomicBool>,
257 #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
258 control_receiver: Receiver<SharedWorkerControlMsg>,
259 insecure_requests_policy: InsecureRequestsPolicy,
260 font_context: Option<Arc<FontContext>>,
261 debugger_global: &DebuggerGlobalScope,
262 cx: &mut js::context::JSContext,
263 ) -> DomRoot<SharedWorkerGlobalScope> {
264 let scope = Box::new(SharedWorkerGlobalScope::new_inherited(
265 init,
266 worker_name,
267 worker_type,
268 worker_url,
269 worker,
270 parent_event_loop_sender,
271 from_devtools_receiver,
272 runtime,
273 own_sender,
274 receiver,
275 closing,
276 #[cfg(feature = "webgpu")]
277 gpu_id_hub,
278 control_receiver,
279 insecure_requests_policy,
280 font_context,
281 debugger_global,
282 ));
283 SharedWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, scope)
284 }
285
286 #[expect(unsafe_code)]
288 #[allow(dead_code)]
289 #[allow(clippy::too_many_arguments)]
290 pub(crate) fn run_shared_worker_scope(
291 init: WorkerGlobalScopeInit,
292 worker_name: DOMString,
293 worker_type: WorkerType,
294 worker_url: UrlWithBlobClaim,
295 worker: TrustedSharedWorkerAddress,
296 parent_event_loop_sender: ScriptEventLoopSender,
297 from_devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
298 own_sender: Sender<SharedWorkerScriptMsg>,
299 receiver: Receiver<SharedWorkerScriptMsg>,
300 worker_load_origin: WorkerScriptLoadOrigin,
301 closing: Arc<AtomicBool>,
302 #[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
303 control_receiver: Receiver<SharedWorkerControlMsg>,
304 context_sender: Sender<ThreadSafeJSContext>,
305 credentials: CredentialsMode,
306 insecure_requests_policy: InsecureRequestsPolicy,
307 policy_container: PolicyContainer,
308 font_context: Option<Arc<FontContext>>,
309 ) -> JoinHandle<()> {
310 let event_loop_id = ScriptEventLoopId::installed()
311 .expect("Should always be in a ScriptThread or in a worker");
312 let current_global = GlobalScope::current().expect("No current global object");
313 let origin = current_global.origin().immutable().clone();
314 let referrer = current_global.get_referrer();
315 let parent = current_global.runtime_handle();
316 let current_global_ancestor_trustworthy = current_global.has_trustworthy_ancestor_origin();
317 let is_nested_browsing_context = current_global.is_nested_browsing_context();
318 let webview_id = current_global.webview_id();
319 let worker_name = worker_name.to_string();
320
321 thread::Builder::new()
322 .name(format!("SWW:{}", worker_url.debug_compact()))
323 .spawn(move || {
324 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
328 ScriptEventLoopId::install(event_loop_id);
329
330 let WorkerScriptLoadOrigin {
331 referrer_url,
332 pipeline_id,
333 ..
334 } = worker_load_origin;
335
336 let referrer = referrer_url.map(Referrer::ReferrerUrl).unwrap_or(referrer);
337
338 let request_client = RequestClient {
339 preloaded_resources: PreloadedResources::default(),
340 policy_container: RequestPolicyContainer::PolicyContainer(
341 policy_container.clone(),
342 ),
343 origin: Origin::Origin(origin.clone()),
344 is_nested_browsing_context,
345 insecure_requests_policy,
346 };
347
348 let event_loop_sender = ScriptEventLoopSender::SharedWorker(own_sender.clone());
349
350 let runtime = unsafe {
351 Runtime::new_with_parent(Some(parent), Some(event_loop_sender.clone()))
352 };
353 let mut cx = unsafe { runtime.cx() };
357 let cx = &mut cx;
358 let debugger_global = DebuggerGlobalScope::new(
359 pipeline_id,
360 init.to_devtools_sender.clone(),
361 init.from_devtools_sender
362 .clone()
363 .expect("Guaranteed by SharedWorker::Constructor"),
364 init.mem_profiler_chan.clone(),
365 init.time_profiler_chan.clone(),
366 init.script_to_constellation_chan.clone(),
367 init.script_to_embedder_chan.clone(),
368 init.resource_threads.clone(),
369 init.storage_threads.clone(),
370 #[cfg(feature = "webgpu")]
371 gpu_id_hub.clone(),
372 cx,
373 );
374 debugger_global.execute(cx);
375
376 let context_for_interrupt = runtime.thread_safe_js_context();
377 let _ = context_sender.send(context_for_interrupt);
378
379 let devtools_mpsc_port = from_devtools_receiver.route_preserving_errors();
380
381 let worker_id = init.worker_id;
382 let devtools_enabled = init.to_devtools_sender.is_some();
383 let global = SharedWorkerGlobalScope::new(
386 init,
387 worker_name.into(),
388 worker_type,
389 worker_url.url(),
390 worker,
391 parent_event_loop_sender,
392 devtools_mpsc_port,
393 runtime,
394 own_sender,
395 receiver,
396 closing,
397 #[cfg(feature = "webgpu")]
398 gpu_id_hub,
399 control_receiver,
400 insecure_requests_policy,
401 font_context,
402 &debugger_global,
403 cx,
404 );
405 let scope = global.upcast::<WorkerGlobalScope>();
406 let global_scope = global.upcast::<GlobalScope>();
407 if devtools_enabled {
408 debugger_global.fire_add_debuggee(
409 cx,
410 global_scope,
411 pipeline_id,
412 Some(worker_id),
413 );
414 }
415
416 let fetch_client = ModuleFetchClient {
417 insecure_requests_policy,
418 has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
419 policy_container,
420 client: request_client,
421 pipeline_id,
422 origin,
423 };
424
425 match worker_type {
429 WorkerType::Classic => {
430 fetch_a_classic_worker_script(
431 scope,
432 worker_url,
433 fetch_client,
434 Destination::SharedWorker,
435 webview_id,
436 referrer,
437 );
438 },
439 WorkerType::Module => {
440 let worker_scope = DomRoot::from_ref(scope);
441 fetch_a_module_worker_script_graph(
442 cx,
443 global_scope,
444 worker_url.url(),
445 fetch_client,
446 Destination::SharedWorker,
447 referrer,
448 credentials,
449 move |cx, module_tree| {
450 worker_scope.on_complete(cx, module_tree.map(Script::Module));
451 },
452 );
453 },
454 }
455
456 let reporter_name = format!("shared-worker-reporter-{}", worker_id);
457 scope
458 .upcast::<GlobalScope>()
459 .mem_profiler_chan()
460 .run_with_memory_reporting(
461 || {
462 while !scope.is_closing() {
464 run_worker_event_loop(&*global, None, cx);
465 }
466 },
467 reporter_name,
468 event_loop_sender,
469 CommonScriptMsg::CollectReports,
470 );
471
472 scope.clear_js_runtime();
473 })
474 .expect("Thread spawning failed")
475 }
476
477 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
478 ScriptEventLoopSender::SharedWorker(self.own_sender.clone())
479 }
480
481 pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
482 let (sender, receiver) = unbounded();
483 (
484 ScriptEventLoopSender::SharedWorker(sender),
485 ScriptEventLoopReceiver::SharedWorker(receiver),
486 )
487 }
488
489 pub(crate) fn forward_simple_error_at_worker(&self) {
491 let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
492 let worker = self.worker.borrow().clone().expect("worker must be set");
493 self.parent_event_loop_sender
494 .send(CommonScriptMsg::Task(
495 WorkerEvent,
496 Box::new(SimpleWorkerErrorHandler::new(worker)),
497 Some(pipeline_id),
498 TaskSourceName::DOMManipulation,
499 ))
500 .expect("Sending to parent failed");
501 }
502
503 pub(crate) fn enable_outside_port_message_queue(&self) {
505 let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
506 let worker = self.worker.borrow().clone().expect("worker must be set");
507
508 self.parent_event_loop_sender
509 .send(CommonScriptMsg::Task(
510 WorkerEvent,
511 Box::new(
512 task!(sharedworker_enable_outside_port_message_queue: move |cx| {
513 SharedWorker::enable_outside_port_message_queue(worker, cx);
514 }),
515 ),
516 Some(pipeline_id),
517 TaskSourceName::DOMManipulation,
518 ))
519 .expect("Sending to parent failed");
520 }
521
522 fn handle_connect(
523 &self,
524 port_impl: MessagePortImpl,
525 cx: &mut JSContext,
526 ) -> DomRoot<MessagePort> {
527 let inside_port = MessagePort::new_transferred(
529 self.upcast::<GlobalScope>(),
530 *port_impl.message_port_id(),
531 port_impl.entangled_port_id(),
532 CanGc::from_cx(cx),
533 );
534 self.upcast::<GlobalScope>()
535 .track_message_port(&inside_port, Some(port_impl));
536 inside_port
537 }
538
539 fn dispatch_connect_event(&self, inside_port: &MessagePort) {
541 let worker_global = Trusted::new(self);
542 let inside_port = Trusted::new(inside_port);
543
544 self.upcast::<GlobalScope>()
545 .task_manager()
546 .dom_manipulation_task_source()
547 .queue(task!(sharedworker_connect_event: move |cx| {
548 let worker_global = worker_global.root();
549 let worker_global = &*worker_global;
550 let inside_port = inside_port.root();
551
552 rooted!(&in(cx) let mut data = UndefinedValue());
553 DOMString::from("").safe_to_jsval(
554 cx.into(),
555 data.handle_mut(),
556 CanGc::from_cx(cx),
557 );
558
559 let source = WindowProxyOrMessagePortOrServiceWorker::MessagePort(
560 inside_port.clone(),
561 );
562 let event = MessageEvent::new(
563 worker_global.upcast::<GlobalScope>(),
564 Atom::from("connect"),
565 false,
566 false,
567 data.handle(),
568 DOMString::from(""),
569 Some(&source),
570 DOMString::new(),
571 vec![inside_port],
572 CanGc::from_cx(cx),
573 );
574
575 event
576 .upcast::<Event>()
577 .fire(cx, worker_global.upcast::<EventTarget>());
578 }));
579 }
580
581 pub(crate) fn fire_pending_connect(&self, _cx: &mut JSContext) {
582 let inside_port = self
583 .pending_connect
584 .borrow_mut()
585 .take()
586 .map(|inside_port| inside_port.as_rooted());
587 if let Some(inside_port) = inside_port {
588 if self.upcast::<WorkerGlobalScope>().is_closing() {
589 return;
590 }
591 self.dispatch_connect_event(&inside_port);
592 }
593 }
594
595 fn handle_script_event(&self, msg: SharedWorkerScriptMsg, cx: &mut JSContext) {
596 match msg {
597 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg)) => {
598 self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
599 },
600 SharedWorkerScriptMsg::Connect(port_impl) => {
601 let inside_port = self.handle_connect(port_impl, cx);
602 if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
603 self.dispatch_connect_event(&inside_port);
604 } else {
605 let mut pending_connect = self.pending_connect.borrow_mut();
606 debug_assert!(
607 pending_connect.is_none(),
608 "SharedWorkerGlobalScope only expects one pre-ready connect in the current implementation"
609 );
610 pending_connect.replace(Dom::from_ref(&*inside_port));
611 }
612 },
613 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::DOMMessage(_)) => {
614 debug_assert!(
617 false,
618 "SharedWorkerGlobalScope does not support direct DOMMessage dispatch"
619 );
620 },
621 SharedWorkerScriptMsg::WakeUp => {},
622 }
623 }
624
625 fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
626 if self.upcast::<WorkerGlobalScope>().is_closing() {
627 return false;
628 }
629
630 match msg {
631 MixedMessage::Devtools(msg) => match msg {
632 DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, _bool_val) => {},
633 DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
634 self.debugger_global.fire_eval(
635 cx,
636 code.into(),
637 id,
638 Some(self.upcast::<WorkerGlobalScope>().worker_id()),
639 frame_actor_id,
640 reply,
641 );
642 },
643 _ => debug!("got an unusable devtools control message inside the worker!"),
644 },
645 MixedMessage::SharedWorker(msg) => {
646 self.handle_script_event(msg, cx);
647 },
648 MixedMessage::Control(SharedWorkerControlMsg::Exit) => {
649 return false;
650 },
651 MixedMessage::Timer => {},
652 }
653
654 true
655 }
656}
657
658impl SharedWorkerGlobalScopeMethods<crate::DomTypeHolder> for SharedWorkerGlobalScope {
659 fn Name(&self) -> DOMString {
661 self.workerglobalscope.worker_name()
664 }
665
666 fn Close(&self) {
668 self.upcast::<WorkerGlobalScope>().close()
670 }
671
672 event_handler!(connect, GetOnconnect, SetOnconnect);
674}