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