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