1use std::cell::Cell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use euclid::{Point2D, Point3D, Rect, RigidTransform3D, Rotation3D, Size2D, Transform3D, Vector3D};
10use js::context::JSContext;
11use js::realm::CurrentRealm;
12use profile_traits::generic_callback::GenericCallback as ProfileGenericCallback;
13use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
14use servo_base::generic_channel::GenericSender;
15use webxr_api::{
16 EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion,
17 MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility,
18};
19
20use crate::conversions::Convert;
21use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
22use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{
23 FakeXRBoundsPoint, FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit,
24 FakeXRViewInit, FakeXRWorldInit,
25};
26use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit;
27use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
28 XRHandedness, XRTargetRayMode,
29};
30use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState;
31use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
32use crate::dom::bindings::error::{Error, Fallible};
33use crate::dom::bindings::refcounted::TrustedPromise;
34use crate::dom::bindings::reflector::DomGlobal;
35use crate::dom::bindings::root::DomRoot;
36use crate::dom::fakexrinputcontroller::{FakeXRInputController, init_to_mock_buttons};
37use crate::dom::globalscope::GlobalScope;
38use crate::dom::promise::Promise;
39
40#[dom_struct]
41pub(crate) struct FakeXRDevice {
42 reflector: Reflector,
43 #[no_trace]
44 sender: GenericSender<MockDeviceMsg>,
45 #[no_trace]
46 next_input_id: Cell<InputId>,
47}
48
49impl FakeXRDevice {
50 pub(crate) fn new_inherited(sender: GenericSender<MockDeviceMsg>) -> FakeXRDevice {
51 FakeXRDevice {
52 reflector: Reflector::new(),
53 sender,
54 next_input_id: Cell::new(InputId(0)),
55 }
56 }
57
58 pub(crate) fn new(
59 cx: &mut JSContext,
60 global: &GlobalScope,
61 sender: GenericSender<MockDeviceMsg>,
62 ) -> DomRoot<FakeXRDevice> {
63 reflect_dom_object_with_cx(Box::new(FakeXRDevice::new_inherited(sender)), global, cx)
64 }
65
66 pub(crate) fn disconnect(&self, sender: ProfileGenericCallback<()>) {
67 let _ = self.sender.send(MockDeviceMsg::Disconnect(sender));
68 }
69}
70
71pub(crate) fn view<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> {
72 if view.projectionMatrix.len() != 16 || view.viewOffset.position.len() != 3 {
73 return Err(Error::Type(c"Incorrectly sized array".into()));
74 }
75
76 let mut proj = [0.; 16];
77 let v: Vec<_> = view.projectionMatrix.iter().map(|x| **x).collect();
78 proj.copy_from_slice(&v);
79 let projection = Transform3D::from_array(proj);
80
81 let transform = get_origin(&view.viewOffset)?.inverse();
83
84 let size = Size2D::new(view.resolution.width, view.resolution.height);
85 let origin = match view.eye {
86 XREye::Right => Point2D::new(size.width, 0),
87 _ => Point2D::zero(),
88 };
89 let viewport = Rect::new(origin, size);
90
91 let fov = view.fieldOfView.as_ref().map(|fov| {
92 (
93 fov.leftDegrees.to_radians(),
94 fov.rightDegrees.to_radians(),
95 fov.upDegrees.to_radians(),
96 fov.downDegrees.to_radians(),
97 )
98 });
99
100 Ok(MockViewInit {
101 projection,
102 transform,
103 viewport,
104 fov,
105 })
106}
107
108pub(crate) fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> {
109 match views.len() {
110 1 => Ok(MockViewsInit::Mono(view(&views[0])?)),
111 2 => {
112 let (left, right) = match (views[0].eye, views[1].eye) {
113 (XREye::Left, XREye::Right) => (&views[0], &views[1]),
114 (XREye::Right, XREye::Left) => (&views[1], &views[0]),
115 _ => return Err(Error::NotSupported(None)),
116 };
117 Ok(MockViewsInit::Stereo(view(left)?, view(right)?))
118 },
119 _ => Err(Error::NotSupported(None)),
120 }
121}
122
123pub(crate) fn get_origin<T, U>(
124 origin: &FakeXRRigidTransformInit,
125) -> Fallible<RigidTransform3D<f32, T, U>> {
126 if origin.position.len() != 3 || origin.orientation.len() != 4 {
127 return Err(Error::Type(c"Incorrectly sized array".into()));
128 }
129 let p = Vector3D::new(
130 *origin.position[0],
131 *origin.position[1],
132 *origin.position[2],
133 );
134 let o = Rotation3D::unit_quaternion(
135 *origin.orientation[0],
136 *origin.orientation[1],
137 *origin.orientation[2],
138 *origin.orientation[3],
139 );
140
141 Ok(RigidTransform3D::new(o, p))
142}
143
144pub(crate) fn get_point<T>(pt: &DOMPointInit) -> Point3D<f32, T> {
145 Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast()
146}
147
148pub(crate) fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> {
149 let regions = world
150 .hitTestRegions
151 .iter()
152 .map(|region| {
153 let ty = region.type_.convert();
154 let faces = region
155 .faces
156 .iter()
157 .map(|face| {
158 if face.vertices.len() != 3 {
159 return Err(Error::Type(
160 c"Incorrectly sized array for triangle list".into(),
161 ));
162 }
163
164 Ok(Triangle {
165 first: get_point(&face.vertices[0]),
166 second: get_point(&face.vertices[1]),
167 third: get_point(&face.vertices[2]),
168 })
169 })
170 .collect::<Fallible<Vec<_>>>()?;
171 Ok(MockRegion { faces, ty })
172 })
173 .collect::<Fallible<Vec<_>>>()?;
174
175 Ok(MockWorld { regions })
176}
177
178impl Convert<EntityType> for FakeXRRegionType {
179 fn convert(self) -> EntityType {
180 match self {
181 FakeXRRegionType::Point => EntityType::Point,
182 FakeXRRegionType::Plane => EntityType::Plane,
183 FakeXRRegionType::Mesh => EntityType::Mesh,
184 }
185 }
186}
187
188impl FakeXRDeviceMethods<crate::DomTypeHolder> for FakeXRDevice {
189 fn SetViews(
191 &self,
192 views: Vec<FakeXRViewInit>,
193 _secondary_views: Option<Vec<FakeXRViewInit>>,
194 ) -> Fallible<()> {
195 let _ = self
196 .sender
197 .send(MockDeviceMsg::SetViews(get_views(&views)?));
198 Ok(())
200 }
201
202 fn SetViewerOrigin(
204 &self,
205 origin: &FakeXRRigidTransformInit,
206 _emulated_position: bool,
207 ) -> Fallible<()> {
208 let _ = self
209 .sender
210 .send(MockDeviceMsg::SetViewerOrigin(Some(get_origin(origin)?)));
211 Ok(())
212 }
213
214 fn ClearViewerOrigin(&self) {
216 let _ = self.sender.send(MockDeviceMsg::SetViewerOrigin(None));
217 }
218
219 fn ClearFloorOrigin(&self) {
221 let _ = self.sender.send(MockDeviceMsg::SetFloorOrigin(None));
222 }
223
224 fn SetFloorOrigin(&self, origin: &FakeXRRigidTransformInit) -> Fallible<()> {
226 let _ = self
227 .sender
228 .send(MockDeviceMsg::SetFloorOrigin(Some(get_origin(origin)?)));
229 Ok(())
230 }
231
232 fn ClearWorld(&self) {
234 let _ = self.sender.send(MockDeviceMsg::ClearWorld);
235 }
236
237 fn SetWorld(&self, world: &FakeXRWorldInit) -> Fallible<()> {
239 let _ = self.sender.send(MockDeviceMsg::SetWorld(get_world(world)?));
240 Ok(())
241 }
242
243 fn SimulateVisibilityChange(&self, v: XRVisibilityState) {
245 let v = match v {
246 XRVisibilityState::Visible => Visibility::Visible,
247 XRVisibilityState::Visible_blurred => Visibility::VisibleBlurred,
248 XRVisibilityState::Hidden => Visibility::Hidden,
249 };
250 let _ = self.sender.send(MockDeviceMsg::VisibilityChange(v));
251 }
252
253 fn SimulateInputSourceConnection(
255 &self,
256 cx: &mut JSContext,
257 init: &FakeXRInputSourceInit,
258 ) -> Fallible<DomRoot<FakeXRInputController>> {
259 let id = self.next_input_id.get();
260 self.next_input_id.set(InputId(id.0 + 1));
261
262 let handedness = init.handedness.convert();
263 let target_ray_mode = init.targetRayMode.convert();
264
265 let pointer_origin = Some(get_origin(&init.pointerOrigin)?);
266
267 let grip_origin = if let Some(ref g) = init.gripOrigin {
268 Some(get_origin(g)?)
269 } else {
270 None
271 };
272
273 let profiles = init.profiles.iter().cloned().map(String::from).collect();
274
275 let mut supported_buttons = vec![];
276 if let Some(ref buttons) = init.supportedButtons {
277 supported_buttons.extend(init_to_mock_buttons(buttons));
278 }
279
280 let source = InputSource {
281 handedness,
282 target_ray_mode,
283 id,
284 supports_grip: true,
285 profiles,
286 hand_support: None,
287 };
288
289 let init = MockInputInit {
290 source,
291 pointer_origin,
292 grip_origin,
293 supported_buttons,
294 };
295
296 let global = self.global();
297 let _ = self.sender.send(MockDeviceMsg::AddInputSource(init));
298
299 let controller = FakeXRInputController::new(cx, &global, self.sender.clone(), id);
300
301 Ok(controller)
302 }
303
304 fn Disconnect(&self, cx: &mut CurrentRealm) -> Rc<Promise> {
306 let global = self.global();
307 let p = Promise::new_in_realm(cx);
308 let mut trusted = Some(TrustedPromise::new(p.clone()));
309 let task_source = global
310 .task_manager()
311 .dom_manipulation_task_source()
312 .to_sendable();
313
314 let callback =
315 ProfileGenericCallback::new(global.time_profiler_chan().clone(), move |_| {
316 let trusted = trusted
317 .take()
318 .expect("disconnect callback called multiple times");
319 task_source.queue(trusted.resolve_task(()));
320 })
321 .expect("Could not create callback");
322 self.disconnect(callback);
323 p
324 }
325
326 fn SetBoundsGeometry(&self, bounds_coodinates: Vec<FakeXRBoundsPoint>) -> Fallible<()> {
328 if bounds_coodinates.len() < 3 {
329 return Err(Error::Type(
330 c"Bounds geometry must contain at least 3 points".into(),
331 ));
332 }
333 let coords = bounds_coodinates
334 .iter()
335 .map(|coord| {
336 let x = *coord.x.unwrap() as f32;
337 let y = *coord.z.unwrap() as f32;
338 Point2D::new(x, y)
339 })
340 .collect();
341 let _ = self.sender.send(MockDeviceMsg::SetBoundsGeometry(coords));
342 Ok(())
343 }
344
345 fn SimulateResetPose(&self) {
347 let _ = self.sender.send(MockDeviceMsg::SimulateResetPose);
348 }
349}
350
351impl Convert<Handedness> for XRHandedness {
352 fn convert(self) -> Handedness {
353 match self {
354 XRHandedness::None => Handedness::None,
355 XRHandedness::Left => Handedness::Left,
356 XRHandedness::Right => Handedness::Right,
357 }
358 }
359}
360
361impl Convert<TargetRayMode> for XRTargetRayMode {
362 fn convert(self) -> TargetRayMode {
363 match self {
364 XRTargetRayMode::Gaze => TargetRayMode::Gaze,
365 XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
366 XRTargetRayMode::Screen => TargetRayMode::Screen,
367 XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
368 }
369 }
370}