1use std::sync::Arc;
6use std::sync::atomic::AtomicBool;
7use std::thread::{self, JoinHandle};
8use std::time::{Duration, Instant};
9
10use base::generic_channel::GenericSender;
11use base::id::PipelineId;
12use constellation_traits::{
13 ScopeThings, ServiceWorkerMsg, WorkerGlobalScopeInit, WorkerScriptLoadOrigin,
14};
15use crossbeam_channel::{Receiver, Sender, after, unbounded};
16use devtools_traits::DevtoolScriptControlMsg;
17use dom_struct::dom_struct;
18use fonts::FontContext;
19use ipc_channel::ipc::IpcReceiver;
20use ipc_channel::router::ROUTER;
21use js::jsapi::{JS_AddInterruptCallback, JSContext};
22use js::jsval::UndefinedValue;
23use net_traits::request::{
24 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
25};
26use net_traits::{CustomResponseMediator, IpcSend};
27use servo_config::pref;
28use servo_rand::random;
29use servo_url::ServoUrl;
30use style::thread_state::{self, ThreadState};
31
32use crate::devtools;
33use crate::dom::abstractworker::WorkerScriptMsg;
34use crate::dom::abstractworkerglobalscope::{WorkerEventLoopMethods, run_worker_event_loop};
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::csp::Violation;
45use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
46use crate::dom::event::Event;
47use crate::dom::eventtarget::EventTarget;
48use crate::dom::extendableevent::ExtendableEvent;
49use crate::dom::extendablemessageevent::ExtendableMessageEvent;
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_realm};
58use crate::script_runtime::{CanGc, JSContext as SafeJSContext, Runtime, ThreadSafeJSContext};
59use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
60use crate::task_source::TaskSourceName;
61
62pub(crate) enum ServiceWorkerScriptMsg {
64 CommonWorker(WorkerScriptMsg),
66 Response(CustomResponseMediator),
68 WakeUp,
70}
71
72impl QueuedTaskConversion for ServiceWorkerScriptMsg {
73 fn task_source_name(&self) -> Option<&TaskSourceName> {
74 let script_msg = match self {
75 ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
76 _ => return None,
77 };
78 match script_msg {
79 CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
80 Some(task_source)
81 },
82 _ => None,
83 }
84 }
85
86 fn pipeline_id(&self) -> Option<PipelineId> {
87 None
90 }
91
92 fn into_queued_task(self) -> Option<QueuedTask> {
93 let script_msg = match self {
94 ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg)) => script_msg,
95 _ => return None,
96 };
97 let (category, boxed, pipeline_id, task_source) = match script_msg {
98 CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
99 (category, boxed, pipeline_id, task_source)
100 },
101 _ => return None,
102 };
103 Some((None, category, boxed, pipeline_id, task_source))
104 }
105
106 fn from_queued_task(queued_task: QueuedTask) -> Self {
107 let (_worker, category, boxed, pipeline_id, task_source) = queued_task;
108 let script_msg = CommonScriptMsg::Task(category, boxed, pipeline_id, task_source);
109 ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(script_msg))
110 }
111
112 fn inactive_msg() -> Self {
113 panic!("Workers should never receive messages marked as inactive");
115 }
116
117 fn wake_up_msg() -> Self {
118 ServiceWorkerScriptMsg::WakeUp
119 }
120
121 fn is_wake_up(&self) -> bool {
122 matches!(self, ServiceWorkerScriptMsg::WakeUp)
123 }
124}
125
126pub(crate) enum ServiceWorkerControlMsg {
128 Exit,
130}
131
132pub(crate) enum MixedMessage {
133 ServiceWorker(ServiceWorkerScriptMsg),
134 Devtools(DevtoolScriptControlMsg),
135 Control(ServiceWorkerControlMsg),
136 Timer,
137}
138
139struct ServiceWorkerCspProcessor {}
140
141impl CspViolationsProcessor for ServiceWorkerCspProcessor {
142 fn process_csp_violations(&self, _violations: Vec<Violation>) {}
143}
144
145#[dom_struct]
146pub(crate) struct ServiceWorkerGlobalScope {
147 workerglobalscope: WorkerGlobalScope,
148
149 #[ignore_malloc_size_of = "Defined in std"]
150 #[no_trace]
151 task_queue: TaskQueue<ServiceWorkerScriptMsg>,
152
153 own_sender: Sender<ServiceWorkerScriptMsg>,
154
155 #[ignore_malloc_size_of = "Defined in std"]
160 #[no_trace]
161 time_out_port: Receiver<Instant>,
162
163 #[ignore_malloc_size_of = "Defined in std"]
164 #[no_trace]
165 swmanager_sender: GenericSender<ServiceWorkerMsg>,
166
167 #[no_trace]
168 scope_url: ServoUrl,
169
170 #[ignore_malloc_size_of = "Channels are hard"]
173 #[no_trace]
174 control_receiver: Receiver<ServiceWorkerControlMsg>,
175}
176
177impl WorkerEventLoopMethods for ServiceWorkerGlobalScope {
178 type WorkerMsg = ServiceWorkerScriptMsg;
179 type ControlMsg = ServiceWorkerControlMsg;
180 type Event = MixedMessage;
181
182 fn task_queue(&self) -> &TaskQueue<ServiceWorkerScriptMsg> {
183 &self.task_queue
184 }
185
186 fn handle_event(&self, event: MixedMessage, can_gc: CanGc) -> bool {
187 self.handle_mixed_message(event, can_gc)
188 }
189
190 fn handle_worker_post_event(
191 &self,
192 _worker: &TrustedWorkerAddress,
193 ) -> Option<AutoWorkerReset<'_>> {
194 None
195 }
196
197 fn from_control_msg(msg: ServiceWorkerControlMsg) -> MixedMessage {
198 MixedMessage::Control(msg)
199 }
200
201 fn from_worker_msg(msg: ServiceWorkerScriptMsg) -> MixedMessage {
202 MixedMessage::ServiceWorker(msg)
203 }
204
205 fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> MixedMessage {
206 MixedMessage::Devtools(msg)
207 }
208
209 fn from_timer_msg() -> MixedMessage {
210 MixedMessage::Timer
211 }
212
213 fn control_receiver(&self) -> &Receiver<ServiceWorkerControlMsg> {
214 &self.control_receiver
215 }
216}
217
218impl ServiceWorkerGlobalScope {
219 #[allow(clippy::too_many_arguments)]
220 fn new_inherited(
221 init: WorkerGlobalScopeInit,
222 worker_url: ServoUrl,
223 from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
224 runtime: Runtime,
225 own_sender: Sender<ServiceWorkerScriptMsg>,
226 receiver: Receiver<ServiceWorkerScriptMsg>,
227 time_out_port: Receiver<Instant>,
228 swmanager_sender: GenericSender<ServiceWorkerMsg>,
229 scope_url: ServoUrl,
230 control_receiver: Receiver<ServiceWorkerControlMsg>,
231 closing: Arc<AtomicBool>,
232 font_context: Arc<FontContext>,
233 ) -> ServiceWorkerGlobalScope {
234 ServiceWorkerGlobalScope {
235 workerglobalscope: WorkerGlobalScope::new_inherited(
236 init,
237 DOMString::new(),
238 WorkerType::Classic, worker_url,
240 runtime,
241 from_devtools_receiver,
242 closing,
243 #[cfg(feature = "webgpu")]
244 Arc::new(IdentityHub::default()),
245 InsecureRequestsPolicy::DoNotUpgrade,
247 Some(font_context),
248 ),
249 task_queue: TaskQueue::new(receiver, own_sender.clone()),
250 own_sender,
251 time_out_port,
252 swmanager_sender,
253 scope_url,
254 control_receiver,
255 }
256 }
257
258 #[allow(unsafe_code, clippy::too_many_arguments)]
259 pub(crate) fn new(
260 init: WorkerGlobalScopeInit,
261 worker_url: ServoUrl,
262 from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
263 runtime: Runtime,
264 own_sender: Sender<ServiceWorkerScriptMsg>,
265 receiver: Receiver<ServiceWorkerScriptMsg>,
266 time_out_port: Receiver<Instant>,
267 swmanager_sender: GenericSender<ServiceWorkerMsg>,
268 scope_url: ServoUrl,
269 control_receiver: Receiver<ServiceWorkerControlMsg>,
270 closing: Arc<AtomicBool>,
271 font_context: Arc<FontContext>,
272 ) -> DomRoot<ServiceWorkerGlobalScope> {
273 let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
274 init,
275 worker_url,
276 from_devtools_receiver,
277 runtime,
278 own_sender,
279 receiver,
280 time_out_port,
281 swmanager_sender,
282 scope_url,
283 control_receiver,
284 closing,
285 font_context,
286 ));
287 ServiceWorkerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(GlobalScope::get_cx(), scope)
288 }
289
290 #[allow(unsafe_code, clippy::too_many_arguments)]
292 pub(crate) fn run_serviceworker_scope(
293 scope_things: ScopeThings,
294 own_sender: Sender<ServiceWorkerScriptMsg>,
295 receiver: Receiver<ServiceWorkerScriptMsg>,
296 devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
297 swmanager_sender: GenericSender<ServiceWorkerMsg>,
298 scope_url: ServoUrl,
299 control_receiver: Receiver<ServiceWorkerControlMsg>,
300 context_sender: Sender<ThreadSafeJSContext>,
301 closing: Arc<AtomicBool>,
302 font_context: Arc<FontContext>,
303 ) -> JoinHandle<()> {
304 let ScopeThings {
305 script_url,
306 init,
307 worker_load_origin,
308 ..
309 } = scope_things;
310
311 let serialized_worker_url = script_url.to_string();
312 let origin = scope_url.origin();
313 thread::Builder::new()
314 .name(format!("SW:{}", script_url.debug_compact()))
315 .spawn(move || {
316 thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
317 let runtime = Runtime::new(None);
318 let context_for_interrupt = runtime.thread_safe_js_context();
319 let _ = context_sender.send(context_for_interrupt);
320
321 let WorkerScriptLoadOrigin {
322 referrer_url,
323 referrer_policy,
324 pipeline_id,
325 } = worker_load_origin;
326
327 let sw_lifetime_timeout = pref!(dom_serviceworker_timeout_seconds) as u64;
330 let time_out_port = after(Duration::new(sw_lifetime_timeout, 0));
331
332 let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded();
333 ROUTER
334 .route_ipc_receiver_to_crossbeam_sender(devtools_receiver, devtools_mpsc_chan);
335
336 let resource_threads_sender = init.resource_threads.sender();
337 let global = ServiceWorkerGlobalScope::new(
338 init,
339 script_url.clone(),
340 devtools_mpsc_port,
341 runtime,
342 own_sender,
343 receiver,
344 time_out_port,
345 swmanager_sender,
346 scope_url,
347 control_receiver,
348 closing,
349 font_context,
350 );
351
352 let scope = global.upcast::<WorkerGlobalScope>();
353
354 let referrer = referrer_url
355 .map(Referrer::ReferrerUrl)
356 .unwrap_or_else(|| global.upcast::<GlobalScope>().get_referrer());
357
358 let request = RequestBuilder::new(None, script_url, referrer)
359 .destination(Destination::ServiceWorker)
360 .credentials_mode(CredentialsMode::Include)
361 .parser_metadata(ParserMetadata::NotParserInserted)
362 .use_url_credentials(true)
363 .pipeline_id(Some(pipeline_id))
364 .referrer_policy(referrer_policy)
365 .insecure_requests_policy(scope.insecure_requests_policy())
366 .origin(origin);
367
368 let (_url, source) = match load_whole_resource(
369 request,
370 &resource_threads_sender,
371 global.upcast(),
372 &ServiceWorkerCspProcessor {},
373 CanGc::note(),
374 ) {
375 Err(_) => {
376 println!("error loading script {}", serialized_worker_url);
377 scope.clear_js_runtime();
378 return;
379 },
380 Ok((metadata, bytes)) => {
381 (metadata.final_url, String::from_utf8(bytes).unwrap())
382 },
383 };
384
385 unsafe {
386 JS_AddInterruptCallback(*scope.get_cx(), Some(interrupt_callback));
388 }
389
390 {
391 let realm = enter_realm(scope);
393 define_all_exposed_interfaces(
394 scope.upcast(),
395 InRealm::entered(&realm),
396 CanGc::note(),
397 );
398 scope.execute_script(DOMString::from(source), CanGc::note());
399 global.dispatch_activate(CanGc::note(), InRealm::entered(&realm));
400 }
401
402 let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
403 scope
404 .upcast::<GlobalScope>()
405 .mem_profiler_chan()
406 .run_with_memory_reporting(
407 || {
408 while !scope.is_closing() && !global.has_timed_out() {
415 run_worker_event_loop(&*global, None, CanGc::note());
416 }
417 },
418 reporter_name,
419 global.event_loop_sender(),
420 CommonScriptMsg::CollectReports,
421 );
422
423 scope.clear_js_runtime();
424 })
425 .expect("Thread spawning failed")
426 }
427
428 fn handle_mixed_message(&self, msg: MixedMessage, can_gc: CanGc) -> bool {
429 match msg {
430 MixedMessage::Devtools(msg) => match msg {
431 DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) => {
432 devtools::handle_evaluate_js(self.upcast(), string, sender, can_gc)
433 },
434 DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) => {
435 devtools::handle_wants_live_notifications(self.upcast(), bool_val)
436 },
437 _ => debug!("got an unusable devtools control message inside the worker!"),
438 },
439 MixedMessage::ServiceWorker(msg) => {
440 self.handle_script_event(msg, can_gc);
441 },
442 MixedMessage::Control(ServiceWorkerControlMsg::Exit) => {
443 return false;
444 },
445 MixedMessage::Timer => {},
446 }
447 true
448 }
449
450 fn has_timed_out(&self) -> bool {
451 false
453 }
454
455 fn handle_script_event(&self, msg: ServiceWorkerScriptMsg, can_gc: CanGc) {
456 use self::ServiceWorkerScriptMsg::*;
457
458 match msg {
459 CommonWorker(WorkerScriptMsg::DOMMessage { data, .. }) => {
460 let scope = self.upcast::<WorkerGlobalScope>();
461 let target = self.upcast();
462 let _ac = enter_realm(scope);
463 rooted!(in(*scope.get_cx()) let mut message = UndefinedValue());
464 if let Ok(ports) =
465 structuredclone::read(scope.upcast(), *data, message.handle_mut())
466 {
467 ExtendableMessageEvent::dispatch_jsval(
468 target,
469 scope.upcast(),
470 message.handle(),
471 ports,
472 can_gc,
473 );
474 } else {
475 ExtendableMessageEvent::dispatch_error(target, scope.upcast(), can_gc);
476 }
477 },
478 CommonWorker(WorkerScriptMsg::Common(msg)) => {
479 self.upcast::<WorkerGlobalScope>().process_event(msg);
480 },
481 Response(mediator) => {
482 self.upcast::<EventTarget>()
486 .fire_event(atom!("fetch"), can_gc);
487 let _ = mediator.response_chan.send(None);
488 },
489 WakeUp => {},
490 }
491 }
492
493 pub(crate) fn event_loop_sender(&self) -> ScriptEventLoopSender {
494 ScriptEventLoopSender::ServiceWorker(self.own_sender.clone())
495 }
496
497 fn dispatch_activate(&self, can_gc: CanGc, _realm: InRealm) {
498 let event = ExtendableEvent::new(self, atom!("activate"), false, false, can_gc);
499 let event = (*event).upcast::<Event>();
500 self.upcast::<EventTarget>().dispatch_event(event, can_gc);
501 }
502}
503
504#[allow(unsafe_code)]
505unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
506 let in_realm_proof = AlreadyInRealm::assert_for_cx(SafeJSContext::from_ptr(cx));
507 let global = GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof));
508 let worker =
509 DomRoot::downcast::<WorkerGlobalScope>(global).expect("global is not a worker scope");
510 assert!(worker.is::<ServiceWorkerGlobalScope>());
511
512 !worker.is_closing()
514}
515
516impl ServiceWorkerGlobalScopeMethods<crate::DomTypeHolder> for ServiceWorkerGlobalScope {
517 event_handler!(message, GetOnmessage, SetOnmessage);
519
520 event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
522}