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