1use std::sync::Arc;
6use std::sync::atomic::AtomicBool;
7use std::thread::{self, JoinHandle};
8use std::time::{Duration, Instant};
9
10use base::generic_channel::{GenericReceiver, GenericSend, GenericSender, RoutedReceiver};
11use base::id::PipelineId;
12use constellation_traits::{
13 ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin,
14};
15use crossbeam_channel::{Receiver, Sender, after};
16use devtools_traits::DevtoolScriptControlMsg;
17use dom_struct::dom_struct;
18use fonts::FontContext;
19use js::jsapi::{JS_AddInterruptCallback, JSContext};
20use js::jsval::UndefinedValue;
21use net_traits::CustomResponseMediator;
22use net_traits::request::{
23 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
24};
25use rand::random;
26use servo_config::pref;
27use servo_url::ServoUrl;
28use style::thread_state::{self, ThreadState};
29
30use crate::devtools;
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::dedicatedworkerglobalscope::AutoWorkerReset;
44use crate::dom::event::Event;
45use crate::dom::eventtarget::EventTarget;
46use crate::dom::extendableevent::ExtendableEvent;
47use crate::dom::extendablemessageevent::ExtendableMessageEvent;
48use crate::dom::global_scope_script_execution::{ErrorReporting, RethrowErrors};
49use crate::dom::globalscope::GlobalScope;
50#[cfg(feature = "webgpu")]
51use crate::dom::webgpu::identityhub::IdentityHub;
52use crate::dom::worker::TrustedWorkerAddress;
53use crate::dom::workerglobalscope::WorkerGlobalScope;
54use crate::fetch::{CspViolationsProcessor, load_whole_resource};
55use crate::messaging::{CommonScriptMsg, ScriptEventLoopSender};
56use crate::realms::{AlreadyInRealm, InRealm, enter_auto_realm, enter_realm};
57use crate::script_module::ScriptFetchOptions;
58use crate::script_runtime::{
59 CanGc, IntroductionType, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext,
60};
61use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
62use crate::task_source::TaskSourceName;
63
64pub(crate) enum ServiceWorkerScriptMsg {
66 CommonWorker(WorkerScriptMsg),
68 Response(CustomResponseMediator),
70 WakeUp,
72}
73
74impl QueuedTaskConversion for ServiceWorkerScriptMsg {
75 fn task_source_name(&self) -> Option<&TaskSourceName> {
76 let script_msg = match self {
77 ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
78 _ => return None,
79 };
80 match script_msg {
81 CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
82 Some(task_source)
83 },
84 _ => None,
85 }
86 }
87
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 Devtools(DevtoolScriptControlMsg),
147 Control(ServiceWorkerControlMsg),
148 Timer,
149}
150
151struct ServiceWorkerCspProcessor {}
152
153impl CspViolationsProcessor for ServiceWorkerCspProcessor {
154 fn process_csp_violations(&self, _violations: Vec<Violation>) {}
155}
156
157#[dom_struct]
158pub(crate) struct ServiceWorkerGlobalScope {
159 workerglobalscope: WorkerGlobalScope,
160
161 #[ignore_malloc_size_of = "Defined in std"]
162 #[no_trace]
163 task_queue: TaskQueue<ServiceWorkerScriptMsg>,
164
165 own_sender: Sender<ServiceWorkerScriptMsg>,
166
167 #[ignore_malloc_size_of = "Defined in std"]
172 #[no_trace]
173 time_out_port: Receiver<Instant>,
174
175 #[ignore_malloc_size_of = "Defined in std"]
176 #[no_trace]
177 swmanager_sender: GenericSender<ServiceWorkerMsg>,
178
179 #[no_trace]
180 scope_url: ServoUrl,
181
182 #[no_trace]
185 control_receiver: Receiver<ServiceWorkerControlMsg>,
186}
187
188impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
189 type WorkerMsg = ServiceWorkerScriptMsg;
190 type ControlMsg = ServiceWorkerControlMsg;
191 type Event = MixedMessage;
192
193 fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
194 &self.task_queue
195 }
196
197 fn handle_event(&self, event: MixedMessage, cx: &mut js::context::JSContext) -> bool {
198 self.handle_mixed_message(event, cx)
199 }
200
201 fn handle_worker_post_event(
202 &self,
203 _worker: &TrustedWorkerAddress,
204 ) -> Option<AutoWorkerReset<'_>> {
205 None
206 }
207
208 fn from_control_msg(msg: ServiceWorkerControlMsg) -> MixedMessage {
209 MixedMessage::Control(msg)
210 }
211
212 fn from_worker_msg(msg: ServiceWorkerScriptMsg) -> MixedMessage {
213 MixedMessage::ServiceWorker(msg)
214 }
215
216 fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
217 MixedMessage::Devtools(msg)
218 }
219
220 fn from_timer_msg() -> MixedMessage {
221 MixedMessage::Timer
222 }
223
224 fn control_receiver(&self) -> &Receiver<ServiceWorkerControlMsg> {
225 &self.control_receiver
226 }
227}
228
229impl ServiceWorkerGlobalScope {
230 #[allow(clippy::too_many_arguments)]
231 fn new_inherited(
232 init: WorkerGlobalScopeInit,
233 worker_url: ServoUrl,
234 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
235 runtime: Runtime,
236 own_sender: Sender<ServiceWorkerScriptMsg>,
237 receiver: Receiver<ServiceWorkerScriptMsg>,
238 time_out_port: Receiver<Instant>,
239 swmanager_sender: GenericSender<ServiceWorkerMsg>,
240 scope_url: ServoUrl,
241 control_receiver: Receiver<ServiceWorkerControlMsg>,
242 closing: Arc<AtomicBool>,
243 font_context: Arc<FontContext>,
244 ) -> ServiceWorkerGlobalScope {
245 ServiceWorkerGlobalScope {
246 workerglobalscope: WorkerGlobalScope::new_inherited(
247 init,
248 DOMString::new(),
249 WorkerType::Classic, worker_url,
251 runtime,
252 from_devtools_receiver,
253 closing,
254 #[cfg(feature = "webgpu")]
255 Arc::new(IdentityHub::default()),
256 InsecureRequestsPolicy::DoNotUpgrade,
258 Some(font_context),
259 ),
260 task_queue: TaskQueue::new(receiver, own_sender.clone()),
261 own_sender,
262 time_out_port,
263 swmanager_sender,
264 scope_url,
265 control_receiver,
266 }
267 }
268
269 #[allow(clippy::too_many_arguments)]
270 pub(crate) fn new(
271 init: WorkerGlobalScopeInit,
272 worker_url: ServoUrl,
273 from_devtools_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
274 runtime: Runtime,
275 own_sender: Sender<ServiceWorkerScriptMsg>,
276 receiver: Receiver<ServiceWorkerScriptMsg>,
277 time_out_port: Receiver<Instant>,
278 swmanager_sender: GenericSender<ServiceWorkerMsg>,
279 scope_url: ServoUrl,
280 control_receiver: Receiver<ServiceWorkerControlMsg>,
281 closing: Arc<AtomicBool>,
282 font_context: Arc<FontContext>,
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 ServiceWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, scope)
300 }
301
302 #[expect(unsafe_code)]
304 #[allow(clippy::too_many_arguments)]
305 pub(crate) fn run_serviceworker_scope(
306 scope_things: ScopeThings,
307 own_sender: Sender<ServiceWorkerScriptMsg>,
308 receiver: Receiver<ServiceWorkerScriptMsg>,
309 devtools_receiver: GenericReceiver<DevtoolScriptControlMsg>,
310 swmanager_sender: GenericSender<ServiceWorkerMsg>,
311 scope_url: ServoUrl,
312 control_receiver: Receiver<ServiceWorkerControlMsg>,
313 context_sender: Sender<ThreadSafeJSContext>,
314 closing: Arc<AtomicBool>,
315 font_context: Arc<FontContext>,
316 ) -> JoinHandle<()> {
317 let ScopeThings {
318 script_url,
319 init,
320 worker_load_origin,
321 ..
322 } = scope_things;
323
324 let serialized_worker_url = script_url.to_string();
325 let origin = scope_url.origin();
326 thread::Builder::new()
327 .name(format!("SW:{}", script_url.debug_compact()))
328 .spawn(move || {
329 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
330 let runtime = Runtime::new(None);
331 let mut cx = unsafe { runtime.cx() };
335 let cx = &mut cx;
336 let context_for_interrupt = runtime.thread_safe_js_context();
337 let _ = context_sender.send(context_for_interrupt);
338
339 let WorkerScriptLoadOrigin {
340 referrer_url,
341 referrer_policy,
342 pipeline_id,
343 } = worker_load_origin;
344
345 let sw_lifetime_timeout = pref!(dom_serviceworker_timeout_seconds) as u64;
348 let time_out_port = after(Duration::new(sw_lifetime_timeout, 0));
349
350 let devtools_mpsc_port = devtools_receiver.route_preserving_errors();
351
352 let resource_threads_sender = init.resource_threads.sender();
353 let global = ServiceWorkerGlobalScope::new(
354 init,
355 script_url.clone(),
356 devtools_mpsc_port,
357 runtime,
358 own_sender,
359 receiver,
360 time_out_port,
361 swmanager_sender,
362 scope_url,
363 control_receiver,
364 closing,
365 font_context,
366 cx,
367 );
368
369 let worker_scope = global.upcast::<WorkerGlobalScope>();
370 let global_scope = global.upcast::<GlobalScope>();
371
372 let referrer = referrer_url
373 .map(Referrer::ReferrerUrl)
374 .unwrap_or_else(|| global_scope.get_referrer());
375
376 let request = RequestBuilder::new(None, script_url, referrer)
377 .destination(Destination::ServiceWorker)
378 .credentials_mode(CredentialsMode::Include)
379 .parser_metadata(ParserMetadata::NotParserInserted)
380 .use_url_credentials(true)
381 .pipeline_id(Some(pipeline_id))
382 .referrer_policy(referrer_policy)
383 .insecure_requests_policy(worker_scope.insecure_requests_policy())
384 .policy_container(global_scope.policy_container())
386 .origin(origin);
387
388 let (url, source) = match load_whole_resource(
389 request,
390 &resource_threads_sender,
391 global.upcast(),
392 &ServiceWorkerCspProcessor {},
393 cx,
394 ) {
395 Err(_) => {
396 error!("error loading script {}", serialized_worker_url);
397 worker_scope.clear_js_runtime();
398 return;
399 },
400 Ok((metadata, bytes, _)) => (metadata.final_url, bytes),
401 };
402
403 unsafe {
404 JS_AddInterruptCallback(cx.raw_cx(), Some(interrupt_callback));
406 }
407
408 {
409 let mut realm = enter_auto_realm(cx, worker_scope);
411 let mut realm = realm.current_realm();
412 define_all_exposed_interfaces(&mut realm, global_scope);
413
414 let script = global_scope.create_a_classic_script(
415 String::from_utf8_lossy(&source),
416 url,
417 ScriptFetchOptions::default_classic_script(global_scope),
418 ErrorReporting::Unmuted,
419 Some(IntroductionType::WORKER),
420 1,
421 true,
422 );
423 _ = global_scope.run_a_classic_script(
424 script,
425 RethrowErrors::No,
426 CanGc::from_cx(&mut realm),
427 );
428 let in_realm_proof = (&mut realm).into();
429 global.dispatch_activate(
430 CanGc::from_cx(&mut realm),
431 InRealm::Already(&in_realm_proof),
432 );
433 }
434
435 let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
436 global_scope.mem_profiler_chan().run_with_memory_reporting(
437 || {
438 while !worker_scope.is_closing() && !global.has_timed_out() {
445 run_worker_event_loop(&*global, None, cx);
446 }
447 },
448 reporter_name,
449 global.event_loop_sender(),
450 CommonScriptMsg::CollectReports,
451 );
452
453 worker_scope.clear_js_runtime();
454 })
455 .expect("Thread spawning failed")
456 }
457
458 fn handle_mixed_message(&self, msg: MixedMessage, cx: &mut js::context::JSContext) -> bool {
459 match msg {
460 MixedMessage::Devtools(msg) => match msg {
461 DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => {
462 devtools::handle_evaluate_js(self.upcast(), string, sender, cx)
463 },
464 DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => {
465 devtools::handle_wants_live_notifications(self.upcast(), bool_val)
466 },
467 _ => debug!("got an unusable devtools control message inside the worker!"),
468 },
469 MixedMessage::ServiceWorker(msg) => {
470 self.handle_script_event(msg, cx);
471 },
472 MixedMessage::Control(ServiceWorkerControlMsg::Exit) => {
473 return false;
474 },
475 MixedMessage::Timer => {},
476 }
477 true
478 }
479
480 fn has_timed_out(&self) -> bool {
481 false
483 }
484
485 fn handle_script_event(&self, msg: ServiceWorkerScriptMsg, cx: &mut js::context::JSContext) {
486 use self::ServiceWorkerScriptMsg::*;
487
488 match msg {
489 CommonWorker(WorkerScriptMsg::DOMMessage(msg)) => {
490 let scope = self.upcast::<WorkerGlobalScope>();
491 let target = self.upcast();
492 let _ac = enter_realm(scope);
493 rooted!(&in(cx) let mut message = UndefinedValue());
494 if let Ok(ports) = structuredclone::read(
495 scope.upcast(),
496 *msg.data,
497 message.handle_mut(),
498 CanGc::from_cx(cx),
499 ) {
500 ExtendableMessageEvent::dispatch_jsval(
501 target,
502 scope.upcast(),
503 message.handle(),
504 ports,
505 CanGc::from_cx(cx),
506 );
507 } else {
508 ExtendableMessageEvent::dispatch_error(
509 target,
510 scope.upcast(),
511 CanGc::from_cx(cx),
512 );
513 }
514 },
515 CommonWorker(WorkerScriptMsg::Common(msg)) => {
516 self.upcast::<WorkerGlobalScope>().process_event(msg, cx);
517 },
518 Response(mediator) => {
519 self.upcast::<EventTarget>()
523 .fire_event(atom!("fetch"), CanGc::from_cx(cx));
524 let _ = mediator.response_chan.send(None);
525 },
526 WakeUp => {},
527 }
528 }
529
530 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
531 ScriptEventLoopSender::ServiceWorker(self.own_sender.clone())
532 }
533
534 fn dispatch_activate(&self, can_gc: CanGc, _realm: InRealm) {
535 let event = ExtendableEvent::new(self, atom!("activate"), false, false, can_gc);
536 let event = (*event).upcast::<Event>();
537 self.upcast::<EventTarget>().dispatch_event(event, can_gc);
538 }
539}
540
541#[expect(unsafe_code)]
542unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
543 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
544 let global = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
545 let worker =
546 DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope");
547 assert!(worker.is::<ServiceWorkerGlobalScope>());
548
549 !worker.is_closing()
551}
552
553impl ServiceWorkerGlobalScopeMethods<crate::DomTypeHolder> for ServiceWorkerGlobalScope {
554 event_handler!(message, GetOnmessage, SetOnmessage);
556
557 event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
559}