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