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