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]
41pub(crate) struct ServiceWorkerContainer {
42 eventtarget: EventTarget,
43 controller: MutNullableDom<ServiceWorker>,
44
45 pending_algorithm_results: DomRefCell<VecDeque<TracedPromise>>,
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(
66 cx,
67 Box::new(ServiceWorkerContainer::new_inherited()),
68 global,
69 )
70 }
71
72 fn handle_job_result(&self, cx: &mut JSContext, result: JobResult, promise: &RootedPromise) {
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: &RootedPromise,
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 promise = self
168 .pending_algorithm_results
169 .borrow_mut()
170 .pop_front()
171 .map(|promise| promise.root(cx));
172 let Some(promise) = promise else {
173 debug_assert!(false, "No pending algorithm result.");
174 return;
175 };
176 self.handle_job_result(cx, job_result, &promise);
177 },
178 ServiceWorkerAlgorithmResult::MatchServiceWorkerRegistration(registration_info) => {
179 let promise = self
180 .pending_algorithm_results
181 .borrow_mut()
182 .pop_front()
183 .map(|promise| promise.root(cx));
184 let Some(promise) = promise else {
185 debug_assert!(false, "No pending algorithm result.");
186 return;
187 };
188 self.handle_match_registration_result(cx, registration_info, &promise);
189 },
190 ServiceWorkerAlgorithmResult::MessageFromWorker {
191 message,
192 source,
193 scope_url,
194 script_url,
195 origin,
196 } => {
197 let global = self.global();
203
204 let _source = global.get_serviceworker(cx, &script_url, &scope_url, source);
208
209 rooted!(&in(cx) let mut message_val = UndefinedValue());
213 if let Ok(ports) =
214 structuredclone::read(cx, &global, message, message_val.handle_mut())
215 {
216 MessageEvent::dispatch_jsval(
220 cx,
221 self.upcast(),
222 &global,
223 message_val.handle(),
224 Some(origin.ascii_serialization().as_ref()),
225 None,
226 ports,
227 );
228 } else {
229 error!("Failed to deserialize message ports in message from service worker.");
230 }
231 },
232 }
233 }
234
235 fn get_or_setup_callback(
237 &self,
238 promise: &RootedPromise,
239 ) -> GenericCallback<ServiceWorkerAlgorithmResult> {
240 self.pending_algorithm_results
241 .borrow_mut()
242 .push_back(promise.to_traced());
243 if let Some(cb) = self.callback.borrow_mut().as_ref() {
244 return cb.clone();
245 }
246
247 let global = self.global();
248 let response_listener = Trusted::new(self);
249
250 let task_source = global
251 .task_manager()
252 .dom_manipulation_task_source()
253 .to_sendable();
254 let callback = GenericCallback::new(move |message| {
255 let response_listener = response_listener.clone();
256 let response = match message {
257 Ok(inner) => inner,
258 Err(err) => {
259 return error!(
260 "Error in Service worker algorithm result handlings {:?}.",
261 err
262 );
263 },
264 };
265 task_source.queue(task!(set_request_result_to_database: move |cx| {
266 let container = response_listener.root();
267 container.handle_algorithm_result(cx, response)
268 }));
269 })
270 .expect("Could not create callback");
271
272 *self.callback.borrow_mut() = Some(callback.clone());
273
274 callback
275 }
276
277 pub(crate) fn create_and_schedule_unregister_job(
280 &self,
281 cx: &mut JSContext,
282 storage_key: ImmutableOrigin,
283 scope: ServoUrl,
284 script_url: ServoUrl,
285 promise: &RootedPromise,
286 ) {
287 let global = self.global();
288 let result_handler = self.get_or_setup_callback(promise);
289
290 let job = Job::create_job(
294 JobType::Unregister,
295 scope,
296 script_url,
297 result_handler,
298 global.creation_url(),
299 None,
300 storage_key,
301 );
302
303 if global
305 .script_to_constellation_chan()
306 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
307 ServiceWorkerAlgorithm::Unregister(job),
308 ))
309 .is_err()
310 {
311 self.pending_algorithm_results.borrow_mut().pop_back();
313
314 debug_assert!(
315 false,
316 "Failed to send Unregister algorithm message to the constellation."
317 );
318 self.handle_algorithm_result(
319 cx,
320 ServiceWorkerAlgorithmResult::Job(JobResult::RejectPromise(JobError::TypeError)),
321 );
322 }
323 }
324}
325
326impl ServiceWorkerContainerMethods<crate::DomTypeHolder> for ServiceWorkerContainer {
327 fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
329 None
330 }
331
332 fn Register(
335 &self,
336 realm: &mut CurrentRealm,
337 script_url: USVString,
338 options: &RegistrationOptions,
339 ) -> RootedPromise {
340 let global = self.global();
342
343 let promise = Promise::new_in_realm(realm);
345 let USVString(ref script_url) = script_url;
346
347 let api_base_url = global.api_base_url();
349 let script_url = match api_base_url.join(script_url) {
350 Ok(url) => url,
351 Err(_) => {
352 promise.reject_error(realm, Error::Type(c"Invalid script URL".to_owned()));
354 return promise;
355 },
356 };
357
358 let scope = match options.scope {
360 Some(ref scope) => {
361 let USVString(inner_scope) = scope;
362 match api_base_url.join(inner_scope) {
363 Ok(url) => url,
364 Err(_) => {
365 promise.reject_error(realm, Error::Type(c"Invalid scope URL".to_owned()));
366 return promise;
367 },
368 }
369 },
370 None => script_url.join("./").unwrap(),
371 };
372
373 match script_url.scheme() {
377 "https" | "http" => {},
378 _ => {
379 promise.reject_error(
380 realm,
381 Error::Type(c"Only secure origins are allowed".to_owned()),
382 );
383 return promise;
384 },
385 }
386 if script_url.path().to_ascii_lowercase().contains("%2f") ||
388 script_url.path().to_ascii_lowercase().contains("%5c")
389 {
390 promise.reject_error(
391 realm,
392 Error::Type(c"Script URL contains forbidden characters".to_owned()),
393 );
394 return promise;
395 }
396
397 match scope.scheme() {
399 "https" | "http" => {},
400 _ => {
401 promise.reject_error(
402 realm,
403 Error::Type(c"Only secure origins are allowed".to_owned()),
404 );
405 return promise;
406 },
407 }
408 if scope.path().to_ascii_lowercase().contains("%2f") ||
410 scope.path().to_ascii_lowercase().contains("%5c")
411 {
412 promise.reject_error(
413 realm,
414 Error::Type(c"Scope URL contains forbidden characters".to_owned()),
415 );
416 return promise;
417 }
418
419 let result_handler = self.get_or_setup_callback(&promise);
420
421 let scope_things =
422 ServiceWorkerRegistration::create_scope_things(&global, script_url.clone());
423
424 let Some(storage_key) = global.obtain_storage_key() else {
428 promise.reject_error(
429 realm,
430 Error::Type(c"Failed to obtain a storage key".to_owned()),
431 );
432 self.pending_algorithm_results.borrow_mut().pop_back();
434 return promise;
435 };
436
437 let job = Job::create_job(
438 JobType::Register,
439 scope,
440 script_url,
441 result_handler,
442 global.creation_url(),
443 Some(scope_things),
444 storage_key,
445 );
446
447 if global
449 .script_to_constellation_chan()
450 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
451 ServiceWorkerAlgorithm::StartRegister(job),
452 ))
453 .is_err()
454 {
455 self.pending_algorithm_results.borrow_mut().pop_back();
457 debug_assert!(
458 false,
459 "Failed to send StartRegister algorithm message to the constellation."
460 );
461 promise.reject_error(
462 realm,
463 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
464 );
465 }
466
467 promise
469 }
470
471 fn GetRegistration(&self, realm: &mut CurrentRealm, client_url: USVString) -> RootedPromise {
473 let global = self.global();
475
476 let promise = Promise::new_in_realm(realm);
479
480 let Some(storage_key) = global.obtain_storage_key() else {
482 promise.reject_error(
483 realm,
484 Error::Type(c"Failed to obtain a storage key".to_owned()),
485 );
486 return promise;
487 };
488
489 let mut client_url = match global.api_base_url().join(&client_url.0) {
491 Ok(url) => url,
492 Err(_) => {
493 promise.reject_error(realm, Error::Type(c"Failed to parse clientURL".to_owned()));
495 return promise;
496 },
497 };
498
499 client_url.set_fragment(None);
501
502 if &client_url.origin() != global.origin().immutable() {
504 promise.reject_error(realm, Error::Security(None));
505 return promise;
506 }
507
508 let result_handler = self.get_or_setup_callback(&promise);
509
510 if global
514 .script_to_constellation_chan()
515 .send(ScriptToConstellationMessage::ServiceWorkerAlgorithm(
516 ServiceWorkerAlgorithm::MatchServiceWorkerRegistration {
517 client_url,
518 storage_key,
519 result_handler,
520 },
521 ))
522 .is_err()
523 {
524 self.pending_algorithm_results.borrow_mut().pop_back();
526 promise.reject_error(
527 realm,
528 Error::Type(c"Failed to send MatchServiceWorkerRegistration algorithm".to_owned()),
529 );
530 }
531
532 promise
534 }
535
536 event_handler!(message, GetOnmessage, SetOnmessage);
538
539 event_handler!(messageerror, GetOnmessageerror, SetOnmessageerror);
541}