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