script/dom/workers/
serviceworkercontainer.rs1use std::default::Default;
6use std::rc::Rc;
7
8use base::generic_channel::GenericCallback;
9use constellation_traits::{
10 Job, JobError, JobResult, JobResultValue, JobType, ScriptToConstellationMessage,
11};
12use dom_struct::dom_struct;
13use js::realm::CurrentRealm;
14
15use crate::dom::bindings::codegen::Bindings::ServiceWorkerContainerBinding::{
16 RegistrationOptions, ServiceWorkerContainerMethods,
17};
18use crate::dom::bindings::error::Error;
19use crate::dom::bindings::refcounted::TrustedPromise;
20use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
21use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
22use crate::dom::bindings::str::USVString;
23use crate::dom::client::Client;
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 client: Dom<Client>,
38}
39
40impl ServiceWorkerContainer {
41 fn new_inherited(client: &Client) -> ServiceWorkerContainer {
42 ServiceWorkerContainer {
43 eventtarget: EventTarget::new_inherited(),
44 controller: Default::default(),
45 client: Dom::from_ref(client),
46 }
47 }
48
49 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
50 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<ServiceWorkerContainer> {
51 let client = Client::new(global.as_window(), can_gc);
52 let container = ServiceWorkerContainer::new_inherited(&client);
53 reflect_dom_object(Box::new(container), global, can_gc)
54 }
55}
56
57impl ServiceWorkerContainerMethods<crate::DomTypeHolder> for ServiceWorkerContainer {
58 fn GetController(&self) -> Option<DomRoot<ServiceWorker>> {
60 self.client.get_controller()
61 }
62
63 fn Register(
66 &self,
67 realm: &mut CurrentRealm,
68 script_url: USVString,
69 options: &RegistrationOptions,
70 can_gc: CanGc,
71 ) -> Rc<Promise> {
72 let global = self.client.global();
74
75 let promise = Promise::new_in_current_realm(InRealm::already(&realm.into()), can_gc);
77 let USVString(ref script_url) = script_url;
78
79 let api_base_url = global.api_base_url();
81 let script_url = match api_base_url.join(script_url) {
82 Ok(url) => url,
83 Err(_) => {
84 promise.reject_error(Error::Type("Invalid script URL".to_owned()), can_gc);
86 return promise;
87 },
88 };
89
90 let scope = match options.scope {
92 Some(ref scope) => {
93 let USVString(inner_scope) = scope;
94 match api_base_url.join(inner_scope) {
95 Ok(url) => url,
96 Err(_) => {
97 promise.reject_error(Error::Type("Invalid scope URL".to_owned()), can_gc);
98 return promise;
99 },
100 }
101 },
102 None => script_url.join("./").unwrap(),
103 };
104
105 match script_url.scheme() {
109 "https" | "http" => {},
110 _ => {
111 promise.reject_error(
112 Error::Type("Only secure origins are allowed".to_owned()),
113 can_gc,
114 );
115 return promise;
116 },
117 }
118 if script_url.path().to_ascii_lowercase().contains("%2f") ||
120 script_url.path().to_ascii_lowercase().contains("%5c")
121 {
122 promise.reject_error(
123 Error::Type("Script URL contains forbidden characters".to_owned()),
124 can_gc,
125 );
126 return promise;
127 }
128
129 match scope.scheme() {
131 "https" | "http" => {},
132 _ => {
133 promise.reject_error(
134 Error::Type("Only secure origins are allowed".to_owned()),
135 can_gc,
136 );
137 return promise;
138 },
139 }
140 if scope.path().to_ascii_lowercase().contains("%2f") ||
142 scope.path().to_ascii_lowercase().contains("%5c")
143 {
144 promise.reject_error(
145 Error::Type("Scope URL contains forbidden characters".to_owned()),
146 can_gc,
147 );
148 return promise;
149 }
150
151 let mut handler = RegisterJobResultHandler {
154 trusted_promise: Some(TrustedPromise::new(promise.clone())),
155 task_source: global.task_manager().dom_manipulation_task_source().into(),
156 };
157
158 let result_handler = GenericCallback::new(move |message| match message {
159 Ok(msg) => handler.handle(msg),
160 Err(err) => warn!("Error receiving a JobResult: {:?}", err),
161 })
162 .expect("Failed to create callback");
163
164 let scope_things =
165 ServiceWorkerRegistration::create_scope_things(&global, script_url.clone());
166
167 let job = Job::create_job(
169 JobType::Register,
170 scope,
171 script_url,
172 result_handler,
173 self.client.creation_url(),
174 Some(scope_things),
175 );
176
177 let _ = global
179 .script_to_constellation_chan()
180 .send(ScriptToConstellationMessage::ScheduleJob(job));
181
182 promise
184 }
185}
186
187struct RegisterJobResultHandler {
190 trusted_promise: Option<TrustedPromise>,
191 task_source: SendableTaskSource,
192}
193
194impl RegisterJobResultHandler {
195 pub(crate) fn handle(&mut self, result: JobResult) {
199 match result {
200 JobResult::RejectPromise(error) => {
201 let promise = self
202 .trusted_promise
203 .take()
204 .expect("No promise to resolve for SW Register job.");
205
206 self.task_source
208 .queue(task!(reject_promise_with_security_error: move || {
209 let promise = promise.root();
210 let _ac = enter_realm(&*promise.global());
211 match error {
212 JobError::TypeError => {
213 promise.reject_error(
214 Error::Type("Failed to register a ServiceWorker".to_string()),
215 CanGc::note(),
216 );
217 },
218 JobError::SecurityError => {
219 promise.reject_error(Error::Security(None), CanGc::note());
220 },
221 }
222
223 }));
224
225 },
227 JobResult::ResolvePromise(job, value) => {
228 let promise = self
229 .trusted_promise
230 .take()
231 .expect("No promise to resolve for SW Register job.");
232
233 self.task_source.queue(task!(resolve_promise: move || {
235 let promise = promise.root();
236 let global = promise.global();
237 let _ac = enter_realm(&*global);
238
239 let JobResultValue::Registration {
241 id,
242 installing_worker,
243 waiting_worker,
244 active_worker,
245 } = value;
246
247 let registration = global.get_serviceworker_registration(
249 &job.script_url,
250 &job.scope_url,
251 id,
252 installing_worker,
253 waiting_worker,
254 active_worker,
255 CanGc::note()
256 );
257
258 promise.resolve_native(&*registration, CanGc::note());
260 }));
261
262 },
264 }
265 }
266}