summaryrefslogtreecommitdiffstats
path: root/dom/vr
diff options
context:
space:
mode:
Diffstat (limited to 'dom/vr')
-rw-r--r--dom/vr/VRDisplay.cpp802
-rw-r--r--dom/vr/VRDisplay.h362
-rw-r--r--dom/vr/VREventObserver.cpp79
-rw-r--r--dom/vr/VREventObserver.h33
-rw-r--r--dom/vr/moz.build22
5 files changed, 1298 insertions, 0 deletions
diff --git a/dom/vr/VRDisplay.cpp b/dom/vr/VRDisplay.cpp
new file mode 100644
index 000000000..80922422f
--- /dev/null
+++ b/dom/vr/VRDisplay.cpp
@@ -0,0 +1,802 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/VRDisplay.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/dom/VRDisplayBinding.h"
+#include "Navigator.h"
+#include "gfxVR.h"
+#include "VRDisplayClient.h"
+#include "VRManagerChild.h"
+#include "VRDisplayPresentation.h"
+#include "nsIObserverService.h"
+#include "nsIFrame.h"
+#include "nsISupportsPrimitives.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace dom {
+
+VRFieldOfView::VRFieldOfView(nsISupports* aParent,
+ double aUpDegrees, double aRightDegrees,
+ double aDownDegrees, double aLeftDegrees)
+ : mParent(aParent)
+ , mUpDegrees(aUpDegrees)
+ , mRightDegrees(aRightDegrees)
+ , mDownDegrees(aDownDegrees)
+ , mLeftDegrees(aLeftDegrees)
+{
+}
+
+VRFieldOfView::VRFieldOfView(nsISupports* aParent, const gfx::VRFieldOfView& aSrc)
+ : mParent(aParent)
+ , mUpDegrees(aSrc.upDegrees)
+ , mRightDegrees(aSrc.rightDegrees)
+ , mDownDegrees(aSrc.downDegrees)
+ , mLeftDegrees(aSrc.leftDegrees)
+{
+}
+
+bool
+VRDisplayCapabilities::HasPosition() const
+{
+ return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Position);
+}
+
+bool
+VRDisplayCapabilities::HasOrientation() const
+{
+ return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Orientation);
+}
+
+bool
+VRDisplayCapabilities::HasExternalDisplay() const
+{
+ return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_External);
+}
+
+bool
+VRDisplayCapabilities::CanPresent() const
+{
+ return bool(mFlags & gfx::VRDisplayCapabilityFlags::Cap_Present);
+}
+
+uint32_t
+VRDisplayCapabilities::MaxLayers() const
+{
+ return CanPresent() ? 1 : 0;
+}
+
+/*static*/ bool
+VRDisplay::RefreshVRDisplays(uint64_t aWindowId)
+{
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ return vm && vm->RefreshVRDisplaysWithCallback(aWindowId);
+}
+
+/*static*/ void
+VRDisplay::UpdateVRDisplays(nsTArray<RefPtr<VRDisplay>>& aDisplays, nsPIDOMWindowInner* aWindow)
+{
+ nsTArray<RefPtr<VRDisplay>> displays;
+
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ nsTArray<RefPtr<gfx::VRDisplayClient>> updatedDisplays;
+ if (vm && vm->GetVRDisplays(updatedDisplays)) {
+ for (size_t i = 0; i < updatedDisplays.Length(); i++) {
+ RefPtr<gfx::VRDisplayClient> display = updatedDisplays[i];
+ bool isNewDisplay = true;
+ for (size_t j = 0; j < aDisplays.Length(); j++) {
+ if (aDisplays[j]->GetClient()->GetDisplayInfo() == display->GetDisplayInfo()) {
+ displays.AppendElement(aDisplays[j]);
+ isNewDisplay = false;
+ }
+ }
+
+ if (isNewDisplay) {
+ displays.AppendElement(new VRDisplay(aWindow, display));
+ }
+ }
+ }
+
+ aDisplays = displays;
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRFieldOfView, mParent)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFieldOfView, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFieldOfView, Release)
+
+
+JSObject*
+VRFieldOfView::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return VRFieldOfViewBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VREyeParameters)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VREyeParameters)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mFOV)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->mOffset = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VREyeParameters)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mFOV)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VREyeParameters)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mOffset)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VREyeParameters, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VREyeParameters, Release)
+
+VREyeParameters::VREyeParameters(nsISupports* aParent,
+ const gfx::Point3D& aEyeTranslation,
+ const gfx::VRFieldOfView& aFOV,
+ const gfx::IntSize& aRenderSize)
+ : mParent(aParent)
+ , mEyeTranslation(aEyeTranslation)
+ , mRenderSize(aRenderSize)
+{
+ mFOV = new VRFieldOfView(aParent, aFOV);
+ mozilla::HoldJSObjects(this);
+}
+
+VREyeParameters::~VREyeParameters()
+{
+ mozilla::DropJSObjects(this);
+}
+
+VRFieldOfView*
+VREyeParameters::FieldOfView()
+{
+ return mFOV;
+}
+
+void
+VREyeParameters::GetOffset(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv)
+{
+ if (!mOffset) {
+ // Lazily create the Float32Array
+ mOffset = dom::Float32Array::Create(aCx, this, 3, mEyeTranslation.components);
+ if (!mOffset) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+ aRetval.set(mOffset);
+}
+
+JSObject*
+VREyeParameters::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return VREyeParametersBinding::Wrap(aCx, this, aGivenProto);
+}
+
+VRStageParameters::VRStageParameters(nsISupports* aParent,
+ const gfx::Matrix4x4& aSittingToStandingTransform,
+ const gfx::Size& aSize)
+ : mParent(aParent)
+ , mSittingToStandingTransform(aSittingToStandingTransform)
+ , mSittingToStandingTransformArray(nullptr)
+ , mSize(aSize)
+{
+ mozilla::HoldJSObjects(this);
+}
+
+VRStageParameters::~VRStageParameters()
+{
+ mozilla::DropJSObjects(this);
+}
+
+JSObject*
+VRStageParameters::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return VRStageParametersBinding::Wrap(aCx, this, aGivenProto);
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRStageParameters)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRStageParameters)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->mSittingToStandingTransformArray = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRStageParameters)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRStageParameters)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mSittingToStandingTransformArray)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRStageParameters, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRStageParameters, Release)
+
+void
+VRStageParameters::GetSittingToStandingTransform(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ if (!mSittingToStandingTransformArray) {
+ // Lazily create the Float32Array
+ mSittingToStandingTransformArray = dom::Float32Array::Create(aCx, this, 16,
+ mSittingToStandingTransform.components);
+ if (!mSittingToStandingTransformArray) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+ aRetval.set(mSittingToStandingTransformArray);
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(VRDisplayCapabilities, mParent)
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRDisplayCapabilities, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRDisplayCapabilities, Release)
+
+JSObject*
+VRDisplayCapabilities::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return VRDisplayCapabilitiesBinding::Wrap(aCx, this, aGivenProto);
+}
+
+VRPose::VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState)
+ : Pose(aParent)
+ , mVRState(aState)
+{
+ mFrameId = aState.inputFrameID;
+ mozilla::HoldJSObjects(this);
+}
+
+VRPose::VRPose(nsISupports* aParent)
+ : Pose(aParent)
+{
+ mFrameId = 0;
+ mVRState.Clear();
+ mozilla::HoldJSObjects(this);
+}
+
+VRPose::~VRPose()
+{
+ mozilla::DropJSObjects(this);
+}
+
+void
+VRPose::GetPosition(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aCx, aRetval, mPosition, mVRState.position, 3,
+ !mPosition && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position),
+ aRv);
+}
+
+void
+VRPose::GetLinearVelocity(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aCx, aRetval, mLinearVelocity, mVRState.linearVelocity, 3,
+ !mLinearVelocity && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position),
+ aRv);
+}
+
+void
+VRPose::GetLinearAcceleration(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aCx, aRetval, mLinearAcceleration, mVRState.linearAcceleration, 3,
+ !mLinearAcceleration && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_LinearAcceleration),
+ aRv);
+
+}
+
+void
+VRPose::GetOrientation(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aCx, aRetval, mOrientation, mVRState.orientation, 4,
+ !mOrientation && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation),
+ aRv);
+}
+
+void
+VRPose::GetAngularVelocity(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aCx, aRetval, mAngularVelocity, mVRState.angularVelocity, 3,
+ !mAngularVelocity && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation),
+ aRv);
+}
+
+void
+VRPose::GetAngularAcceleration(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ SetFloat32Array(aCx, aRetval, mAngularAcceleration, mVRState.angularAcceleration, 3,
+ !mAngularAcceleration && bool(mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_AngularAcceleration),
+ aRv);
+}
+
+JSObject*
+VRPose::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return VRPoseBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* virtual */ JSObject*
+VRDisplay::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return VRDisplayBinding::Wrap(aCx, this, aGivenProto);
+}
+
+VRDisplay::VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient)
+ : DOMEventTargetHelper(aWindow)
+ , mClient(aClient)
+ , mDepthNear(0.01f) // Default value from WebVR Spec
+ , mDepthFar(10000.0f) // Default value from WebVR Spec
+{
+ const gfx::VRDisplayInfo& info = aClient->GetDisplayInfo();
+ mDisplayId = info.GetDisplayID();
+ mDisplayName = NS_ConvertASCIItoUTF16(info.GetDisplayName());
+ mCapabilities = new VRDisplayCapabilities(aWindow, info.GetCapabilities());
+ if (info.GetCapabilities() & gfx::VRDisplayCapabilityFlags::Cap_StageParameters) {
+ mStageParameters = new VRStageParameters(aWindow,
+ info.GetSittingToStandingTransform(),
+ info.GetStageSize());
+ }
+ mozilla::HoldJSObjects(this);
+}
+
+VRDisplay::~VRDisplay()
+{
+ ExitPresentInternal();
+ mozilla::DropJSObjects(this);
+}
+
+void
+VRDisplay::LastRelease()
+{
+ // We don't want to wait for the CC to free up the presentation
+ // for use in other documents, so we do this in LastRelease().
+ ExitPresentInternal();
+}
+
+already_AddRefed<VREyeParameters>
+VRDisplay::GetEyeParameters(VREye aEye)
+{
+ gfx::VRDisplayInfo::Eye eye = aEye == VREye::Left ? gfx::VRDisplayInfo::Eye_Left : gfx::VRDisplayInfo::Eye_Right;
+ RefPtr<VREyeParameters> params =
+ new VREyeParameters(GetParentObject(),
+ mClient->GetDisplayInfo().GetEyeTranslation(eye),
+ mClient->GetDisplayInfo().GetEyeFOV(eye),
+ mClient->GetDisplayInfo().SuggestedEyeResolution());
+ return params.forget();
+}
+
+VRDisplayCapabilities*
+VRDisplay::Capabilities()
+{
+ return mCapabilities;
+}
+
+VRStageParameters*
+VRDisplay::GetStageParameters()
+{
+ return mStageParameters;
+}
+
+void
+VRDisplay::UpdateFrameInfo()
+{
+ /**
+ * The WebVR 1.1 spec Requires that VRDisplay.getPose and VRDisplay.getFrameData
+ * must return the same values until the next VRDisplay.submitFrame.
+ *
+ * mFrameInfo is marked dirty at the end of the frame or start of a new
+ * composition and lazily created here in order to receive mid-frame
+ * pose-prediction updates while still ensuring conformance to the WebVR spec
+ * requirements.
+ *
+ * If we are not presenting WebVR content, the frame will never end and we should
+ * return the latest frame data always.
+ */
+ if (mFrameInfo.IsDirty() || !mPresentation) {
+ gfx::VRHMDSensorState state = mClient->GetSensorState();
+ const gfx::VRDisplayInfo& info = mClient->GetDisplayInfo();
+ mFrameInfo.Update(info, state, mDepthNear, mDepthFar);
+ }
+}
+
+bool
+VRDisplay::GetFrameData(VRFrameData& aFrameData)
+{
+ UpdateFrameInfo();
+ aFrameData.Update(mFrameInfo);
+ return true;
+}
+
+already_AddRefed<VRPose>
+VRDisplay::GetPose()
+{
+ UpdateFrameInfo();
+ RefPtr<VRPose> obj = new VRPose(GetParentObject(), mFrameInfo.mVRState);
+
+ return obj.forget();
+}
+
+void
+VRDisplay::ResetPose()
+{
+ mClient->ZeroSensor();
+}
+
+already_AddRefed<Promise>
+VRDisplay::RequestPresent(const nsTArray<VRLayer>& aLayers, ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ NS_ENSURE_TRUE(obs, nullptr);
+
+ if (mClient->GetIsPresenting()) {
+ // Only one presentation allowed per VRDisplay
+ // on a first-come-first-serve basis.
+ promise->MaybeRejectWithUndefined();
+ } else {
+ mPresentation = mClient->BeginPresentation(aLayers);
+ mFrameInfo.Clear();
+
+ nsresult rv = obs->AddObserver(this, "inner-window-destroyed", false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPresentation = nullptr;
+ promise->MaybeRejectWithUndefined();
+ } else {
+ promise->MaybeResolve(JS::UndefinedHandleValue);
+ }
+ }
+ return promise.forget();
+}
+
+NS_IMETHODIMP
+VRDisplay::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (strcmp(aTopic, "inner-window-destroyed") == 0) {
+ nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+ NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+ uint64_t innerID;
+ nsresult rv = wrapper->GetData(&innerID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!GetOwner() || GetOwner()->WindowID() == innerID) {
+ ExitPresentInternal();
+ }
+
+ return NS_OK;
+ }
+
+ // This should not happen.
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<Promise>
+VRDisplay::ExitPresent(ErrorResult& aRv)
+{
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ if (!IsPresenting()) {
+ // We can not exit a presentation outside of the context that
+ // started the presentation.
+ promise->MaybeRejectWithUndefined();
+ } else {
+ promise->MaybeResolve(JS::UndefinedHandleValue);
+ ExitPresentInternal();
+ }
+
+ return promise.forget();
+}
+
+void
+VRDisplay::ExitPresentInternal()
+{
+ mPresentation = nullptr;
+}
+
+void
+VRDisplay::GetLayers(nsTArray<VRLayer>& result)
+{
+ if (mPresentation) {
+ mPresentation->GetDOMLayers(result);
+ } else {
+ result = nsTArray<VRLayer>();
+ }
+}
+
+void
+VRDisplay::SubmitFrame()
+{
+ if (mPresentation) {
+ mPresentation->SubmitFrame();
+ }
+ mFrameInfo.Clear();
+}
+
+int32_t
+VRDisplay::RequestAnimationFrame(FrameRequestCallback& aCallback,
+ErrorResult& aError)
+{
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+
+ int32_t handle;
+ aError = vm->ScheduleFrameRequestCallback(aCallback, &handle);
+ return handle;
+}
+
+void
+VRDisplay::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError)
+{
+ gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+ vm->CancelFrameRequestCallback(aHandle);
+}
+
+
+bool
+VRDisplay::IsPresenting() const
+{
+ // IsPresenting returns true only if this Javascript context is presenting
+ // and will return false if another context is presenting.
+ return mPresentation != nullptr;
+}
+
+bool
+VRDisplay::IsConnected() const
+{
+ return mClient->GetIsConnected();
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(VRDisplay, DOMEventTargetHelper, mCapabilities, mStageParameters)
+
+NS_IMPL_ADDREF_INHERITED(VRDisplay, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(VRDisplay, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(VRDisplay)
+NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, DOMEventTargetHelper)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(VRFrameData)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(VRFrameData)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent, mPose)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->mLeftProjectionMatrix = nullptr;
+ tmp->mLeftViewMatrix = nullptr;
+ tmp->mRightProjectionMatrix = nullptr;
+ tmp->mRightViewMatrix = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(VRFrameData)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent, mPose)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(VRFrameData)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLeftProjectionMatrix)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLeftViewMatrix)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRightProjectionMatrix)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mRightViewMatrix)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(VRFrameData, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(VRFrameData, Release)
+
+VRFrameData::VRFrameData(nsISupports* aParent)
+ : mParent(aParent)
+ , mLeftProjectionMatrix(nullptr)
+ , mLeftViewMatrix(nullptr)
+ , mRightProjectionMatrix(nullptr)
+ , mRightViewMatrix(nullptr)
+{
+ mozilla::HoldJSObjects(this);
+ mPose = new VRPose(aParent);
+}
+
+VRFrameData::~VRFrameData()
+{
+ mozilla::DropJSObjects(this);
+}
+
+/* static */ already_AddRefed<VRFrameData>
+VRFrameData::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
+{
+ RefPtr<VRFrameData> obj = new VRFrameData(aGlobal.GetAsSupports());
+ return obj.forget();
+}
+
+JSObject*
+VRFrameData::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto)
+{
+ return VRFrameDataBinding::Wrap(aCx, this, aGivenProto);
+}
+
+VRPose*
+VRFrameData::Pose()
+{
+ return mPose;
+}
+
+void
+VRFrameData::LazyCreateMatrix(JS::Heap<JSObject*>& aArray, gfx::Matrix4x4& aMat, JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv)
+{
+ if (!aArray) {
+ // Lazily create the Float32Array
+ aArray = dom::Float32Array::Create(aCx, this, 16, aMat.components);
+ if (!aArray) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ }
+ if (aArray) {
+ JS::ExposeObjectToActiveJS(aArray);
+ }
+ aRetval.set(aArray);
+}
+
+double
+VRFrameData::Timestamp() const
+{
+ // Converting from seconds to milliseconds
+ return mFrameInfo.mVRState.timestamp * 1000.0f;
+}
+
+void
+VRFrameData::GetLeftProjectionMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ LazyCreateMatrix(mLeftProjectionMatrix, mFrameInfo.mLeftProjection, aCx,
+ aRetval, aRv);
+}
+
+void
+VRFrameData::GetLeftViewMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ LazyCreateMatrix(mLeftViewMatrix, mFrameInfo.mLeftView, aCx, aRetval, aRv);
+}
+
+void
+VRFrameData::GetRightProjectionMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ LazyCreateMatrix(mRightProjectionMatrix, mFrameInfo.mRightProjection, aCx,
+ aRetval, aRv);
+}
+
+void
+VRFrameData::GetRightViewMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv)
+{
+ LazyCreateMatrix(mRightViewMatrix, mFrameInfo.mRightView, aCx, aRetval, aRv);
+}
+
+void
+VRFrameData::Update(const VRFrameInfo& aFrameInfo)
+{
+ mFrameInfo = aFrameInfo;
+
+ mLeftProjectionMatrix = nullptr;
+ mLeftViewMatrix = nullptr;
+ mRightProjectionMatrix = nullptr;
+ mRightViewMatrix = nullptr;
+
+ mPose = new VRPose(GetParentObject(), mFrameInfo.mVRState);
+}
+
+void
+VRFrameInfo::Update(const gfx::VRDisplayInfo& aInfo,
+ const gfx::VRHMDSensorState& aState,
+ float aDepthNear,
+ float aDepthFar)
+{
+ mVRState = aState;
+
+ gfx::Quaternion qt;
+ if (mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Orientation) {
+ qt.x = mVRState.orientation[0];
+ qt.y = mVRState.orientation[1];
+ qt.z = mVRState.orientation[2];
+ qt.w = mVRState.orientation[3];
+ }
+ gfx::Point3D pos;
+ if (mVRState.flags & gfx::VRDisplayCapabilityFlags::Cap_Position) {
+ pos.x = -mVRState.position[0];
+ pos.y = -mVRState.position[1];
+ pos.z = -mVRState.position[2];
+ }
+ gfx::Matrix4x4 matHead;
+ matHead.SetRotationFromQuaternion(qt);
+ matHead.PreTranslate(pos);
+
+ mLeftView = matHead;
+ mLeftView.PostTranslate(-aInfo.mEyeTranslation[gfx::VRDisplayInfo::Eye_Left]);
+
+ mRightView = matHead;
+ mRightView.PostTranslate(-aInfo.mEyeTranslation[gfx::VRDisplayInfo::Eye_Right]);
+
+ // Avoid division by zero within ConstructProjectionMatrix
+ const float kEpsilon = 0.00001f;
+ if (fabs(aDepthFar - aDepthNear) < kEpsilon) {
+ aDepthFar = aDepthNear + kEpsilon;
+ }
+
+ const gfx::VRFieldOfView leftFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Left];
+ mLeftProjection = leftFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
+ const gfx::VRFieldOfView rightFOV = aInfo.mEyeFOV[gfx::VRDisplayInfo::Eye_Right];
+ mRightProjection = rightFOV.ConstructProjectionMatrix(aDepthNear, aDepthFar, true);
+}
+
+VRFrameInfo::VRFrameInfo()
+{
+ mVRState.Clear();
+}
+
+bool
+VRFrameInfo::IsDirty()
+{
+ return mVRState.timestamp == 0;
+}
+
+void
+VRFrameInfo::Clear()
+{
+ mVRState.Clear();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/vr/VRDisplay.h b/dom/vr/VRDisplay.h
new file mode 100644
index 000000000..d40d3d8ac
--- /dev/null
+++ b/dom/vr/VRDisplay.h
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_VRDisplay_h_
+#define mozilla_dom_VRDisplay_h_
+
+#include <stdint.h>
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/VRDisplayBinding.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/Pose.h"
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include "gfxVR.h"
+
+namespace mozilla {
+namespace gfx {
+class VRDisplayClient;
+class VRDisplayPresentation;
+struct VRFieldOfView;
+enum class VRDisplayCapabilityFlags : uint16_t;
+struct VRHMDSensorState;
+}
+namespace dom {
+class Navigator;
+
+class VRFieldOfView final : public nsWrapperCache
+{
+public:
+ VRFieldOfView(nsISupports* aParent,
+ double aUpDegrees, double aRightDegrees,
+ double aDownDegrees, double aLeftDegrees);
+ VRFieldOfView(nsISupports* aParent, const gfx::VRFieldOfView& aSrc);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFieldOfView)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFieldOfView)
+
+ double UpDegrees() const { return mUpDegrees; }
+ double RightDegrees() const { return mRightDegrees; }
+ double DownDegrees() const { return mDownDegrees; }
+ double LeftDegrees() const { return mLeftDegrees; }
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ virtual ~VRFieldOfView() {}
+
+ nsCOMPtr<nsISupports> mParent;
+
+ double mUpDegrees;
+ double mRightDegrees;
+ double mDownDegrees;
+ double mLeftDegrees;
+};
+
+class VRDisplayCapabilities final : public nsWrapperCache
+{
+public:
+ VRDisplayCapabilities(nsISupports* aParent, const gfx::VRDisplayCapabilityFlags& aFlags)
+ : mParent(aParent)
+ , mFlags(aFlags)
+ {
+ }
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRDisplayCapabilities)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRDisplayCapabilities)
+
+ nsISupports* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool HasPosition() const;
+ bool HasOrientation() const;
+ bool HasExternalDisplay() const;
+ bool CanPresent() const;
+ uint32_t MaxLayers() const;
+
+protected:
+ ~VRDisplayCapabilities() {}
+ nsCOMPtr<nsISupports> mParent;
+ gfx::VRDisplayCapabilityFlags mFlags;
+};
+
+class VRPose final : public Pose
+{
+
+public:
+ VRPose(nsISupports* aParent, const gfx::VRHMDSensorState& aState);
+ explicit VRPose(nsISupports* aParent);
+
+ uint32_t FrameID() const { return mFrameId; }
+
+ virtual void GetPosition(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetLinearVelocity(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetLinearAcceleration(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetOrientation(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetAngularVelocity(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+ virtual void GetAngularAcceleration(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) override;
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ ~VRPose();
+
+ uint32_t mFrameId;
+ gfx::VRHMDSensorState mVRState;
+};
+
+struct VRFrameInfo
+{
+ VRFrameInfo();
+
+ void Update(const gfx::VRDisplayInfo& aInfo,
+ const gfx::VRHMDSensorState& aState,
+ float aDepthNear,
+ float aDepthFar);
+
+ void Clear();
+ bool IsDirty();
+
+ gfx::VRHMDSensorState mVRState;
+ gfx::Matrix4x4 mLeftProjection;
+ gfx::Matrix4x4 mLeftView;
+ gfx::Matrix4x4 mRightProjection;
+ gfx::Matrix4x4 mRightView;
+
+};
+
+class VRFrameData final : public nsWrapperCache
+{
+public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRFrameData)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRFrameData)
+
+ explicit VRFrameData(nsISupports* aParent);
+ static already_AddRefed<VRFrameData> Constructor(const GlobalObject& aGlobal,
+ ErrorResult& aRv);
+
+ void Update(const VRFrameInfo& aFrameInfo);
+
+ // WebIDL Members
+ double Timestamp() const;
+ void GetLeftProjectionMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void GetLeftViewMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void GetRightProjectionMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void GetRightViewMatrix(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+
+ VRPose* Pose();
+
+ // WebIDL Boilerplate
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ ~VRFrameData();
+ nsCOMPtr<nsISupports> mParent;
+
+ VRFrameInfo mFrameInfo;
+ RefPtr<VRPose> mPose;
+ JS::Heap<JSObject*> mLeftProjectionMatrix;
+ JS::Heap<JSObject*> mLeftViewMatrix;
+ JS::Heap<JSObject*> mRightProjectionMatrix;
+ JS::Heap<JSObject*> mRightViewMatrix;
+
+ void LazyCreateMatrix(JS::Heap<JSObject*>& aArray, gfx::Matrix4x4& aMat,
+ JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+};
+
+class VRStageParameters final : public nsWrapperCache
+{
+public:
+ VRStageParameters(nsISupports* aParent,
+ const gfx::Matrix4x4& aSittingToStandingTransform,
+ const gfx::Size& aSize);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VRStageParameters)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VRStageParameters)
+
+ void GetSittingToStandingTransform(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ float SizeX() const { return mSize.width; }
+ float SizeZ() const { return mSize.height; }
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+ ~VRStageParameters();
+
+ nsCOMPtr<nsISupports> mParent;
+
+ gfx::Matrix4x4 mSittingToStandingTransform;
+ JS::Heap<JSObject*> mSittingToStandingTransformArray;
+ gfx::Size mSize;
+};
+
+class VREyeParameters final : public nsWrapperCache
+{
+public:
+ VREyeParameters(nsISupports* aParent,
+ const gfx::Point3D& aEyeTranslation,
+ const gfx::VRFieldOfView& aFOV,
+ const gfx::IntSize& aRenderSize);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(VREyeParameters)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(VREyeParameters)
+
+ void GetOffset(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal,
+ ErrorResult& aRv);
+
+ VRFieldOfView* FieldOfView();
+
+ uint32_t RenderWidth() const { return mRenderSize.width; }
+ uint32_t RenderHeight() const { return mRenderSize.height; }
+
+ nsISupports* GetParentObject() const { return mParent; }
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+protected:
+ ~VREyeParameters();
+
+ nsCOMPtr<nsISupports> mParent;
+
+
+ gfx::Point3D mEyeTranslation;
+ gfx::IntSize mRenderSize;
+ JS::Heap<JSObject*> mOffset;
+ RefPtr<VRFieldOfView> mFOV;
+};
+
+class VRDisplay final : public DOMEventTargetHelper
+ , public nsIObserver
+{
+public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(VRDisplay, DOMEventTargetHelper)
+
+ virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+ bool IsPresenting() const;
+ bool IsConnected() const;
+
+ VRDisplayCapabilities* Capabilities();
+ VRStageParameters* GetStageParameters();
+
+ uint32_t DisplayId() const { return mDisplayId; }
+ void GetDisplayName(nsAString& aDisplayName) const { aDisplayName = mDisplayName; }
+
+ static bool RefreshVRDisplays(uint64_t aWindowId);
+ static void UpdateVRDisplays(nsTArray<RefPtr<VRDisplay> >& aDisplays,
+ nsPIDOMWindowInner* aWindow);
+
+ gfx::VRDisplayClient *GetClient() {
+ return mClient;
+ }
+
+ virtual already_AddRefed<VREyeParameters> GetEyeParameters(VREye aEye);
+
+ bool GetFrameData(VRFrameData& aFrameData);
+ already_AddRefed<VRPose> GetPose();
+ void ResetPose();
+
+ double DepthNear() {
+ return mDepthNear;
+ }
+
+ double DepthFar() {
+ return mDepthFar;
+ }
+
+ void SetDepthNear(double aDepthNear) {
+ // XXX When we start sending depth buffers to VRLayer's we will want
+ // to communicate this with the VRDisplayHost
+ mDepthNear = aDepthNear;
+ }
+
+ void SetDepthFar(double aDepthFar) {
+ // XXX When we start sending depth buffers to VRLayer's we will want
+ // to communicate this with the VRDisplayHost
+ mDepthFar = aDepthFar;
+ }
+
+ already_AddRefed<Promise> RequestPresent(const nsTArray<VRLayer>& aLayers, ErrorResult& aRv);
+ already_AddRefed<Promise> ExitPresent(ErrorResult& aRv);
+ void GetLayers(nsTArray<VRLayer>& result);
+ void SubmitFrame();
+
+ int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback,
+ mozilla::ErrorResult& aError);
+ void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError);
+
+protected:
+ VRDisplay(nsPIDOMWindowInner* aWindow, gfx::VRDisplayClient* aClient);
+ virtual ~VRDisplay();
+ virtual void LastRelease() override;
+
+ void ExitPresentInternal();
+ void UpdateFrameInfo();
+
+ RefPtr<gfx::VRDisplayClient> mClient;
+
+ uint32_t mDisplayId;
+ nsString mDisplayName;
+
+ RefPtr<VRDisplayCapabilities> mCapabilities;
+ RefPtr<VRStageParameters> mStageParameters;
+
+ double mDepthNear;
+ double mDepthFar;
+
+ RefPtr<gfx::VRDisplayPresentation> mPresentation;
+
+ /**
+ * The WebVR 1.1 spec Requires that VRDisplay.getPose and VRDisplay.getFrameData
+ * must return the same values until the next VRDisplay.submitFrame.
+ * mFrameInfo is updated only on the first call to either function within one
+ * frame. Subsequent calls before the next SubmitFrame or ExitPresent call
+ * will use these cached values.
+ */
+ VRFrameInfo mFrameInfo;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/vr/VREventObserver.cpp b/dom/vr/VREventObserver.cpp
new file mode 100644
index 000000000..1b6d1b978
--- /dev/null
+++ b/dom/vr/VREventObserver.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VREventObserver.h"
+
+#include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
+#include "VRManagerChild.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace gfx;
+
+/**
+ * This class is used by nsGlobalWindow to implement window.onvrdisplayconnected,
+ * window.onvrdisplaydisconnected, and window.onvrdisplaypresentchange.
+ */
+VREventObserver::VREventObserver(nsGlobalWindow* aGlobalWindow)
+ : mWindow(aGlobalWindow)
+{
+ MOZ_ASSERT(aGlobalWindow && aGlobalWindow->IsInnerWindow());
+
+ VRManagerChild* vmc = VRManagerChild::Get();
+ if (vmc) {
+ vmc->AddListener(this);
+ }
+}
+
+VREventObserver::~VREventObserver()
+{
+ VRManagerChild* vmc = VRManagerChild::Get();
+ if (vmc) {
+ vmc->RemoveListener(this);
+ }
+}
+
+void
+VREventObserver::NotifyVRDisplayConnect()
+{
+ /**
+ * We do not call nsGlobalWindow::NotifyActiveVRDisplaysChanged here, as we
+ * can assume that a newly enumerated display is not presenting WebVR
+ * content.
+ */
+ if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ mWindow->GetOuterWindow()->DispatchCustomEvent(
+ NS_LITERAL_STRING("vrdisplayconnected"));
+ }
+}
+
+void
+VREventObserver::NotifyVRDisplayDisconnect()
+{
+ if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+ mWindow->NotifyActiveVRDisplaysChanged();
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ mWindow->GetOuterWindow()->DispatchCustomEvent(
+ NS_LITERAL_STRING("vrdisplaydisconnected"));
+ }
+}
+
+void
+VREventObserver::NotifyVRDisplayPresentChange()
+{
+ if (mWindow->AsInner()->IsCurrentInnerWindow()) {
+ mWindow->NotifyActiveVRDisplaysChanged();
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ mWindow->GetOuterWindow()->DispatchCustomEvent(
+ NS_LITERAL_STRING("vrdisplaypresentchange"));
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/vr/VREventObserver.h b/dom/vr/VREventObserver.h
new file mode 100644
index 000000000..a30bb5960
--- /dev/null
+++ b/dom/vr/VREventObserver.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_VREventObserver_h
+#define mozilla_dom_VREventObserver_h
+
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace dom {
+
+class VREventObserver final
+{
+public:
+ ~VREventObserver();
+ explicit VREventObserver(nsGlobalWindow* aGlobalWindow);
+
+ void NotifyVRDisplayConnect();
+ void NotifyVRDisplayDisconnect();
+ void NotifyVRDisplayPresentChange();
+
+private:
+ // Weak pointer, instance is owned by mWindow.
+ nsGlobalWindow* MOZ_NON_OWNING_REF mWindow;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_VREventObserver_h
diff --git a/dom/vr/moz.build b/dom/vr/moz.build
new file mode 100644
index 000000000..a4aa8d69b
--- /dev/null
+++ b/dom/vr/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+ 'VRDisplay.h',
+ 'VREventObserver.h',
+ ]
+
+UNIFIED_SOURCES = [
+ 'VRDisplay.cpp',
+ 'VREventObserver.cpp',
+ ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+LOCAL_INCLUDES += [
+ '/dom/base'
+]