1use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Condvar, LazyLock, Mutex};
7
8use crossbeam_channel::{Sender, unbounded};
9use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId};
10use dom_struct::dom_struct;
11use js::context::JSContext;
12use js::rust::HandleObject;
13use malloc_size_of_derive::MallocSizeOf;
14use net_traits::pub_domains::reg_suffix;
15use net_traits::request::{CredentialsMode, Referrer};
16use script_bindings::reflector::reflect_dom_object_with_proto_and_cx;
17use servo_base::generic_channel;
18use servo_constellation_traits::{MessagePortImpl, WorkerScriptLoadOrigin};
19use servo_url::{Host, ImmutableOrigin, ServoUrl};
20use uuid::Uuid;
21
22use crate::conversions::Convert;
23use crate::dom::abstractworker::SimpleWorkerErrorHandler;
24use crate::dom::bindings::codegen::Bindings::SharedWorkerBinding::{
25 SharedWorkerMethods, SharedWorkerOptions,
26};
27use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
28use crate::dom::bindings::codegen::UnionTypes::{
29 StringOrSharedWorkerOptions, TrustedScriptURLOrUSVString,
30};
31use crate::dom::bindings::error::{Error, Fallible};
32use crate::dom::bindings::inheritance::Castable;
33use crate::dom::bindings::refcounted::Trusted;
34use crate::dom::bindings::reflector::DomGlobal;
35use crate::dom::bindings::root::{Dom, DomRoot};
36use crate::dom::bindings::trace::CustomTraceable;
37use crate::dom::bindings::transferable::Transferable;
38use crate::dom::eventtarget::EventTarget;
39use crate::dom::globalscope::GlobalScope;
40use crate::dom::messageport::MessagePort;
41use crate::dom::sharedworkerglobalscope::{
42 SharedWorkerControlMsg, SharedWorkerGlobalScope, SharedWorkerScriptMsg,
43};
44use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
45use crate::dom::window::Window;
46use crate::dom::workerglobalscope::prepare_workerscope_init;
47use crate::task::TaskOnce;
48use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
49
50#[dom_struct]
52pub(crate) struct SharedWorker {
53 eventtarget: EventTarget,
54 port: Dom<MessagePort>,
55 _control_sender: Sender<SharedWorkerControlMsg>,
56}
57
58pub(crate) type TrustedSharedWorkerAddress = Trusted<SharedWorker>;
59
60#[derive(Clone)]
62struct SharedWorkerKey {
63 storage_key: SharedWorkerStorageKey,
64 constructor_origin: ImmutableOrigin,
65 constructor_url: ServoUrl,
66 name: String,
67}
68
69impl SharedWorkerKey {
70 fn matches(
71 &self,
72 storage_key: &SharedWorkerStorageKey,
73 constructor_origin: &ImmutableOrigin,
74 constructor_url: &ServoUrl,
75 name: &str,
76 ) -> bool {
77 self.storage_key == *storage_key &&
78 self.constructor_origin == *constructor_origin &&
79 self.constructor_url == *constructor_url &&
80 self.name == name
81 }
82}
83
84#[derive(Clone, Eq, MallocSizeOf, PartialEq)]
85enum SharedWorkerSite {
86 Opaque(ImmutableOrigin),
87 Tuple { scheme: String, host: Host },
88}
89
90impl SharedWorkerSite {
91 fn from_origin(origin: ImmutableOrigin) -> Self {
93 match origin {
94 ImmutableOrigin::Opaque(_) => SharedWorkerSite::Opaque(origin),
96 ImmutableOrigin::Tuple(scheme, Host::Domain(domain), _) => SharedWorkerSite::Tuple {
98 scheme,
99 host: Host::Domain(reg_suffix(&domain).to_owned()),
100 },
101 ImmutableOrigin::Tuple(scheme, host, _) => SharedWorkerSite::Tuple { scheme, host },
104 }
105 }
106}
107
108#[derive(Clone, Eq, MallocSizeOf, PartialEq)]
109pub(crate) struct SharedWorkerStorageKey {
110 origin: ImmutableOrigin,
111 top_level_site: Option<SharedWorkerSite>,
112}
113
114impl SharedWorkerStorageKey {
115 fn for_window(global: &GlobalScope, window: &Window) -> Self {
117 let origin = global.obtain_storage_key_for_non_storage_purposes();
120
121 let top_level_site = window
122 .top_level_document_if_local()
123 .map(|document| SharedWorkerSite::from_origin(document.origin().immutable().clone()));
124
125 SharedWorkerStorageKey {
126 origin,
127 top_level_site,
128 }
129 }
130}
131
132enum SharedWorkerRegistryState {
133 Creating { waiters: usize },
134 Created(SharedWorkerRegistration),
135 Failed { waiters: usize },
136}
137
138struct SharedWorkerRegistryEntry {
139 key: SharedWorkerKey,
140 state: SharedWorkerRegistryState,
141}
142
143#[derive(Clone)]
145struct SharedWorkerRegistration {
146 id: Uuid,
147 worker_type: WorkerType,
148 credentials: CredentialsMode,
149 extended_lifetime: bool,
150 worker_is_secure_context: bool,
151 closing: Arc<AtomicBool>,
152 sender: Sender<SharedWorkerScriptMsg>,
153 _control_sender: Sender<SharedWorkerControlMsg>,
154}
155
156static SHARED_WORKERS: LazyLock<(Mutex<Vec<SharedWorkerRegistryEntry>>, Condvar)> =
162 LazyLock::new(|| (Mutex::new(Vec::new()), Condvar::new()));
163
164enum SharedWorkerClaimResult {
168 Created(SharedWorkerRegistration),
169 Claimed,
170 Failed,
171}
172
173fn prune_closed_shared_workers(workers: &mut Vec<SharedWorkerRegistryEntry>) {
174 workers.retain(|entry| match &entry.state {
175 SharedWorkerRegistryState::Creating { .. } => true,
176 SharedWorkerRegistryState::Created(worker) => !worker.closing.load(Ordering::SeqCst),
177 SharedWorkerRegistryState::Failed { waiters } => *waiters > 0,
178 });
179}
180
181fn find_matching_shared_worker(
182 workers: &[SharedWorkerRegistryEntry],
183 key: &SharedWorkerKey,
184) -> Option<usize> {
185 workers.iter().position(|entry| {
186 entry.key.matches(
187 &key.storage_key,
188 &key.constructor_origin,
189 &key.constructor_url,
190 &key.name,
191 )
192 })
193}
194
195fn find_or_claim_shared_worker(key: SharedWorkerKey) -> SharedWorkerClaimResult {
198 let (workers, ready) = &*SHARED_WORKERS;
199 let mut workers = workers.lock().expect("SharedWorker registry poisoned");
200
201 prune_closed_shared_workers(&mut workers);
202
203 let Some(index) = find_matching_shared_worker(&workers, &key) else {
204 workers.push(SharedWorkerRegistryEntry {
205 key,
206 state: SharedWorkerRegistryState::Creating { waiters: 0 },
207 });
208 return SharedWorkerClaimResult::Claimed;
209 };
210
211 match &mut workers[index].state {
212 SharedWorkerRegistryState::Creating { waiters } => *waiters += 1,
213 SharedWorkerRegistryState::Created(registration) => {
214 return SharedWorkerClaimResult::Created(registration.clone());
215 },
216 SharedWorkerRegistryState::Failed { waiters } => *waiters += 1,
217 };
218
219 loop {
220 workers = ready.wait(workers).expect("SharedWorker registry poisoned");
221
222 let Some(index) = find_matching_shared_worker(&workers, &key) else {
223 return SharedWorkerClaimResult::Failed;
224 };
225
226 match &mut workers[index].state {
227 SharedWorkerRegistryState::Creating { .. } => {},
228 SharedWorkerRegistryState::Created(registration) => {
229 return SharedWorkerClaimResult::Created(registration.clone());
230 },
231 SharedWorkerRegistryState::Failed { waiters } => {
232 debug_assert!(*waiters > 0);
233 if *waiters > 0 {
234 *waiters -= 1;
235 }
236 if *waiters == 0 {
237 workers.remove(index);
238 }
241 return SharedWorkerClaimResult::Failed;
242 },
243 }
244 }
245}
246
247fn transition_creating_to_created(
248 key: &SharedWorkerKey,
249 registration: SharedWorkerRegistration,
250) -> bool {
251 let (workers, ready) = &*SHARED_WORKERS;
252 let mut workers = workers.lock().expect("SharedWorker registry poisoned");
253 let index = find_matching_shared_worker(&workers, key);
254 debug_assert!(index.is_some(), "claimed SharedWorker entry should exist");
255 let Some(index) = index else {
256 ready.notify_all();
257 return false;
258 };
259
260 let entry_is_creating = matches!(
261 &workers[index].state,
262 SharedWorkerRegistryState::Creating { .. }
263 );
264 debug_assert!(
265 entry_is_creating,
266 "claimed SharedWorker entry should still be creating"
267 );
268 if !entry_is_creating {
269 ready.notify_all();
270 return false;
271 }
272
273 workers[index].state = SharedWorkerRegistryState::Created(registration);
274 ready.notify_all();
275 true
276}
277
278fn remove_creating_shared_worker(key: &SharedWorkerKey) {
279 let (workers, ready) = &*SHARED_WORKERS;
280 let mut workers = workers.lock().expect("SharedWorker registry poisoned");
281 let index = find_matching_shared_worker(&workers, key);
282 debug_assert!(index.is_some(), "claimed SharedWorker entry should exist");
283 let Some(index) = index else {
284 return;
285 };
286
287 let waiters = match &workers[index].state {
288 SharedWorkerRegistryState::Creating { waiters } => *waiters,
289 state => {
290 debug_assert!(
291 matches!(state, SharedWorkerRegistryState::Creating { .. }),
292 "claimed SharedWorker entry should still be creating"
293 );
294 return;
295 },
296 };
297
298 if waiters == 0 {
299 workers.remove(index);
300 } else {
301 workers[index].state = SharedWorkerRegistryState::Failed { waiters };
302 };
303 ready.notify_all();
304}
305
306fn send_connect_to_created_worker(
307 registration: &SharedWorkerRegistration,
308 inside_port: MessagePortImpl,
309) -> bool {
310 registration
311 .sender
312 .send(SharedWorkerScriptMsg::Connect(inside_port))
313 .is_err()
314}
315
316impl SharedWorker {
317 pub(crate) fn unregister_shared_worker(id: Uuid) {
318 let (workers, ready) = &*SHARED_WORKERS;
319 let mut workers = workers.lock().expect("SharedWorker registry poisoned");
320 let old_len = workers.len();
321 workers.retain(|entry| {
322 !matches!(&entry.state, SharedWorkerRegistryState::Created(worker) if worker.id == id)
323 });
324 if workers.len() != old_len {
325 ready.notify_all();
326 }
327 }
328
329 fn new_inherited(
330 port: &MessagePort,
331 control_sender: Sender<SharedWorkerControlMsg>,
332 ) -> SharedWorker {
333 SharedWorker {
334 eventtarget: EventTarget::new_inherited(),
335 port: Dom::from_ref(port),
336 _control_sender: control_sender,
337 }
338 }
339
340 fn new(
341 global: &GlobalScope,
342 proto: Option<HandleObject>,
343 port: &MessagePort,
344 control_sender: Sender<SharedWorkerControlMsg>,
345 cx: &mut js::context::JSContext,
346 ) -> DomRoot<SharedWorker> {
347 reflect_dom_object_with_proto_and_cx(
348 Box::new(SharedWorker::new_inherited(port, control_sender)),
349 global,
350 proto,
351 cx,
352 )
353 }
354
355 pub(crate) fn dispatch_simple_error(cx: &mut JSContext, address: TrustedSharedWorkerAddress) {
356 let worker = address.root();
357 worker.upcast().fire_event(cx, atom!("error"));
358 }
359
360 fn queue_simple_error(global: &GlobalScope, address: TrustedSharedWorkerAddress) {
361 global.task_manager().dom_manipulation_task_source().queue(
362 task!(sharedworker_constructor_error: move |cx| {
363 SharedWorker::dispatch_simple_error(cx, address);
364 }),
365 );
366 }
367
368 fn create_entangled_inside_port(
369 cx: &mut JSContext,
370 global: &GlobalScope,
371 outside_port: &MessagePort,
372 ) -> Fallible<MessagePortImpl> {
373 let inside_port = MessagePort::new(cx, global);
374 global.track_message_port(&inside_port, None);
375 global.entangle_ports(
376 *outside_port.message_port_id(),
377 *inside_port.message_port_id(),
378 );
379 let (_, inside_port_impl) = inside_port.transfer(cx)?;
380 Ok(inside_port_impl)
381 }
382
383 pub(crate) fn enable_outside_port_message_queue(
385 address: TrustedSharedWorkerAddress,
386 cx: &mut JSContext,
387 ) {
388 let worker = address.root();
389 let global = worker.global();
390 global.start_message_port(cx, worker.port.message_port_id());
392 }
393}
394
395impl SharedWorkerMethods<crate::DomTypeHolder> for SharedWorker {
396 fn Constructor(
398 cx: &mut JSContext,
399 window: &Window,
400 proto: Option<HandleObject>,
401 script_url: TrustedScriptURLOrUSVString,
402 options: StringOrSharedWorkerOptions,
403 ) -> Fallible<DomRoot<SharedWorker>> {
404 let global = window.upcast::<GlobalScope>();
405
406 let compliant_script_url = TrustedScriptURL::get_trusted_type_compliant_string(
410 cx,
411 global,
412 script_url,
413 "SharedWorker constructor",
414 )?;
415
416 let worker_options = match options {
420 StringOrSharedWorkerOptions::String(name) => {
421 let mut options = SharedWorkerOptions::empty();
422 options.parent.name = name;
423 options
424 },
425 StringOrSharedWorkerOptions::SharedWorkerOptions(options) => options,
426 };
427 let worker_name = worker_options.parent.name.clone();
428 let worker_type = worker_options.parent.type_;
429 let credentials = worker_options.parent.credentials.convert();
430 let extended_lifetime = worker_options.extendedLifetime;
431
432 let Ok(worker_url) = global
438 .encoding_parse_a_url(&compliant_script_url.str())
439 .map(|url| ensure_blob_referenced_by_url_is_kept_alive(global, url))
440 else {
441 return Err(Error::Syntax(None));
442 };
443 let constructor_origin = global.origin().immutable().clone();
444 let constructor_url = worker_url.url();
445
446 let outside_port = MessagePort::new(cx, global);
448 global.track_message_port(&outside_port, None);
449
450 let caller_is_secure_context = global.is_secure_context();
454 let outside_storage_key = SharedWorkerStorageKey::for_window(global, window);
457
458 let worker_name_string = worker_name.to_string();
459 let (control_sender, control_receiver) = unbounded();
460
461 let worker = SharedWorker::new(global, proto, &outside_port, control_sender.clone(), cx);
463 let worker_addr = Trusted::new(&*worker);
464
465 let shared_worker_key = SharedWorkerKey {
468 storage_key: outside_storage_key.clone(),
469 constructor_origin: constructor_origin.clone(),
471 constructor_url: constructor_url.clone(),
472 name: worker_name_string,
473 };
474
475 let shared_worker = find_or_claim_shared_worker(shared_worker_key.clone());
486
487 match shared_worker {
488 SharedWorkerClaimResult::Created(registration) => {
489 if registration.worker_type != worker_type ||
497 registration.credentials != credentials ||
498 registration.extended_lifetime != extended_lifetime
499 {
500 SharedWorker::queue_simple_error(global, worker_addr);
502 return Ok(worker);
504 }
505
506 if registration.worker_is_secure_context != caller_is_secure_context {
511 SharedWorker::queue_simple_error(global, worker_addr);
513 return Ok(worker);
515 }
516
517 let inside_port_impl =
521 SharedWorker::create_entangled_inside_port(cx, global, &outside_port)?;
522 if send_connect_to_created_worker(®istration, inside_port_impl) {
524 SharedWorker::queue_simple_error(global, worker_addr);
525 }
526 return Ok(worker);
528 },
529 SharedWorkerClaimResult::Failed => {
530 SharedWorker::queue_simple_error(global, worker_addr);
531 return Ok(worker);
532 },
533 SharedWorkerClaimResult::Claimed => {},
534 }
535
536 let initial_inside_port_impl =
537 match SharedWorker::create_entangled_inside_port(cx, global, &outside_port) {
538 Ok(inside_port_impl) => inside_port_impl,
539 Err(error) => {
540 remove_creating_shared_worker(&shared_worker_key);
541 return Err(error);
542 },
543 };
544
545 let parent_event_loop_sender = global
546 .event_loop_sender()
547 .expect("Window global must have an event loop sender");
548
549 let (sender, receiver) = unbounded();
550 let closing = Arc::new(AtomicBool::new(false));
551 let registration_id = Uuid::new_v4();
552
553 let worker_load_origin = WorkerScriptLoadOrigin {
554 referrer_url: match global.get_referrer() {
555 Referrer::Client(url) => Some(url),
556 Referrer::ReferrerUrl(url) => Some(url),
557 _ => None,
558 },
559 referrer_policy: global.get_referrer_policy(),
560 pipeline_id: global.pipeline_id(),
561 };
562
563 let (devtools_sender, devtools_receiver) = generic_channel::channel().unwrap();
564 let worker_id = WorkerId(Uuid::new_v4());
565 if let Some(chan) = global.devtools_chan() {
566 let webview_id = global
567 .webview_id()
568 .expect("Window global must have a WebViewId");
569 let page_info = DevtoolsPageInfo {
570 title: format!("SharedWorker for {}", worker_url.url()),
571 url: worker_url.url(),
572 is_top_level_global: false,
573 is_service_worker: false,
574 };
575 let _ = chan.send(ScriptToDevtoolsControlMsg::NewGlobal(
576 (
577 window.window_proxy().browsing_context_id(),
578 global.pipeline_id(),
579 Some(worker_id),
580 webview_id,
581 ),
582 devtools_sender.clone(),
583 page_info,
584 ));
585 }
586
587 let init = prepare_workerscope_init(
588 global,
589 Some(devtools_sender),
590 Some(worker_id),
591 window.webgl_chan_value(),
592 );
593
594 let (setup_sender, setup_receiver) = unbounded();
595 let (registered_sender, registered_receiver) = unbounded();
596
597 let _join_handle = match SharedWorkerGlobalScope::run_shared_worker_scope(
599 init,
600 window.webview_id(),
601 Some(window.window_proxy().browsing_context_id()),
602 worker_name,
603 worker_type,
604 worker_url,
605 worker_addr.clone(),
606 parent_event_loop_sender,
607 devtools_receiver,
608 sender.clone(),
609 receiver,
610 worker_load_origin,
611 closing.clone(),
612 global.image_cache(),
613 #[cfg(feature = "webgpu")]
614 global.wgpu_id_hub(),
615 control_receiver,
616 setup_sender,
617 registered_receiver,
618 registration_id,
619 credentials,
620 extended_lifetime,
621 constructor_origin,
622 constructor_url,
623 outside_storage_key,
624 global.insecure_requests_policy(),
625 global.policy_container(),
626 global.font_context().cloned(),
627 ) {
628 Ok(join_handle) => join_handle,
629 Err(error) => {
630 error!("Failed to spawn SharedWorker thread: {error}");
631 remove_creating_shared_worker(&shared_worker_key);
632 SharedWorker::queue_simple_error(global, worker_addr);
633 return Ok(worker);
634 },
635 };
636
637 let Ok(worker_is_secure_context) = setup_receiver.recv() else {
639 remove_creating_shared_worker(&shared_worker_key);
640 SharedWorker::queue_simple_error(global, worker_addr);
641 return Ok(worker);
642 };
643
644 let registration = SharedWorkerRegistration {
645 id: registration_id,
646 worker_type,
647 credentials,
648 extended_lifetime,
649 worker_is_secure_context,
650 closing,
651 sender,
652 _control_sender: control_sender,
653 };
654
655 if !transition_creating_to_created(&shared_worker_key, registration.clone()) {
656 SharedWorker::queue_simple_error(global, worker_addr);
657 return Ok(worker);
658 }
659
660 if registered_sender.send(()).is_err() {
661 SharedWorker::unregister_shared_worker(registration_id);
662 SharedWorker::queue_simple_error(global, worker_addr);
663 return Ok(worker);
664 }
665
666 if send_connect_to_created_worker(®istration, initial_inside_port_impl) {
673 SharedWorker::unregister_shared_worker(registration_id);
674 SharedWorker::queue_simple_error(global, worker_addr);
675 return Ok(worker);
676 }
677
678 Ok(worker)
679 }
680
681 fn Port(&self) -> DomRoot<MessagePort> {
683 DomRoot::from_ref(&*self.port)
685 }
686
687 event_handler!(error, GetOnerror, SetOnerror);
689}
690
691impl TaskOnce for SimpleWorkerErrorHandler<SharedWorker> {
692 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
693 fn run_once(self, cx: &mut JSContext) {
694 SharedWorker::dispatch_simple_error(cx, self.addr);
695 }
696}