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::jsapi::{JS_AddInterruptCallback, JSContext};
15use js::jsval::UndefinedValue;
16use js::realm::CurrentRealm;
17use net_traits::CustomResponseMediator;
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::request::{
20 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
21};
22use rand::random;
23use servo_base::generic_channel::{GenericReceiver, GenericSend, GenericSender, RoutedReceiver};
24use servo_base::id::{PipelineId, ServiceWorkerId};
25use servo_config::pref;
26use servo_constellation_traits::{
27 ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin,
28};
29use servo_url::ServoUrl;
30use style::thread_state::{self, ThreadState};
31
32use crate::dom::abstractworker::WorkerScriptMsg;
33use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
34use crate::dom::bindings::codegen::Bindings::ClientBinding::FrameType;
35use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
36use crate::dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
37use crate::dom::bindings::codegen::Bindings::WorkerBinding::WorkerType;
38use crate::dom::bindings::inheritance::Castable;
39use crate::dom::bindings::root::DomRoot;
40use crate::dom::bindings::str::DOMString;
41use crate::dom::bindings::structuredclone;
42use crate::dom::bindings::trace::CustomTraceable;
43use crate::dom::bindings::utils::define_all_exposed_interfaces;
44use crate::dom::client::Client;
45use crate::dom::csp::Violation;
46use crate::dom::debugger::debuggerglobalscope::DebuggerGlobalScope;
47use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
48use crate::dom::event::Event;
49use crate::dom::eventtarget::EventTarget;
50use crate::dom::extendableevent::ExtendableEvent;
51use crate::dom::extendablemessageevent::{ExtendableMessageEvent, MessageSource};
52use crate::dom::global_scope_script_execution::{ErrorReporting, RethrowErrors};
53use crate::dom::globalscope::GlobalScope;
54#[cfg(feature = "webgpu")]
55use crate::dom::webgpu::identityhub::IdentityHub;
56use crate::dom::worker::TrustedWorkerAddress;
57use crate::dom::workerglobalscope::WorkerGlobalScope;
58use crate::fetch::{CspViolationsProcessor, load_whole_resource};
59use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
60use crate::realms::{AlreadyInRealm, InRealm, enter_auto_realm};
61use crate::script_module::ScriptFetchOptions;
62use crate::script_runtime::{
63 CanGc, IntroductionType, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext,
64};
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, _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 js::context::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 ),
266 task_queue: TaskQueue::new(receiver, own_sender.clone()),
267 own_sender,
268 time_out_port,
269 swmanager_sender,
270 scope_url,
271 control_receiver,
272 worker_id,
273 }
274 }
275
276 #[allow(clippy::too_many_arguments)]
277 pub(crate) fn new(
278 init: WorkerGlobalScopeInit,
279 worker_url: ServoUrl,
280 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
281 runtime: Runtime,
282 own_sender: Sender<ServiceWorkerScriptMsg>,
283 receiver: Receiver<ServiceWorkerScriptMsg>,
284 time_out_port: Receiver<Instant>,
285 swmanager_sender: GenericSender<ServiceWorkerMsg>,
286 scope_url: ServoUrl,
287 control_receiver: Receiver<ServiceWorkerControlMsg>,
288 closing: Arc<AtomicBool>,
289 font_context: Arc<FontContext>,
290 debugger_global: &DebuggerGlobalScope,
291 worker_id: ServiceWorkerId,
292 cx: &mut js::context::JSContext,
293 ) -> DomRoot<ServiceWorkerGlobalScope> {
294 let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
295 init,
296 worker_url,
297 from_devtools_receiver,
298 runtime,
299 own_sender,
300 receiver,
301 time_out_port,
302 swmanager_sender,
303 scope_url,
304 control_receiver,
305 closing,
306 font_context,
307 worker_id,
308 ));
309 let scope = ServiceWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, scope);
310 scope
311 .upcast::<WorkerGlobalScope>()
312 .init_debugger_global(debugger_global, cx);
313
314 scope
315 }
316
317 #[expect(unsafe_code)]
319 #[allow(clippy::too_many_arguments)]
320 pub(crate) fn run_serviceworker_scope(
321 scope_things: ScopeThings,
322 own_sender: Sender<ServiceWorkerScriptMsg>,
323 receiver: Receiver<ServiceWorkerScriptMsg>,
324 devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
325 swmanager_sender: GenericSender<ServiceWorkerMsg>,
326 scope_url: ServoUrl,
327 control_receiver: Receiver<ServiceWorkerControlMsg>,
328 context_sender: Sender<ThreadSafeJSContext>,
329 closing: Arc<AtomicBool>,
330 font_context: Arc<FontContext>,
331 worker_id: ServiceWorkerId,
332 ) -> JoinHandle<()> {
333 let ScopeThings {
334 script_url,
335 init,
336 worker_load_origin,
337 ..
338 } = scope_things;
339
340 let serialized_worker_url = script_url.to_string();
341 let origin = scope_url.origin();
342 thread::Builder::new()
343 .name(format!("SW:{}", script_url.debug_compact()))
344 .spawn(move || {
345 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
346 let runtime = Runtime::new(None);
347 let mut cx = unsafe { runtime.cx() };
351 let cx = &mut cx;
352 let context_for_interrupt = runtime.thread_safe_js_context();
353 let _ = context_sender.send(context_for_interrupt);
354
355 let WorkerScriptLoadOrigin {
356 referrer_url,
357 referrer_policy,
358 pipeline_id,
359 } = worker_load_origin;
360
361 let debugger_global = DebuggerGlobalScope::new(
362 pipeline_id,
363 init.to_devtools_sender.clone(),
364 init.from_devtools_sender
365 .clone()
366 .expect("Guaranteed by update_serviceworker"),
367 init.mem_profiler_chan.clone(),
368 init.time_profiler_chan.clone(),
369 init.script_to_constellation_chan.clone(),
370 init.script_to_embedder_chan.clone(),
371 init.resource_threads.clone(),
372 init.storage_threads.clone(),
373 #[cfg(feature = "webgpu")]
374 Arc::new(IdentityHub::default()),
375 cx,
376 );
377 debugger_global.execute(cx);
378
379 let sw_lifetime_timeout = pref!(dom_serviceworker_timeout_seconds) as u64;
382 let time_out_port = after(Duration::new(sw_lifetime_timeout, 0));
383
384 let devtools_mpsc_port = devtools_receiver.route_preserving_errors();
385
386 let resource_threads_sender = init.resource_threads.sender();
387 let devtools_enabled = init.to_devtools_sender.is_some();
388 let global = ServiceWorkerGlobalScope::new(
389 init,
390 script_url.clone(),
391 devtools_mpsc_port,
392 runtime,
393 own_sender,
394 receiver,
395 time_out_port,
396 swmanager_sender,
397 scope_url,
398 control_receiver,
399 closing,
400 font_context,
401 &debugger_global,
402 worker_id,
403 cx,
404 );
405
406 let worker_scope = global.upcast::<WorkerGlobalScope>();
407 let global_scope = global.upcast::<GlobalScope>();
408
409 if devtools_enabled {
410 debugger_global.fire_add_debuggee(
411 cx,
412 global_scope,
413 pipeline_id,
414 Some(worker_scope.worker_id()),
415 );
416 }
417
418 let referrer = referrer_url
419 .map(Referrer::ReferrerUrl)
420 .unwrap_or_else(|| global_scope.get_referrer());
421
422 let request = RequestBuilder::new(
423 None,
424 UrlWithBlobClaim::from_url_without_having_claimed_blob(script_url),
425 referrer,
426 )
427 .destination(Destination::ServiceWorker)
428 .credentials_mode(CredentialsMode::Include)
429 .parser_metadata(ParserMetadata::NotParserInserted)
430 .use_url_credentials(true)
431 .pipeline_id(Some(pipeline_id))
432 .referrer_policy(referrer_policy)
433 .insecure_requests_policy(worker_scope.insecure_requests_policy())
434 .policy_container(global_scope.policy_container())
436 .origin(origin);
437
438 let (url, source) = match load_whole_resource(
439 request,
440 &resource_threads_sender,
441 global.upcast(),
442 &ServiceWorkerCspProcessor {},
443 cx,
444 ) {
445 Err(_) => {
446 error!("error loading script {}", serialized_worker_url);
447 worker_scope.clear_js_runtime();
448 return;
449 },
450 Ok((metadata, bytes, _)) => (metadata.final_url, bytes),
451 };
452
453 unsafe {
454 JS_AddInterruptCallback(cx.raw_cx(), Some(interrupt_callback));
456 }
457
458 {
459 let mut realm = enter_auto_realm(cx, worker_scope);
461 let mut realm = realm.current_realm();
462 define_all_exposed_interfaces(&mut realm, global_scope);
463
464 let script = global_scope.create_a_classic_script(
465 &mut realm,
466 String::from_utf8_lossy(&source),
467 url,
468 ScriptFetchOptions::default_classic_script(),
469 ErrorReporting::Unmuted,
470 Some(IntroductionType::WORKER),
471 1,
472 true,
473 );
474 _ = global_scope.run_a_classic_script(&mut realm, script, RethrowErrors::No);
475 global.dispatch_activate(&mut realm);
476 }
477
478 let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
479 global_scope.mem_profiler_chan().run_with_memory_reporting(
480 || {
481 while !worker_scope.is_closing() && !global.has_timed_out() {
488 run_worker_event_loop(&*global, None, cx);
489 }
490 },
491 reporter_name,
492 global.event_loop_sender(),
493 CommonScriptMsg::CollectReports,
494 );
495
496 worker_scope.clear_js_runtime();
497 })
498 .expect("Thread spawning failed")
499 }
500
501 fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut js::context::JSContext) -> bool {
502 match msg {
503 MixedMessage::Devtools(msg) => self
504 .upcast::<WorkerGlobalScope>()
505 .handle_devtools_message(msg, cx),
506 MixedMessage::ServiceWorker(msg) => {
507 self.handle_script_event(msg, cx);
508 },
509 MixedMessage::Control(ServiceWorkerControlMsg::Exit) => {
510 return false;
511 },
512 MixedMessage::Timer => {},
513 }
514 true
515 }
516
517 fn has_timed_out(&self) -> bool {
518 false
520 }
521
522 fn handle_script_event(&self, msg: ServiceWorkerScriptMsg, cx: &mut js::context::JSContext) {
523 use self::ServiceWorkerScriptMsg::*;
524
525 match msg {
526 CommonWorker(WorkerScriptMsg::DOMMessage(msg)) => {
527 let scope = self.upcast::<WorkerGlobalScope>();
528 let target = self.upcast();
529
530 let mut realm = enter_auto_realm(cx, scope);
531 let cx = &mut realm.current_realm();
532
533 rooted!(&in(cx) let mut message = UndefinedValue());
534 let client = Client::new(
535 scope.upcast(),
536 self.swmanager_sender.clone(),
537 self.scope_url.clone(),
538 FrameType::None,
539 self.worker_id,
540 CanGc::from_cx(cx),
541 );
542 if let Ok(ports) =
543 structuredclone::read(cx, scope.upcast(), *msg.data, message.handle_mut())
544 {
545 ExtendableMessageEvent::dispatch_jsval(
546 cx,
547 target,
548 scope.upcast(),
549 message.handle(),
550 Some(MessageSource::Client(client)),
551 ports,
552 );
553 } else {
554 ExtendableMessageEvent::dispatch_error(cx, target, scope.upcast());
555 }
556 },
557 CommonWorker(WorkerScriptMsg::Common(msg)) => {
558 self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
559 },
560 Response(mediator) => {
561 self.upcast::<EventTarget>().fire_event(cx, atom!("fetch"));
565 let _ = mediator.response_chan.send(None);
566 },
567 WakeUp => {},
568 }
569 }
570
571 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
572 ScriptEventLoopSender::ServiceWorker(self.own_sender.clone())
573 }
574
575 fn dispatch_activate(&self, cx: &mut CurrentRealm) {
576 let event = ExtendableEvent::new(self, atom!("activate"), false, false, CanGc::from_cx(cx));
577 let event = (*event).upcast::<Event>();
578 event.dispatch(cx, self.upcast(), false);
579 }
580}
581
582#[expect(unsafe_code)]
583unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
584 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
585 let global = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
586 let worker =
587 DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope");
588 assert!(worker.is::<ServiceWorkerGlobalScope>());
589
590 !worker.is_closing()
592}
593
594impl ServiceWorkerGlobalScopeMethods<crate::DomTypeHolder> for ServiceWorkerGlobalScope {
595 event_handler!(message, GetOnmessage, SetOnmessage);
597
598 event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
600}