1use std::sync::Arc;
6use std::sync::atomic::AtomicBool;
7use std::thread::{self, JoinHandle};
8use std::time::{Duration, Instant};
9
10use crossbeam_channel::{Receiver, Sender, after};
11use devtools_traits::DevtoolScriptControlMsg;
12use dom_struct::dom_struct;
13use fonts::FontContext;
14use js::context::{JSContext, RawJSContext};
15use js::jsapi::JS_AddInterruptCallback;
16use js::jsval::UndefinedValue;
17use js::realm::CurrentRealm;
18use net_traits::CustomResponseMediator;
19use net_traits::blob_url_store::UrlWithBlobClaim;
20use net_traits::request::{
21 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
22};
23use rand::random;
24use script_bindings::interfaces::HasOrigin;
25use servo_base::generic_channel::{GenericReceiver, GenericSend, GenericSender, RoutedReceiver};
26use servo_base::id::{PipelineId, ServiceWorkerId};
27use servo_config::pref;
28use servo_constellation_traits::{
29 ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin,
30};
31use servo_url::{MutableOrigin, ServoUrl};
32use style::thread_state::{self, ThreadState};
33
34use crate::dom::abstractworker::WorkerScriptMsg;
35use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
36use crate::dom::bindings::codegen::Bindings::ClientBinding::FrameType;
37use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
38use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
39use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
40use crate::dom::bindings::codegen::UnionTypes::ClientOrServiceWorkerOrMessagePort;
41use crate::dom::bindings::inheritance::Castable;
42use crate::dom::bindings::root::DomRoot;
43use crate::dom::bindings::str::DOMString;
44use crate::dom::bindings::structuredclone;
45use crate::dom::bindings::trace::CustomTraceable;
46use crate::dom::bindings::utils::define_all_exposed_interfaces;
47use crate::dom::client::Client;
48use crate::dom::csp::Violation;
49use crate::dom::debugger::debuggerglobalscope::DebuggerGlobalScope;
50use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
51use crate::dom::event::Event;
52use crate::dom::eventtarget::EventTarget;
53use crate::dom::extendableevent::ExtendableEvent;
54use crate::dom::extendablemessageevent::ExtendableMessageEvent;
55use crate::dom::globalscope::GlobalScope;
56use crate::dom::globalscope::script_execution::{ErrorReporting, RethrowErrors};
57#[cfg(feature = "webgpu")]
58use crate::dom::webgpu::identityhub::IdentityHub;
59use crate::dom::worker::TrustedWorkerAddress;
60use crate::dom::workerglobalscope::WorkerGlobalScope;
61use crate::fetch::{CspViolationsProcessor, load_whole_resource};
62use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
63use crate::modules::script_module::ScriptFetchOptions;
64use crate::realms::enter_auto_realm;
65use crate::script_runtime::{IntroductionType, Runtime, ThreadSafeJSContext};
66use crate::tasks::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
67use crate::tasks::task_source::TaskSourceName;
68
69pub(crate) enum ServiceWorkerScriptMsg {
71 CommonWorker(WorkerScriptMsg),
73 Response(CustomResponseMediator),
75 WakeUp,
77}
78
79impl QueuedTaskConversion for ServiceWorkerScriptMsg {
80 fn task_source_name(&self) -> Option<&TaskSourceName> {
81 let script_msg = match self {
82 ServiceWorkerScriptMsg::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<PipelineId> {
94 None
97 }
98
99 fn into_queued_task(self) -> Option<QueuedTask> {
100 let script_msg = match self {
101 ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
102 _ => return None,
103 };
104 let (event_category, task, pipeline_id, task_source) = match script_msg {
105 CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
106 (category, boxed, pipeline_id, task_source)
107 },
108 _ => return None,
109 };
110 Some(QueuedTask {
111 worker: None,
112 event_category,
113 task,
114 pipeline_id,
115 task_source,
116 })
117 }
118
119 fn from_queued_task(queued_task: QueuedTask) -> Self {
120 let script_msg = CommonScriptMsg::Task(
121 queued_task.event_category,
122 queued_task.task,
123 queued_task.pipeline_id,
124 queued_task.task_source,
125 );
126 ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
127 }
128
129 fn inactive_msg() -> Self {
130 panic!("Workers should never receive messages marked as inactive");
132 }
133
134 fn wake_up_msg() -> Self {
135 ServiceWorkerScriptMsg::WakeUp
136 }
137
138 fn is_wake_up(&self) -> bool {
139 matches!(self, ServiceWorkerScriptMsg::WakeUp)
140 }
141}
142
143pub(crate) enum ServiceWorkerControlMsg {
145 Exit,
147}
148
149pub(crate) enum MixedMessage {
150 ServiceWorker(ServiceWorkerScriptMsg),
151 Devtools(DevtoolScriptControlMsg),
152 Control(ServiceWorkerControlMsg),
153 Timer,
154}
155
156struct ServiceWorkerCspProcessor {}
157
158impl CspViolationsProcessor for ServiceWorkerCspProcessor {
159 fn process_csp_violations(&self, _cx: &mut JSContext, _violations: Vec<Violation>) {}
160}
161
162#[dom_struct]
163pub(crate) struct ServiceWorkerGlobalScope {
164 workerglobalscope: WorkerGlobalScope,
165
166 #[ignore_malloc_size_of = "Defined in std"]
167 #[no_trace]
168 task_queue: TaskQueue<ServiceWorkerScriptMsg>,
169
170 own_sender: Sender<ServiceWorkerScriptMsg>,
171
172 #[no_trace]
177 time_out_port: Receiver<Instant>,
178
179 #[no_trace]
180 swmanager_sender: GenericSender<ServiceWorkerMsg>,
181
182 #[no_trace]
183 scope_url: ServoUrl,
184
185 #[no_trace]
188 control_receiver: Receiver<ServiceWorkerControlMsg>,
189
190 #[no_trace]
191 worker_id: ServiceWorkerId,
192}
193
194impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
195 type WorkerMsg = ServiceWorkerScriptMsg;
196 type ControlMsg = ServiceWorkerControlMsg;
197 type Event = MixedMessage;
198
199 fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
200 &self.task_queue
201 }
202
203 fn handle_event(&self, event: MixedMessage, cx: &mut JSContext) -> bool {
204 self.handle_mixed_message(event, cx)
205 }
206
207 fn handle_worker_post_event(
208 &self,
209 _worker: &TrustedWorkerAddress,
210 ) -> Option<AutoWorkerReset<'_>> {
211 None
212 }
213
214 fn from_control_msg(msg: ServiceWorkerControlMsg) -> MixedMessage {
215 MixedMessage::Control(msg)
216 }
217
218 fn from_worker_msg(msg: ServiceWorkerScriptMsg) -> MixedMessage {
219 MixedMessage::ServiceWorker(msg)
220 }
221
222 fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
223 MixedMessage::Devtools(msg)
224 }
225
226 fn from_timer_msg() -> MixedMessage {
227 MixedMessage::Timer
228 }
229
230 fn control_receiver(&self) -> &Receiver<ServiceWorkerControlMsg> {
231 &self.control_receiver
232 }
233}
234
235impl ServiceWorkerGlobalScope {
236 #[allow(clippy::too_many_arguments)]
237 fn new_inherited(
238 init: WorkerGlobalScopeInit,
239 worker_url: ServoUrl,
240 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
241 runtime: Runtime,
242 own_sender: Sender<ServiceWorkerScriptMsg>,
243 receiver: Receiver<ServiceWorkerScriptMsg>,
244 time_out_port: Receiver<Instant>,
245 swmanager_sender: GenericSender<ServiceWorkerMsg>,
246 scope_url: ServoUrl,
247 control_receiver: Receiver<ServiceWorkerControlMsg>,
248 closing: Arc<AtomicBool>,
249 font_context: Arc<FontContext>,
250 worker_id: ServiceWorkerId,
251 ) -> ServiceWorkerGlobalScope {
252 ServiceWorkerGlobalScope {
253 workerglobalscope: WorkerGlobalScope::new_inherited(
254 init,
255 DOMString::new(),
256 WorkerType::Classic, worker_url,
258 runtime,
259 from_devtools_receiver,
260 closing,
261 #[cfg(feature = "webgpu")]
262 Arc::new(IdentityHub::default()),
263 InsecureRequestsPolicy::DoNotUpgrade,
265 Some(font_context),
266 Some(ScriptEventLoopSender::ServiceWorker(own_sender.clone())),
267 ),
268 task_queue: TaskQueue::new(receiver, own_sender.clone()),
269 own_sender,
270 time_out_port,
271 swmanager_sender,
272 scope_url,
273 control_receiver,
274 worker_id,
275 }
276 }
277
278 #[allow(clippy::too_many_arguments)]
279 pub(crate) fn new(
280 init: WorkerGlobalScopeInit,
281 worker_url: ServoUrl,
282 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
283 runtime: Runtime,
284 own_sender: Sender<ServiceWorkerScriptMsg>,
285 receiver: Receiver<ServiceWorkerScriptMsg>,
286 time_out_port: Receiver<Instant>,
287 swmanager_sender: GenericSender<ServiceWorkerMsg>,
288 scope_url: ServoUrl,
289 control_receiver: Receiver<ServiceWorkerControlMsg>,
290 closing: Arc<AtomicBool>,
291 font_context: Arc<FontContext>,
292 debugger_global: &DebuggerGlobalScope,
293 worker_id: ServiceWorkerId,
294 cx: &mut JSContext,
295 ) -> DomRoot<ServiceWorkerGlobalScope> {
296 let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
297 init,
298 worker_url,
299 from_devtools_receiver,
300 runtime,
301 own_sender,
302 receiver,
303 time_out_port,
304 swmanager_sender,
305 scope_url,
306 control_receiver,
307 closing,
308 font_context,
309 worker_id,
310 ));
311 let scope = ServiceWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(
312 cx,
313 &scope.origin(),
314 scope,
315 );
316 scope
317 .upcast::<WorkerGlobalScope>()
318 .init_debugger_global(debugger_global, cx);
319
320 scope
321 }
322
323 #[expect(unsafe_code)]
325 #[allow(clippy::too_many_arguments)]
326 pub(crate) fn run_serviceworker_scope(
327 scope_things: ScopeThings,
328 own_sender: Sender<ServiceWorkerScriptMsg>,
329 receiver: Receiver<ServiceWorkerScriptMsg>,
330 devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
331 swmanager_sender: GenericSender<ServiceWorkerMsg>,
332 scope_url: ServoUrl,
333 control_receiver: Receiver<ServiceWorkerControlMsg>,
334 context_sender: Sender<ThreadSafeJSContext>,
335 closing: Arc<AtomicBool>,
336 font_context: Arc<FontContext>,
337 worker_id: ServiceWorkerId,
338 ) -> JoinHandle<()> {
339 let ScopeThings {
340 script_url,
341 init,
342 worker_load_origin,
343 ..
344 } = scope_things;
345
346 let serialized_worker_url = script_url.to_string();
347 let origin = scope_url.origin();
348 thread::Builder::new()
349 .name(format!("SW:{}", script_url.debug_compact()))
350 .spawn(move || {
351 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
352 let runtime = Runtime::new(None);
353 let mut cx = unsafe { runtime.cx() };
357 let cx = &mut cx;
358 let context_for_interrupt = runtime.thread_safe_js_context();
359 let _ = context_sender.send(context_for_interrupt);
360
361 let WorkerScriptLoadOrigin {
362 referrer_url,
363 referrer_policy,
364 pipeline_id,
365 } = worker_load_origin;
366
367 let debugger_global = DebuggerGlobalScope::new(
368 pipeline_id,
369 init.to_devtools_sender.clone(),
370 init.from_devtools_sender
371 .clone()
372 .expect("Guaranteed by update_serviceworker"),
373 init.mem_profiler_chan.clone(),
374 init.time_profiler_chan.clone(),
375 init.script_to_constellation_chan.clone(),
376 init.script_to_embedder_chan.clone(),
377 init.resource_threads.clone(),
378 init.storage_threads.clone(),
379 #[cfg(feature = "webgpu")]
380 Arc::new(IdentityHub::default()),
381 cx,
382 );
383 debugger_global.execute(cx);
384
385 let sw_lifetime_timeout = pref!(dom_serviceworker_timeout_seconds) as u64;
388 let time_out_port = after(Duration::new(sw_lifetime_timeout, 0));
389
390 let devtools_mpsc_port = devtools_receiver.route_preserving_errors();
391
392 let resource_threads_sender = init.resource_threads.sender();
393 let devtools_enabled = init.to_devtools_sender.is_some();
394 let global = ServiceWorkerGlobalScope::new(
395 init,
396 script_url.clone(),
397 devtools_mpsc_port,
398 runtime,
399 own_sender,
400 receiver,
401 time_out_port,
402 swmanager_sender,
403 scope_url,
404 control_receiver,
405 closing,
406 font_context,
407 &debugger_global,
408 worker_id,
409 cx,
410 );
411
412 let worker_scope = global.upcast::<WorkerGlobalScope>();
413 let global_scope = global.upcast::<GlobalScope>();
414
415 if devtools_enabled {
416 debugger_global.fire_add_debuggee(
417 cx,
418 global_scope,
419 pipeline_id,
420 Some(worker_scope.worker_id()),
421 );
422 }
423
424 let referrer = referrer_url
425 .map(Referrer::ReferrerUrl)
426 .unwrap_or_else(|| global_scope.get_referrer());
427
428 let request = RequestBuilder::new(
429 None,
430 UrlWithBlobClaim::from_url_without_having_claimed_blob(script_url),
431 referrer,
432 )
433 .destination(Destination::ServiceWorker)
434 .credentials_mode(CredentialsMode::Include)
435 .parser_metadata(ParserMetadata::NotParserInserted)
436 .use_url_credentials(true)
437 .pipeline_id(Some(pipeline_id))
438 .referrer_policy(referrer_policy)
439 .policy_container(global_scope.policy_container())
441 .origin(origin);
442
443 let (url, source) = match load_whole_resource(
444 request,
445 &resource_threads_sender,
446 global.upcast(),
447 &ServiceWorkerCspProcessor {},
448 cx,
449 ) {
450 Err(_) => {
451 error!("error loading script {}", serialized_worker_url);
452 worker_scope.clear_js_runtime();
453 return;
454 },
455 Ok((metadata, bytes, _)) => (metadata.final_url, bytes),
456 };
457
458 unsafe {
459 JS_AddInterruptCallback(cx.raw_cx(), Some(interrupt_callback));
461 }
462
463 {
464 let mut realm = enter_auto_realm(cx, worker_scope);
466 let mut realm = realm.current_realm();
467 define_all_exposed_interfaces(&mut realm, global_scope);
468
469 let script = global_scope.create_a_classic_script(
470 &mut realm,
471 String::from_utf8_lossy(&source),
472 url,
473 ScriptFetchOptions::default_classic_script(),
474 ErrorReporting::Unmuted,
475 Some(IntroductionType::WORKER),
476 1,
477 true,
478 );
479 _ = global_scope.run_a_classic_script(&mut realm, script, RethrowErrors::No);
480 global.dispatch_activate(&mut realm);
481 }
482
483 let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
484 global_scope.mem_profiler_chan().run_with_memory_reporting(
485 || {
486 while !worker_scope.is_closing() && !global.has_timed_out() {
493 run_worker_event_loop(&*global, None, cx);
494 }
495 },
496 reporter_name,
497 global.event_loop_sender(),
498 CommonScriptMsg::CollectReports,
499 );
500
501 worker_scope.clear_js_runtime();
502 })
503 .expect("Thread spawning failed")
504 }
505
506 fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut JSContext) -> bool {
507 match msg {
508 MixedMessage::Devtools(msg) => self
509 .upcast::<WorkerGlobalScope>()
510 .handle_devtools_message(msg, cx),
511 MixedMessage::ServiceWorker(msg) => {
512 self.handle_script_event(msg, cx);
513 },
514 MixedMessage::Control(ServiceWorkerControlMsg::Exit) => {
515 return false;
516 },
517 MixedMessage::Timer => {},
518 }
519 true
520 }
521
522 fn has_timed_out(&self) -> bool {
523 false
525 }
526
527 fn handle_script_event(&self, msg: ServiceWorkerScriptMsg, cx: &mut JSContext) {
528 use self::ServiceWorkerScriptMsg::*;
529
530 match msg {
531 CommonWorker(WorkerScriptMsg::DOMMessage(msg)) => {
532 let scope = self.upcast::<WorkerGlobalScope>();
533 let target = self.upcast();
534
535 let mut realm = enter_auto_realm(cx, scope);
536 let cx = &mut realm.current_realm();
537
538 rooted!(&in(cx) let mut message = UndefinedValue());
539 let client = Client::new(
540 cx,
541 scope.upcast(),
542 self.swmanager_sender.clone(),
543 self.scope_url.clone(),
544 FrameType::None,
545 self.worker_id,
546 );
547 if let Ok(ports) =
548 structuredclone::read(cx, scope.upcast(), *msg.data, message.handle_mut())
549 {
550 ExtendableMessageEvent::dispatch_jsval(
551 cx,
552 target,
553 scope.upcast(),
554 message.handle(),
555 Some(&ClientOrServiceWorkerOrMessagePort::Client(client)),
556 ports,
557 );
558 } else {
559 ExtendableMessageEvent::dispatch_error(cx, target, scope.upcast());
560 }
561 },
562 CommonWorker(WorkerScriptMsg::Common(msg)) => {
563 self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
564 },
565 Response(mediator) => {
566 self.upcast::<EventTarget>().fire_event(cx, atom!("fetch"));
570 let _ = mediator.response_chan.send(None);
571 },
572 WakeUp => {},
573 }
574 }
575
576 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
577 ScriptEventLoopSender::ServiceWorker(self.own_sender.clone())
578 }
579
580 fn dispatch_activate(&self, cx: &mut CurrentRealm) {
581 let event = ExtendableEvent::new(cx, self, atom!("activate"), false, false);
582 let event = (*event).upcast::<Event>();
583 event.dispatch(cx, self.upcast(), false);
584 }
585}
586
587#[expect(unsafe_code)]
588unsafe extern "C" fn interrupt_callback(cx: *mut RawJSContext) -> bool {
589 let mut cx = unsafe { JSContext::from_ptr(std::ptr::NonNull::new(cx).unwrap()) };
591 let mut realm = CurrentRealm::assert(&mut cx);
592
593 let global = GlobalScope::from_current_realm(&mut realm);
594 let worker =
595 DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope");
596 assert!(worker.is::<ServiceWorkerGlobalScope>());
597
598 !worker.is_closing()
600}
601
602impl ServiceWorkerGlobalScopeMethods<crate::DomTypeHolder> for ServiceWorkerGlobalScope {
603 event_handler!(message, GetOnmessage, SetOnmessage);
605
606 event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
608}
609
610impl HasOrigin for ServiceWorkerGlobalScope {
611 fn origin(&self) -> MutableOrigin {
612 self.upcast::<WorkerGlobalScope>().origin()
613 }
614}