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