1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 use devtools;
6 use devtools_traits::DevtoolScriptControlMsg;
7 use dom::abstractworker::WorkerScriptMsg;
8 use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding;
9 use dom::bindings::codegen::Bindings::ServiceWorkerGlobalScopeBinding::ServiceWorkerGlobalScopeMethods;
10 use dom::bindings::inheritance::Castable;
11 use dom::bindings::reflector::DomObject;
12 use dom::bindings::root::{DomRoot, RootCollection, ThreadLocalStackRoots};
13 use dom::bindings::str::DOMString;
14 use dom::event::Event;
15 use dom::eventtarget::EventTarget;
16 use dom::extendableevent::ExtendableEvent;
17 use dom::extendablemessageevent::ExtendableMessageEvent;
18 use dom::globalscope::GlobalScope;
19 use dom::workerglobalscope::WorkerGlobalScope;
20 use dom_struct::dom_struct;
21 use ipc_channel::ipc::{self, IpcSender, IpcReceiver};
22 use ipc_channel::router::ROUTER;
23 use js::jsapi::{JS_SetInterruptCallback, JSAutoCompartment, JSContext};
24 use js::jsval::UndefinedValue;
25 use net_traits::{load_whole_resource, IpcSend, CustomResponseMediator};
26 use net_traits::request::{CredentialsMode, Destination, RequestInit};
27 use script_runtime::{CommonScriptMsg, ScriptChan, new_rt_and_cx, Runtime};
28 use script_traits::{TimerEvent, WorkerGlobalScopeInit, ScopeThings, ServiceWorkerMsg, WorkerScriptLoadOrigin};
29 use servo_config::prefs::PREFS;
30 use servo_rand::random;
31 use servo_url::ServoUrl;
32 use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel};
33 use std::thread;
34 use std::time::Duration;
35 use style::thread_state::{self, ThreadState};
36 
37 /// Messages used to control service worker event loop
38 pub enum ServiceWorkerScriptMsg {
39     /// Message common to all workers
40     CommonWorker(WorkerScriptMsg),
41     // Message to request a custom response by the service worker
42     Response(CustomResponseMediator)
43 }
44 
45 pub enum MixedMessage {
46     FromServiceWorker(ServiceWorkerScriptMsg),
47     FromDevtools(DevtoolScriptControlMsg),
48     FromTimeoutThread(())
49 }
50 
51 #[derive(Clone, JSTraceable)]
52 pub struct ServiceWorkerChan {
53     pub sender: Sender<ServiceWorkerScriptMsg>
54 }
55 
56 impl ScriptChan for ServiceWorkerChan {
send(&self, msg: CommonScriptMsg) -> Result<(), ()>57     fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> {
58         self.sender
59             .send(ServiceWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(msg)))
60             .map_err(|_| ())
61     }
62 
clone(&self) -> Box<ScriptChan + Send>63     fn clone(&self) -> Box<ScriptChan + Send> {
64         Box::new(ServiceWorkerChan {
65             sender: self.sender.clone(),
66         })
67     }
68 }
69 
70 #[dom_struct]
71 pub struct ServiceWorkerGlobalScope {
72     workerglobalscope: WorkerGlobalScope,
73     #[ignore_malloc_size_of = "Defined in std"]
74     receiver: Receiver<ServiceWorkerScriptMsg>,
75     #[ignore_malloc_size_of = "Defined in std"]
76     own_sender: Sender<ServiceWorkerScriptMsg>,
77     #[ignore_malloc_size_of = "Defined in std"]
78     timer_event_port: Receiver<()>,
79     #[ignore_malloc_size_of = "Defined in std"]
80     swmanager_sender: IpcSender<ServiceWorkerMsg>,
81     scope_url: ServoUrl,
82 }
83 
84 impl ServiceWorkerGlobalScope {
new_inherited(init: WorkerGlobalScopeInit, worker_url: ServoUrl, from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, runtime: Runtime, own_sender: Sender<ServiceWorkerScriptMsg>, receiver: Receiver<ServiceWorkerScriptMsg>, timer_event_chan: IpcSender<TimerEvent>, timer_event_port: Receiver<()>, swmanager_sender: IpcSender<ServiceWorkerMsg>, scope_url: ServoUrl) -> ServiceWorkerGlobalScope85     fn new_inherited(init: WorkerGlobalScopeInit,
86                      worker_url: ServoUrl,
87                      from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
88                      runtime: Runtime,
89                      own_sender: Sender<ServiceWorkerScriptMsg>,
90                      receiver: Receiver<ServiceWorkerScriptMsg>,
91                      timer_event_chan: IpcSender<TimerEvent>,
92                      timer_event_port: Receiver<()>,
93                      swmanager_sender: IpcSender<ServiceWorkerMsg>,
94                      scope_url: ServoUrl)
95                      -> ServiceWorkerGlobalScope {
96         ServiceWorkerGlobalScope {
97             workerglobalscope: WorkerGlobalScope::new_inherited(init,
98                                                                 worker_url,
99                                                                 runtime,
100                                                                 from_devtools_receiver,
101                                                                 timer_event_chan,
102                                                                 None),
103             receiver: receiver,
104             timer_event_port: timer_event_port,
105             own_sender: own_sender,
106             swmanager_sender: swmanager_sender,
107             scope_url: scope_url
108         }
109     }
110 
111     #[allow(unsafe_code)]
new(init: WorkerGlobalScopeInit, worker_url: ServoUrl, from_devtools_receiver: Receiver<DevtoolScriptControlMsg>, runtime: Runtime, own_sender: Sender<ServiceWorkerScriptMsg>, receiver: Receiver<ServiceWorkerScriptMsg>, timer_event_chan: IpcSender<TimerEvent>, timer_event_port: Receiver<()>, swmanager_sender: IpcSender<ServiceWorkerMsg>, scope_url: ServoUrl) -> DomRoot<ServiceWorkerGlobalScope>112     pub fn new(init: WorkerGlobalScopeInit,
113                worker_url: ServoUrl,
114                from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
115                runtime: Runtime,
116                own_sender: Sender<ServiceWorkerScriptMsg>,
117                receiver: Receiver<ServiceWorkerScriptMsg>,
118                timer_event_chan: IpcSender<TimerEvent>,
119                timer_event_port: Receiver<()>,
120                swmanager_sender: IpcSender<ServiceWorkerMsg>,
121                scope_url: ServoUrl)
122                -> DomRoot<ServiceWorkerGlobalScope> {
123         let cx = runtime.cx();
124         let scope = Box::new(ServiceWorkerGlobalScope::new_inherited(
125             init,
126             worker_url,
127             from_devtools_receiver,
128             runtime,
129             own_sender,
130             receiver,
131             timer_event_chan,
132             timer_event_port,
133             swmanager_sender,
134             scope_url
135         ));
136         unsafe {
137             ServiceWorkerGlobalScopeBinding::Wrap(cx, scope)
138         }
139     }
140 
141     #[allow(unsafe_code)]
run_serviceworker_scope(scope_things: ScopeThings, own_sender: Sender<ServiceWorkerScriptMsg>, receiver: Receiver<ServiceWorkerScriptMsg>, devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>, swmanager_sender: IpcSender<ServiceWorkerMsg>, scope_url: ServoUrl)142     pub fn run_serviceworker_scope(scope_things: ScopeThings,
143                             own_sender: Sender<ServiceWorkerScriptMsg>,
144                             receiver: Receiver<ServiceWorkerScriptMsg>,
145                             devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
146                             swmanager_sender: IpcSender<ServiceWorkerMsg>,
147                             scope_url: ServoUrl) {
148         let ScopeThings { script_url,
149                           init,
150                           worker_load_origin,
151                           .. } = scope_things;
152 
153         let serialized_worker_url = script_url.to_string();
154         let origin = GlobalScope::current().expect("No current global object").origin().immutable().clone();
155         thread::Builder::new().name(format!("ServiceWorker for {}", serialized_worker_url)).spawn(move || {
156             thread_state::initialize(ThreadState::SCRIPT | ThreadState::IN_WORKER);
157             let roots = RootCollection::new();
158             let _stack_roots = ThreadLocalStackRoots::new(&roots);
159 
160             let WorkerScriptLoadOrigin { referrer_url, referrer_policy, pipeline_id } = worker_load_origin;
161 
162             let request = RequestInit {
163                 url: script_url.clone(),
164                 destination: Destination::ServiceWorker,
165                 credentials_mode: CredentialsMode::Include,
166                 use_url_credentials: true,
167                 pipeline_id: pipeline_id,
168                 referrer_url: referrer_url,
169                 referrer_policy: referrer_policy,
170                 origin,
171                 .. RequestInit::default()
172             };
173 
174             let (url, source) = match load_whole_resource(request,
175                                                           &init.resource_threads.sender()) {
176                 Err(_) => {
177                     println!("error loading script {}", serialized_worker_url);
178                     return;
179                 }
180                 Ok((metadata, bytes)) => {
181                     (metadata.final_url, String::from_utf8(bytes).unwrap())
182                 }
183             };
184 
185             let runtime = unsafe { new_rt_and_cx() };
186 
187             let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
188             ROUTER.route_ipc_receiver_to_mpsc_sender(devtools_receiver, devtools_mpsc_chan);
189             // TODO XXXcreativcoder use this timer_ipc_port, when we have a service worker instance here
190             let (timer_ipc_chan, _timer_ipc_port) = ipc::channel().unwrap();
191             let (timer_chan, timer_port) = channel();
192             let global = ServiceWorkerGlobalScope::new(
193                 init, url, devtools_mpsc_port, runtime,
194                 own_sender, receiver,
195                 timer_ipc_chan, timer_port, swmanager_sender, scope_url);
196             let scope = global.upcast::<WorkerGlobalScope>();
197 
198             unsafe {
199                 // Handle interrupt requests
200                 JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback));
201             }
202 
203             scope.execute_script(DOMString::from(source));
204             // Service workers are time limited
205             thread::Builder::new().name("SWTimeoutThread".to_owned()).spawn(move || {
206                 let sw_lifetime_timeout = PREFS.get("dom.serviceworker.timeout_seconds").as_u64().unwrap();
207                 thread::sleep(Duration::new(sw_lifetime_timeout, 0));
208                 let _ = timer_chan.send(());
209             }).expect("Thread spawning failed");
210 
211             global.dispatch_activate();
212             let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
213             scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| {
214                 // https://html.spec.whatwg.org/multipage/#event-loop-processing-model
215                 // Step 1
216                 while let Ok(event) = global.receive_event() {
217                     // Step 3
218                     if !global.handle_event(event) {
219                         break;
220                     }
221                     // Step 6
222                     global.upcast::<GlobalScope>().perform_a_microtask_checkpoint();
223                 }
224             }, reporter_name, scope.script_chan(), CommonScriptMsg::CollectReports);
225         }).expect("Thread spawning failed");
226     }
227 
handle_event(&self, event: MixedMessage) -> bool228     fn handle_event(&self, event: MixedMessage) -> bool {
229         match event {
230             MixedMessage::FromDevtools(msg) => {
231                 match msg {
232                     DevtoolScriptControlMsg::EvaluateJS(_pipe_id, string, sender) =>
233                         devtools::handle_evaluate_js(self.upcast(), string, sender),
234                     DevtoolScriptControlMsg::GetCachedMessages(pipe_id, message_types, sender) =>
235                         devtools::handle_get_cached_messages(pipe_id, message_types, sender),
236                     DevtoolScriptControlMsg::WantsLiveNotifications(_pipe_id, bool_val) =>
237                         devtools::handle_wants_live_notifications(self.upcast(), bool_val),
238                     _ => debug!("got an unusable devtools control message inside the worker!"),
239                 }
240                 true
241             }
242             MixedMessage::FromServiceWorker(msg) => {
243                 self.handle_script_event(msg);
244                 true
245             }
246             MixedMessage::FromTimeoutThread(_) => {
247                 let _ = self.swmanager_sender.send(ServiceWorkerMsg::Timeout(self.scope_url.clone()));
248                 false
249             }
250         }
251     }
252 
handle_script_event(&self, msg: ServiceWorkerScriptMsg)253     fn handle_script_event(&self, msg: ServiceWorkerScriptMsg) {
254         use self::ServiceWorkerScriptMsg::*;
255 
256         match msg {
257             CommonWorker(WorkerScriptMsg::DOMMessage(data)) => {
258                 let scope = self.upcast::<WorkerGlobalScope>();
259                 let target = self.upcast();
260                 let _ac = JSAutoCompartment::new(scope.get_cx(), scope.reflector().get_jsobject().get());
261                 rooted!(in(scope.get_cx()) let mut message = UndefinedValue());
262                 data.read(scope.upcast(), message.handle_mut());
263                 ExtendableMessageEvent::dispatch_jsval(target, scope.upcast(), message.handle());
264             },
265             CommonWorker(WorkerScriptMsg::Common(msg)) => {
266                 self.upcast::<WorkerGlobalScope>().process_event(msg);
267             },
268             Response(mediator) => {
269                 // TODO XXXcreativcoder This will eventually use a FetchEvent interface to fire event
270                 // when we have the Request and Response dom api's implemented
271                 // https://slightlyoff.github.io/ServiceWorker/spec/service_worker_1/index.html#fetch-event-section
272                 self.upcast::<EventTarget>().fire_event(atom!("fetch"));
273                 let _ = mediator.response_chan.send(None);
274             }
275         }
276     }
277 
278     #[allow(unsafe_code)]
receive_event(&self) -> Result<MixedMessage, RecvError>279     fn receive_event(&self) -> Result<MixedMessage, RecvError> {
280         let scope = self.upcast::<WorkerGlobalScope>();
281         let worker_port = &self.receiver;
282         let devtools_port = scope.from_devtools_receiver();
283         let timer_event_port = &self.timer_event_port;
284 
285         let sel = Select::new();
286         let mut worker_handle = sel.handle(worker_port);
287         let mut devtools_handle = sel.handle(devtools_port);
288         let mut timer_port_handle = sel.handle(timer_event_port);
289         unsafe {
290             worker_handle.add();
291             if scope.from_devtools_sender().is_some() {
292                 devtools_handle.add();
293             }
294             timer_port_handle.add();
295         }
296 
297         let ret = sel.wait();
298         if ret == worker_handle.id() {
299             Ok(MixedMessage::FromServiceWorker(worker_port.recv()?))
300         }else if ret == devtools_handle.id() {
301             Ok(MixedMessage::FromDevtools(devtools_port.recv()?))
302         } else if ret == timer_port_handle.id() {
303             Ok(MixedMessage::FromTimeoutThread(timer_event_port.recv()?))
304         } else {
305             panic!("unexpected select result!")
306         }
307     }
308 
script_chan(&self) -> Box<ScriptChan + Send>309     pub fn script_chan(&self) -> Box<ScriptChan + Send> {
310         Box::new(ServiceWorkerChan {
311             sender: self.own_sender.clone()
312         })
313     }
314 
dispatch_activate(&self)315     fn dispatch_activate(&self) {
316         let event = ExtendableEvent::new(self, atom!("activate"), false, false);
317         let event = (&*event).upcast::<Event>();
318         self.upcast::<EventTarget>().dispatch_event(event);
319     }
320 }
321 
322 #[allow(unsafe_code)]
interrupt_callback(cx: *mut JSContext) -> bool323 unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
324     let worker =
325         DomRoot::downcast::<WorkerGlobalScope>(GlobalScope::from_context(cx))
326             .expect("global is not a worker scope");
327     assert!(worker.is::<ServiceWorkerGlobalScope>());
328 
329     // A false response causes the script to terminate
330     !worker.is_closing()
331 }
332 
333 impl ServiceWorkerGlobalScopeMethods for ServiceWorkerGlobalScope {
334     // https://w3c.github.io/ServiceWorker/#service-worker-global-scope-onmessage-attribute
335     event_handler!(message, GetOnmessage, SetOnmessage);
336 }
337