aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/vrdisplay.rs
blob: d63e0f514e02e4e391f7775c10cc17c1b7ec38f4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use canvas_traits::webgl::{webgl_channel, WebGLReceiver, WebVRCommand};
use dom::bindings::callback::ExceptionHandling;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceBinding::PerformanceMethods;
use dom::bindings::codegen::Bindings::VRDisplayBinding;
use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods;
use dom::bindings::codegen::Bindings::VRDisplayBinding::VREye;
use dom::bindings::codegen::Bindings::VRLayerBinding::VRLayer;
use dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
use dom::bindings::codegen::Bindings::WindowBinding::FrameRequestCallback;
use dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::num::Finite;
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::root::{DomRoot, MutDom, MutNullableDom};
use dom::bindings::str::DOMString;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use dom::vrdisplaycapabilities::VRDisplayCapabilities;
use dom::vrdisplayevent::VRDisplayEvent;
use dom::vreyeparameters::VREyeParameters;
use dom::vrframedata::VRFrameData;
use dom::vrpose::VRPose;
use dom::vrstageparameters::VRStageParameters;
use dom::webglrenderingcontext::WebGLRenderingContext;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self, IpcSender};
use js::jsapi::JSContext;
use script_runtime::CommonScriptMsg;
use script_runtime::ScriptThreadEventCategory::WebVREvent;
use std::cell::Cell;
use std::mem;
use std::ops::Deref;
use std::rc::Rc;
use std::sync::mpsc;
use std::thread;
use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVRFrameData, WebVRLayer, WebVRMsg};

#[dom_struct]
pub struct VRDisplay {
    eventtarget: EventTarget,
    #[ignore_malloc_size_of = "Defined in rust-webvr"]
    display: DomRefCell<WebVRDisplayData>,
    depth_near: Cell<f64>,
    depth_far: Cell<f64>,
    presenting: Cell<bool>,
    left_eye_params: MutDom<VREyeParameters>,
    right_eye_params: MutDom<VREyeParameters>,
    capabilities: MutDom<VRDisplayCapabilities>,
    stage_params: MutNullableDom<VRStageParameters>,
    #[ignore_malloc_size_of = "Defined in rust-webvr"]
    frame_data: DomRefCell<WebVRFrameData>,
    #[ignore_malloc_size_of = "Defined in rust-webvr"]
    layer: DomRefCell<WebVRLayer>,
    layer_ctx: MutNullableDom<WebGLRenderingContext>,
    #[ignore_malloc_size_of = "Defined in rust-webvr"]
    next_raf_id: Cell<u32>,
    /// List of request animation frame callbacks
    #[ignore_malloc_size_of = "closures are hard"]
    raf_callback_list: DomRefCell<Vec<(u32, Option<Rc<FrameRequestCallback>>)>>,
    // Compositor VRFrameData synchonization
    frame_data_status: Cell<VRFrameDataStatus>,
    #[ignore_malloc_size_of = "closures are hard"]
    frame_data_receiver: DomRefCell<Option<WebGLReceiver<Result<Vec<u8>, ()>>>>,
    running_display_raf: Cell<bool>,
    paused: Cell<bool>,
    stopped_on_pause: Cell<bool>,
}

unsafe_no_jsmanaged_fields!(WebVRDisplayData);
unsafe_no_jsmanaged_fields!(WebVRFrameData);
unsafe_no_jsmanaged_fields!(WebVRLayer);

#[derive(Clone, Copy, Eq, MallocSizeOf, PartialEq)]
enum VRFrameDataStatus {
    Waiting,
    Synced,
    Exit
}

unsafe_no_jsmanaged_fields!(VRFrameDataStatus);

impl VRDisplay {
    fn new_inherited(global: &GlobalScope, display: WebVRDisplayData) -> VRDisplay {
        let stage = match display.stage_parameters {
            Some(ref params) => Some(VRStageParameters::new(params.clone(), &global)),
            None => None
        };

        VRDisplay {
            eventtarget: EventTarget::new_inherited(),
            display: DomRefCell::new(display.clone()),
            depth_near: Cell::new(0.01),
            depth_far: Cell::new(10000.0),
            presenting: Cell::new(false),
            left_eye_params: MutDom::new(&*VREyeParameters::new(display.left_eye_parameters.clone(), &global)),
            right_eye_params: MutDom::new(&*VREyeParameters::new(display.right_eye_parameters.clone(), &global)),
            capabilities: MutDom::new(&*VRDisplayCapabilities::new(display.capabilities.clone(), &global)),
            stage_params: MutNullableDom::new(stage.as_ref().map(|v| v.deref())),
            frame_data: DomRefCell::new(Default::default()),
            layer: DomRefCell::new(Default::default()),
            layer_ctx: MutNullableDom::default(),
            next_raf_id: Cell::new(1),
            raf_callback_list: DomRefCell::new(vec![]),
            frame_data_status: Cell::new(VRFrameDataStatus::Waiting),
            frame_data_receiver: DomRefCell::new(None),
            running_display_raf: Cell::new(false),
            // Some VR implementations (e.g. Daydream) can be paused in some life cycle situations
            // such as showing and hiding the controller pairing screen.
            paused: Cell::new(false),
            // This flag is set when the Display was presenting when it received a VR Pause event.
            // When the VR Resume event is received and the flag is set, VR presentation automatically restarts.
            stopped_on_pause: Cell::new(false)
        }
    }

    pub fn new(global: &GlobalScope, display: WebVRDisplayData) -> DomRoot<VRDisplay> {
        reflect_dom_object(Box::new(VRDisplay::new_inherited(&global, display)),
                           global,
                           VRDisplayBinding::Wrap)
    }
}

impl Drop for VRDisplay {
    fn drop(&mut self) {
        if self.presenting.get() {
            self.force_stop_present();
        }
    }
}

impl VRDisplayMethods for VRDisplay {
    // https://w3c.github.io/webvr/#dom-vrdisplay-isconnected
    fn IsConnected(&self) -> bool {
        self.display.borrow().connected
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-ispresenting
    fn IsPresenting(&self) -> bool {
        self.presenting.get()
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-capabilities
    fn Capabilities(&self) -> DomRoot<VRDisplayCapabilities> {
        DomRoot::from_ref(&*self.capabilities.get())
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-stageparameters
    fn GetStageParameters(&self) -> Option<DomRoot<VRStageParameters>> {
        self.stage_params.get().map(|s| DomRoot::from_ref(&*s))
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-geteyeparameters
    fn GetEyeParameters(&self, eye: VREye) -> DomRoot<VREyeParameters> {
        match eye {
            VREye::Left => DomRoot::from_ref(&*self.left_eye_params.get()),
            VREye::Right => DomRoot::from_ref(&*self.right_eye_params.get())
        }
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-displayid
    fn DisplayId(&self) -> u32 {
        self.display.borrow().display_id
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-displayname
    fn DisplayName(&self) -> DOMString {
        DOMString::from(self.display.borrow().display_name.clone())
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-getframedata-framedata-framedata
    fn GetFrameData(&self, frameData: &VRFrameData) -> bool {
        // If presenting we use a synced data with compositor for the whole frame.
        // Frame data is only synced with compositor when GetFrameData is called from
        // inside the VRDisplay.requestAnimationFrame. This is checked using the running_display_raf property.
        // This check avoids data race conditions when calling GetFrameData from outside of the
        // VRDisplay.requestAnimationFrame callbacks and fixes a possible deadlock during the interval
        // when the requestAnimationFrame is moved from window to VRDisplay.
        if self.presenting.get() && self.running_display_raf.get() {
            if self.frame_data_status.get() == VRFrameDataStatus::Waiting {
                self.sync_frame_data();
            }
            frameData.update(& self.frame_data.borrow());
            return true;
        }

        // If not presenting we fetch inmediante VRFrameData
        let (sender, receiver) = ipc::channel().unwrap();
        self.webvr_thread().send(WebVRMsg::GetFrameData(self.global().pipeline_id(),
                                                        self.DisplayId(),
                                                        self.depth_near.get(),
                                                        self.depth_far.get(),
                                                        sender)).unwrap();
        return match receiver.recv().unwrap() {
            Ok(data) => {
                frameData.update(&data);
                true
            },
            Err(e) => {
                error!("WebVR::GetFrameData: {:?}", e);
                false
            }
        };
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-getpose
    fn GetPose(&self) -> DomRoot<VRPose> {
        VRPose::new(&self.global(), &self.frame_data.borrow().pose)
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-resetpose
    fn ResetPose(&self) {
        let (sender, receiver) = ipc::channel().unwrap();
        self.webvr_thread().send(WebVRMsg::ResetPose(self.global().pipeline_id(),
                                                     self.DisplayId(),
                                                     sender)).unwrap();
        if let Ok(data) = receiver.recv().unwrap() {
            // Some VRDisplay data might change after calling ResetPose()
            *self.display.borrow_mut() = data;
        }
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-depthnear
    fn DepthNear(&self) -> Finite<f64> {
        Finite::wrap(self.depth_near.get())
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-depthnear
    fn SetDepthNear(&self, value: Finite<f64>) {
        self.depth_near.set(*value.deref());
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-depthfar
    fn DepthFar(&self) -> Finite<f64> {
        Finite::wrap(self.depth_far.get())
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-depthfar
    fn SetDepthFar(&self, value: Finite<f64>) {
        self.depth_far.set(*value.deref());
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-requestanimationframe
    fn RequestAnimationFrame(&self, callback: Rc<FrameRequestCallback>) -> u32 {
        if self.presenting.get() {
            let raf_id = self.next_raf_id.get();
            self.next_raf_id.set(raf_id + 1);
            self.raf_callback_list.borrow_mut().push((raf_id, Some(callback)));
            raf_id
        } else {
            // WebVR spec: When a VRDisplay is not presenting it should
            // fallback to window.requestAnimationFrame.
            self.global().as_window().RequestAnimationFrame(callback)
        }
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-cancelanimationframe
    fn CancelAnimationFrame(&self, handle: u32) {
        if self.presenting.get() {
            let mut list = self.raf_callback_list.borrow_mut();
            if let Some(pair) = list.iter_mut().find(|pair| pair.0 == handle) {
                pair.1 = None;
            }
        } else {
            // WebVR spec: When a VRDisplay is not presenting it should
            // fallback to window.cancelAnimationFrame.
            self.global().as_window().CancelAnimationFrame(handle);
        }
    }

    #[allow(unrooted_must_root)]
    // https://w3c.github.io/webvr/#dom-vrdisplay-requestpresent
    fn RequestPresent(&self, layers: Vec<VRLayer>) -> Rc<Promise> {
        let promise = Promise::new(&self.global());
        // TODO: WebVR spec: this method must be called in response to a user gesture

        // WebVR spec: If canPresent is false the promise MUST be rejected
        if !self.display.borrow().capabilities.can_present {
            let msg = "VRDisplay canPresent is false".to_string();
            promise.reject_native(&msg);
            return promise;
        }

        // Current WebVRSpec only allows 1 VRLayer if the VRDevice can present.
        // Future revisions of this spec may allow multiple layers to enable more complex rendering effects
        // such as compositing WebGL and DOM elements together.
        // That functionality is not allowed by this revision of the spec.
        if layers.len() != 1 {
            let msg = "The number of layers must be 1".to_string();
            promise.reject_native(&msg);
            return promise;
        }

        // Parse and validate received VRLayer
        let layer = validate_layer(self.global().get_cx(), &layers[0]);

        let layer_bounds;
        let layer_ctx;

        match layer {
            Ok((bounds, ctx)) => {
                layer_bounds = bounds;
                layer_ctx = ctx;
            },
            Err(msg) => {
                let msg = msg.to_string();
                promise.reject_native(&msg);
                return promise;
            }
        };

        // WebVR spec: Repeat calls while already presenting will update the VRLayers being displayed.
        if self.presenting.get() {
            *self.layer.borrow_mut() = layer_bounds;
            self.layer_ctx.set(Some(&layer_ctx));
            promise.resolve_native(&());
            return promise;
        }

        // Request Present
        let (sender, receiver) = ipc::channel().unwrap();
        self.webvr_thread().send(WebVRMsg::RequestPresent(self.global().pipeline_id(),
                                                          self.display.borrow().display_id,
                                                          sender))
                                                          .unwrap();
        match receiver.recv().unwrap() {
            Ok(()) => {
                *self.layer.borrow_mut() = layer_bounds;
                self.layer_ctx.set(Some(&layer_ctx));
                self.init_present();
                promise.resolve_native(&());
            },
            Err(e) => {
                promise.reject_native(&e);
            }
        }

        promise
    }

    #[allow(unrooted_must_root)]
    // https://w3c.github.io/webvr/#dom-vrdisplay-exitpresent
    fn ExitPresent(&self) -> Rc<Promise> {
        let promise = Promise::new(&self.global());

        // WebVR spec: If the VRDisplay is not presenting the promise MUST be rejected.
        if !self.presenting.get() {
            let msg = "VRDisplay is not presenting".to_string();
            promise.reject_native(&msg);
            return promise;
        }

        // Exit present
        let (sender, receiver) = ipc::channel().unwrap();
        self.webvr_thread().send(WebVRMsg::ExitPresent(self.global().pipeline_id(),
                                                       self.display.borrow().display_id,
                                                       Some(sender)))
                                                       .unwrap();
        match receiver.recv().unwrap() {
            Ok(()) => {
                self.stop_present();
                promise.resolve_native(&());
            },
            Err(e) => {
                promise.reject_native(&e);
            }
        }

        promise
    }

    // https://w3c.github.io/webvr/#dom-vrdisplay-submitframe
    fn SubmitFrame(&self) {
        if !self.presenting.get() {
            warn!("VRDisplay not presenting");
            return;
        }

        let display_id = self.display.borrow().display_id;
        let layer = self.layer.borrow();
        let msg = WebVRCommand::SubmitFrame(display_id, layer.left_bounds, layer.right_bounds);
        self.layer_ctx.get().unwrap().send_vr_command(msg);
    }

    // https://w3c.github.io/webvr/spec/1.1/#dom-vrdisplay-getlayers
    fn GetLayers(&self) -> Vec<VRLayer> {
        // WebVR spec: MUST return an empty array if the VRDisplay is not currently presenting
        if !self.presenting.get() {
            return Vec::new();
        }

        let layer = self.layer.borrow();

        vec![VRLayer {
            leftBounds: Some(bounds_to_vec(&layer.left_bounds)),
            rightBounds: Some(bounds_to_vec(&layer.right_bounds)),
            source: self.layer_ctx.get().map(|ctx| ctx.Canvas()),
        }]
    }
}

impl VRDisplay {
    fn webvr_thread(&self) -> IpcSender<WebVRMsg> {
        self.global().as_window().webvr_thread().expect("Shouldn't arrive here with WebVR disabled")
    }

    pub fn update_display(&self, display: &WebVRDisplayData) {
        *self.display.borrow_mut() = display.clone();
        if let Some(ref stage) = display.stage_parameters {
            if self.stage_params.get().is_none() {
                let params = Some(VRStageParameters::new(stage.clone(), &self.global()));
                self.stage_params.set(params.as_ref().map(|v| v.deref()));
            } else {
                self.stage_params.get().unwrap().update(&stage);
            }
        } else {
            self.stage_params.set(None);
        }
    }

    pub fn handle_webvr_event(&self, event: &WebVRDisplayEvent) {
        match *event {
            WebVRDisplayEvent::Connect(ref display) => {
                self.update_display(&display);
            },
            WebVRDisplayEvent::Disconnect(_id) => {
                self.display.borrow_mut().connected = false;
            },
            WebVRDisplayEvent::Activate(ref display, _) |
            WebVRDisplayEvent::Deactivate(ref display, _) |
            WebVRDisplayEvent::Blur(ref display) |
            WebVRDisplayEvent::Focus(ref display) => {
                self.update_display(&display);
                self.notify_event(&event);
            },
            WebVRDisplayEvent::PresentChange(ref display, presenting) => {
                self.update_display(&display);
                self.presenting.set(presenting);
                self.notify_event(&event);
            },
            WebVRDisplayEvent::Change(ref display) => {
                // Change event doesn't exist in WebVR spec.
                // So we update display data but don't notify JS.
                self.update_display(&display);
            },
            WebVRDisplayEvent::Pause(_) => {
                if self.paused.get() {
                    return;
                }
                self.paused.set(true);
                if self.presenting.get() {
                    self.stop_present();
                    self.stopped_on_pause.set(true);
                }

            },
            WebVRDisplayEvent::Resume(_) => {
                self.paused.set(false);
                if self.stopped_on_pause.get() {
                    self.stopped_on_pause.set(false);
                    self.init_present();
                }
            },
            WebVRDisplayEvent::Exit(_) => {
                self.stopped_on_pause.set(false);
                if self.presenting.get() {
                    self.stop_present();
                }
            }
        };
    }

    fn notify_event(&self, event: &WebVRDisplayEvent) {
        let root = DomRoot::from_ref(&*self);
        let event = VRDisplayEvent::new_from_webvr(&self.global(), &root, &event);
        event.upcast::<Event>().fire(self.global().upcast::<EventTarget>());
    }

    fn init_present(&self) {
        self.presenting.set(true);
        let (sync_sender, sync_receiver) = webgl_channel().unwrap();
        *self.frame_data_receiver.borrow_mut() = Some(sync_receiver);

        let display_id = self.display.borrow().display_id;
        let api_sender = self.layer_ctx.get().unwrap().webgl_sender();
        let js_sender = self.global().script_chan();
        let address = Trusted::new(&*self);
        let near_init = self.depth_near.get();
        let far_init = self.depth_far.get();

        // The render loop at native headset frame rate is implemented using a dedicated thread.
        // Every loop iteration syncs pose data with the HMD, submits the pixels to the display and waits for Vsync.
        // Both the requestAnimationFrame call of a VRDisplay in the JavaScript thread and the VRSyncPoses call
        // in the Webrender thread are executed in parallel. This allows to get some JavaScript code executed ahead.
        // while the render thread is syncing the VRFrameData to be used for the current frame.
        // This thread runs until the user calls ExitPresent, the tab is closed or some unexpected error happened.
        thread::Builder::new().name("WebVR_RAF".into()).spawn(move || {
            let (raf_sender, raf_receiver) = mpsc::channel();
            let mut near = near_init;
            let mut far = far_init;

            // Initialize compositor
            api_sender.send_vr(WebVRCommand::Create(display_id)).unwrap();
            loop {
                // Run RAF callbacks on JavaScript thread
                let this = address.clone();
                let sender = raf_sender.clone();
                let task = Box::new(task!(handle_vrdisplay_raf: move || {
                    this.root().handle_raf(&sender);
                }));
                js_sender.send(CommonScriptMsg::Task(WebVREvent, task)).unwrap();

                // Run Sync Poses in parallell on Render thread
                let msg = WebVRCommand::SyncPoses(display_id, near, far, sync_sender.clone());
                api_sender.send_vr(msg).unwrap();

                // Wait until both SyncPoses & RAF ends
                if let Ok(depth) = raf_receiver.recv().unwrap() {
                    near = depth.0;
                    far = depth.1;
                } else {
                    // Stop thread
                    // ExitPresent called or some error happened
                    return;
                }
            }
        }).expect("Thread spawning failed");
    }

    fn stop_present(&self) {
        self.presenting.set(false);
        *self.frame_data_receiver.borrow_mut() = None;

        let api_sender = self.layer_ctx.get().unwrap().webgl_sender();
        let display_id = self.display.borrow().display_id;
        api_sender.send_vr(WebVRCommand::Release(display_id)).unwrap();
    }

    // Only called when the JSContext is destroyed while presenting.
    // In this case we don't want to wait for WebVR Thread response.
    fn force_stop_present(&self) {
        self.webvr_thread().send(WebVRMsg::ExitPresent(self.global().pipeline_id(),
                                                       self.display.borrow().display_id,
                                                       None))
                                                       .unwrap();
        self.stop_present();
    }

    fn sync_frame_data(&self) {
        let status = if let Some(receiver) = self.frame_data_receiver.borrow().as_ref() {
            match receiver.recv().unwrap() {
                Ok(bytes) => {
                    *self.frame_data.borrow_mut() = WebVRFrameData::from_bytes(&bytes[..]);
                    VRFrameDataStatus::Synced
                },
                Err(()) => {
                    VRFrameDataStatus::Exit
                }
            }
        } else {
            VRFrameDataStatus::Exit
        };

        self.frame_data_status.set(status);
    }

    fn handle_raf(&self, end_sender: &mpsc::Sender<Result<(f64, f64), ()>>) {
        self.frame_data_status.set(VRFrameDataStatus::Waiting);
        self.running_display_raf.set(true);

        let mut callbacks = mem::replace(&mut *self.raf_callback_list.borrow_mut(), vec![]);
        let now = self.global().as_window().Performance().Now();

        // Call registered VRDisplay.requestAnimationFrame callbacks.
        for (_, callback) in callbacks.drain(..) {
            if let Some(callback) = callback {
                let _ = callback.Call__(Finite::wrap(*now), ExceptionHandling::Report);
            }
        }

        self.running_display_raf.set(false);
        if self.frame_data_status.get() == VRFrameDataStatus::Waiting {
            // User didn't call getFrameData while presenting.
            // We automatically reads the pending VRFrameData to avoid overflowing the IPC-Channel buffers.
            // Show a warning as the WebVR Spec recommends.
            warn!("WebVR: You should call GetFrameData while presenting");
            self.sync_frame_data();
        }

        match self.frame_data_status.get() {
            VRFrameDataStatus::Synced => {
                // Sync succeeded. Notify RAF thread.
                end_sender.send(Ok((self.depth_near.get(), self.depth_far.get()))).unwrap();
            },
            VRFrameDataStatus::Exit | VRFrameDataStatus::Waiting => {
                // ExitPresent called or some error ocurred.
                // Notify VRDisplay RAF thread to stop.
                end_sender.send(Err(())).unwrap();
            }
        }
    }
}

// WebVR Spec: If the number of values in the leftBounds/rightBounds arrays
// is not 0 or 4 for any of the passed layers the promise is rejected
fn parse_bounds(src: &Option<Vec<Finite<f32>>>, dst: &mut [f32; 4]) -> Result<(), &'static str> {
    match *src {
        Some(ref values) => {
            if values.len() == 0 {
                return Ok(())
            }
            if values.len() != 4 {
                return Err("The number of values in the leftBounds/rightBounds arrays must be 0 or 4")
            }
            for i in 0..4 {
                dst[i] = *values[i].deref();
            }
            Ok(())
        },
        None => Ok(())
    }
}

fn validate_layer(cx: *mut JSContext,
                  layer: &VRLayer)
                  -> Result<(WebVRLayer, DomRoot<WebGLRenderingContext>), &'static str> {
    let ctx = layer.source.as_ref().map(|ref s| s.get_or_init_webgl_context(cx, None)).unwrap_or(None);
    if let Some(ctx) = ctx {
        let mut data = WebVRLayer::default();
        parse_bounds(&layer.leftBounds, &mut data.left_bounds)?;
        parse_bounds(&layer.rightBounds, &mut data.right_bounds)?;
        Ok((data, ctx))
    } else {
        Err("VRLayer source must be a WebGL Context")
    }
}

fn bounds_to_vec(src: &[f32; 4]) -> Vec<Finite<f32>> {
    vec![Finite::wrap(src[0]),
         Finite::wrap(src[1]),
         Finite::wrap(src[2]),
         Finite::wrap(src[3])]
}