Filter out webidl files based on special comments, and feature-gate webxr interfaces. (#34348)

* Filter out webidl files based on skip-if directives.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* script: Don't build XR functionality without webxr feature.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Fix tidy.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* script: Adjust imports for file movement.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Fix clippy.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Formatting.

Signed-off-by: Josh Matthews <josh@joshmatthews.net>

* Clean up webxr module import.

Co-authored-by: Samson <16504129+sagudev@users.noreply.github.com>
Signed-off-by: Josh Matthews <josh@joshmatthews.net>

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Samson <16504129+sagudev@users.noreply.github.com>
This commit is contained in:
Josh Matthews 2024-11-24 13:01:35 -05:00 committed by GitHub
parent e956f3124c
commit 3faed9b921
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
94 changed files with 206 additions and 53 deletions

View file

@ -0,0 +1,368 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::rc::Rc;
use dom_struct::dom_struct;
use euclid::{Point2D, Point3D, Rect, RigidTransform3D, Rotation3D, Size2D, Transform3D, Vector3D};
use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
use profile_traits::ipc;
use webxr_api::{
EntityType, Handedness, InputId, InputSource, MockDeviceMsg, MockInputInit, MockRegion,
MockViewInit, MockViewsInit, MockWorld, TargetRayMode, Triangle, Visibility,
};
use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::{
FakeXRBoundsPoint, FakeXRDeviceMethods, FakeXRRegionType, FakeXRRigidTransformInit,
FakeXRViewInit, FakeXRWorldInit,
};
use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::FakeXRInputSourceInit;
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
XRHandedness, XRTargetRayMode,
};
use crate::dom::bindings::codegen::Bindings::XRSessionBinding::XRVisibilityState;
use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::refcounted::TrustedPromise;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::fakexrinputcontroller::{init_to_mock_buttons, FakeXRInputController};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::script_runtime::CanGc;
use crate::task_source::TaskSource;
#[dom_struct]
pub struct FakeXRDevice {
reflector: Reflector,
#[ignore_malloc_size_of = "defined in ipc-channel"]
#[no_trace]
sender: IpcSender<MockDeviceMsg>,
#[ignore_malloc_size_of = "defined in webxr-api"]
#[no_trace]
next_input_id: Cell<InputId>,
}
impl FakeXRDevice {
pub fn new_inherited(sender: IpcSender<MockDeviceMsg>) -> FakeXRDevice {
FakeXRDevice {
reflector: Reflector::new(),
sender,
next_input_id: Cell::new(InputId(0)),
}
}
pub fn new(global: &GlobalScope, sender: IpcSender<MockDeviceMsg>) -> DomRoot<FakeXRDevice> {
reflect_dom_object(Box::new(FakeXRDevice::new_inherited(sender)), global)
}
pub fn disconnect(&self, sender: IpcSender<()>) {
let _ = self.sender.send(MockDeviceMsg::Disconnect(sender));
}
}
pub fn view<Eye>(view: &FakeXRViewInit) -> Fallible<MockViewInit<Eye>> {
if view.projectionMatrix.len() != 16 || view.viewOffset.position.len() != 3 {
return Err(Error::Type("Incorrectly sized array".into()));
}
let mut proj = [0.; 16];
let v: Vec<_> = view.projectionMatrix.iter().map(|x| **x).collect();
proj.copy_from_slice(&v);
let projection = Transform3D::from_array(proj);
// spec defines offsets as origins, but mock API expects the inverse transform
let transform = get_origin(&view.viewOffset)?.inverse();
let size = Size2D::new(view.resolution.width, view.resolution.height);
let origin = match view.eye {
XREye::Right => Point2D::new(size.width, 0),
_ => Point2D::zero(),
};
let viewport = Rect::new(origin, size);
let fov = view.fieldOfView.as_ref().map(|fov| {
(
fov.leftDegrees.to_radians(),
fov.rightDegrees.to_radians(),
fov.upDegrees.to_radians(),
fov.downDegrees.to_radians(),
)
});
Ok(MockViewInit {
projection,
transform,
viewport,
fov,
})
}
pub fn get_views(views: &[FakeXRViewInit]) -> Fallible<MockViewsInit> {
match views.len() {
1 => Ok(MockViewsInit::Mono(view(&views[0])?)),
2 => {
let (left, right) = match (views[0].eye, views[1].eye) {
(XREye::Left, XREye::Right) => (&views[0], &views[1]),
(XREye::Right, XREye::Left) => (&views[1], &views[0]),
_ => return Err(Error::NotSupported),
};
Ok(MockViewsInit::Stereo(view(left)?, view(right)?))
},
_ => Err(Error::NotSupported),
}
}
pub fn get_origin<T, U>(
origin: &FakeXRRigidTransformInit,
) -> Fallible<RigidTransform3D<f32, T, U>> {
if origin.position.len() != 3 || origin.orientation.len() != 4 {
return Err(Error::Type("Incorrectly sized array".into()));
}
let p = Vector3D::new(
*origin.position[0],
*origin.position[1],
*origin.position[2],
);
let o = Rotation3D::unit_quaternion(
*origin.orientation[0],
*origin.orientation[1],
*origin.orientation[2],
*origin.orientation[3],
);
Ok(RigidTransform3D::new(o, p))
}
pub fn get_point<T>(pt: &DOMPointInit) -> Point3D<f32, T> {
Point3D::new(pt.x / pt.w, pt.y / pt.w, pt.z / pt.w).cast()
}
pub fn get_world(world: &FakeXRWorldInit) -> Fallible<MockWorld> {
let regions = world
.hitTestRegions
.iter()
.map(|region| {
let ty = region.type_.into();
let faces = region
.faces
.iter()
.map(|face| {
if face.vertices.len() != 3 {
return Err(Error::Type(
"Incorrectly sized array for triangle list".into(),
));
}
Ok(Triangle {
first: get_point(&face.vertices[0]),
second: get_point(&face.vertices[1]),
third: get_point(&face.vertices[2]),
})
})
.collect::<Fallible<Vec<_>>>()?;
Ok(MockRegion { faces, ty })
})
.collect::<Fallible<Vec<_>>>()?;
Ok(MockWorld { regions })
}
impl From<FakeXRRegionType> for EntityType {
fn from(x: FakeXRRegionType) -> Self {
match x {
FakeXRRegionType::Point => EntityType::Point,
FakeXRRegionType::Plane => EntityType::Plane,
FakeXRRegionType::Mesh => EntityType::Mesh,
}
}
}
impl FakeXRDeviceMethods<crate::DomTypeHolder> for FakeXRDevice {
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
fn SetViews(
&self,
views: Vec<FakeXRViewInit>,
_secondary_views: Option<Vec<FakeXRViewInit>>,
) -> Fallible<()> {
let _ = self
.sender
.send(MockDeviceMsg::SetViews(get_views(&views)?));
// TODO: Support setting secondary views for mock backend
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setviewerorigin>
fn SetViewerOrigin(
&self,
origin: &FakeXRRigidTransformInit,
_emulated_position: bool,
) -> Fallible<()> {
let _ = self
.sender
.send(MockDeviceMsg::SetViewerOrigin(Some(get_origin(origin)?)));
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearviewerorigin>
fn ClearViewerOrigin(&self) {
let _ = self.sender.send(MockDeviceMsg::SetViewerOrigin(None));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearfloororigin>
fn ClearFloorOrigin(&self) {
let _ = self.sender.send(MockDeviceMsg::SetFloorOrigin(None));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setfloororigin>
fn SetFloorOrigin(&self, origin: &FakeXRRigidTransformInit) -> Fallible<()> {
let _ = self
.sender
.send(MockDeviceMsg::SetFloorOrigin(Some(get_origin(origin)?)));
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-clearworld>
fn ClearWorld(&self) {
let _ = self.sender.send(MockDeviceMsg::ClearWorld);
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setworld>
fn SetWorld(&self, world: &FakeXRWorldInit) -> Fallible<()> {
let _ = self.sender.send(MockDeviceMsg::SetWorld(get_world(world)?));
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulatevisibilitychange>
fn SimulateVisibilityChange(&self, v: XRVisibilityState) {
let v = match v {
XRVisibilityState::Visible => Visibility::Visible,
XRVisibilityState::Visible_blurred => Visibility::VisibleBlurred,
XRVisibilityState::Hidden => Visibility::Hidden,
};
let _ = self.sender.send(MockDeviceMsg::VisibilityChange(v));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulateinputsourceconnection>
fn SimulateInputSourceConnection(
&self,
init: &FakeXRInputSourceInit,
) -> Fallible<DomRoot<FakeXRInputController>> {
let id = self.next_input_id.get();
self.next_input_id.set(InputId(id.0 + 1));
let handedness = init.handedness.into();
let target_ray_mode = init.targetRayMode.into();
let pointer_origin = Some(get_origin(&init.pointerOrigin)?);
let grip_origin = if let Some(ref g) = init.gripOrigin {
Some(get_origin(g)?)
} else {
None
};
let profiles = init.profiles.iter().cloned().map(String::from).collect();
let mut supported_buttons = vec![];
if let Some(ref buttons) = init.supportedButtons {
supported_buttons.extend(init_to_mock_buttons(buttons));
}
let source = InputSource {
handedness,
target_ray_mode,
id,
supports_grip: true,
profiles,
hand_support: None,
};
let init = MockInputInit {
source,
pointer_origin,
grip_origin,
supported_buttons,
};
let global = self.global();
let _ = self.sender.send(MockDeviceMsg::AddInputSource(init));
let controller = FakeXRInputController::new(&global, self.sender.clone(), id);
Ok(controller)
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-disconnect>
fn Disconnect(&self, can_gc: CanGc) -> Rc<Promise> {
let global = self.global();
let p = Promise::new(&global, can_gc);
let mut trusted = Some(TrustedPromise::new(p.clone()));
let (task_source, canceller) = global
.as_window()
.task_manager()
.dom_manipulation_task_source_with_canceller();
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
ROUTER.add_typed_route(
receiver.to_ipc_receiver(),
Box::new(move |_| {
let trusted = trusted
.take()
.expect("disconnect callback called multiple times");
let _ = task_source.queue_with_canceller(trusted.resolve_task(()), &canceller);
}),
);
self.disconnect(sender);
p
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-setboundsgeometry>
fn SetBoundsGeometry(&self, bounds_coodinates: Vec<FakeXRBoundsPoint>) -> Fallible<()> {
if bounds_coodinates.len() < 3 {
return Err(Error::Type(
"Bounds geometry must contain at least 3 points".into(),
));
}
let coords = bounds_coodinates
.iter()
.map(|coord| {
let x = *coord.x.unwrap() as f32;
let y = *coord.z.unwrap() as f32;
Point2D::new(x, y)
})
.collect();
let _ = self.sender.send(MockDeviceMsg::SetBoundsGeometry(coords));
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrdevice-simulateresetpose>
fn SimulateResetPose(&self) {
let _ = self.sender.send(MockDeviceMsg::SimulateResetPose);
}
}
impl From<XRHandedness> for Handedness {
fn from(h: XRHandedness) -> Self {
match h {
XRHandedness::None => Handedness::None,
XRHandedness::Left => Handedness::Left,
XRHandedness::Right => Handedness::Right,
}
}
}
impl From<XRTargetRayMode> for TargetRayMode {
fn from(t: XRTargetRayMode) -> Self {
match t {
XRTargetRayMode::Gaze => TargetRayMode::Gaze,
XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
XRTargetRayMode::Screen => TargetRayMode::Screen,
XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
}
}
}

View file

@ -0,0 +1,194 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use webxr_api::{
Handedness, InputId, MockButton, MockButtonType, MockDeviceMsg, MockInputMsg, SelectEvent,
SelectKind, TargetRayMode,
};
use crate::dom::bindings::codegen::Bindings::FakeXRDeviceBinding::FakeXRRigidTransformInit;
use crate::dom::bindings::codegen::Bindings::FakeXRInputControllerBinding::{
FakeXRButtonStateInit, FakeXRButtonType, FakeXRInputControllerMethods,
};
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
XRHandedness, XRTargetRayMode,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::fakexrdevice::get_origin;
use crate::dom::globalscope::GlobalScope;
#[dom_struct]
pub struct FakeXRInputController {
reflector: Reflector,
#[ignore_malloc_size_of = "defined in ipc-channel"]
#[no_trace]
sender: IpcSender<MockDeviceMsg>,
#[ignore_malloc_size_of = "defined in webxr-api"]
#[no_trace]
id: InputId,
}
impl FakeXRInputController {
pub fn new_inherited(sender: IpcSender<MockDeviceMsg>, id: InputId) -> FakeXRInputController {
FakeXRInputController {
reflector: Reflector::new(),
sender,
id,
}
}
pub fn new(
global: &GlobalScope,
sender: IpcSender<MockDeviceMsg>,
id: InputId,
) -> DomRoot<FakeXRInputController> {
reflect_dom_object(
Box::new(FakeXRInputController::new_inherited(sender, id)),
global,
)
}
fn send_message(&self, msg: MockInputMsg) {
let _ = self
.sender
.send(MockDeviceMsg::MessageInputSource(self.id, msg));
}
}
impl FakeXRInputControllerMethods<crate::DomTypeHolder> for FakeXRInputController {
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setpointerorigin>
fn SetPointerOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> {
self.send_message(MockInputMsg::SetPointerOrigin(Some(get_origin(origin)?)));
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setgriporigin>
fn SetGripOrigin(&self, origin: &FakeXRRigidTransformInit, _emulated: bool) -> Fallible<()> {
self.send_message(MockInputMsg::SetGripOrigin(Some(get_origin(origin)?)));
Ok(())
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-cleargriporigin>
fn ClearGripOrigin(&self) {
self.send_message(MockInputMsg::SetGripOrigin(None))
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-disconnect>
fn Disconnect(&self) {
self.send_message(MockInputMsg::Disconnect)
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-reconnect>
fn Reconnect(&self) {
self.send_message(MockInputMsg::Reconnect)
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-startselection>
fn StartSelection(&self) {
self.send_message(MockInputMsg::TriggerSelect(
SelectKind::Select,
SelectEvent::Start,
))
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-endselection>
fn EndSelection(&self) {
self.send_message(MockInputMsg::TriggerSelect(
SelectKind::Select,
SelectEvent::End,
))
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-simulateselect>
fn SimulateSelect(&self) {
self.send_message(MockInputMsg::TriggerSelect(
SelectKind::Select,
SelectEvent::Select,
))
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-sethandedness>
fn SetHandedness(&self, handedness: XRHandedness) {
let h = match handedness {
XRHandedness::None => Handedness::None,
XRHandedness::Left => Handedness::Left,
XRHandedness::Right => Handedness::Right,
};
self.send_message(MockInputMsg::SetHandedness(h));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-settargetraymode>
fn SetTargetRayMode(&self, target_ray_mode: XRTargetRayMode) {
let t = match target_ray_mode {
XRTargetRayMode::Gaze => TargetRayMode::Gaze,
XRTargetRayMode::Tracked_pointer => TargetRayMode::TrackedPointer,
XRTargetRayMode::Screen => TargetRayMode::Screen,
XRTargetRayMode::Transient_pointer => TargetRayMode::TransientPointer,
};
self.send_message(MockInputMsg::SetTargetRayMode(t));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setprofiles>
fn SetProfiles(&self, profiles: Vec<DOMString>) {
let t = profiles.into_iter().map(String::from).collect();
self.send_message(MockInputMsg::SetProfiles(t));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-setsupportedbuttons>
fn SetSupportedButtons(&self, supported_buttons: Vec<FakeXRButtonStateInit>) {
let supported = init_to_mock_buttons(&supported_buttons);
self.send_message(MockInputMsg::SetSupportedButtons(supported));
}
/// <https://immersive-web.github.io/webxr-test-api/#dom-fakexrinputcontroller-updatebuttonstate>
fn UpdateButtonState(&self, button_state: &FakeXRButtonStateInit) -> Fallible<()> {
// https://immersive-web.github.io/webxr-test-api/#validate-a-button-state
if (button_state.pressed || *button_state.pressedValue > 0.0) && !button_state.touched {
return Err(Error::Type("Pressed button must also be touched".into()));
}
if *button_state.pressedValue < 0.0 {
return Err(Error::Type("Pressed value must be non-negative".into()));
}
// TODO: Steps 3-5 of updateButtonState
// Passing the one WPT test that utilizes this will require additional work
// to specify gamepad button/axes list lengths, as well as passing that info
// to the constructor of XRInputSource
Ok(())
}
}
impl From<FakeXRButtonType> for MockButtonType {
fn from(b: FakeXRButtonType) -> Self {
match b {
FakeXRButtonType::Grip => MockButtonType::Grip,
FakeXRButtonType::Touchpad => MockButtonType::Touchpad,
FakeXRButtonType::Thumbstick => MockButtonType::Thumbstick,
FakeXRButtonType::Optional_button => MockButtonType::OptionalButton,
FakeXRButtonType::Optional_thumbstick => MockButtonType::OptionalThumbstick,
}
}
}
/// <https://immersive-web.github.io/webxr-test-api/#parse-supported-buttons>
pub fn init_to_mock_buttons(buttons: &[FakeXRButtonStateInit]) -> Vec<MockButton> {
let supported: Vec<MockButton> = buttons
.iter()
.map(|b| MockButton {
button_type: b.buttonType.into(),
pressed: b.pressed,
touched: b.touched,
pressed_value: *b.pressedValue,
x_value: *b.xValue,
y_value: *b.yValue,
})
.collect();
supported
}

View file

@ -0,0 +1,44 @@
/* 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 https://mozilla.org/MPL/2.0/. */
pub mod fakexrdevice;
pub mod fakexrinputcontroller;
pub mod xrboundedreferencespace;
pub mod xrcompositionlayer;
pub mod xrcubelayer;
pub mod xrcylinderlayer;
pub mod xrequirectlayer;
pub mod xrframe;
pub mod xrhand;
pub mod xrhittestresult;
pub mod xrhittestsource;
pub mod xrinputsource;
pub mod xrinputsourcearray;
pub mod xrinputsourceevent;
pub mod xrinputsourceschangeevent;
pub mod xrjointpose;
pub mod xrjointspace;
pub mod xrlayer;
pub mod xrlayerevent;
pub mod xrmediabinding;
pub mod xrpose;
pub mod xrprojectionlayer;
pub mod xrquadlayer;
pub mod xrray;
pub mod xrreferencespace;
pub mod xrreferencespaceevent;
pub mod xrrenderstate;
pub mod xrrigidtransform;
pub mod xrsession;
pub mod xrsessionevent;
pub mod xrspace;
pub mod xrsubimage;
pub mod xrsystem;
pub mod xrtest;
pub mod xrview;
pub mod xrviewerpose;
pub mod xrviewport;
pub mod xrwebglbinding;
pub mod xrwebgllayer;
pub mod xrwebglsubimage;

View file

@ -0,0 +1,91 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::MutableHandleValue;
use crate::dom::bindings::codegen::Bindings::XRBoundedReferenceSpaceBinding::XRBoundedReferenceSpaceMethods;
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::dompointreadonly::DOMPointReadOnly;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::dom::xrsession::XRSession;
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRBoundedReferenceSpace {
reference_space: XRReferenceSpace,
offset: Dom<XRRigidTransform>,
}
impl XRBoundedReferenceSpace {
pub fn new_inherited(
session: &XRSession,
offset: &XRRigidTransform,
) -> XRBoundedReferenceSpace {
XRBoundedReferenceSpace {
reference_space: XRReferenceSpace::new_inherited(
session,
offset,
XRReferenceSpaceType::Bounded_floor,
),
offset: Dom::from_ref(offset),
}
}
#[allow(unused)]
pub fn new(
global: &GlobalScope,
session: &XRSession,
can_gc: CanGc,
) -> DomRoot<XRBoundedReferenceSpace> {
let offset = XRRigidTransform::identity(global, can_gc);
Self::new_offset(global, session, &offset)
}
#[allow(unused)]
pub fn new_offset(
global: &GlobalScope,
session: &XRSession,
offset: &XRRigidTransform,
) -> DomRoot<XRBoundedReferenceSpace> {
reflect_dom_object(
Box::new(XRBoundedReferenceSpace::new_inherited(session, offset)),
global,
)
}
pub fn reference_space(&self) -> &XRReferenceSpace {
&self.reference_space
}
}
impl XRBoundedReferenceSpaceMethods<crate::DomTypeHolder> for XRBoundedReferenceSpace {
/// <https://www.w3.org/TR/webxr/#dom-xrboundedreferencespace-boundsgeometry>
fn BoundsGeometry(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
if let Some(bounds) = self.reference_space.get_bounds() {
let points: Vec<DomRoot<DOMPointReadOnly>> = bounds
.into_iter()
.map(|point| {
DOMPointReadOnly::new(
&self.global(),
point.x.into(),
0.0,
point.y.into(),
1.0,
can_gc,
)
})
.collect();
to_frozen_array(&points, cx, retval)
} else {
to_frozen_array::<DomRoot<DOMPointReadOnly>>(&[], cx, retval)
}
}
}

View file

@ -0,0 +1,12 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::xrlayer::XRLayer;
#[dom_struct]
pub struct XRCompositionLayer {
xr_layer: XRLayer,
}

View file

@ -0,0 +1,12 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::xrcompositionlayer::XRCompositionLayer;
#[dom_struct]
pub struct XRCubeLayer {
composition_layer: XRCompositionLayer,
}

View file

@ -0,0 +1,12 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::xrcompositionlayer::XRCompositionLayer;
#[dom_struct]
pub struct XRCylinderLayer {
composition_layer: XRCompositionLayer,
}

View file

@ -0,0 +1,12 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::xrcompositionlayer::XRCompositionLayer;
#[dom_struct]
pub struct XREquirectLayer {
composition_layer: XRCompositionLayer,
}

View file

@ -0,0 +1,294 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use js::gc::CustomAutoRooterGuard;
use js::typedarray::Float32Array;
use webxr_api::{Frame, LayerId, SubImages};
use crate::dom::bindings::codegen::Bindings::XRFrameBinding::XRFrameMethods;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrhittestresult::XRHitTestResult;
use crate::dom::xrhittestsource::XRHitTestSource;
use crate::dom::xrjointpose::XRJointPose;
use crate::dom::xrjointspace::XRJointSpace;
use crate::dom::xrpose::XRPose;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrsession::{ApiPose, XRSession};
use crate::dom::xrspace::XRSpace;
use crate::dom::xrviewerpose::XRViewerPose;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRFrame {
reflector_: Reflector,
session: Dom<XRSession>,
#[ignore_malloc_size_of = "defined in webxr_api"]
#[no_trace]
data: Frame,
active: Cell<bool>,
animation_frame: Cell<bool>,
}
impl XRFrame {
fn new_inherited(session: &XRSession, data: Frame) -> XRFrame {
XRFrame {
reflector_: Reflector::new(),
session: Dom::from_ref(session),
data,
active: Cell::new(false),
animation_frame: Cell::new(false),
}
}
pub fn new(global: &GlobalScope, session: &XRSession, data: Frame) -> DomRoot<XRFrame> {
reflect_dom_object(Box::new(XRFrame::new_inherited(session, data)), global)
}
/// <https://immersive-web.github.io/webxr/#xrframe-active>
pub fn set_active(&self, active: bool) {
self.active.set(active);
}
/// <https://immersive-web.github.io/webxr/#xrframe-animationframe>
pub fn set_animation_frame(&self, animation_frame: bool) {
self.animation_frame.set(animation_frame);
}
pub fn get_pose(&self, space: &XRSpace) -> Option<ApiPose> {
space.get_pose(&self.data)
}
pub fn get_sub_images(&self, layer_id: LayerId) -> Option<&SubImages> {
self.data
.sub_images
.iter()
.find(|sub_images| sub_images.layer_id == layer_id)
}
}
impl XRFrameMethods<crate::DomTypeHolder> for XRFrame {
/// <https://immersive-web.github.io/webxr/#dom-xrframe-session>
fn Session(&self) -> DomRoot<XRSession> {
DomRoot::from_ref(&self.session)
}
/// <https://www.w3.org/TR/webxr/#dom-xrframe-predicteddisplaytime>
fn PredictedDisplayTime(&self) -> Finite<f64> {
// TODO: If inline, return the same value
// as the timestamp passed to XRFrameRequestCallback
Finite::new(self.data.predicted_display_time)
.expect("Failed to create predictedDisplayTime")
}
/// <https://immersive-web.github.io/webxr/#dom-xrframe-getviewerpose>
fn GetViewerPose(
&self,
reference: &XRReferenceSpace,
can_gc: CanGc,
) -> Result<Option<DomRoot<XRViewerPose>>, Error> {
if self.session != reference.upcast::<XRSpace>().session() {
return Err(Error::InvalidState);
}
if !self.active.get() || !self.animation_frame.get() {
return Err(Error::InvalidState);
}
let to_base = if let Some(to_base) = reference.get_base_transform(&self.data) {
to_base
} else {
return Ok(None);
};
let viewer_pose = if let Some(pose) = self.data.pose.as_ref() {
pose
} else {
return Ok(None);
};
Ok(Some(XRViewerPose::new(
&self.global(),
&self.session,
to_base,
viewer_pose,
can_gc,
)))
}
/// <https://immersive-web.github.io/webxr/#dom-xrframe-getpose>
fn GetPose(
&self,
space: &XRSpace,
base_space: &XRSpace,
can_gc: CanGc,
) -> Result<Option<DomRoot<XRPose>>, Error> {
if self.session != space.session() || self.session != base_space.session() {
return Err(Error::InvalidState);
}
if !self.active.get() {
return Err(Error::InvalidState);
}
let space = if let Some(space) = self.get_pose(space) {
space
} else {
return Ok(None);
};
let base_space = if let Some(r) = self.get_pose(base_space) {
r
} else {
return Ok(None);
};
let pose = space.then(&base_space.inverse());
Ok(Some(XRPose::new(&self.global(), pose, can_gc)))
}
/// <https://immersive-web.github.io/webxr/#dom-xrframe-getpose>
fn GetJointPose(
&self,
space: &XRJointSpace,
base_space: &XRSpace,
can_gc: CanGc,
) -> Result<Option<DomRoot<XRJointPose>>, Error> {
if self.session != space.upcast::<XRSpace>().session() ||
self.session != base_space.session()
{
return Err(Error::InvalidState);
}
if !self.active.get() {
return Err(Error::InvalidState);
}
let joint_frame = if let Some(frame) = space.frame(&self.data) {
frame
} else {
return Ok(None);
};
let base_space = if let Some(r) = self.get_pose(base_space) {
r
} else {
return Ok(None);
};
let pose = joint_frame.pose.then(&base_space.inverse());
Ok(Some(XRJointPose::new(
&self.global(),
pose.cast_unit(),
Some(joint_frame.radius),
can_gc,
)))
}
/// <https://immersive-web.github.io/hit-test/#dom-xrframe-gethittestresults>
fn GetHitTestResults(&self, source: &XRHitTestSource) -> Vec<DomRoot<XRHitTestResult>> {
self.data
.hit_test_results
.iter()
.filter(|r| r.id == source.id())
.map(|r| XRHitTestResult::new(&self.global(), *r, self))
.collect()
}
#[allow(unsafe_code)]
/// <https://www.w3.org/TR/webxr-hand-input-1/#dom-xrframe-filljointradii>
fn FillJointRadii(
&self,
joint_spaces: Vec<DomRoot<XRJointSpace>>,
mut radii: CustomAutoRooterGuard<Float32Array>,
) -> Result<bool, Error> {
if !self.active.get() {
return Err(Error::InvalidState);
}
for joint_space in &joint_spaces {
if self.session != joint_space.upcast::<XRSpace>().session() {
return Err(Error::InvalidState);
}
}
if joint_spaces.len() > radii.len() {
return Err(Error::Type(
"Length of radii does not match length of joint spaces".to_string(),
));
}
let mut radii_vec = radii.to_vec();
let mut all_valid = true;
radii_vec.iter_mut().enumerate().for_each(|(i, radius)| {
if let Some(joint_frame) = joint_spaces
.get(i)
.and_then(|joint_space| joint_space.frame(&self.data))
{
*radius = joint_frame.radius;
} else {
all_valid = false;
}
});
if !all_valid {
radii_vec.fill(f32::NAN);
}
radii.update(&radii_vec);
Ok(all_valid)
}
#[allow(unsafe_code)]
/// <https://www.w3.org/TR/webxr-hand-input-1/#dom-xrframe-fillposes>
fn FillPoses(
&self,
spaces: Vec<DomRoot<XRSpace>>,
base_space: &XRSpace,
mut transforms: CustomAutoRooterGuard<Float32Array>,
) -> Result<bool, Error> {
if !self.active.get() {
return Err(Error::InvalidState);
}
for space in &spaces {
if self.session != space.session() {
return Err(Error::InvalidState);
}
}
if self.session != base_space.session() {
return Err(Error::InvalidState);
}
if spaces.len() * 16 > transforms.len() {
return Err(Error::Type(
"Transforms array length does not match 16 * spaces length".to_string(),
));
}
let mut transforms_vec = transforms.to_vec();
let mut all_valid = true;
spaces.iter().enumerate().for_each(|(i, space)| {
let Some(joint_pose) = self.get_pose(space) else {
all_valid = false;
return;
};
let Some(base_pose) = self.get_pose(base_space) else {
all_valid = false;
return;
};
let pose = joint_pose.then(&base_pose.inverse());
let elements = pose.to_transform();
let elements_arr = elements.to_array();
transforms_vec[i * 16..(i + 1) * 16].copy_from_slice(&elements_arr);
});
if !all_valid {
transforms_vec.fill(f32::NAN);
}
transforms.update(&transforms_vec);
Ok(all_valid)
}
}

View file

@ -0,0 +1,176 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use webxr_api::{FingerJoint, Hand, Joint};
use crate::dom::bindings::codegen::Bindings::XRHandBinding::{XRHandJoint, XRHandMethods};
use crate::dom::bindings::iterable::Iterable;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrinputsource::XRInputSource;
use crate::dom::xrjointspace::XRJointSpace;
const JOINT_SPACE_MAP: [(XRHandJoint, Joint); 25] = [
(XRHandJoint::Wrist, Joint::Wrist),
(XRHandJoint::Thumb_metacarpal, Joint::ThumbMetacarpal),
(
XRHandJoint::Thumb_phalanx_proximal,
Joint::ThumbPhalanxProximal,
),
(XRHandJoint::Thumb_phalanx_distal, Joint::ThumbPhalanxDistal),
(XRHandJoint::Thumb_tip, Joint::ThumbPhalanxTip),
(
XRHandJoint::Index_finger_metacarpal,
Joint::Index(FingerJoint::Metacarpal),
),
(
XRHandJoint::Index_finger_phalanx_proximal,
Joint::Index(FingerJoint::PhalanxProximal),
),
(XRHandJoint::Index_finger_phalanx_intermediate, {
Joint::Index(FingerJoint::PhalanxIntermediate)
}),
(
XRHandJoint::Index_finger_phalanx_distal,
Joint::Index(FingerJoint::PhalanxDistal),
),
(
XRHandJoint::Index_finger_tip,
Joint::Index(FingerJoint::PhalanxTip),
),
(
XRHandJoint::Middle_finger_metacarpal,
Joint::Middle(FingerJoint::Metacarpal),
),
(
XRHandJoint::Middle_finger_phalanx_proximal,
Joint::Middle(FingerJoint::PhalanxProximal),
),
(XRHandJoint::Middle_finger_phalanx_intermediate, {
Joint::Middle(FingerJoint::PhalanxIntermediate)
}),
(
XRHandJoint::Middle_finger_phalanx_distal,
Joint::Middle(FingerJoint::PhalanxDistal),
),
(
XRHandJoint::Middle_finger_tip,
Joint::Middle(FingerJoint::PhalanxTip),
),
(
XRHandJoint::Ring_finger_metacarpal,
Joint::Ring(FingerJoint::Metacarpal),
),
(
XRHandJoint::Ring_finger_phalanx_proximal,
Joint::Ring(FingerJoint::PhalanxProximal),
),
(XRHandJoint::Ring_finger_phalanx_intermediate, {
Joint::Ring(FingerJoint::PhalanxIntermediate)
}),
(
XRHandJoint::Ring_finger_phalanx_distal,
Joint::Ring(FingerJoint::PhalanxDistal),
),
(
XRHandJoint::Ring_finger_tip,
Joint::Ring(FingerJoint::PhalanxTip),
),
(
XRHandJoint::Pinky_finger_metacarpal,
Joint::Little(FingerJoint::Metacarpal),
),
(
XRHandJoint::Pinky_finger_phalanx_proximal,
Joint::Little(FingerJoint::PhalanxProximal),
),
(XRHandJoint::Pinky_finger_phalanx_intermediate, {
Joint::Little(FingerJoint::PhalanxIntermediate)
}),
(
XRHandJoint::Pinky_finger_phalanx_distal,
Joint::Little(FingerJoint::PhalanxDistal),
),
(
XRHandJoint::Pinky_finger_tip,
Joint::Little(FingerJoint::PhalanxTip),
),
];
#[dom_struct]
pub struct XRHand {
reflector_: Reflector,
#[ignore_malloc_size_of = "defined in webxr"]
source: Dom<XRInputSource>,
#[ignore_malloc_size_of = "partially defind in webxr"]
#[custom_trace]
spaces: Hand<Dom<XRJointSpace>>,
}
impl XRHand {
fn new_inherited(source: &XRInputSource, spaces: &Hand<DomRoot<XRJointSpace>>) -> XRHand {
XRHand {
reflector_: Reflector::new(),
source: Dom::from_ref(source),
spaces: spaces.map(|j, _| j.as_ref().map(|j| Dom::from_ref(&**j))),
}
}
pub fn new(global: &GlobalScope, source: &XRInputSource, support: Hand<()>) -> DomRoot<XRHand> {
let id = source.id();
let session = source.session();
let spaces = support.map(|field, joint| {
let hand_joint = JOINT_SPACE_MAP
.iter()
.find(|&&(_, value)| value == joint)
.map(|&(hand_joint, _)| hand_joint)
.expect("Invalid joint name");
field.map(|_| XRJointSpace::new(global, session, id, joint, hand_joint))
});
reflect_dom_object(Box::new(XRHand::new_inherited(source, &spaces)), global)
}
}
impl XRHandMethods<crate::DomTypeHolder> for XRHand {
/// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
fn Size(&self) -> u32 {
XRHandJoint::Pinky_finger_tip as u32 + 1
}
/// <https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md>
fn Get(&self, joint_name: XRHandJoint) -> DomRoot<XRJointSpace> {
let joint = JOINT_SPACE_MAP
.iter()
.find(|&&(key, _)| key == joint_name)
.map(|&(_, joint)| joint)
.expect("Invalid joint name");
self.spaces
.get(joint)
.map(|j| DomRoot::from_ref(&**j))
.expect("Failed to get joint pose")
}
}
impl Iterable for XRHand {
type Key = XRHandJoint;
type Value = DomRoot<XRJointSpace>;
fn get_iterable_length(&self) -> u32 {
JOINT_SPACE_MAP.len() as u32
}
fn get_value_at_index(&self, n: u32) -> DomRoot<XRJointSpace> {
let joint = JOINT_SPACE_MAP[n as usize].1;
self.spaces
.get(joint)
.map(|j| DomRoot::from_ref(&**j))
.expect("Failed to get joint pose")
}
fn get_key_at_index(&self, n: u32) -> XRHandJoint {
JOINT_SPACE_MAP[n as usize].0
}
}

View file

@ -0,0 +1,54 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use webxr_api::HitTestResult;
use crate::dom::bindings::codegen::Bindings::XRHitTestResultBinding::XRHitTestResultMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrpose::XRPose;
use crate::dom::xrspace::XRSpace;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRHitTestResult {
reflector_: Reflector,
#[ignore_malloc_size_of = "defined in webxr"]
#[no_trace]
result: HitTestResult,
frame: Dom<XRFrame>,
}
impl XRHitTestResult {
fn new_inherited(result: HitTestResult, frame: &XRFrame) -> XRHitTestResult {
XRHitTestResult {
reflector_: Reflector::new(),
result,
frame: Dom::from_ref(frame),
}
}
pub fn new(
global: &GlobalScope,
result: HitTestResult,
frame: &XRFrame,
) -> DomRoot<XRHitTestResult> {
reflect_dom_object(
Box::new(XRHitTestResult::new_inherited(result, frame)),
global,
)
}
}
impl XRHitTestResultMethods<crate::DomTypeHolder> for XRHitTestResult {
// https://immersive-web.github.io/hit-test/#dom-xrhittestresult-getpose
fn GetPose(&self, base: &XRSpace, can_gc: CanGc) -> Option<DomRoot<XRPose>> {
let base = self.frame.get_pose(base)?;
let pose = self.result.space.then(&base.inverse());
Some(XRPose::new(&self.global(), pose.cast_unit(), can_gc))
}
}

View file

@ -0,0 +1,53 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use webxr_api::HitTestId;
use crate::dom::bindings::codegen::Bindings::XRHitTestSourceBinding::XRHitTestSourceMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrsession::XRSession;
#[dom_struct]
pub struct XRHitTestSource {
reflector_: Reflector,
#[ignore_malloc_size_of = "defined in webxr"]
#[no_trace]
id: HitTestId,
session: Dom<XRSession>,
}
impl XRHitTestSource {
fn new_inherited(id: HitTestId, session: &XRSession) -> XRHitTestSource {
XRHitTestSource {
reflector_: Reflector::new(),
id,
session: Dom::from_ref(session),
}
}
pub fn new(
global: &GlobalScope,
id: HitTestId,
session: &XRSession,
) -> DomRoot<XRHitTestSource> {
reflect_dom_object(
Box::new(XRHitTestSource::new_inherited(id, session)),
global,
)
}
pub fn id(&self) -> HitTestId {
self.id
}
}
impl XRHitTestSourceMethods<crate::DomTypeHolder> for XRHitTestSource {
// https://immersive-web.github.io/hit-test/#dom-xrhittestsource-cancel
fn Cancel(&self) {
self.session.with_session(|s| s.cancel_hit_test(self.id));
}
}

View file

@ -0,0 +1,186 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::conversions::ToJSValConvertible;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use js::rust::MutableHandleValue;
use script_traits::GamepadSupportedHapticEffects;
use webxr_api::{Handedness, InputFrame, InputId, InputSource, TargetRayMode};
use crate::dom::bindings::codegen::Bindings::XRInputSourceBinding::{
XRHandedness, XRInputSourceMethods, XRTargetRayMode,
};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::gamepad::Gamepad;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrhand::XRHand;
use crate::dom::xrsession::XRSession;
use crate::dom::xrspace::XRSpace;
use crate::realms::enter_realm;
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRInputSource {
reflector: Reflector,
session: Dom<XRSession>,
#[ignore_malloc_size_of = "Defined in rust-webxr"]
#[no_trace]
info: InputSource,
target_ray_space: MutNullableDom<XRSpace>,
grip_space: MutNullableDom<XRSpace>,
hand: MutNullableDom<XRHand>,
#[ignore_malloc_size_of = "mozjs"]
profiles: Heap<JSVal>,
gamepad: DomRoot<Gamepad>,
}
impl XRInputSource {
pub fn new_inherited(
global: &GlobalScope,
session: &XRSession,
info: InputSource,
can_gc: CanGc,
) -> XRInputSource {
// <https://www.w3.org/TR/webxr-gamepads-module-1/#gamepad-differences>
let gamepad = Gamepad::new(
global,
0,
"".into(),
"xr-standard".into(),
(-1.0, 1.0),
(0.0, 1.0),
GamepadSupportedHapticEffects {
supports_dual_rumble: false,
supports_trigger_rumble: false,
},
true,
can_gc,
);
XRInputSource {
reflector: Reflector::new(),
session: Dom::from_ref(session),
info,
target_ray_space: Default::default(),
grip_space: Default::default(),
hand: Default::default(),
profiles: Heap::default(),
gamepad,
}
}
#[allow(unsafe_code)]
pub fn new(
global: &GlobalScope,
session: &XRSession,
info: InputSource,
can_gc: CanGc,
) -> DomRoot<XRInputSource> {
let source = reflect_dom_object(
Box::new(XRInputSource::new_inherited(global, session, info, can_gc)),
global,
);
let _ac = enter_realm(global);
let cx = GlobalScope::get_cx();
unsafe {
rooted!(in(*cx) let mut profiles = UndefinedValue());
source.info.profiles.to_jsval(*cx, profiles.handle_mut());
source.profiles.set(profiles.get());
}
source
}
pub fn id(&self) -> InputId {
self.info.id
}
pub fn session(&self) -> &XRSession {
&self.session
}
pub fn update_gamepad_state(&self, frame: InputFrame) {
frame
.button_values
.iter()
.enumerate()
.for_each(|(i, value)| {
self.gamepad.map_and_normalize_buttons(i, *value as f64);
});
frame.axis_values.iter().enumerate().for_each(|(i, value)| {
self.gamepad.map_and_normalize_axes(i, *value as f64);
});
}
pub fn gamepad(&self) -> &DomRoot<Gamepad> {
&self.gamepad
}
}
impl XRInputSourceMethods<crate::DomTypeHolder> for XRInputSource {
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-handedness>
fn Handedness(&self) -> XRHandedness {
match self.info.handedness {
Handedness::None => XRHandedness::None,
Handedness::Left => XRHandedness::Left,
Handedness::Right => XRHandedness::Right,
}
}
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetraymode>
fn TargetRayMode(&self) -> XRTargetRayMode {
match self.info.target_ray_mode {
TargetRayMode::Gaze => XRTargetRayMode::Gaze,
TargetRayMode::TrackedPointer => XRTargetRayMode::Tracked_pointer,
TargetRayMode::Screen => XRTargetRayMode::Screen,
TargetRayMode::TransientPointer => XRTargetRayMode::Transient_pointer,
}
}
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-targetrayspace>
fn TargetRaySpace(&self) -> DomRoot<XRSpace> {
self.target_ray_space.or_init(|| {
let global = self.global();
XRSpace::new_inputspace(&global, &self.session, self, false)
})
}
/// <https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace>
fn GetGripSpace(&self) -> Option<DomRoot<XRSpace>> {
if self.info.supports_grip {
Some(self.grip_space.or_init(|| {
let global = self.global();
XRSpace::new_inputspace(&global, &self.session, self, true)
}))
} else {
None
}
}
// https://immersive-web.github.io/webxr/#dom-xrinputsource-profiles
fn Profiles(&self, _cx: JSContext, mut retval: MutableHandleValue) {
retval.set(self.profiles.get())
}
/// <https://www.w3.org/TR/webxr/#dom-xrinputsource-skiprendering>
fn SkipRendering(&self) -> bool {
// Servo is not currently supported anywhere that would allow for skipped
// controller rendering.
false
}
/// <https://www.w3.org/TR/webxr-gamepads-module-1/#xrinputsource-interface>
fn GetGamepad(&self) -> Option<DomRoot<Gamepad>> {
Some(DomRoot::from_ref(&*self.gamepad))
}
// https://github.com/immersive-web/webxr-hands-input/blob/master/explainer.md
fn GetHand(&self) -> Option<DomRoot<XRHand>> {
self.info.hand_support.as_ref().map(|hand| {
self.hand
.or_init(|| XRHand::new(&self.global(), self, hand.clone()))
})
}
}

View file

@ -0,0 +1,152 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use webxr_api::{InputId, InputSource};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::XRInputSourceArrayBinding::XRInputSourceArrayMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::event::Event;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrinputsource::XRInputSource;
use crate::dom::xrinputsourceschangeevent::XRInputSourcesChangeEvent;
use crate::dom::xrsession::XRSession;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRInputSourceArray {
reflector_: Reflector,
input_sources: DomRefCell<Vec<Dom<XRInputSource>>>,
}
impl XRInputSourceArray {
fn new_inherited() -> XRInputSourceArray {
XRInputSourceArray {
reflector_: Reflector::new(),
input_sources: DomRefCell::new(vec![]),
}
}
pub fn new(global: &GlobalScope) -> DomRoot<XRInputSourceArray> {
reflect_dom_object(Box::new(XRInputSourceArray::new_inherited()), global)
}
pub fn add_input_sources(&self, session: &XRSession, inputs: &[InputSource], can_gc: CanGc) {
let global = self.global();
let mut added = vec![];
for info in inputs {
// This is quadratic, but won't be a problem for the only case
// where we add multiple input sources (the initial input sources case)
debug_assert!(
!self
.input_sources
.borrow()
.iter()
.any(|i| i.id() == info.id),
"Should never add a duplicate input id!"
);
let input = XRInputSource::new(&global, session, info.clone(), can_gc);
self.input_sources.borrow_mut().push(Dom::from_ref(&input));
added.push(input);
}
let event = XRInputSourcesChangeEvent::new(
&global,
atom!("inputsourceschange"),
false,
true,
session,
&added,
&[],
can_gc,
);
event.upcast::<Event>().fire(session.upcast(), can_gc);
}
pub fn remove_input_source(&self, session: &XRSession, id: InputId, can_gc: CanGc) {
let global = self.global();
let removed = if let Some(i) = self.input_sources.borrow().iter().find(|i| i.id() == id) {
i.gamepad().update_connected(false, false, can_gc);
[DomRoot::from_ref(&**i)]
} else {
return;
};
let event = XRInputSourcesChangeEvent::new(
&global,
atom!("inputsourceschange"),
false,
true,
session,
&[],
&removed,
can_gc,
);
self.input_sources.borrow_mut().retain(|i| i.id() != id);
event.upcast::<Event>().fire(session.upcast(), can_gc);
}
pub fn add_remove_input_source(
&self,
session: &XRSession,
id: InputId,
info: InputSource,
can_gc: CanGc,
) {
let global = self.global();
let root;
let removed = if let Some(i) = self.input_sources.borrow().iter().find(|i| i.id() == id) {
i.gamepad().update_connected(false, false, can_gc);
root = [DomRoot::from_ref(&**i)];
&root as &[_]
} else {
warn!("Could not find removed input source with id {:?}", id);
&[]
};
self.input_sources.borrow_mut().retain(|i| i.id() != id);
let input = XRInputSource::new(&global, session, info, can_gc);
self.input_sources.borrow_mut().push(Dom::from_ref(&input));
let added = [input];
let event = XRInputSourcesChangeEvent::new(
&global,
atom!("inputsourceschange"),
false,
true,
session,
&added,
removed,
can_gc,
);
event.upcast::<Event>().fire(session.upcast(), can_gc);
}
pub fn find(&self, id: InputId) -> Option<DomRoot<XRInputSource>> {
self.input_sources
.borrow()
.iter()
.find(|x| x.id() == id)
.map(|x| DomRoot::from_ref(&**x))
}
}
impl XRInputSourceArrayMethods<crate::DomTypeHolder> for XRInputSourceArray {
/// <https://immersive-web.github.io/webxr/#dom-xrinputsourcearray-length>
fn Length(&self) -> u32 {
self.input_sources.borrow().len() as u32
}
/// <https://immersive-web.github.io/webxr/#xrinputsourcearray>
fn IndexedGetter(&self, n: u32) -> Option<DomRoot<XRInputSource>> {
self.input_sources
.borrow()
.get(n as usize)
.map(|x| DomRoot::from_ref(&**x))
}
}

View file

@ -0,0 +1,116 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::XRInputSourceEventBinding::{
self, XRInputSourceEventMethods,
};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrinputsource::XRInputSource;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRInputSourceEvent {
event: Event,
frame: Dom<XRFrame>,
source: Dom<XRInputSource>,
}
impl XRInputSourceEvent {
#[allow(crown::unrooted_must_root)]
fn new_inherited(frame: &XRFrame, source: &XRInputSource) -> XRInputSourceEvent {
XRInputSourceEvent {
event: Event::new_inherited(),
frame: Dom::from_ref(frame),
source: Dom::from_ref(source),
}
}
pub fn new(
global: &GlobalScope,
type_: Atom,
bubbles: bool,
cancelable: bool,
frame: &XRFrame,
source: &XRInputSource,
can_gc: CanGc,
) -> DomRoot<XRInputSourceEvent> {
Self::new_with_proto(
global, None, type_, bubbles, cancelable, frame, source, can_gc,
)
}
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
frame: &XRFrame,
source: &XRInputSource,
can_gc: CanGc,
) -> DomRoot<XRInputSourceEvent> {
let trackevent = reflect_dom_object_with_proto(
Box::new(XRInputSourceEvent::new_inherited(frame, source)),
global,
proto,
can_gc,
);
{
let event = trackevent.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
trackevent
}
}
impl XRInputSourceEventMethods<crate::DomTypeHolder> for XRInputSourceEvent {
// https://immersive-web.github.io/webxr/#dom-xrinputsourceevent-xrinputsourceevent
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &XRInputSourceEventBinding::XRInputSourceEventInit,
) -> Fallible<DomRoot<XRInputSourceEvent>> {
Ok(XRInputSourceEvent::new_with_proto(
&window.global(),
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.frame,
&init.inputSource,
can_gc,
))
}
// https://immersive-web.github.io/webxr/#dom-xrinputsourceeventinit-frame
fn Frame(&self) -> DomRoot<XRFrame> {
DomRoot::from_ref(&*self.frame)
}
// https://immersive-web.github.io/webxr/#dom-xrinputsourceeventinit-inputsource
fn InputSource(&self) -> DomRoot<XRInputSource> {
DomRoot::from_ref(&*self.source)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,140 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::jsapi::Heap;
use js::jsval::JSVal;
use js::rust::{HandleObject, MutableHandleValue};
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::XRInputSourcesChangeEventBinding::{
self, XRInputSourcesChangeEventMethods,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::event::Event;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::dom::xrinputsource::XRInputSource;
use crate::dom::xrsession::XRSession;
use crate::realms::enter_realm;
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRInputSourcesChangeEvent {
event: Event,
session: Dom<XRSession>,
#[ignore_malloc_size_of = "mozjs"]
added: Heap<JSVal>,
#[ignore_malloc_size_of = "mozjs"]
removed: Heap<JSVal>,
}
impl XRInputSourcesChangeEvent {
#[allow(crown::unrooted_must_root)]
fn new_inherited(session: &XRSession) -> XRInputSourcesChangeEvent {
XRInputSourcesChangeEvent {
event: Event::new_inherited(),
session: Dom::from_ref(session),
added: Heap::default(),
removed: Heap::default(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn new(
global: &GlobalScope,
type_: Atom,
bubbles: bool,
cancelable: bool,
session: &XRSession,
added: &[DomRoot<XRInputSource>],
removed: &[DomRoot<XRInputSource>],
can_gc: CanGc,
) -> DomRoot<XRInputSourcesChangeEvent> {
Self::new_with_proto(
global, None, type_, bubbles, cancelable, session, added, removed, can_gc,
)
}
#[allow(unsafe_code)]
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
session: &XRSession,
added: &[DomRoot<XRInputSource>],
removed: &[DomRoot<XRInputSource>],
can_gc: CanGc,
) -> DomRoot<XRInputSourcesChangeEvent> {
let changeevent = reflect_dom_object_with_proto(
Box::new(XRInputSourcesChangeEvent::new_inherited(session)),
global,
proto,
can_gc,
);
{
let event = changeevent.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
let _ac = enter_realm(global);
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut frozen_val: JSVal);
to_frozen_array(added, cx, frozen_val.handle_mut());
changeevent.added.set(*frozen_val);
to_frozen_array(removed, cx, frozen_val.handle_mut());
changeevent.removed.set(*frozen_val);
changeevent
}
}
impl XRInputSourcesChangeEventMethods<crate::DomTypeHolder> for XRInputSourcesChangeEvent {
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-xrinputsourceschangeevent
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &XRInputSourcesChangeEventBinding::XRInputSourcesChangeEventInit,
) -> DomRoot<XRInputSourcesChangeEvent> {
XRInputSourcesChangeEvent::new_with_proto(
&window.global(),
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.session,
&init.added,
&init.removed,
can_gc,
)
}
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-session
fn Session(&self) -> DomRoot<XRSession> {
DomRoot::from_ref(&*self.session)
}
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-added
fn Added(&self, _cx: JSContext, mut retval: MutableHandleValue) {
retval.set(self.added.get())
}
// https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-removed
fn Removed(&self, _cx: JSContext, mut retval: MutableHandleValue) {
retval.set(self.removed.get())
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,51 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::XRJointPoseBinding::XRJointPoseMethods;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrpose::XRPose;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::dom::xrsession::ApiRigidTransform;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRJointPose {
pose: XRPose,
radius: Option<f32>,
}
impl XRJointPose {
fn new_inherited(transform: &XRRigidTransform, radius: Option<f32>) -> XRJointPose {
XRJointPose {
pose: XRPose::new_inherited(transform),
radius,
}
}
#[allow(unsafe_code)]
pub fn new(
global: &GlobalScope,
pose: ApiRigidTransform,
radius: Option<f32>,
can_gc: CanGc,
) -> DomRoot<XRJointPose> {
let transform = XRRigidTransform::new(global, pose, can_gc);
reflect_dom_object(
Box::new(XRJointPose::new_inherited(&transform, radius)),
global,
)
}
}
impl XRJointPoseMethods<crate::DomTypeHolder> for XRJointPose {
/// <https://immersive-web.github.io/webxr/#dom-XRJointPose-views>
fn GetRadius(&self) -> Option<Finite<f32>> {
self.radius.map(Finite::wrap)
}
}

View file

@ -0,0 +1,83 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::RigidTransform3D;
use webxr_api::{BaseSpace, Frame, InputId, Joint, JointFrame, Space};
use crate::dom::bindings::codegen::Bindings::XRHandBinding::XRHandJoint;
use crate::dom::bindings::codegen::Bindings::XRJointSpaceBinding::XRJointSpaceMethods;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrsession::{ApiPose, XRSession};
use crate::dom::xrspace::XRSpace;
#[dom_struct]
pub struct XRJointSpace {
xrspace: XRSpace,
#[ignore_malloc_size_of = "defined in rust-webxr"]
#[no_trace]
input: InputId,
#[ignore_malloc_size_of = "defined in rust-webxr"]
#[no_trace]
joint: Joint,
hand_joint: XRHandJoint,
}
impl XRJointSpace {
pub fn new_inherited(
session: &XRSession,
input: InputId,
joint: Joint,
hand_joint: XRHandJoint,
) -> XRJointSpace {
XRJointSpace {
xrspace: XRSpace::new_inherited(session),
input,
joint,
hand_joint,
}
}
#[allow(unused)]
pub fn new(
global: &GlobalScope,
session: &XRSession,
input: InputId,
joint: Joint,
hand_joint: XRHandJoint,
) -> DomRoot<XRJointSpace> {
reflect_dom_object(
Box::new(Self::new_inherited(session, input, joint, hand_joint)),
global,
)
}
pub fn space(&self) -> Space {
let base = BaseSpace::Joint(self.input, self.joint);
let offset = RigidTransform3D::identity();
Space { base, offset }
}
pub fn frame<'a>(&self, frame: &'a Frame) -> Option<&'a JointFrame> {
frame
.inputs
.iter()
.find(|i| i.id == self.input)
.and_then(|i| i.hand.as_ref())
.and_then(|h| h.get(self.joint))
}
pub fn get_pose(&self, frame: &Frame) -> Option<ApiPose> {
self.frame(frame).map(|f| f.pose).map(|t| t.cast_unit())
}
}
impl XRJointSpaceMethods<crate::DomTypeHolder> for XRJointSpace {
/// <https://www.w3.org/TR/webxr-hand-input-1/#xrjointspace-jointname>
fn JointName(&self) -> XRHandJoint {
self.hand_joint
}
}

View file

@ -0,0 +1,77 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use canvas_traits::webgl::WebGLContextId;
use dom_struct::dom_struct;
use webxr_api::LayerId;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::Dom;
use crate::dom::eventtarget::EventTarget;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrsession::XRSession;
use crate::dom::xrwebgllayer::XRWebGLLayer;
#[dom_struct]
pub struct XRLayer {
event_target: EventTarget,
session: Dom<XRSession>,
context: Dom<WebGLRenderingContext>,
/// If none, the session is inline (the composition disabled flag is true)
/// and this is a XRWebGLLayer.
#[ignore_malloc_size_of = "Layer ids don't heap-allocate"]
#[no_trace]
layer_id: Option<LayerId>,
}
impl XRLayer {
#[allow(dead_code)]
pub fn new_inherited(
session: &XRSession,
context: &WebGLRenderingContext,
layer_id: Option<LayerId>,
) -> XRLayer {
XRLayer {
event_target: EventTarget::new_inherited(),
session: Dom::from_ref(session),
context: Dom::from_ref(context),
layer_id,
}
}
pub(crate) fn layer_id(&self) -> Option<LayerId> {
self.layer_id
}
pub(crate) fn context_id(&self) -> WebGLContextId {
self.context.context_id()
}
pub(crate) fn context(&self) -> &WebGLRenderingContext {
&self.context
}
pub(crate) fn session(&self) -> &XRSession {
&self.session
}
pub fn begin_frame(&self, frame: &XRFrame) -> Option<()> {
// TODO: Implement this for other layer types
if let Some(this) = self.downcast::<XRWebGLLayer>() {
this.begin_frame(frame)
} else {
unimplemented!()
}
}
pub fn end_frame(&self, frame: &XRFrame) -> Option<()> {
// TODO: Implement this for other layer types
if let Some(this) = self.downcast::<XRWebGLLayer>() {
this.end_frame(frame)
} else {
unimplemented!()
}
}
}

View file

@ -0,0 +1,77 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::XRLayerEventBinding::{
XRLayerEventInit, XRLayerEventMethods,
};
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::window::Window;
use crate::dom::xrlayer::XRLayer;
use crate::script_runtime::CanGc;
// https://w3c.github.io/uievents/#interface-uievent
#[dom_struct]
pub struct XRLayerEvent {
event: Event,
layer: Dom<XRLayer>,
}
impl XRLayerEvent {
pub fn new_inherited(layer: &XRLayer) -> XRLayerEvent {
XRLayerEvent {
event: Event::new_inherited(),
layer: Dom::from_ref(layer),
}
}
fn new(
window: &Window,
proto: Option<HandleObject>,
layer: &XRLayer,
can_gc: CanGc,
) -> DomRoot<XRLayerEvent> {
reflect_dom_object_with_proto(
Box::new(XRLayerEvent::new_inherited(layer)),
window,
proto,
can_gc,
)
}
}
impl XRLayerEventMethods<crate::DomTypeHolder> for XRLayerEvent {
// https://immersive-web.github.io/layers/#dom-xrlayerevent-xrlayerevent
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &XRLayerEventInit,
) -> DomRoot<XRLayerEvent> {
let event = XRLayerEvent::new(window, proto, &init.layer, can_gc);
let type_ = Atom::from(type_);
let bubbles = init.parent.bubbles;
let cancelable = init.parent.cancelable;
event.event.init_event(type_, bubbles, cancelable);
event
}
// https://immersive-web.github.io/layers/#dom-xrlayerevent-layer
fn Layer(&self) -> DomRoot<XRLayer> {
DomRoot::from_ref(&self.layer)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,101 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::XRMediaBindingBinding::XRMediaBinding_Binding::XRMediaBindingMethods;
use crate::dom::bindings::codegen::Bindings::XRMediaBindingBinding::XRMediaLayerInit;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::htmlvideoelement::HTMLVideoElement;
use crate::dom::window::Window;
use crate::dom::xrcylinderlayer::XRCylinderLayer;
use crate::dom::xrequirectlayer::XREquirectLayer;
use crate::dom::xrquadlayer::XRQuadLayer;
use crate::dom::xrsession::XRSession;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRMediaBinding {
reflector: Reflector,
session: Dom<XRSession>,
}
impl XRMediaBinding {
pub fn new_inherited(session: &XRSession) -> XRMediaBinding {
XRMediaBinding {
reflector: Reflector::new(),
session: Dom::from_ref(session),
}
}
fn new(
global: &Window,
proto: Option<HandleObject>,
session: &XRSession,
can_gc: CanGc,
) -> DomRoot<XRMediaBinding> {
reflect_dom_object_with_proto(
Box::new(XRMediaBinding::new_inherited(session)),
global,
proto,
can_gc,
)
}
}
impl XRMediaBindingMethods<crate::DomTypeHolder> for XRMediaBinding {
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-xrmediabinding>
fn Constructor(
global: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
session: &XRSession,
) -> Fallible<DomRoot<XRMediaBinding>> {
// Step 1.
if session.is_ended() {
return Err(Error::InvalidState);
}
// Step 2.
if !session.is_immersive() {
return Err(Error::InvalidState);
}
// Steps 3-5.
Ok(XRMediaBinding::new(global, proto, session, can_gc))
}
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-createquadlayer>
fn CreateQuadLayer(
&self,
_: &HTMLVideoElement,
_: &XRMediaLayerInit,
) -> Fallible<DomRoot<XRQuadLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-createcylinderlayer>
fn CreateCylinderLayer(
&self,
_: &HTMLVideoElement,
_: &XRMediaLayerInit,
) -> Fallible<DomRoot<XRCylinderLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrmediabinding-createequirectlayer>
fn CreateEquirectLayer(
&self,
_: &HTMLVideoElement,
_: &XRMediaLayerInit,
) -> Fallible<DomRoot<XREquirectLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
}

View file

@ -0,0 +1,65 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::XRPoseBinding::XRPoseMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::dompointreadonly::DOMPointReadOnly;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::dom::xrsession::ApiRigidTransform;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRPose {
reflector_: Reflector,
transform: Dom<XRRigidTransform>,
}
impl XRPose {
pub fn new_inherited(transform: &XRRigidTransform) -> XRPose {
XRPose {
reflector_: Reflector::new(),
transform: Dom::from_ref(transform),
}
}
#[allow(unused)]
pub fn new(
global: &GlobalScope,
transform: ApiRigidTransform,
can_gc: CanGc,
) -> DomRoot<XRPose> {
let transform = XRRigidTransform::new(global, transform, can_gc);
reflect_dom_object(Box::new(XRPose::new_inherited(&transform)), global)
}
}
impl XRPoseMethods<crate::DomTypeHolder> for XRPose {
/// <https://immersive-web.github.io/webxr/#dom-xrpose-transform>
fn Transform(&self) -> DomRoot<XRRigidTransform> {
DomRoot::from_ref(&self.transform)
}
/// <https://www.w3.org/TR/webxr/#dom-xrpose-linearvelocity>
fn GetLinearVelocity(&self) -> Option<DomRoot<DOMPointReadOnly>> {
// TODO: Expose from webxr crate
None
}
/// <https://www.w3.org/TR/webxr/#dom-xrpose-angularvelocity>
fn GetAngularVelocity(&self) -> Option<DomRoot<DOMPointReadOnly>> {
// TODO: Expose from webxr crate
None
}
/// <https://www.w3.org/TR/webxr/#dom-xrpose-emulatedposition>
fn EmulatedPosition(&self) -> bool {
// There are currently no instances in which we would need to rely
// on emulation for reporting pose, so return false.
false
}
}

View file

@ -0,0 +1,12 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::xrcompositionlayer::XRCompositionLayer;
#[dom_struct]
pub struct XRProjectionLayer {
composition_layer: XRCompositionLayer,
}

View file

@ -0,0 +1,12 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::xrcompositionlayer::XRCompositionLayer;
#[dom_struct]
pub struct XRQuadLayer {
composition_layer: XRCompositionLayer,
}

View file

@ -0,0 +1,174 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::{Angle, RigidTransform3D, Rotation3D, Vector3D};
use js::rust::HandleObject;
use js::typedarray::{Float32, Float32Array};
use webxr_api::{ApiSpace, Ray};
use crate::dom::bindings::buffer_source::HeapBufferSource;
use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
use crate::dom::bindings::codegen::Bindings::XRRayBinding::{XRRayDirectionInit, XRRayMethods};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::dompointreadonly::DOMPointReadOnly;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRRay {
reflector_: Reflector,
#[ignore_malloc_size_of = "defined in webxr"]
#[no_trace]
ray: Ray<ApiSpace>,
#[ignore_malloc_size_of = "defined in mozjs"]
matrix: HeapBufferSource<Float32>,
}
impl XRRay {
fn new_inherited(ray: Ray<ApiSpace>) -> XRRay {
XRRay {
reflector_: Reflector::new(),
ray,
matrix: HeapBufferSource::default(),
}
}
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
ray: Ray<ApiSpace>,
can_gc: CanGc,
) -> DomRoot<XRRay> {
reflect_dom_object_with_proto(Box::new(XRRay::new_inherited(ray)), global, proto, can_gc)
}
pub fn ray(&self) -> Ray<ApiSpace> {
self.ray
}
}
impl XRRayMethods<crate::DomTypeHolder> for XRRay {
/// <https://immersive-web.github.io/hit-test/#dom-xrray-xrray>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
origin: &DOMPointInit,
direction: &XRRayDirectionInit,
) -> Fallible<DomRoot<Self>> {
if origin.w != 1.0 {
return Err(Error::Type("Origin w coordinate must be 1".into()));
}
if *direction.w != 0.0 {
return Err(Error::Type("Direction w coordinate must be 0".into()));
}
if *direction.x == 0.0 && *direction.y == 0.0 && *direction.z == 0.0 {
return Err(Error::Type(
"Direction vector cannot have zero length".into(),
));
}
let origin = Vector3D::new(origin.x as f32, origin.y as f32, origin.z as f32);
let direction = Vector3D::new(
*direction.x as f32,
*direction.y as f32,
*direction.z as f32,
)
.normalize();
Ok(Self::new(
&window.global(),
proto,
Ray { origin, direction },
can_gc,
))
}
/// <https://immersive-web.github.io/hit-test/#dom-xrray-xrray-transform>
fn Constructor_(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
transform: &XRRigidTransform,
) -> Fallible<DomRoot<Self>> {
let transform = transform.transform();
let origin = transform.translation;
let direction = transform
.rotation
.transform_vector3d(Vector3D::new(0., 0., -1.));
Ok(Self::new(
&window.global(),
proto,
Ray { origin, direction },
can_gc,
))
}
/// <https://immersive-web.github.io/hit-test/#dom-xrray-origin>
fn Origin(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
DOMPointReadOnly::new(
&self.global(),
self.ray.origin.x as f64,
self.ray.origin.y as f64,
self.ray.origin.z as f64,
1.,
can_gc,
)
}
/// <https://immersive-web.github.io/hit-test/#dom-xrray-direction>
fn Direction(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
DOMPointReadOnly::new(
&self.global(),
self.ray.direction.x as f64,
self.ray.direction.y as f64,
self.ray.direction.z as f64,
0.,
can_gc,
)
}
/// <https://immersive-web.github.io/hit-test/#dom-xrray-matrix>
fn Matrix(&self, _cx: JSContext) -> Float32Array {
// https://immersive-web.github.io/hit-test/#xrray-obtain-the-matrix
if !self.matrix.is_initialized() {
// Step 1
let z = Vector3D::new(0., 0., -1.);
// Step 2
let axis = z.cross(self.ray.direction);
// Step 3
let cos_angle = z.dot(self.ray.direction);
// Step 4
let rotation = if cos_angle > -1. && cos_angle < 1. {
Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos()))
} else if cos_angle == -1. {
let axis = Vector3D::new(1., 0., 0.);
Rotation3D::around_axis(axis, Angle::radians(cos_angle.acos()))
} else {
Rotation3D::identity()
};
// Step 5
let translation = self.ray.origin;
// Step 6
// According to the spec all matrices are column-major,
// however euclid uses row vectors so we use .to_array()
let arr = RigidTransform3D::new(rotation, translation)
.to_transform()
.to_array();
self.matrix
.set_data(_cx, &arr)
.expect("Failed to set matrix data on XRRAy.")
}
self.matrix
.get_buffer()
.expect("Failed to get matrix from XRRay.")
}
}

View file

@ -0,0 +1,153 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::{Point2D, RigidTransform3D};
use webxr_api::{self, Floor, Frame, Space};
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::{
XRReferenceSpaceMethods, XRReferenceSpaceType,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::dom::xrsession::{cast_transform, ApiPose, BaseTransform, XRSession};
use crate::dom::xrspace::XRSpace;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRReferenceSpace {
xrspace: XRSpace,
offset: Dom<XRRigidTransform>,
ty: XRReferenceSpaceType,
}
impl XRReferenceSpace {
pub fn new_inherited(
session: &XRSession,
offset: &XRRigidTransform,
ty: XRReferenceSpaceType,
) -> XRReferenceSpace {
XRReferenceSpace {
xrspace: XRSpace::new_inherited(session),
offset: Dom::from_ref(offset),
ty,
}
}
#[allow(unused)]
pub fn new(
global: &GlobalScope,
session: &XRSession,
ty: XRReferenceSpaceType,
can_gc: CanGc,
) -> DomRoot<XRReferenceSpace> {
let offset = XRRigidTransform::identity(global, can_gc);
Self::new_offset(global, session, ty, &offset)
}
#[allow(unused)]
pub fn new_offset(
global: &GlobalScope,
session: &XRSession,
ty: XRReferenceSpaceType,
offset: &XRRigidTransform,
) -> DomRoot<XRReferenceSpace> {
reflect_dom_object(
Box::new(XRReferenceSpace::new_inherited(session, offset, ty)),
global,
)
}
pub fn space(&self) -> Space {
let base = match self.ty {
XRReferenceSpaceType::Local => webxr_api::BaseSpace::Local,
XRReferenceSpaceType::Viewer => webxr_api::BaseSpace::Viewer,
XRReferenceSpaceType::Local_floor => webxr_api::BaseSpace::Floor,
XRReferenceSpaceType::Bounded_floor => webxr_api::BaseSpace::BoundedFloor,
_ => panic!("unsupported reference space found"),
};
let offset = self.offset.transform();
Space { base, offset }
}
pub fn ty(&self) -> XRReferenceSpaceType {
self.ty
}
}
impl XRReferenceSpaceMethods<crate::DomTypeHolder> for XRReferenceSpace {
/// <https://immersive-web.github.io/webxr/#dom-xrreferencespace-getoffsetreferencespace>
fn GetOffsetReferenceSpace(&self, new: &XRRigidTransform, can_gc: CanGc) -> DomRoot<Self> {
let offset = new.transform().then(&self.offset.transform());
let offset = XRRigidTransform::new(&self.global(), offset, can_gc);
Self::new_offset(
&self.global(),
self.upcast::<XRSpace>().session(),
self.ty,
&offset,
)
}
// https://www.w3.org/TR/webxr/#dom-xrreferencespace-onreset
event_handler!(reset, GetOnreset, SetOnreset);
}
impl XRReferenceSpace {
/// Get a transform that can be used to locate the base space
///
/// This is equivalent to `get_pose(self).inverse()` (in column vector notation),
/// but with better types
pub fn get_base_transform(&self, base_pose: &Frame) -> Option<BaseTransform> {
let pose = self.get_pose(base_pose)?;
Some(pose.inverse().cast_unit())
}
/// Gets pose represented by this space
///
/// The reference origin used is common between all
/// get_pose calls for spaces from the same device, so this can be used to compare
/// with other spaces
pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> {
let pose = self.get_unoffset_pose(base_pose)?;
let offset = self.offset.transform();
// pose is a transform from the unoffset space to native space,
// offset is a transform from offset space to unoffset space,
// we want a transform from unoffset space to native space,
// which is pose * offset in column vector notation
Some(offset.then(&pose))
}
/// Gets pose represented by this space
///
/// Does not apply originOffset, use get_viewer_pose instead if you need it
pub fn get_unoffset_pose(&self, base_pose: &Frame) -> Option<ApiPose> {
match self.ty {
XRReferenceSpaceType::Local => {
// The eye-level pose is basically whatever the headset pose was at t=0, which
// for most devices is (0, 0, 0)
Some(RigidTransform3D::identity())
},
XRReferenceSpaceType::Local_floor | XRReferenceSpaceType::Bounded_floor => {
let native_to_floor = self
.upcast::<XRSpace>()
.session()
.with_session(|s| s.floor_transform())?;
Some(cast_transform(native_to_floor.inverse()))
},
XRReferenceSpaceType::Viewer => {
Some(cast_transform(base_pose.pose.as_ref()?.transform))
},
_ => unimplemented!(),
}
}
pub fn get_bounds(&self) -> Option<Vec<Point2D<f32, Floor>>> {
self.upcast::<XRSpace>()
.session()
.with_session(|s| s.reference_space_bounds())
}
}

View file

@ -0,0 +1,121 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceEventBinding::{
XRReferenceSpaceEventInit, XRReferenceSpaceEventMethods,
};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRReferenceSpaceEvent {
event: Event,
space: Dom<XRReferenceSpace>,
transform: Option<Dom<XRRigidTransform>>,
}
impl XRReferenceSpaceEvent {
#[allow(crown::unrooted_must_root)]
fn new_inherited(
space: &XRReferenceSpace,
transform: Option<&XRRigidTransform>,
) -> XRReferenceSpaceEvent {
XRReferenceSpaceEvent {
event: Event::new_inherited(),
space: Dom::from_ref(space),
transform: transform.map(Dom::from_ref),
}
}
pub fn new(
global: &GlobalScope,
type_: Atom,
bubbles: bool,
cancelable: bool,
space: &XRReferenceSpace,
transform: Option<&XRRigidTransform>,
can_gc: CanGc,
) -> DomRoot<XRReferenceSpaceEvent> {
Self::new_with_proto(
global, None, type_, bubbles, cancelable, space, transform, can_gc,
)
}
#[allow(clippy::too_many_arguments)]
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
space: &XRReferenceSpace,
transform: Option<&XRRigidTransform>,
can_gc: CanGc,
) -> DomRoot<XRReferenceSpaceEvent> {
let trackevent = reflect_dom_object_with_proto(
Box::new(XRReferenceSpaceEvent::new_inherited(space, transform)),
global,
proto,
can_gc,
);
{
let event = trackevent.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
trackevent
}
}
impl XRReferenceSpaceEventMethods<crate::DomTypeHolder> for XRReferenceSpaceEvent {
/// <https://www.w3.org/TR/webxr/#dom-xrreferencespaceevent-xrreferencespaceevent>
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &XRReferenceSpaceEventInit,
) -> Fallible<DomRoot<XRReferenceSpaceEvent>> {
Ok(XRReferenceSpaceEvent::new_with_proto(
&window.global(),
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.referenceSpace,
init.transform.as_deref(),
can_gc,
))
}
/// <https://www.w3.org/TR/webxr/#dom-xrreferencespaceeventinit-session>
fn ReferenceSpace(&self) -> DomRoot<XRReferenceSpace> {
DomRoot::from_ref(&*self.space)
}
/// <https://www.w3.org/TR/webxr/#dom-xrreferencespaceevent-transform>
fn GetTransform(&self) -> Option<DomRoot<XRRigidTransform>> {
self.transform
.as_ref()
.map(|transform| DomRoot::from_ref(&**transform))
}
/// <https://dom.spec.whatwg.org/#dom-event-istrusted>
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,155 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use js::rust::MutableHandleValue;
use webxr_api::SubImages;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::XRRenderStateBinding::XRRenderStateMethods;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::utils::to_frozen_array;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrlayer::XRLayer;
use crate::dom::xrwebgllayer::XRWebGLLayer;
use crate::script_runtime::JSContext;
#[dom_struct]
pub struct XRRenderState {
reflector_: Reflector,
depth_near: Cell<f64>,
depth_far: Cell<f64>,
inline_vertical_fov: Cell<Option<f64>>,
base_layer: MutNullableDom<XRWebGLLayer>,
layers: DomRefCell<Vec<Dom<XRLayer>>>,
}
impl XRRenderState {
pub fn new_inherited(
depth_near: f64,
depth_far: f64,
inline_vertical_fov: Option<f64>,
layer: Option<&XRWebGLLayer>,
layers: Vec<&XRLayer>,
) -> XRRenderState {
debug_assert!(layer.is_none() || layers.is_empty());
XRRenderState {
reflector_: Reflector::new(),
depth_near: Cell::new(depth_near),
depth_far: Cell::new(depth_far),
inline_vertical_fov: Cell::new(inline_vertical_fov),
base_layer: MutNullableDom::new(layer),
layers: DomRefCell::new(layers.into_iter().map(Dom::from_ref).collect()),
}
}
pub fn new(
global: &GlobalScope,
depth_near: f64,
depth_far: f64,
inline_vertical_fov: Option<f64>,
layer: Option<&XRWebGLLayer>,
layers: Vec<&XRLayer>,
) -> DomRoot<XRRenderState> {
reflect_dom_object(
Box::new(XRRenderState::new_inherited(
depth_near,
depth_far,
inline_vertical_fov,
layer,
layers,
)),
global,
)
}
pub fn clone_object(&self) -> DomRoot<Self> {
XRRenderState::new(
&self.global(),
self.depth_near.get(),
self.depth_far.get(),
self.inline_vertical_fov.get(),
self.base_layer.get().as_deref(),
self.layers.borrow().iter().map(|x| &**x).collect(),
)
}
pub fn set_depth_near(&self, depth: f64) {
self.depth_near.set(depth)
}
pub fn set_depth_far(&self, depth: f64) {
self.depth_far.set(depth)
}
pub fn set_inline_vertical_fov(&self, fov: f64) {
debug_assert!(self.inline_vertical_fov.get().is_some());
self.inline_vertical_fov.set(Some(fov))
}
pub fn set_base_layer(&self, layer: Option<&XRWebGLLayer>) {
self.base_layer.set(layer)
}
pub fn set_layers(&self, layers: Vec<&XRLayer>) {
*self.layers.borrow_mut() = layers.into_iter().map(Dom::from_ref).collect();
}
pub fn with_layers<F, R>(&self, f: F) -> R
where
F: FnOnce(&[Dom<XRLayer>]) -> R,
{
let layers = self.layers.borrow();
f(&layers)
}
pub fn has_sub_images(&self, sub_images: &[SubImages]) -> bool {
if let Some(base_layer) = self.base_layer.get() {
match sub_images.len() {
// For inline sessions, there may be a base layer, but it won't have a framebuffer
0 => base_layer.layer_id().is_none(),
// For immersive sessions, the base layer will have a framebuffer,
// so we make sure the layer id's match up
1 => base_layer.layer_id() == Some(sub_images[0].layer_id),
_ => false,
}
} else {
// The layers API is only for immersive sessions
let layers = self.layers.borrow();
sub_images.len() == layers.len() &&
sub_images
.iter()
.zip(layers.iter())
.all(|(sub_image, layer)| Some(sub_image.layer_id) == layer.layer_id())
}
}
}
impl XRRenderStateMethods<crate::DomTypeHolder> for XRRenderState {
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthnear>
fn DepthNear(&self) -> Finite<f64> {
Finite::wrap(self.depth_near.get())
}
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-depthfar>
fn DepthFar(&self) -> Finite<f64> {
Finite::wrap(self.depth_far.get())
}
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-inlineverticalfieldofview>
fn GetInlineVerticalFieldOfView(&self) -> Option<Finite<f64>> {
self.inline_vertical_fov.get().map(Finite::wrap)
}
/// <https://immersive-web.github.io/webxr/#dom-xrrenderstate-baselayer>
fn GetBaseLayer(&self) -> Option<DomRoot<XRWebGLLayer>> {
self.base_layer.get()
}
/// <https://immersive-web.github.io/layers/#dom-xrrenderstate-layers>
fn Layers(&self, cx: JSContext, retval: MutableHandleValue) {
// TODO: cache this array?
let layers = self.layers.borrow();
let layers: Vec<&XRLayer> = layers.iter().map(|x| &**x).collect();
to_frozen_array(&layers[..], cx, retval)
}
}

View file

@ -0,0 +1,188 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::{RigidTransform3D, Rotation3D, Vector3D};
use js::rust::HandleObject;
use js::typedarray::{Float32, Float32Array};
use crate::dom::bindings::buffer_source::HeapBufferSource;
use crate::dom::bindings::codegen::Bindings::DOMPointBinding::DOMPointInit;
use crate::dom::bindings::codegen::Bindings::XRRigidTransformBinding::XRRigidTransformMethods;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject, Reflector};
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::dompointreadonly::DOMPointReadOnly;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::dom::xrsession::ApiRigidTransform;
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRRigidTransform {
reflector_: Reflector,
position: MutNullableDom<DOMPointReadOnly>,
orientation: MutNullableDom<DOMPointReadOnly>,
#[ignore_malloc_size_of = "defined in euclid"]
#[no_trace]
transform: ApiRigidTransform,
inverse: MutNullableDom<XRRigidTransform>,
#[ignore_malloc_size_of = "defined in mozjs"]
matrix: HeapBufferSource<Float32>,
}
impl XRRigidTransform {
fn new_inherited(transform: ApiRigidTransform) -> XRRigidTransform {
XRRigidTransform {
reflector_: Reflector::new(),
position: MutNullableDom::default(),
orientation: MutNullableDom::default(),
transform,
inverse: MutNullableDom::default(),
matrix: HeapBufferSource::default(),
}
}
pub fn new(
global: &GlobalScope,
transform: ApiRigidTransform,
can_gc: CanGc,
) -> DomRoot<XRRigidTransform> {
Self::new_with_proto(global, None, transform, can_gc)
}
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
transform: ApiRigidTransform,
can_gc: CanGc,
) -> DomRoot<XRRigidTransform> {
reflect_dom_object_with_proto(
Box::new(XRRigidTransform::new_inherited(transform)),
global,
proto,
can_gc,
)
}
pub fn identity(window: &GlobalScope, can_gc: CanGc) -> DomRoot<XRRigidTransform> {
let transform = RigidTransform3D::identity();
XRRigidTransform::new(window, transform, can_gc)
}
}
impl XRRigidTransformMethods<crate::DomTypeHolder> for XRRigidTransform {
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-xrrigidtransform
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
position: &DOMPointInit,
orientation: &DOMPointInit,
) -> Fallible<DomRoot<Self>> {
if position.w != 1.0 {
return Err(Error::Type(format!(
"XRRigidTransform must be constructed with a position that has a w value of of 1.0, not {}",
position.w
)));
}
if !position.x.is_finite() ||
!position.y.is_finite() ||
!position.z.is_finite() ||
!position.w.is_finite()
{
return Err(Error::Type(
"Position must not contain non-finite values".into(),
));
}
if !orientation.x.is_finite() ||
!orientation.y.is_finite() ||
!orientation.z.is_finite() ||
!orientation.w.is_finite()
{
return Err(Error::Type(
"Orientation must not contain non-finite values".into(),
));
}
let translate = Vector3D::new(position.x as f32, position.y as f32, position.z as f32);
let rotate = Rotation3D::unit_quaternion(
orientation.x as f32,
orientation.y as f32,
orientation.z as f32,
orientation.w as f32,
);
if !rotate.i.is_finite() {
// if quaternion has zero norm, we'll get an infinite or NaN
// value for each element. This is preferable to checking for zero.
return Err(Error::InvalidState);
}
let transform = RigidTransform3D::new(rotate, translate);
Ok(XRRigidTransform::new_with_proto(
&window.global(),
proto,
transform,
can_gc,
))
}
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-position
fn Position(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
self.position.or_init(|| {
let t = &self.transform.translation;
DOMPointReadOnly::new(
&self.global(),
t.x.into(),
t.y.into(),
t.z.into(),
1.0,
can_gc,
)
})
}
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-orientation
fn Orientation(&self, can_gc: CanGc) -> DomRoot<DOMPointReadOnly> {
self.orientation.or_init(|| {
let r = &self.transform.rotation;
DOMPointReadOnly::new(
&self.global(),
r.i.into(),
r.j.into(),
r.k.into(),
r.r.into(),
can_gc,
)
})
}
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-inverse
fn Inverse(&self, can_gc: CanGc) -> DomRoot<XRRigidTransform> {
self.inverse.or_init(|| {
let transform = XRRigidTransform::new(&self.global(), self.transform.inverse(), can_gc);
transform.inverse.set(Some(self));
transform
})
}
// https://immersive-web.github.io/webxr/#dom-xrrigidtransform-matrix
fn Matrix(&self, _cx: JSContext) -> Float32Array {
if !self.matrix.is_initialized() {
self.matrix
.set_data(_cx, &self.transform.to_transform().to_array())
.expect("Failed to set on data on transform's internal matrix.")
}
self.matrix
.get_buffer()
.expect("Failed to get transform's internal matrix.")
}
}
impl XRRigidTransform {
/// <https://immersive-web.github.io/webxr/#dom-xrpose-transform>
pub fn transform(&self) -> ApiRigidTransform {
self.transform
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,100 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use servo_atoms::Atom;
use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
use crate::dom::bindings::codegen::Bindings::XRSessionEventBinding::{self, XRSessionEventMethods};
use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
use crate::dom::event::Event;
use crate::dom::globalscope::GlobalScope;
use crate::dom::window::Window;
use crate::dom::xrsession::XRSession;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRSessionEvent {
event: Event,
session: Dom<XRSession>,
}
impl XRSessionEvent {
#[allow(crown::unrooted_must_root)]
fn new_inherited(session: &XRSession) -> XRSessionEvent {
XRSessionEvent {
event: Event::new_inherited(),
session: Dom::from_ref(session),
}
}
pub fn new(
global: &GlobalScope,
type_: Atom,
bubbles: bool,
cancelable: bool,
session: &XRSession,
can_gc: CanGc,
) -> DomRoot<XRSessionEvent> {
Self::new_with_proto(global, None, type_, bubbles, cancelable, session, can_gc)
}
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
type_: Atom,
bubbles: bool,
cancelable: bool,
session: &XRSession,
can_gc: CanGc,
) -> DomRoot<XRSessionEvent> {
let trackevent = reflect_dom_object_with_proto(
Box::new(XRSessionEvent::new_inherited(session)),
global,
proto,
can_gc,
);
{
let event = trackevent.upcast::<Event>();
event.init_event(type_, bubbles, cancelable);
}
trackevent
}
}
impl XRSessionEventMethods<crate::DomTypeHolder> for XRSessionEvent {
// https://immersive-web.github.io/webxr/#dom-xrsessionevent-xrsessionevent
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
type_: DOMString,
init: &XRSessionEventBinding::XRSessionEventInit,
) -> Fallible<DomRoot<XRSessionEvent>> {
Ok(XRSessionEvent::new_with_proto(
&window.global(),
proto,
Atom::from(type_),
init.parent.bubbles,
init.parent.cancelable,
&init.session,
can_gc,
))
}
// https://immersive-web.github.io/webxr/#dom-xrsessioneventinit-session
fn Session(&self) -> DomRoot<XRSession> {
DomRoot::from_ref(&*self.session)
}
// https://dom.spec.whatwg.org/#dom-event-istrusted
fn IsTrusted(&self) -> bool {
self.event.IsTrusted()
}
}

View file

@ -0,0 +1,120 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::RigidTransform3D;
use webxr_api::{BaseSpace, Frame, Space};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrinputsource::XRInputSource;
use crate::dom::xrjointspace::XRJointSpace;
use crate::dom::xrreferencespace::XRReferenceSpace;
use crate::dom::xrsession::{cast_transform, ApiPose, XRSession};
#[dom_struct]
pub struct XRSpace {
eventtarget: EventTarget,
session: Dom<XRSession>,
input_source: MutNullableDom<XRInputSource>,
/// If we're an input space, are we an aim space or a grip space?
is_grip_space: bool,
}
impl XRSpace {
pub fn new_inherited(session: &XRSession) -> XRSpace {
XRSpace {
eventtarget: EventTarget::new_inherited(),
session: Dom::from_ref(session),
input_source: Default::default(),
is_grip_space: false,
}
}
fn new_inputspace_inner(
session: &XRSession,
input: &XRInputSource,
is_grip_space: bool,
) -> XRSpace {
XRSpace {
eventtarget: EventTarget::new_inherited(),
session: Dom::from_ref(session),
input_source: MutNullableDom::new(Some(input)),
is_grip_space,
}
}
pub fn new_inputspace(
global: &GlobalScope,
session: &XRSession,
input: &XRInputSource,
is_grip_space: bool,
) -> DomRoot<XRSpace> {
reflect_dom_object(
Box::new(XRSpace::new_inputspace_inner(session, input, is_grip_space)),
global,
)
}
pub fn space(&self) -> Space {
if let Some(rs) = self.downcast::<XRReferenceSpace>() {
rs.space()
} else if let Some(j) = self.downcast::<XRJointSpace>() {
j.space()
} else if let Some(source) = self.input_source.get() {
let base = if self.is_grip_space {
BaseSpace::Grip(source.id())
} else {
BaseSpace::TargetRay(source.id())
};
Space {
base,
offset: RigidTransform3D::identity(),
}
} else {
panic!("invalid space found")
}
}
}
impl XRSpace {
/// Gets pose represented by this space
///
/// The reference origin used is common between all
/// get_pose calls for spaces from the same device, so this can be used to compare
/// with other spaces
pub fn get_pose(&self, base_pose: &Frame) -> Option<ApiPose> {
if let Some(reference) = self.downcast::<XRReferenceSpace>() {
reference.get_pose(base_pose)
} else if let Some(joint) = self.downcast::<XRJointSpace>() {
joint.get_pose(base_pose)
} else if let Some(source) = self.input_source.get() {
// XXXManishearth we should be able to request frame information
// for inputs when necessary instead of always loading it
//
// Also, the below code is quadratic, so this API may need an overhaul anyway
let id = source.id();
// XXXManishearth once we have dynamic inputs we'll need to handle this better
let frame = base_pose
.inputs
.iter()
.find(|i| i.id == id)
.expect("no input found");
if self.is_grip_space {
frame.grip_origin.map(cast_transform)
} else {
frame.target_ray_origin.map(cast_transform)
}
} else {
unreachable!()
}
}
pub fn session(&self) -> &XRSession {
&self.session
}
}

View file

@ -0,0 +1,23 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use crate::dom::bindings::codegen::Bindings::XRSubImageBinding::XRSubImage_Binding::XRSubImageMethods;
use crate::dom::bindings::reflector::Reflector;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::xrviewport::XRViewport;
#[dom_struct]
pub struct XRSubImage {
reflector: Reflector,
viewport: Dom<XRViewport>,
}
impl XRSubImageMethods<crate::DomTypeHolder> for XRSubImage {
/// <https://immersive-web.github.io/layers/#dom-xrsubimage-viewport>
fn Viewport(&self) -> DomRoot<XRViewport> {
DomRoot::from_ref(&self.viewport)
}
}

View file

@ -0,0 +1,334 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::rc::Rc;
use base::id::PipelineId;
use dom_struct::dom_struct;
use ipc_channel::ipc::{self as ipc_crate, IpcReceiver};
use ipc_channel::router::ROUTER;
use profile_traits::ipc;
use servo_config::pref;
use webxr_api::{Error as XRError, Frame, Session, SessionInit, SessionMode};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::XRSystemBinding::{
XRSessionInit, XRSessionMode, XRSystemMethods,
};
use crate::dom::bindings::conversions::{ConversionResult, FromJSValConvertible};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::eventtarget::EventTarget;
use crate::dom::gamepad::Gamepad;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::window::Window;
use crate::dom::xrsession::XRSession;
use crate::dom::xrtest::XRTest;
use crate::realms::InRealm;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
use crate::task_source::TaskSource;
#[dom_struct]
pub struct XRSystem {
eventtarget: EventTarget,
gamepads: DomRefCell<Vec<Dom<Gamepad>>>,
pending_immersive_session: Cell<bool>,
active_immersive_session: MutNullableDom<XRSession>,
active_inline_sessions: DomRefCell<Vec<Dom<XRSession>>>,
test: MutNullableDom<XRTest>,
#[no_trace]
pipeline: PipelineId,
}
impl XRSystem {
fn new_inherited(pipeline: PipelineId) -> XRSystem {
XRSystem {
eventtarget: EventTarget::new_inherited(),
gamepads: DomRefCell::new(Vec::new()),
pending_immersive_session: Cell::new(false),
active_immersive_session: Default::default(),
active_inline_sessions: DomRefCell::new(Vec::new()),
test: Default::default(),
pipeline,
}
}
pub fn new(window: &Window) -> DomRoot<XRSystem> {
reflect_dom_object(
Box::new(XRSystem::new_inherited(window.pipeline_id())),
window,
)
}
pub fn pending_or_active_session(&self) -> bool {
self.pending_immersive_session.get() || self.active_immersive_session.get().is_some()
}
pub fn set_pending(&self) {
self.pending_immersive_session.set(true)
}
pub fn set_active_immersive_session(&self, session: &XRSession) {
// XXXManishearth when we support non-immersive (inline) sessions we should
// ensure they never reach these codepaths
self.pending_immersive_session.set(false);
self.active_immersive_session.set(Some(session))
}
/// <https://immersive-web.github.io/webxr/#ref-for-eventdef-xrsession-end>
pub fn end_session(&self, session: &XRSession) {
// Step 3
if let Some(active) = self.active_immersive_session.get() {
if Dom::from_ref(&*active) == Dom::from_ref(session) {
self.active_immersive_session.set(None);
// Dirty the canvas, since it has been skipping this step whilst in immersive
// mode
session.dirty_layers();
}
}
self.active_inline_sessions
.borrow_mut()
.retain(|sess| Dom::from_ref(&**sess) != Dom::from_ref(session));
}
}
impl From<XRSessionMode> for SessionMode {
fn from(mode: XRSessionMode) -> SessionMode {
match mode {
XRSessionMode::Immersive_vr => SessionMode::ImmersiveVR,
XRSessionMode::Immersive_ar => SessionMode::ImmersiveAR,
XRSessionMode::Inline => SessionMode::Inline,
}
}
}
impl XRSystemMethods<crate::DomTypeHolder> for XRSystem {
/// <https://immersive-web.github.io/webxr/#dom-xr-issessionsupported>
fn IsSessionSupported(&self, mode: XRSessionMode, can_gc: CanGc) -> Rc<Promise> {
// XXXManishearth this should select an XR device first
let promise = Promise::new(&self.global(), can_gc);
let mut trusted = Some(TrustedPromise::new(promise.clone()));
let global = self.global();
let window = global.as_window();
let (task_source, canceller) = window
.task_manager()
.dom_manipulation_task_source_with_canceller();
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
ROUTER.add_typed_route(
receiver.to_ipc_receiver(),
Box::new(move |message| {
// router doesn't know this is only called once
let trusted = if let Some(trusted) = trusted.take() {
trusted
} else {
error!("supportsSession callback called twice!");
return;
};
let message: Result<(), webxr_api::Error> = if let Ok(message) = message {
message
} else {
error!("supportsSession callback given incorrect payload");
return;
};
if let Ok(()) = message {
let _ =
task_source.queue_with_canceller(trusted.resolve_task(true), &canceller);
} else {
let _ =
task_source.queue_with_canceller(trusted.resolve_task(false), &canceller);
};
}),
);
if let Some(mut r) = window.webxr_registry() {
r.supports_session(mode.into(), sender);
}
promise
}
/// <https://immersive-web.github.io/webxr/#dom-xr-requestsession>
#[allow(unsafe_code)]
fn RequestSession(
&self,
mode: XRSessionMode,
init: RootedTraceableBox<XRSessionInit>,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let global = self.global();
let window = global.as_window();
let promise = Promise::new_in_current_realm(comp, can_gc);
if mode != XRSessionMode::Inline {
if !ScriptThread::is_user_interacting() {
if pref!(dom.webxr.unsafe_assume_user_intent) {
warn!("The dom.webxr.unsafe-assume-user-intent preference assumes user intent to enter WebXR.");
} else {
promise.reject_error(Error::Security);
return promise;
}
}
if self.pending_or_active_session() {
promise.reject_error(Error::InvalidState);
return promise;
}
self.set_pending();
}
let mut required_features = vec![];
let mut optional_features = vec![];
let cx = GlobalScope::get_cx();
if let Some(ref r) = init.requiredFeatures {
for feature in r {
unsafe {
if let Ok(ConversionResult::Success(s)) =
String::from_jsval(*cx, feature.handle(), ())
{
required_features.push(s)
} else {
warn!("Unable to convert required feature to string");
if mode != XRSessionMode::Inline {
self.pending_immersive_session.set(false);
}
promise.reject_error(Error::NotSupported);
return promise;
}
}
}
}
if let Some(ref o) = init.optionalFeatures {
for feature in o {
unsafe {
if let Ok(ConversionResult::Success(s)) =
String::from_jsval(*cx, feature.handle(), ())
{
optional_features.push(s)
} else {
warn!("Unable to convert optional feature to string");
}
}
}
}
if !required_features.contains(&"viewer".to_string()) {
required_features.push("viewer".to_string());
}
if !required_features.contains(&"local".to_string()) && mode != XRSessionMode::Inline {
required_features.push("local".to_string());
}
let init = SessionInit {
required_features,
optional_features,
first_person_observer_view: pref!(dom.webxr.first_person_observer_view),
};
let mut trusted = Some(TrustedPromise::new(promise.clone()));
let this = Trusted::new(self);
let (task_source, canceller) = window
.task_manager()
.dom_manipulation_task_source_with_canceller();
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
let (frame_sender, frame_receiver) = ipc_crate::channel().unwrap();
let mut frame_receiver = Some(frame_receiver);
ROUTER.add_typed_route(
receiver.to_ipc_receiver(),
Box::new(move |message| {
// router doesn't know this is only called once
let trusted = trusted.take().unwrap();
let this = this.clone();
let frame_receiver = frame_receiver.take().unwrap();
let message: Result<Session, webxr_api::Error> = if let Ok(message) = message {
message
} else {
error!("requestSession callback given incorrect payload");
return;
};
let _ = task_source.queue_with_canceller(
task!(request_session: move || {
this.root().session_obtained(message, trusted.root(), mode, frame_receiver);
}),
&canceller,
);
}),
);
if let Some(mut r) = window.webxr_registry() {
r.request_session(mode.into(), init, sender, frame_sender);
}
promise
}
// https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md
fn Test(&self) -> DomRoot<XRTest> {
self.test.or_init(|| XRTest::new(&self.global()))
}
}
impl XRSystem {
fn session_obtained(
&self,
response: Result<Session, XRError>,
promise: Rc<Promise>,
mode: XRSessionMode,
frame_receiver: IpcReceiver<Frame>,
) {
let session = match response {
Ok(session) => session,
Err(e) => {
warn!("Error requesting XR session: {:?}", e);
if mode != XRSessionMode::Inline {
self.pending_immersive_session.set(false);
}
promise.reject_error(Error::NotSupported);
return;
},
};
let session = XRSession::new(&self.global(), session, mode, frame_receiver);
if mode == XRSessionMode::Inline {
self.active_inline_sessions
.borrow_mut()
.push(Dom::from_ref(&*session));
} else {
self.set_active_immersive_session(&session);
}
promise.resolve_native(&session);
// https://github.com/immersive-web/webxr/issues/961
// This must be called _after_ the promise is resolved
session.setup_initial_inputs();
}
// https://github.com/immersive-web/navigation/issues/10
pub fn dispatch_sessionavailable(&self) {
let xr = Trusted::new(self);
let global = self.global();
let window = global.as_window();
window
.task_manager()
.dom_manipulation_task_source()
.queue(
task!(fire_sessionavailable_event: move || {
// The sessionavailable event indicates user intent to enter an XR session
let xr = xr.root();
let interacting = ScriptThread::is_user_interacting();
ScriptThread::set_user_interacting(true);
xr.upcast::<EventTarget>().fire_bubbling_event(atom!("sessionavailable"), CanGc::note());
ScriptThread::set_user_interacting(interacting);
}),
window.upcast(),
)
.unwrap();
}
}

View file

@ -0,0 +1,235 @@
/* 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 https://mozilla.org/MPL/2.0/. */
/* 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 https://mozilla.org/MPL/2.0/. */
use std::rc::Rc;
use dom_struct::dom_struct;
use ipc_channel::ipc::IpcSender;
use ipc_channel::router::ROUTER;
use js::jsval::JSVal;
use profile_traits::ipc;
use webxr_api::{self, Error as XRError, MockDeviceInit, MockDeviceMsg};
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function;
use crate::dom::bindings::codegen::Bindings::XRSystemBinding::XRSessionMode;
use crate::dom::bindings::codegen::Bindings::XRTestBinding::{FakeXRDeviceInit, XRTestMethods};
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::fakexrdevice::{get_origin, get_views, get_world, FakeXRDevice};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
use crate::task_source::TaskSource;
#[dom_struct]
pub struct XRTest {
reflector: Reflector,
devices_connected: DomRefCell<Vec<Dom<FakeXRDevice>>>,
}
impl XRTest {
pub fn new_inherited() -> XRTest {
XRTest {
reflector: Reflector::new(),
devices_connected: DomRefCell::new(vec![]),
}
}
pub fn new(global: &GlobalScope) -> DomRoot<XRTest> {
reflect_dom_object(Box::new(XRTest::new_inherited()), global)
}
fn device_obtained(
&self,
response: Result<IpcSender<MockDeviceMsg>, XRError>,
trusted: TrustedPromise,
) {
let promise = trusted.root();
if let Ok(sender) = response {
let device = FakeXRDevice::new(&self.global(), sender);
self.devices_connected
.borrow_mut()
.push(Dom::from_ref(&device));
promise.resolve_native(&device);
} else {
promise.reject_native(&());
}
}
}
impl XRTestMethods<crate::DomTypeHolder> for XRTest {
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
#[allow(unsafe_code)]
fn SimulateDeviceConnection(&self, init: &FakeXRDeviceInit, can_gc: CanGc) -> Rc<Promise> {
let global = self.global();
let p = Promise::new(&global, can_gc);
let origin = if let Some(ref o) = init.viewerOrigin {
match get_origin(o) {
Ok(origin) => Some(origin),
Err(e) => {
p.reject_error(e);
return p;
},
}
} else {
None
};
let floor_origin = if let Some(ref o) = init.floorOrigin {
match get_origin(o) {
Ok(origin) => Some(origin),
Err(e) => {
p.reject_error(e);
return p;
},
}
} else {
None
};
let views = match get_views(&init.views) {
Ok(views) => views,
Err(e) => {
p.reject_error(e);
return p;
},
};
let supported_features = if let Some(ref s) = init.supportedFeatures {
s.iter().cloned().map(String::from).collect()
} else {
vec![]
};
let world = if let Some(ref w) = init.world {
let w = match get_world(w) {
Ok(w) => w,
Err(e) => {
p.reject_error(e);
return p;
},
};
Some(w)
} else {
None
};
let (mut supports_inline, mut supports_vr, mut supports_ar) = (false, false, false);
if let Some(ref modes) = init.supportedModes {
for mode in modes {
match mode {
XRSessionMode::Immersive_vr => supports_vr = true,
XRSessionMode::Immersive_ar => supports_ar = true,
XRSessionMode::Inline => supports_inline = true,
}
}
}
let init = MockDeviceInit {
viewer_origin: origin,
views,
supports_inline,
supports_vr,
supports_ar,
floor_origin,
supported_features,
world,
};
let global = self.global();
let window = global.as_window();
let this = Trusted::new(self);
let mut trusted = Some(TrustedPromise::new(p.clone()));
let (task_source, canceller) = window
.task_manager()
.dom_manipulation_task_source_with_canceller();
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
ROUTER.add_typed_route(
receiver.to_ipc_receiver(),
Box::new(move |message| {
let trusted = trusted
.take()
.expect("SimulateDeviceConnection callback called twice");
let this = this.clone();
let message =
message.expect("SimulateDeviceConnection callback given incorrect payload");
let _ = task_source.queue_with_canceller(
task!(request_session: move || {
this.root().device_obtained(message, trusted);
}),
&canceller,
);
}),
);
if let Some(mut r) = window.webxr_registry() {
r.simulate_device_connection(init, sender);
}
p
}
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
fn SimulateUserActivation(&self, f: Rc<Function>) {
ScriptThread::set_user_interacting(true);
rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
let _ = f.Call__(vec![], value.handle_mut(), ExceptionHandling::Rethrow);
ScriptThread::set_user_interacting(false);
}
/// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
fn DisconnectAllDevices(&self, can_gc: CanGc) -> Rc<Promise> {
// XXXManishearth implement device disconnection and session ending
let global = self.global();
let p = Promise::new(&global, can_gc);
let mut devices = self.devices_connected.borrow_mut();
if devices.is_empty() {
p.resolve_native(&());
} else {
let mut len = devices.len();
let (sender, receiver) = ipc::channel(global.time_profiler_chan().clone()).unwrap();
let mut rooted_devices: Vec<_> =
devices.iter().map(|x| DomRoot::from_ref(&**x)).collect();
devices.clear();
let mut trusted = Some(TrustedPromise::new(p.clone()));
let (task_source, canceller) = global
.as_window()
.task_manager()
.dom_manipulation_task_source_with_canceller();
ROUTER.add_typed_route(
receiver.to_ipc_receiver(),
Box::new(move |_| {
len -= 1;
if len == 0 {
let trusted = trusted
.take()
.expect("DisconnectAllDevices disconnected more devices than expected");
let _ =
task_source.queue_with_canceller(trusted.resolve_task(()), &canceller);
}
}),
);
for device in rooted_devices.drain(..) {
device.disconnect(sender.clone());
}
};
p
}
}

View file

@ -0,0 +1,137 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use dom_struct::dom_struct;
use euclid::RigidTransform3D;
use js::typedarray::{Float32, Float32Array};
use webxr_api::{ApiSpace, View};
use crate::dom::bindings::buffer_source::HeapBufferSource;
use crate::dom::bindings::codegen::Bindings::XRViewBinding::{XREye, XRViewMethods};
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::dom::xrsession::{cast_transform, BaseSpace, BaseTransform, XRSession};
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRView {
reflector_: Reflector,
session: Dom<XRSession>,
eye: XREye,
viewport_index: usize,
#[ignore_malloc_size_of = "mozjs"]
proj: HeapBufferSource<Float32>,
#[ignore_malloc_size_of = "defined in rust-webxr"]
#[no_trace]
view: View<ApiSpace>,
transform: Dom<XRRigidTransform>,
requested_viewport_scale: Cell<f64>,
}
impl XRView {
fn new_inherited(
session: &XRSession,
transform: &XRRigidTransform,
eye: XREye,
viewport_index: usize,
view: View<ApiSpace>,
) -> XRView {
XRView {
reflector_: Reflector::new(),
session: Dom::from_ref(session),
eye,
viewport_index,
proj: HeapBufferSource::default(),
view,
transform: Dom::from_ref(transform),
requested_viewport_scale: Cell::new(1.0),
}
}
pub fn new<V: Copy>(
global: &GlobalScope,
session: &XRSession,
view: &View<V>,
eye: XREye,
viewport_index: usize,
to_base: &BaseTransform,
can_gc: CanGc,
) -> DomRoot<XRView> {
let transform: RigidTransform3D<f32, V, BaseSpace> = view.transform.then(to_base);
let transform = XRRigidTransform::new(global, cast_transform(transform), can_gc);
reflect_dom_object(
Box::new(XRView::new_inherited(
session,
&transform,
eye,
viewport_index,
view.cast_unit(),
)),
global,
)
}
pub fn session(&self) -> &XRSession {
&self.session
}
pub fn viewport_index(&self) -> usize {
self.viewport_index
}
}
impl XRViewMethods<crate::DomTypeHolder> for XRView {
/// <https://immersive-web.github.io/webxr/#dom-xrview-eye>
fn Eye(&self) -> XREye {
self.eye
}
/// <https://immersive-web.github.io/webxr/#dom-xrview-projectionmatrix>
fn ProjectionMatrix(&self, _cx: JSContext) -> Float32Array {
if !self.proj.is_initialized() {
let cx = GlobalScope::get_cx();
// row_major since euclid uses row vectors
let proj = self.view.projection.to_array();
self.proj
.set_data(cx, &proj)
.expect("Failed to set projection matrix.")
}
self.proj
.get_buffer()
.expect("Failed to get projection matrix.")
}
/// <https://immersive-web.github.io/webxr/#dom-xrview-transform>
fn Transform(&self) -> DomRoot<XRRigidTransform> {
DomRoot::from_ref(&self.transform)
}
/// <https://www.w3.org/TR/webxr/#dom-xrview-recommendedviewportscale>
fn GetRecommendedViewportScale(&self) -> Option<Finite<f64>> {
// Just return 1.0 since we currently will always use full-sized viewports
Finite::new(1.0)
}
/// <https://www.w3.org/TR/webxr/#dom-xrview-requestviewportscale>
fn RequestViewportScale(&self, scale: Option<Finite<f64>>) {
if let Some(scale) = scale {
if *scale > 0.0 {
let clamped_scale = scale.clamp(0.0, 1.0);
self.requested_viewport_scale.set(clamped_scale);
}
}
}
/// <https://www.w3.org/TR/webxr-ar-module-1/#dom-xrview-isfirstpersonobserver>
fn IsFirstPersonObserver(&self) -> bool {
// Servo is not currently supported anywhere that supports this, so return false
false
}
}

View file

@ -0,0 +1,196 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::RigidTransform3D;
use js::conversions::ToJSValConvertible;
use js::jsapi::Heap;
use js::jsval::{JSVal, UndefinedValue};
use js::rust::MutableHandleValue;
use webxr_api::{Viewer, ViewerPose, Views};
use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
use crate::dom::bindings::codegen::Bindings::XRViewerPoseBinding::XRViewerPoseMethods;
use crate::dom::bindings::reflector::reflect_dom_object;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::dom::xrpose::XRPose;
use crate::dom::xrrigidtransform::XRRigidTransform;
use crate::dom::xrsession::{cast_transform, BaseSpace, BaseTransform, XRSession};
use crate::dom::xrview::XRView;
use crate::realms::enter_realm;
use crate::script_runtime::{CanGc, JSContext};
#[dom_struct]
pub struct XRViewerPose {
pose: XRPose,
#[ignore_malloc_size_of = "mozjs"]
views: Heap<JSVal>,
}
impl XRViewerPose {
fn new_inherited(transform: &XRRigidTransform) -> XRViewerPose {
XRViewerPose {
pose: XRPose::new_inherited(transform),
views: Heap::default(),
}
}
#[allow(unsafe_code)]
pub fn new(
global: &GlobalScope,
session: &XRSession,
to_base: BaseTransform,
viewer_pose: &ViewerPose,
can_gc: CanGc,
) -> DomRoot<XRViewerPose> {
let _ac = enter_realm(global);
rooted_vec!(let mut views);
match &viewer_pose.views {
Views::Inline => views.push(XRView::new(
global,
session,
&session.inline_view(),
XREye::None,
0,
&to_base,
can_gc,
)),
Views::Mono(view) => views.push(XRView::new(
global,
session,
view,
XREye::None,
0,
&to_base,
can_gc,
)),
Views::Stereo(left, right) => {
views.push(XRView::new(
global,
session,
left,
XREye::Left,
0,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
right,
XREye::Right,
1,
&to_base,
can_gc,
));
},
Views::StereoCapture(left, right, third_eye) => {
views.push(XRView::new(
global,
session,
left,
XREye::Left,
0,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
right,
XREye::Right,
1,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
third_eye,
XREye::None,
2,
&to_base,
can_gc,
));
},
Views::Cubemap(front, left, right, top, bottom, back) => {
views.push(XRView::new(
global,
session,
front,
XREye::None,
0,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
left,
XREye::None,
1,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
right,
XREye::None,
2,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
top,
XREye::None,
3,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
bottom,
XREye::None,
4,
&to_base,
can_gc,
));
views.push(XRView::new(
global,
session,
back,
XREye::None,
5,
&to_base,
can_gc,
));
},
};
let transform: RigidTransform3D<f32, Viewer, BaseSpace> =
viewer_pose.transform.then(&to_base);
let transform = XRRigidTransform::new(global, cast_transform(transform), can_gc);
let pose = reflect_dom_object(Box::new(XRViewerPose::new_inherited(&transform)), global);
let cx = GlobalScope::get_cx();
unsafe {
rooted!(in(*cx) let mut jsval = UndefinedValue());
views.to_jsval(*cx, jsval.handle_mut());
pose.views.set(jsval.get());
}
pose
}
}
impl XRViewerPoseMethods<crate::DomTypeHolder> for XRViewerPose {
/// <https://immersive-web.github.io/webxr/#dom-xrviewerpose-views>
fn Views(&self, _cx: JSContext, mut retval: MutableHandleValue) {
retval.set(self.views.get())
}
}

View file

@ -0,0 +1,54 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::Rect;
use webxr_api::Viewport;
use crate::dom::bindings::codegen::Bindings::XRViewportBinding::XRViewportMethods;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
#[dom_struct]
pub struct XRViewport {
reflector_: Reflector,
#[no_trace]
viewport: Rect<i32, Viewport>,
}
impl XRViewport {
fn new_inherited(viewport: Rect<i32, Viewport>) -> XRViewport {
XRViewport {
reflector_: Reflector::new(),
viewport,
}
}
pub fn new(global: &GlobalScope, viewport: Rect<i32, Viewport>) -> DomRoot<XRViewport> {
reflect_dom_object(Box::new(XRViewport::new_inherited(viewport)), global)
}
}
impl XRViewportMethods<crate::DomTypeHolder> for XRViewport {
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-x>
fn X(&self) -> i32 {
self.viewport.origin.x
}
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-y>
fn Y(&self) -> i32 {
self.viewport.origin.y
}
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-width>
fn Width(&self) -> i32 {
self.viewport.size.width
}
/// <https://immersive-web.github.io/webxr/#dom-xrviewport-height>
fn Height(&self) -> i32 {
self.viewport.size.height
}
}

View file

@ -0,0 +1,168 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::XRWebGLBinding_Binding::XRWebGLBindingMethods;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContext_Binding::WebGLRenderingContextMethods;
use crate::dom::bindings::codegen::Bindings::XRWebGLBindingBinding::{
XRCubeLayerInit, XRCylinderLayerInit, XREquirectLayerInit, XRProjectionLayerInit,
XRQuadLayerInit, XRTextureType,
};
use crate::dom::bindings::codegen::UnionTypes::WebGLRenderingContextOrWebGL2RenderingContext;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::window::Window;
use crate::dom::xrcompositionlayer::XRCompositionLayer;
use crate::dom::xrcubelayer::XRCubeLayer;
use crate::dom::xrcylinderlayer::XRCylinderLayer;
use crate::dom::xrequirectlayer::XREquirectLayer;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrprojectionlayer::XRProjectionLayer;
use crate::dom::xrquadlayer::XRQuadLayer;
use crate::dom::xrsession::XRSession;
use crate::dom::xrview::XRView;
use crate::dom::xrwebglsubimage::XRWebGLSubImage;
use crate::script_runtime::CanGc;
#[dom_struct]
pub struct XRWebGLBinding {
reflector: Reflector,
session: Dom<XRSession>,
context: Dom<WebGLRenderingContext>,
}
impl XRWebGLBinding {
pub fn new_inherited(session: &XRSession, context: &WebGLRenderingContext) -> XRWebGLBinding {
XRWebGLBinding {
reflector: Reflector::new(),
session: Dom::from_ref(session),
context: Dom::from_ref(context),
}
}
fn new(
global: &Window,
proto: Option<HandleObject>,
session: &XRSession,
context: &WebGLRenderingContext,
can_gc: CanGc,
) -> DomRoot<XRWebGLBinding> {
reflect_dom_object_with_proto(
Box::new(XRWebGLBinding::new_inherited(session, context)),
global,
proto,
can_gc,
)
}
}
impl XRWebGLBindingMethods<crate::DomTypeHolder> for XRWebGLBinding {
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-xrwebglbinding>
fn Constructor(
global: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
session: &XRSession,
context: WebGLRenderingContextOrWebGL2RenderingContext,
) -> Fallible<DomRoot<XRWebGLBinding>> {
let context = match context {
WebGLRenderingContextOrWebGL2RenderingContext::WebGLRenderingContext(ctx) => ctx,
WebGLRenderingContextOrWebGL2RenderingContext::WebGL2RenderingContext(ctx) => {
ctx.base_context()
},
};
// Step 2
if session.is_ended() {
return Err(Error::InvalidState);
}
// step 3
if context.IsContextLost() {
return Err(Error::InvalidState);
}
// Step 4
if !session.is_immersive() {
return Err(Error::InvalidState);
};
// Step 5 throw an InvalidStateError If contexts XR compatible boolean is false.
Ok(XRWebGLBinding::new(
global, proto, session, &context, can_gc,
))
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createprojectionlayer>
fn CreateProjectionLayer(
&self,
_: XRTextureType,
_: &XRProjectionLayerInit,
) -> Fallible<DomRoot<XRProjectionLayer>> {
// https://github.com/servo/servo/issues/27468
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createquadlayer>
fn CreateQuadLayer(
&self,
_: XRTextureType,
_: &Option<XRQuadLayerInit>,
) -> Fallible<DomRoot<XRQuadLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createcylinderlayer>
fn CreateCylinderLayer(
&self,
_: XRTextureType,
_: &Option<XRCylinderLayerInit>,
) -> Fallible<DomRoot<XRCylinderLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createequirectlayer>
fn CreateEquirectLayer(
&self,
_: XRTextureType,
_: &Option<XREquirectLayerInit>,
) -> Fallible<DomRoot<XREquirectLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-createcubelayer>
fn CreateCubeLayer(&self, _: &Option<XRCubeLayerInit>) -> Fallible<DomRoot<XRCubeLayer>> {
// https://github.com/servo/servo/issues/27493
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-getsubimage>
fn GetSubImage(
&self,
_: &XRCompositionLayer,
_: &XRFrame,
_: XREye,
) -> Fallible<DomRoot<XRWebGLSubImage>> {
// https://github.com/servo/servo/issues/27468
Err(Error::NotSupported)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglbinding-getviewsubimage>
fn GetViewSubImage(
&self,
_: &XRProjectionLayer,
_: &XRView,
) -> Fallible<DomRoot<XRWebGLSubImage>> {
// https://github.com/servo/servo/issues/27468
Err(Error::NotSupported)
}
}

View file

@ -0,0 +1,367 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use std::convert::TryInto;
use canvas_traits::webgl::{WebGLCommand, WebGLContextId, WebGLTextureId};
use dom_struct::dom_struct;
use euclid::{Rect, Size2D};
use js::rust::HandleObject;
use webxr_api::{ContextId as WebXRContextId, LayerId, LayerInit, Viewport};
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextMethods;
use crate::dom::bindings::codegen::Bindings::XRWebGLLayerBinding::{
XRWebGLLayerInit, XRWebGLLayerMethods, XRWebGLRenderingContext,
};
use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::globalscope::GlobalScope;
use crate::dom::webglframebuffer::WebGLFramebuffer;
use crate::dom::webglobject::WebGLObject;
use crate::dom::webglrenderingcontext::WebGLRenderingContext;
use crate::dom::webgltexture::WebGLTexture;
use crate::dom::window::Window;
use crate::dom::xrframe::XRFrame;
use crate::dom::xrlayer::XRLayer;
use crate::dom::xrsession::XRSession;
use crate::dom::xrview::XRView;
use crate::dom::xrviewport::XRViewport;
use crate::script_runtime::CanGc;
impl<'a> From<&'a XRWebGLLayerInit> for LayerInit {
fn from(init: &'a XRWebGLLayerInit) -> LayerInit {
LayerInit::WebGLLayer {
alpha: init.alpha,
antialias: init.antialias,
depth: init.depth,
stencil: init.stencil,
framebuffer_scale_factor: *init.framebufferScaleFactor as f32,
ignore_depth_values: init.ignoreDepthValues,
}
}
}
#[dom_struct]
pub struct XRWebGLLayer {
xr_layer: XRLayer,
antialias: bool,
depth: bool,
stencil: bool,
alpha: bool,
ignore_depth_values: bool,
/// If none, this is an inline session (the composition disabled flag is true)
framebuffer: Option<Dom<WebGLFramebuffer>>,
}
impl XRWebGLLayer {
pub fn new_inherited(
session: &XRSession,
context: &WebGLRenderingContext,
init: &XRWebGLLayerInit,
framebuffer: Option<&WebGLFramebuffer>,
layer_id: Option<LayerId>,
) -> XRWebGLLayer {
XRWebGLLayer {
xr_layer: XRLayer::new_inherited(session, context, layer_id),
antialias: init.antialias,
depth: init.depth,
stencil: init.stencil,
alpha: init.alpha,
ignore_depth_values: init.ignoreDepthValues,
framebuffer: framebuffer.map(Dom::from_ref),
}
}
#[allow(clippy::too_many_arguments)]
fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
session: &XRSession,
context: &WebGLRenderingContext,
init: &XRWebGLLayerInit,
framebuffer: Option<&WebGLFramebuffer>,
layer_id: Option<LayerId>,
can_gc: CanGc,
) -> DomRoot<XRWebGLLayer> {
reflect_dom_object_with_proto(
Box::new(XRWebGLLayer::new_inherited(
session,
context,
init,
framebuffer,
layer_id,
)),
global,
proto,
can_gc,
)
}
pub fn layer_id(&self) -> Option<LayerId> {
self.xr_layer.layer_id()
}
pub fn context_id(&self) -> WebGLContextId {
self.xr_layer.context_id()
}
pub fn session(&self) -> &XRSession {
self.xr_layer.session()
}
pub fn size(&self) -> Size2D<u32, Viewport> {
if let Some(framebuffer) = self.framebuffer.as_ref() {
let size = framebuffer.size().unwrap_or((0, 0));
Size2D::new(
size.0.try_into().unwrap_or(0),
size.1.try_into().unwrap_or(0),
)
} else {
let size = match self.context().Canvas() {
HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => canvas.get_size(),
HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => {
let size = canvas.get_size();
Size2D::new(
size.width.try_into().unwrap_or(0),
size.height.try_into().unwrap_or(0),
)
},
};
Size2D::from_untyped(size)
}
}
fn texture_target(&self) -> u32 {
if cfg!(target_os = "macos") {
glow::TEXTURE_RECTANGLE
} else {
glow::TEXTURE_2D
}
}
pub fn begin_frame(&self, frame: &XRFrame) -> Option<()> {
debug!("XRWebGLLayer begin frame");
let framebuffer = self.framebuffer.as_ref()?;
let context = framebuffer.upcast::<WebGLObject>().context();
let sub_images = frame.get_sub_images(self.layer_id()?)?;
let session = self.session();
// TODO: Cache this texture
let color_texture_id =
WebGLTextureId::maybe_new(sub_images.sub_image.as_ref()?.color_texture)?;
let color_texture = WebGLTexture::new_webxr(context, color_texture_id, session);
let target = self.texture_target();
// Save the current bindings
let saved_framebuffer = context.get_draw_framebuffer_slot().get();
let saved_framebuffer_target = framebuffer.target();
let saved_texture_id = context
.textures()
.active_texture_slot(target, context.webgl_version())
.ok()
.and_then(|slot| slot.get().map(|texture| texture.id()));
// We have to pick a framebuffer target.
// If there is a draw framebuffer, we use its target,
// otherwise we just use DRAW_FRAMEBUFFER.
let framebuffer_target = saved_framebuffer
.as_ref()
.and_then(|fb| fb.target())
.unwrap_or(constants::DRAW_FRAMEBUFFER);
// Update the attachments
context.send_command(WebGLCommand::BindTexture(target, Some(color_texture_id)));
framebuffer.bind(framebuffer_target);
framebuffer
.texture2d_even_if_opaque(
constants::COLOR_ATTACHMENT0,
self.texture_target(),
Some(&color_texture),
0,
)
.ok()?;
if let Some(id) = sub_images.sub_image.as_ref()?.depth_stencil_texture {
// TODO: Cache this texture
let depth_stencil_texture_id = WebGLTextureId::maybe_new(id)?;
let depth_stencil_texture =
WebGLTexture::new_webxr(context, depth_stencil_texture_id, session);
framebuffer
.texture2d_even_if_opaque(
constants::DEPTH_STENCIL_ATTACHMENT,
constants::TEXTURE_2D,
Some(&depth_stencil_texture),
0,
)
.ok()?;
}
// Restore the old bindings
context.send_command(WebGLCommand::BindTexture(target, saved_texture_id));
if let Some(framebuffer_target) = saved_framebuffer_target {
framebuffer.bind(framebuffer_target);
}
if let Some(framebuffer) = saved_framebuffer {
framebuffer.bind(framebuffer_target);
}
Some(())
}
pub fn end_frame(&self, _frame: &XRFrame) -> Option<()> {
debug!("XRWebGLLayer end frame");
// TODO: invalidate the old texture
let framebuffer = self.framebuffer.as_ref()?;
// TODO: rebind the current bindings
framebuffer.bind(constants::FRAMEBUFFER);
framebuffer
.texture2d_even_if_opaque(constants::COLOR_ATTACHMENT0, self.texture_target(), None, 0)
.ok()?;
framebuffer
.texture2d_even_if_opaque(
constants::DEPTH_STENCIL_ATTACHMENT,
constants::DEPTH_STENCIL_ATTACHMENT,
None,
0,
)
.ok()?;
framebuffer.upcast::<WebGLObject>().context().Flush();
Some(())
}
pub(crate) fn context(&self) -> &WebGLRenderingContext {
self.xr_layer.context()
}
}
impl XRWebGLLayerMethods<crate::DomTypeHolder> for XRWebGLLayer {
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-xrwebgllayer>
fn Constructor(
global: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
session: &XRSession,
context: XRWebGLRenderingContext,
init: &XRWebGLLayerInit,
) -> Fallible<DomRoot<Self>> {
let context = match context {
XRWebGLRenderingContext::WebGLRenderingContext(ctx) => ctx,
XRWebGLRenderingContext::WebGL2RenderingContext(ctx) => ctx.base_context(),
};
// Step 2
if session.is_ended() {
return Err(Error::InvalidState);
}
// XXXManishearth step 3: throw error if context is lost
// XXXManishearth step 4: check XR compat flag for immersive sessions
let (framebuffer, layer_id) = if session.is_immersive() {
// Step 9.2. "Initialize layers framebuffer to a new opaque framebuffer created with context."
let size = session
.with_session(|session| session.recommended_framebuffer_resolution())
.ok_or(Error::Operation)?;
let framebuffer = WebGLFramebuffer::maybe_new_webxr(session, &context, size)
.ok_or(Error::Operation)?;
// Step 9.3. "Allocate and initialize resources compatible with sessions XR device,
// including GPU accessible memory buffers, as required to support the compositing of layer."
let context_id = WebXRContextId::from(context.context_id());
let layer_init = LayerInit::from(init);
let layer_id = session
.with_session(|session| session.create_layer(context_id, layer_init))
.map_err(|_| Error::Operation)?;
// Step 9.4: "If layers resources were unable to be created for any reason,
// throw an OperationError and abort these steps."
(Some(framebuffer), Some(layer_id))
} else {
(None, None)
};
// Ensure that we finish setting up this layer before continuing.
context.Finish();
// Step 10. "Return layer."
Ok(XRWebGLLayer::new(
&global.global(),
proto,
session,
&context,
init,
framebuffer.as_deref(),
layer_id,
can_gc,
))
}
/// <https://www.w3.org/TR/webxr/#dom-xrwebgllayer-getnativeframebufferscalefactor>
fn GetNativeFramebufferScaleFactor(_window: &Window, session: &XRSession) -> Finite<f64> {
let value: f64 = if session.is_ended() { 0.0 } else { 1.0 };
Finite::wrap(value)
}
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-antialias>
fn Antialias(&self) -> bool {
self.antialias
}
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-ignoredepthvalues>
fn IgnoreDepthValues(&self) -> bool {
self.ignore_depth_values
}
/// <https://www.w3.org/TR/webxr/#dom-xrwebgllayer-fixedfoveation>
fn GetFixedFoveation(&self) -> Option<Finite<f32>> {
// Fixed foveation is only available on Quest/Pico headset runtimes
None
}
/// <https://www.w3.org/TR/webxr/#dom-xrwebgllayer-fixedfoveation>
fn SetFixedFoveation(&self, _value: Option<Finite<f32>>) {
// no-op until fixed foveation is supported
}
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebuffer>
fn GetFramebuffer(&self) -> Option<DomRoot<WebGLFramebuffer>> {
self.framebuffer.as_ref().map(|x| DomRoot::from_ref(&**x))
}
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferwidth>
fn FramebufferWidth(&self) -> u32 {
self.size().width
}
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-framebufferheight>
fn FramebufferHeight(&self) -> u32 {
self.size().height
}
/// <https://immersive-web.github.io/webxr/#dom-xrwebgllayer-getviewport>
fn GetViewport(&self, view: &XRView) -> Option<DomRoot<XRViewport>> {
if self.session() != view.session() {
return None;
}
let index = view.viewport_index();
let viewport = self.session().with_session(|s| {
// Inline sessions
if s.viewports().is_empty() {
Rect::from_size(self.size().to_i32())
} else {
s.viewports()[index]
}
});
// NOTE: According to spec, viewport sizes should be recalculated here if the
// requested viewport scale has changed. However, existing browser implementations
// don't seem to do this for stereoscopic immersive sessions.
// Revisit if Servo gets support for handheld AR/VR via ARCore/ARKit
Some(XRViewport::new(&self.global(), viewport))
}
}

View file

@ -0,0 +1,49 @@
/* 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 https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use euclid::Size2D;
use webxr_api::Viewport;
use crate::dom::bindings::codegen::Bindings::XRWebGLSubImageBinding::XRWebGLSubImage_Binding::XRWebGLSubImageMethods;
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::webgltexture::WebGLTexture;
use crate::dom::xrsubimage::XRSubImage;
#[dom_struct]
pub struct XRWebGLSubImage {
xr_sub_image: XRSubImage,
color_texture: Dom<WebGLTexture>,
depth_stencil_texture: Option<Dom<WebGLTexture>>,
image_index: Option<u32>,
#[no_trace]
size: Size2D<u32, Viewport>,
}
impl XRWebGLSubImageMethods<crate::DomTypeHolder> for XRWebGLSubImage {
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-colortexture>
fn ColorTexture(&self) -> DomRoot<WebGLTexture> {
DomRoot::from_ref(&self.color_texture)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-depthstenciltexture>
fn GetDepthStencilTexture(&self) -> Option<DomRoot<WebGLTexture>> {
self.depth_stencil_texture.as_deref().map(DomRoot::from_ref)
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-imageindex>
fn GetImageIndex(&self) -> Option<u32> {
self.image_index
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-texturewidth>
fn TextureWidth(&self) -> u32 {
self.size.width
}
/// <https://immersive-web.github.io/layers/#dom-xrwebglsubimage-textureheight>
fn TextureHeight(&self) -> u32 {
self.size.height
}
}