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