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(
87 cx,
88 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
89 );
90 },
91 JobError::SecurityError => {
92 promise.reject_error(cx, Error::Security(None));
93 },
94 },
95 JobResult::ResolvePromise(value) => {
97 match value {
98 JobResultValue::Unregister(success) => {
99 promise.resolve_native(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(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(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(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(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(realm, Error::Type(c"Invalid scope URL".to_owned()));
358 return promise;
359 },
360 }
361 },
362 None => script_url.join("./").unwrap(),
363 };
364
365 match script_url.scheme() {
369 "https" | "http" => {},
370 _ => {
371 promise.reject_error(
372 realm,
373 Error::Type(c"Only secure origins are allowed".to_owned()),
374 );
375 return promise;
376 },
377 }
378 if script_url.path().to_ascii_lowercase().contains("%2f") ||
380 script_url.path().to_ascii_lowercase().contains("%5c")
381 {
382 promise.reject_error(
383 realm,
384 Error::Type(c"Script URL contains forbidden characters".to_owned()),
385 );
386 return promise;
387 }
388
389 match scope.scheme() {
391 "https" | "http" => {},
392 _ => {
393 promise.reject_error(
394 realm,
395 Error::Type(c"Only secure origins are allowed".to_owned()),
396 );
397 return promise;
398 },
399 }
400 if scope.path().to_ascii_lowercase().contains("%2f") ||
402 scope.path().to_ascii_lowercase().contains("%5c")
403 {
404 promise.reject_error(
405 realm,
406 Error::Type(c"Scope URL contains forbidden characters".to_owned()),
407 );
408 return promise;
409 }
410
411 let result_handler = self.get_or_setup_callback(promise.clone());
412
413 let scope_things =
414 ServiceWorkerRegistration::create_scope_things(&global, script_url.clone());
415
416 let Some(storage_key) = global.obtain_storage_key() else {
420 promise.reject_error(
421 realm,
422 Error::Type(c"Failed to obtain a storage key".to_owned()),
423 );
424 self.pending_algorithm_results.borrow_mut().pop_back();
426 return promise;
427 };
428
429 let job = Job::create_job(
430 JobType::Register,
431 scope,
432 script_url,
433 result_handler,
434 global.creation_url(),
435 Some(scope_things),
436 storage_key,
437 );
438
439 if global
441 .script_to_constellation_chan()
442 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
443 ServiceWorkerAlgorithm::StartRegister(job),
444 ))
445 .is_err()
446 {
447 self.pending_algorithm_results.borrow_mut().pop_back();
449 debug_assert!(
450 false,
451 "Failed to send StartRegister algorithm message to the constellation."
452 );
453 promise.reject_error(
454 realm,
455 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
456 );
457 }
458
459 promise
461 }
462
463 fn GetRegistration(&self, realm: &mut CurrentRealm, client_url: USVString) -> Rc<Promise> {
465 let global = self.global();
467
468 let promise = Promise::new_in_realm(realm);
471
472 let Some(storage_key) = global.obtain_storage_key() else {
474 promise.reject_error(
475 realm,
476 Error::Type(c"Failed to obtain a storage key".to_owned()),
477 );
478 return promise;
479 };
480
481 let mut client_url = match global.api_base_url().join(&client_url.0) {
483 Ok(url) => url,
484 Err(_) => {
485 promise.reject_error(realm, Error::Type(c"Failed to parse clientURL".to_owned()));
487 return promise;
488 },
489 };
490
491 client_url.set_fragment(None);
493
494 if &client_url.origin() != global.origin().immutable() {
496 promise.reject_error(realm, Error::Security(None));
497 return promise;
498 }
499
500 let result_handler = self.get_or_setup_callback(promise.clone());
501
502 if global
506 .script_to_constellation_chan()
507 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
508 ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
509 client_url,
510 storage_key,
511 result_handler,
512 },
513 ))
514 .is_err()
515 {
516 self.pending_algorithm_results.borrow_mut().pop_back();
518 promise.reject_error(
519 realm,
520 Error::Type(c"Failed to send MatchServiceWorkerRegistration algorithm".to_owned()),
521 );
522 }
523
524 promise
526 }
527}