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 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::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
187impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
188 type WorkerMsg = ServiceWorkerScriptMsg;
189 type ControlMsg = ServiceWorkerControlMsg;
190 type Event = MixedMessage;
191
192 fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
193 &self.task_queue
194 }
195
196 fn handle_event(&self, event: MixedMessage, cx: &mut js::context::JSContext) -> bool {
197 self.handle_mixed_message(event, cx)
198 }
199
200 fn handle_worker_post_event(
201 &self,
202 _worker: &TrustedWorkerAddress,
203 ) -> Option<AutoWorkerReset<'_>> {
204 None
205 }
206
207 fn from_control_msg(msg: ServiceWorkerControlMsg) -> MixedMessage {
208 MixedMessage::Control(msg)
209 }
210
211 fn from_worker_msg(msg: ServiceWorkerScriptMsg) -> MixedMessage {
212 MixedMessage::ServiceWorker(msg)
213 }
214
215 fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
216 MixedMessage::Devtools(msg)
217 }
218
219 fn from_timer_msg() -> MixedMessage {
220 MixedMessage::Timer
221 }
222
223 fn control_receiver(&self) -> &Receiver<ServiceWorkerControlMsg> {
224 &self.control_receiver
225 }
226}
227
228impl ServiceWorkerGlobalScope {
229 #[allow(clippy::too_many_arguments)]
230 fn new_inherited(
231 init: WorkerGlobalScopeInit,
232 worker_url: ServoUrl,
233 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
234 runtime: Runtime,
235 own_sender: Sender<ServiceWorkerScriptMsg>,
236 receiver: Receiver<ServiceWorkerScriptMsg>,
237 time_out_port: Receiver<Instant>,
238 swmanager_sender: GenericSender<ServiceWorkerMsg>,
239 scope_url: ServoUrl,
240 control_receiver: Receiver<ServiceWorkerControlMsg>,
241 closing: Arc<AtomicBool>,
242 font_context: Arc<FontContext>,
243 ) -> ServiceWorkerGlobalScope {
244 ServiceWorkerGlobalScope {
245 workerglobalscope: WorkerGlobalScope::new_inherited(
246 init,
247 DOMString::new(),
248 WorkerType::Classic, worker_url,
250 runtime,
251 from_devtools_receiver,
252 closing,
253 #[cfg(feature = "webgpu")]
254 Arc::new(IdentityHub::default()),
255 InsecureRequestsPolicy::DoNotUpgrade,
257 Some(font_context),
258 ),
259 task_queue: TaskQueue::new(receiver, own_sender.clone()),
260 own_sender,
261 time_out_port,
262 swmanager_sender,
263 scope_url,
264 control_receiver,
265 }
266 }
267
268 #[allow(clippy::too_many_arguments)]
269 pub(crate) fn new(
270 init: WorkerGlobalScopeInit,
271 worker_url: ServoUrl,
272 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
273 runtime: Runtime,
274 own_sender: Sender<ServiceWorkerScriptMsg>,
275 receiver: Receiver<ServiceWorkerScriptMsg>,
276 time_out_port: Receiver<Instant>,
277 swmanager_sender: GenericSender<ServiceWorkerMsg>,
278 scope_url: ServoUrl,
279 control_receiver: Receiver<ServiceWorkerControlMsg>,
280 closing: Arc<AtomicBool>,
281 font_context: Arc<FontContext>,
282 debugger_global: &DebuggerGlobalScope,
283 cx: &mut js::context::JSContext,
284 ) -> DomRoot<ServiceWorkerGlobalScope> {
285 let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
286 init,
287 worker_url,
288 from_devtools_receiver,
289 runtime,
290 own_sender,
291 receiver,
292 time_out_port,
293 swmanager_sender,
294 scope_url,
295 control_receiver,
296 closing,
297 font_context,
298 ));
299 let scope = ServiceWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, scope);
300 scope
301 .upcast::<WorkerGlobalScope>()
302 .init_debugger_global(debugger_global, cx);
303
304 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 = DebuggerGlobalScope::new(
351 pipeline_id,
352 init.to_devtools_sender.clone(),
353 init.from_devtools_sender
354 .clone()
355 .expect("Guaranteed by update_serviceworker"),
356 init.mem_profiler_chan.clone(),
357 init.time_profiler_chan.clone(),
358 init.script_to_constellation_chan.clone(),
359 init.script_to_embedder_chan.clone(),
360 init.resource_threads.clone(),
361 init.storage_threads.clone(),
362 #[cfg(feature = "webgpu")]
363 Arc::new(IdentityHub::default()),
364 cx,
365 );
366 debugger_global.execute(cx);
367
368 let sw_lifetime_timeout = pref!(dom_serviceworker_timeout_seconds) as u64;
371 let time_out_port = after(Duration::new(sw_lifetime_timeout, 0));
372
373 let devtools_mpsc_port = devtools_receiver.route_preserving_errors();
374
375 let resource_threads_sender = init.resource_threads.sender();
376 let global = ServiceWorkerGlobalScope::new(
377 init,
378 script_url.clone(),
379 devtools_mpsc_port,
380 runtime,
381 own_sender,
382 receiver,
383 time_out_port,
384 swmanager_sender,
385 scope_url,
386 control_receiver,
387 closing,
388 font_context,
389 &debugger_global,
390 cx,
391 );
392
393 let worker_scope = global.upcast::<WorkerGlobalScope>();
394 let global_scope = global.upcast::<GlobalScope>();
395
396 debugger_global.fire_add_debuggee(
397 cx,
398 global_scope,
399 pipeline_id,
400 Some(worker_scope.worker_id()),
401 );
402
403 let referrer = referrer_url
404 .map(Referrer::ReferrerUrl)
405 .unwrap_or_else(|| global_scope.get_referrer());
406
407 let request = RequestBuilder::new(
408 None,
409 UrlWithBlobClaim::from_url_without_having_claimed_blob(script_url),
410 referrer,
411 )
412 .destination(Destination::ServiceWorker)
413 .credentials_mode(CredentialsMode::Include)
414 .parser_metadata(ParserMetadata::NotParserInserted)
415 .use_url_credentials(true)
416 .pipeline_id(Some(pipeline_id))
417 .referrer_policy(referrer_policy)
418 .insecure_requests_policy(worker_scope.insecure_requests_policy())
419 .policy_container(global_scope.policy_container())
421 .origin(origin);
422
423 let (url, source) = match load_whole_resource(
424 request,
425 &resource_threads_sender,
426 global.upcast(),
427 &ServiceWorkerCspProcessor {},
428 cx,
429 ) {
430 Err(_) => {
431 error!("error loading script {}", serialized_worker_url);
432 worker_scope.clear_js_runtime();
433 return;
434 },
435 Ok((metadata, bytes, _)) => (metadata.final_url, bytes),
436 };
437
438 unsafe {
439 JS_AddInterruptCallback(cx.raw_cx(), Some(interrupt_callback));
441 }
442
443 {
444 let mut realm = enter_auto_realm(cx, worker_scope);
446 let mut realm = realm.current_realm();
447 define_all_exposed_interfaces(&mut realm, global_scope);
448
449 let script = global_scope.create_a_classic_script(
450 &mut realm,
451 String::from_utf8_lossy(&source),
452 url,
453 ScriptFetchOptions::default_classic_script(),
454 ErrorReporting::Unmuted,
455 Some(IntroductionType::WORKER),
456 1,
457 true,
458 );
459 _ = global_scope.run_a_classic_script(&mut realm, script, RethrowErrors::No);
460 let in_realm_proof = (&mut realm).into();
461 global.dispatch_activate(
462 CanGc::from_cx(&mut realm),
463 InRealm::Already(&in_realm_proof),
464 );
465 }
466
467 let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
468 global_scope.mem_profiler_chan().run_with_memory_reporting(
469 || {
470 while !worker_scope.is_closing() && !global.has_timed_out() {
477 run_worker_event_loop(&*global, None, cx);
478 }
479 },
480 reporter_name,
481 global.event_loop_sender(),
482 CommonScriptMsg::CollectReports,
483 );
484
485 worker_scope.clear_js_runtime();
486 })
487 .expect("Thread spawning failed")
488 }
489
490 fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut js::context::JSContext) -> bool {
491 match msg {
492 MixedMessage::Devtools(msg) => self
493 .upcast::<WorkerGlobalScope>()
494 .handle_devtools_message(msg, cx),
495 MixedMessage::ServiceWorker(msg) => {
496 self.handle_script_event(msg, cx);
497 },
498 MixedMessage::Control(ServiceWorkerControlMsg::Exit) => {
499 return false;
500 },
501 MixedMessage::Timer => {},
502 }
503 true
504 }
505
506 fn has_timed_out(&self) -> bool {
507 false
509 }
510
511 fn handle_script_event(&self, msg: ServiceWorkerScriptMsg, cx: &mut js::context::JSContext) {
512 use self::ServiceWorkerScriptMsg::*;
513
514 match msg {
515 CommonWorker(WorkerScriptMsg::DOMMessage(msg)) => {
516 let scope = self.upcast::<WorkerGlobalScope>();
517 let target = self.upcast();
518 let _ac = enter_realm(scope);
519 rooted!(&in(cx) let mut message = UndefinedValue());
520 if let Ok(ports) = structuredclone::read(
521 scope.upcast(),
522 *msg.data,
523 message.handle_mut(),
524 CanGc::from_cx(cx),
525 ) {
526 ExtendableMessageEvent::dispatch_jsval(
527 target,
528 scope.upcast(),
529 message.handle(),
530 ports,
531 CanGc::from_cx(cx),
532 );
533 } else {
534 ExtendableMessageEvent::dispatch_error(cx, target, scope.upcast());
535 }
536 },
537 CommonWorker(WorkerScriptMsg::Common(msg)) => {
538 self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
539 },
540 Response(mediator) => {
541 self.upcast::<EventTarget>().fire_event(cx, atom!("fetch"));
545 let _ = mediator.response_chan.send(None);
546 },
547 WakeUp => {},
548 }
549 }
550
551 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
552 ScriptEventLoopSender::ServiceWorker(self.own_sender.clone())
553 }
554
555 fn dispatch_activate(&self, can_gc: CanGc, _realm: InRealm) {
556 let event = ExtendableEvent::new(self, atom!("activate"), false, false, can_gc);
557 let event = (*event).upcast::<Event>();
558 self.upcast::<EventTarget>().dispatch_event(event, can_gc);
559 }
560}
561
562#[expect(unsafe_code)]
563unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
564 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
565 let global = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
566 let worker =
567 DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope");
568 assert!(worker.is::<ServiceWorkerGlobalScope>());
569
570 !worker.is_closing()
572}
573
574impl ServiceWorkerGlobalScopeMethods<crate::DomTypeHolder> for ServiceWorkerGlobalScope {
575 event_handler!(message, GetOnmessage, SetOnmessage);
577
578 event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
580}