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;
38use crate::script_runtime::CanGc;
39
40#[dom_struct]
41pub(crate) struct ServiceWorkerContainer {
42 eventtarget: EventTarget,
43 controller: MutNullableDom<ServiceWorker>,
44
45 #[conditional_malloc_size_of]
48 pending_algorithm_results: DomRefCell<VecDeque<Rc<Promise>>>,
49
50 #[no_trace]
52 callback: DomRefCell<Option<GenericCallback<ServiceWorkerAlgorithmResult>>>,
53}
54
55impl ServiceWorkerContainer {
56 fn new_inherited() -> ServiceWorkerContainer {
57 ServiceWorkerContainer {
58 eventtarget: EventTarget::new_inherited(),
59 controller: Default::default(),
60 pending_algorithm_results: Default::default(),
61 callback: Default::default(),
62 }
63 }
64
65 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<ServiceWorkerContainer> {
66 reflect_dom_object_with_cx(
67 Box::new(ServiceWorkerContainer::new_inherited()),
68 global,
69 cx,
70 )
71 }
72
73 fn handle_job_result(&self, cx: &mut JSContext, result: JobResult, promise: Rc<Promise>) {
76 let global = self.global();
77 match result {
78 JobResult::RejectPromise(error) => match error {
85 JobError::TypeError => {
86 promise.reject_error_with_cx(
87 cx,
88 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
89 );
90 },
91 JobError::SecurityError => {
92 promise.reject_error_with_cx(cx, Error::Security(None));
93 },
94 },
95 JobResult::ResolvePromise(value) => {
97 match value {
98 JobResultValue::Unregister(success) => {
99 promise.resolve_native_with_cx(cx, &success);
100 },
101 JobResultValue::Register(value) => {
102 let ServiceWorkerRegistrationInfo {
103 id,
104 installing_worker,
105 waiting_worker,
106 active_worker,
107 storage_key: _,
108 scope_url,
109 script_url,
110 } = value;
111 let registration = global.get_serviceworker_registration(
115 &script_url,
116 &scope_url,
117 id,
118 installing_worker,
119 waiting_worker,
120 active_worker,
121 CanGc::from_cx(cx),
122 );
123
124 promise.resolve_native_with_cx(cx, &*registration);
128 },
129 }
130 },
131 }
132 }
133
134 fn handle_match_registration_result(
137 &self,
138 cx: &mut JSContext,
139 registration_info: Option<ServiceWorkerRegistrationInfo>,
140 promise: Rc<Promise>,
141 ) {
142 let Some(info) = registration_info else {
147 promise.resolve_native_with_cx(cx, &());
148 return;
149 };
150
151 let registration = self.global().get_serviceworker_registration(
154 &info.script_url,
155 &info.scope_url,
156 info.id,
157 info.installing_worker,
158 info.waiting_worker,
159 info.active_worker,
160 CanGc::from_cx(cx),
161 );
162 promise.resolve_native_with_cx(cx, &*registration);
163 }
164
165 fn handle_algorithm_result(&self, cx: &mut JSContext, result: ServiceWorkerAlgorithmResult) {
166 match result {
167 ServiceWorkerAlgorithmResult::Job(job_result) => {
168 let Some(promise) = self.pending_algorithm_results.borrow_mut().pop_front() else {
169 debug_assert!(false, "No pending algorithm result.");
170 return;
171 };
172 self.handle_job_result(cx, job_result, promise);
173 },
174 ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(registration_info) => {
175 let Some(promise) = self.pending_algorithm_results.borrow_mut().pop_front() else {
176 debug_assert!(false, "No pending algorithm result.");
177 return;
178 };
179 self.handle_match_registration_result(cx, registration_info, promise);
180 },
181 ServiceWorkerAlgorithmResult::MessageFromWorker {
182 message,
183 source,
184 scope_url,
185 script_url,
186 origin,
187 } => {
188 let global = self.global();
194
195 let _source =
199 global.get_serviceworker(&script_url, &scope_url, source, CanGc::from_cx(cx));
200
201 rooted!(&in(cx) let mut message_val = UndefinedValue());
205 if let Ok(ports) =
206 structuredclone::read(cx, &global, message, message_val.handle_mut())
207 {
208 MessageEvent::dispatch_jsval(
212 cx,
213 self.upcast(),
214 &global,
215 message_val.handle(),
216 Some(&origin.ascii_serialization()),
217 None,
218 ports,
219 );
220 } else {
221 error!("Failed to deserialize message ports in message from service worker.");
222 }
223 },
224 }
225 }
226
227 fn get_or_setup_callback(
229 &self,
230 promise: Rc<Promise>,
231 ) -> GenericCallback<ServiceWorkerAlgorithmResult> {
232 self.pending_algorithm_results
233 .borrow_mut()
234 .push_back(promise);
235 if let Some(cb) = self.callback.borrow_mut().as_ref() {
236 return cb.clone();
237 }
238
239 let global = self.global();
240 let response_listener = Trusted::new(self);
241
242 let task_source = global
243 .task_manager()
244 .dom_manipulation_task_source()
245 .to_sendable();
246 let callback = GenericCallback::new(move |message| {
247 let response_listener = response_listener.clone();
248 let response = match message {
249 Ok(inner) => inner,
250 Err(err) => {
251 return error!(
252 "Error in Service worker algorithm result handlings {:?}.",
253 err
254 );
255 },
256 };
257 task_source.queue(task!(set_request_result_to_database: move |cx| {
258 let container = response_listener.root();
259 container.handle_algorithm_result(cx, response)
260 }));
261 })
262 .expect("Could not create callback");
263
264 *self.callback.borrow_mut() = Some(callback.clone());
265
266 callback
267 }
268
269 pub(crate) fn create_and_schedule_unregister_job(
272 &self,
273 cx: &mut JSContext,
274 storage_key: ImmutableOrigin,
275 scope: ServoUrl,
276 script_url: ServoUrl,
277 promise: Rc<Promise>,
278 ) {
279 let global = self.global();
280 let result_handler = self.get_or_setup_callback(promise);
281
282 let job = Job::create_job(
286 JobType::Unregister,
287 scope,
288 script_url,
289 result_handler,
290 global.creation_url(),
291 None,
292 storage_key,
293 );
294
295 if global
297 .script_to_constellation_chan()
298 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
299 ServiceWorkerAlgorithm::Unregister(job),
300 ))
301 .is_err()
302 {
303 self.pending_algorithm_results.borrow_mut().pop_back();
305
306 debug_assert!(
307 false,
308 "Failed to send Unregister algorithm message to the constellation."
309 );
310 self.handle_algorithm_result(
311 cx,
312 ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(JobError::TypeError)),
313 );
314 }
315 }
316}
317
318impl ServiceWorkerContainerMethods<crate::DomTypeHolder> for ServiceWorkerContainer {
319 fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
321 None
322 }
323
324 fn Register(
327 &self,
328 realm: &mut CurrentRealm,
329 script_url: USVString,
330 options: &RegistrationOptions,
331 ) -> Rc<Promise> {
332 let global = self.global();
334
335 let promise = Promise::new_in_realm(realm);
337 let USVString(ref script_url) = script_url;
338
339 let api_base_url = global.api_base_url();
341 let script_url = match api_base_url.join(script_url) {
342 Ok(url) => url,
343 Err(_) => {
344 promise.reject_error_with_cx(realm, Error::Type(c"Invalid script URL".to_owned()));
346 return promise;
347 },
348 };
349
350 let scope = match options.scope {
352 Some(ref scope) => {
353 let USVString(inner_scope) = scope;
354 match api_base_url.join(inner_scope) {
355 Ok(url) => url,
356 Err(_) => {
357 promise.reject_error_with_cx(
358 realm,
359 Error::Type(c"Invalid scope URL".to_owned()),
360 );
361 return promise;
362 },
363 }
364 },
365 None => script_url.join("./").unwrap(),
366 };
367
368 match script_url.scheme() {
372 "https" | "http" => {},
373 _ => {
374 promise.reject_error_with_cx(
375 realm,
376 Error::Type(c"Only secure origins are allowed".to_owned()),
377 );
378 return promise;
379 },
380 }
381 if script_url.path().to_ascii_lowercase().contains("%2f") ||
383 script_url.path().to_ascii_lowercase().contains("%5c")
384 {
385 promise.reject_error_with_cx(
386 realm,
387 Error::Type(c"Script URL contains forbidden characters".to_owned()),
388 );
389 return promise;
390 }
391
392 match scope.scheme() {
394 "https" | "http" => {},
395 _ => {
396 promise.reject_error_with_cx(
397 realm,
398 Error::Type(c"Only secure origins are allowed".to_owned()),
399 );
400 return promise;
401 },
402 }
403 if scope.path().to_ascii_lowercase().contains("%2f") ||
405 scope.path().to_ascii_lowercase().contains("%5c")
406 {
407 promise.reject_error_with_cx(
408 realm,
409 Error::Type(c"Scope URL contains forbidden characters".to_owned()),
410 );
411 return promise;
412 }
413
414 let result_handler = self.get_or_setup_callback(promise.clone());
415
416 let scope_things =
417 ServiceWorkerRegistration::create_scope_things(&global, script_url.clone());
418
419 let Some(storage_key) = global.obtain_storage_key() else {
423 promise.reject_error_with_cx(
424 realm,
425 Error::Type(c"Failed to obtain a storage key".to_owned()),
426 );
427 self.pending_algorithm_results.borrow_mut().pop_back();
429 return promise;
430 };
431
432 let job = Job::create_job(
433 JobType::Register,
434 scope,
435 script_url,
436 result_handler,
437 global.creation_url(),
438 Some(scope_things),
439 storage_key,
440 );
441
442 if global
444 .script_to_constellation_chan()
445 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
446 ServiceWorkerAlgorithm::StartRegister(job),
447 ))
448 .is_err()
449 {
450 self.pending_algorithm_results.borrow_mut().pop_back();
452 debug_assert!(
453 false,
454 "Failed to send StartRegister algorithm message to the constellation."
455 );
456 promise.reject_error_with_cx(
457 realm,
458 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
459 );
460 }
461
462 promise
464 }
465
466 fn GetRegistration(&self, realm: &mut CurrentRealm, client_url: USVString) -> Rc<Promise> {
468 let global = self.global();
470
471 let promise = Promise::new_in_realm(realm);
474
475 let Some(storage_key) = global.obtain_storage_key() else {
477 promise.reject_error_with_cx(
478 realm,
479 Error::Type(c"Failed to obtain a storage key".to_owned()),
480 );
481 return promise;
482 };
483
484 let mut client_url = match global.api_base_url().join(&client_url.0) {
486 Ok(url) => url,
487 Err(_) => {
488 promise.reject_error_with_cx(
490 realm,
491 Error::Type(c"Failed to parse clientURL".to_owned()),
492 );
493 return promise;
494 },
495 };
496
497 client_url.set_fragment(None);
499
500 if &client_url.origin() != global.origin().immutable() {
502 promise.reject_error_with_cx(realm, Error::Security(None));
503 return promise;
504 }
505
506 let result_handler = self.get_or_setup_callback(promise.clone());
507
508 if global
512 .script_to_constellation_chan()
513 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
514 ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
515 client_url,
516 storage_key,
517 result_handler,
518 },
519 ))
520 .is_err()
521 {
522 self.pending_algorithm_results.borrow_mut().pop_back();
524 promise.reject_error_with_cx(
525 realm,
526 Error::Type(c"Failed to send MatchServiceWorkerRegistration algorithm".to_owned()),
527 );
528 }
529
530 promise
532 }
533}