script/dom/webxr/
xrtest.rs

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 https://mozilla.org/MPL/2.0/. */
4
5/* This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
8
9use std::rc::Rc;
10
11use base::generic_channel::GenericSender;
12use dom_struct::dom_struct;
13use js::jsval::JSVal;
14use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
15use webxr_api::{self, Error as XRError, MockDeviceInit, MockDeviceMsg};
16
17use crate::ScriptThread;
18use crate::dom::bindings::callback::ExceptionHandling;
19use crate::dom::bindings::cell::DomRefCell;
20use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
21use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode;
22use crate::dom::bindings::codegen::Bindings::XRTestBinding::{FakeXRDeviceInit, XRTestMethods};
23use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
24use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::fakexrdevice::{FakeXRDevice, get_origin, get_views, get_world};
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::promise::Promise;
29use crate::script_runtime::CanGc;
30
31#[dom_struct]
32pub(crate) struct XRTest {
33    reflector: Reflector,
34    devices_connected: DomRefCell<Vec<Dom<FakeXRDevice>>>,
35}
36
37impl XRTest {
38    pub(crate) fn new_inherited() -> XRTest {
39        XRTest {
40            reflector: Reflector::new(),
41            devices_connected: DomRefCell::new(vec![]),
42        }
43    }
44
45    pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<XRTest> {
46        reflect_dom_object(Box::new(XRTest::new_inherited()), global, can_gc)
47    }
48
49    fn device_obtained(
50        &self,
51        response: Result<GenericSender<MockDeviceMsg>, XRError>,
52        trusted: TrustedPromise,
53        can_gc: CanGc,
54    ) {
55        let promise = trusted.root();
56        if let Ok(sender) = response {
57            let device = FakeXRDevice::new(&self.global(), sender, CanGc::note());
58            self.devices_connected
59                .borrow_mut()
60                .push(Dom::from_ref(&device));
61            promise.resolve_native(&device, can_gc);
62        } else {
63            promise.reject_native(&(), can_gc);
64        }
65    }
66}
67
68impl XRTestMethods<crate::DomTypeHolder> for XRTest {
69    /// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
70    fn SimulateDeviceConnection(&self, init: &FakeXRDeviceInit, can_gc: CanGc) -> Rc<Promise> {
71        let global = self.global();
72        let p = Promise::new(&global, can_gc);
73
74        let origin = if let Some(ref o) = init.viewerOrigin {
75            match get_origin(o) {
76                Ok(origin) => Some(origin),
77                Err(e) => {
78                    p.reject_error(e, can_gc);
79                    return p;
80                },
81            }
82        } else {
83            None
84        };
85
86        let floor_origin = if let Some(ref o) = init.floorOrigin {
87            match get_origin(o) {
88                Ok(origin) => Some(origin),
89                Err(e) => {
90                    p.reject_error(e, can_gc);
91                    return p;
92                },
93            }
94        } else {
95            None
96        };
97
98        let views = match get_views(&init.views) {
99            Ok(views) => views,
100            Err(e) => {
101                p.reject_error(e, can_gc);
102                return p;
103            },
104        };
105
106        let supported_features = if let Some(ref s) = init.supportedFeatures {
107            s.iter().cloned().map(String::from).collect()
108        } else {
109            vec![]
110        };
111
112        let world = if let Some(ref w) = init.world {
113            let w = match get_world(w) {
114                Ok(w) => w,
115                Err(e) => {
116                    p.reject_error(e, can_gc);
117                    return p;
118                },
119            };
120            Some(w)
121        } else {
122            None
123        };
124
125        let (mut supports_inline, mut supports_vr, mut supports_ar) = (false, false, false);
126
127        if let Some(ref modes) = init.supportedModes {
128            for mode in modes {
129                match mode {
130                    XRSessionMode::Immersive_vr => supports_vr = true,
131                    XRSessionMode::Immersive_ar => supports_ar = true,
132                    XRSessionMode::Inline => supports_inline = true,
133                }
134            }
135        }
136
137        let init = MockDeviceInit {
138            viewer_origin: origin,
139            views,
140            supports_inline,
141            supports_vr,
142            supports_ar,
143            floor_origin,
144            supported_features,
145            world,
146        };
147
148        let global = self.global();
149        let this = Trusted::new(self);
150        let mut trusted = Some(TrustedPromise::new(p.clone()));
151
152        let task_source = global
153            .task_manager()
154            .dom_manipulation_task_source()
155            .to_sendable();
156
157        let callback =
158            ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |message| {
159                let trusted = trusted
160                    .take()
161                    .expect("SimulateDeviceConnection callback called twice");
162                let this = this.clone();
163                let message =
164                    message.expect("SimulateDeviceConnection callback given incorrect payload");
165
166                task_source.queue(task!(request_session: move || {
167                    this.root().device_obtained(message, trusted, CanGc::note());
168                }));
169            })
170            .expect("Could not create callback");
171        if let Some(mut r) = global.as_window().webxr_registry() {
172            r.simulate_device_connection(init, callback);
173        }
174
175        p
176    }
177
178    /// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
179    fn SimulateUserActivation(&self, f: Rc<Function>, can_gc: CanGc) {
180        let _guard = ScriptThread::user_interacting_guard();
181        rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
182        let _ = f.Call__(
183            vec![],
184            value.handle_mut(),
185            ExceptionHandling::Rethrow,
186            can_gc,
187        );
188    }
189
190    /// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
191    fn DisconnectAllDevices(&self, can_gc: CanGc) -> Rc<Promise> {
192        // XXXManishearth implement device disconnection and session ending
193        let global = self.global();
194        let p = Promise::new(&global, can_gc);
195        let mut devices = self.devices_connected.borrow_mut();
196        if devices.is_empty() {
197            p.resolve_native(&(), can_gc);
198        } else {
199            let mut len = devices.len();
200
201            let mut rooted_devices: Vec<_> =
202                devices.iter().map(|x| DomRoot::from_ref(&**x)).collect();
203            devices.clear();
204
205            let mut trusted = Some(TrustedPromise::new(p.clone()));
206            let task_source = global
207                .task_manager()
208                .dom_manipulation_task_source()
209                .to_sendable();
210
211            let callback =
212                ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |_| {
213                    len -= 1;
214                    if len == 0 {
215                        let trusted = trusted
216                            .take()
217                            .expect("DisconnectAllDevices disconnected more devices than expected");
218                        task_source.queue(trusted.resolve_task(()));
219                    }
220                })
221                .expect("Could not create callback");
222
223            for device in rooted_devices.drain(..) {
224                device.disconnect(callback.clone());
225            }
226        };
227        p
228    }
229}