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