1use std::collections::VecDeque;
6use std::default::Default;
7use std::rc::Rc;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::jsval::UndefinedValue;
12use js::realm::CurrentRealm;
13use script_bindings::cell::DomRefCell;
14use script_bindings::inheritance::Castable;
15use script_bindings::reflector::reflect_dom_object_with_cx;
16use servo_base::generic_channel::GenericCallback;
17use servo_constellation_traits::{
18 Job, JobError, JobResult, JobResultValue, JobType, ScriptToConstellationMessage,
19 ServiceWorkerAlgorithm, ServiceWorkerAlgorithmResult, ServiceWorkerRegistrationInfo,
20};
21use servo_url::{ImmutableOrigin, ServoUrl};
22
23use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{
24 RegistrationOptions, ServiceWorkerContainerMethods,
25};
26use crate::dom::bindings::error::Error;
27use crate::dom::bindings::refcounted::Trusted;
28use crate::dom::bindings::reflector::DomGlobal;
29use crate::dom::bindings::root::{DomRoot, MutNullableDom};
30use crate::dom::bindings::str::USVString;
31use crate::dom::bindings::structuredclone;
32use crate::dom::eventtarget::EventTarget;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::promise::Promise;
35use crate::dom::serviceworker::ServiceWorker;
36use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
37use crate::dom::types::MessageEvent;
38
39#[dom_struct]
40pub(crate) struct ServiceWorkerContainer {
41 eventtarget: EventTarget,
42 controller: MutNullableDom<ServiceWorker>,
43
44 #[conditional_malloc_size_of]
47 pending_algorithm_results: DomRefCell<VecDeque<Rc<Promise>>>,
48
49 #[no_trace]
51 callback: DomRefCell<Option<GenericCallback<ServiceWorkerAlgorithmResult>>>,
52}
53
54impl ServiceWorkerContainer {
55 fn new_inherited() -> ServiceWorkerContainer {
56 ServiceWorkerContainer {
57 eventtarget: EventTarget::new_inherited(),
58 controller: Default::default(),
59 pending_algorithm_results: Default::default(),
60 callback: Default::default(),
61 }
62 }
63
64 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<ServiceWorkerContainer> {
65 reflect_dom_object_with_cx(
66 Box::new(ServiceWorkerContainer::new_inherited()),
67 global,
68 cx,
69 )
70 }
71
72 fn handle_job_result(&self, cx: &mut JSContext, result: JobResult, promise: Rc<Promise>) {
75 let global = self.global();
76 match result {
77 JobResult::RejectPromise(error) => match error {
84 JobError::TypeError => {
85 promise.reject_error(
86 cx,
87 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
88 );
89 },
90 JobError::SecurityError => {
91 promise.reject_error(cx, Error::Security(None));
92 },
93 },
94 JobResult::ResolvePromise(value) => {
96 match value {
97 JobResultValue::Unregister(success) => {
98 promise.resolve_native(cx, &success);
99 },
100 JobResultValue::Register(value) => {
101 let ServiceWorkerRegistrationInfo {
102 id,
103 installing_worker,
104 waiting_worker,
105 active_worker,
106 storage_key: _,
107 scope_url,
108 script_url,
109 } = value;
110 let registration = global.get_serviceworker_registration(
114 cx,
115 &script_url,
116 &scope_url,
117 id,
118 installing_worker,
119 waiting_worker,
120 active_worker,
121 );
122
123 promise.resolve_native(cx, &*registration);
127 },
128 }
129 },
130 }
131 }
132
133 fn handle_match_registration_result(
136 &self,
137 cx: &mut JSContext,
138 registration_info: Option<ServiceWorkerRegistrationInfo>,
139 promise: Rc<Promise>,
140 ) {
141 let Some(info) = registration_info else {
146 promise.resolve_native(cx, &());
147 return;
148 };
149
150 let registration = self.global().get_serviceworker_registration(
153 cx,
154 &info.script_url,
155 &info.scope_url,
156 info.id,
157 info.installing_worker,
158 info.waiting_worker,
159 info.active_worker,
160 );
161 promise.resolve_native(cx, &*registration);
162 }
163
164 fn handle_algorithm_result(&self, cx: &mut JSContext, result: ServiceWorkerAlgorithmResult) {
165 match result {
166 ServiceWorkerAlgorithmResult::Job(job_result) => {
167 let Some(promise) = self.pending_algorithm_results.borrow_mut().pop_front() else {
168 debug_assert!(false, "No pending algorithm result.");
169 return;
170 };
171 self.handle_job_result(cx, job_result, promise);
172 },
173 ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(registration_info) => {
174 let Some(promise) = self.pending_algorithm_results.borrow_mut().pop_front() else {
175 debug_assert!(false, "No pending algorithm result.");
176 return;
177 };
178 self.handle_match_registration_result(cx, registration_info, promise);
179 },
180 ServiceWorkerAlgorithmResult::MessageFromWorker {
181 message,
182 source,
183 scope_url,
184 script_url,
185 origin,
186 } => {
187 let global = self.global();
193
194 let _source = global.get_serviceworker(cx, &script_url, &scope_url, source);
198
199 rooted!(&in(cx) let mut message_val = UndefinedValue());
203 if let Ok(ports) =
204 structuredclone::read(cx, &global, message, message_val.handle_mut())
205 {
206 MessageEvent::dispatch_jsval(
210 cx,
211 self.upcast(),
212 &global,
213 message_val.handle(),
214 Some(&origin.ascii_serialization()),
215 None,
216 ports,
217 );
218 } else {
219 error!("Failed to deserialize message ports in message from service worker.");
220 }
221 },
222 }
223 }
224
225 fn get_or_setup_callback(
227 &self,
228 promise: Rc<Promise>,
229 ) -> GenericCallback<ServiceWorkerAlgorithmResult> {
230 self.pending_algorithm_results
231 .borrow_mut()
232 .push_back(promise);
233 if let Some(cb) = self.callback.borrow_mut().as_ref() {
234 return cb.clone();
235 }
236
237 let global = self.global();
238 let response_listener = Trusted::new(self);
239
240 let task_source = global
241 .task_manager()
242 .dom_manipulation_task_source()
243 .to_sendable();
244 let callback = GenericCallback::new(move |message| {
245 let response_listener = response_listener.clone();
246 let response = match message {
247 Ok(inner) => inner,
248 Err(err) => {
249 return error!(
250 "Error in Service worker algorithm result handlings {:?}.",
251 err
252 );
253 },
254 };
255 task_source.queue(task!(set_request_result_to_database: move |cx| {
256 let container = response_listener.root();
257 container.handle_algorithm_result(cx, response)
258 }));
259 })
260 .expect("Could not create callback");
261
262 *self.callback.borrow_mut() = Some(callback.clone());
263
264 callback
265 }
266
267 pub(crate) fn create_and_schedule_unregister_job(
270 &self,
271 cx: &mut JSContext,
272 storage_key: ImmutableOrigin,
273 scope: ServoUrl,
274 script_url: ServoUrl,
275 promise: Rc<Promise>,
276 ) {
277 let global = self.global();
278 let result_handler = self.get_or_setup_callback(promise);
279
280 let job = Job::create_job(
284 JobType::Unregister,
285 scope,
286 script_url,
287 result_handler,
288 global.creation_url(),
289 None,
290 storage_key,
291 );
292
293 if global
295 .script_to_constellation_chan()
296 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
297 ServiceWorkerAlgorithm::Unregister(job),
298 ))
299 .is_err()
300 {
301 self.pending_algorithm_results.borrow_mut().pop_back();
303
304 debug_assert!(
305 false,
306 "Failed to send Unregister algorithm message to the constellation."
307 );
308 self.handle_algorithm_result(
309 cx,
310 ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(JobError::TypeError)),
311 );
312 }
313 }
314}
315
316impl ServiceWorkerContainerMethods<crate::DomTypeHolder> for ServiceWorkerContainer {
317 fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
319 None
320 }
321
322 fn Register(
325 &self,
326 realm: &mut CurrentRealm,
327 script_url: USVString,
328 options: &RegistrationOptions,
329 ) -> Rc<Promise> {
330 let global = self.global();
332
333 let promise = Promise::new_in_realm(realm);
335 let USVString(ref script_url) = script_url;
336
337 let api_base_url = global.api_base_url();
339 let script_url = match api_base_url.join(script_url) {
340 Ok(url) => url,
341 Err(_) => {
342 promise.reject_error(realm, Error::Type(c"Invalid script URL".to_owned()));
344 return promise;
345 },
346 };
347
348 let scope = match options.scope {
350 Some(ref scope) => {
351 let USVString(inner_scope) = scope;
352 match api_base_url.join(inner_scope) {
353 Ok(url) => url,
354 Err(_) => {
355 promise.reject_error(realm, Error::Type(c"Invalid scope URL".to_owned()));
356 return promise;
357 },
358 }
359 },
360 None => script_url.join("./").unwrap(),
361 };
362
363 match script_url.scheme() {
367 "https" | "http" => {},
368 _ => {
369 promise.reject_error(
370 realm,
371 Error::Type(c"Only secure origins are allowed".to_owned()),
372 );
373 return promise;
374 },
375 }
376 if script_url.path().to_ascii_lowercase().contains("%2f") ||
378 script_url.path().to_ascii_lowercase().contains("%5c")
379 {
380 promise.reject_error(
381 realm,
382 Error::Type(c"Script URL contains forbidden characters".to_owned()),
383 );
384 return promise;
385 }
386
387 match scope.scheme() {
389 "https" | "http" => {},
390 _ => {
391 promise.reject_error(
392 realm,
393 Error::Type(c"Only secure origins are allowed".to_owned()),
394 );
395 return promise;
396 },
397 }
398 if scope.path().to_ascii_lowercase().contains("%2f") ||
400 scope.path().to_ascii_lowercase().contains("%5c")
401 {
402 promise.reject_error(
403 realm,
404 Error::Type(c"Scope URL contains forbidden characters".to_owned()),
405 );
406 return promise;
407 }
408
409 let result_handler = self.get_or_setup_callback(promise.clone());
410
411 let scope_things =
412 ServiceWorkerRegistration::create_scope_things(&global, script_url.clone());
413
414 let Some(storage_key) = global.obtain_storage_key() else {
418 promise.reject_error(
419 realm,
420 Error::Type(c"Failed to obtain a storage key".to_owned()),
421 );
422 self.pending_algorithm_results.borrow_mut().pop_back();
424 return promise;
425 };
426
427 let job = Job::create_job(
428 JobType::Register,
429 scope,
430 script_url,
431 result_handler,
432 global.creation_url(),
433 Some(scope_things),
434 storage_key,
435 );
436
437 if global
439 .script_to_constellation_chan()
440 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
441 ServiceWorkerAlgorithm::StartRegister(job),
442 ))
443 .is_err()
444 {
445 self.pending_algorithm_results.borrow_mut().pop_back();
447 debug_assert!(
448 false,
449 "Failed to send StartRegister algorithm message to the constellation."
450 );
451 promise.reject_error(
452 realm,
453 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
454 );
455 }
456
457 promise
459 }
460
461 fn GetRegistration(&self, realm: &mut CurrentRealm, client_url: USVString) -> Rc<Promise> {
463 let global = self.global();
465
466 let promise = Promise::new_in_realm(realm);
469
470 let Some(storage_key) = global.obtain_storage_key() else {
472 promise.reject_error(
473 realm,
474 Error::Type(c"Failed to obtain a storage key".to_owned()),
475 );
476 return promise;
477 };
478
479 let mut client_url = match global.api_base_url().join(&client_url.0) {
481 Ok(url) => url,
482 Err(_) => {
483 promise.reject_error(realm, Error::Type(c"Failed to parse clientURL".to_owned()));
485 return promise;
486 },
487 };
488
489 client_url.set_fragment(None);
491
492 if &client_url.origin() != global.origin().immutable() {
494 promise.reject_error(realm, Error::Security(None));
495 return promise;
496 }
497
498 let result_handler = self.get_or_setup_callback(promise.clone());
499
500 if global
504 .script_to_constellation_chan()
505 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
506 ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
507 client_url,
508 storage_key,
509 result_handler,
510 },
511 ))
512 .is_err()
513 {
514 self.pending_algorithm_results.borrow_mut().pop_back();
516 promise.reject_error(
517 realm,
518 Error::Type(c"Failed to send MatchServiceWorkerRegistration algorithm".to_owned()),
519 );
520 }
521
522 promise
524 }
525}