Implement Gamepad API

This commit is contained in:
Imanol Fernandez 2017-03-29 15:41:40 +02:00
parent 69eda6a60c
commit 0158b5b2af
27 changed files with 1193 additions and 96 deletions

View file

@ -102,7 +102,6 @@ use script_traits::{LayoutMsg as FromLayoutMsg, ScriptMsg as FromScriptMsg, Scri
use script_traits::{LogEntry, ServiceWorkerMsg, webdriver_msg};
use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, WindowSizeData};
use script_traits::{SWManagerMsg, ScopeThings, WindowSizeType};
use script_traits::WebVREventMsg;
use serde::{Deserialize, Serialize};
use servo_config::opts;
use servo_config::prefs::PREFS;
@ -124,7 +123,7 @@ use style_traits::cursor::Cursor;
use style_traits::viewport::ViewportConstraints;
use timer_scheduler::TimerScheduler;
use webrender_traits;
use webvr_traits::WebVRMsg;
use webvr_traits::{WebVREvent, WebVRMsg};
/// The `Constellation` itself. In the servo browser, there is one
/// constellation, which maintains all of the browser global data.
@ -897,9 +896,9 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
assert!(self.webvr_thread.is_none());
self.webvr_thread = Some(webvr_thread)
}
FromCompositorMsg::WebVREvent(pipeline_ids, event) => {
debug!("constellation got WebVR event");
self.handle_webvr_event(pipeline_ids, event);
FromCompositorMsg::WebVREvents(pipeline_ids, events) => {
debug!("constellation got {:?} WebVR events", events.len());
self.handle_webvr_events(pipeline_ids, events);
}
}
}
@ -1326,12 +1325,12 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
}
}
fn handle_webvr_event(&mut self, ids: Vec<PipelineId>, event: WebVREventMsg) {
fn handle_webvr_events(&mut self, ids: Vec<PipelineId>, events: Vec<WebVREvent>) {
for id in ids {
match self.pipelines.get_mut(&id) {
Some(ref pipeline) => {
// Notify script thread
let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvent(id, event.clone()));
let _ = pipeline.event_loop.send(ConstellationControlMsg::WebVREvents(id, events.clone()));
},
None => warn!("constellation got webvr event for dead pipeline")
}

View file

@ -107,6 +107,7 @@ use time::Duration;
use uuid::Uuid;
use webrender_traits::{WebGLBufferId, WebGLError, WebGLFramebufferId, WebGLProgramId};
use webrender_traits::{WebGLRenderbufferId, WebGLShaderId, WebGLTextureId};
use webvr_traits::WebVRGamepadHand;
/// A trait to allow tracing (only) DOM objects.
pub unsafe trait JSTraceable {
@ -381,6 +382,7 @@ unsafe_no_jsmanaged_fields!(WebGLRenderbufferId);
unsafe_no_jsmanaged_fields!(WebGLShaderId);
unsafe_no_jsmanaged_fields!(WebGLTextureId);
unsafe_no_jsmanaged_fields!(MediaList);
unsafe_no_jsmanaged_fields!(WebVRGamepadHand);
unsafe impl<'a> JSTraceable for &'a str {
#[inline]

View file

@ -0,0 +1,206 @@
/* 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 core::nonzero::NonZero;
use dom::bindings::codegen::Bindings::GamepadBinding;
use dom::bindings::codegen::Bindings::GamepadBinding::GamepadMethods;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::gamepadbuttonlist::GamepadButtonList;
use dom::gamepadevent::{GamepadEvent, GamepadEventType};
use dom::globalscope::GlobalScope;
use dom::vrpose::VRPose;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject};
use js::typedarray::{Float64Array, CreateWith};
use std::cell::Cell;
use std::ptr;
use webvr_traits::{WebVRGamepadData, WebVRGamepadHand, WebVRGamepadState};
#[dom_struct]
pub struct Gamepad {
reflector_: Reflector,
gamepad_id: u32,
id: String,
index: Cell<i32>,
connected: Cell<bool>,
timestamp: Cell<f64>,
mapping_type: String,
axes: Heap<*mut JSObject>,
buttons: JS<GamepadButtonList>,
pose: Option<JS<VRPose>>,
#[ignore_heap_size_of = "Defined in rust-webvr"]
hand: WebVRGamepadHand,
display_id: u32
}
impl Gamepad {
fn new_inherited(gamepad_id: u32,
id: String,
index: i32,
connected: bool,
timestamp: f64,
mapping_type: String,
axes: *mut JSObject,
buttons: &GamepadButtonList,
pose: Option<&VRPose>,
hand: WebVRGamepadHand,
display_id: u32) -> Gamepad {
Self {
reflector_: Reflector::new(),
gamepad_id: gamepad_id,
id: id,
index: Cell::new(index),
connected: Cell::new(connected),
timestamp: Cell::new(timestamp),
mapping_type: mapping_type,
axes: Heap::new(axes),
buttons: JS::from_ref(buttons),
pose: pose.map(JS::from_ref),
hand: hand,
display_id: display_id
}
}
#[allow(unsafe_code)]
pub fn new_from_vr(global: &GlobalScope,
index: i32,
data: &WebVRGamepadData,
state: &WebVRGamepadState) -> Root<Gamepad> {
let buttons = GamepadButtonList::new_from_vr(&global, &state.buttons);
let pose = VRPose::new(&global, &state.pose);
let cx = global.get_cx();
rooted!(in (cx) let mut axes = ptr::null_mut());
unsafe {
let _ = Float64Array::create(cx,
CreateWith::Slice(&state.axes),
axes.handle_mut());
}
reflect_dom_object(box Gamepad::new_inherited(state.gamepad_id,
data.name.clone(),
index,
state.connected,
state.timestamp,
"".into(),
axes.get(),
&buttons,
Some(&pose),
data.hand.clone(),
data.display_id),
global,
GamepadBinding::Wrap)
}
}
impl GamepadMethods for Gamepad {
// https://w3c.github.io/gamepad/#dom-gamepad-id
fn Id(&self) -> DOMString {
DOMString::from(self.id.clone())
}
// https://w3c.github.io/gamepad/#dom-gamepad-index
fn Index(&self) -> i32 {
self.index.get()
}
// https://w3c.github.io/gamepad/#dom-gamepad-connected
fn Connected(&self) -> bool {
self.connected.get()
}
// https://w3c.github.io/gamepad/#dom-gamepad-timestamp
fn Timestamp(&self) -> Finite<f64> {
Finite::wrap(self.timestamp.get())
}
// https://w3c.github.io/gamepad/#dom-gamepad-mapping
fn Mapping(&self) -> DOMString {
DOMString::from(self.mapping_type.clone())
}
#[allow(unsafe_code)]
// https://w3c.github.io/gamepad/#dom-gamepad-axes
unsafe fn Axes(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
NonZero::new(self.axes.get())
}
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn Buttons(&self) -> Root<GamepadButtonList> {
Root::from_ref(&*self.buttons)
}
// https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
fn Hand(&self) -> DOMString {
let value = match self.hand {
WebVRGamepadHand::Unknown => "",
WebVRGamepadHand::Left => "left",
WebVRGamepadHand::Right => "right"
};
value.into()
}
// https://w3c.github.io/gamepad/extensions.html#dom-gamepad-pose
fn GetPose(&self) -> Option<Root<VRPose>> {
self.pose.as_ref().map(|p| Root::from_ref(&**p))
}
// https://w3c.github.io/webvr/spec/1.1/#gamepad-getvrdisplays-attribute
fn DisplayId(&self) -> u32 {
self.display_id
}
}
impl Gamepad {
#[allow(unsafe_code)]
pub fn update_from_vr(&self, state: &WebVRGamepadState) {
self.timestamp.set(state.timestamp);
unsafe {
let cx = self.global().get_cx();
typedarray!(in(cx) let axes: Float64Array = self.axes.get());
if let Ok(mut array) = axes {
array.update(&state.axes);
}
}
self.buttons.sync_from_vr(&state.buttons);
if let Some(ref pose) = self.pose {
pose.update(&state.pose);
}
self.update_connected(state.connected);
}
pub fn gamepad_id(&self) -> u32 {
self.gamepad_id
}
pub fn update_connected(&self, connected: bool) {
if self.connected.get() == connected {
return;
}
self.connected.set(connected);
let event_type = if connected {
GamepadEventType::Connected
} else {
GamepadEventType::Disconnected
};
self.notify_event(event_type);
}
pub fn update_index(&self, index: i32) {
self.index.set(index);
}
pub fn notify_event(&self, event_type: GamepadEventType) {
let event = GamepadEvent::new_with_type(&self.global(), event_type, &self);
event.upcast::<Event>().fire(self.global().as_window().upcast::<EventTarget>());
}
}

View file

@ -0,0 +1,61 @@
/* 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 dom::bindings::codegen::Bindings::GamepadButtonBinding;
use dom::bindings::codegen::Bindings::GamepadButtonBinding::GamepadButtonMethods;
use dom::bindings::js::Root;
use dom::bindings::num::Finite;
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use std::cell::Cell;
#[dom_struct]
pub struct GamepadButton {
reflector_: Reflector,
pressed: Cell<bool>,
touched: Cell<bool>,
value: Cell<f64>,
}
impl GamepadButton {
pub fn new_inherited(pressed: bool, touched: bool) -> GamepadButton {
Self {
reflector_: Reflector::new(),
pressed: Cell::new(pressed),
touched: Cell::new(touched),
value: Cell::new(0.0),
}
}
pub fn new(global: &GlobalScope, pressed: bool, touched: bool) -> Root<GamepadButton> {
reflect_dom_object(box GamepadButton::new_inherited(pressed, touched),
global,
GamepadButtonBinding::Wrap)
}
}
impl GamepadButtonMethods for GamepadButton {
// https://www.w3.org/TR/gamepad/#widl-GamepadButton-pressed
fn Pressed(&self) -> bool {
self.pressed.get()
}
// https://www.w3.org/TR/gamepad/#widl-GamepadButton-touched
fn Touched(&self) -> bool {
self.touched.get()
}
// https://www.w3.org/TR/gamepad/#widl-GamepadButton-value
fn Value(&self) -> Finite<f64> {
Finite::wrap(self.value.get())
}
}
impl GamepadButton {
pub fn update(&self, pressed: bool, touched: bool) {
self.pressed.set(pressed);
self.touched.set(touched);
}
}

View file

@ -0,0 +1,63 @@
/* 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 dom::bindings::codegen::Bindings::GamepadButtonListBinding;
use dom::bindings::codegen::Bindings::GamepadButtonListBinding::GamepadButtonListMethods;
use dom::bindings::js::{JS, Root, RootedReference};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::gamepadbutton::GamepadButton;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
use webvr_traits::WebVRGamepadButton;
// https://w3c.github.io/gamepad/#gamepadbutton-interface
#[dom_struct]
pub struct GamepadButtonList {
reflector_: Reflector,
list: Vec<JS<GamepadButton>>
}
impl GamepadButtonList {
#[allow(unrooted_must_root)]
fn new_inherited(list: &[&GamepadButton]) -> GamepadButtonList {
GamepadButtonList {
reflector_: Reflector::new(),
list: list.iter().map(|button| JS::from_ref(*button)).collect(),
}
}
pub fn new_from_vr(global: &GlobalScope, buttons: &[WebVRGamepadButton]) -> Root<GamepadButtonList> {
rooted_vec!(let list <- buttons.iter()
.map(|btn| GamepadButton::new(&global, btn.pressed, btn.touched)));
reflect_dom_object(box GamepadButtonList::new_inherited(list.r()),
global,
GamepadButtonListBinding::Wrap)
}
pub fn sync_from_vr(&self, vr_buttons: &[WebVRGamepadButton]) {
let mut index = 0;
for btn in vr_buttons {
self.list.get(index).as_ref().unwrap().update(btn.pressed, btn.touched);
index += 1;
}
}
}
impl GamepadButtonListMethods for GamepadButtonList {
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn Length(&self) -> u32 {
self.list.len() as u32
}
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn Item(&self, index: u32) -> Option<Root<GamepadButton>> {
self.list.get(index as usize).map(|button| Root::from_ref(&**button))
}
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
fn IndexedGetter(&self, index: u32) -> Option<Root<GamepadButton>> {
self.Item(index)
}
}

View file

@ -0,0 +1,92 @@
/* 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 dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods;
use dom::bindings::codegen::Bindings::GamepadEventBinding;
use dom::bindings::codegen::Bindings::GamepadEventBinding::GamepadEventMethods;
use dom::bindings::error::Fallible;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::event::Event;
use dom::gamepad::Gamepad;
use dom::globalscope::GlobalScope;
use dom::window::Window;
use dom_struct::dom_struct;
use servo_atoms::Atom;
#[dom_struct]
pub struct GamepadEvent {
event: Event,
gamepad: JS<Gamepad>,
}
pub enum GamepadEventType {
Connected,
Disconnected
}
impl GamepadEvent {
fn new_inherited(gamepad: &Gamepad) -> GamepadEvent {
GamepadEvent {
event: Event::new_inherited(),
gamepad: JS::from_ref(gamepad),
}
}
pub fn new(global: &GlobalScope,
type_: Atom,
bubbles: bool,
cancelable: bool,
gamepad: &Gamepad)
-> Root<GamepadEvent> {
let ev = reflect_dom_object(box GamepadEvent::new_inherited(&gamepad),
global,
GamepadEventBinding::Wrap);
{
let event = ev.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
ev
}
pub fn new_with_type(global: &GlobalScope, event_type: GamepadEventType, gamepad: &Gamepad)
-> Root<GamepadEvent> {
let name = match event_type {
GamepadEventType::Connected => "gamepadconnected",
GamepadEventType::Disconnected => "gamepaddisconnected"
};
GamepadEvent::new(&global,
name.into(),
false,
false,
&gamepad)
}
// https://w3c.github.io/gamepad/#gamepadevent-interface
pub fn Constructor(window: &Window,
type_: DOMString,
init: &GamepadEventBinding::GamepadEventInit)
-> Fallible<Root<GamepadEvent>> {
Ok(GamepadEvent::new(&window.global(),
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.gamepad))
}
}
impl GamepadEventMethods for GamepadEvent {
// https://w3c.github.io/gamepad/#gamepadevent-interface
fn Gamepad(&self) -> Root<Gamepad> {
Root::from_ref(&*self.gamepad)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,61 @@
/* 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 dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::GamepadListBinding;
use dom::bindings::codegen::Bindings::GamepadListBinding::GamepadListMethods;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::gamepad::Gamepad;
use dom::globalscope::GlobalScope;
use dom_struct::dom_struct;
// https://www.w3.org/TR/gamepad/
#[dom_struct]
pub struct GamepadList {
reflector_: Reflector,
list: DOMRefCell<Vec<JS<Gamepad>>>
}
impl GamepadList {
fn new_inherited(list: &[&Gamepad]) -> GamepadList {
GamepadList {
reflector_: Reflector::new(),
list: DOMRefCell::new(list.iter().map(|g| JS::from_ref(&**g)).collect())
}
}
pub fn new(global: &GlobalScope, list: &[&Gamepad]) -> Root<GamepadList> {
reflect_dom_object(box GamepadList::new_inherited(list),
global,
GamepadListBinding::Wrap)
}
pub fn add_if_not_exists(&self, gamepads: &[Root<Gamepad>]) {
for gamepad in gamepads {
if !self.list.borrow().iter().any(|g| g.gamepad_id() == gamepad.gamepad_id()) {
self.list.borrow_mut().push(JS::from_ref(&*gamepad));
// Ensure that the gamepad has the correct index
gamepad.update_index(self.list.borrow().len() as i32 - 1);
}
}
}
}
impl GamepadListMethods for GamepadList {
// https://w3c.github.io/gamepad/#dom-navigator-getgamepads
fn Length(&self) -> u32 {
self.list.borrow().len() as u32
}
// https://w3c.github.io/gamepad/#dom-navigator-getgamepads
fn Item(&self, index: u32) -> Option<Root<Gamepad>> {
self.list.borrow().get(index as usize).map(|gamepad| Root::from_ref(&**gamepad))
}
// https://w3c.github.io/gamepad/#dom-navigator-getgamepads
fn IndexedGetter(&self, index: u32) -> Option<Root<Gamepad>> {
self.Item(index)
}
}

View file

@ -290,6 +290,11 @@ pub mod filereadersync;
pub mod focusevent;
pub mod forcetouchevent;
pub mod formdata;
pub mod gamepad;
pub mod gamepadbutton;
pub mod gamepadbuttonlist;
pub mod gamepadevent;
pub mod gamepadlist;
pub mod globalscope;
pub mod hashchangeevent;
pub mod headers;

View file

@ -8,6 +8,7 @@ use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::reflector::{Reflector, DomObject, reflect_dom_object};
use dom::bindings::str::DOMString;
use dom::bluetooth::Bluetooth;
use dom::gamepadlist::GamepadList;
use dom::mimetypearray::MimeTypeArray;
use dom::navigatorinfo;
use dom::permissions::Permissions;
@ -16,7 +17,6 @@ use dom::serviceworkercontainer::ServiceWorkerContainer;
use dom::vr::VR;
use dom::window::Window;
use dom_struct::dom_struct;
use script_traits::WebVREventMsg;
#[dom_struct]
pub struct Navigator {
@ -26,6 +26,7 @@ pub struct Navigator {
mime_types: MutNullableJS<MimeTypeArray>,
service_worker: MutNullableJS<ServiceWorkerContainer>,
vr: MutNullableJS<VR>,
gamepads: MutNullableJS<GamepadList>,
permissions: MutNullableJS<Permissions>,
}
@ -38,6 +39,7 @@ impl Navigator {
mime_types: Default::default(),
service_worker: Default::default(),
vr: Default::default(),
gamepads: Default::default(),
permissions: Default::default(),
}
}
@ -128,15 +130,19 @@ impl NavigatorMethods for Navigator {
self.vr.or_init(|| VR::new(&self.global()))
}
// https://www.w3.org/TR/gamepad/#navigator-interface-extension
fn GetGamepads(&self) -> Root<GamepadList> {
let root = self.gamepads.or_init(|| {
GamepadList::new(&self.global(), &[])
});
let vr_gamepads = self.Vr().get_gamepads();
root.add_if_not_exists(&vr_gamepads);
// TODO: Add not VR related gamepads
root
}
// https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
fn Permissions(&self) -> Root<Permissions> {
self.permissions.or_init(|| Permissions::new(&self.global()))
}
}
impl Navigator {
pub fn handle_webvr_event(&self, event: WebVREventMsg) {
self.vr.get().expect("Shouldn't arrive here with an empty VR instance")
.handle_webvr_event(event);
}
}

View file

@ -5,12 +5,15 @@
use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::VRBinding;
use dom::bindings::codegen::Bindings::VRBinding::VRMethods;
use dom::bindings::codegen::Bindings::VRDisplayBinding::VRDisplayMethods;
use dom::bindings::error::Error;
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{JS, Root};
use dom::bindings::reflector::{DomObject, reflect_dom_object};
use dom::event::Event;
use dom::eventtarget::EventTarget;
use dom::gamepad::Gamepad;
use dom::gamepadevent::GamepadEventType;
use dom::globalscope::GlobalScope;
use dom::promise::Promise;
use dom::vrdisplay::VRDisplay;
@ -18,22 +21,23 @@ use dom::vrdisplayevent::VRDisplayEvent;
use dom_struct::dom_struct;
use ipc_channel::ipc;
use ipc_channel::ipc::IpcSender;
use script_traits::WebVREventMsg;
use std::rc::Rc;
use webvr_traits::WebVRMsg;
use webvr_traits::webvr;
use webvr_traits::{WebVRDisplayData, WebVRDisplayEvent, WebVREvent, WebVRMsg};
use webvr_traits::{WebVRGamepadData, WebVRGamepadEvent, WebVRGamepadState};
#[dom_struct]
pub struct VR {
eventtarget: EventTarget,
displays: DOMRefCell<Vec<JS<VRDisplay>>>
displays: DOMRefCell<Vec<JS<VRDisplay>>>,
gamepads: DOMRefCell<Vec<JS<Gamepad>>>
}
impl VR {
fn new_inherited() -> VR {
VR {
eventtarget: EventTarget::new_inherited(),
displays: DOMRefCell::new(Vec::new())
displays: DOMRefCell::new(Vec::new()),
gamepads: DOMRefCell::new(Vec::new()),
}
}
@ -95,10 +99,10 @@ impl VR {
self.global().as_window().webvr_thread()
}
fn find_display(&self, display_id: u64) -> Option<Root<VRDisplay>> {
fn find_display(&self, display_id: u32) -> Option<Root<VRDisplay>> {
self.displays.borrow()
.iter()
.find(|d| d.get_display_id() == display_id)
.find(|d| d.DisplayId() == display_id)
.map(|d| Root::from_ref(&**d))
}
@ -116,7 +120,7 @@ impl VR {
}
}
fn sync_display(&self, display: &webvr::VRDisplayData) -> Root<VRDisplay> {
fn sync_display(&self, display: &WebVRDisplayData) -> Root<VRDisplay> {
if let Some(existing) = self.find_display(display.display_id) {
existing.update_display(&display);
existing
@ -127,35 +131,121 @@ impl VR {
}
}
pub fn handle_webvr_event(&self, event: WebVREventMsg) {
let WebVREventMsg::DisplayEvent(event) = event;
match &event {
&webvr::VRDisplayEvent::Connect(ref display) => {
fn handle_display_event(&self, event: WebVRDisplayEvent) {
match event {
WebVRDisplayEvent::Connect(ref display) => {
let display = self.sync_display(&display);
display.handle_webvr_event(&event);
self.notify_event(&display, &event);
self.notify_display_event(&display, &event);
},
&webvr::VRDisplayEvent::Disconnect(id) => {
WebVRDisplayEvent::Disconnect(id) => {
if let Some(display) = self.find_display(id) {
display.handle_webvr_event(&event);
self.notify_event(&display, &event);
self.notify_display_event(&display, &event);
}
},
&webvr::VRDisplayEvent::Activate(ref display, _) |
&webvr::VRDisplayEvent::Deactivate(ref display, _) |
&webvr::VRDisplayEvent::Blur(ref display) |
&webvr::VRDisplayEvent::Focus(ref display) |
&webvr::VRDisplayEvent::PresentChange(ref display, _) |
&webvr::VRDisplayEvent::Change(ref display) => {
WebVRDisplayEvent::Activate(ref display, _) |
WebVRDisplayEvent::Deactivate(ref display, _) |
WebVRDisplayEvent::Blur(ref display) |
WebVRDisplayEvent::Focus(ref display) |
WebVRDisplayEvent::PresentChange(ref display, _) |
WebVRDisplayEvent::Change(ref display) => {
let display = self.sync_display(&display);
display.handle_webvr_event(&event);
}
};
}
fn notify_event(&self, display: &VRDisplay, event: &webvr::VRDisplayEvent) {
fn handle_gamepad_event(&self, event: WebVRGamepadEvent) {
match event {
WebVRGamepadEvent::Connect(data, state) => {
if let Some(gamepad) = self.find_gamepad(state.gamepad_id) {
gamepad.update_from_vr(&state);
} else {
// new gamepad
self.sync_gamepad(Some(data), &state);
}
},
WebVRGamepadEvent::Disconnect(id) => {
if let Some(gamepad) = self.find_gamepad(id) {
gamepad.update_connected(false);
}
}
};
}
pub fn handle_webvr_event(&self, event: WebVREvent) {
match event {
WebVREvent::Display(event) => {
self.handle_display_event(event);
},
WebVREvent::Gamepad(event) => {
self.handle_gamepad_event(event);
}
};
}
pub fn handle_webvr_events(&self, events: Vec<WebVREvent>) {
for event in events {
self.handle_webvr_event(event);
}
}
fn notify_display_event(&self, display: &VRDisplay, event: &WebVRDisplayEvent) {
let event = VRDisplayEvent::new_from_webvr(&self.global(), &display, &event);
event.upcast::<Event>().fire(self.upcast());
}
}
// Gamepad
impl VR {
fn find_gamepad(&self, gamepad_id: u32) -> Option<Root<Gamepad>> {
self.gamepads.borrow()
.iter()
.find(|g| g.gamepad_id() == gamepad_id)
.map(|g| Root::from_ref(&**g))
}
fn sync_gamepad(&self, data: Option<WebVRGamepadData>, state: &WebVRGamepadState) {
if let Some(existing) = self.find_gamepad(state.gamepad_id) {
existing.update_from_vr(&state);
} else {
let index = self.gamepads.borrow().len();
let data = data.unwrap_or_default();
let root = Gamepad::new_from_vr(&self.global(),
index as i32,
&data,
&state);
self.gamepads.borrow_mut().push(JS::from_ref(&*root));
if state.connected {
root.notify_event(GamepadEventType::Connected);
}
}
}
// Gamepads are synced immediately in response to the API call.
// The current approach allows the to sample gamepad state multiple times per frame. This
// guarantees that the gamepads always have a valid state and can be very useful for
// motion capture or drawing applications.
pub fn get_gamepads(&self) -> Vec<Root<Gamepad>> {
if let Some(wevbr_sender) = self.webvr_thread() {
let (sender, receiver) = ipc::channel().unwrap();
let synced_ids = self.gamepads.borrow().iter().map(|g| g.gamepad_id()).collect();
wevbr_sender.send(WebVRMsg::GetGamepads(synced_ids, sender)).unwrap();
match receiver.recv().unwrap() {
Ok(gamepads) => {
// Sync displays
for gamepad in gamepads {
self.sync_gamepad(gamepad.0, &gamepad.1);
}
},
Err(_) => {}
}
}
// We can add other not VR related gamepad providers here
self.gamepads.borrow().iter()
.map(|g| Root::from_ref(&**g))
.collect()
}
}

View file

@ -161,7 +161,7 @@ impl VRDisplayMethods for VRDisplay {
// https://w3c.github.io/webvr/#dom-vrdisplay-displayid
fn DisplayId(&self) -> u32 {
self.display.borrow().display_id as u32
self.display.borrow().display_id
}
// https://w3c.github.io/webvr/#dom-vrdisplay-displayname
@ -188,7 +188,7 @@ impl VRDisplayMethods for VRDisplay {
// If not presenting we fetch inmediante VRFrameData
let (sender, receiver) = ipc::channel().unwrap();
self.webvr_thread().send(WebVRMsg::GetFrameData(self.global().pipeline_id(),
self.get_display_id(),
self.DisplayId(),
self.depth_near.get(),
self.depth_far.get(),
sender)).unwrap();
@ -213,7 +213,7 @@ impl VRDisplayMethods for VRDisplay {
fn ResetPose(&self) {
let (sender, receiver) = ipc::channel().unwrap();
self.webvr_thread().send(WebVRMsg::ResetPose(self.global().pipeline_id(),
self.get_display_id(),
self.DisplayId(),
sender)).unwrap();
if let Ok(data) = receiver.recv().unwrap() {
// Some VRDisplay data might change after calling ResetPose()
@ -378,7 +378,7 @@ impl VRDisplayMethods for VRDisplay {
}
let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
let display_id = self.display.borrow().display_id;
let display_id = self.display.borrow().display_id as u64;
let layer = self.layer.borrow();
let msg = VRCompositorCommand::SubmitFrame(display_id, layer.left_bounds, layer.right_bounds);
api_sender.send(CanvasMsg::WebVR(msg)).unwrap();
@ -390,10 +390,6 @@ impl VRDisplay {
self.global().as_window().webvr_thread().expect("Shouldn't arrive here with WebVR disabled")
}
pub fn get_display_id(&self) -> u64 {
self.display.borrow().display_id
}
pub fn update_display(&self, display: &WebVRDisplayData) {
*self.display.borrow_mut() = display.clone();
if let Some(ref stage) = display.stage_parameters {
@ -447,7 +443,7 @@ impl VRDisplay {
let (sync_sender, sync_receiver) = ipc::channel().unwrap();
*self.frame_data_receiver.borrow_mut() = Some(sync_receiver);
let display_id = self.display.borrow().display_id;
let display_id = self.display.borrow().display_id as u64;
let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
let js_sender = self.global().script_chan();
let address = Trusted::new(&*self);
@ -497,7 +493,7 @@ impl VRDisplay {
*self.frame_data_receiver.borrow_mut() = None;
let api_sender = self.layer_ctx.get().unwrap().ipc_renderer();
let display_id = self.display.borrow().display_id;
let display_id = self.display.borrow().display_id as u64;
let msg = VRCompositorCommand::Release(display_id);
api_sender.send(CanvasMsg::WebVR(msg)).unwrap();
}

View file

@ -32,7 +32,9 @@ unsafe fn update_or_create_typed_array(cx: *mut JSContext,
match src {
Some(data) => {
if dst.get().is_null() {
let _ = Float32Array::create(cx, CreateWith::Slice(data), dst.handle_mut());
rooted!(in (cx) let mut array = ptr::null_mut());
let _ = Float32Array::create(cx, CreateWith::Slice(data), array.handle_mut());
(*dst).set(array.get());
} else {
typedarray!(in(cx) let array: Float32Array = dst.get());
if let Ok(mut array) = array {

View file

@ -0,0 +1,26 @@
/* 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/. */
// https://w3c.github.io/gamepad/#gamepad-interface
[Pref="dom.gamepad.enabled"]
interface Gamepad {
readonly attribute DOMString id;
readonly attribute long index;
readonly attribute boolean connected;
readonly attribute DOMHighResTimeStamp timestamp;
readonly attribute DOMString mapping;
readonly attribute Float64Array axes;
[SameObject] readonly attribute GamepadButtonList buttons;
};
// https://w3c.github.io/gamepad/extensions.html#dom-gamepad
partial interface Gamepad {
readonly attribute DOMString hand;
readonly attribute VRPose? pose;
};
// https://w3c.github.io/webvr/spec/1.1/#interface-gamepad
partial interface Gamepad {
readonly attribute unsigned long displayId;
};

View file

@ -0,0 +1,11 @@
/* 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/. */
// https://w3c.github.io/gamepad/#gamepadbutton-interface
[Pref="dom.gamepad.enabled"]
interface GamepadButton {
readonly attribute boolean pressed;
readonly attribute boolean touched;
readonly attribute double value;
};

View file

@ -0,0 +1,10 @@
/* 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/. */
// https://w3c.github.io/gamepad/#dom-gamepad-buttons
[Pref="dom.gamepad.enabled"]
interface GamepadButtonList {
getter GamepadButton? item(unsigned long index);
readonly attribute unsigned long length;
};

View file

@ -0,0 +1,13 @@
/* 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/. */
// https://w3c.github.io/gamepad/#gamepadevent-interface
[Pref="dom.gamepad.enabled", Constructor(DOMString type, GamepadEventInit eventInitDict)]
interface GamepadEvent : Event {
readonly attribute Gamepad gamepad;
};
dictionary GamepadEventInit : EventInit {
required Gamepad gamepad;
};

View file

@ -0,0 +1,10 @@
/* 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/. */
// https://w3c.github.io/gamepad/#navigator-interface-extension
[Pref="dom.gamepad.enabled"]
interface GamepadList {
getter Gamepad? item(unsigned long index);
readonly attribute unsigned long length;
};

View file

@ -68,3 +68,8 @@ partial interface Navigator {
partial interface Navigator {
[Pref="dom.permissions.enabled"] readonly attribute Permissions permissions;
};
// https://w3c.github.io/gamepad/#navigator-interface-extension
partial interface Navigator {
[Pref="dom.gamepad.enabled"] GamepadList getGamepads();
};

View file

@ -27,6 +27,7 @@ use dom::bindings::cell::DOMRefCell;
use dom::bindings::codegen::Bindings::CSSStyleDeclarationBinding::CSSStyleDeclarationMethods;
use dom::bindings::codegen::Bindings::DocumentBinding::{DocumentMethods, DocumentReadyState};
use dom::bindings::codegen::Bindings::EventBinding::EventInit;
use dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
use dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::conversions::{ConversionResult, FromJSValConvertible, StringificationBehavior};
@ -91,7 +92,6 @@ use script_traits::{ScriptThreadFactory, TimerEvent, TimerSchedulerMsg, TimerSou
use script_traits::{TouchEventType, TouchId, UntrustedNodeAddress, WindowSizeData, WindowSizeType};
use script_traits::CompositorEvent::{KeyEvent, MouseButtonEvent, MouseMoveEvent, ResizeEvent};
use script_traits::CompositorEvent::{TouchEvent, TouchpadPressureEvent};
use script_traits::WebVREventMsg;
use script_traits::webdriver_msg::WebDriverScriptCommand;
use serviceworkerjob::{Job, JobQueue, AsyncJobHandler};
use servo_config::opts;
@ -119,7 +119,7 @@ use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSour
use time::Tm;
use url::Position;
use webdriver_handlers;
use webvr_traits::WebVRMsg;
use webvr_traits::{WebVREvent, WebVRMsg};
pub type ImageCacheMsg = (PipelineId, PendingImageResponse);
@ -1070,8 +1070,8 @@ impl ScriptThread {
self.handle_reload(pipeline_id),
ConstellationControlMsg::ExitPipeline(pipeline_id, discard_browsing_context) =>
self.handle_exit_pipeline_msg(pipeline_id, discard_browsing_context),
ConstellationControlMsg::WebVREvent(pipeline_id, event) =>
self.handle_webvr_event(pipeline_id, event),
ConstellationControlMsg::WebVREvents(pipeline_id, events) =>
self.handle_webvr_events(pipeline_id, events),
msg @ ConstellationControlMsg::AttachLayout(..) |
msg @ ConstellationControlMsg::Viewport(..) |
msg @ ConstellationControlMsg::SetScrollState(..) |
@ -2186,11 +2186,11 @@ impl ScriptThread {
}
}
fn handle_webvr_event(&self, pipeline_id: PipelineId, event: WebVREventMsg) {
fn handle_webvr_events(&self, pipeline_id: PipelineId, events: Vec<WebVREvent>) {
let window = self.documents.borrow().find_window(pipeline_id);
if let Some(window) = window {
let navigator = window.Navigator();
navigator.handle_webvr_event(event);
let vr = window.Navigator().Vr();
vr.handle_webvr_events(events);
}
}

View file

@ -71,7 +71,7 @@ use std::sync::Arc;
use std::sync::mpsc::{Receiver, Sender};
use style_traits::{CSSPixel, UnsafeNode};
use webdriver_msg::{LoadStatus, WebDriverScriptCommand};
use webvr_traits::{WebVRDisplayEvent, WebVRMsg};
use webvr_traits::{WebVREvent, WebVRMsg};
pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry};
pub use script_msg::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders, DOMMessage};
@ -280,8 +280,8 @@ pub enum ConstellationControlMsg {
ReportCSSError(PipelineId, String, usize, usize, String),
/// Reload the given page.
Reload(PipelineId),
/// Notifies the script thread of a WebVR device event
WebVREvent(PipelineId, WebVREventMsg)
/// Notifies the script thread of WebVR events.
WebVREvents(PipelineId, Vec<WebVREvent>)
}
impl fmt::Debug for ConstellationControlMsg {
@ -314,7 +314,7 @@ impl fmt::Debug for ConstellationControlMsg {
FramedContentChanged(..) => "FramedContentChanged",
ReportCSSError(..) => "ReportCSSError",
Reload(..) => "Reload",
WebVREvent(..) => "WebVREvent",
WebVREvents(..) => "WebVREvents",
};
write!(formatter, "ConstellationMsg::{}", variant)
}
@ -751,16 +751,8 @@ pub enum ConstellationMsg {
LogEntry(Option<FrameId>, Option<String>, LogEntry),
/// Set the WebVR thread channel.
SetWebVRThread(IpcSender<WebVRMsg>),
/// Dispatch a WebVR event to the subscribed script threads.
WebVREvent(Vec<PipelineId>, WebVREventMsg),
}
/// Messages to the constellation originating from the WebVR thread.
/// Used to dispatch VR Headset state events: connected, unconnected, and more.
#[derive(Deserialize, Serialize, Clone)]
pub enum WebVREventMsg {
/// Inform the constellation of a VR display event.
DisplayEvent(WebVRDisplayEvent)
/// Dispatch WebVR events to the subscribed script threads.
WebVREvents(Vec<PipelineId>, Vec<WebVREvent>),
}
/// Resources required by workerglobalscopes

View file

@ -5,7 +5,7 @@
use ipc_channel::ipc;
use ipc_channel::ipc::{IpcReceiver, IpcSender};
use msg::constellation_msg::PipelineId;
use script_traits::{ConstellationMsg, WebVREventMsg};
use script_traits::ConstellationMsg;
use servo_config::prefs::PREFS;
use std::{thread, time};
use std::collections::{HashMap, HashSet};
@ -42,7 +42,7 @@ pub struct WebVRThread {
constellation_chan: Sender<ConstellationMsg>,
vr_compositor_chan: WebVRCompositorSender,
polling_events: bool,
presenting: HashMap<u64, PipelineId>
presenting: HashMap<u32, PipelineId>
}
impl WebVRThread {
@ -108,6 +108,9 @@ impl WebVRThread {
WebVRMsg::CreateCompositor(display_id) => {
self.handle_create_compositor(display_id);
},
WebVRMsg::GetGamepads(synced_ids, sender) => {
self.handle_get_gamepads(synced_ids, sender);
}
WebVRMsg::Exit => {
break
},
@ -134,7 +137,7 @@ impl WebVRThread {
fn handle_framedata(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
near: f64,
far: f64,
sender: IpcSender<WebVRResult<VRFrameData>>) {
@ -148,7 +151,7 @@ impl WebVRThread {
fn handle_reset_pose(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
sender: IpcSender<WebVRResult<VRDisplayData>>) {
match self.access_check(pipeline, display_id) {
Ok(display) => {
@ -166,7 +169,7 @@ impl WebVRThread {
// while the user is having a VR experience in the current tab.
// These security rules also avoid multithreading race conditions between WebVRThread and
// Webrender thread. See WebVRCompositorHandler implementation notes for more details about this.
fn access_check(&self, pipeline: PipelineId, display_id: u64) -> Result<&VRDisplayPtr, &'static str> {
fn access_check(&self, pipeline: PipelineId, display_id: u32) -> Result<&VRDisplayPtr, &'static str> {
if *self.presenting.get(&display_id).unwrap_or(&pipeline) != pipeline {
return Err("No access granted to this Display because it's presenting on other JavaScript Tab");
}
@ -175,14 +178,14 @@ impl WebVRThread {
fn handle_request_present(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
sender: IpcSender<WebVRResult<()>>) {
match self.access_check(pipeline, display_id).map(|d| d.clone()) {
Ok(display) => {
self.presenting.insert(display_id, pipeline);
let data = display.borrow().data();
sender.send(Ok(())).unwrap();
self.notify_event(VRDisplayEvent::PresentChange(data, true));
self.notify_event(VRDisplayEvent::PresentChange(data, true).into());
},
Err(msg) => {
sender.send(Err(msg.into())).unwrap();
@ -192,7 +195,7 @@ impl WebVRThread {
fn handle_exit_present(&mut self,
pipeline: PipelineId,
display_id: u64,
display_id: u32,
sender: Option<IpcSender<WebVRResult<()>>>) {
match self.access_check(pipeline, display_id).map(|d| d.clone()) {
Ok(display) => {
@ -201,7 +204,7 @@ impl WebVRThread {
sender.send(Ok(())).unwrap();
}
let data = display.borrow().data();
self.notify_event(VRDisplayEvent::PresentChange(data, false));
self.notify_event(VRDisplayEvent::PresentChange(data, false).into());
},
Err(msg) => {
if let Some(sender) = sender {
@ -211,11 +214,28 @@ impl WebVRThread {
}
}
fn handle_create_compositor(&mut self, display_id: u64) {
fn handle_create_compositor(&mut self, display_id: u32) {
let compositor = self.service.get_display(display_id).map(|d| WebVRCompositor(d.as_ptr()));
self.vr_compositor_chan.send(compositor).unwrap();
}
fn handle_get_gamepads(&mut self,
synced_ids: Vec<u32>,
sender: IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>) {
let gamepads = self.service.get_gamepads();
let data = gamepads.iter().map(|g| {
let g = g.borrow();
// Optimization, don't fetch and send gamepad static data when the gamepad is already synced.
let data = if synced_ids.iter().any(|v| *v == g.id()) {
None
} else {
Some(g.data())
};
(data, g.state())
}).collect();
sender.send(Ok(data)).unwrap();
}
fn poll_events(&mut self, sender: IpcSender<bool>) {
loop {
let events = self.service.poll_events();
@ -230,16 +250,13 @@ impl WebVRThread {
sender.send(self.polling_events).unwrap();
}
fn notify_events(&self, events: Vec<VRDisplayEvent>) {
fn notify_events(&self, events: Vec<VREvent>) {
let pipeline_ids: Vec<PipelineId> = self.contexts.iter().map(|c| *c).collect();
for event in events {
let event = WebVREventMsg::DisplayEvent(event);
self.constellation_chan.send(ConstellationMsg::WebVREvent(pipeline_ids.clone(), event)).unwrap();
}
self.constellation_chan.send(ConstellationMsg::WebVREvents(pipeline_ids.clone(), events)).unwrap();
}
#[inline]
fn notify_event(&self, event: VRDisplayEvent) {
fn notify_event(&self, event: VREvent) {
self.notify_events(vec![event]);
}
@ -334,7 +351,8 @@ impl webrender_traits::VRCompositorHandler for WebVRCompositorHandler {
let layer = VRLayer {
texture_id: texture_id,
left_bounds: left_bounds,
right_bounds: right_bounds
right_bounds: right_bounds,
texture_size: None
};
unsafe {
(*compositor.0).submit_frame(&layer);
@ -357,7 +375,7 @@ impl WebVRCompositorHandler {
None => return,
};
sender.send(WebVRMsg::CreateCompositor(display_id)).unwrap();
sender.send(WebVRMsg::CreateCompositor(display_id as u32)).unwrap();
let display = self.webvr_thread_receiver.recv().unwrap();
match display {

View file

@ -12,6 +12,6 @@ path = "lib.rs"
[dependencies]
ipc-channel = "0.7"
msg = {path = "../msg"}
rust-webvr = {version = "0.2", features = ["serde-serialization"]}
rust-webvr = {version = "0.3", features = ["serde-serialization"]}
serde = "0.9"
serde_derive = "0.9"

View file

@ -16,9 +16,15 @@ pub use webvr::VRDisplayData as WebVRDisplayData;
pub use webvr::VRDisplayCapabilities as WebVRDisplayCapabilities;
pub use webvr::VRDisplayEvent as WebVRDisplayEvent;
pub use webvr::VRDisplayEventReason as WebVRDisplayEventReason;
pub use webvr::VREvent as WebVREvent;
pub use webvr::VREye as WebVREye;
pub use webvr::VREyeParameters as WebVREyeParameters;
pub use webvr::VRFieldOfView as WebVRFieldOfView;
pub use webvr::VRGamepadButton as WebVRGamepadButton;
pub use webvr::VRGamepadData as WebVRGamepadData;
pub use webvr::VRGamepadEvent as WebVRGamepadEvent;
pub use webvr::VRGamepadHand as WebVRGamepadHand;
pub use webvr::VRGamepadState as WebVRGamepadState;
pub use webvr::VRFrameData as WebVRFrameData;
pub use webvr::VRLayer as WebVRLayer;
pub use webvr::VRPose as WebVRPose;

View file

@ -15,10 +15,11 @@ pub enum WebVRMsg {
UnregisterContext(PipelineId),
PollEvents(IpcSender<bool>),
GetDisplays(IpcSender<WebVRResult<Vec<VRDisplayData>>>),
GetFrameData(PipelineId, u64, f64, f64, IpcSender<WebVRResult<VRFrameData>>),
ResetPose(PipelineId, u64, IpcSender<WebVRResult<VRDisplayData>>),
RequestPresent(PipelineId, u64, IpcSender<WebVRResult<()>>),
ExitPresent(PipelineId, u64, Option<IpcSender<WebVRResult<()>>>),
CreateCompositor(u64),
GetFrameData(PipelineId, u32, f64, f64, IpcSender<WebVRResult<VRFrameData>>),
ResetPose(PipelineId, u32, IpcSender<WebVRResult<VRDisplayData>>),
RequestPresent(PipelineId, u32, IpcSender<WebVRResult<()>>),
ExitPresent(PipelineId, u32, Option<IpcSender<WebVRResult<()>>>),
CreateCompositor(u32),
GetGamepads(Vec<u32>, IpcSender<WebVRResult<Vec<(Option<VRGamepadData>, VRGamepadState)>>>),
Exit,
}