script/dom/workers/
serviceworkercontainer.rs1use std::default::Default;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::realm::CurrentRealm;
10use script_bindings::reflector::reflect_dom_object;
11use servo_base::generic_channel::GenericCallback;
12use servo_constellation_traits::{
13 Job, JobError, JobResult, JobResultValue, JobType, ScriptToConstellationMessage,
14};
15
16use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{
17 RegistrationOptions, ServiceWorkerContainerMethods,
18};
19use crate::dom::bindings::error::Error;
20use crate::dom::bindings::refcounted::TrustedPromise;
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::root::{DomRoot, MutNullableDom};
23use crate::dom::bindings::str::USVString;
24use crate::dom::eventtarget::EventTarget;
25use crate::dom::globalscope::GlobalScope;
26use crate::dom::promise::Promise;
27use crate::dom::serviceworker::ServiceWorker;
28use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
29use crate::realms::{InRealm, enter_realm};
30use crate::script_runtime::CanGc;
31use crate::task_source::SendableTaskSource;
32
33#[dom_struct]
34pub(crate) struct ServiceWorkerContainer {
35 eventtarget: EventTarget,
36 controller: MutNullableDom<ServiceWorker>,
37}
38
39impl ServiceWorkerContainer {
40 fn new_inherited() -> ServiceWorkerContainer {
41 ServiceWorkerContainer {
42 eventtarget: EventTarget::new_inherited(),
43 controller: Default::default(),
44 }
45 }
46
47 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<ServiceWorkerContainer> {
48 reflect_dom_object(
49 Box::new(ServiceWorkerContainer::new_inherited()),
50 global,
51 can_gc,
52 )
53 }
54}
55
56impl ServiceWorkerContainerMethods<crate::DomTypeHolder> for ServiceWorkerContainer {
57 fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
59 None
60 }
61
62 fn Register(
65 &self,
66 realm: &mut CurrentRealm,
67 script_url: USVString,
68 options: &RegistrationOptions,
69 ) -> Rc<Promise> {
70 let can_gc = CanGc::from_cx(realm);
71 let global = self.global();
73
74 let promise = Promise::new_in_current_realm(InRealm::already(&realm.into()), can_gc);
76 let USVString(ref script_url) = script_url;
77
78 let api_base_url = global.api_base_url();
80 let script_url = match api_base_url.join(script_url) {
81 Ok(url) => url,
82 Err(_) => {
83 promise.reject_error(Error::Type(c"Invalid script URL".to_owned()), can_gc);
85 return promise;
86 },
87 };
88
89 let scope = match options.scope {
91 Some(ref scope) => {
92 let USVString(inner_scope) = scope;
93 match api_base_url.join(inner_scope) {
94 Ok(url) => url,
95 Err(_) => {
96 promise.reject_error(Error::Type(c"Invalid scope URL".to_owned()), can_gc);
97 return promise;
98 },
99 }
100 },
101 None => script_url.join("./").unwrap(),
102 };
103
104 match script_url.scheme() {
108 "https" | "http" => {},
109 _ => {
110 promise.reject_error(
111 Error::Type(c"Only secure origins are allowed".to_owned()),
112 can_gc,
113 );
114 return promise;
115 },
116 }
117 if script_url.path().to_ascii_lowercase().contains("%2f") ||
119 script_url.path().to_ascii_lowercase().contains("%5c")
120 {
121 promise.reject_error(
122 Error::Type(c"Script URL contains forbidden characters".to_owned()),
123 can_gc,
124 );
125 return promise;
126 }
127
128 match scope.scheme() {
130 "https" | "http" => {},
131 _ => {
132 promise.reject_error(
133 Error::Type(c"Only secure origins are allowed".to_owned()),
134 can_gc,
135 );
136 return promise;
137 },
138 }
139 if scope.path().to_ascii_lowercase().contains("%2f") ||
141 scope.path().to_ascii_lowercase().contains("%5c")
142 {
143 promise.reject_error(
144 Error::Type(c"Scope URL contains forbidden characters".to_owned()),
145 can_gc,
146 );
147 return promise;
148 }
149
150 let mut handler = RegisterJobResultHandler {
153 trusted_promise: Some(TrustedPromise::new(promise.clone())),
154 task_source: global.task_manager().dom_manipulation_task_source().into(),
155 };
156
157 let result_handler = GenericCallback::new(move |message| match message {
158 Ok(msg) => handler.handle(msg),
159 Err(err) => warn!("Error receiving a JobResult: {:?}", err),
160 })
161 .expect("Failed to create callback");
162
163 let scope_things =
164 ServiceWorkerRegistration::create_scope_things(&global, script_url.clone());
165
166 let Some(storage_key) = global.obtain_storage_key() else {
170 promise.reject_error(
171 Error::Type(c"Failed to obtain a storage key".to_owned()),
172 can_gc,
173 );
174 return promise;
175 };
176
177 let job = Job::create_job(
178 JobType::Register,
179 scope,
180 script_url,
181 result_handler,
182 global.creation_url(),
183 Some(scope_things),
184 storage_key,
185 );
186
187 let _ = global
189 .script_to_constellation_chan()
190 .send(ScriptToConstellationMessage::ScheduleJob(job));
191
192 promise
194 }
195}
196
197struct RegisterJobResultHandler {
200 trusted_promise: Option<TrustedPromise>,
201 task_source: SendableTaskSource,
202}
203
204impl RegisterJobResultHandler {
205 pub(crate) fn handle(&mut self, result: JobResult) {
209 match result {
210 JobResult::RejectPromise(error) => {
211 let promise = self
212 .trusted_promise
213 .take()
214 .expect("No promise to resolve for SW Register job.");
215
216 self.task_source
218 .queue(task!(reject_promise_with_security_error: move || {
219 let promise = promise.root();
220 let _ac = enter_realm(&*promise.global());
221 match error {
222 JobError::TypeError => {
223 promise.reject_error(
224 Error::Type(c"Failed to register a ServiceWorker".to_owned()),
225 CanGc::deprecated_note(),
226 );
227 },
228 JobError::SecurityError => {
229 promise.reject_error(Error::Security(None), CanGc::deprecated_note());
230 },
231 }
232
233 }));
234
235 },
237 JobResult::ResolvePromise(job, value) => {
238 let promise = self
239 .trusted_promise
240 .take()
241 .expect("No promise to resolve for SW Register job.");
242
243 self.task_source.queue(task!(resolve_promise: move || {
245 let promise = promise.root();
246 let global = promise.global();
247 let _ac = enter_realm(&*global);
248
249 let JobResultValue::Registration {
251 id,
252 installing_worker,
253 waiting_worker,
254 active_worker,
255 } = value;
256
257 let registration = global.get_serviceworker_registration(
259 &job.script_url,
260 &job.scope_url,
261 id,
262 installing_worker,
263 waiting_worker,
264 active_worker,
265 CanGc::deprecated_note()
266 );
267
268 promise.resolve_native(&*registration, CanGc::deprecated_note());
270 }));
271
272 },
274 }
275 }
276}