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 global = SharedWorkerGlobalScope::new(
385 init,
386 worker_name.into(),
387 worker_type,
388 worker_url.url(),
389 worker,
390 parent_event_loop_sender,
391 devtools_mpsc_port,
392 runtime,
393 own_sender,
394 receiver,
395 closing,
396 #[cfg(feature = "webgpu")]
397 gpu_id_hub,
398 control_receiver,
399 insecure_requests_policy,
400 font_context,
401 &debugger_global,
402 cx,
403 );
404 let scope = global.upcast::<WorkerGlobalScope>();
405 let global_scope = global.upcast::<GlobalScope>();
406 debugger_global.fire_add_debuggee(cx, global_scope, pipeline_id, Some(worker_id));
407
408 let fetch_client = ModuleFetchClient {
409 insecure_requests_policy,
410 has_trustworthy_ancestor_origin: current_global_ancestor_trustworthy,
411 policy_container,
412 client: request_client,
413 pipeline_id,
414 origin,
415 };
416
417 match worker_type {
421 WorkerType::Classic => {
422 fetch_a_classic_worker_script(
423 scope,
424 worker_url,
425 fetch_client,
426 Destination::SharedWorker,
427 webview_id,
428 referrer,
429 );
430 },
431 WorkerType::Module => {
432 let worker_scope = DomRoot::from_ref(scope);
433 fetch_a_module_worker_script_graph(
434 cx,
435 global_scope,
436 worker_url.url(),
437 fetch_client,
438 Destination::SharedWorker,
439 referrer,
440 credentials,
441 move |cx, module_tree| {
442 worker_scope.on_complete(cx, module_tree.map(Script::Module));
443 },
444 );
445 },
446 }
447
448 let reporter_name = format!("shared-worker-reporter-{}", worker_id);
449 scope
450 .upcast::<GlobalScope>()
451 .mem_profiler_chan()
452 .run_with_memory_reporting(
453 || {
454 while !scope.is_closing() {
456 run_worker_event_loop(&*global, None, cx);
457 }
458 },
459 reporter_name,
460 event_loop_sender,
461 CommonScriptMsg::CollectReports,
462 );
463
464 scope.clear_js_runtime();
465 })
466 .expect("Thread spawning failed")
467 }
468
469 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
470 ScriptEventLoopSender::SharedWorker(self.own_sender.clone())
471 }
472
473 pub(crate) fn new_script_pair(&self) -> (ScriptEventLoopSender, ScriptEventLoopReceiver) {
474 let (sender, receiver) = unbounded();
475 (
476 ScriptEventLoopSender::SharedWorker(sender),
477 ScriptEventLoopReceiver::SharedWorker(receiver),
478 )
479 }
480
481 pub(crate) fn forward_simple_error_at_worker(&self) {
483 let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
484 let worker = self.worker.borrow().clone().expect("worker must be set");
485 self.parent_event_loop_sender
486 .send(CommonScriptMsg::Task(
487 WorkerEvent,
488 Box::new(SimpleWorkerErrorHandler::new(worker)),
489 Some(pipeline_id),
490 TaskSourceName::DOMManipulation,
491 ))
492 .expect("Sending to parent failed");
493 }
494
495 pub(crate) fn enable_outside_port_message_queue(&self) {
497 let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
498 let worker = self.worker.borrow().clone().expect("worker must be set");
499
500 self.parent_event_loop_sender
501 .send(CommonScriptMsg::Task(
502 WorkerEvent,
503 Box::new(
504 task!(sharedworker_enable_outside_port_message_queue: move |cx| {
505 SharedWorker::enable_outside_port_message_queue(worker, cx);
506 }),
507 ),
508 Some(pipeline_id),
509 TaskSourceName::DOMManipulation,
510 ))
511 .expect("Sending to parent failed");
512 }
513
514 fn handle_connect(
515 &self,
516 port_impl: MessagePortImpl,
517 cx: &mut JSContext,
518 ) -> DomRoot<MessagePort> {
519 let inside_port = MessagePort::new_transferred(
521 self.upcast::<GlobalScope>(),
522 *port_impl.message_port_id(),
523 port_impl.entangled_port_id(),
524 CanGc::from_cx(cx),
525 );
526 self.upcast::<GlobalScope>()
527 .track_message_port(&inside_port, Some(port_impl));
528 inside_port
529 }
530
531 fn dispatch_connect_event(&self, inside_port: &MessagePort) {
533 let worker_global = Trusted::new(self);
534 let inside_port = Trusted::new(inside_port);
535
536 self.upcast::<GlobalScope>()
537 .task_manager()
538 .dom_manipulation_task_source()
539 .queue(task!(sharedworker_connect_event: move |cx| {
540 let worker_global = worker_global.root();
541 let worker_global = &*worker_global;
542 let inside_port = inside_port.root();
543
544 rooted!(&in(cx) let mut data = UndefinedValue());
545 DOMString::from("").safe_to_jsval(
546 cx.into(),
547 data.handle_mut(),
548 CanGc::from_cx(cx),
549 );
550
551 let source = WindowProxyOrMessagePortOrServiceWorker::MessagePort(
552 inside_port.clone(),
553 );
554 let event = MessageEvent::new(
555 worker_global.upcast::<GlobalScope>(),
556 Atom::from("connect"),
557 false,
558 false,
559 data.handle(),
560 DOMString::from(""),
561 Some(&source),
562 DOMString::new(),
563 vec![inside_port],
564 CanGc::from_cx(cx),
565 );
566
567 event
568 .upcast::<Event>()
569 .fire(worker_global.upcast::<EventTarget>(), CanGc::from_cx(cx));
570 }));
571 }
572
573 pub(crate) fn fire_pending_connect(&self, _cx: &mut JSContext) {
574 let inside_port = self
575 .pending_connect
576 .borrow_mut()
577 .take()
578 .map(|inside_port| inside_port.as_rooted());
579 if let Some(inside_port) = inside_port {
580 if self.upcast::<WorkerGlobalScope>().is_closing() {
581 return;
582 }
583 self.dispatch_connect_event(&inside_port);
584 }
585 }
586
587 fn handle_script_event(&self, msg: SharedWorkerScriptMsg, cx: &mut JSContext) {
588 match msg {
589 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg)) => {
590 self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
591 },
592 SharedWorkerScriptMsg::Connect(port_impl) => {
593 let inside_port = self.handle_connect(port_impl, cx);
594 if self.upcast::<WorkerGlobalScope>().is_execution_ready() {
595 self.dispatch_connect_event(&inside_port);
596 } else {
597 let mut pending_connect = self.pending_connect.borrow_mut();
598 debug_assert!(
599 pending_connect.is_none(),
600 "SharedWorkerGlobalScope only expects one pre-ready connect in the current implementation"
601 );
602 pending_connect.replace(Dom::from_ref(&*inside_port));
603 }
604 },
605 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::DOMMessage(_)) => {
606 debug_assert!(
609 false,
610 "SharedWorkerGlobalScope does not support direct DOMMessage dispatch"
611 );
612 },
613 SharedWorkerScriptMsg::WakeUp => {},
614 }
615 }
616
617 fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
618 if self.upcast::<WorkerGlobalScope>().is_closing() {
619 return false;
620 }
621
622 match msg {
623 MixedMessage::Devtools(msg) => match msg {
624 DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, _bool_val) => {},
625 DevtoolScriptControlMsg::Eval(code, id, frame_actor_id, reply) => {
626 self.debugger_global.fire_eval(
627 cx,
628 code.into(),
629 id,
630 Some(self.upcast::<WorkerGlobalScope>().worker_id()),
631 frame_actor_id,
632 reply,
633 );
634 },
635 _ => debug!("got an unusable devtools control message inside the worker!"),
636 },
637 MixedMessage::SharedWorker(msg) => {
638 self.handle_script_event(msg, cx);
639 },
640 MixedMessage::Control(SharedWorkerControlMsg::Exit) => {
641 return false;
642 },
643 MixedMessage::Timer => {},
644 }
645
646 true
647 }
648}
649
650impl SharedWorkerGlobalScopeMethods<crate::DomTypeHolder> for SharedWorkerGlobalScope {
651 fn Name(&self) -> DOMString {
653 self.workerglobalscope.worker_name()
656 }
657
658 fn Close(&self) {
660 self.upcast::<WorkerGlobalScope>().close()
662 }
663
664 event_handler!(connect, GetOnconnect, SetOnconnect);
666}