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